frequent 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +153 -0
- data/Rakefile +9 -0
- data/frequent.gemspec +19 -0
- data/lib/frequent.rb +147 -0
- data/spec/frequent_spec.rb +221 -0
- data/spec/spec_helper.rb +6 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2012 Ben Weintraub
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# Frequent
|
2
|
+
|
3
|
+
Frequent is a little Ruby metaprogramming demo gem that can keep track of what's
|
4
|
+
happening during your Ruby program's execution - specifically, how many times a
|
5
|
+
targeted method is called.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
To use frequent, install the gem, then `require 'frequent'` in one of your source
|
10
|
+
files and set the `COUNT_CALLS_TO` environment variable to the name of the
|
11
|
+
method you'd like to count calls to. Like this:
|
12
|
+
|
13
|
+
```
|
14
|
+
require 'frequent'
|
15
|
+
|
16
|
+
100.times do
|
17
|
+
puts "hello, frequent".split(',').inspect
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
```
|
22
|
+
$ COUNT_CALLS_TO='String#split' ruby test.rb
|
23
|
+
...
|
24
|
+
String#split called 100 times
|
25
|
+
```
|
26
|
+
|
27
|
+
Following Ruby conventions, instance methods are identified with a hash
|
28
|
+
(e.g. `String#split`), and class methods are identified with a period
|
29
|
+
(e.g. `File.join`).
|
30
|
+
|
31
|
+
### API
|
32
|
+
|
33
|
+
You can also use the `Frequent` module to manually place probes in your code:
|
34
|
+
|
35
|
+
```
|
36
|
+
probe = Frequent.instrument('MyClass#instance_method')
|
37
|
+
...
|
38
|
+
probe.calls # number of calls to target since instrumentation
|
39
|
+
```
|
40
|
+
|
41
|
+
If you're only interested in capturing calls during a specific part of your
|
42
|
+
code's execution, you can pass a block to `instrument`, during which
|
43
|
+
instrumentation will be enabled:
|
44
|
+
|
45
|
+
```
|
46
|
+
probe = Frequent.instrument('MyClass.method') do
|
47
|
+
...
|
48
|
+
end
|
49
|
+
probe.calls # number of calls to target that happened in the block
|
50
|
+
```
|
51
|
+
|
52
|
+
Your targetd class/module and method need not be defined yet at the time of
|
53
|
+
instrumentation, but see the performance section below for notes about the
|
54
|
+
performance implementations of instrumenting not-yet-defined targets.
|
55
|
+
|
56
|
+
## Internals
|
57
|
+
|
58
|
+
Frequent works by overwriting the original implementation of the targeted method
|
59
|
+
with an instrumented version that keeps a call count, using Ruby's `class_eval`,
|
60
|
+
`alias_method`, and `define_method` facilities. The original version of the
|
61
|
+
instrumented method is saved so that it can be optionally restored after
|
62
|
+
instrumentation.
|
63
|
+
|
64
|
+
## Performance
|
65
|
+
|
66
|
+
Frequent is relatively low-overhead, but there are a few things to keep in mind
|
67
|
+
regarding performance:
|
68
|
+
|
69
|
+
1. You'll get better performance if you place your probes *after* your target
|
70
|
+
method has been defined. Frequent will attempt to place your probe immediately
|
71
|
+
upon calls to `Frequent.instrument`, but if it ever encounters a probe that it
|
72
|
+
cannot place yet due to a missing host class/module or method, it will make
|
73
|
+
use of the `method_added`, `singleton_method_added`, and `included` hooks in
|
74
|
+
Ruby.
|
75
|
+
|
76
|
+
These hooks will cause a bit of code to execute each time a new method is
|
77
|
+
added to a Ruby module in your process, and each time a module is included
|
78
|
+
in a new class. Frequent uses these hooks so that it has a chance to place
|
79
|
+
instrumentation as soon as your targeted class/module and method become
|
80
|
+
available.
|
81
|
+
|
82
|
+
Most Ruby processes don't add many additional methods or create additional
|
83
|
+
classes after an initial start-up sequence, meaning that the performance
|
84
|
+
impact of using these hooks should be generally limited to a slightly higher
|
85
|
+
fixed start-up cost for your process.
|
86
|
+
|
87
|
+
2. Calls to instrumented methods will be slower than calls to uninstrumented
|
88
|
+
methods. Only methods that are actually instrumented will be subject to this
|
89
|
+
added overhead.
|
90
|
+
|
91
|
+
The degree to which this affects your program's performance in practice will
|
92
|
+
depend heavily on how hot your instrumented method is.
|
93
|
+
Instrumentation of a method called in a tight loop many times during your
|
94
|
+
program's execution will be more expensive overall than instrumentation of a
|
95
|
+
method only called a few times during the life of your program.
|
96
|
+
|
97
|
+
The benchmark in `spec/frequent_spec.rb` (run with `bundle exec rake test BENCH=1`)
|
98
|
+
attempts to measure the overhead incurred by instrumentation of a method with
|
99
|
+
Frequent. The benchmark compares times to call empty instrumented and
|
100
|
+
uninstrumented methods (both class and instance methods).
|
101
|
+
|
102
|
+
When interpreting the results, keep in mind that most methods worth
|
103
|
+
instrumenting are not empty -- that is, they do some non-trivial work that will
|
104
|
+
help amortize the per-call overhead.
|
105
|
+
|
106
|
+
Benchmark results will vary from machine to machine (and between different Ruby
|
107
|
+
implementations), but on the author's machine, the overhead introduced by
|
108
|
+
instrumenting a method is ~0.5 μs / call. This represents a slowdown of about
|
109
|
+
2.5x - 7x for a completely empty method call (depending on whether a class or
|
110
|
+
instance method is being instrumented).
|
111
|
+
|
112
|
+
## Caveats
|
113
|
+
|
114
|
+
### method_missing
|
115
|
+
|
116
|
+
In Ruby, it's fairly common to encounter methods that aren't explicitly defined,
|
117
|
+
but are instead dynamically implemented using Ruby's `method_missing` facility.
|
118
|
+
Frequent will not work for methods defined in this way.
|
119
|
+
|
120
|
+
The main challenge in dealing with methods defined through `method_missing` is
|
121
|
+
knowing when to place instrumentation. In order for instrumentation of
|
122
|
+
`method_missing` methods to work, probes must be placed immediately upon
|
123
|
+
creation of the targeted class or module - `method_added` is of no use here,
|
124
|
+
since the target method is never actually added.
|
125
|
+
|
126
|
+
There's unfortunately no `const_added` hook available in Ruby (though there was
|
127
|
+
discussion on the mailing list of adding one a while ago), and the author has
|
128
|
+
not found any alternatives for detecting module / class creation that he finds
|
129
|
+
sufficiently robust, simple, and performant.
|
130
|
+
|
131
|
+
Alternatives include using `set_trace_func` temporarily until the target class
|
132
|
+
or method has been created and then disabling it (slow and complex) and looking
|
133
|
+
for placeable probes after each `require` or `load` (doesn't work for
|
134
|
+
dynamically-created classes).
|
135
|
+
|
136
|
+
### Metaprogramming
|
137
|
+
|
138
|
+
Frequent isn't magic - it relies on the same hooks and facilities that are made
|
139
|
+
available by the Ruby interpreter to any code in your program. That means it's
|
140
|
+
probably possible to break or trick it in many ways.
|
141
|
+
|
142
|
+
### Instrumenting the same target multiple times
|
143
|
+
|
144
|
+
This won't work, and should probably raise an exception, but currently just
|
145
|
+
fails.
|
146
|
+
|
147
|
+
## License
|
148
|
+
|
149
|
+
MIT license. See LICENSE for details.
|
150
|
+
|
151
|
+
## Author
|
152
|
+
|
153
|
+
Ben Weintraub - benweint@gmail.com
|
data/Rakefile
ADDED
data/frequent.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ["Ben Weintraub"]
|
5
|
+
gem.email = ["benweint@gmail.com"]
|
6
|
+
gem.description = %q{Ruby method instrumentation demo}
|
7
|
+
gem.summary = %q{Ruby method instrumentation demo}
|
8
|
+
gem.homepage = ""
|
9
|
+
|
10
|
+
gem.files = `git ls-files`.split($\)
|
11
|
+
gem.test_files = gem.files.grep(%r{^(spec)/})
|
12
|
+
gem.name = "frequent"
|
13
|
+
gem.require_paths = ["lib"]
|
14
|
+
gem.version = '0.1'
|
15
|
+
|
16
|
+
gem.add_development_dependency('rake')
|
17
|
+
gem.add_development_dependency('minitest')
|
18
|
+
gem.add_development_dependency('minitest-matchers')
|
19
|
+
end
|
data/lib/frequent.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
module Frequent
|
2
|
+
VERSION = 0.1
|
3
|
+
|
4
|
+
ProbeNameError = Class.new(StandardError)
|
5
|
+
|
6
|
+
def self.instrument(name)
|
7
|
+
probe = Frequent::Probe.new(name)
|
8
|
+
probes[name] = probe
|
9
|
+
Frequent::Deferred.enable! unless probe.enabled?
|
10
|
+
if block_given?
|
11
|
+
yield
|
12
|
+
probe.disable!
|
13
|
+
probes.delete(name)
|
14
|
+
end
|
15
|
+
probe
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.constantize(name)
|
19
|
+
names = name.split('::')
|
20
|
+
names.shift if names.first == ''
|
21
|
+
constant = Object
|
22
|
+
until names.empty?
|
23
|
+
n = names.shift
|
24
|
+
return nil unless constant.const_defined?(n)
|
25
|
+
constant = constant.const_get(n, false)
|
26
|
+
end
|
27
|
+
constant
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.probes
|
31
|
+
@probes ||= {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module Frequent
|
36
|
+
class Probe
|
37
|
+
attr_reader :name, :calls, :class_name, :method_name, :original_implementation
|
38
|
+
alias_method :to_s, :name
|
39
|
+
|
40
|
+
def initialize(name)
|
41
|
+
@calls = 0
|
42
|
+
@name = name
|
43
|
+
@enabled = false
|
44
|
+
parse_name(name)
|
45
|
+
enable! if ready?
|
46
|
+
end
|
47
|
+
|
48
|
+
def increment
|
49
|
+
@calls += 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def enabled?
|
53
|
+
@enabled
|
54
|
+
end
|
55
|
+
|
56
|
+
def ready?
|
57
|
+
!enabled? && target_defined?
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_owner
|
61
|
+
owner = Frequent.constantize(class_name)
|
62
|
+
return unless owner
|
63
|
+
@type == :instance ? owner : owner.singleton_class
|
64
|
+
end
|
65
|
+
|
66
|
+
def target_defined?
|
67
|
+
owner = method_owner
|
68
|
+
owner && (
|
69
|
+
owner.method_defined?(method_name) ||
|
70
|
+
owner.private_instance_methods.include?(method_name)
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def enable!
|
75
|
+
unless @enabling
|
76
|
+
@enabling = true
|
77
|
+
@original_implementation = method_owner.instance_method(method_name)
|
78
|
+
probe = self
|
79
|
+
aliased_name = self.aliased_name
|
80
|
+
method_owner.class_eval do
|
81
|
+
alias_method aliased_name, probe.method_name
|
82
|
+
define_method(probe.method_name) do |*args, &blk|
|
83
|
+
probe.increment
|
84
|
+
send(aliased_name, *args, &blk)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
@enabled = true
|
88
|
+
@enabling = false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def disable!
|
93
|
+
if enabled?
|
94
|
+
probe = self
|
95
|
+
method_owner.class_eval do
|
96
|
+
define_method(probe.method_name, probe.original_implementation)
|
97
|
+
remove_method(probe.aliased_name)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def aliased_name
|
103
|
+
"__frequent_original_#{method_name}".to_sym
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_name(name)
|
107
|
+
md = name.match(/(.*)(\.|\#)(.*)/)
|
108
|
+
raise ProbeNameError.new("Failed to parse probe name '#{name}'") unless md
|
109
|
+
class_name, sep, method_name = md[1..3]
|
110
|
+
@class_name = class_name
|
111
|
+
@type = (sep == '#') ? :instance : :class
|
112
|
+
@method_name = method_name.to_sym
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
module Frequent
|
118
|
+
module Deferred
|
119
|
+
def self.place_by_name(name)
|
120
|
+
p = Frequent.probes[name]
|
121
|
+
p.enable! if p && p.ready?
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.enable!
|
125
|
+
return if @enabled
|
126
|
+
::Module.class_eval do
|
127
|
+
def method_added(m)
|
128
|
+
Frequent::Deferred.place_by_name("#{self}##{m}")
|
129
|
+
end
|
130
|
+
|
131
|
+
def singleton_method_added(m)
|
132
|
+
Frequent::Deferred.place_by_name("#{self}.#{m}")
|
133
|
+
end
|
134
|
+
|
135
|
+
def included(host)
|
136
|
+
Frequent.probes.values.select(&:ready?).each(&:enable!)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
@enabled = true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
if ENV['COUNT_CALLS_TO']
|
145
|
+
probe = Frequent.instrument(ENV['COUNT_CALLS_TO'])
|
146
|
+
at_exit { puts "#{probe} called #{probe.calls} times" }
|
147
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Frequent do
|
4
|
+
before :each do
|
5
|
+
class Potato
|
6
|
+
def instance_method; end
|
7
|
+
def block_method(x); yield x; end
|
8
|
+
def self.class_method(*args); end
|
9
|
+
|
10
|
+
def self.recursive_class_method(n)
|
11
|
+
return if n == 1
|
12
|
+
recursive_class_method(n-1)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.respond_to?(method)
|
16
|
+
method.to_s == 'class_missing_method' ? true : super
|
17
|
+
end
|
18
|
+
|
19
|
+
def overridden_method; end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def protected_instance_method; end
|
23
|
+
|
24
|
+
private
|
25
|
+
def private_instance_method; end
|
26
|
+
end
|
27
|
+
|
28
|
+
class RedPotato < Potato
|
29
|
+
def overridden_method; end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'probe name parsing' do
|
34
|
+
it 'should raise ProbeNameError on invalid probe name' do
|
35
|
+
proc { Frequent.instrument("Lava$monster") }.must_raise(Frequent::ProbeNameError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'instance method instrumentation' do
|
40
|
+
it 'should support simple method call counting' do
|
41
|
+
p = Frequent.instrument('Potato#instance_method')
|
42
|
+
11.times { Potato.new.instance_method }
|
43
|
+
p.calls.must_equal(11)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should apply to already-created instances' do
|
47
|
+
instance = Potato.new
|
48
|
+
p = Frequent.instrument('Potato#instance_method')
|
49
|
+
3.times { instance.instance_method }
|
50
|
+
p.calls.must_equal(3)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should not count calls identically-named methods from parent class' do
|
54
|
+
p0 = Frequent.instrument('Potato#overridden_method')
|
55
|
+
p1 = Frequent.instrument('RedPotato#overridden_method')
|
56
|
+
2.times { Potato.new.overridden_method }
|
57
|
+
3.times { RedPotato.new.overridden_method }
|
58
|
+
p0.calls.must_equal(2)
|
59
|
+
p1.calls.must_equal(3)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should support private / protected methods' do
|
63
|
+
p0 = Frequent.instrument('Potato#private_instance_method')
|
64
|
+
p1 = Frequent.instrument('Potato#protected_instance_method')
|
65
|
+
3.times { Potato.new.send(:private_instance_method) }
|
66
|
+
4.times { Potato.new.send(:protected_instance_method) }
|
67
|
+
p0.calls.must_equal(3)
|
68
|
+
p1.calls.must_equal(4)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should support instrumenting methods on Object' do
|
72
|
+
p = Frequent.instrument('Object#tainted?') do
|
73
|
+
Object.new.tainted?
|
74
|
+
end
|
75
|
+
p.calls.must_equal(1)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should pass-thru blocks and args' do
|
79
|
+
v = nil
|
80
|
+
p = Frequent.instrument('Potato#block_method')
|
81
|
+
Potato.new.block_method(42) { |n| v = n }
|
82
|
+
v.must_equal(42)
|
83
|
+
p.calls.must_equal(1)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'instrumentation of class methods' do
|
88
|
+
it 'should support simple call counting' do
|
89
|
+
p = Frequent.instrument('Potato.class_method')
|
90
|
+
9.times { Potato.class_method }
|
91
|
+
p.calls.must_equal(9)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should support recursive methods' do
|
95
|
+
p = Frequent.instrument('Potato.recursive_class_method')
|
96
|
+
Potato.recursive_class_method(7)
|
97
|
+
p.calls.must_equal(7)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'probe removal' do
|
102
|
+
it 'should remove probes on instance methods' do
|
103
|
+
p = Frequent.instrument('Potato#instance_method')
|
104
|
+
Potato.new.instance_method
|
105
|
+
p.disable!
|
106
|
+
Potato.new.instance_method
|
107
|
+
p.calls.must_equal(1)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should remove probes on class methods' do
|
111
|
+
p = Frequent.instrument('Potato.class_method')
|
112
|
+
Potato.class_method
|
113
|
+
p.disable!
|
114
|
+
Potato.class_method
|
115
|
+
p.calls.must_equal(1)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should instrument scoped to block' do
|
119
|
+
p = Frequent.instrument('Potato#instance_method') do
|
120
|
+
5.times { Potato.new.instance_method }
|
121
|
+
end
|
122
|
+
3.times { Potato.new.instance_method }
|
123
|
+
p.calls.must_equal(5)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe 'instrumentation of modules' do
|
128
|
+
it 'should catch module methods even if included after instrumentation' do
|
129
|
+
module Mod1
|
130
|
+
def demo; end
|
131
|
+
end
|
132
|
+
|
133
|
+
p = Frequent.instrument('Mod1#demo')
|
134
|
+
|
135
|
+
class Dummy1
|
136
|
+
include Mod1
|
137
|
+
end
|
138
|
+
Dummy1.new.demo
|
139
|
+
|
140
|
+
p.calls.must_equal(1)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should work if probe placed before module/class definition' do
|
144
|
+
p = Frequent.instrument('Dummy3#foo')
|
145
|
+
|
146
|
+
module Dummy2; def foo; end; end
|
147
|
+
class Dummy3; include Dummy2; end
|
148
|
+
|
149
|
+
10.times { Dummy3.new.foo }
|
150
|
+
|
151
|
+
p.calls.must_equal(10)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should work for module methods' do
|
155
|
+
p = Frequent.instrument('Dummy5.foo')
|
156
|
+
module Dummy5; def self.foo; end; end
|
157
|
+
5.times { Dummy5.foo }
|
158
|
+
p.calls.must_equal(5)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should work with nested modules' do
|
162
|
+
p = Frequent.instrument('Dummies::Dummy6.foo')
|
163
|
+
|
164
|
+
module Dummies
|
165
|
+
module Dummy6; def self.foo; end; end
|
166
|
+
end
|
167
|
+
|
168
|
+
5.times { Dummies::Dummy6.foo }
|
169
|
+
p.calls.must_equal(5)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should work if method is added to class after instrumentation' do
|
174
|
+
class Dummy7; end
|
175
|
+
|
176
|
+
p = Frequent.instrument('Dummy7#foo')
|
177
|
+
|
178
|
+
class Dummy7; def foo; end; end
|
179
|
+
|
180
|
+
3.times { Dummy7.new.foo }
|
181
|
+
p.calls.must_equal(3)
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should work for dynamically-created classes' do
|
185
|
+
p = Frequent.instrument('Dummy8.foo')
|
186
|
+
|
187
|
+
eval "class Dummy8; def self.foo; end; end; 5.times { Dummy8.foo }"
|
188
|
+
|
189
|
+
p.calls.must_equal(5)
|
190
|
+
end
|
191
|
+
|
192
|
+
if ENV['BENCH']
|
193
|
+
it 'should be fast' do
|
194
|
+
p = Potato.new
|
195
|
+
n = 1000000
|
196
|
+
|
197
|
+
puts "Trials: #{n}"
|
198
|
+
Benchmark.bmbm do |rpt|
|
199
|
+
rpt.report("Uninstrumented instance method") do
|
200
|
+
n.times { p.instance_method }
|
201
|
+
end
|
202
|
+
|
203
|
+
rpt.report("Instrumented instance method") do
|
204
|
+
Frequent.instrument('Potato#instance_method') do
|
205
|
+
n.times { p.instance_method }
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
rpt.report("Uninstrumented class method") do
|
210
|
+
n.times { Potato.class_method }
|
211
|
+
end
|
212
|
+
|
213
|
+
rpt.report("Instrumented class method") do
|
214
|
+
Frequent.instrument("Potato.class_method") do
|
215
|
+
n.times { Potato.class_method }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: frequent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ben Weintraub
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-24 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70106089255840 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70106089255840
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: minitest
|
27
|
+
requirement: &70106089255400 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70106089255400
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest-matchers
|
38
|
+
requirement: &70106089254980 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70106089254980
|
47
|
+
description: Ruby method instrumentation demo
|
48
|
+
email:
|
49
|
+
- benweint@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- frequent.gemspec
|
60
|
+
- lib/frequent.rb
|
61
|
+
- spec/frequent_spec.rb
|
62
|
+
- spec/spec_helper.rb
|
63
|
+
homepage: ''
|
64
|
+
licenses: []
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.10
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Ruby method instrumentation demo
|
87
|
+
test_files:
|
88
|
+
- spec/frequent_spec.rb
|
89
|
+
- spec/spec_helper.rb
|