stackprof 0.2.12 → 0.2.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 25c29d953e534a2d31065493cbffa762c5f92ee1
4
- data.tar.gz: f9533f32cd7d5d648f3b9e16fd9d43469181521a
2
+ SHA256:
3
+ metadata.gz: 81201dbe84f93b79242ce9987c763b7edaa54b4f66a7005ff83d22a9b4e25cb4
4
+ data.tar.gz: c80ad82b003cb989c83bc5882f4bcdeb6f6db697729315faf6d4507b7f3888fb
5
5
  SHA512:
6
- metadata.gz: 69d486b03120ed37b76b16e65825b98e00fa6fbfb3aa3c484870ebead42cdbfed204e1e4e4362e3d5bdadee9c81ccf4169bcdaf109191c8b0fa580f97a8dfc62
7
- data.tar.gz: b735b0457b8ee0bbf217d0a891abd81c88ecbfd63a435001d1aaaec9707ac01607e14bea0b82a6c0c6ba827b64b3885d0cbffffdabe0742a3cc51bb402a59fc2
6
+ metadata.gz: f4fec270902e0b6f21e9d37b8317a2c3614cf4647ad29956b52561bc6c86d7625c4ee3362388c1323c1b74fd2fbb6c99242e75cf0314e20f5be97491e9888122
7
+ data.tar.gz: 01a18137afc847cba43882fbc7e22483e350490e655e9536f0dc1cb5275a49c222c4613b9a4f39cb0d3265a6e834560fc87cac4fae77324634bc2f68cde0c03a
data/.gitignore CHANGED
@@ -2,5 +2,4 @@
2
2
  /lib/stackprof/stackprof.bundle
3
3
  /lib/stackprof/stackprof.so
4
4
  *.sw?
5
- /ext
6
5
  /pkg
@@ -1,8 +1,22 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.1
5
- - 2.2
6
- - 2.3
7
- - 2.4
8
- - ruby-head
1
+ sudo: required
2
+
3
+ services:
4
+ - docker
5
+
6
+ language: general
7
+
8
+ env:
9
+ matrix:
10
+ - RVM_RUBY_VERSION=2.1
11
+ - RVM_RUBY_VERSION=2.2
12
+ - RVM_RUBY_VERSION=2.3
13
+ - RVM_RUBY_VERSION=2.4
14
+ - RVM_RUBY_VERSION=2.5
15
+ - RVM_RUBY_VERSION=2.6
16
+ - RVM_RUBY_VERSION=ruby-head
17
+
18
+ before_install:
19
+ - sudo docker build -t stackprof-$RVM_RUBY_VERSION --build-arg=RVM_RUBY_VERSION=$RVM_RUBY_VERSION .
20
+
21
+ script:
22
+ - sudo docker run --name stackprof-$RVM_RUBY_VERSION stackprof-$RVM_RUBY_VERSION
@@ -1,3 +1,15 @@
1
- # 0.2.12
1
+ # 0.2.13
2
2
 
3
- * Eliminate one loop over the stack by using `overall_samples` [#98]
3
+ * Remove /ext from .gitignore
4
+ * update gemfile
5
+ * Add ruby 2.5 to CI targets
6
+ * comment some of the inner workings
7
+ * feature: add --json format
8
+ * Add test coverage around the string branch in result writing
9
+ * Flip conditional to use duck typing
10
+ * Allow Pathname objects for Stackprof :out
11
+ * Fix a compilation error and a compilation warning
12
+ * Add `--alphabetical-flamegraph` for population-based instead of timeline
13
+ * Add `--d3-flamegraph` to output html using d3-flame-graph
14
+ * Avoid JSON::NestingError when processing deep stacks
15
+ * Use docker for CI
@@ -0,0 +1,21 @@
1
+ FROM ubuntu:16.04
2
+ ARG DEBIAN_FRONTEND=noninteractive
3
+ RUN apt-get update -q && \
4
+ apt-get install -qy \
5
+ curl ca-certificates gnupg2 dirmngr build-essential \
6
+ gawk git autoconf automake pkg-config \
7
+ bison libffi-dev libgdbm-dev libncurses5-dev libsqlite3-dev libtool \
8
+ libyaml-dev sqlite3 zlib1g-dev libgmp-dev libreadline-dev libssl-dev \
9
+ ruby --no-install-recommends && \
10
+ apt-get clean
11
+
12
+ RUN gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
13
+ RUN curl -sSL https://get.rvm.io | bash -s
14
+ ARG RVM_RUBY_VERSION=ruby-head
15
+ RUN /bin/bash -l -c "echo $RVM_RUBY_VERSION"
16
+ RUN /bin/bash -l -c ". /etc/profile.d/rvm.sh && rvm install $RVM_RUBY_VERSION --binary || rvm install $RVM_RUBY_VERSION"
17
+ ADD . /stackprof/
18
+ WORKDIR /stackprof/
19
+ RUN /bin/bash -l -c ". /etc/profile.d/rvm.sh && gem install bundler:1.16.0"
20
+ RUN /bin/bash -l -c ". /etc/profile.d/rvm.sh && bundle install"
21
+ CMD /bin/bash -l -c ". /etc/profile.d/rvm.sh && bundle exec rake"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stackprof (0.2.11)
4
+ stackprof (0.2.13)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -24,4 +24,4 @@ DEPENDENCIES
24
24
  stackprof!
25
25
 
26
26
  BUNDLED WITH
27
- 1.15.4
27
+ 1.16.0
data/README.md CHANGED
@@ -97,6 +97,14 @@ The `--flamegraph-viewer` command will output the exact shell command you need t
97
97
 
98
98
  ![](http://i.imgur.com/EwndrgD.png)
99
99
 
100
+ Alternatively, you can generate a flamegraph that uses [d3-flame-graph](https://github.com/spiermar/d3-flame-graph):
101
+
102
+ ```
103
+ $ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > flamegraph.html
104
+ ```
105
+
106
+ And just open the result by your browser.
107
+
100
108
  ## Sampling
101
109
 
102
110
  four sampling modes are supported:
@@ -8,6 +8,7 @@ parser = OptionParser.new(ARGV) do |o|
8
8
  o.banner = "Usage: stackprof [file.dump]+ [--text|--method=NAME|--callgrind|--graphviz]"
9
9
 
10
10
  o.on('--text', 'Text summary per method (default)'){ options[:format] = :text }
11
+ o.on('--json', 'JSON output (use with web viewers)'){ options[:format] = :json }
11
12
  o.on('--files', 'List of files'){ |f| options[:format] = :files }
12
13
  o.on('--limit [num]', Integer, 'Limit --text, --files, or --graphviz output to N entries'){ |n| options[:limit] = n }
13
14
  o.on('--sort-total', "Sort --text or --files output on total samples\n\n"){ options[:sort] = true }
@@ -18,11 +19,14 @@ parser = OptionParser.new(ARGV) do |o|
18
19
  o.on('--graphviz', "Graphviz output (use with dot)"){ options[:format] = :graphviz }
19
20
  o.on('--node-fraction [frac]', OptionParser::DecimalNumeric, 'Drop nodes representing less than [frac] fraction of samples'){ |n| options[:node_fraction] = n }
20
21
  o.on('--stackcollapse', 'stackcollapse.pl compatible output (use with stackprof-flamegraph.pl)'){ options[:format] = :stackcollapse }
21
- o.on('--flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :flamegraph }
22
- o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output\n\n"){ |file|
22
+ o.on('--timeline-flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :timeline_flamegraph }
23
+ o.on('--alphabetical-flamegraph', "alphabetical-flamegraph output (js)"){ options[:format] = :alphabetical_flamegraph }
24
+ o.on('--flamegraph', "alias to --timeline-flamegraph"){ options[:format] = :timeline_flamegraph }
25
+ o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output"){ |file|
23
26
  puts("open file://#{File.expand_path('../../lib/stackprof/flamegraph/viewer.html', __FILE__)}?data=#{File.expand_path(file)}")
24
27
  exit
25
28
  }
29
+ o.on('--d3-flamegraph', "flamegraph output (html using d3-flame-graph)\n\n"){ options[:format] = :d3_flamegraph }
26
30
  o.on('--select-files []', String, 'Show results of matching files'){ |path| (options[:select_files] ||= []) << File.expand_path(path) }
27
31
  o.on('--reject-files []', String, 'Exclude results of matching files'){ |path| (options[:reject_files] ||= []) << File.expand_path(path) }
28
32
  o.on('--select-names []', Regexp, 'Show results of matching method names'){ |regexp| (options[:select_names] ||= []) << regexp }
@@ -62,6 +66,8 @@ options.delete(:limit) if options[:limit] == 0
62
66
  case options[:format]
63
67
  when :text
64
68
  report.print_text(options[:sort], options[:limit], options[:select_files], options[:reject_files], options[:select_names], options[:reject_names])
69
+ when :json
70
+ report.print_json
65
71
  when :debug
66
72
  report.print_debug
67
73
  when :dump
@@ -72,8 +78,12 @@ when :graphviz
72
78
  report.print_graphviz(options)
73
79
  when :stackcollapse
74
80
  report.print_stackcollapse
75
- when :flamegraph
76
- report.print_flamegraph
81
+ when :timeline_flamegraph
82
+ report.print_timeline_flamegraph
83
+ when :alphabetical_flamegraph
84
+ report.print_alphabetical_flamegraph
85
+ when :d3_flamegraph
86
+ report.print_d3_flamegraph
77
87
  when :method
78
88
  options[:walk] ? report.walk_method(options[:filter]) : report.print_method(options[:filter])
79
89
  when :file
@@ -0,0 +1,9 @@
1
+ require 'mkmf'
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/stackprof')
7
+ else
8
+ fail 'missing API: are you using ruby 2.1+?'
9
+ end
@@ -0,0 +1,696 @@
1
+ /**********************************************************************
2
+
3
+ stackprof.c - Sampling call-stack frame profiler for MRI.
4
+
5
+ vim: noexpandtab shiftwidth=4 tabstop=8 softtabstop=4
6
+
7
+ **********************************************************************/
8
+
9
+ #include <ruby/ruby.h>
10
+ #include <ruby/debug.h>
11
+ #include <ruby/st.h>
12
+ #include <ruby/io.h>
13
+ #include <ruby/intern.h>
14
+ #include <signal.h>
15
+ #include <sys/time.h>
16
+ #include <pthread.h>
17
+
18
+ #define BUF_SIZE 2048
19
+
20
+ typedef struct {
21
+ size_t total_samples;
22
+ size_t caller_samples;
23
+ size_t seen_at_sample_number;
24
+ st_table *edges;
25
+ st_table *lines;
26
+ } frame_data_t;
27
+
28
+ static struct {
29
+ int running;
30
+ int raw;
31
+ int aggregate;
32
+
33
+ VALUE mode;
34
+ VALUE interval;
35
+ VALUE out;
36
+
37
+ VALUE *raw_samples;
38
+ size_t raw_samples_len;
39
+ size_t raw_samples_capa;
40
+ size_t raw_sample_index;
41
+
42
+ struct timeval last_sample_at;
43
+ int *raw_timestamp_deltas;
44
+ size_t raw_timestamp_deltas_len;
45
+ size_t raw_timestamp_deltas_capa;
46
+
47
+ size_t overall_signals;
48
+ size_t overall_samples;
49
+ size_t during_gc;
50
+ size_t unrecorded_gc_samples;
51
+ st_table *frames;
52
+
53
+ VALUE fake_gc_frame;
54
+ VALUE fake_gc_frame_name;
55
+ VALUE empty_string;
56
+ VALUE frames_buffer[BUF_SIZE];
57
+ int lines_buffer[BUF_SIZE];
58
+ } _stackprof;
59
+
60
+ static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
61
+ static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
62
+ static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_frames, sym_out, sym_aggregate, sym_raw_timestamp_deltas;
63
+ static VALUE sym_gc_samples, objtracer;
64
+ static VALUE gc_hook;
65
+ static VALUE rb_mStackProf;
66
+
67
+ static void stackprof_newobj_handler(VALUE, void*);
68
+ static void stackprof_signal_handler(int sig, siginfo_t* sinfo, void* ucontext);
69
+
70
+ static VALUE
71
+ stackprof_start(int argc, VALUE *argv, VALUE self)
72
+ {
73
+ struct sigaction sa;
74
+ struct itimerval timer;
75
+ VALUE opts = Qnil, mode = Qnil, interval = Qnil, out = Qfalse;
76
+ int raw = 0, aggregate = 1;
77
+
78
+ if (_stackprof.running)
79
+ return Qfalse;
80
+
81
+ rb_scan_args(argc, argv, "0:", &opts);
82
+
83
+ if (RTEST(opts)) {
84
+ mode = rb_hash_aref(opts, sym_mode);
85
+ interval = rb_hash_aref(opts, sym_interval);
86
+ out = rb_hash_aref(opts, sym_out);
87
+
88
+ if (RTEST(rb_hash_aref(opts, sym_raw)))
89
+ raw = 1;
90
+ if (rb_hash_lookup2(opts, sym_aggregate, Qundef) == Qfalse)
91
+ aggregate = 0;
92
+ }
93
+ if (!RTEST(mode)) mode = sym_wall;
94
+
95
+ if (!_stackprof.frames) {
96
+ _stackprof.frames = st_init_numtable();
97
+ _stackprof.overall_signals = 0;
98
+ _stackprof.overall_samples = 0;
99
+ _stackprof.during_gc = 0;
100
+ }
101
+
102
+ if (mode == sym_object) {
103
+ if (!RTEST(interval)) interval = INT2FIX(1);
104
+
105
+ objtracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, stackprof_newobj_handler, 0);
106
+ rb_tracepoint_enable(objtracer);
107
+ } else if (mode == sym_wall || mode == sym_cpu) {
108
+ if (!RTEST(interval)) interval = INT2FIX(1000);
109
+
110
+ sa.sa_sigaction = stackprof_signal_handler;
111
+ sa.sa_flags = SA_RESTART | SA_SIGINFO;
112
+ sigemptyset(&sa.sa_mask);
113
+ sigaction(mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
114
+
115
+ timer.it_interval.tv_sec = 0;
116
+ timer.it_interval.tv_usec = NUM2LONG(interval);
117
+ timer.it_value = timer.it_interval;
118
+ setitimer(mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
119
+ } else if (mode == sym_custom) {
120
+ /* sampled manually */
121
+ interval = Qnil;
122
+ } else {
123
+ rb_raise(rb_eArgError, "unknown profiler mode");
124
+ }
125
+
126
+ _stackprof.running = 1;
127
+ _stackprof.raw = raw;
128
+ _stackprof.aggregate = aggregate;
129
+ _stackprof.mode = mode;
130
+ _stackprof.interval = interval;
131
+ _stackprof.out = out;
132
+
133
+ if (raw) {
134
+ gettimeofday(&_stackprof.last_sample_at, NULL);
135
+ }
136
+
137
+ return Qtrue;
138
+ }
139
+
140
+ static VALUE
141
+ stackprof_stop(VALUE self)
142
+ {
143
+ struct sigaction sa;
144
+ struct itimerval timer;
145
+
146
+ if (!_stackprof.running)
147
+ return Qfalse;
148
+ _stackprof.running = 0;
149
+
150
+ if (_stackprof.mode == sym_object) {
151
+ rb_tracepoint_disable(objtracer);
152
+ } else if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
153
+ memset(&timer, 0, sizeof(timer));
154
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
155
+
156
+ sa.sa_handler = SIG_IGN;
157
+ sa.sa_flags = SA_RESTART;
158
+ sigemptyset(&sa.sa_mask);
159
+ sigaction(_stackprof.mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
160
+ } else if (_stackprof.mode == sym_custom) {
161
+ /* sampled manually */
162
+ } else {
163
+ rb_raise(rb_eArgError, "unknown profiler mode");
164
+ }
165
+
166
+ return Qtrue;
167
+ }
168
+
169
+ static int
170
+ frame_edges_i(st_data_t key, st_data_t val, st_data_t arg)
171
+ {
172
+ VALUE edges = (VALUE)arg;
173
+
174
+ intptr_t weight = (intptr_t)val;
175
+ rb_hash_aset(edges, rb_obj_id((VALUE)key), INT2FIX(weight));
176
+ return ST_CONTINUE;
177
+ }
178
+
179
+ static int
180
+ frame_lines_i(st_data_t key, st_data_t val, st_data_t arg)
181
+ {
182
+ VALUE lines = (VALUE)arg;
183
+
184
+ size_t weight = (size_t)val;
185
+ size_t total = weight & (~(size_t)0 << (8*SIZEOF_SIZE_T/2));
186
+ weight -= total;
187
+ total = total >> (8*SIZEOF_SIZE_T/2);
188
+ rb_hash_aset(lines, INT2FIX(key), rb_ary_new3(2, ULONG2NUM(total), ULONG2NUM(weight)));
189
+ return ST_CONTINUE;
190
+ }
191
+
192
+ static int
193
+ frame_i(st_data_t key, st_data_t val, st_data_t arg)
194
+ {
195
+ VALUE frame = (VALUE)key;
196
+ frame_data_t *frame_data = (frame_data_t *)val;
197
+ VALUE results = (VALUE)arg;
198
+ VALUE details = rb_hash_new();
199
+ VALUE name, file, edges, lines;
200
+ VALUE line;
201
+
202
+ rb_hash_aset(results, rb_obj_id(frame), details);
203
+
204
+ if (frame == _stackprof.fake_gc_frame) {
205
+ name = _stackprof.fake_gc_frame_name;
206
+ file = _stackprof.empty_string;
207
+ line = INT2FIX(0);
208
+ } else {
209
+ name = rb_profile_frame_full_label(frame);
210
+
211
+ file = rb_profile_frame_absolute_path(frame);
212
+ if (NIL_P(file))
213
+ file = rb_profile_frame_path(frame);
214
+ line = rb_profile_frame_first_lineno(frame);
215
+ }
216
+
217
+ rb_hash_aset(details, sym_name, name);
218
+ rb_hash_aset(details, sym_file, file);
219
+ if (line != INT2FIX(0)) {
220
+ rb_hash_aset(details, sym_line, line);
221
+ }
222
+
223
+ rb_hash_aset(details, sym_total_samples, SIZET2NUM(frame_data->total_samples));
224
+ rb_hash_aset(details, sym_samples, SIZET2NUM(frame_data->caller_samples));
225
+
226
+ if (frame_data->edges) {
227
+ edges = rb_hash_new();
228
+ rb_hash_aset(details, sym_edges, edges);
229
+ st_foreach(frame_data->edges, frame_edges_i, (st_data_t)edges);
230
+ st_free_table(frame_data->edges);
231
+ frame_data->edges = NULL;
232
+ }
233
+
234
+ if (frame_data->lines) {
235
+ lines = rb_hash_new();
236
+ rb_hash_aset(details, sym_lines, lines);
237
+ st_foreach(frame_data->lines, frame_lines_i, (st_data_t)lines);
238
+ st_free_table(frame_data->lines);
239
+ frame_data->lines = NULL;
240
+ }
241
+
242
+ xfree(frame_data);
243
+ return ST_DELETE;
244
+ }
245
+
246
+ static VALUE
247
+ stackprof_results(int argc, VALUE *argv, VALUE self)
248
+ {
249
+ VALUE results, frames;
250
+
251
+ if (!_stackprof.frames || _stackprof.running)
252
+ return Qnil;
253
+
254
+ results = rb_hash_new();
255
+ rb_hash_aset(results, sym_version, DBL2NUM(1.2));
256
+ rb_hash_aset(results, sym_mode, _stackprof.mode);
257
+ rb_hash_aset(results, sym_interval, _stackprof.interval);
258
+ rb_hash_aset(results, sym_samples, SIZET2NUM(_stackprof.overall_samples));
259
+ rb_hash_aset(results, sym_gc_samples, SIZET2NUM(_stackprof.during_gc));
260
+ rb_hash_aset(results, sym_missed_samples, SIZET2NUM(_stackprof.overall_signals - _stackprof.overall_samples));
261
+
262
+ frames = rb_hash_new();
263
+ rb_hash_aset(results, sym_frames, frames);
264
+ st_foreach(_stackprof.frames, frame_i, (st_data_t)frames);
265
+
266
+ st_free_table(_stackprof.frames);
267
+ _stackprof.frames = NULL;
268
+
269
+ if (_stackprof.raw && _stackprof.raw_samples_len) {
270
+ size_t len, n, o;
271
+ VALUE raw_timestamp_deltas;
272
+ VALUE raw_samples = rb_ary_new_capa(_stackprof.raw_samples_len);
273
+
274
+ for (n = 0; n < _stackprof.raw_samples_len; n++) {
275
+ len = (size_t)_stackprof.raw_samples[n];
276
+ rb_ary_push(raw_samples, SIZET2NUM(len));
277
+
278
+ for (o = 0, n++; o < len; n++, o++)
279
+ rb_ary_push(raw_samples, rb_obj_id(_stackprof.raw_samples[n]));
280
+ rb_ary_push(raw_samples, SIZET2NUM((size_t)_stackprof.raw_samples[n]));
281
+ }
282
+
283
+ free(_stackprof.raw_samples);
284
+ _stackprof.raw_samples = NULL;
285
+ _stackprof.raw_samples_len = 0;
286
+ _stackprof.raw_samples_capa = 0;
287
+ _stackprof.raw_sample_index = 0;
288
+
289
+ rb_hash_aset(results, sym_raw, raw_samples);
290
+
291
+ raw_timestamp_deltas = rb_ary_new_capa(_stackprof.raw_timestamp_deltas_len);
292
+
293
+ for (n = 0; n < _stackprof.raw_timestamp_deltas_len; n++) {
294
+ rb_ary_push(raw_timestamp_deltas, INT2FIX(_stackprof.raw_timestamp_deltas[n]));
295
+ }
296
+
297
+ free(_stackprof.raw_timestamp_deltas);
298
+ _stackprof.raw_timestamp_deltas = NULL;
299
+ _stackprof.raw_timestamp_deltas_len = 0;
300
+ _stackprof.raw_timestamp_deltas_capa = 0;
301
+
302
+ rb_hash_aset(results, sym_raw_timestamp_deltas, raw_timestamp_deltas);
303
+
304
+ _stackprof.raw = 0;
305
+ }
306
+
307
+ if (argc == 1)
308
+ _stackprof.out = argv[0];
309
+
310
+ if (RTEST(_stackprof.out)) {
311
+ VALUE file;
312
+ if (rb_respond_to(_stackprof.out, rb_intern("to_io"))) {
313
+ file = rb_io_check_io(_stackprof.out);
314
+ } else {
315
+ file = rb_file_open_str(_stackprof.out, "w");
316
+ }
317
+
318
+ rb_marshal_dump(results, file);
319
+ rb_io_flush(file);
320
+ _stackprof.out = Qnil;
321
+ return file;
322
+ } else {
323
+ return results;
324
+ }
325
+ }
326
+
327
+ static VALUE
328
+ stackprof_run(int argc, VALUE *argv, VALUE self)
329
+ {
330
+ rb_need_block();
331
+ stackprof_start(argc, argv, self);
332
+ rb_ensure(rb_yield, Qundef, stackprof_stop, self);
333
+ return stackprof_results(0, 0, self);
334
+ }
335
+
336
+ static VALUE
337
+ stackprof_running_p(VALUE self)
338
+ {
339
+ return _stackprof.running ? Qtrue : Qfalse;
340
+ }
341
+
342
+ static inline frame_data_t *
343
+ sample_for(VALUE frame)
344
+ {
345
+ st_data_t key = (st_data_t)frame, val = 0;
346
+ frame_data_t *frame_data;
347
+
348
+ if (st_lookup(_stackprof.frames, key, &val)) {
349
+ frame_data = (frame_data_t *)val;
350
+ } else {
351
+ frame_data = ALLOC_N(frame_data_t, 1);
352
+ MEMZERO(frame_data, frame_data_t, 1);
353
+ val = (st_data_t)frame_data;
354
+ st_insert(_stackprof.frames, key, val);
355
+ }
356
+
357
+ return frame_data;
358
+ }
359
+
360
+ static int
361
+ numtable_increment_callback(st_data_t *key, st_data_t *value, st_data_t arg, int existing)
362
+ {
363
+ size_t *weight = (size_t *)value;
364
+ size_t increment = (size_t)arg;
365
+
366
+ if (existing)
367
+ (*weight) += increment;
368
+ else
369
+ *weight = increment;
370
+
371
+ return ST_CONTINUE;
372
+ }
373
+
374
+ void
375
+ st_numtable_increment(st_table *table, st_data_t key, size_t increment)
376
+ {
377
+ st_update(table, key, numtable_increment_callback, (st_data_t)increment);
378
+ }
379
+
380
+ void
381
+ stackprof_record_sample_for_stack(int num, int timestamp_delta)
382
+ {
383
+ int i, n;
384
+ VALUE prev_frame = Qnil;
385
+
386
+ _stackprof.overall_samples++;
387
+
388
+ if (_stackprof.raw) {
389
+ int found = 0;
390
+
391
+ /* If there's no sample buffer allocated, then allocate one. The buffer
392
+ * format is the number of frames (num), then the list of frames (from
393
+ * `_stackprof.raw_samples`), followed by the number of times this
394
+ * particular stack has been seen in a row. Each "new" stack is added
395
+ * to the end of the buffer, but if the previous stack is the same as
396
+ * the current stack, the counter will be incremented. */
397
+ if (!_stackprof.raw_samples) {
398
+ _stackprof.raw_samples_capa = num * 100;
399
+ _stackprof.raw_samples = malloc(sizeof(VALUE) * _stackprof.raw_samples_capa);
400
+ }
401
+
402
+ /* If we can't fit all the samples in the buffer, double the buffer size. */
403
+ while (_stackprof.raw_samples_capa <= _stackprof.raw_samples_len + (num + 2)) {
404
+ _stackprof.raw_samples_capa *= 2;
405
+ _stackprof.raw_samples = realloc(_stackprof.raw_samples, sizeof(VALUE) * _stackprof.raw_samples_capa);
406
+ }
407
+
408
+ /* If we've seen this stack before in the last sample, then increment the "seen" count. */
409
+ if (_stackprof.raw_samples_len > 0 && _stackprof.raw_samples[_stackprof.raw_sample_index] == (VALUE)num) {
410
+ /* The number of samples could have been the same, but the stack
411
+ * might be different, so we need to check the stack here. Stacks
412
+ * in the raw buffer are stored in the opposite direction of stacks
413
+ * in the frames buffer that came from Ruby. */
414
+ for (i = num-1, n = 0; i >= 0; i--, n++) {
415
+ VALUE frame = _stackprof.frames_buffer[i];
416
+ if (_stackprof.raw_samples[_stackprof.raw_sample_index + 1 + n] != frame)
417
+ break;
418
+ }
419
+ if (i == -1) {
420
+ _stackprof.raw_samples[_stackprof.raw_samples_len-1] += 1;
421
+ found = 1;
422
+ }
423
+ }
424
+
425
+ /* If we haven't seen the stack, then add it to the buffer along with
426
+ * the length of the stack and a 1 for the "seen" count */
427
+ if (!found) {
428
+ /* Bump the `raw_sample_index` up so that the next iteration can
429
+ * find the previously recorded stack size. */
430
+ _stackprof.raw_sample_index = _stackprof.raw_samples_len;
431
+ _stackprof.raw_samples[_stackprof.raw_samples_len++] = (VALUE)num;
432
+ for (i = num-1; i >= 0; i--) {
433
+ VALUE frame = _stackprof.frames_buffer[i];
434
+ _stackprof.raw_samples[_stackprof.raw_samples_len++] = frame;
435
+ }
436
+ _stackprof.raw_samples[_stackprof.raw_samples_len++] = (VALUE)1;
437
+ }
438
+
439
+ /* If there's no timestamp delta buffer, allocate one */
440
+ if (!_stackprof.raw_timestamp_deltas) {
441
+ _stackprof.raw_timestamp_deltas_capa = 100;
442
+ _stackprof.raw_timestamp_deltas = malloc(sizeof(int) * _stackprof.raw_timestamp_deltas_capa);
443
+ _stackprof.raw_timestamp_deltas_len = 0;
444
+ }
445
+
446
+ /* Double the buffer size if it's too small */
447
+ while (_stackprof.raw_timestamp_deltas_capa <= _stackprof.raw_timestamp_deltas_len + 1) {
448
+ _stackprof.raw_timestamp_deltas_capa *= 2;
449
+ _stackprof.raw_timestamp_deltas = realloc(_stackprof.raw_timestamp_deltas, sizeof(int) * _stackprof.raw_timestamp_deltas_capa);
450
+ }
451
+
452
+ /* Store the time delta (which is the amount of time between samples) */
453
+ _stackprof.raw_timestamp_deltas[_stackprof.raw_timestamp_deltas_len++] = timestamp_delta;
454
+ }
455
+
456
+ for (i = 0; i < num; i++) {
457
+ int line = _stackprof.lines_buffer[i];
458
+ VALUE frame = _stackprof.frames_buffer[i];
459
+ frame_data_t *frame_data = sample_for(frame);
460
+
461
+ if (frame_data->seen_at_sample_number != _stackprof.overall_samples) {
462
+ frame_data->total_samples++;
463
+ }
464
+ frame_data->seen_at_sample_number = _stackprof.overall_samples;
465
+
466
+ if (i == 0) {
467
+ frame_data->caller_samples++;
468
+ } else if (_stackprof.aggregate) {
469
+ if (!frame_data->edges)
470
+ frame_data->edges = st_init_numtable();
471
+ st_numtable_increment(frame_data->edges, (st_data_t)prev_frame, 1);
472
+ }
473
+
474
+ if (_stackprof.aggregate && line > 0) {
475
+ size_t half = (size_t)1<<(8*SIZEOF_SIZE_T/2);
476
+ size_t increment = i == 0 ? half + 1 : half;
477
+ if (!frame_data->lines)
478
+ frame_data->lines = st_init_numtable();
479
+ st_numtable_increment(frame_data->lines, (st_data_t)line, increment);
480
+ }
481
+
482
+ prev_frame = frame;
483
+ }
484
+
485
+ if (_stackprof.raw) {
486
+ gettimeofday(&_stackprof.last_sample_at, NULL);
487
+ }
488
+ }
489
+
490
+ void
491
+ stackprof_record_sample()
492
+ {
493
+ int timestamp_delta = 0;
494
+ int num;
495
+ if (_stackprof.raw) {
496
+ struct timeval t;
497
+ struct timeval diff;
498
+ gettimeofday(&t, NULL);
499
+ timersub(&t, &_stackprof.last_sample_at, &diff);
500
+ timestamp_delta = (1000 * diff.tv_sec) + diff.tv_usec;
501
+ }
502
+ num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer) / sizeof(VALUE), _stackprof.frames_buffer, _stackprof.lines_buffer);
503
+ stackprof_record_sample_for_stack(num, timestamp_delta);
504
+ }
505
+
506
+ void
507
+ stackprof_record_gc_samples()
508
+ {
509
+ int delta_to_first_unrecorded_gc_sample = 0;
510
+ int i;
511
+ if (_stackprof.raw) {
512
+ struct timeval t;
513
+ struct timeval diff;
514
+ gettimeofday(&t, NULL);
515
+ timersub(&t, &_stackprof.last_sample_at, &diff);
516
+
517
+ // We don't know when the GC samples were actually marked, so let's
518
+ // assume that they were marked at a perfectly regular interval.
519
+ delta_to_first_unrecorded_gc_sample = (1000 * diff.tv_sec + diff.tv_usec) - (_stackprof.unrecorded_gc_samples - 1) * NUM2LONG(_stackprof.interval);
520
+ if (delta_to_first_unrecorded_gc_sample < 0) {
521
+ delta_to_first_unrecorded_gc_sample = 0;
522
+ }
523
+ }
524
+
525
+ _stackprof.frames_buffer[0] = _stackprof.fake_gc_frame;
526
+ _stackprof.lines_buffer[0] = 0;
527
+
528
+ for (i = 0; i < _stackprof.unrecorded_gc_samples; i++) {
529
+ int timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
530
+ stackprof_record_sample_for_stack(1, timestamp_delta);
531
+ }
532
+ _stackprof.during_gc += _stackprof.unrecorded_gc_samples;
533
+ _stackprof.unrecorded_gc_samples = 0;
534
+ }
535
+
536
+ static void
537
+ stackprof_gc_job_handler(void *data)
538
+ {
539
+ static int in_signal_handler = 0;
540
+ if (in_signal_handler) return;
541
+ if (!_stackprof.running) return;
542
+
543
+ in_signal_handler++;
544
+ stackprof_record_gc_samples();
545
+ in_signal_handler--;
546
+ }
547
+
548
+ static void
549
+ stackprof_job_handler(void *data)
550
+ {
551
+ static int in_signal_handler = 0;
552
+ if (in_signal_handler) return;
553
+ if (!_stackprof.running) return;
554
+
555
+ in_signal_handler++;
556
+ stackprof_record_sample();
557
+ in_signal_handler--;
558
+ }
559
+
560
+ static void
561
+ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
562
+ {
563
+ _stackprof.overall_signals++;
564
+ if (rb_during_gc()) {
565
+ _stackprof.unrecorded_gc_samples++;
566
+ rb_postponed_job_register_one(0, stackprof_gc_job_handler, (void*)0);
567
+ } else {
568
+ rb_postponed_job_register_one(0, stackprof_job_handler, (void*)0);
569
+ }
570
+ }
571
+
572
+ static void
573
+ stackprof_newobj_handler(VALUE tpval, void *data)
574
+ {
575
+ _stackprof.overall_signals++;
576
+ if (RTEST(_stackprof.interval) && _stackprof.overall_signals % NUM2LONG(_stackprof.interval))
577
+ return;
578
+ stackprof_job_handler(0);
579
+ }
580
+
581
+ static VALUE
582
+ stackprof_sample(VALUE self)
583
+ {
584
+ if (!_stackprof.running)
585
+ return Qfalse;
586
+
587
+ _stackprof.overall_signals++;
588
+ stackprof_job_handler(0);
589
+ return Qtrue;
590
+ }
591
+
592
+ static int
593
+ frame_mark_i(st_data_t key, st_data_t val, st_data_t arg)
594
+ {
595
+ VALUE frame = (VALUE)key;
596
+ rb_gc_mark(frame);
597
+ return ST_CONTINUE;
598
+ }
599
+
600
+ static void
601
+ stackprof_gc_mark(void *data)
602
+ {
603
+ if (RTEST(_stackprof.out))
604
+ rb_gc_mark(_stackprof.out);
605
+
606
+ if (_stackprof.frames)
607
+ st_foreach(_stackprof.frames, frame_mark_i, 0);
608
+ }
609
+
610
+ static void
611
+ stackprof_atfork_prepare(void)
612
+ {
613
+ struct itimerval timer;
614
+ if (_stackprof.running) {
615
+ if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
616
+ memset(&timer, 0, sizeof(timer));
617
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
618
+ }
619
+ }
620
+ }
621
+
622
+ static void
623
+ stackprof_atfork_parent(void)
624
+ {
625
+ struct itimerval timer;
626
+ if (_stackprof.running) {
627
+ if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
628
+ timer.it_interval.tv_sec = 0;
629
+ timer.it_interval.tv_usec = NUM2LONG(_stackprof.interval);
630
+ timer.it_value = timer.it_interval;
631
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
632
+ }
633
+ }
634
+ }
635
+
636
+ static void
637
+ stackprof_atfork_child(void)
638
+ {
639
+ stackprof_stop(rb_mStackProf);
640
+ }
641
+
642
+ void
643
+ Init_stackprof(void)
644
+ {
645
+ #define S(name) sym_##name = ID2SYM(rb_intern(#name));
646
+ S(object);
647
+ S(custom);
648
+ S(wall);
649
+ S(cpu);
650
+ S(name);
651
+ S(file);
652
+ S(line);
653
+ S(total_samples);
654
+ S(gc_samples);
655
+ S(missed_samples);
656
+ S(samples);
657
+ S(edges);
658
+ S(lines);
659
+ S(version);
660
+ S(mode);
661
+ S(interval);
662
+ S(raw);
663
+ S(raw_timestamp_deltas);
664
+ S(out);
665
+ S(frames);
666
+ S(aggregate);
667
+ #undef S
668
+
669
+ gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, &_stackprof);
670
+ rb_global_variable(&gc_hook);
671
+
672
+ _stackprof.raw_samples = NULL;
673
+ _stackprof.raw_samples_len = 0;
674
+ _stackprof.raw_samples_capa = 0;
675
+ _stackprof.raw_sample_index = 0;
676
+
677
+ _stackprof.raw_timestamp_deltas = NULL;
678
+ _stackprof.raw_timestamp_deltas_len = 0;
679
+ _stackprof.raw_timestamp_deltas_capa = 0;
680
+
681
+ _stackprof.fake_gc_frame = INT2FIX(0x9C);
682
+ _stackprof.empty_string = rb_str_new_cstr("");
683
+ _stackprof.fake_gc_frame_name = rb_str_new_cstr("(garbage collection)");
684
+ rb_global_variable(&_stackprof.fake_gc_frame_name);
685
+ rb_global_variable(&_stackprof.empty_string);
686
+
687
+ rb_mStackProf = rb_define_module("StackProf");
688
+ rb_define_singleton_method(rb_mStackProf, "running?", stackprof_running_p, 0);
689
+ rb_define_singleton_method(rb_mStackProf, "run", stackprof_run, -1);
690
+ rb_define_singleton_method(rb_mStackProf, "start", stackprof_start, -1);
691
+ rb_define_singleton_method(rb_mStackProf, "stop", stackprof_stop, 0);
692
+ rb_define_singleton_method(rb_mStackProf, "results", stackprof_results, -1);
693
+ rb_define_singleton_method(rb_mStackProf, "sample", stackprof_sample, 0);
694
+
695
+ pthread_atfork(stackprof_atfork_prepare, stackprof_atfork_parent, stackprof_atfork_child);
696
+ }
@@ -1,4 +1,8 @@
1
1
  require "stackprof/stackprof"
2
2
 
3
+ module StackProf
4
+ VERSION = '0.2.13'
5
+ end
6
+
3
7
  StackProf.autoload :Report, "stackprof/report.rb"
4
8
  StackProf.autoload :Middleware, "stackprof/middleware.rb"
@@ -68,6 +68,11 @@ module StackProf
68
68
  f.puts Marshal.dump(@data.reject{|k,v| k == :files })
69
69
  end
70
70
 
71
+ def print_json(f=STDOUT)
72
+ require "json"
73
+ f.puts JSON.generate(@data, max_nesting: false)
74
+ end
75
+
71
76
  def print_stackcollapse
72
77
  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
73
78
 
@@ -80,7 +85,15 @@ module StackProf
80
85
  end
81
86
  end
82
87
 
83
- def print_flamegraph(f=STDOUT, skip_common=true)
88
+ def print_timeline_flamegraph(f=STDOUT, skip_common=true)
89
+ print_flamegraph(f, skip_common, false)
90
+ end
91
+
92
+ def print_alphabetical_flamegraph(f=STDOUT, skip_common=true)
93
+ print_flamegraph(f, skip_common, true)
94
+ end
95
+
96
+ def print_flamegraph(f, skip_common, alphabetical=false)
84
97
  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
85
98
 
86
99
  stacks = []
@@ -93,6 +106,8 @@ module StackProf
93
106
  max_x += stack.last
94
107
  end
95
108
 
109
+ stacks.sort! if alphabetical
110
+
96
111
  f.puts 'flamegraph(['
97
112
  max_y.times do |y|
98
113
  row_prev = nil
@@ -148,6 +163,216 @@ module StackProf
148
163
  f.puts %{{"x":#{x},"y":#{y},"width":#{weight},"frame_id":#{addr},"frame":#{frame[:name].dump},"file":#{frame[:file].dump}}}
149
164
  end
150
165
 
166
+ def convert_to_d3_flame_graph_format(name, stacks, depth)
167
+ weight = 0
168
+ children = []
169
+ stacks.chunk do |stack|
170
+ if depth == stack.length - 1
171
+ :leaf
172
+ else
173
+ stack[depth]
174
+ end
175
+ end.each do |val, child_stacks|
176
+ if val == :leaf
177
+ child_stacks.each do |stack|
178
+ weight += stack.last
179
+ end
180
+ else
181
+ frame = frames[val]
182
+ child_name = "#{ frame[:name] } : #{ frame[:file] }"
183
+ child_data = convert_to_d3_flame_graph_format(child_name, child_stacks, depth + 1)
184
+ weight += child_data["value"]
185
+ children << child_data
186
+ end
187
+ end
188
+
189
+ {
190
+ "name" => name,
191
+ "value" => weight,
192
+ "children" => children,
193
+ }
194
+ end
195
+
196
+ def print_d3_flamegraph(f=STDOUT, skip_common=true)
197
+ raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
198
+
199
+ stacks = []
200
+ max_x = 0
201
+ max_y = 0
202
+ while len = raw.shift
203
+ max_y = len if len > max_y
204
+ stack = raw.slice!(0, len+1)
205
+ stacks << stack
206
+ max_x += stack.last
207
+ end
208
+
209
+ # d3-flame-grpah supports only alphabetical flamegraph
210
+ stacks.sort!
211
+
212
+ require "json"
213
+ json = JSON.generate(convert_to_d3_flame_graph_format("<root>", stacks, 0), max_nesting: false)
214
+
215
+ # This html code is almost copied from d3-flame-graph sample code.
216
+ # (Apache License 2.0)
217
+ # https://github.com/spiermar/d3-flame-graph/blob/gh-pages/index.html
218
+
219
+ f.print <<-END
220
+ <!DOCTYPE html>
221
+ <html lang="en">
222
+ <head>
223
+ <meta charset="utf-8">
224
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
225
+ <meta name="viewport" content="width=device-width, initial-scale=1">
226
+
227
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
228
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.css">
229
+
230
+ <style>
231
+
232
+ /* Space out content a bit */
233
+ body {
234
+ padding-top: 20px;
235
+ padding-bottom: 20px;
236
+ }
237
+
238
+ /* Custom page header */
239
+ .header {
240
+ padding-bottom: 20px;
241
+ padding-right: 15px;
242
+ padding-left: 15px;
243
+ border-bottom: 1px solid #e5e5e5;
244
+ }
245
+
246
+ /* Make the masthead heading the same height as the navigation */
247
+ .header h3 {
248
+ margin-top: 0;
249
+ margin-bottom: 0;
250
+ line-height: 40px;
251
+ }
252
+
253
+ /* Customize container */
254
+ .container {
255
+ max-width: 990px;
256
+ }
257
+
258
+ address {
259
+ text-align: right;
260
+ }
261
+ </style>
262
+
263
+ <title>stackprof (mode: #{ data[:mode] })</title>
264
+
265
+ <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
266
+ <!--[if lt IE 9]>
267
+ <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
268
+ <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
269
+ <![endif]-->
270
+ </head>
271
+ <body>
272
+ <div class="container">
273
+ <div class="header clearfix">
274
+ <nav>
275
+ <div class="pull-right">
276
+ <form class="form-inline" id="form">
277
+ <a class="btn" href="javascript: resetZoom();">Reset zoom</a>
278
+ <a class="btn" href="javascript: clear();">Clear</a>
279
+ <div class="form-group">
280
+ <input type="text" class="form-control" id="term">
281
+ </div>
282
+ <a class="btn btn-primary" href="javascript: search();">Search</a>
283
+ </form>
284
+ </div>
285
+ </nav>
286
+ <h3 class="text-muted">stackprof (mode: #{ data[:mode] })</h3>
287
+ </div>
288
+ <div id="chart">
289
+ </div>
290
+ <address>
291
+ powered by <a href="https://github.com/spiermar/d3-flame-graph">d3-flame-graph</a>
292
+ </address>
293
+ <hr>
294
+ <div id="details">
295
+ </div>
296
+ </div>
297
+
298
+ <!-- D3.js -->
299
+ <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
300
+
301
+ <!-- d3-tip -->
302
+ <script type="text/javascript" src=https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js></script>
303
+
304
+ <!-- d3-flamegraph -->
305
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.min.js"></script>
306
+
307
+ <script type="text/javascript">
308
+ var flameGraph = d3.flamegraph()
309
+ .width(960)
310
+ .cellHeight(18)
311
+ .transitionDuration(750)
312
+ .minFrameSize(5)
313
+ .transitionEase(d3.easeCubic)
314
+ .sort(true)
315
+ //Example to sort in reverse order
316
+ //.sort(function(a,b){ return d3.descending(a.name, b.name);})
317
+ .title("")
318
+ .onClick(onClick)
319
+ .differential(false)
320
+ .selfValue(false);
321
+
322
+
323
+ // Example on how to use custom tooltips using d3-tip.
324
+ // var tip = d3.tip()
325
+ // .direction("s")
326
+ // .offset([8, 0])
327
+ // .attr('class', 'd3-flame-graph-tip')
328
+ // .html(function(d) { return "name: " + d.data.name + ", value: " + d.data.value; });
329
+
330
+ // flameGraph.tooltip(tip);
331
+
332
+ var details = document.getElementById("details");
333
+ flameGraph.setDetailsElement(details);
334
+
335
+ // Example on how to use custom labels
336
+ // var label = function(d) {
337
+ // return "name: " + d.name + ", value: " + d.value;
338
+ // }
339
+ // flameGraph.label(label);
340
+
341
+ // Example of how to set fixed chart height
342
+ // flameGraph.height(540);
343
+
344
+ d3.select("#chart")
345
+ .datum(#{ json })
346
+ .call(flameGraph);
347
+
348
+ document.getElementById("form").addEventListener("submit", function(event){
349
+ event.preventDefault();
350
+ search();
351
+ });
352
+
353
+ function search() {
354
+ var term = document.getElementById("term").value;
355
+ flameGraph.search(term);
356
+ }
357
+
358
+ function clear() {
359
+ document.getElementById('term').value = '';
360
+ flameGraph.clear();
361
+ }
362
+
363
+ function resetZoom() {
364
+ flameGraph.resetZoom();
365
+ }
366
+
367
+ function onClick(d) {
368
+ console.info("Clicked on " + d.data.name);
369
+ }
370
+ </script>
371
+ </body>
372
+ </html>
373
+ END
374
+ end
375
+
151
376
  def print_graphviz(options = {}, f = STDOUT)
152
377
  if filter = options[:filter]
153
378
  mark_stack = []
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'stackprof'
3
- s.version = '0.2.12'
3
+ s.version = '0.2.13'
4
4
  s.homepage = 'http://github.com/tmm1/stackprof'
5
5
 
6
6
  s.authors = 'Aman Gupta'
@@ -2,6 +2,7 @@ $:.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'stackprof'
3
3
  require 'minitest/autorun'
4
4
  require 'tempfile'
5
+ require 'pathname'
5
6
 
6
7
  class StackProfTest < MiniTest::Test
7
8
  def test_info
@@ -167,6 +168,32 @@ class StackProfTest < MiniTest::Test
167
168
  refute_empty profile[:frames]
168
169
  end
169
170
 
171
+ def test_out_to_path_string
172
+ tmpfile = Tempfile.new('stackprof-out')
173
+ ret = StackProf.run(mode: :custom, out: tmpfile.path) do
174
+ StackProf.sample
175
+ end
176
+
177
+ refute_equal tmpfile, ret
178
+ assert_equal tmpfile.path, ret.path
179
+ tmpfile.rewind
180
+ profile = Marshal.load(tmpfile.read)
181
+ refute_empty profile[:frames]
182
+ end
183
+
184
+ def test_pathname_out
185
+ tmpfile = Tempfile.new('stackprof-out')
186
+ pathname = Pathname.new(tmpfile.path)
187
+ ret = StackProf.run(mode: :custom, out: pathname) do
188
+ StackProf.sample
189
+ end
190
+
191
+ assert_equal tmpfile.path, ret.path
192
+ tmpfile.rewind
193
+ profile = Marshal.load(tmpfile.read)
194
+ refute_empty profile[:frames]
195
+ end
196
+
170
197
  def math
171
198
  250_000.times do
172
199
  2 ** 10
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.2.12
4
+ version: 0.2.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aman Gupta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-29 00:00:00.000000000 Z
11
+ date: 2019-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -66,6 +66,7 @@ files:
66
66
  - ".gitignore"
67
67
  - ".travis.yml"
68
68
  - CHANGELOG.md
69
+ - Dockerfile
69
70
  - Gemfile
70
71
  - Gemfile.lock
71
72
  - LICENSE
@@ -109,8 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  - !ruby/object:Gem::Version
110
111
  version: '0'
111
112
  requirements: []
112
- rubyforge_project:
113
- rubygems_version: 2.4.6
113
+ rubygems_version: 3.1.0.pre1
114
114
  signing_key:
115
115
  specification_version: 4
116
116
  summary: sampling callstack-profiler for ruby 2.1+