stackprof 0.2.16 → 0.2.19

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
2
  SHA256:
3
- metadata.gz: 109d5dc07fefb68933ae164c88420aecc662593f9fa96e102a90e7c8d4c605a9
4
- data.tar.gz: 7cbd4e6919a160f5e7b680bd0994e9e33097df03c7098ffda7317996a8afb1f6
3
+ metadata.gz: 6662250e5e20ee3388bfce740a053c83e53fe9304b518ad22f0ce9ffc5d10077
4
+ data.tar.gz: 7480e1896e253bd530573fc2f66b3d7690db97bee3f892c77729b75c2dc8c94e
5
5
  SHA512:
6
- metadata.gz: d4c6894359a809ea8e504eca85506179eb598d2b0db6833b1661e08d982efb78ec0fa526e73ba30a7388c443b5b4eb16a466baa7dd3dcd4e139c0bec7b22ab5d
7
- data.tar.gz: 040bc4d3c1ffb1f724bce5ca7db4ddeae7af7ecdc526f893f878049bf42c715983802def5a2e8905f6b1016401a478457a237ef0a76dc742e7070780940e762b
6
+ metadata.gz: 3f92e6401b16f83d9398b94e8b0506f597a785a19e1552245f39e1c77ab0c2d3d40186b8b05f91972b6d899995718c28e1724acff97248d497a3768deed601bb
7
+ data.tar.gz: 0663a1cdac8730bcbc4dd68750fffa6885b72eca7c37915191b6ff44ff101349ba274648b6641531c1bfff34144179b76d527ca8426db91af2c0536629f6ee65
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ rubies:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [ ruby-head, '3.0', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2' ]
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v2
15
+ - name: Set up Ruby
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ - name: Install dependencies
20
+ run: bundle install
21
+ - name: Run test
22
+ run: rake
23
+ - name: Install gem
24
+ run: rake install
25
+ platforms:
26
+ strategy:
27
+ matrix:
28
+ os: [macos]
29
+ ruby: ['3.0']
30
+ runs-on: ${{ matrix.os }}-latest
31
+ steps:
32
+ - name: Checkout
33
+ uses: actions/checkout@v2
34
+ - name: Set up Ruby
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: ${{ matrix.ruby }}
38
+ - name: Install dependencies
39
+ run: bundle install
40
+ - name: Run test
41
+ run: rake
42
+ - name: Install gem
43
+ run: rake install
data/README.md CHANGED
@@ -81,21 +81,21 @@ $ stackprof tmp/stackprof-cpu-*.dump --method 'Object#present?'
81
81
 
82
82
  For an experimental version of WebUI reporting of stackprof, see [stackprof-webnav](https://github.com/alisnic/stackprof-webnav)
83
83
 
84
- You can generate a flamegraph however additional data must be collected using the `raw: true` flag. Once you've collected results with this flag enabled you can generate a flamegraph:
84
+ To generate flamegraphs with Stackprof, additional data must be collected using the `raw: true` flag. Once you've collected results with this flag enabled, generate a flamegraph with:
85
85
 
86
86
  ```
87
87
  $ stackprof --flamegraph tmp/stackprof-cpu-myapp.dump > tmp/flamegraph
88
88
  ```
89
89
 
90
- Once the flamegraph has been generated you can generate a viewer command with:
90
+ After the flamegraph has been generated, you can generate a viewer command with:
91
91
 
92
92
  ```
93
93
  $ stackprof --flamegraph-viewer=tmp/flamegraph
94
94
  ```
95
95
 
96
- The `--flamegraph-viewer` command will output the exact shell command you need to run to open the `tmp/flamegraph` you generated with the built in stackprof flamegraph viewer:
96
+ The `--flamegraph-viewer` command will output the exact shell command you need to run in order to open the `tmp/flamegraph` you generated with the built-in stackprof flamegraph viewer:
97
97
 
98
- ![](http://i.imgur.com/EwndrgD.png)
98
+ ![Flamegraph Viewer](http://i.imgur.com/EwndrgD.png)
99
99
 
100
100
  Alternatively, you can generate a flamegraph that uses [d3-flame-graph](https://github.com/spiermar/d3-flame-graph):
101
101
 
@@ -107,16 +107,16 @@ And just open the result by your browser.
107
107
 
108
108
  ## Sampling
109
109
 
110
- four sampling modes are supported:
110
+ Four sampling modes are supported:
111
111
 
112
- - :wall (using `ITIMER_REAL` and `SIGALRM`) [default mode]
113
- - :cpu (using `ITIMER_PROF` and `SIGPROF`)
114
- - :object (using `RUBY_INTERNAL_EVENT_NEWOBJ`)
115
- - :custom (user-defined via `StackProf.sample`)
112
+ - `:wall` (using `ITIMER_REAL` and `SIGALRM`) [default mode]
113
+ - `:cpu` (using `ITIMER_PROF` and `SIGPROF`)
114
+ - `:object` (using `RUBY_INTERNAL_EVENT_NEWOBJ`)
115
+ - `:custom` (user-defined via `StackProf.sample`)
116
116
 
117
- samplers have a tuneable interval which can be used to reduce overhead or increase granularity:
117
+ Samplers have a tuneable interval which can be used to reduce overhead or increase granularity:
118
118
 
119
- - wall time: sample every _interval_ microseconds of wallclock time (default: 1000)
119
+ - Wall time: sample every _interval_ microseconds of wallclock time (default: 1000)
120
120
 
121
121
  ```ruby
122
122
  StackProf.run(mode: :wall, out: 'tmp/stackprof.dump', interval: 1000) do
@@ -124,7 +124,7 @@ StackProf.run(mode: :wall, out: 'tmp/stackprof.dump', interval: 1000) do
124
124
  end
125
125
  ```
126
126
 
127
- - cpu time: sample every _interval_ microseconds of cpu activity (default: 1000 = 1 millisecond)
127
+ - CPU time: sample every _interval_ microseconds of CPU activity (default: 1000 = 1 millisecond)
128
128
 
129
129
  ```ruby
130
130
  StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump', interval: 1000) do
@@ -132,7 +132,7 @@ StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump', interval: 1000) do
132
132
  end
133
133
  ```
134
134
 
135
- - object allocation: sample every _interval_ allocations (default: 1)
135
+ - Object allocation: sample every _interval_ allocations (default: 1)
136
136
 
137
137
 
138
138
  ```ruby
@@ -141,36 +141,36 @@ StackProf.run(mode: :object, out: 'tmp/stackprof.dump', interval: 1) do
141
141
  end
142
142
  ```
143
143
 
144
- by default, samples taken during garbage collection will show as garbage collection frames
145
- including both mark and sweep phases. for longer traces, these can leave gaps in a flamegraph
146
- that are hard to follow and can be disabled by setting the `ignore_gc` option to true.
147
- garbage collection time will still be present in the profile but not explicitly marked with
144
+ By default, samples taken during garbage collection will show as garbage collection frames
145
+ including both mark and sweep phases. For longer traces, these can leave gaps in a flamegraph
146
+ that are hard to follow. They can be disabled by setting the `ignore_gc` option to true.
147
+ Garbage collection time will still be present in the profile but not explicitly marked with
148
148
  its own frame.
149
149
 
150
- samples are taken using a combination of three new C-APIs in ruby 2.1:
150
+ Samples are taken using a combination of three new C-APIs in ruby 2.1:
151
151
 
152
- - signal handlers enqueue a sampling job using `rb_postponed_job_register_one`.
152
+ - Signal handlers enqueue a sampling job using `rb_postponed_job_register_one`.
153
153
  this ensures callstack samples can be taken safely, in case the VM is garbage collecting
154
154
  or in some other inconsistent state during the interruption.
155
155
 
156
- - stack frames are collected via `rb_profile_frames`, which provides low-overhead C-API access
157
- to the VM's call stack. no object allocations occur in this path, allowing stackprof to collect
156
+ - Stack frames are collected via `rb_profile_frames`, which provides low-overhead C-API access
157
+ to the VM's call stack. No object allocations occur in this path, allowing stackprof to collect
158
158
  callstacks in allocation mode.
159
159
 
160
- - in allocation mode, samples are taken via `rb_tracepoint_new(RUBY_INTERNAL_EVENT_NEWOBJ)`,
160
+ - In allocation mode, samples are taken via `rb_tracepoint_new(RUBY_INTERNAL_EVENT_NEWOBJ)`,
161
161
  which provides a notification every time the VM allocates a new object.
162
162
 
163
163
  ## Aggregation
164
164
 
165
- each sample consists of N stack frames, where a frame looks something like `MyClass#method` or `block in MySingleton.method`.
166
- for each of these frames in the sample, the profiler collects a few pieces of metadata:
165
+ Each sample consists of N stack frames, where a frame looks something like `MyClass#method` or `block in MySingleton.method`.
166
+ For each of these frames in the sample, the profiler collects a few pieces of metadata:
167
167
 
168
- - samples: number of samples where this was the topmost frame
169
- - total_samples: samples where this frame was in the stack
170
- - lines: samples per line number in this frame
171
- - edges: samples per callee frame (methods invoked by this frame)
168
+ - `samples`: Number of samples where this was the topmost frame
169
+ - `total_samples`: Samples where this frame was in the stack
170
+ - `lines`: Samples per line number in this frame
171
+ - `edges`: Samples per callee frame (methods invoked by this frame)
172
172
 
173
- the aggregation algorithm is roughly equivalent to the following pseudo code:
173
+ The aggregation algorithm is roughly equivalent to the following pseudo code:
174
174
 
175
175
  ``` ruby
176
176
  trap('PROF') do
@@ -189,16 +189,16 @@ trap('PROF') do
189
189
  end
190
190
  ```
191
191
 
192
- this technique builds up an incremental callgraph from the samples. on any given frame,
192
+ This technique builds up an incremental call graph from the samples. On any given frame,
193
193
  the sum of the outbound edge weights is equal to total samples collected on that frame
194
194
  (`frame.total_samples == frame.edges.values.sum`).
195
195
 
196
196
  ## Reporting
197
197
 
198
- multiple reporting modes are supported:
199
- - text
200
- - dotgraph
201
- - source annotation
198
+ Multiple reporting modes are supported:
199
+ - Text
200
+ - Dotgraph
201
+ - Source annotation
202
202
 
203
203
  ### `StackProf::Report.new(data).print_text`
204
204
 
@@ -217,8 +217,6 @@ multiple reporting modes are supported:
217
217
 
218
218
  ### `StackProf::Report.new(data).print_graphviz`
219
219
 
220
- ![](http://cl.ly/image/2t3l2q0l0B0A/content)
221
-
222
220
  ```
223
221
  digraph profile {
224
222
  70346498324780 [size=23.5531914893617] [fontsize=23.5531914893617] [shape=box] [label="A#pow\n91 (48.4%)\r"];
@@ -265,8 +263,8 @@ block in A#math (/Users/tmm1/code/stackprof/sample.rb:21)
265
263
 
266
264
  ## Usage
267
265
 
268
- the profiler is compiled as a C-extension and exposes a simple api: `StackProf.run(mode: [:cpu|:wall|:object])`.
269
- the `run` method takes a block of code and returns a profile as a simple hash.
266
+ The profiler is compiled as a C-extension and exposes a simple api: `StackProf.run(mode: [:cpu|:wall|:object])`.
267
+ The `run` method takes a block of code and returns a profile as a simple hash.
270
268
 
271
269
  ``` ruby
272
270
  # sample after every 1ms of cpu activity
@@ -275,12 +273,12 @@ profile = StackProf.run(mode: :cpu, interval: 1000) do
275
273
  end
276
274
  ```
277
275
 
278
- this profile data structure is part of the public API, and is intended to be saved
279
- (as json/marshal for example) for later processing. the reports above can be generated
276
+ This profile data structure is part of the public API, and is intended to be saved
277
+ (as json/marshal for example) for later processing. The reports above can be generated
280
278
  by passing this structure into `StackProf::Report.new`.
281
279
 
282
- the format itself is very simple. it contains a header and a list of frames. each frame has a unique id and
283
- identifying information such as its name, file and line. the frame also contains sampling data, including per-line
280
+ The format itself is very simple. It contains a header and a list of frames. Each frame has a unique ID and
281
+ identifying information such as its name, file, and line. The frame also contains sampling data, including per-line
284
282
  samples, and a list of relationships to other frames represented as weighted edges.
285
283
 
286
284
  ``` ruby
@@ -307,20 +305,21 @@ samples, and a list of relationships to other frames represented as weighted edg
307
305
  :lines=>{8=>1}},
308
306
  ```
309
307
 
310
- above, `A#pow` was involved in 91 samples, and in all cases it was at the top of the stack on line 12.
308
+ Above, `A#pow` was involved in 91 samples, and in all cases it was at the top of the stack on line 12.
311
309
 
312
- `A#initialize` was in 185 samples, but it was at the top of the stack in only 1 sample. the rest of the samples are
313
- divided up between its callee edges. all 91 calls to `A#pow` came from `A#initialize`, as seen by the edge numbered
310
+ `A#initialize` was in 185 samples, but it was at the top of the stack in only 1 sample. The rest of the samples are
311
+ divided up between its callee edges. All 91 calls to `A#pow` came from `A#initialize`, as seen by the edge numbered
314
312
  `70346498324780`.
315
313
 
316
314
  ## Advanced usage
317
315
 
318
- the profiler can be started and stopped manually. results are accumulated until retrieval, across
319
- multiple start/stop invocations.
316
+ The profiler can be started and stopped manually. Results are accumulated until retrieval, across
317
+ multiple `start`/`stop` invocations.
320
318
 
321
319
  ``` ruby
322
- StackProf.running?
320
+ StackProf.running? # => false
323
321
  StackProf.start(mode: :cpu)
322
+ StackProf.running? # => true
324
323
  StackProf.stop
325
324
  StackProf.results('/tmp/some.file')
326
325
  ```
@@ -331,14 +330,14 @@ StackProf.results('/tmp/some.file')
331
330
 
332
331
  Option | Meaning
333
332
  ------- | ---------
334
- `mode` | mode of sampling: `:cpu`, `:wall`, `:object`, or `:custom` [c.f.](#sampling)
335
- `out` | the target file, which will be overwritten
336
- `interval` | mode-relative sample rate [c.f.](#sampling)
333
+ `mode` | Mode of sampling: `:cpu`, `:wall`, `:object`, or `:custom` [c.f.](#sampling)
334
+ `out` | The target file, which will be overwritten
335
+ `interval` | Mode-relative sample rate [c.f.](#sampling)
337
336
  `ignore_gc` | Ignore garbage collection frames
338
- `aggregate` | defaults: `true` - if `false` disables [aggregation](#aggregation)
339
- `raw` | defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types
340
- `metadata` | defaults to `{}`. Must be a `Hash`. metadata associated with this profile
341
- `save_every`| (rack middleware only) write the target file after this many requests
337
+ `aggregate` | Defaults: `true` - if `false` disables [aggregation](#aggregation)
338
+ `raw` | Defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types
339
+ `metadata` | Defaults to `{}`. Must be a `Hash`. metadata associated with this profile
340
+ `save_every`| (Rack middleware only) write the target file after this many requests
342
341
 
343
342
  ## Todo
344
343
 
data/Rakefile CHANGED
@@ -1,31 +1,17 @@
1
- task :default => :test
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
2
3
 
3
- # ==========================================================
4
- # Packaging
5
- # ==========================================================
6
-
7
- GEMSPEC = Gem::Specification::load('stackprof.gemspec')
8
-
9
- require 'rubygems/package_task'
10
- Gem::PackageTask.new(GEMSPEC) do |pkg|
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/test_*.rb"]
11
8
  end
12
9
 
13
- # ==========================================================
14
- # Ruby Extension
15
- # ==========================================================
10
+ require "rake/extensiontask"
16
11
 
17
- require 'rake/extensiontask'
18
- Rake::ExtensionTask.new('stackprof', GEMSPEC) do |ext|
19
- ext.lib_dir = 'lib/stackprof'
12
+ Rake::ExtensionTask.new("stackprof") do |ext|
13
+ ext.ext_dir = "ext/stackprof"
14
+ ext.lib_dir = "lib/stackprof"
20
15
  end
21
- task :build => :compile
22
16
 
23
- # ==========================================================
24
- # Testing
25
- # ==========================================================
26
-
27
- require 'rake/testtask'
28
- Rake::TestTask.new 'test' do |t|
29
- t.test_files = FileList['test/test_*.rb']
30
- end
31
- task :test => :build
17
+ task default: %i(compile test)
@@ -7,21 +7,32 @@
7
7
  **********************************************************************/
8
8
 
9
9
  #include <ruby/ruby.h>
10
+ #include <ruby/version.h>
10
11
  #include <ruby/debug.h>
11
12
  #include <ruby/st.h>
12
13
  #include <ruby/io.h>
13
14
  #include <ruby/intern.h>
14
15
  #include <signal.h>
15
16
  #include <sys/time.h>
17
+ #include <time.h>
16
18
  #include <pthread.h>
17
19
 
18
20
  #define BUF_SIZE 2048
19
21
  #define MICROSECONDS_IN_SECOND 1000000
22
+ #define NANOSECONDS_IN_SECOND 1000000000
20
23
 
21
24
  #define FAKE_FRAME_GC INT2FIX(0)
22
25
  #define FAKE_FRAME_MARK INT2FIX(1)
23
26
  #define FAKE_FRAME_SWEEP INT2FIX(2)
24
27
 
28
+ /*
29
+ * As of Ruby 3.0, it should be safe to read stack frames at any time
30
+ * See https://github.com/ruby/ruby/commit/0e276dc458f94d9d79a0f7c7669bde84abe80f21
31
+ */
32
+ #if RUBY_API_VERSION_MAJOR < 3
33
+ #define USE_POSTPONED_JOB
34
+ #endif
35
+
25
36
  static const char *fake_frame_cstrs[] = {
26
37
  "(garbage collection)",
27
38
  "(marking)",
@@ -30,6 +41,47 @@ static const char *fake_frame_cstrs[] = {
30
41
 
31
42
  #define TOTAL_FAKE_FRAMES (sizeof(fake_frame_cstrs) / sizeof(char *))
32
43
 
44
+ #ifdef _POSIX_MONOTONIC_CLOCK
45
+ #define timestamp_t timespec
46
+ typedef struct timestamp_t timestamp_t;
47
+
48
+ static void capture_timestamp(timestamp_t *ts) {
49
+ clock_gettime(CLOCK_MONOTONIC, ts);
50
+ }
51
+
52
+ static int64_t delta_usec(timestamp_t *start, timestamp_t *end) {
53
+ int64_t result = MICROSECONDS_IN_SECOND * (end->tv_sec - start->tv_sec);
54
+ if (end->tv_nsec < start->tv_nsec) {
55
+ result -= MICROSECONDS_IN_SECOND;
56
+ result += (NANOSECONDS_IN_SECOND + end->tv_nsec - start->tv_nsec) / 1000;
57
+ } else {
58
+ result += (end->tv_nsec - start->tv_nsec) / 1000;
59
+ }
60
+ return result;
61
+ }
62
+
63
+ static uint64_t timestamp_usec(timestamp_t *ts) {
64
+ return (MICROSECONDS_IN_SECOND * ts->tv_sec) + (ts->tv_nsec / 1000);
65
+ }
66
+ #else
67
+ #define timestamp_t timeval
68
+ typedef struct timestamp_t timestamp_t;
69
+
70
+ static void capture_timestamp(timestamp_t *ts) {
71
+ gettimeofday(ts, NULL);
72
+ }
73
+
74
+ static int64_t delta_usec(timestamp_t *start, timestamp_t *end) {
75
+ struct timeval diff;
76
+ timersub(end, start, &diff);
77
+ return (MICROSECONDS_IN_SECOND * diff.tv_sec) + diff.tv_usec;
78
+ }
79
+
80
+ static uint64_t timestamp_usec(timestamp_t *ts) {
81
+ return (MICROSECONDS_IN_SECOND * ts.tv_sec) + diff.tv_usec
82
+ }
83
+ #endif
84
+
33
85
  typedef struct {
34
86
  size_t total_samples;
35
87
  size_t caller_samples;
@@ -38,6 +90,11 @@ typedef struct {
38
90
  st_table *lines;
39
91
  } frame_data_t;
40
92
 
93
+ typedef struct {
94
+ uint64_t timestamp_usec;
95
+ int64_t delta_usec;
96
+ } sample_time_t;
97
+
41
98
  static struct {
42
99
  int running;
43
100
  int raw;
@@ -54,10 +111,10 @@ static struct {
54
111
  size_t raw_samples_capa;
55
112
  size_t raw_sample_index;
56
113
 
57
- struct timeval last_sample_at;
58
- int *raw_timestamp_deltas;
59
- size_t raw_timestamp_deltas_len;
60
- size_t raw_timestamp_deltas_capa;
114
+ struct timestamp_t last_sample_at;
115
+ sample_time_t *raw_sample_times;
116
+ size_t raw_sample_times_len;
117
+ size_t raw_sample_times_capa;
61
118
 
62
119
  size_t overall_signals;
63
120
  size_t overall_samples;
@@ -69,6 +126,9 @@ static struct {
69
126
 
70
127
  VALUE fake_frame_names[TOTAL_FAKE_FRAMES];
71
128
  VALUE empty_string;
129
+
130
+ int buffer_count;
131
+ sample_time_t buffer_time;
72
132
  VALUE frames_buffer[BUF_SIZE];
73
133
  int lines_buffer[BUF_SIZE];
74
134
  } _stackprof;
@@ -76,7 +136,7 @@ static struct {
76
136
  static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
77
137
  static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
78
138
  static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_metadata, sym_frames, sym_ignore_gc, sym_out;
79
- static VALUE sym_aggregate, sym_raw_timestamp_deltas, sym_state, sym_marking, sym_sweeping;
139
+ static VALUE sym_aggregate, sym_raw_sample_timestamps, sym_raw_timestamp_deltas, sym_state, sym_marking, sym_sweeping;
80
140
  static VALUE sym_gc_samples, objtracer;
81
141
  static VALUE gc_hook;
82
142
  static VALUE rb_mStackProf;
@@ -166,7 +226,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
166
226
  _stackprof.out = out;
167
227
 
168
228
  if (raw) {
169
- gettimeofday(&_stackprof.last_sample_at, NULL);
229
+ capture_timestamp(&_stackprof.last_sample_at);
170
230
  }
171
231
 
172
232
  return Qtrue;
@@ -201,13 +261,19 @@ stackprof_stop(VALUE self)
201
261
  return Qtrue;
202
262
  }
203
263
 
264
+ #if SIZEOF_VOIDP == SIZEOF_LONG
265
+ # define PTR2NUM(x) (LONG2NUM((long)(x)))
266
+ #else
267
+ # define PTR2NUM(x) (LL2NUM((LONG_LONG)(x)))
268
+ #endif
269
+
204
270
  static int
205
271
  frame_edges_i(st_data_t key, st_data_t val, st_data_t arg)
206
272
  {
207
273
  VALUE edges = (VALUE)arg;
208
274
 
209
275
  intptr_t weight = (intptr_t)val;
210
- rb_hash_aset(edges, rb_obj_id((VALUE)key), INT2FIX(weight));
276
+ rb_hash_aset(edges, PTR2NUM(key), INT2FIX(weight));
211
277
  return ST_CONTINUE;
212
278
  }
213
279
 
@@ -234,7 +300,7 @@ frame_i(st_data_t key, st_data_t val, st_data_t arg)
234
300
  VALUE name, file, edges, lines;
235
301
  VALUE line;
236
302
 
237
- rb_hash_aset(results, rb_obj_id(frame), details);
303
+ rb_hash_aset(results, PTR2NUM(frame), details);
238
304
 
239
305
  if (FIXNUM_P(frame)) {
240
306
  name = _stackprof.fake_frame_names[FIX2INT(frame)];
@@ -306,7 +372,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
306
372
 
307
373
  if (_stackprof.raw && _stackprof.raw_samples_len) {
308
374
  size_t len, n, o;
309
- VALUE raw_timestamp_deltas;
375
+ VALUE raw_sample_timestamps, raw_timestamp_deltas;
310
376
  VALUE raw_samples = rb_ary_new_capa(_stackprof.raw_samples_len);
311
377
 
312
378
  for (n = 0; n < _stackprof.raw_samples_len; n++) {
@@ -314,7 +380,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
314
380
  rb_ary_push(raw_samples, SIZET2NUM(len));
315
381
 
316
382
  for (o = 0, n++; o < len; n++, o++)
317
- rb_ary_push(raw_samples, rb_obj_id(_stackprof.raw_samples[n]));
383
+ rb_ary_push(raw_samples, PTR2NUM(_stackprof.raw_samples[n]));
318
384
  rb_ary_push(raw_samples, SIZET2NUM((size_t)_stackprof.raw_samples[n]));
319
385
  }
320
386
 
@@ -326,17 +392,20 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
326
392
 
327
393
  rb_hash_aset(results, sym_raw, raw_samples);
328
394
 
329
- raw_timestamp_deltas = rb_ary_new_capa(_stackprof.raw_timestamp_deltas_len);
395
+ raw_sample_timestamps = rb_ary_new_capa(_stackprof.raw_sample_times_len);
396
+ raw_timestamp_deltas = rb_ary_new_capa(_stackprof.raw_sample_times_len);
330
397
 
331
- for (n = 0; n < _stackprof.raw_timestamp_deltas_len; n++) {
332
- rb_ary_push(raw_timestamp_deltas, INT2FIX(_stackprof.raw_timestamp_deltas[n]));
398
+ for (n = 0; n < _stackprof.raw_sample_times_len; n++) {
399
+ rb_ary_push(raw_sample_timestamps, ULL2NUM(_stackprof.raw_sample_times[n].timestamp_usec));
400
+ rb_ary_push(raw_timestamp_deltas, LL2NUM(_stackprof.raw_sample_times[n].delta_usec));
333
401
  }
334
402
 
335
- free(_stackprof.raw_timestamp_deltas);
336
- _stackprof.raw_timestamp_deltas = NULL;
337
- _stackprof.raw_timestamp_deltas_len = 0;
338
- _stackprof.raw_timestamp_deltas_capa = 0;
403
+ free(_stackprof.raw_sample_times);
404
+ _stackprof.raw_sample_times = NULL;
405
+ _stackprof.raw_sample_times_len = 0;
406
+ _stackprof.raw_sample_times_capa = 0;
339
407
 
408
+ rb_hash_aset(results, sym_raw_sample_timestamps, raw_sample_timestamps);
340
409
  rb_hash_aset(results, sym_raw_timestamp_deltas, raw_timestamp_deltas);
341
410
 
342
411
  _stackprof.raw = 0;
@@ -416,14 +485,14 @@ st_numtable_increment(st_table *table, st_data_t key, size_t increment)
416
485
  }
417
486
 
418
487
  void
419
- stackprof_record_sample_for_stack(int num, int timestamp_delta)
488
+ stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t timestamp_delta)
420
489
  {
421
490
  int i, n;
422
491
  VALUE prev_frame = Qnil;
423
492
 
424
493
  _stackprof.overall_samples++;
425
494
 
426
- if (_stackprof.raw) {
495
+ if (_stackprof.raw && num > 0) {
427
496
  int found = 0;
428
497
 
429
498
  /* If there's no sample buffer allocated, then allocate one. The buffer
@@ -475,20 +544,23 @@ stackprof_record_sample_for_stack(int num, int timestamp_delta)
475
544
  }
476
545
 
477
546
  /* If there's no timestamp delta buffer, allocate one */
478
- if (!_stackprof.raw_timestamp_deltas) {
479
- _stackprof.raw_timestamp_deltas_capa = 100;
480
- _stackprof.raw_timestamp_deltas = malloc(sizeof(int) * _stackprof.raw_timestamp_deltas_capa);
481
- _stackprof.raw_timestamp_deltas_len = 0;
547
+ if (!_stackprof.raw_sample_times) {
548
+ _stackprof.raw_sample_times_capa = 100;
549
+ _stackprof.raw_sample_times = malloc(sizeof(sample_time_t) * _stackprof.raw_sample_times_capa);
550
+ _stackprof.raw_sample_times_len = 0;
482
551
  }
483
552
 
484
553
  /* Double the buffer size if it's too small */
485
- while (_stackprof.raw_timestamp_deltas_capa <= _stackprof.raw_timestamp_deltas_len + 1) {
486
- _stackprof.raw_timestamp_deltas_capa *= 2;
487
- _stackprof.raw_timestamp_deltas = realloc(_stackprof.raw_timestamp_deltas, sizeof(int) * _stackprof.raw_timestamp_deltas_capa);
554
+ while (_stackprof.raw_sample_times_capa <= _stackprof.raw_sample_times_len + 1) {
555
+ _stackprof.raw_sample_times_capa *= 2;
556
+ _stackprof.raw_sample_times = realloc(_stackprof.raw_sample_times, sizeof(sample_time_t) * _stackprof.raw_sample_times_capa);
488
557
  }
489
558
 
490
- /* Store the time delta (which is the amount of time between samples) */
491
- _stackprof.raw_timestamp_deltas[_stackprof.raw_timestamp_deltas_len++] = timestamp_delta;
559
+ /* Store the time delta (which is the amount of microseconds between samples). */
560
+ _stackprof.raw_sample_times[_stackprof.raw_sample_times_len++] = (sample_time_t) {
561
+ .timestamp_usec = sample_timestamp,
562
+ .delta_usec = timestamp_delta,
563
+ };
492
564
  }
493
565
 
494
566
  for (i = 0; i < num; i++) {
@@ -521,48 +593,59 @@ stackprof_record_sample_for_stack(int num, int timestamp_delta)
521
593
  }
522
594
 
523
595
  if (_stackprof.raw) {
524
- gettimeofday(&_stackprof.last_sample_at, NULL);
596
+ capture_timestamp(&_stackprof.last_sample_at);
525
597
  }
526
598
  }
527
599
 
600
+ // buffer the current profile frames
601
+ // This must be async-signal-safe
602
+ // Returns immediately if another set of frames are already in the buffer
528
603
  void
529
- stackprof_record_sample()
604
+ stackprof_buffer_sample(void)
530
605
  {
531
- int timestamp_delta = 0;
606
+ if (_stackprof.buffer_count > 0) {
607
+ // Another sample is already pending
608
+ return;
609
+ }
610
+
611
+ uint64_t start_timestamp = 0;
612
+ int64_t timestamp_delta = 0;
532
613
  int num;
533
614
  if (_stackprof.raw) {
534
- struct timeval t;
535
- struct timeval diff;
536
- gettimeofday(&t, NULL);
537
- timersub(&t, &_stackprof.last_sample_at, &diff);
538
- timestamp_delta = (1000 * diff.tv_sec) + diff.tv_usec;
615
+ struct timestamp_t t;
616
+ capture_timestamp(&t);
617
+ start_timestamp = timestamp_usec(&t);
618
+ timestamp_delta = delta_usec(&_stackprof.last_sample_at, &t);
539
619
  }
620
+
540
621
  num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer) / sizeof(VALUE), _stackprof.frames_buffer, _stackprof.lines_buffer);
541
- stackprof_record_sample_for_stack(num, timestamp_delta);
622
+
623
+ _stackprof.buffer_count = num;
624
+ _stackprof.buffer_time.timestamp_usec = start_timestamp;
625
+ _stackprof.buffer_time.delta_usec = timestamp_delta;
542
626
  }
543
627
 
544
628
  void
545
- stackprof_record_gc_samples()
629
+ stackprof_record_gc_samples(void)
546
630
  {
547
- int delta_to_first_unrecorded_gc_sample = 0;
548
- int i;
631
+ int64_t delta_to_first_unrecorded_gc_sample = 0;
632
+ uint64_t start_timestamp = 0;
633
+ size_t i;
549
634
  if (_stackprof.raw) {
550
- struct timeval t;
551
- struct timeval diff;
552
- gettimeofday(&t, NULL);
553
- timersub(&t, &_stackprof.last_sample_at, &diff);
635
+ struct timestamp_t t;
636
+ capture_timestamp(&t);
637
+ start_timestamp = timestamp_usec(&t);
554
638
 
555
639
  // We don't know when the GC samples were actually marked, so let's
556
640
  // assume that they were marked at a perfectly regular interval.
557
- delta_to_first_unrecorded_gc_sample = (1000 * diff.tv_sec + diff.tv_usec) - (_stackprof.unrecorded_gc_samples - 1) * NUM2LONG(_stackprof.interval);
641
+ delta_to_first_unrecorded_gc_sample = delta_usec(&_stackprof.last_sample_at, &t) - (_stackprof.unrecorded_gc_samples - 1) * NUM2LONG(_stackprof.interval);
558
642
  if (delta_to_first_unrecorded_gc_sample < 0) {
559
643
  delta_to_first_unrecorded_gc_sample = 0;
560
644
  }
561
645
  }
562
646
 
563
-
564
647
  for (i = 0; i < _stackprof.unrecorded_gc_samples; i++) {
565
- int timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
648
+ int64_t timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
566
649
 
567
650
  if (_stackprof.unrecorded_gc_marking_samples) {
568
651
  _stackprof.frames_buffer[0] = FAKE_FRAME_MARK;
@@ -571,7 +654,7 @@ stackprof_record_gc_samples()
571
654
  _stackprof.lines_buffer[1] = 0;
572
655
  _stackprof.unrecorded_gc_marking_samples--;
573
656
 
574
- stackprof_record_sample_for_stack(2, timestamp_delta);
657
+ stackprof_record_sample_for_stack(2, start_timestamp, timestamp_delta);
575
658
  } else if (_stackprof.unrecorded_gc_sweeping_samples) {
576
659
  _stackprof.frames_buffer[0] = FAKE_FRAME_SWEEP;
577
660
  _stackprof.lines_buffer[0] = 0;
@@ -580,11 +663,11 @@ stackprof_record_gc_samples()
580
663
 
581
664
  _stackprof.unrecorded_gc_sweeping_samples--;
582
665
 
583
- stackprof_record_sample_for_stack(2, timestamp_delta);
666
+ stackprof_record_sample_for_stack(2, start_timestamp, timestamp_delta);
584
667
  } else {
585
668
  _stackprof.frames_buffer[0] = FAKE_FRAME_GC;
586
669
  _stackprof.lines_buffer[0] = 0;
587
- stackprof_record_sample_for_stack(1, timestamp_delta);
670
+ stackprof_record_sample_for_stack(1, start_timestamp, timestamp_delta);
588
671
  }
589
672
  }
590
673
  _stackprof.during_gc += _stackprof.unrecorded_gc_samples;
@@ -593,34 +676,60 @@ stackprof_record_gc_samples()
593
676
  _stackprof.unrecorded_gc_sweeping_samples = 0;
594
677
  }
595
678
 
679
+ // record the sample previously buffered by stackprof_buffer_sample
596
680
  static void
597
- stackprof_gc_job_handler(void *data)
681
+ stackprof_record_buffer(void)
682
+ {
683
+ stackprof_record_sample_for_stack(_stackprof.buffer_count, _stackprof.buffer_time.timestamp_usec, _stackprof.buffer_time.delta_usec);
684
+
685
+ // reset the buffer
686
+ _stackprof.buffer_count = 0;
687
+ }
688
+
689
+ static void
690
+ stackprof_sample_and_record(void)
691
+ {
692
+ stackprof_buffer_sample();
693
+ stackprof_record_buffer();
694
+ }
695
+
696
+ static void
697
+ stackprof_job_record_gc(void *data)
598
698
  {
599
- static int in_signal_handler = 0;
600
- if (in_signal_handler) return;
601
699
  if (!_stackprof.running) return;
602
700
 
603
- in_signal_handler++;
604
701
  stackprof_record_gc_samples();
605
- in_signal_handler--;
606
702
  }
607
703
 
704
+ #ifdef USE_POSTPONED_JOB
705
+ static void
706
+ stackprof_job_sample_and_record(void *data)
707
+ {
708
+ if (!_stackprof.running) return;
709
+
710
+ stackprof_sample_and_record();
711
+ }
712
+ #endif
713
+
608
714
  static void
609
- stackprof_job_handler(void *data)
715
+ stackprof_job_record_buffer(void *data)
610
716
  {
611
- static int in_signal_handler = 0;
612
- if (in_signal_handler) return;
613
717
  if (!_stackprof.running) return;
614
718
 
615
- in_signal_handler++;
616
- stackprof_record_sample();
617
- in_signal_handler--;
719
+ stackprof_record_buffer();
618
720
  }
619
721
 
620
722
  static void
621
723
  stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
622
724
  {
725
+ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
726
+
623
727
  _stackprof.overall_signals++;
728
+
729
+ if (!_stackprof.running) return;
730
+ if (!ruby_native_thread_p()) return;
731
+ if (pthread_mutex_trylock(&lock)) return;
732
+
624
733
  if (!_stackprof.ignore_gc && rb_during_gc()) {
625
734
  VALUE mode = rb_gc_latest_gc_info(sym_state);
626
735
  if (mode == sym_marking) {
@@ -629,10 +738,19 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
629
738
  _stackprof.unrecorded_gc_sweeping_samples++;
630
739
  }
631
740
  _stackprof.unrecorded_gc_samples++;
632
- rb_postponed_job_register_one(0, stackprof_gc_job_handler, (void*)0);
741
+ rb_postponed_job_register_one(0, stackprof_job_record_gc, (void*)0);
633
742
  } else {
634
- rb_postponed_job_register_one(0, stackprof_job_handler, (void*)0);
743
+ #ifdef USE_POSTPONED_JOB
744
+ rb_postponed_job_register_one(0, stackprof_job_sample_and_record, (void*)0);
745
+ #else
746
+ // Buffer a sample immediately, if an existing sample exists this will
747
+ // return immediately
748
+ stackprof_buffer_sample();
749
+ // Enqueue a job to record the sample
750
+ rb_postponed_job_register_one(0, stackprof_job_record_buffer, (void*)0);
751
+ #endif
635
752
  }
753
+ pthread_mutex_unlock(&lock);
636
754
  }
637
755
 
638
756
  static void
@@ -641,7 +759,7 @@ stackprof_newobj_handler(VALUE tpval, void *data)
641
759
  _stackprof.overall_signals++;
642
760
  if (RTEST(_stackprof.interval) && _stackprof.overall_signals % NUM2LONG(_stackprof.interval))
643
761
  return;
644
- stackprof_job_handler(0);
762
+ stackprof_sample_and_record();
645
763
  }
646
764
 
647
765
  static VALUE
@@ -651,7 +769,7 @@ stackprof_sample(VALUE self)
651
769
  return Qfalse;
652
770
 
653
771
  _stackprof.overall_signals++;
654
- stackprof_job_handler(0);
772
+ stackprof_sample_and_record();
655
773
  return Qtrue;
656
774
  }
657
775
 
@@ -730,6 +848,7 @@ Init_stackprof(void)
730
848
  S(mode);
731
849
  S(interval);
732
850
  S(raw);
851
+ S(raw_sample_timestamps);
733
852
  S(raw_timestamp_deltas);
734
853
  S(out);
735
854
  S(metadata);
@@ -752,9 +871,9 @@ Init_stackprof(void)
752
871
  _stackprof.raw_samples_capa = 0;
753
872
  _stackprof.raw_sample_index = 0;
754
873
 
755
- _stackprof.raw_timestamp_deltas = NULL;
756
- _stackprof.raw_timestamp_deltas_len = 0;
757
- _stackprof.raw_timestamp_deltas_capa = 0;
874
+ _stackprof.raw_sample_times = NULL;
875
+ _stackprof.raw_sample_times_len = 0;
876
+ _stackprof.raw_sample_times_capa = 0;
758
877
 
759
878
  _stackprof.empty_string = rb_str_new_cstr("");
760
879
  rb_global_variable(&_stackprof.empty_string);
@@ -95,51 +95,10 @@ module StackProf
95
95
  print_flamegraph(f, skip_common, true)
96
96
  end
97
97
 
98
- StackCursor = Struct.new(:raw, :idx, :length) do
99
- def weight
100
- @weight ||= raw[1 + idx + length]
101
- end
102
-
103
- def [](i)
104
- if i >= length
105
- nil
106
- else
107
- raw[1 + idx + i]
108
- end
109
- end
110
-
111
- def <=>(other)
112
- i = 0
113
- while i < length && i < other.length
114
- if self[i] != other[i]
115
- return self[i] <=> other[i]
116
- end
117
- i += 1
118
- end
119
-
120
- return length <=> other.length
121
- end
122
- end
123
-
124
98
  def print_flamegraph(f, skip_common, alphabetical=false)
125
99
  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
126
100
 
127
- stacks = []
128
- max_x = 0
129
- max_y = 0
130
-
131
- idx = 0
132
- loop do
133
- len = raw[idx]
134
- break unless len
135
- max_y = len if len > max_y
136
-
137
- stack = StackCursor.new(raw, idx, len)
138
- stacks << stack
139
- max_x += stack.weight
140
-
141
- idx += len + 2
142
- end
101
+ stacks, max_x, max_y = flamegraph_stacks(raw)
143
102
 
144
103
  stacks.sort! if alphabetical
145
104
 
@@ -150,7 +109,7 @@ module StackProf
150
109
  x = 0
151
110
 
152
111
  stacks.each do |stack|
153
- weight = stack.weight
112
+ weight = stack.last
154
113
  cell = stack[y] unless y == stack.length-1
155
114
 
156
115
  if cell.nil?
@@ -191,6 +150,24 @@ module StackProf
191
150
  f.puts '])'
192
151
  end
193
152
 
153
+ def flamegraph_stacks(raw)
154
+ stacks = []
155
+ max_x = 0
156
+ max_y = 0
157
+ idx = 0
158
+
159
+ while len = raw[idx]
160
+ idx += 1
161
+ max_y = len if len > max_y
162
+ stack = raw.slice(idx, len+1)
163
+ idx += len+1
164
+ stacks << stack
165
+ max_x += stack.last
166
+ end
167
+
168
+ return stacks, max_x, max_y
169
+ end
170
+
194
171
  def flamegraph_row(f, x, y, weight, addr)
195
172
  frame = @data[:frames][addr]
196
173
  f.print ',' if @rows_started
@@ -231,15 +208,7 @@ module StackProf
231
208
  def print_d3_flamegraph(f=STDOUT, skip_common=true)
232
209
  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
233
210
 
234
- stacks = []
235
- max_x = 0
236
- max_y = 0
237
- while len = raw.shift
238
- max_y = len if len > max_y
239
- stack = raw.slice!(0, len+1)
240
- stacks << stack
241
- max_x += stack.last
242
- end
211
+ stacks, * = flamegraph_stacks(raw)
243
212
 
244
213
  # d3-flame-grpah supports only alphabetical flamegraph
245
214
  stacks.sort!
@@ -445,7 +414,7 @@ module StackProf
445
414
  call, total = info.values_at(:samples, :total_samples)
446
415
  break if total < node_minimum || (limit && index >= limit)
447
416
 
448
- sample = ''
417
+ sample = ''.dup
449
418
  sample << "#{call} (%2.1f%%)\\rof " % (call*100.0/overall_samples) if call < total
450
419
  sample << "#{total} (%2.1f%%)\\r" % (total*100.0/overall_samples)
451
420
  fontsize = (1.0 * call / max_samples) * 28 + 10
data/lib/stackprof.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require "stackprof/stackprof"
2
2
 
3
3
  module StackProf
4
- VERSION = '0.2.16'
4
+ VERSION = '0.2.19'
5
5
  end
6
6
 
7
7
  StackProf.autoload :Report, "stackprof/report.rb"
data/stackprof.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'stackprof'
3
- s.version = '0.2.16'
3
+ s.version = '0.2.19'
4
4
  s.homepage = 'http://github.com/tmm1/stackprof'
5
5
 
6
6
  s.authors = 'Aman Gupta'
@@ -39,16 +39,30 @@ class StackProfTest < MiniTest::Test
39
39
  end
40
40
  assert_equal :object, profile[:mode]
41
41
  assert_equal 1, profile[:interval]
42
- assert_equal 2, profile[:samples]
42
+ if RUBY_VERSION >= '3'
43
+ assert_equal 4, profile[:samples]
44
+ else
45
+ assert_equal 2, profile[:samples]
46
+ end
43
47
 
44
48
  frame = profile[:frames].values.first
45
49
  assert_includes frame[:name], "StackProfTest#test_object_allocation"
46
50
  assert_equal 2, frame[:samples]
47
51
  assert_includes [profile_base_line - 2, profile_base_line], frame[:line]
48
- assert_equal [1, 1], frame[:lines][profile_base_line+1]
49
- assert_equal [1, 1], frame[:lines][profile_base_line+2]
52
+ if RUBY_VERSION >= '3'
53
+ assert_equal [2, 1], frame[:lines][profile_base_line+1]
54
+ assert_equal [2, 1], frame[:lines][profile_base_line+2]
55
+ else
56
+ assert_equal [1, 1], frame[:lines][profile_base_line+1]
57
+ assert_equal [1, 1], frame[:lines][profile_base_line+2]
58
+ end
50
59
  frame = profile[:frames].values[1] if RUBY_VERSION < '2.3'
51
- assert_equal [2, 0], frame[:lines][profile_base_line]
60
+
61
+ if RUBY_VERSION >= '3'
62
+ assert_equal [4, 0], frame[:lines][profile_base_line]
63
+ else
64
+ assert_equal [2, 0], frame[:lines][profile_base_line]
65
+ end
52
66
  end
53
67
 
54
68
  def test_object_allocation_interval
@@ -64,8 +78,14 @@ class StackProfTest < MiniTest::Test
64
78
  end
65
79
 
66
80
  assert_operator profile[:samples], :>=, 1
67
- frame = profile[:frames].values.first
68
- assert_includes frame[:name], "StackProfTest#math"
81
+ if RUBY_VERSION >= '3'
82
+ assert profile[:frames].values.take(2).map { |f|
83
+ f[:name].include? "StackProfTest#math"
84
+ }.any?
85
+ else
86
+ frame = profile[:frames].values.first
87
+ assert_includes frame[:name], "StackProfTest#math"
88
+ end
69
89
  end
70
90
 
71
91
  def test_walltime
@@ -74,7 +94,11 @@ class StackProfTest < MiniTest::Test
74
94
  end
75
95
 
76
96
  frame = profile[:frames].values.first
77
- assert_equal "StackProfTest#idle", frame[:name]
97
+ if RUBY_VERSION >= '3'
98
+ assert_equal "IO.select", frame[:name]
99
+ else
100
+ assert_equal "StackProfTest#idle", frame[:name]
101
+ end
78
102
  assert_in_delta 200, frame[:samples], 25
79
103
  end
80
104
 
@@ -89,24 +113,51 @@ class StackProfTest < MiniTest::Test
89
113
  assert_equal :custom, profile[:mode]
90
114
  assert_equal 10, profile[:samples]
91
115
 
92
- frame = profile[:frames].values.first
116
+ offset = RUBY_VERSION >= '3' ? 1 : 0
117
+ frame = profile[:frames].values[offset]
93
118
  assert_includes frame[:name], "StackProfTest#test_custom"
94
119
  assert_includes [profile_base_line-2, profile_base_line+1], frame[:line]
95
- assert_equal [10, 10], frame[:lines][profile_base_line+2]
120
+
121
+ if RUBY_VERSION >= '3'
122
+ assert_equal [10, 0], frame[:lines][profile_base_line+2]
123
+ else
124
+ assert_equal [10, 10], frame[:lines][profile_base_line+2]
125
+ end
96
126
  end
97
127
 
98
128
  def test_raw
129
+ before_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
130
+
99
131
  profile = StackProf.run(mode: :custom, raw: true) do
100
132
  10.times do
101
133
  StackProf.sample
134
+ sleep 0.0001
102
135
  end
103
136
  end
104
137
 
138
+ after_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
139
+
105
140
  raw = profile[:raw]
106
141
  assert_equal 10, raw[-1]
107
142
  assert_equal raw[0] + 2, raw.size
108
- assert_includes profile[:frames][raw[-2]][:name], 'StackProfTest#test_raw'
143
+
144
+ offset = RUBY_VERSION >= '3' ? -3 : -2
145
+ assert_includes profile[:frames][raw[offset]][:name], 'StackProfTest#test_raw'
146
+
147
+ assert_equal 10, profile[:raw_sample_timestamps].size
148
+ profile[:raw_sample_timestamps].each_cons(2) do |t1, t2|
149
+ assert_operator t1, :>, before_monotonic
150
+ assert_operator t2, :>=, t1
151
+ assert_operator t2, :<, after_monotonic
152
+ end
153
+
109
154
  assert_equal 10, profile[:raw_timestamp_deltas].size
155
+ total_duration = after_monotonic - before_monotonic
156
+ assert_operator profile[:raw_timestamp_deltas].inject(&:+), :<, total_duration
157
+
158
+ profile[:raw_timestamp_deltas].each do |delta|
159
+ assert_operator delta, :>, 0
160
+ end
110
161
  end
111
162
 
112
163
  def test_metadata
@@ -178,7 +229,6 @@ class StackProfTest < MiniTest::Test
178
229
  end
179
230
  end
180
231
 
181
- raw = profile[:raw]
182
232
  gc_frame = profile[:frames].values.find{ |f| f[:name] == "(garbage collection)" }
183
233
  marking_frame = profile[:frames].values.find{ |f| f[:name] == "(marking)" }
184
234
  sweeping_frame = profile[:frames].values.find{ |f| f[:name] == "(sweeping)" }
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.16
4
+ version: 0.2.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aman Gupta
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-22 00:00:00.000000000 Z
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -63,10 +63,9 @@ extensions:
63
63
  - ext/stackprof/extconf.rb
64
64
  extra_rdoc_files: []
65
65
  files:
66
+ - ".github/workflows/ci.yml"
66
67
  - ".gitignore"
67
- - ".travis.yml"
68
68
  - CHANGELOG.md
69
- - Dockerfile
70
69
  - Gemfile
71
70
  - LICENSE
72
71
  - README.md
@@ -95,10 +94,10 @@ licenses:
95
94
  - MIT
96
95
  metadata:
97
96
  bug_tracker_uri: https://github.com/tmm1/stackprof/issues
98
- changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.16/CHANGELOG.md
99
- documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.16
100
- source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.16
101
- post_install_message:
97
+ changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.19/CHANGELOG.md
98
+ documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.19
99
+ source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.19
100
+ post_install_message:
102
101
  rdoc_options: []
103
102
  require_paths:
104
103
  - lib
@@ -113,8 +112,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
112
  - !ruby/object:Gem::Version
114
113
  version: '0'
115
114
  requirements: []
116
- rubygems_version: 3.2.0.rc.2
117
- signing_key:
115
+ rubygems_version: 3.0.3.1
116
+ signing_key:
118
117
  specification_version: 4
119
118
  summary: sampling callstack-profiler for ruby 2.2+
120
119
  test_files: []
data/.travis.yml DELETED
@@ -1,21 +0,0 @@
1
- sudo: required
2
-
3
- services:
4
- - docker
5
-
6
- language: general
7
-
8
- env:
9
- matrix:
10
- - RVM_RUBY_VERSION=2.2
11
- - RVM_RUBY_VERSION=2.3
12
- - RVM_RUBY_VERSION=2.4
13
- - RVM_RUBY_VERSION=2.5
14
- - RVM_RUBY_VERSION=2.6
15
- - RVM_RUBY_VERSION=ruby-head
16
-
17
- before_install:
18
- - sudo docker build -t stackprof-$RVM_RUBY_VERSION --build-arg=RVM_RUBY_VERSION=$RVM_RUBY_VERSION .
19
-
20
- script:
21
- - sudo docker run --name stackprof-$RVM_RUBY_VERSION stackprof-$RVM_RUBY_VERSION
data/Dockerfile DELETED
@@ -1,21 +0,0 @@
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"