Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Separate profiling and reporting into two classes.

  • Loading branch information...
commit 97a54fafa1a6a5b571646965521b2e9f7c340e30 1 parent f48a7b6
@jimmycuadra jimmycuadra authored
View
143 lib/method_profiler.rb
@@ -1,142 +1,9 @@
-require 'benchmark'
-require 'hirb'
+require 'method_profiler/profiler'
-class MethodProfiler
- attr_reader :observed_singleton_methods, :observed_instance_methods, :data
+module MethodProfiler
+ extend self
- def initialize(obj)
- @obj = obj
- @observed_singleton_methods = find_object_methods(obj.singleton_class, true)
- @observed_instance_methods = find_object_methods(obj)
- initialize_data
- wrap_methods_with_profiling
- end
-
- def profile(method, singleton = false, &block)
- method_name = singleton ? ".#{method}" : "##{method}"
- result = nil
- benchmark = Benchmark.measure { result = block.call }
- elapsed_time = benchmark.to_s.match(/\(\s*([^\)]+)\)/)[1].to_f
- @data[method_name] << elapsed_time
- result
- end
-
- def report(options = {})
- normalize_options!(options)
- [
- "MethodProfiler results for: #{@obj}",
- Hirb::Helpers::Table.render(
- final_data(options),
- headers: {
- method: "Method",
- min: "Min Time",
- max: "Max Time",
- average: "Average Time",
- total_calls: "Total Calls"
- },
- fields: [:method, :min, :max, :average, :total_calls],
- description: false
- )
- ].join("\n")
- end
-
- def reset!
- initialize_data
- @final_data = nil
- end
-
- private
-
- def initialize_data
- @data = Hash.new { |h, k| h[k] = [] }
- end
-
- def find_object_methods(obj, singleton = false)
- obj.instance_methods - obj.ancestors.map do |a|
- if a == obj
- []
- else
- if singleton
- a.singleton_class.instance_methods
- else
- a.instance_methods
- end
- end
- end.flatten
- end
-
- def wrap_methods_with_profiling
- profiler = self
- osm = observed_singleton_methods
- oim = observed_instance_methods
-
- @obj.singleton_class.module_eval do
- osm.each do |method|
- define_method("#{method}_with_profiling") do |*args|
- profiler.profile(method, true) { send("#{method}_without_profiling", *args) }
- end
-
- alias_method "#{method}_without_profiling", method
- alias_method method, "#{method}_with_profiling"
- end
- end
-
- @obj.module_eval do
- oim.each do |method|
- define_method("#{method}_with_profiling") do |*args|
- profiler.profile(method) { send("#{method}_without_profiling", *args) }
- end
-
- alias_method "#{method}_without_profiling", method
- alias_method method, "#{method}_with_profiling"
- end
- end
- end
-
- def normalize_options!(options)
- options[:sort_by] ||= :average
-
- options[:order] ||= if options[:sort_by] == :method
- :ascending
- else
- :descending
- end
-
- options[:order] = :ascending if options[:order] == :asc
- options[:order] = :descending if options[:order] == :desc
-
- options
- end
-
- def final_data(options)
- final_data = []
-
- data.each do |method, records|
- total_calls = records.size
- average = records.reduce(:+) / total_calls
- final_data << {
- method: method,
- min: records.min,
- max: records.max,
- average: average,
- total_calls: total_calls
- }
- end
-
- if options[:order] == :ascending
- final_data.sort! { |a, b| a[options[:sort_by]] <=> b[options[:sort_by]] }
- else
- final_data.sort! { |a, b| b[options[:sort_by]] <=> a[options[:sort_by]] }
- end
-
- final_data.each do |record|
- [:min, :max, :average].each { |k| record[k] = to_ms(record[k]) }
- end
-
- final_data
- end
-
- def to_ms(seconds)
- "%.3f ms" % (seconds * 1000)
+ def observe(obj)
+ Profiler.new(obj)
end
end
View
7 lib/method_profiler/hirb.rb
@@ -0,0 +1,7 @@
+require 'hirb'
+
+module Hirb::Helpers::Table::Filters
+ def to_milliseconds(seconds)
+ "%.3f ms" % (seconds * 1000)
+ end
+end
View
98 lib/method_profiler/profiler.rb
@@ -0,0 +1,98 @@
+require 'method_profiler/report'
+
+require 'benchmark'
+
+module MethodProfiler
+ class Profiler
+ attr_reader :observed_singleton_methods, :observed_instance_methods, :data
+
+ def initialize(obj)
+ @obj = obj
+ @observed_singleton_methods = find_object_methods(obj.singleton_class, true)
+ @observed_instance_methods = find_object_methods(obj)
+
+ reset!
+ wrap_methods_with_profiling
+ end
+
+ def profile(method, singleton = false, &block)
+ method_name = singleton ? ".#{method}" : "##{method}"
+ result = nil
+ benchmark = Benchmark.measure { result = block.call }
+ elapsed_time = benchmark.to_s.match(/\(\s*([^\)]+)\)/)[1].to_f
+ @data[method_name] << elapsed_time
+ result
+ end
+
+ def report
+ Report.new(final_data)
+ end
+
+ def reset!
+ @data = Hash.new { |h, k| h[k] = [] }
+ end
+
+ private
+
+ def find_object_methods(obj, singleton = false)
+ obj.instance_methods - obj.ancestors.map do |a|
+ if a == obj
+ []
+ else
+ if singleton
+ a.singleton_class.instance_methods
+ else
+ a.instance_methods
+ end
+ end
+ end.flatten
+ end
+
+ def wrap_methods_with_profiling
+ profiler = self
+ osm = observed_singleton_methods
+ oim = observed_instance_methods
+
+ @obj.singleton_class.module_eval do
+ osm.each do |method|
+ define_method("#{method}_with_profiling") do |*args|
+ profiler.profile(method, true) { send("#{method}_without_profiling", *args) }
+ end
+
+ alias_method "#{method}_without_profiling", method
+ alias_method method, "#{method}_with_profiling"
+ end
+ end
+
+ @obj.module_eval do
+ oim.each do |method|
+ define_method("#{method}_with_profiling") do |*args|
+ profiler.profile(method) { send("#{method}_without_profiling", *args) }
+ end
+
+ alias_method "#{method}_without_profiling", method
+ alias_method method, "#{method}_with_profiling"
+ end
+ end
+ end
+
+ def final_data(options)
+ results = []
+
+ data.each do |method, records|
+ total_calls = records.size
+ average = records.reduce(:+) / total_calls
+ results << {
+ method: method,
+ min: records.min,
+ max: records.max,
+ average: average,
+ total_calls: total_calls
+ }
+ end
+
+ results
+ end
+
+ end
+end
View
62 lib/method_profiler/report.rb
@@ -0,0 +1,62 @@
+require 'hirb'
+require 'method_profiler/hirb'
+
+module MethodProfiler
+ class Report
+ FIELDS = [:method, :min, :max, :average, :total_calls]
+ DIRECTIONS = [:asc, :ascending, :desc, :descending]
+
+ def initialize(data)
+ @data = data
+ @sort_by = :average
+ @order = :descending
+ end
+
+ def sort_by(field = nil)
+ field = :average unless FIELDS.include?(field)
+
+ @sort_by = field
+ end
+
+ def order(direction = nil)
+ direction = :descending unless DIRECTIONS.include?(direction)
+ direction = :descending if direction == :desc
+ direction = :ascending if direction == :asc
+
+ @order = direction
+ end
+
+ def to_s
+ [
+ "MethodProfiler results for: #{@obj}",
+ Hirb::Helpers::Table.render(
+ sorted_data,
+ headers: {
+ method: "Method",
+ min: "Min Time",
+ max: "Max Time",
+ average: "Average Time",
+ total_calls: "Total Calls"
+ },
+ fields: [:method, :min, :max, :average, :total_calls],
+ filter_classes: {
+ min: :to_milliseconds,
+ max: :to_milliseconds,
+ average: :to_milliseconds
+ },
+ description: false
+ )
+ ].join("\n")
+ end
+
+ private
+
+ def sorted_data
+ if @order == :ascending
+ @data.sort { |a, b| a[@sort_by] <=> b[@sort_by] }
+ else
+ @data.sort { |a, b| b[@sort_by] <=> a[@sort_by] }
+ end
+ end
+ end
+end
View
29 spec/method_profiler/report_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe MethodProfiler::Report do
+ xit "outputs a string of report data" do
+ profiler.report.should be_an_instance_of(String)
+ end
+
+ xit "outputs one line for each method that was called" do
+ @petition.class.hay
+ @petition.class.guys
+ @petition.foo
+ @petition.bar
+ @petition.baz
+ profiler.report.scan(/.hay/).size.should == 1
+ profiler.report.scan(/.guys/).size.should == 1
+ profiler.report.scan(/#foo/).size.should == 1
+ profiler.report.scan(/#bar/).size.should == 1
+ profiler.report.scan(/#baz/).size.should == 1
+ end
+
+ xit "combines multiple calls to the same method into one line" do
+ @petition.class.hay
+ @petition.class.hay
+ @petition.foo
+ @petition.foo
+ profiler.report.scan(/.hay/).size.should == 1
+ profiler.report.scan(/#foo/).size.should == 1
+ end
+end
View
30 spec/method_profiler_spec.rb
@@ -6,10 +6,6 @@
before { @petition = Petition.new }
after { profiler.reset! }
- it "can be instantiated with an object to observe" do
- profiler.should be_true
- end
-
it "finds all the object's instance methods" do
profiler.observed_singleton_methods.sort.should == [:hay, :guys].sort
profiler.observed_instance_methods.sort.should == [:foo, :bar, :baz].sort
@@ -40,30 +36,8 @@
end
describe "#report" do
- it "outputs a string of report data" do
- profiler.report.should be_an_instance_of(String)
- end
-
- it "outputs one line for each method that was called" do
- @petition.class.hay
- @petition.class.guys
- @petition.foo
- @petition.bar
- @petition.baz
- profiler.report.scan(/.hay/).size.should == 1
- profiler.report.scan(/.guys/).size.should == 1
- profiler.report.scan(/#foo/).size.should == 1
- profiler.report.scan(/#bar/).size.should == 1
- profiler.report.scan(/#baz/).size.should == 1
- end
-
- it "combines multiple calls to the same method into one line" do
- @petition.class.hay
- @petition.class.hay
- @petition.foo
- @petition.foo
- profiler.report.scan(/.hay/).size.should == 1
- profiler.report.scan(/#foo/).size.should == 1
+ it "returns a new Report object" do
+ profiler.report.should be_an_instance_of MethodProfiler::Report
end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.