minstrel 0.2.20110428124038 → 0.3.0
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/lib/minstrel.rb +185 -241
- data/minstrel.gemspec +1 -2
- metadata +20 -41
data/lib/minstrel.rb
CHANGED
@@ -1,282 +1,226 @@
|
|
1
1
|
# Wrap method calls for a class of your choosing.
|
2
2
|
# Example:
|
3
|
-
# instrument = Minstrel::Instrument.new()
|
4
|
-
# instrument.wrap(String) do |point, klass, method, *args|
|
5
|
-
# ...
|
6
|
-
# end
|
7
3
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# * klass is the class object (String, etc)
|
11
|
-
# * method is the method (a Symbol)
|
12
|
-
# * *args is the arguments passed
|
4
|
+
# instrument = Minstrel::Instrument.new()
|
5
|
+
# instrument.observe(class_or_method) { |event| ... }
|
13
6
|
#
|
14
|
-
#
|
7
|
+
# The 'event' is a Minstrel::Event.
|
15
8
|
#
|
16
|
-
#
|
9
|
+
# You can also wrap from the command-line
|
10
|
+
# RUBY_INSTRUMENT=comma_separated_classnames ruby -rminstrel ./your/program.rb
|
17
11
|
#
|
18
12
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
attr_accessor :
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
13
|
+
module Minstrel; end
|
14
|
+
|
15
|
+
class Minstrel::Event
|
16
|
+
attr_accessor :action
|
17
|
+
attr_accessor :file
|
18
|
+
attr_accessor :line
|
19
|
+
attr_accessor :method
|
20
|
+
attr_accessor :binding
|
21
|
+
attr_accessor :class
|
22
|
+
attr_accessor :args_hash
|
23
|
+
attr_accessor :args_order
|
24
|
+
attr_accessor :timestamp
|
25
|
+
|
26
|
+
# Duration is only valid for 'return' or 'c-return' events
|
27
|
+
attr_accessor :duration
|
28
|
+
|
29
|
+
# The call stack depth of this event
|
30
|
+
attr_accessor :depth
|
31
|
+
|
32
|
+
public
|
33
|
+
def initialize(action, file, line, method, binding, klass)
|
34
|
+
@action = action
|
35
|
+
@file = file
|
36
|
+
@line = line
|
37
|
+
@method = method
|
38
|
+
@binding = binding
|
39
|
+
@class = klass
|
40
|
+
@timestamp = Time.now
|
41
|
+
|
42
|
+
if ["c-call", "call"].include?(@action)
|
43
|
+
@args_hash = {}
|
44
|
+
@args_order = eval('local_variables', binding)
|
45
|
+
|
46
|
+
# In ruby 1.9, local_variables returns an array of symbols, not strings.
|
47
|
+
@args_order.collect { |v| @args_hash[v] = eval(v.to_s, binding) }
|
54
48
|
end
|
55
|
-
puts "Wrapping #{klass.class} #{klass}" if $DEBUG
|
56
49
|
|
57
|
-
|
58
|
-
|
59
|
-
|
50
|
+
@block_given = eval('block_given?', @binding)
|
51
|
+
end # def initialize
|
52
|
+
|
53
|
+
# Get the call args as an array in-order
|
54
|
+
public
|
55
|
+
def args
|
56
|
+
return nil unless @args_order
|
57
|
+
return @args_order.collect { |v| @args_hash[v] }
|
58
|
+
end # def args
|
59
|
+
|
60
|
+
public
|
61
|
+
def use_related_event(event)
|
62
|
+
@args_order = event.args_order
|
63
|
+
@args_hash = event.args_hash
|
64
|
+
@block_given = event.block_given?
|
65
|
+
@duration = @timestamp - event.timestamp
|
66
|
+
end # def use_related_event
|
67
|
+
|
68
|
+
# Is this event a method entry?
|
69
|
+
public
|
70
|
+
def entry?
|
71
|
+
return ["c-call", "call"].include?(action)
|
72
|
+
end # def entry?
|
73
|
+
|
74
|
+
# Is this event a method return?
|
75
|
+
public
|
76
|
+
def exit?
|
77
|
+
return ["c-return", "return"].include?(action)
|
78
|
+
end # def exit?
|
79
|
+
|
80
|
+
# Was there a block given to this method?
|
81
|
+
public
|
82
|
+
def block_given?
|
83
|
+
@block_given
|
84
|
+
end # def block_given ?
|
85
|
+
|
86
|
+
public
|
87
|
+
def symbol
|
88
|
+
case @action
|
89
|
+
when "c-call", "call" ; return "=>"
|
90
|
+
when "c-return", "return" ; return "<="
|
91
|
+
when "raise"; return "<E"
|
92
|
+
else return " +"
|
93
|
+
end
|
94
|
+
end # def symbol
|
60
95
|
|
61
|
-
|
96
|
+
public
|
97
|
+
def to_s
|
98
|
+
return "#{" " * @depth}#{symbol} #{@class.to_s}##{@method}(#{args.inspect}) #{block_given? ? "{ ... }" : ""} (thread=#{Thread.current}) #{@duration.nil? ? "" : sprintf("(%.5f seconds)", @duration)}"
|
99
|
+
end # def to_s
|
62
100
|
|
63
|
-
|
64
|
-
skip = false
|
65
|
-
(ancestors & DONOTWRAP.keys).each do |key|
|
66
|
-
if DONOTWRAP[key].include?(method)
|
67
|
-
skip = true
|
68
|
-
break
|
69
|
-
end
|
70
|
-
end
|
71
|
-
if skip
|
72
|
-
#puts "Skipping #{klass}##{method} (do not wrap)"
|
73
|
-
next
|
74
|
-
end
|
101
|
+
end # class Minstrel::Event
|
75
102
|
|
76
|
-
|
77
|
-
|
78
|
-
orig_method_proc = klass.instance_method(method)
|
79
|
-
alias_method orig_method, method
|
80
|
-
#block.call(:wrap, klass, method)
|
81
|
-
puts "Wrapping #{klass.name}##{method} (method)" if $DEBUG
|
82
|
-
define_method(method) do |*args, &argblock|
|
83
|
-
exception = false
|
84
|
-
block.call(:enter, self, method, *args)
|
85
|
-
begin
|
86
|
-
# TODO(sissel): Not sure which is better:
|
87
|
-
# * UnboundMethod#bind(self).call(...)
|
88
|
-
# * self.method(orig_method).call(...)
|
89
|
-
val = orig_method_proc.bind(self).call(*args, &argblock)
|
90
|
-
#m = self.method(orig_method)
|
91
|
-
#val = m.call(*args, &argblock)
|
92
|
-
rescue => e
|
93
|
-
exception = e
|
94
|
-
end
|
95
|
-
if exception
|
96
|
-
# TODO(sissel): Include the exception
|
97
|
-
block.call(:exit_exception, self, method, *args)
|
98
|
-
raise e if exception
|
99
|
-
else
|
100
|
-
# TODO(sissel): Include the return value
|
101
|
-
block.call(:exit, self, method, *args)
|
102
|
-
end
|
103
|
-
return val
|
104
|
-
end # define_method(method)
|
105
|
-
end # klass.class_eval
|
106
|
-
end # klass.instance_methods.each
|
103
|
+
class Minstrel::Instrument
|
104
|
+
attr_accessor :counter
|
107
105
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# If we shouldn't wrap a certain class method, skip it.
|
113
|
-
skip = false
|
114
|
-
ancestors = klass.ancestors.collect {|k| k.to_s}
|
115
|
-
(ancestors & DONOTWRAP.keys).each do |key|
|
116
|
-
if DONOTWRAP[key].include?(method)
|
117
|
-
skip = true
|
118
|
-
#break
|
119
|
-
end
|
120
|
-
end
|
106
|
+
public
|
107
|
+
def self.singleton
|
108
|
+
@minstrel ||= Minstrel::Instrument.new
|
109
|
+
end # def self.singleton
|
121
110
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
111
|
+
public
|
112
|
+
def initialize
|
113
|
+
@observe = []
|
114
|
+
@stack = Hash.new { |h,k| h[k] = [] } # per thread
|
115
|
+
end # def initialize
|
128
116
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
117
|
+
public
|
118
|
+
def stack
|
119
|
+
return @stack
|
120
|
+
end
|
133
121
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
122
|
+
public
|
123
|
+
def observe(thing=nil, &block)
|
124
|
+
if !thing.nil?
|
125
|
+
# Is thing a class name?
|
126
|
+
@observe << lambda do |event|
|
127
|
+
if class?(thing)
|
128
|
+
if event.class.to_s == thing.to_s
|
129
|
+
block.call(event)
|
142
130
|
end
|
143
|
-
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
if orig_method.is_a?(Symbol)
|
150
|
-
val = send(orig_method, *args, &argblock)
|
151
|
-
else
|
152
|
-
val = orig_method.call(*args, &argblock)
|
153
|
-
end
|
154
|
-
rescue => e
|
155
|
-
exception = e
|
156
|
-
end
|
157
|
-
if exception
|
158
|
-
block.call(:class_exit_exception, self, method, *args)
|
159
|
-
raise e if exception
|
160
|
-
else
|
161
|
-
block.call(:class_exit, self, method, *args)
|
162
|
-
end
|
163
|
-
return val
|
131
|
+
else
|
132
|
+
# assume it's a method?
|
133
|
+
#p :observe => nil, :class => thing, :is => class?(thing)
|
134
|
+
klass, method = thing.split(/[.#]/, 2)
|
135
|
+
if (event.class.to_s == klass and event.method = method.to_sym)
|
136
|
+
block.call(event)
|
164
137
|
end
|
165
138
|
end
|
166
|
-
#block.call(:class_wrap, self, method, self.method(method))
|
167
|
-
end # klass.instance_eval
|
168
|
-
end # klass.instance_methods.each
|
169
|
-
|
170
|
-
return true
|
171
|
-
end # def wrap
|
172
|
-
|
173
|
-
def wrap_classname(klassname, &block)
|
174
|
-
begin
|
175
|
-
klass = eval(klassname)
|
176
|
-
wrap(klass, &block)
|
177
|
-
return true
|
178
|
-
rescue NameError => e
|
179
|
-
@@deferred_wraps[klassname] = block
|
180
|
-
end
|
181
|
-
return false
|
182
|
-
end
|
183
|
-
|
184
|
-
def wrap_method(fullname, &block)
|
185
|
-
puts "Want to wrap #{fullname}" if $DEBUG
|
186
|
-
begin
|
187
|
-
klassname, method = fullname.split(/[#.]/, 2)
|
188
|
-
klass = eval(klassname)
|
189
|
-
wrap(klass, method, &block)
|
190
|
-
return true
|
191
|
-
rescue NameError => e
|
192
|
-
@@deferred_method_wraps[fullname] = block
|
193
|
-
return false
|
194
|
-
end
|
195
|
-
end # def wrap_method
|
196
|
-
|
197
|
-
def wrap_all(&block)
|
198
|
-
@@deferred_wraps[:all] = block
|
199
|
-
ObjectSpace.each_object do |obj|
|
200
|
-
next unless obj.is_a?(Class)
|
201
|
-
wrap(obj, &block)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def self.wrap_require
|
206
|
-
Kernel.class_eval do
|
207
|
-
alias_method :old_require, :require
|
208
|
-
def require(*args)
|
209
|
-
return Minstrel::Instrument::instrumented_loader(:require, *args)
|
210
139
|
end
|
140
|
+
elsif block_given?
|
141
|
+
@observe << block
|
142
|
+
else
|
143
|
+
raise ArgumentError.new("No block given.")
|
211
144
|
end
|
212
|
-
end
|
145
|
+
end # def observe
|
213
146
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
147
|
+
# Is the thing here a class?
|
148
|
+
# Can be a string name of a class or a class object
|
149
|
+
private
|
150
|
+
def class?(thing)
|
151
|
+
#p :method => "class?", :thing => thing, :class => thing.is_a?(Class), :foo => thing.inspect
|
152
|
+
begin
|
153
|
+
return true if thing.is_a?(Class)
|
154
|
+
return false if (thing.is_a?(String) and thing.include?("#"))
|
155
|
+
obj = eval(thing)
|
156
|
+
return obj.is_a?(Class)
|
157
|
+
rescue => e
|
158
|
+
false
|
220
159
|
end
|
221
|
-
end
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
160
|
+
end # def class?
|
161
|
+
|
162
|
+
# Activate tracing
|
163
|
+
public
|
164
|
+
def enable
|
165
|
+
set_trace_func(Proc.new { |*args| trace_callback(*args) })
|
166
|
+
end # def enable
|
167
|
+
|
168
|
+
# Disable tracing
|
169
|
+
public
|
170
|
+
def disable
|
171
|
+
set_trace_func(nil)
|
172
|
+
end # def disable
|
173
|
+
|
174
|
+
# This method is invoked by the ruby tracer
|
175
|
+
private
|
176
|
+
def trace_callback(action, file, line, method, binding, klass)
|
177
|
+
begin
|
178
|
+
event = Minstrel::Event.new(action, file, line, method, binding, klass)
|
179
|
+
# At the time of "c-call" there's no other local variables other than
|
180
|
+
# the method arguments. Pull the args out of binding
|
181
|
+
#event.args = eval('local_variables.collect { |v| "(#{v}) #{v.inspect}" }', binding)
|
182
|
+
if ["c-call", "call"].include?(action)
|
183
|
+
@stack[Thread.current].push event
|
184
|
+
event.depth = @stack[Thread.current].size
|
185
|
+
elsif ["c-return", "return", "raise"].include?(action)
|
186
|
+
# TODO(sissel): validate top of stack looks right?
|
187
|
+
event.depth = @stack[Thread.current].size
|
188
|
+
entry_event = @stack[Thread.current].pop
|
189
|
+
if !entry_event.nil?
|
190
|
+
event.use_related_event(entry_event)
|
240
191
|
end
|
192
|
+
else
|
193
|
+
event.depth = @stack[Thread.current].size
|
241
194
|
end
|
242
195
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
@@deferred_method_wraps.delete(fullname)
|
250
|
-
end
|
251
|
-
end
|
196
|
+
# Call any observers
|
197
|
+
@observe.each { |callback| callback.call(event) }
|
198
|
+
rescue => e
|
199
|
+
$stderr.puts "Exception in trace_callback: #{e}"
|
200
|
+
$stderr.puts e.backtrace
|
201
|
+
raise e
|
252
202
|
end
|
253
|
-
|
254
|
-
|
255
|
-
end; end # class Minstrel::Instrument
|
256
|
-
|
257
|
-
Minstrel::Instrument.wrap_require
|
258
|
-
Minstrel::Instrument.wrap_load
|
203
|
+
end # def trace_callback
|
204
|
+
end # class Minstrel::Instrument
|
259
205
|
|
260
206
|
# Provide a way to instrument a class using the command line:
|
261
207
|
# RUBY_INSTRUMENT=String ruby -rminstrel ./your/program
|
262
208
|
if ENV["RUBY_INSTRUMENT"]
|
263
209
|
klasses = ENV["RUBY_INSTRUMENT"].split(",")
|
264
210
|
|
265
|
-
callback = proc do |
|
266
|
-
|
211
|
+
callback = proc do |event|
|
212
|
+
# Only show entry or exits
|
213
|
+
next unless (event.entry? or event.exit?)
|
214
|
+
puts event
|
267
215
|
end
|
216
|
+
|
268
217
|
instrument = Minstrel::Instrument.new
|
269
218
|
if klasses.include?(":all:")
|
270
|
-
instrument.
|
219
|
+
instrument.observe(&callback)
|
271
220
|
else
|
272
221
|
klasses.each do |klassname|
|
273
|
-
|
274
|
-
# This will wrap one method as indicated by: ClassName#method
|
275
|
-
# TODO(sissel): Maybe also allow ModuleName::method
|
276
|
-
instrument.wrap_method(klassname, &callback)
|
277
|
-
else
|
278
|
-
instrument.wrap_classname(klassname, &callback)
|
279
|
-
end
|
222
|
+
instrument.observe(klassname, &callback)
|
280
223
|
end # klasses.each
|
281
224
|
end
|
225
|
+
instrument.enable
|
282
226
|
end # if ENV["RUBY_INSTRUMENT"]
|
data/minstrel.gemspec
CHANGED
@@ -6,9 +6,8 @@ Gem::Specification.new do |spec|
|
|
6
6
|
"./minstrel.gemspec",
|
7
7
|
]
|
8
8
|
|
9
|
-
rev = Time.now.strftime("%Y%m%d%H%M%S")
|
10
9
|
spec.name = "minstrel"
|
11
|
-
spec.version = "0.
|
10
|
+
spec.version = "0.3.0"
|
12
11
|
spec.summary = "minstrel - a ruby instrumentation tool"
|
13
12
|
spec.description = "Instrument class methods"
|
14
13
|
spec.files = files
|
metadata
CHANGED
@@ -1,71 +1,50 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: minstrel
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 20110428124038
|
10
|
-
version: 0.2.20110428124038
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Jordan Sissel
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
date: 2011-04-28 00:00:00 -07:00
|
19
|
-
default_executable:
|
12
|
+
date: 2012-01-21 00:00:00.000000000 Z
|
20
13
|
dependencies: []
|
21
|
-
|
22
14
|
description: Instrument class methods
|
23
15
|
email: jls@semicomplete.com
|
24
|
-
executables:
|
16
|
+
executables:
|
25
17
|
- minstrel
|
26
18
|
extensions: []
|
27
|
-
|
28
19
|
extra_rdoc_files: []
|
29
|
-
|
30
|
-
files:
|
20
|
+
files:
|
31
21
|
- ./lib/minstrel.rb
|
32
22
|
- ./README.textile
|
33
23
|
- ./minstrel.gemspec
|
34
24
|
- bin/minstrel
|
35
|
-
has_rdoc: true
|
36
25
|
homepage: https://github.com/jordansissel/ruby-minstrel
|
37
26
|
licenses: []
|
38
|
-
|
39
27
|
post_install_message:
|
40
28
|
rdoc_options: []
|
41
|
-
|
42
|
-
require_paths:
|
29
|
+
require_paths:
|
43
30
|
- lib
|
44
31
|
- lib
|
45
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
33
|
none: false
|
47
|
-
requirements:
|
48
|
-
- -
|
49
|
-
- !ruby/object:Gem::Version
|
50
|
-
|
51
|
-
|
52
|
-
- 0
|
53
|
-
version: "0"
|
54
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
39
|
none: false
|
56
|
-
requirements:
|
57
|
-
- -
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
|
60
|
-
segments:
|
61
|
-
- 0
|
62
|
-
version: "0"
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
63
44
|
requirements: []
|
64
|
-
|
65
45
|
rubyforge_project:
|
66
|
-
rubygems_version: 1.
|
46
|
+
rubygems_version: 1.8.10
|
67
47
|
signing_key:
|
68
48
|
specification_version: 3
|
69
49
|
summary: minstrel - a ruby instrumentation tool
|
70
50
|
test_files: []
|
71
|
-
|