stackprof 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e9663289cf7257e1ce593f22f1bc635b0d4406d
4
- data.tar.gz: 56484ba1a6c48b2c9c41bb056500b7aa432db974
3
+ metadata.gz: c4d9be2e4ad16929b326e3a1e86c841ad0e13dc6
4
+ data.tar.gz: 826eff5e56b4ae6c03c613dd9937a5ae396efa98
5
5
  SHA512:
6
- metadata.gz: 4a0062a181c777976147420395729d65eff6fa4ef10a6624153629bcaed7a5ed8c6b071e2e30176d6a28b3d6c0dcd4ed1ab5b4cc8373fbdbc814ed57dff20a93
7
- data.tar.gz: 1e004d7757b016d00cda696be9fe6a4769aa11b97651228069d63df4193219c16bbc2f9c44800234d9d98855db04dde6f1cbcc68a7c1913949c43f18c68a419e
6
+ metadata.gz: 6153df14d977ffa0b1445950de2e779a8e8dd6606e665ad22bad348619b16659862bce078d1c12eb0d48f6bb90a5c0bd8ae73bc2c4d861466d6f16cda285d4c1
7
+ data.tar.gz: 06ccf3dc44113e69b544aab7d9c4820daec59ec4b0d3b5e9784960d52b6ead75907f320d66cf4084319567f890862e4ab6954211e1b5c09d114f8553942e51ad
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  /tmp
2
2
  /lib/stackprof.bundle
3
+ /lib/stackprof.so
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stackprof (0.1.0)
4
+ stackprof (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -7,16 +7,17 @@ and written as a replacement for [perftools.rb](https://github.com/tmm1/perftool
7
7
 
8
8
  ### sampling
9
9
 
10
- three sampling modes are supported:
10
+ four sampling modes are supported:
11
11
 
12
- - cpu time (using `ITIMER_PROF` and `SIGPROF`)
13
- - wall time (using `ITIMER_REAL` and `SIGALRM`)
14
- - object allocation (using `RUBY_INTERNAL_EVENT_NEWOBJ`)
12
+ - :wall (using `ITIMER_REAL` and `SIGALRM`) [default mode]
13
+ - :cpu (using `ITIMER_PROF` and `SIGPROF`)
14
+ - :object (using `RUBY_INTERNAL_EVENT_NEWOBJ`)
15
+ - :custom (user-defined via `StackProf.sample`)
15
16
 
16
17
  samplers have a tuneable interval which can be used to reduce overhead or increase granularity:
17
18
 
18
- - cpu time: sample every _interval_ microseconds of cpu activity (default: 10000 = 10 milliseconds)
19
- - wall time: sample every _interval_ microseconds of wallclock time (default: 10000)
19
+ - wall time: sample every _interval_ microseconds of wallclock time (default: 1000)
20
+ - cpu time: sample every _interval_ microseconds of cpu activity (default: 1000 = 1 millisecond)
20
21
  - object allocation: sample every _interval_ allocations (default: 1)
21
22
 
22
23
  samples are taken using a combination of three new C-APIs in ruby 2.1:
@@ -137,11 +138,12 @@ block in A#math (/Users/tmm1/code/stackprof/sample.rb:21)
137
138
 
138
139
  ### usage
139
140
 
140
- the profiler is compiled as a C-extension and exposes a simple api: `StackProf.run(mode, interval)`.
141
+ the profiler is compiled as a C-extension and exposes a simple api: `StackProf.run(mode: [:cpu|:wall|:object])`.
141
142
  the `run` method takes a block of code and returns a profile as a simple hash.
142
143
 
143
144
  ``` ruby
144
- profile = StackProf.run(sampling_mode, sampling_interval) do
145
+ # sample after every 1ms of cpu activity
146
+ profile = StackProf.run(mode: :cpu, interval: 1000) do
145
147
  MyCode.execute
146
148
  end
147
149
  ```
@@ -154,10 +156,12 @@ the format itself is very simple. it contains a header and a list of frames. eac
154
156
  identifying information such as its name, file and line. the frame also contains sampling data, including per-line
155
157
  samples, and a list of relationships to other frames represented as weighted edges.
156
158
 
157
- ```
159
+ ``` ruby
158
160
  {:version=>1.0,
159
- :mode=>"cpu(1000)",
161
+ :mode=>:cpu,
162
+ :inteval=>1000,
160
163
  :samples=>188,
164
+ :missed_samples=>0,
161
165
  :frames=>
162
166
  {70346498324780=>
163
167
  {:name=>"A#pow",
@@ -184,14 +188,17 @@ divided up between its callee edges. all 91 calls to `A#pow` came from `A#initia
184
188
 
185
189
  ### advanced usage
186
190
 
187
- the profiler can be started, paused, resumed and stopped manually for greater control.
191
+ the profiler can be started and stopped manually. results are accumulated until retrieval, across
192
+ multiple start/stop invocations.
188
193
 
189
- ```
194
+ ``` ruby
190
195
  StackProf.running?
191
196
  StackProf.start
192
- StackProf.pause
193
- StackProf.paused?
194
- StackProf.resume
195
197
  StackProf.stop
196
198
  StackProf.results
197
199
  ```
200
+
201
+ ### todo
202
+
203
+ * file/iseq blacklist
204
+ * restore signal handlers on stop
data/bin/stackprof CHANGED
@@ -1,3 +1,46 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'optparse'
3
3
  require 'stackprof'
4
+
5
+ options = {
6
+ :format => :text,
7
+ :sort => false,
8
+ :limit => 30
9
+ }
10
+
11
+ OptionParser.new(ARGV) do |o|
12
+ o.banner = 'stackprof-report [file.dump]+'
13
+
14
+ o.on('--text', 'Text output (default)'){ options[:format] = :text }
15
+ o.on('--callgrind'){ options[:format] = :callgrind }
16
+ o.on('--graphviz'){ options[:format] = :graphviz }
17
+ o.on('--file [grep]'){ |f| options[:format] = :file; options[:filter] = f }
18
+ o.on('--files'){ |f| options[:format] = :files }
19
+ o.on('--method [grep]'){ |f| options[:format] = :method; options[:filter] = f }
20
+ o.on('--debug'){ options[:format] = :debug }
21
+ o.on('--sort-total'){ options[:sort] = true }
22
+ o.on('--limit [num]', Integer){ |n| options[:limit] = n }
23
+ end.parse!
24
+
25
+ reports = []
26
+ while ARGV.size > 0
27
+ reports << StackProf::Report.new(Marshal.load(IO.binread(ARGV.pop)))
28
+ end
29
+ report = reports.inject(:+)
30
+
31
+ case options[:format]
32
+ when :text
33
+ report.print_text(options[:sort], options[:limit])
34
+ when :callgrind
35
+ report.print_callgrind
36
+ when :graphviz
37
+ report.print_graphviz
38
+ when :method
39
+ report.print_method(options[:filter])
40
+ when :file
41
+ report.print_file(options[:filter])
42
+ when :files
43
+ report.print_files(options[:sort], options[:limit])
44
+ else
45
+ raise ArgumentError, "unknown format: #{options[:format]}"
46
+ end
data/ext/extconf.rb CHANGED
@@ -1,2 +1,9 @@
1
1
  require 'mkmf'
2
- create_makefile('stackprof')
2
+ if have_func('rb_postponed_job_register_one') &&
3
+ have_func('rb_profile_frames') &&
4
+ have_func('rb_tracepoint_new') &&
5
+ have_const('RUBY_INTERNAL_EVENT_NEWOBJ')
6
+ create_makefile('stackprof')
7
+ else
8
+ fail 'missing API: are you using ruby 2.1+?'
9
+ end
data/ext/stackprof.c CHANGED
@@ -15,7 +15,9 @@
15
15
  #include <ruby/ruby.h>
16
16
  #include <ruby/debug.h>
17
17
  #include <ruby/st.h>
18
+ #include <signal.h>
18
19
  #include <sys/time.h>
20
+ #include <pthread.h>
19
21
 
20
22
  #define BUF_SIZE 2048
21
23
 
@@ -27,76 +29,112 @@ typedef struct {
27
29
  } frame_data_t;
28
30
 
29
31
  static struct {
30
- enum {
31
- PROF_NONE = 0,
32
- PROF_CPU,
33
- PROF_WALL,
34
- PROF_OBJECT
35
- } type;
32
+ int running;
33
+ VALUE mode;
34
+ VALUE interval;
36
35
 
36
+ size_t overall_signals;
37
37
  size_t overall_samples;
38
+ size_t during_gc;
38
39
  st_table *frames;
39
40
 
40
41
  VALUE frames_buffer[BUF_SIZE];
41
42
  int lines_buffer[BUF_SIZE];
42
- } _results;
43
+ } _stackprof;
43
44
 
44
- static VALUE sym_object, sym_wall, sym_name, sym_file, sym_line;
45
- static VALUE sym_samples, sym_total_samples, sym_edges, sym_lines;
46
- static VALUE sym_version, sym_mode, sym_frames;
47
- static VALUE objtracer;
45
+ static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
46
+ static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
47
+ static VALUE sym_version, sym_mode, sym_interval, sym_frames;
48
+ static VALUE sym_gc_samples, objtracer;
48
49
  static VALUE gc_hook;
50
+ static VALUE rb_mStackProf;
49
51
 
50
52
  static void stackprof_newobj_handler(VALUE, void*);
51
53
  static void stackprof_signal_handler(int sig, siginfo_t* sinfo, void* ucontext);
52
54
 
53
55
  static VALUE
54
- stackprof_start(VALUE self, VALUE type, VALUE usec)
56
+ stackprof_start(int argc, VALUE *argv, VALUE self)
55
57
  {
56
- if (type == sym_object) {
57
- _results.type = PROF_OBJECT;
58
+ struct sigaction sa;
59
+ struct itimerval timer;
60
+ VALUE opts = Qnil, mode = Qnil, interval = Qnil;
61
+
62
+ if (_stackprof.running)
63
+ return Qfalse;
64
+
65
+ rb_scan_args(argc, argv, "0:", &opts);
66
+
67
+ if (RTEST(opts)) {
68
+ mode = rb_hash_aref(opts, sym_mode);
69
+ interval = rb_hash_aref(opts, sym_interval);
70
+ }
71
+ if (!RTEST(mode)) mode = sym_wall;
72
+
73
+ if (!_stackprof.frames) {
74
+ _stackprof.frames = st_init_numtable();
75
+ _stackprof.overall_signals = 0;
76
+ _stackprof.overall_samples = 0;
77
+ _stackprof.during_gc = 0;
78
+ }
79
+
80
+ if (mode == sym_object) {
81
+ if (!RTEST(interval)) interval = INT2FIX(1);
82
+
58
83
  objtracer = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, stackprof_newobj_handler, 0);
59
84
  rb_tracepoint_enable(objtracer);
60
- } else {
61
- if (type == sym_wall)
62
- _results.type = PROF_WALL;
63
- else
64
- _results.type = PROF_CPU;
85
+ } else if (mode == sym_wall || mode == sym_cpu) {
86
+ if (!RTEST(interval)) interval = INT2FIX(1000);
65
87
 
66
- struct sigaction sa;
67
88
  sa.sa_sigaction = stackprof_signal_handler;
68
89
  sa.sa_flags = SA_RESTART | SA_SIGINFO;
69
90
  sigemptyset(&sa.sa_mask);
70
- sigaction(_results.type == PROF_WALL ? SIGALRM : SIGPROF, &sa, NULL);
91
+ sigaction(mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
71
92
 
72
- struct itimerval timer;
73
93
  timer.it_interval.tv_sec = 0;
74
- timer.it_interval.tv_usec = NUM2LONG(usec);
94
+ timer.it_interval.tv_usec = NUM2LONG(interval);
75
95
  timer.it_value = timer.it_interval;
76
- setitimer(_results.type == PROF_WALL ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
96
+ setitimer(mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
97
+ } else if (mode == sym_custom) {
98
+ /* sampled manually */
99
+ interval = Qnil;
100
+ } else {
101
+ rb_raise(rb_eArgError, "unknown profiler mode");
77
102
  }
78
103
 
79
- return Qnil;
104
+ _stackprof.running = 1;
105
+ _stackprof.mode = mode;
106
+ _stackprof.interval = interval;
107
+
108
+ return Qtrue;
80
109
  }
81
110
 
82
111
  static VALUE
83
112
  stackprof_stop(VALUE self)
84
113
  {
85
- if (_results.type == PROF_OBJECT) {
114
+ struct sigaction sa;
115
+ struct itimerval timer;
116
+
117
+ if (!_stackprof.running)
118
+ return Qfalse;
119
+ _stackprof.running = 0;
120
+
121
+ if (_stackprof.mode == sym_object) {
86
122
  rb_tracepoint_disable(objtracer);
87
- } else {
88
- struct itimerval timer;
123
+ } else if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
89
124
  memset(&timer, 0, sizeof(timer));
90
- setitimer(_results.type == PROF_WALL ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
125
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
91
126
 
92
- struct sigaction sa;
93
127
  sa.sa_handler = SIG_IGN;
94
128
  sa.sa_flags = SA_RESTART;
95
129
  sigemptyset(&sa.sa_mask);
96
- sigaction(_results.type == PROF_WALL ? SIGALRM : SIGPROF, &sa, NULL);
130
+ sigaction(_stackprof.mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
131
+ } else if (_stackprof.mode == sym_custom) {
132
+ /* sampled manually */
133
+ } else {
134
+ rb_raise(rb_eArgError, "unknown profiler mode");
97
135
  }
98
136
 
99
- return Qnil;
137
+ return Qtrue;
100
138
  }
101
139
 
102
140
  static int
@@ -114,8 +152,11 @@ frame_lines_i(st_data_t key, st_data_t val, st_data_t arg)
114
152
  {
115
153
  VALUE lines = (VALUE)arg;
116
154
 
117
- intptr_t weight = (intptr_t)val;
118
- rb_hash_aset(lines, INT2FIX(key), INT2FIX(weight));
155
+ size_t weight = (size_t)val;
156
+ size_t total = weight & (~(size_t)0 << (8*SIZEOF_SIZE_T/2));
157
+ weight -= total;
158
+ total = total >> (8*SIZEOF_SIZE_T/2);
159
+ rb_hash_aset(lines, INT2FIX(key), rb_ary_new3(2, ULONG2NUM(total), ULONG2NUM(weight)));
119
160
  return ST_CONTINUE;
120
161
  }
121
162
 
@@ -127,7 +168,6 @@ frame_i(st_data_t key, st_data_t val, st_data_t arg)
127
168
  VALUE results = (VALUE)arg;
128
169
  VALUE details = rb_hash_new();
129
170
  VALUE name, file, edges, lines;
130
- VALUE label, method_name;
131
171
  VALUE line;
132
172
 
133
173
  rb_hash_aset(results, rb_obj_id(frame), details);
@@ -167,85 +207,114 @@ frame_i(st_data_t key, st_data_t val, st_data_t arg)
167
207
  }
168
208
 
169
209
  static VALUE
170
- stackprof_run(VALUE self, VALUE type, VALUE usec)
210
+ stackprof_results(VALUE self)
171
211
  {
172
212
  VALUE results, frames;
173
- rb_need_block();
174
- if (!_results.frames)
175
- _results.frames = st_init_numtable();
176
- _results.overall_samples = 0;
177
213
 
178
- stackprof_start(self, type, usec);
179
- rb_yield(Qundef);
180
- stackprof_stop(self);
214
+ if (!_stackprof.frames || _stackprof.running)
215
+ return Qnil;
181
216
 
182
217
  results = rb_hash_new();
183
- rb_hash_aset(results, sym_version, DBL2NUM(1.0));
184
- rb_hash_aset(results, sym_mode, rb_sprintf("%"PRIsVALUE"(%"PRIsVALUE")", type, usec));
185
- rb_hash_aset(results, sym_samples, SIZET2NUM(_results.overall_samples));
218
+ rb_hash_aset(results, sym_version, DBL2NUM(1.1));
219
+ rb_hash_aset(results, sym_mode, _stackprof.mode);
220
+ rb_hash_aset(results, sym_interval, _stackprof.interval);
221
+ rb_hash_aset(results, sym_samples, SIZET2NUM(_stackprof.overall_samples));
222
+ rb_hash_aset(results, sym_gc_samples, SIZET2NUM(_stackprof.during_gc));
223
+ rb_hash_aset(results, sym_missed_samples, SIZET2NUM(_stackprof.overall_signals - _stackprof.overall_samples));
186
224
 
187
225
  frames = rb_hash_new();
188
226
  rb_hash_aset(results, sym_frames, frames);
189
- st_foreach(_results.frames, frame_i, (st_data_t)frames);
227
+ st_foreach(_stackprof.frames, frame_i, (st_data_t)frames);
228
+
229
+ st_free_table(_stackprof.frames);
230
+ _stackprof.frames = NULL;
190
231
 
191
232
  return results;
192
233
  }
193
234
 
235
+ static VALUE
236
+ stackprof_run(int argc, VALUE *argv, VALUE self)
237
+ {
238
+ rb_need_block();
239
+ stackprof_start(argc, argv, self);
240
+ rb_ensure(rb_yield, Qundef, stackprof_stop, self);
241
+ return stackprof_results(self);
242
+ }
243
+
244
+ static VALUE
245
+ stackprof_running_p(VALUE self)
246
+ {
247
+ return _stackprof.running ? Qtrue : Qfalse;
248
+ }
249
+
194
250
  static inline frame_data_t *
195
251
  sample_for(VALUE frame)
196
252
  {
197
253
  st_data_t key = (st_data_t)frame, val = 0;
198
254
  frame_data_t *frame_data;
199
255
 
200
- if (st_lookup(_results.frames, key, &val)) {
256
+ if (st_lookup(_stackprof.frames, key, &val)) {
201
257
  frame_data = (frame_data_t *)val;
202
258
  } else {
203
259
  frame_data = ALLOC_N(frame_data_t, 1);
204
260
  MEMZERO(frame_data, frame_data_t, 1);
205
261
  val = (st_data_t)frame_data;
206
- st_insert(_results.frames, key, val);
262
+ st_insert(_stackprof.frames, key, val);
207
263
  }
208
264
 
209
265
  return frame_data;
210
266
  }
211
267
 
268
+ static int
269
+ numtable_increment_callback(st_data_t *key, st_data_t *value, st_data_t arg, int existing)
270
+ {
271
+ size_t *weight = (size_t *)value;
272
+ size_t increment = (size_t)arg;
273
+
274
+ if (existing)
275
+ (*weight) += increment;
276
+ else
277
+ *weight = increment;
278
+
279
+ return ST_CONTINUE;
280
+ }
281
+
212
282
  void
213
- st_numtable_increment(st_table *table, st_data_t key)
283
+ st_numtable_increment(st_table *table, st_data_t key, size_t increment)
214
284
  {
215
- intptr_t weight = 0;
216
- st_lookup(table, key, (st_data_t *)&weight);
217
- weight++;
218
- st_insert(table, key, weight);
285
+ st_update(table, key, numtable_increment_callback, (st_data_t)increment);
219
286
  }
220
287
 
221
- static void
222
- stackprof_sample()
288
+ void
289
+ stackprof_record_sample()
223
290
  {
224
291
  int num, i;
225
- VALUE prev_frame;
226
- st_data_t key;
292
+ VALUE prev_frame = Qnil;
227
293
 
228
- _results.overall_samples++;
229
- num = rb_profile_frames(0, sizeof(_results.frames_buffer), _results.frames_buffer, _results.lines_buffer);
294
+ _stackprof.overall_samples++;
295
+ num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer), _stackprof.frames_buffer, _stackprof.lines_buffer);
230
296
 
231
297
  for (i = 0; i < num; i++) {
232
- int line = _results.lines_buffer[i];
233
- VALUE frame = _results.frames_buffer[i];
298
+ int line = _stackprof.lines_buffer[i];
299
+ VALUE frame = _stackprof.frames_buffer[i];
234
300
  frame_data_t *frame_data = sample_for(frame);
235
301
 
236
302
  frame_data->total_samples++;
237
303
 
238
304
  if (i == 0) {
239
305
  frame_data->caller_samples++;
240
- if (line > 0) {
241
- if (!frame_data->lines)
242
- frame_data->lines = st_init_numtable();
243
- st_numtable_increment(frame_data->lines, (st_data_t)line);
244
- }
245
306
  } else {
246
307
  if (!frame_data->edges)
247
308
  frame_data->edges = st_init_numtable();
248
- st_numtable_increment(frame_data->edges, (st_data_t)prev_frame);
309
+ st_numtable_increment(frame_data->edges, (st_data_t)prev_frame, 1);
310
+ }
311
+
312
+ if (line > 0) {
313
+ if (!frame_data->lines)
314
+ frame_data->lines = st_init_numtable();
315
+ size_t half = (size_t)1<<(8*SIZEOF_SIZE_T/2);
316
+ size_t increment = i == 0 ? half + 1 : half;
317
+ st_numtable_increment(frame_data->lines, (st_data_t)line, increment);
249
318
  }
250
319
 
251
320
  prev_frame = frame;
@@ -257,59 +326,122 @@ stackprof_job_handler(void *data)
257
326
  {
258
327
  static int in_signal_handler = 0;
259
328
  if (in_signal_handler) return;
329
+ if (!_stackprof.running) return;
260
330
 
261
331
  in_signal_handler++;
262
- stackprof_sample();
332
+ stackprof_record_sample();
263
333
  in_signal_handler--;
264
334
  }
265
335
 
266
336
  static void
267
337
  stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
268
338
  {
269
- rb_postponed_job_register_one(0, stackprof_job_handler, 0);
339
+ _stackprof.overall_signals++;
340
+ if (rb_during_gc())
341
+ _stackprof.during_gc++, _stackprof.overall_samples++;
342
+ else
343
+ rb_postponed_job_register_one(0, stackprof_job_handler, 0);
270
344
  }
271
345
 
272
346
  static void
273
347
  stackprof_newobj_handler(VALUE tpval, void *data)
274
348
  {
349
+ _stackprof.overall_signals++;
350
+ stackprof_job_handler(0);
351
+ }
352
+
353
+ static VALUE
354
+ stackprof_sample(VALUE self)
355
+ {
356
+ if (!_stackprof.running)
357
+ return Qfalse;
358
+
359
+ _stackprof.overall_signals++;
275
360
  stackprof_job_handler(0);
361
+ return Qtrue;
276
362
  }
277
363
 
278
364
  static int
279
365
  frame_mark_i(st_data_t key, st_data_t val, st_data_t arg)
280
366
  {
281
367
  VALUE frame = (VALUE)key;
282
- rb_gc_mark_maybe(frame);
368
+ rb_gc_mark(frame);
283
369
  return ST_CONTINUE;
284
370
  }
285
371
 
286
372
  static void
287
- stackprof_gc_mark()
373
+ stackprof_gc_mark(void *data)
374
+ {
375
+ if (_stackprof.frames)
376
+ st_foreach(_stackprof.frames, frame_mark_i, 0);
377
+ }
378
+
379
+ static void
380
+ stackprof_atfork_prepare(void)
288
381
  {
289
- if (_results.frames)
290
- st_foreach(_results.frames, frame_mark_i, 0);
382
+ struct itimerval timer;
383
+ if (_stackprof.running) {
384
+ if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
385
+ memset(&timer, 0, sizeof(timer));
386
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
387
+ }
388
+ }
389
+ }
390
+
391
+ static void
392
+ stackprof_atfork_parent(void)
393
+ {
394
+ struct itimerval timer;
395
+ if (_stackprof.running) {
396
+ if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
397
+ timer.it_interval.tv_sec = 0;
398
+ timer.it_interval.tv_usec = NUM2LONG(_stackprof.interval);
399
+ timer.it_value = timer.it_interval;
400
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
401
+ }
402
+ }
403
+ }
404
+
405
+ static void
406
+ stackprof_atfork_child(void)
407
+ {
408
+ stackprof_stop(rb_mStackProf);
291
409
  }
292
410
 
293
411
  void
294
412
  Init_stackprof(void)
295
413
  {
296
414
  sym_object = ID2SYM(rb_intern("object"));
297
- sym_name = ID2SYM(rb_intern("name"));
415
+ sym_custom = ID2SYM(rb_intern("custom"));
298
416
  sym_wall = ID2SYM(rb_intern("wall"));
417
+ sym_cpu = ID2SYM(rb_intern("cpu"));
418
+ sym_name = ID2SYM(rb_intern("name"));
299
419
  sym_file = ID2SYM(rb_intern("file"));
300
420
  sym_line = ID2SYM(rb_intern("line"));
301
421
  sym_total_samples = ID2SYM(rb_intern("total_samples"));
422
+ sym_gc_samples = ID2SYM(rb_intern("gc_samples"));
423
+ sym_missed_samples = ID2SYM(rb_intern("missed_samples"));
302
424
  sym_samples = ID2SYM(rb_intern("samples"));
303
425
  sym_edges = ID2SYM(rb_intern("edges"));
304
426
  sym_lines = ID2SYM(rb_intern("lines"));
305
427
  sym_version = ID2SYM(rb_intern("version"));
306
428
  sym_mode = ID2SYM(rb_intern("mode"));
429
+ sym_interval = ID2SYM(rb_intern("interval"));
307
430
  sym_frames = ID2SYM(rb_intern("frames"));
308
431
 
309
432
  gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, NULL);
310
433
  rb_global_variable(&gc_hook);
311
434
 
312
- VALUE rb_mStackProf = rb_define_module("StackProf");
313
- rb_define_singleton_method(rb_mStackProf, "run", stackprof_run, 2);
435
+ rb_mStackProf = rb_define_module("StackProf");
436
+ rb_define_singleton_method(rb_mStackProf, "running?", stackprof_running_p, 0);
437
+ rb_define_singleton_method(rb_mStackProf, "run", stackprof_run, -1);
438
+ rb_define_singleton_method(rb_mStackProf, "start", stackprof_start, -1);
439
+ rb_define_singleton_method(rb_mStackProf, "stop", stackprof_stop, 0);
440
+ rb_define_singleton_method(rb_mStackProf, "results", stackprof_results, 0);
441
+ rb_define_singleton_method(rb_mStackProf, "sample", stackprof_sample, 0);
442
+
314
443
  rb_autoload(rb_mStackProf, rb_intern_const("Report"), "stackprof/report.rb");
444
+ rb_autoload(rb_mStackProf, rb_intern_const("Middleware"), "stackprof/middleware.rb");
445
+
446
+ pthread_atfork(stackprof_atfork_prepare, stackprof_atfork_parent, stackprof_atfork_child);
315
447
  }
@@ -0,0 +1,31 @@
1
+ require 'fileutils'
2
+
3
+ module StackProf
4
+ class Middleware
5
+ def initialize(app)
6
+ @app = app
7
+ at_exit{ Middleware.save if Middleware.enabled? }
8
+ end
9
+
10
+ def call(env)
11
+ StackProf.start(mode: :cpu, interval: 1000) if self.class.enabled?
12
+ @app.call(env)
13
+ ensure
14
+ StackProf.stop if self.class.enabled?
15
+ end
16
+
17
+ class << self
18
+ attr_accessor :enabled
19
+ alias enabled? enabled
20
+
21
+ def save
22
+ if results = StackProf.results
23
+ FileUtils.mkdir_p('tmp')
24
+ File.open("tmp/stackprof-#{results[:mode]}-#{Process.pid}-#{Time.now.to_i}.dump", 'w') do |f|
25
+ f.write Marshal.dump(results)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,71 +1,279 @@
1
1
  require 'pp'
2
+ require 'digest/md5'
2
3
 
3
4
  module StackProf
4
5
  class Report
5
6
  def initialize(data)
6
7
  @data = data
8
+
9
+ frames = {}
10
+ @data[:frames].each{ |k,v| frames[k.to_s] = v }
11
+ @data[:frames] = frames
12
+ end
13
+ attr_reader :data
14
+
15
+ def frames(sort_by_total=false)
16
+ Hash[ *@data[:frames].sort_by{ |iseq, stats| -stats[sort_by_total ? :total_samples : :samples] }.flatten(1) ]
17
+ end
18
+
19
+ def normalized_frames
20
+ id2hash = {}
21
+ @data[:frames].each do |frame, info|
22
+ id2hash[frame.to_s] = info[:hash] = Digest::MD5.hexdigest("#{info[:name]}#{info[:file]}#{info[:line]}")
23
+ end
24
+ @data[:frames].inject(Hash.new) do |hash, (frame, info)|
25
+ info = hash[id2hash[frame.to_s]] = info.dup
26
+ info[:edges] = info[:edges].inject(Hash.new){ |edges, (edge, weight)| edges[id2hash[edge.to_s]] = weight; edges } if info[:edges]
27
+ hash
28
+ end
7
29
  end
8
30
 
9
- def frames
10
- @data[:frames].sort_by{ |iseq, stats| -stats[:samples] }
31
+ def version
32
+ @data[:version]
33
+ end
34
+
35
+ def modeline
36
+ "#{@data[:mode]}(#{@data[:interval]})"
11
37
  end
12
38
 
13
39
  def overall_samples
14
40
  @data[:samples]
15
41
  end
16
42
 
43
+ def max_samples
44
+ @data[:max_samples] ||= frames.max_by{ |addr, frame| frame[:samples] }.last[:samples]
45
+ end
46
+
47
+ def files
48
+ @data[:files] ||= @data[:frames].inject(Hash.new) do |hash, (addr, frame)|
49
+ if file = frame[:file] and lines = frame[:lines]
50
+ hash[file] ||= Hash.new
51
+ lines.each do |line, weight|
52
+ hash[file][line] = add_lines(hash[file][line], weight)
53
+ end
54
+ end
55
+ hash
56
+ end
57
+ end
58
+
59
+ def add_lines(a, b)
60
+ return b if a.nil?
61
+ return a+b if a.is_a? Fixnum
62
+ return [ a[0], a[1]+b ] if b.is_a? Fixnum
63
+ [ a[0]+b[0], a[1]+b[1] ]
64
+ end
65
+
17
66
  def print_debug
18
67
  pp @data
19
68
  end
20
69
 
21
- def print_graphviz
22
- f = STDOUT
70
+ def print_graphviz(filter = nil, f = STDOUT)
71
+ if filter
72
+ mark_stack = []
73
+ list = frames
74
+ list.each{ |addr, frame| mark_stack << addr if frame[:name] =~ filter }
75
+ while addr = mark_stack.pop
76
+ frame = list[addr]
77
+ unless frame[:marked]
78
+ $stderr.puts frame[:edges].inspect
79
+ mark_stack += frame[:edges].map{ |addr, weight| addr.to_s if list[addr.to_s][:total_samples] <= weight*1.2 }.compact if frame[:edges]
80
+ frame[:marked] = true
81
+ end
82
+ end
83
+ list = list.select{ |addr, frame| frame[:marked] }
84
+ list.each{ |addr, frame| frame[:edges] && frame[:edges].delete_if{ |k,v| list[k.to_s].nil? } }
85
+ list
86
+ else
87
+ list = frames
88
+ end
89
+
23
90
  f.puts "digraph profile {"
24
- frames.each do |frame, info|
91
+ list.each do |frame, info|
25
92
  call, total = info.values_at(:samples, :total_samples)
26
93
  sample = ''
27
94
  sample << "#{call} (%2.1f%%)\\rof " % (call*100.0/overall_samples) if call < total
28
95
  sample << "#{total} (%2.1f%%)\\r" % (total*100.0/overall_samples)
29
- size = (1.0 * call / overall_samples) * 28 + 10
96
+ fontsize = (1.0 * call / max_samples) * 28 + 10
97
+ size = (1.0 * total / overall_samples) * 2.0 + 0.5
30
98
 
31
- f.puts " #{frame} [size=#{size}] [fontsize=#{size}] [shape=box] [label=\"#{info[:name]}\\n#{sample}\"];"
99
+ f.puts " #{frame} [size=#{size}] [fontsize=#{fontsize}] [penwidth=\"#{size}\"] [shape=box] [label=\"#{info[:name]}\\n#{sample}\"];"
32
100
  if edges = info[:edges]
33
101
  edges.each do |edge, weight|
34
- size = (1.0 * weight / overall_samples) * 28
35
- f.puts " #{frame} -> #{edge} [label=\"#{weight}\"];"
102
+ size = (1.0 * weight / overall_samples) * 2.0 + 0.5
103
+ f.puts " #{frame} -> #{edge} [label=\"#{weight}\"] [weight=\"#{weight}\"] [penwidth=\"#{size}\"];"
36
104
  end
37
105
  end
38
106
  end
39
107
  f.puts "}"
40
108
  end
41
109
 
42
- def print_text
43
- printf "% 10s (pct) % 10s (pct) FRAME\n" % ["TOTAL", "SAMPLES"]
44
- frames.each do |frame, info|
110
+ def print_text(sort_by_total=false, limit=nil, f = STDOUT)
111
+ f.puts "=================================="
112
+ f.printf " Mode: #{modeline}\n"
113
+ f.printf " Samples: #{@data[:samples]} (%.2f%% miss rate)\n", 100.0*@data[:missed_samples]/(@data[:missed_samples]+@data[:samples])
114
+ f.printf " GC: #{@data[:gc_samples]} (%.2f%%)\n", 100.0*@data[:gc_samples]/@data[:samples]
115
+ f.puts "=================================="
116
+ f.printf "% 10s (pct) % 10s (pct) FRAME\n" % ["TOTAL", "SAMPLES"]
117
+ list = frames(sort_by_total)
118
+ list = list.first(limit) if limit
119
+ list.each do |frame, info|
45
120
  call, total = info.values_at(:samples, :total_samples)
46
- printf "% 10d % 8s % 10d % 8s %s\n", total, "(%2.1f%%)" % (total*100.0/overall_samples), call, "(%2.1f%%)" % (call*100.0/overall_samples), info[:name]
121
+ f.printf "% 10d % 8s % 10d % 8s %s\n", total, "(%2.1f%%)" % (total*100.0/overall_samples), call, "(%2.1f%%)" % (call*100.0/overall_samples), info[:name]
47
122
  end
48
123
  end
49
124
 
50
- def print_source(name)
125
+ def print_callgrind(f = STDOUT)
126
+ f.puts "version: 1"
127
+ f.puts "creator: stackprof"
128
+ f.puts "pid: 0"
129
+ f.puts "cmd: ruby"
130
+ f.puts "part: 1"
131
+ f.puts "desc: mode: #{modeline}"
132
+ f.puts "desc: missed: #{@data[:missed_samples]})"
133
+ f.puts "positions: line"
134
+ f.puts "events: Instructions"
135
+ f.puts "summary: #{@data[:samples]}"
136
+
137
+ list = frames
138
+ list.each do |addr, frame|
139
+ f.puts "fl=#{frame[:file]}"
140
+ f.puts "fn=#{frame[:name]}"
141
+ frame[:lines].each do |line, weight|
142
+ f.puts "#{line} #{weight.is_a?(Array) ? weight[1] : weight}"
143
+ end if frame[:lines]
144
+ frame[:edges].each do |edge, weight|
145
+ oframe = list[edge.to_s]
146
+ f.puts "cfl=#{oframe[:file]}" unless oframe[:file] == frame[:file]
147
+ f.puts "cfn=#{oframe[:name]}"
148
+ f.puts "calls=#{weight} #{frame[:line] || 0}\n#{oframe[:line] || 0} #{weight}"
149
+ end if frame[:edges]
150
+ f.puts
151
+ end
152
+
153
+ f.puts "totals: #{@data[:samples]}"
154
+ end
155
+
156
+ def print_method(name, f = STDOUT)
51
157
  name = /#{Regexp.escape name}/ unless Regexp === name
52
158
  frames.each do |frame, info|
53
159
  next unless info[:name] =~ name
54
160
  file, line = info.values_at(:file, :line)
55
-
56
- maxline = info[:lines] ? info[:lines].keys.max : line + 5
57
- printf "%s (%s:%d)\n", info[:name], file, line
161
+ line ||= 1
58
162
 
59
163
  lines = info[:lines]
60
- source = File.readlines(file).each_with_index do |code, i|
61
- next unless (line-1..maxline).include?(i)
62
- if lines and samples = lines[i+1]
63
- printf "% 5d % 7s / % 7s | % 5d | %s", samples, "(%2.1f%%" % (100.0*samples/overall_samples), "%2.1f%%)" % (100.0*samples/info[:samples]), i+1, code
164
+ maxline = lines ? lines.keys.max : line + 5
165
+ f.printf "%s (%s:%d)\n", info[:name], file, line
166
+ f.printf " samples: % 5d self (%2.1f%%) / % 5d total (%2.1f%%)\n", info[:samples], 100.0*info[:samples]/overall_samples, info[:total_samples], 100.0*info[:total_samples]/overall_samples
167
+
168
+ if (callers = data[:frames].map{ |id, other| [other[:name], other[:edges][frame.to_i]] if other[:edges] && other[:edges].include?(frame.to_i) }.compact).any?
169
+ f.puts " callers:"
170
+ callers = callers.sort_by(&:last).reverse
171
+ callers.each do |name, weight|
172
+ f.printf " % 5d (% 8s) %s\n", weight, "%3.1f%%" % (100.0*weight/info[:total_samples]), name
173
+ end
174
+ end
175
+
176
+ if callees = info[:edges]
177
+ f.printf " callees (%d total):\n", info[:total_samples]-info[:samples]
178
+ callees = callees.map{ |k, weight| [data[:frames][k.to_s][:name], weight] }
179
+ callees.each do |name, weight|
180
+ f.printf " % 5d (% 8s) %s\n", weight, "%3.1f%%" % (100.0*weight/(info[:total_samples]-info[:samples])), name
181
+ end
182
+ end
183
+
184
+ f.puts " code:"
185
+ source_display(f, file, lines, line-1..maxline)
186
+ end
187
+ end
188
+
189
+ def print_files(sort_by_total=false, limit=nil, f = STDOUT)
190
+ list = files.map{ |file, vals| [file, vals.values.inject([0,0]){ |sum, n| add_lines(sum, n) }] }
191
+ list = list.sort_by{ |file, samples| -samples[1] }
192
+ list = list.first(limit) if limit
193
+ list.each do |file, vals|
194
+ total_samples, samples = *vals
195
+ f.printf "% 5d (%2.1f%%) / % 5d (%2.1f%%) %s\n", total_samples, (100.0*total_samples/overall_samples), samples, (100.0*samples/overall_samples), file
196
+ end
197
+ end
198
+
199
+ def print_file(filter, f = STDOUT)
200
+ filter = /#{Regexp.escape filter}/ unless Regexp === filter
201
+ list = files
202
+ list.select!{ |name, lines| name =~ filter }
203
+ list.sort_by{ |file, vals| -vals.values.inject(0){ |sum, n| sum + (n.is_a?(Array) ? n[1] : n) } }.each do |file, lines|
204
+ source_display(f, file, lines)
205
+ end
206
+ end
207
+
208
+ private
209
+
210
+ def source_display(f, file, lines, range=nil)
211
+ File.readlines(file).each_with_index do |code, i|
212
+ next unless range.nil? || range.include?(i)
213
+ if lines and lineinfo = lines[i+1]
214
+ total_samples, samples = lineinfo
215
+ if version == 1.0
216
+ samples = total_samples
217
+ f.printf "% 5d % 7s | % 5d | %s", samples, "(%2.1f%%)" % (100.0*samples/overall_samples), i+1, code
218
+ elsif samples > 0
219
+ f.printf "% 5d % 8s / % 5d % 7s | % 5d | %s", total_samples, "(%2.1f%%)" % (100.0*total_samples/overall_samples), samples, "(%2.1f%%)" % (100.0*samples/overall_samples), i+1, code
64
220
  else
65
- printf " | % 5d | %s", i+1, code
221
+ f.printf "% 5d % 8s | % 5d | %s", total_samples, "(%3.1f%%)" % (100.0*total_samples/overall_samples), i+1, code
222
+ end
223
+ else
224
+ if version == 1.0
225
+ f.printf " | % 5d | %s", i+1, code
226
+ else
227
+ f.printf " | % 5d | %s", i+1, code
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ def +(other)
234
+ raise ArgumentError, "cannot combine #{other.class}" unless self.class == other.class
235
+ raise ArgumentError, "cannot combine #{modeline} with #{other.modeline}" unless modeline == other.modeline
236
+ raise ArgumentError, "cannot combine v#{version} with v#{other.version}" unless version == other.version
237
+
238
+ f1, f2 = normalized_frames, other.normalized_frames
239
+ frames = (f1.keys + f2.keys).uniq.inject(Hash.new) do |hash, id|
240
+ if f1[id].nil?
241
+ hash[id] = f2[id]
242
+ elsif f2[id]
243
+ hash[id] = f1[id]
244
+ hash[id][:total_samples] += f2[id][:total_samples]
245
+ hash[id][:samples] += f2[id][:samples]
246
+ if f2[id][:edges]
247
+ edges = hash[id][:edges] ||= {}
248
+ f2[id][:edges].each do |edge, weight|
249
+ edges[edge] ||= 0
250
+ edges[edge] += weight
251
+ end
66
252
  end
253
+ if f2[id][:lines]
254
+ lines = hash[id][:lines] ||= {}
255
+ f2[id][:lines].each do |line, weight|
256
+ lines[line] = add_lines(lines[line], weight)
257
+ end
258
+ end
259
+ else
260
+ hash[id] = f1[id]
67
261
  end
262
+ hash
68
263
  end
264
+
265
+ d1, d2 = data, other.data
266
+ data = {
267
+ version: version,
268
+ mode: d1[:mode],
269
+ interval: d1[:interval],
270
+ samples: d1[:samples] + d2[:samples],
271
+ gc_samples: d1[:gc_samples] + d2[:gc_samples],
272
+ missed_samples: d1[:missed_samples] + d2[:missed_samples],
273
+ frames: frames
274
+ }
275
+
276
+ self.class.new(data)
69
277
  end
70
278
  end
71
279
  end
data/stackprof.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'stackprof'
3
- s.version = '0.1.0'
3
+ s.version = '0.2.0'
4
4
  s.homepage = 'http://github.com/tmm1/stackprof'
5
5
 
6
6
  s.authors = 'Aman Gupta'
@@ -17,5 +17,6 @@ Gem::Specification.new do |s|
17
17
 
18
18
  s.license = 'MIT'
19
19
 
20
+ # s.add_dependency 'yajl-ruby'
20
21
  s.add_development_dependency 'rake-compiler'
21
22
  end
@@ -4,39 +4,65 @@ require 'test/unit'
4
4
 
5
5
  class StackProfTest < Test::Unit::TestCase
6
6
  def test_info
7
- profile = StackProf.run(:wall, 1000){}
8
- assert_equal 1.0, profile[:version]
9
- assert_equal "wall(1000)", profile[:mode]
7
+ profile = StackProf.run{}
8
+ assert_equal 1.1, profile[:version]
9
+ assert_equal :wall, profile[:mode]
10
+ assert_equal 1000, profile[:interval]
10
11
  assert_equal 0, profile[:samples]
11
12
  end
12
13
 
14
+ def test_running
15
+ assert_equal false, StackProf.running?
16
+ StackProf.run{ assert_equal true, StackProf.running? }
17
+ end
18
+
19
+ def test_start_stop_results
20
+ assert_equal nil, StackProf.results
21
+ assert_equal true, StackProf.start
22
+ assert_equal false, StackProf.start
23
+ assert_equal true, StackProf.running?
24
+ assert_equal nil, StackProf.results
25
+ assert_equal true, StackProf.stop
26
+ assert_equal false, StackProf.stop
27
+ assert_equal false, StackProf.running?
28
+ assert_kind_of Hash, StackProf.results
29
+ assert_equal nil, StackProf.results
30
+ end
31
+
13
32
  def test_object_allocation
14
- profile = StackProf.run(:object, 1) do
33
+ profile = StackProf.run(mode: :object) do
15
34
  Object.new
16
35
  Object.new
17
36
  end
18
- assert_equal "object(1)", profile[:mode]
37
+ assert_equal :object, profile[:mode]
38
+ assert_equal 1, profile[:interval]
19
39
  assert_equal 2, profile[:samples]
20
40
 
21
41
  frame = profile[:frames].values.first
22
42
  assert_equal "block in StackProfTest#test_object_allocation", frame[:name]
23
43
  assert_equal 2, frame[:samples]
24
- assert_equal 14, frame[:line]
25
- assert_equal 1, frame[:lines][15]
26
- assert_equal 1, frame[:lines][16]
44
+ line = __LINE__
45
+ assert_equal line-11, frame[:line]
46
+ assert_equal [1, 1], frame[:lines][line-10]
47
+ assert_equal [1, 1], frame[:lines][line-9]
48
+
49
+ frame = profile[:frames].values[1]
50
+ assert_equal [2, 0], frame[:lines][line-11]
27
51
  end
28
52
 
29
53
  def test_cputime
30
- profile = StackProf.run(:cpu, 1000) do
54
+ profile = StackProf.run(mode: :cpu, interval: 500) do
31
55
  math
32
56
  end
33
57
 
58
+ assert_operator profile[:samples], :>, 1
34
59
  frame = profile[:frames].values.first
35
60
  assert_equal "block in StackProfTest#math", frame[:name]
61
+ File.open('/tmp/cputime.dump','w'){|f| f.write Marshal.dump(profile) }
36
62
  end
37
63
 
38
64
  def test_walltime
39
- profile = StackProf.run(:wall, 1000) do
65
+ profile = StackProf.run(mode: :wall) do
40
66
  idle
41
67
  end
42
68
 
@@ -45,6 +71,43 @@ class StackProfTest < Test::Unit::TestCase
45
71
  assert_in_delta 200, frame[:samples], 5
46
72
  end
47
73
 
74
+ def test_custom
75
+ profile = StackProf.run(mode: :custom) do
76
+ 10.times do
77
+ StackProf.sample
78
+ end
79
+ end
80
+
81
+ assert_equal :custom, profile[:mode]
82
+ assert_equal 10, profile[:samples]
83
+
84
+ frame = profile[:frames].values.first
85
+ assert_equal "block (2 levels) in StackProfTest#test_custom", frame[:name]
86
+ assert_equal __LINE__-10, frame[:line]
87
+ assert_equal [10, 10], frame[:lines][__LINE__-10]
88
+ end
89
+
90
+ def test_fork
91
+ StackProf.run do
92
+ pid = fork do
93
+ exit! StackProf.running?? 1 : 0
94
+ end
95
+ Process.wait(pid)
96
+ assert_equal 0, $?.exitstatus
97
+ assert_equal true, StackProf.running?
98
+ end
99
+ end
100
+
101
+ def test_gc
102
+ profile = StackProf.run(mode: :cpu, interval: 100) do
103
+ GC.start
104
+ end
105
+
106
+ assert_empty profile[:frames]
107
+ assert_operator profile[:gc_samples], :>, 0
108
+ assert_equal 0, profile[:missed_samples]
109
+ end
110
+
48
111
  def math
49
112
  250_000.times do
50
113
  2 ** 10
@@ -52,6 +115,10 @@ class StackProfTest < Test::Unit::TestCase
52
115
  end
53
116
 
54
117
  def idle
55
- sleep 0.2
118
+ r, w = IO.pipe
119
+ IO.select([r], nil, nil, 0.2)
120
+ ensure
121
+ r.close
122
+ w.close
56
123
  end
57
124
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aman Gupta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-14 00:00:00.000000000 Z
11
+ date: 2013-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -41,6 +41,7 @@ files:
41
41
  - bin/stackprof
42
42
  - ext/extconf.rb
43
43
  - ext/stackprof.c
44
+ - lib/stackprof/middleware.rb
44
45
  - lib/stackprof/report.rb
45
46
  - sample.rb
46
47
  - stackprof.gemspec
@@ -65,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
66
  version: '0'
66
67
  requirements: []
67
68
  rubyforge_project:
68
- rubygems_version: 2.2.0.preview.1
69
+ rubygems_version: 2.2.0
69
70
  signing_key:
70
71
  specification_version: 4
71
72
  summary: sampling callstack-profiler for ruby 2.1+