minstrel 0.2.20101115170047 → 0.2.20110404021920
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/README.textile +19 -0
- data/lib/minstrel.rb +131 -38
- metadata +18 -29
data/README.textile
CHANGED
@@ -16,6 +16,10 @@ to debug or dig into how a piece of code works.
|
|
16
16
|
|
17
17
|
It's a lot like strace/tcpdump/dtrace for ruby, or aims to be, anyway.
|
18
18
|
|
19
|
+
I wanted a tool that was useful for my own code as well as for helping
|
20
|
+
understand and debug other code (puppet, mcollective, rails, activerecord,
|
21
|
+
sinatra, etc...).
|
22
|
+
|
19
23
|
h2. Examples
|
20
24
|
|
21
25
|
h3. From the commandline
|
@@ -135,4 +139,19 @@ Wrap of TCPSocket successful
|
|
135
139
|
Minstrel will wrap 'require' and check for classes you want wrapped at each
|
136
140
|
require until it finds all the classes you asked to be wrapped.
|
137
141
|
|
142
|
+
h2. Caveats
|
143
|
+
|
144
|
+
Metaprogramming will not be often caught, necessarily, by minstrel, because they don't
|
145
|
+
show usuall up as methods. However, the things invoking metaprogramming are
|
146
|
+
usually methods so in most cases you'll get lucky enough to see what's going
|
147
|
+
on.
|
148
|
+
|
149
|
+
Some cases of metaprogramming (dynamic method generation, DSLs, etc) can be
|
150
|
+
caught if you call Minstrel::Instrument#wrap() late enough in the lifetime of
|
151
|
+
the program that the dynamic methods have been created.
|
152
|
+
|
153
|
+
h2. Bugs?
|
138
154
|
|
155
|
+
If you find bugs, have feature suggestions, etc, feel free to open bugs here on
|
156
|
+
github (https://github.com/jordansissel/ruby-minstrel/issues). I also read email:
|
157
|
+
jls@semicomplete.com
|
data/lib/minstrel.rb
CHANGED
@@ -15,63 +15,118 @@
|
|
15
15
|
#
|
16
16
|
# RUBY_INSTRUMENT=comma_separated_classnames ruby -rminstrel ./your/program.rb
|
17
17
|
#
|
18
|
+
|
19
|
+
require "set"
|
20
|
+
|
18
21
|
module Minstrel; class Instrument
|
19
22
|
attr_accessor :counter
|
20
23
|
|
21
24
|
class << self
|
22
25
|
@@deferred_wraps = {}
|
26
|
+
@@wrapped = Set.new
|
23
27
|
end
|
24
28
|
|
25
29
|
# Put methods we must not be wrapping here.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
DONOTWRAP << :send
|
30
|
+
DONOTWRAP = {
|
31
|
+
"Minstrel::Instrument" => Minstrel::Instrument.instance_methods.collect { |m| m.to_sym },
|
32
|
+
"Object" => [ :to_sym, :respond_to?, :send, :java_send, :method, :java_method,
|
33
|
+
:ancestors, :inspect, :to_s, :instance_eval, :instance_exec,
|
34
|
+
:class_eval, :class_exec, :module_eval, :module_exec],
|
35
|
+
}
|
33
36
|
|
34
37
|
# Wrap a class's instance methods with your block.
|
35
38
|
# The block will be called with 4 arguments, and called
|
36
39
|
# before and after the original method.
|
37
40
|
# Arguments:
|
38
|
-
# * point - the point (symbol, :entry or :exit) of call
|
39
|
-
# *
|
41
|
+
# * point - the point (symbol, :entry or :exit) of call,
|
42
|
+
# * this - the object instance in scope (use 'this.class' for the class)
|
40
43
|
# * method - the method (symbol) being called
|
41
44
|
# * *args - the arguments (array) passed to this method.
|
42
45
|
def wrap(klass, &block)
|
43
|
-
|
44
|
-
instrumenter = self
|
46
|
+
return true if @@wrapped.include?(klass)
|
47
|
+
instrumenter = self # save 'self' for scoping below
|
48
|
+
@@wrapped << klass
|
49
|
+
|
50
|
+
ancestors = klass.ancestors.collect {|k| k.to_s }
|
51
|
+
if ancestors.include?("Exception")
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
puts "Wrapping #{klass.class} #{klass}" if $DEBUG
|
45
55
|
|
56
|
+
# Wrap class instance methods (like File#read)
|
46
57
|
klass.instance_methods.each do |method|
|
47
|
-
|
58
|
+
method = method.to_sym
|
59
|
+
|
60
|
+
# If we shouldn't wrap a certain class method, skip it.
|
61
|
+
skip = false
|
62
|
+
(ancestors & DONOTWRAP.keys).each do |key|
|
63
|
+
if DONOTWRAP[key].include?(method)
|
64
|
+
skip = true
|
65
|
+
break
|
66
|
+
end
|
67
|
+
end
|
68
|
+
if skip
|
69
|
+
#puts "Skipping #{klass}##{method} (do not wrap)"
|
70
|
+
next
|
71
|
+
end
|
72
|
+
|
48
73
|
klass.class_eval do
|
49
74
|
orig_method = "#{method}_original(wrapped)".to_sym
|
50
|
-
|
75
|
+
orig_method_proc = klass.instance_method(method)
|
51
76
|
alias_method orig_method, method
|
52
77
|
#block.call(:wrap, klass, method)
|
78
|
+
puts "Wrapping #{klass.name}##{method} (method)" if $DEBUG
|
53
79
|
define_method(method) do |*args, &argblock|
|
54
|
-
block.call(:enter, klass, method, *args)
|
55
80
|
exception = false
|
81
|
+
block.call(:enter, self, method, *args)
|
56
82
|
begin
|
57
|
-
|
58
|
-
|
83
|
+
# TODO(sissel): Not sure which is better:
|
84
|
+
# * UnboundMethod#bind(self).call(...)
|
85
|
+
# * self.method(orig_method).call(...)
|
86
|
+
val = orig_method_proc.bind(self).call(*args, &argblock)
|
87
|
+
#m = self.method(orig_method)
|
88
|
+
#val = m.call(*args, &argblock)
|
59
89
|
rescue => e
|
60
90
|
exception = e
|
61
91
|
end
|
62
92
|
if exception
|
63
|
-
|
93
|
+
# TODO(sissel): Include the exception
|
94
|
+
block.call(:exit_exception, self, method, *args)
|
64
95
|
raise e if exception
|
65
96
|
else
|
66
|
-
|
97
|
+
# TODO(sissel): Include the return value
|
98
|
+
block.call(:exit, self, method, *args)
|
67
99
|
end
|
68
100
|
return val
|
69
|
-
end
|
101
|
+
end # define_method(method)
|
70
102
|
end # klass.class_eval
|
71
103
|
end # klass.instance_methods.each
|
72
104
|
|
105
|
+
# Wrap class methods (like File.open)
|
73
106
|
klass.methods.each do |method|
|
74
|
-
|
107
|
+
method = method.to_sym
|
108
|
+
# If we shouldn't wrap a certain class method, skip it.
|
109
|
+
skip = false
|
110
|
+
ancestors = klass.ancestors.collect {|k| k.to_s}
|
111
|
+
(ancestors & DONOTWRAP.keys).each do |key|
|
112
|
+
if DONOTWRAP[key].include?(method)
|
113
|
+
skip = true
|
114
|
+
#break
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Doubly-ensure certain methods are not wrapped.
|
119
|
+
# Some classes like "Timeout" do not have ancestors.
|
120
|
+
if DONOTWRAP["Object"].include?(method)
|
121
|
+
#puts "!! Skipping #{klass}##{method} (do not wrap)"
|
122
|
+
skip = true
|
123
|
+
end
|
124
|
+
|
125
|
+
if skip
|
126
|
+
puts "Skipping #{klass}##{method} (do not wrap, not safe)" if $DEBUG
|
127
|
+
next
|
128
|
+
end
|
129
|
+
|
75
130
|
klass.instance_eval do
|
76
131
|
orig_method = "#{method}_original(classwrapped)".to_sym
|
77
132
|
(class << self; self; end).instance_eval do
|
@@ -82,8 +137,9 @@ module Minstrel; class Instrument
|
|
82
137
|
orig_method = self.method(method.to_sym)
|
83
138
|
end
|
84
139
|
method = method.to_sym
|
140
|
+
#puts "Wrapping #{klass.name}.#{method} (classmethod)"
|
85
141
|
define_method(method) do |*args, &argblock|
|
86
|
-
block.call(:class_enter,
|
142
|
+
block.call(:class_enter, self, method, *args)
|
87
143
|
exception = false
|
88
144
|
begin
|
89
145
|
if orig_method.is_a?(Symbol)
|
@@ -95,17 +151,19 @@ module Minstrel; class Instrument
|
|
95
151
|
exception = e
|
96
152
|
end
|
97
153
|
if exception
|
98
|
-
block.call(:class_exit_exception,
|
154
|
+
block.call(:class_exit_exception, self, method, *args)
|
99
155
|
raise e if exception
|
100
156
|
else
|
101
|
-
block.call(:class_exit,
|
157
|
+
block.call(:class_exit, self, method, *args)
|
102
158
|
end
|
103
159
|
return val
|
104
160
|
end
|
105
161
|
end
|
106
|
-
#block.call(:class_wrap,
|
107
|
-
end # klass.
|
162
|
+
#block.call(:class_wrap, self, method, self.method(method))
|
163
|
+
end # klass.instance_eval
|
108
164
|
end # klass.instance_methods.each
|
165
|
+
|
166
|
+
return true
|
109
167
|
end # def wrap
|
110
168
|
|
111
169
|
def wrap_classname(klassname, &block)
|
@@ -119,24 +177,50 @@ module Minstrel; class Instrument
|
|
119
177
|
return false
|
120
178
|
end
|
121
179
|
|
180
|
+
def wrap_all(&block)
|
181
|
+
@@deferred_wraps[:all] = block
|
182
|
+
ObjectSpace.each_object do |obj|
|
183
|
+
next unless obj.is_a?(Class)
|
184
|
+
wrap(obj, &block)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
122
188
|
def self.wrap_require
|
123
189
|
Kernel.class_eval do
|
124
190
|
alias_method :old_require, :require
|
125
191
|
def require(*args)
|
126
|
-
return Minstrel::Instrument::
|
192
|
+
return Minstrel::Instrument::instrumented_loader(:require, *args)
|
127
193
|
end
|
128
194
|
end
|
129
195
|
end
|
130
196
|
|
131
|
-
def self.
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
197
|
+
def self.wrap_load
|
198
|
+
Kernel.class_eval do
|
199
|
+
alias_method :old_load, :load
|
200
|
+
def load(*args)
|
201
|
+
return Minstrel::Instrument::instrumented_loader(:load, *args)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.instrumented_loader(method, *args)
|
207
|
+
ret = self.send(:"old_#{method}", *args)
|
208
|
+
if @@deferred_wraps.include?(:all)
|
209
|
+
# try to wrap anything new that is not wrapped
|
210
|
+
wrap_all(@@deferred_wraps[:all])
|
211
|
+
else
|
212
|
+
# look for deferred class wraps
|
213
|
+
klasses = @@deferred_wraps.keys
|
214
|
+
klasses.each do |klassname|
|
215
|
+
if @@deferred_wraps.include?("ALL")
|
216
|
+
all = true
|
217
|
+
end
|
218
|
+
block = @@deferred_wraps[klassname]
|
219
|
+
instrument = Minstrel::Instrument.new
|
220
|
+
if instrument.wrap_classname(klassname, &block)
|
221
|
+
$stderr.puts "Wrap of #{klassname} successful"
|
222
|
+
@@deferred_wraps.delete(klassname) if !all
|
223
|
+
end
|
140
224
|
end
|
141
225
|
end
|
142
226
|
return ret
|
@@ -144,14 +228,23 @@ module Minstrel; class Instrument
|
|
144
228
|
end; end # class Minstrel::Instrument
|
145
229
|
|
146
230
|
Minstrel::Instrument.wrap_require
|
231
|
+
Minstrel::Instrument.wrap_load
|
147
232
|
|
148
233
|
# Provide a way to instrument a class using the command line:
|
149
234
|
# RUBY_INSTRUMENT=String ruby -rminstrel ./your/program
|
150
235
|
if ENV["RUBY_INSTRUMENT"]
|
151
|
-
ENV["RUBY_INSTRUMENT"].split(",")
|
236
|
+
klasses = ENV["RUBY_INSTRUMENT"].split(",")
|
237
|
+
|
238
|
+
callback = proc do |point, this, method, *args|
|
239
|
+
puts "#{point} #{this.to_s}##{method}(#{args.inspect}) (thread=#{Thread.current}, self=#{this.inspect})"
|
240
|
+
end
|
241
|
+
if klasses.include?(":all:")
|
152
242
|
instrument = Minstrel::Instrument.new
|
153
|
-
instrument.
|
154
|
-
|
243
|
+
instrument.wrap_all &callback
|
244
|
+
else
|
245
|
+
klasses.each do |klassname|
|
246
|
+
instrument = Minstrel::Instrument.new
|
247
|
+
instrument.wrap_classname(klassname, &callback)
|
155
248
|
end
|
156
249
|
end
|
157
250
|
end
|
metadata
CHANGED
@@ -1,37 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minstrel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 20101115170047
|
10
|
-
version: 0.2.20101115170047
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.20110404021920
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
|
-
- Jordan Sissel
|
8
|
+
- Jordan Sissel
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date:
|
13
|
+
date: 2011-04-04 00:00:00 -07:00
|
19
14
|
default_executable:
|
20
15
|
dependencies: []
|
21
16
|
|
22
17
|
description: Instrument class methods
|
23
18
|
email: jls@semicomplete.com
|
24
19
|
executables:
|
25
|
-
- minstrel
|
20
|
+
- minstrel
|
26
21
|
extensions: []
|
27
22
|
|
28
23
|
extra_rdoc_files: []
|
29
24
|
|
30
25
|
files:
|
31
|
-
- ./lib/minstrel.rb
|
32
|
-
- ./README.textile
|
33
|
-
- ./minstrel.gemspec
|
34
|
-
- bin/minstrel
|
26
|
+
- ./lib/minstrel.rb
|
27
|
+
- ./README.textile
|
28
|
+
- ./minstrel.gemspec
|
29
|
+
- bin/minstrel
|
35
30
|
has_rdoc: true
|
36
31
|
homepage: https://github.com/jordansissel/ruby-minstrel
|
37
32
|
licenses: []
|
@@ -40,30 +35,24 @@ post_install_message:
|
|
40
35
|
rdoc_options: []
|
41
36
|
|
42
37
|
require_paths:
|
43
|
-
- lib
|
44
|
-
- lib
|
38
|
+
- lib
|
39
|
+
- lib
|
45
40
|
required_ruby_version: !ruby/object:Gem::Requirement
|
46
41
|
none: false
|
47
42
|
requirements:
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
segments:
|
52
|
-
- 0
|
53
|
-
version: "0"
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
54
46
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
47
|
none: false
|
56
48
|
requirements:
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
segments:
|
61
|
-
- 0
|
62
|
-
version: "0"
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
63
52
|
requirements: []
|
64
53
|
|
65
54
|
rubyforge_project:
|
66
|
-
rubygems_version: 1.
|
55
|
+
rubygems_version: 1.5.1
|
67
56
|
signing_key:
|
68
57
|
specification_version: 3
|
69
58
|
summary: minstrel - a ruby instrumentation tool
|