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 +4 -4
- data/.github/workflows/ci.yml +43 -0
- data/README.md +54 -55
- data/Rakefile +11 -25
- data/ext/stackprof/stackprof.c +187 -68
- data/lib/stackprof/report.rb +22 -53
- data/lib/stackprof.rb +1 -1
- data/stackprof.gemspec +1 -1
- data/test/test_stackprof.rb +61 -11
- metadata +10 -11
- data/.travis.yml +0 -21
- data/Dockerfile +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6662250e5e20ee3388bfce740a053c83e53fe9304b518ad22f0ce9ffc5d10077
|
4
|
+
data.tar.gz: 7480e1896e253bd530573fc2f66b3d7690db97bee3f892c77729b75c2dc8c94e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
110
|
+
Four sampling modes are supported:
|
111
111
|
|
112
|
-
-
|
113
|
-
-
|
114
|
-
-
|
115
|
-
-
|
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
|
-
|
117
|
+
Samplers have a tuneable interval which can be used to reduce overhead or increase granularity:
|
118
118
|
|
119
|
-
-
|
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
|
-
-
|
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
|
-
-
|
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
|
-
|
145
|
-
including both mark and sweep phases.
|
146
|
-
that are hard to follow
|
147
|
-
|
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
|
-
|
150
|
+
Samples are taken using a combination of three new C-APIs in ruby 2.1:
|
151
151
|
|
152
|
-
-
|
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
|
-
-
|
157
|
-
to the VM's call stack.
|
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
|
-
-
|
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
|
-
|
166
|
-
|
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
|
169
|
-
- total_samples
|
170
|
-
- lines
|
171
|
-
- edges
|
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
|
-
|
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
|
-
|
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
|
-
|
199
|
-
-
|
200
|
-
-
|
201
|
-
-
|
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
|
-
|
269
|
-
|
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
|
-
|
279
|
-
(as json/marshal for example) for later processing.
|
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
|
-
|
283
|
-
identifying information such as its name, file and 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
|
-
|
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.
|
313
|
-
divided up between its callee edges.
|
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
|
-
|
319
|
-
multiple start
|
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` |
|
335
|
-
`out` |
|
336
|
-
`interval` |
|
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` |
|
339
|
-
`raw` |
|
340
|
-
`metadata` |
|
341
|
-
`save_every`| (
|
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
|
-
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
2
3
|
|
3
|
-
|
4
|
-
|
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
|
-
|
18
|
-
|
19
|
-
ext.lib_dir =
|
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)
|
data/ext/stackprof/stackprof.c
CHANGED
@@ -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
|
58
|
-
|
59
|
-
size_t
|
60
|
-
size_t
|
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
|
-
|
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,
|
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,
|
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,
|
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
|
-
|
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.
|
332
|
-
rb_ary_push(
|
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.
|
336
|
-
_stackprof.
|
337
|
-
_stackprof.
|
338
|
-
_stackprof.
|
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,
|
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.
|
479
|
-
_stackprof.
|
480
|
-
_stackprof.
|
481
|
-
_stackprof.
|
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.
|
486
|
-
_stackprof.
|
487
|
-
_stackprof.
|
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
|
491
|
-
_stackprof.
|
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
|
-
|
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
|
-
|
604
|
+
stackprof_buffer_sample(void)
|
530
605
|
{
|
531
|
-
|
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
|
535
|
-
|
536
|
-
|
537
|
-
|
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
|
-
|
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
|
-
|
548
|
-
|
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
|
551
|
-
|
552
|
-
|
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 = (
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
741
|
+
rb_postponed_job_register_one(0, stackprof_job_record_gc, (void*)0);
|
633
742
|
} else {
|
634
|
-
|
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
|
-
|
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
|
-
|
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.
|
756
|
-
_stackprof.
|
757
|
-
_stackprof.
|
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);
|
data/lib/stackprof/report.rb
CHANGED
@@ -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.
|
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
data/stackprof.gemspec
CHANGED
data/test/test_stackprof.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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:
|
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.
|
99
|
-
documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.
|
100
|
-
source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.
|
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.
|
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"
|