frequent 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|