motion-profiler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ coverage/*
6
+ .yardoc/*
7
+ doc/*
8
+ .repl_history
9
+ build
10
+ resources/*.nib
11
+ resources/*.momd
12
+ resources/*.storyboardc
13
+ *.swp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (C) 2013 by Marcus Gartner
2
+
3
+ Copyright (C) 2012 by Jimmy Cuadra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ [![Build Status](https://secure.travis-ci.org/change/method_profiler.png)](http://travis-ci.org/change/method_profiler) [![Code Climate](https://codeclimate.com/github/change/method_profiler.png)](https://codeclimate.com/github/change/method_profiler) [![endorse](http://api.coderwall.com/jimmycuadra/endorsecount.png)](http://coderwall.com/jimmycuadra)
2
+
3
+ # MethodProfiler
4
+
5
+ **MethodProfiler** collects performance information about the methods in your objects and creates reports to help you identify slow methods. The collected data can be sorted in various ways, converted into an array, or pretty printed as a table.
6
+
7
+ ## Basic usage
8
+
9
+ Create a new profiler by passing the object you want to profile to `MethodProfiler.observe`. All future class and instance methods called on your object will be recorded by the profiler. To see the results of the profiling as a table, simply print out the report returned by `#report` on the profiler object.
10
+
11
+ ```ruby
12
+ profiler = MethodProfiler.observe(MyClass)
13
+
14
+ MyClass.labore_voluptatum
15
+ MyClass.labore_voluptatum
16
+
17
+ my_obj = MyClass.new
18
+
19
+ my_obj.accusamus_est
20
+ my_obj.accusamus_est
21
+ my_obj.accusamus_est
22
+
23
+ puts profiler.report
24
+ ```
25
+
26
+ The resulting chart includes each method, the minimum time it took to run, the maximum time, the average across all calls, and the total number of times it was called. Class methods are prefixed by a `.` and instance methods are prefixed with a `#`.
27
+
28
+ ```
29
+ MethodProfiler results for: MyClass
30
+ +-----------------------+-----------+------------+--------------+------------+-------------+
31
+ | Method | Min Time | Max Time | Average Time | Total Time | Total Calls |
32
+ +-----------------------+-----------+------------+--------------+------------+-------------+
33
+ | #accusamus_est | 28.722 ms | 393.649 ms | 150.543 ms | 451.628 ms | 3 |
34
+ | #autem_iste! | 26.220 ms | 387.026 ms | 146.644 ms | 439.933 ms | 3 |
35
+ | #distinctio_eos | 26.095 ms | 386.903 ms | 146.520 ms | 439.559 ms | 3 |
36
+ | #laborum_fugit | 14.887 ms | 351.369 ms | 127.564 ms | 382.692 ms | 3 |
37
+ | #suscipit_architecto | 9.876 ms | 269.339 ms | 96.440 ms | 289.319 ms | 3 |
38
+ | #et_fugit | 0.005 ms | 63.101 ms | 10.704 ms | 64.225 ms | 6 |
39
+ | #porro_rerum | 2.970 ms | 15.137 ms | 7.126 ms | 21.378 ms | 3 |
40
+ | #provident_molestiae | 0.097 ms | 17.860 ms | 1.134 ms | 27.225 ms | 24 |
41
+ | #nisi_inventore | 0.098 ms | 15.076 ms | 1.044 ms | 54.272 ms | 52 |
42
+ | #quis_temporibus | 0.004 ms | 11.908 ms | 0.643 ms | 15.430 ms | 24 |
43
+ | .labore_voluptatum | 0.440 ms | 0.470 ms | 0.455 ms | 0.910 ms | 2 |
44
+ | #quia_est | 0.004 ms | 11.133 ms | 0.453 ms | 47.092 ms | 104 |
45
+ | #ut_reiciendis | 0.004 ms | 5.626 ms | 0.346 ms | 8.302 ms | 24 |
46
+ | #sint_quasi | 0.062 ms | 2.152 ms | 0.188 ms | 4.504 ms | 24 |
47
+ | #sed_at | 0.065 ms | 0.150 ms | 0.085 ms | 2.034 ms | 24 |
48
+ | #repellendus_suscipit | 0.051 ms | 0.122 ms | 0.070 ms | 1.684 ms | 24 |
49
+ | .quas_nesciunt | 0.058 ms | 0.124 ms | 0.062 ms | 4.303 ms | 69 |
50
+ | #iure_quis | 0.021 ms | 0.025 ms | 0.023 ms | 0.069 ms | 3 |
51
+ | #dicta_ipsam | 0.006 ms | 0.266 ms | 0.017 ms | 0.798 ms | 48 |
52
+ | #perspiciatis_aut | 0.004 ms | 0.068 ms | 0.013 ms | 0.314 ms | 24 |
53
+ | .aperiam_laborum | 0.005 ms | 0.015 ms | 0.006 ms | 0.438 ms | 69 |
54
+ | #voluptas_ratione | 0.005 ms | 0.007 ms | 0.006 ms | 0.018 ms | 3 |
55
+ | #ex_voluptas | 0.004 ms | 0.010 ms | 0.005 ms | 0.212 ms | 41 |
56
+ +-----------------------+-----------+------------+--------------+------------+-------------+
57
+ ```
58
+
59
+ ## Reporting
60
+
61
+ `MethodProfiler::Profiler#report` actually returns a report object which can be used to sort and display the data in various ways. A report has chainable `#sort_by` and `#order` methods to control the sorting of the report when it is ultimately displayed. The report can be turned into an array by calling `#to_a` and the table shown above by calling `#to_s`.
62
+
63
+ *Example of sorting by the number of total calls, ascending:*
64
+
65
+ ```ruby
66
+ puts profiler.report.sort_by(:total_calls).order(:ascending)
67
+ ```
68
+
69
+ `#sort_by` accepts a symbol or string with the name of any of the columns in the table: `:method`, `:min`, `:max`, `:average`, `:total_time`, or `:total_calls`.
70
+
71
+ `#order` accepts a symbol or string of `:ascending` or `:descending`. These can also be abbreviated with `:asc` and `:desc`.
72
+
73
+ ## Documentation
74
+
75
+ The public API is fully documented using [YARD](http://yardoc.org/) and can be viewed on [RubyDoc.info](http://rubydoc.info/).
76
+
77
+ ## Tests
78
+
79
+ All code is tested with [RSpec](https://github.com/rspec/rspec). To run the specs, clone the repository, install the dependencies with `bundle install`, and then run `rake`.
80
+
81
+ ## Issues
82
+
83
+ If you have any problems or suggestions for the project, please open a GitHub issue.
84
+
85
+ ## License
86
+
87
+ MethodProfiler is available under the included MIT license.
88
+
89
+ ## Acknowledgements
90
+
91
+ Thank you to [Change.org](http://www.change.org/) for sponsoring the project and to my coworker [Alain Bloch](https://github.com/alainbloch) for the inspiration.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ $:.unshift("/Library/RubyMotion/lib")
2
+ require 'motion/project'
3
+ require 'bundler/gem_tasks'
4
+ require 'bundler/setup'
5
+
6
+ $:.unshift("./lib/")
7
+ require './lib/motion-profiler.rb'
8
+
9
+ require 'motion-colorize'
10
+
11
+ Motion::Project::App.setup do |app|
12
+ app.name = 'velocity'
13
+ end
@@ -0,0 +1,5 @@
1
+ class AppDelegate
2
+ def application(application, didFinishLaunchingWithOptions:launchOptions)
3
+ true
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "This file must be required within a RubyMotion project Rakefile."
3
+ end
4
+
5
+ Motion::Project::App.setup do |app|
6
+ Dir.glob(File.join(File.dirname(__FILE__), 'motion-profiler/*.rb')).each do |file|
7
+ app.files.unshift(file)
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ module MotionProfiler
2
+
3
+ # Contains convenience methods.
4
+ module Helper
5
+
6
+ # A convenience method for observing a class with a profiler.
7
+ # The same as calling `MotionProfiler::Profiler.new(obj)`.
8
+ def observe(obj)
9
+ Profiler.new(obj)
10
+ end
11
+ end
12
+
13
+ extend Helper
14
+ end
@@ -0,0 +1,81 @@
1
+ module MotionProfiler
2
+
3
+ # Velocity::Profiler collects execution time of method calls of an object.
4
+ class Profiler
5
+
6
+ # Initializes a new Profiler to observe the given object.
7
+ def initialize(obj)
8
+ @obj = obj
9
+ @data = Hash.new { |h, k| h[k] = [] }
10
+
11
+ wrap_methods_with_profiling(self)
12
+ end
13
+
14
+ # Returns a report of the profile data collected on the object.
15
+ def report
16
+ Report.new(final_data, @obj.name)
17
+ end
18
+
19
+ private
20
+
21
+ # Wraps all the methods of @obj to calculate exectuion time.
22
+ def wrap_methods_with_profiling(profiler)
23
+ [
24
+ { object: @obj.singleton_class, methods: @obj.methods(false), private: false, singleton: true },
25
+ { object: @obj, methods: @obj.instance_methods(false), private: false },
26
+ { object: @obj, methods: @obj.private_instance_methods(false), private: true }
27
+ ].each do |group|
28
+ group[:object].module_eval do
29
+ group[:methods].each do |method|
30
+ define_method("#{method}_with_profiling") do |*args, &block|
31
+ profiler.send(:profile, method, singleton: group[:singleton]) do
32
+ send("#{method}_without_profiling", *args, &block)
33
+ end
34
+ end
35
+
36
+ alias_method "#{method}_without_profiling", method
37
+ alias_method method, "#{method}_with_profiling"
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ # Profiles the block and records the elapsed time.
44
+ def profile(method, options = {}, &block)
45
+ method_name = options[:singleton] ? ".#{method}" : "##{method}"
46
+ elapsed_time, result = benchmark(block)
47
+ @data[method_name] << elapsed_time
48
+ result
49
+ end
50
+
51
+ # Returns the elapsed time taken for block_to_benchmark.
52
+ def benchmark(block_to_benchmark)
53
+ result = nil
54
+ t0 = Time.now
55
+ result = block_to_benchmark.call
56
+ return Time.now - t0, result
57
+ end
58
+
59
+ # Compiles the collected data into several useful catagories.
60
+ def final_data
61
+ results = []
62
+
63
+ @data.each do |method, records|
64
+ total_calls = records.size
65
+ total_time = records.reduce(:+)
66
+ average = total_time / total_calls
67
+ results << {
68
+ method: method,
69
+ min: records.min,
70
+ max: records.max,
71
+ average: average,
72
+ total_time: total_time,
73
+ total_calls: total_calls,
74
+ }
75
+ end
76
+
77
+ results
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,71 @@
1
+ # Add a convenience method to float for coverting from seconds to
2
+ # milliseconds.
3
+ class Float
4
+ def to_ms
5
+ "%.3f ms" % (self * 1000)
6
+ end
7
+ end
8
+
9
+ module MotionProfiler
10
+
11
+ # Sorts and displays data collected by a Profiler.
12
+ class Report
13
+
14
+ # Fields that can be passed to #sort_by.
15
+ FIELDS = [:method, :min, :max, :average, :total_time, :total_calls]
16
+
17
+ # Directions that can be passed to #order.
18
+ DIRECTIONS = [:asc, :desc,]
19
+
20
+ # Initializes a new Report.
21
+ def initialize(data, name)
22
+ @data = data
23
+ @name = name
24
+ @sort_by = :average
25
+ @order = :descending
26
+ end
27
+
28
+ # Sorts the report by the given field. Defaults to `:average`.
29
+ # Chainable with #order.
30
+ def sort_by(field)
31
+ field = field.to_sym
32
+ field = :average unless FIELDS.include?(field)
33
+ @sort_by = field
34
+ self
35
+ end
36
+
37
+ # Changes the direction of the sort. Defaults to `:desc`.
38
+ # Chainable with #sort_by.
39
+ def order(direction)
40
+ direction = direction.to_sym
41
+ direction = :desc unless DIRECTIONS.include?(direction)
42
+ @order = direction
43
+ self
44
+ end
45
+
46
+ # Sorts the data by order and sort_by and returns an array.
47
+ def to_a
48
+ if @order == :asc
49
+ @data.sort { |a, b| a[@sort_by] <=> b[@sort_by] }
50
+ else
51
+ @data.sort { |a, b| b[@sort_by] <=> a[@sort_by] }
52
+ end
53
+ end
54
+
55
+ # Returns a printable string of the sorted data.
56
+ def to_s
57
+ string = "\nVelocity results for: #{@name}\n".light_blue
58
+ self.to_a.each do |method_data|
59
+ string += "-- #{method_data.shift[1]}\n".red
60
+ method_data.each_pair do |metric, time|
61
+ if time.is_a?(Float)
62
+ time = time.to_ms
63
+ end
64
+ string += " #{metric}: #{time}\n"
65
+ end
66
+ end
67
+ return string
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "motion-profiler"
3
+ s.version = "0.0.1"
4
+ s.authors = ["Marcus Garter"]
5
+ s.email = ["magartner@gmail.com"]
6
+ s.homepage = "https://github.com/mgarter/motion-profiler"
7
+ s.summary = %q{A profiler for RubyMotion.}
8
+ s.description = %q{A profiler for RubyMotion based on method_profiler.}
9
+
10
+ s.files = `git ls-files`.split("\n")
11
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
12
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
+ s.require_paths = ["lib"]
14
+
15
+ s.add_dependency 'motion-colorize'
16
+ end
@@ -0,0 +1,32 @@
1
+ # Helper class used for testing.
2
+ class Lion
3
+
4
+ # TODO: Figure out why this breaks the profiler.
5
+ # def initialize(name)
6
+ # @name = name
7
+ # end
8
+
9
+ def zzz(sec = 1)
10
+ sleep(sec)
11
+ end
12
+
13
+ def eat(n = 100)
14
+ privately do
15
+ 100.times do
16
+ rand + rand
17
+ end
18
+ end
19
+ end
20
+
21
+ def hunt(n = 100)
22
+ n.times do
23
+ rand ** rand ** rand
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def privately(&block)
30
+ block.call
31
+ end
32
+ end
data/spec/main_spec.rb ADDED
@@ -0,0 +1,16 @@
1
+ describe "Velocity" do
2
+ before do
3
+ @app = UIApplication.sharedApplication
4
+ end
5
+ it "should profile the class" do
6
+ profiler = MotionProfiler.observe(Lion)
7
+
8
+ lion = Lion.new
9
+ lion.hunt
10
+ lion.eat
11
+ lion.zzz
12
+
13
+ puts profiler.report
14
+ true.should.be == true
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motion-profiler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Marcus Garter
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: motion-colorize
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: A profiler for RubyMotion based on method_profiler.
31
+ email:
32
+ - magartner@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - .rspec
39
+ - .travis.yml
40
+ - Gemfile
41
+ - LICENSE
42
+ - README.md
43
+ - Rakefile
44
+ - app/app_delegate.rb
45
+ - lib/motion-profiler.rb
46
+ - lib/motion-profiler/helper.rb
47
+ - lib/motion-profiler/profiler.rb
48
+ - lib/motion-profiler/report.rb
49
+ - motion-profiler.gemspec
50
+ - spec/helpers/lion.rb
51
+ - spec/main_spec.rb
52
+ homepage: https://github.com/mgarter/motion-profiler
53
+ licenses: []
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.24
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: A profiler for RubyMotion.
76
+ test_files:
77
+ - spec/helpers/lion.rb
78
+ - spec/main_spec.rb