stackprof 0.2.12 → 0.2.13

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
- 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+