stack_tracy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ *.gem
2
+ *.o
3
+ *.bundle
4
+ *.csv
5
+ *.html
6
+ .bundle
7
+ .rvmrc
8
+ .DS_Store
9
+ Gemfile.lock
10
+ doc
11
+ pkg
12
+ Makefile
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = StackTracy CHANGELOG
2
+
3
+ == Version 0.1.0 (August 16, 2012)
4
+
5
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :gem_default do
6
+ gem "stack_tracy", :path => "."
7
+ end
8
+
9
+ group :gem_development do
10
+ gem "pry"
11
+ end
12
+
13
+ group :gem_test do
14
+ gem "minitest"
15
+ gem "mocha"
16
+ gem "pry"
17
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Paul Engel
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,367 @@
1
+ **Note: This gem is not available on [http://rubygems.org](http://rubygems.org) yet**
2
+
3
+ # StackTracy
4
+
5
+ Investigate and detect slow methods within the stack trace of your Ruby (optionally Sinatra) application
6
+
7
+ ## Introduction
8
+
9
+ StackTracy is created for simply investigating your stack trace for called methods and its corresponding duration. You can filter modules / classes / methods within the output tree.
10
+
11
+ The gem is partly written in C to reduce the application performance as minimal as possible.
12
+
13
+ ![Dick .. Stack Tracy](http://codehero.es/images/stack_tracy.jpg)
14
+
15
+ ## Installation
16
+
17
+ ### Add `StackTracy` to your Gemfile
18
+
19
+ gem "stack_tracy"
20
+
21
+ ### Install the gem dependencies
22
+
23
+ $ bundle
24
+
25
+ ## Usage
26
+
27
+ ### Recording stack events
28
+
29
+ Using `StackTracy` is pretty straightforward. You can either `start` and `stop` stack events recording.
30
+
31
+ [1] pry(main)> StackTracy.start
32
+ [2] pry(main)> puts "testing"
33
+ [3] pry(main)> StackTracy.stop
34
+
35
+ Or you can use the `stack_tracy` convenience method which records stack events within the passed block.
36
+
37
+ [1] pry(main)> stack_tracy do
38
+ [1] pry(main)> puts "testing"
39
+ [1] pry(main)> end
40
+
41
+ ### Reducing recorded stack events
42
+
43
+ As StackTracy records every `call` and `return` event, the resulted stack tree can get immense huge. Fortunately, you can reduce the recorded stack events by passing options to `StackTracy.start` or `stack_tracy`.
44
+
45
+ They accept an options `Hash` with at least one of the following keys:
46
+
47
+ * `:only` - Matching events will be recorded
48
+ * `:exclude` - Matching events will be ignored
49
+
50
+ The value can be either a String (optionally whitespace-delimited) or an array of strings containing `class` and/or `module` names. You can use the wildcard (`*`) for matching classes and/or modules within that namespace.
51
+
52
+ #### Some examples
53
+
54
+ You can pass options as follows:
55
+
56
+ [1] pry(main)> StackTracy.start :only => ["Foo"]
57
+
58
+ When using the convenience method:
59
+
60
+ [2] pry(main)> stack_tracy(:only => "Foo") { puts "testing" }
61
+ [3] pry(main)> stack_tracy(:dump, {:only => "Foo*"}) { puts "testing" }
62
+ [4] pry(main)> stack_tracy(:open, {:exclude => ["Array", "Hash"]}) { puts "testing" }
63
+
64
+ Let's say that you have the following code:
65
+
66
+ class Foo
67
+ class Bar; end
68
+ module CandyBar; end
69
+ end
70
+
71
+ class FooBar; end
72
+
73
+ A couple of examples:
74
+
75
+ * `:only => "Foo"` records `Foo`
76
+ * `:only => "Foo*"` records `Foo`, `Foo::Bar` and `Foo::CandyBar`
77
+ * `:only => "Foo*", :exclude => "Foo::Bar"` records `Foo` and `Foo::CandyBar`
78
+ * `:exclude => ["Foo*", "FooBar"]` records everything except for `Foo`, `Foo::Bar`, `Foo::CandyBar` and `FooBar`
79
+ * `:only => "Foo* Kernel"` records `Foo`, `Foo::Bar`, `Foo::CandyBar` and `Kernel`
80
+
81
+ ### Configure StackTracy
82
+
83
+ You can configure the default stack tree reduction behaviour and dump directory of `StackTracy`:
84
+
85
+ StackTracy.configure do |c|
86
+ c.dump_dir = "." #=> default: Dir::tmpdir
87
+ c.only = "Foo*" #=> default: nil
88
+ c.exclude = %w(Foo::Bar Foo::CandyBar) #=> default: nil
89
+ end
90
+
91
+ ### Using recorded stack events
92
+
93
+ Once you have recorded stack events, you can call the following methods:
94
+
95
+ `StackTracy.stack_trace` - returns all recorded stack events (`array` of `StackTracy::EventInfo` instances)
96
+
97
+ [2] pry(main)> StackTracy.stack_trace.inspect
98
+ => [
99
+ #<StackTracy::EventInfo:0x0000010154b418 @event="c-call", @file="(pry)", @line=2, @singleton=false, @object=Kernel, @method="puts", @nsec=1344465432463251968>,
100
+ #<StackTracy::EventInfo:0x0000010154b350 @event="c-call", @file="(pry)", @line=2, @singleton=false, @object=IO, @method="puts", @nsec=1344465432463272960>,
101
+ #<StackTracy::EventInfo:0x0000010154b1c0 @event="c-call", @file="(pry)", @line=2, @singleton=false, @object=IO, @method="write", @nsec=1344465432463288064>,
102
+ #<StackTracy::EventInfo:0x0000010154b0a8 @event="c-return", @file="(pry)", @line=2, @singleton=false, @object=IO, @method="write", @nsec=1344465432463331072>,
103
+ #<StackTracy::EventInfo:0x0000010154af68 @event="c-call", @file="(pry)", @line=2, @singleton=false, @object=IO, @method="write", @nsec=1344465432463344896>,
104
+ #<StackTracy::EventInfo:0x0000010154ae50 @event="c-return", @file="(pry)", @line=2, @singleton=false, @object=IO, @method="write", @nsec=1344465432463365888>,
105
+ #<StackTracy::EventInfo:0x0000010154ad38 @event="c-return", @file="(pry)", @line=2, @singleton=false, @object=IO, @method="puts", @nsec=1344465432463378944>,
106
+ #<StackTracy::EventInfo:0x0000010154ac20 @event="c-return", @file="(pry)", @line=2, @singleton=false, @object=Kernel, @method="puts", @nsec=1344465432463390976>]
107
+ ]
108
+
109
+ `StackTracy.select` - returns (optionally filtered) recorded stack events for printing purposes (`array` of `Hash` instances)
110
+
111
+ [3] pry(main)> StackTracy.select.inspect
112
+ => [
113
+ {:event=>"c-call", :file=>"(pry)", :line=>2, :singleton=>false, :object=>Kernel, :method=>"puts", :nsec=>1344464282852728064, :call=>"Kernel#puts", :depth=>0, :duration=>0.000129024},
114
+ {:event=>"c-call", :file=>"(pry)", :line=>2, :singleton=>false, :object=>IO, :method=>"puts", :nsec=>1344464282852748032, :call=>"IO#puts", :depth=>1, :duration=>9.7024e-05},
115
+ {:event=>"c-call", :file=>"(pry)", :line=>2, :singleton=>false, :object=>IO, :method=>"write", :nsec=>1344464282852762112, :call=>"IO#write", :depth=>2, :duration=>3.4816e-05},
116
+ {:event=>"c-call", :file=>"(pry)", :line=>2, :singleton=>false, :object=>IO, :method=>"write", :nsec=>1344464282852811008, :call=>"IO#write", :depth=>2, :duration=>2.0992e-05}
117
+ ]
118
+ [4] pry(main)> StackTracy.select(%w(Kernel)).inspect
119
+ => [
120
+ {:event=>"c-call", :file=>"(pry)", :line=>2, :singleton=>false, :object=>Kernel, :method=>"puts", :nsec=>1344464282852728064, :call=>"Kernel#puts", :depth=>0, :duration=>0.000129024}
121
+ ]
122
+ [5] pry(main)> StackTracy.select("Kernel IO#puts").inspect
123
+ => [
124
+ {:event=>"c-call", :file=>"(pry)", :line=>2, :singleton=>false, :object=>Kernel, :method=>"puts", :nsec=>1344464282852728064, :call=>"Kernel#puts", :depth=>0, :duration=>0.000129024},
125
+ {:event=>"c-call", :file=>"(pry)", :line=>2, :singleton=>false, :object=>IO, :method=>"puts", :nsec=>1344464282852748032, :call=>"IO#puts", :depth=>1, :duration=>9.7024e-05}
126
+ ]
127
+
128
+ `StackTracy.print` - prints (optionally filtered) recorded stack events as a tree
129
+
130
+ [6] pry(main)> StackTracy.print
131
+ Kernel#puts <0.000094>
132
+ IO#puts <0.000069>
133
+ IO#write <0.000018>
134
+ IO#write <0.000016>
135
+ => nil
136
+
137
+ ### Writing data to CSV files
138
+
139
+ You can dump (optionally filtered) recorded stack events to a CSV file.
140
+
141
+ [1] pry(main)> StackTracy.start
142
+ [2] pry(main)> puts "testing"
143
+ => testing
144
+ [3] pry(main)> StackTracy.stop
145
+ Kernel#puts <0.000121>
146
+ IO#puts <0.000091>
147
+ IO#write <0.000032>
148
+ IO#write <0.000020>
149
+ => nil
150
+ [4] pry(main)> StackTracy.dump "result.csv"
151
+ => true
152
+
153
+ #### CSV sample file
154
+
155
+ This is what the contents of `result.csv` would look like:
156
+
157
+ event;file;line;singleton;object;method;nsec;call;depth;duration
158
+ c-call;(pry);2;false;Kernel;puts;1344466943040581120;Kernel#puts;0;0.000120832
159
+ c-call;(pry);2;false;IO;puts;1344466943040599040;IO#puts;1;9.1904e-05
160
+ c-call;(pry);2;false;IO;write;1344466943040613120;IO#write;2;3.2768e-05
161
+ c-call;(pry);2;false;IO;write;1344466943040658944;IO#write;2;1.9968e-05
162
+
163
+ ### Viewing stack events in your browser
164
+
165
+ You can easily view the dumped stack events within your browser by either calling the following within Ruby:
166
+
167
+ [1] pry(main)> StackTracy.open "some/dir/file.csv"
168
+
169
+ or the following within the Terminal:
170
+
171
+ $ tracy "some/dir/file.csv"
172
+
173
+ Your default browser will be launched in which the stack events will be displayed.
174
+
175
+ When passing no path, `tracy` will look for `stack_events-<random generated postfix>.csv` in either the default dump directory or in `Dir::tmpdir` and display it in the browser. When not found, it will display the last compiled stack tree when available:
176
+
177
+ $ tracy
178
+
179
+ ### Kernel#stack_tracy
180
+
181
+ As already mentioned, there is a convenience method called `stack_tracy` convenience method. The following shows a couple variants with its equivalent "normal implementation".
182
+
183
+ #### Without passing an argument
184
+
185
+ Record stack events executed within a block:
186
+
187
+ [1] pry(main)> stack_tracy do
188
+ [1] pry(main)> puts "testing"
189
+ [1] pry(main)> end
190
+
191
+ Its equivalent:
192
+
193
+ [1] pry(main)> StackTracy.start
194
+ [2] pry(main)> puts "testing"
195
+ [3] pry(main)> StackTracy.stop
196
+
197
+ #### Passing `:print`
198
+
199
+ Record stack events executed within a block and print the stack tree:
200
+
201
+ [1] pry(main)> stack_tracy :print do
202
+ [1] pry(main)> puts "testing"
203
+ [1] pry(main)> end
204
+
205
+ Its equivalent:
206
+
207
+ [1] pry(main)> StackTracy.start
208
+ [2] pry(main)> puts "testing"
209
+ [3] pry(main)> StackTracy.stop
210
+ [4] pry(main)> StackTracy.print
211
+
212
+ #### Passing `:dump`
213
+
214
+ Record stack events executed within a block and write the obtained data to `<default dump directory>/stack_events-<random generated postfix>.csv`:
215
+
216
+ [1] pry(main)> stack_tracy :dump do
217
+ [1] pry(main)> puts "testing"
218
+ [1] pry(main)> end
219
+
220
+ Its equivalent:
221
+
222
+ [1] pry(main)> StackTracy.start
223
+ [2] pry(main)> puts "testing"
224
+ [3] pry(main)> StackTracy.stop
225
+ [4] pry(main)> StackTracy.dump
226
+
227
+ #### Passing a target directory
228
+
229
+ Record stack events executed within a block and write the obtained data to `<passed directory>/stack_events-<random generated postfix>.csv`:
230
+
231
+ [1] pry(main)> stack_tracy Dir::tmpdir do
232
+ [1] pry(main)> puts "testing"
233
+ [1] pry(main)> end
234
+
235
+ Its equivalent:
236
+
237
+ [1] pry(main)> StackTracy.start
238
+ [2] pry(main)> puts "testing"
239
+ [3] pry(main)> StackTracy.stop
240
+ [4] pry(main)> StackTracy.dump Dir::tmpdir
241
+
242
+ #### Passing a CSV file path
243
+
244
+ Record stack events executed within a block and write the obtained data to the passed file path:
245
+
246
+ [1] pry(main)> stack_tracy "some/file.csv" do
247
+ [1] pry(main)> puts "testing"
248
+ [1] pry(main)> end
249
+
250
+ Its equivalent:
251
+
252
+ [1] pry(main)> StackTracy.start
253
+ [2] pry(main)> puts "testing"
254
+ [3] pry(main)> StackTracy.stop
255
+ [4] pry(main)> StackTracy.dump "some/file.csv"
256
+
257
+ #### Passing `:open`
258
+
259
+ Record stack events executed within a block and open the stack tree in your browser:
260
+
261
+ [1] pry(main)> stack_tracy :open do
262
+ [1] pry(main)> puts "testing"
263
+ [1] pry(main)> end
264
+
265
+ Its equivalent:
266
+
267
+ [1] pry(main)> StackTracy.start
268
+ [2] pry(main)> puts "testing"
269
+ [3] pry(main)> StackTracy.stop
270
+ [4] pry(main)> file = StackTracy.dump Dir::tmpdir
271
+ [5] pry(main)> StackTracy.open file
272
+
273
+ ## Hooking into Sinatra requests
274
+
275
+ You can easily hook `StackTracy` into [Sinatra](http://www.sinatrarb.com) requests. This is a complete working example:
276
+
277
+ require "sinatra"
278
+ require "stack_tracy"
279
+
280
+ use StackTracy::Sinatra
281
+
282
+ get "/" do
283
+ "Hello world!"
284
+ end
285
+
286
+ **Note**: Make sure you have the `sinatra` and `stack_tracy` gems installed.
287
+
288
+ Open the Sinatra application in your browser at [http://localhost:4567](http://localhost:4567) and open [http://localhost:4567/tracy](http://localhost:4567/tracy) afterwards and the complete stack tree will be displayed in your browser! ^^
289
+
290
+ ### Taking more control
291
+
292
+ I can imagine that you don't want to hook into every Sinatra request. So you can pass a block which will be yielded before every request. The request will traced when it does **note** return either `false` or `nil`:
293
+
294
+ use StackTracy::Sinatra do |path, params|
295
+ path == "/" #=> only trace "http://localhost:4567"
296
+ end
297
+
298
+ Also, you can determine what StackTracy has to do after the request has finished. It resembles the invocation of `stack_tracy`. The following will dump every request into the current directory:
299
+
300
+ use StackTracy::Sinatra, "."
301
+
302
+ This will immediately open the stack tree in your default browser after every traced request:
303
+
304
+ use StackTracy::Sinatra, :open do
305
+ path == "/paul/engel" #=> only trace and open stack tree when opening "http://localhost:4567/paul/engel"
306
+ end
307
+
308
+ Reduce the stack tree and open it immediately:
309
+
310
+ use StackTracy::Sinatra, :open, :only => "Foo*"
311
+
312
+ ## Using the console
313
+
314
+ The StackTracy repo is provided with `script/console` which you can use for development / testing purposes.
315
+
316
+ Run the following command in your console:
317
+
318
+ $ script/console
319
+ Loading development environment (StackTracy 0.1.0)
320
+ [1] pry(main)> stack_tracy :print do
321
+ [1] pry(main)* puts "testing"
322
+ [1] pry(main)* end
323
+ testing
324
+ Kernel#puts <0.000121>
325
+ IO#puts <0.000091>
326
+ IO#write <0.000032>
327
+ IO#write <0.000020>
328
+ => nil
329
+ [2] pry(main)>
330
+
331
+ ## Testing
332
+
333
+ Run the following command for testing:
334
+
335
+ $ rake
336
+
337
+ You can also run a single test:
338
+
339
+ $ ruby test/unit/test_tracy.rb
340
+
341
+ ## TODO
342
+
343
+ * Optimize C implementation performance when converting C data to Ruby objects within `stack_tracy_stop`
344
+ * Improve stack tree reduction by checking on method level
345
+ * Correct `StackTracy::PRESETS` regarding `:active_record` and `:data_mapper`
346
+ * Easily hook into Rails requests?
347
+
348
+ ## Contact me
349
+
350
+ For support, remarks and requests, please mail me at [paul.engel@holder.nl](mailto:paul.engel@holder.nl).
351
+
352
+ ## Credit
353
+
354
+ * Two functions within the StackTracy C implementation are taken from [ruby-prof](https://github.com/rdp/ruby-prof).
355
+ * The table sort within the Cumulatives tab is implemented with [TinySort](http://tinysort.sjeiti.com/).
356
+
357
+ ## License
358
+
359
+ Copyright (c) 2012 Paul Engel, released under the MIT license
360
+
361
+ [http://holder.nl](http://holder.nl) - [http://codehero.es](http://codehero.es) - [http://gettopup.com](http://gettopup.com) - [http://github.com/archan937](http://github.com/archan937) - [http://twitter.com/archan937](http://twitter.com/archan937) - [paul.engel@holder.nl](mailto:paul.engel@holder.nl)
362
+
363
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
364
+
365
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
366
+
367
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |test|
8
+ test.pattern = "test/**/test_*.rb"
9
+ end
10
+
11
+ desc "Run benchmarks"
12
+ task :benchmark do
13
+ system "ruby benchmarks/benchmark.rb"
14
+ end
15
+ task :bm => :benchmark
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,64 @@
1
+ #
2
+ # This benchmark is compared with ruby-prof:
3
+ #
4
+ # require 'ruby-prof'
5
+ # require 'benchmark'
6
+ #
7
+ # $stdout.sync = true
8
+ #
9
+ # puts Benchmark.realtime {
10
+ # 100000.times { print "" }
11
+ # }
12
+ #
13
+ # puts Benchmark.realtime {
14
+ # RubyProf.profile do
15
+ # 100000.times { print "" }
16
+ # end
17
+ # }
18
+ #
19
+ # for n in [5, 100] do
20
+ # n.times { Thread.new { sleep }}
21
+ # puts Benchmark.realtime {
22
+ # RubyProf.profile do
23
+ # 100000.times { print "" }
24
+ # end
25
+ # }
26
+ # end
27
+ #
28
+ # $ ruby ruby-prof/benchmarks/benchmark.rb
29
+ # 0.061315
30
+ # 1.201144
31
+ # 1.404983
32
+ # 6.558329
33
+
34
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
35
+
36
+ require "benchmark"
37
+ require "stack_tracy"
38
+
39
+ $stdout.sync = true
40
+
41
+ puts Benchmark.realtime {
42
+ 100000.times { print "" }
43
+ }
44
+
45
+ puts Benchmark.realtime {
46
+ stack_tracy do
47
+ 100000.times { print "" }
48
+ end
49
+ }
50
+
51
+ for n in [5, 100] do
52
+ n.times { Thread.new { sleep }}
53
+ puts Benchmark.realtime {
54
+ stack_tracy do
55
+ 100000.times { print "" }
56
+ end
57
+ }
58
+ end
59
+
60
+ # $ ruby stack_tracy/benchmarks/benchmark.rb
61
+ # 0.05799508094787598
62
+ # 2.315906047821045
63
+ # 2.786252021789551
64
+ # 5.190935850143433