minstrel 0.2.20101115170047 → 0.2.20110404021920

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.textile +19 -0
  2. data/lib/minstrel.rb +131 -38
  3. 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
- #DONOTWRAP = #[Kernel, Object, Module, Class,
27
- DONOTWRAP = [Minstrel::Instrument].collect do |obj|
28
- obj.methods.collect { |m| m.to_sym }
29
- end.flatten
30
- DONOTWRAP << :to_sym
31
- DONOTWRAP << :respond_to?
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
- # * klass - the class (object) owning this method
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
- #puts "Instrumenting #{klass.name} with #{block.inspect}"
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
- next if DONOTWRAP.include?(method.to_sym)
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
- method = method.to_sym
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
- m = self.method(orig_method)
58
- val = m.call(*args, &argblock)
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
- block.call(:exit_exception, klass, method, *args)
93
+ # TODO(sissel): Include the exception
94
+ block.call(:exit_exception, self, method, *args)
64
95
  raise e if exception
65
96
  else
66
- block.call(:exit, klass, method, *args)
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
- next if DONOTWRAP.include?(method.to_sym)
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, klass, method, *args)
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, klass, method, *args)
154
+ block.call(:class_exit_exception, self, method, *args)
99
155
  raise e if exception
100
156
  else
101
- block.call(:class_exit, klass, method, *args)
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, klass, method, self.method(method))
107
- end # klass.class_eval
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::instrumented_require(*args)
192
+ return Minstrel::Instrument::instrumented_loader(:require, *args)
127
193
  end
128
194
  end
129
195
  end
130
196
 
131
- def self.instrumented_require(*args)
132
- ret = old_require(*args)
133
- klasses = @@deferred_wraps.keys
134
- klasses.each do |klassname|
135
- block = @@deferred_wraps[klassname]
136
- instrument = Minstrel::Instrument.new
137
- if instrument.wrap_classname(klassname, &block)
138
- puts "Wrap of #{klassname} successful"
139
- @@deferred_wraps.delete(klassname)
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(",").each do |klassname|
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.wrap_classname(klassname) do |point, klass, method, *args|
154
- puts "#{point} #{klass.name || klassname}##{method}(#{args.inspect})"
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
- hash: 40202230340073
5
- prerelease: false
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: 2010-11-15 00:00:00 -08:00
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
- - !ruby/object:Gem::Version
50
- hash: 3
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
- - !ruby/object:Gem::Version
59
- hash: 3
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.3.7
55
+ rubygems_version: 1.5.1
67
56
  signing_key:
68
57
  specification_version: 3
69
58
  summary: minstrel - a ruby instrumentation tool