motion-profiler 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +91 -0
- data/Rakefile +13 -0
- data/app/app_delegate.rb +5 -0
- data/lib/motion-profiler.rb +9 -0
- data/lib/motion-profiler/helper.rb +14 -0
- data/lib/motion-profiler/profiler.rb +81 -0
- data/lib/motion-profiler/report.rb +71 -0
- data/motion-profiler.gemspec +16 -0
- data/spec/helpers/lion.rb +32 -0
- data/spec/main_spec.rb +16 -0
- metadata +78 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
|
data/app/app_delegate.rb
ADDED
@@ -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
|