stackprofx 0.2.7
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 +7 -0
- data/.gitignore +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +24 -0
- data/README.md +41 -0
- data/Rakefile +31 -0
- data/ext/extconf.rb +11 -0
- data/ext/ruby_headers/215/id.h +171 -0
- data/ext/ruby_headers/215/internal.h +889 -0
- data/ext/ruby_headers/215/iseq.h +136 -0
- data/ext/ruby_headers/215/method.h +142 -0
- data/ext/ruby_headers/215/node.h +543 -0
- data/ext/ruby_headers/215/ruby_atomic.h +170 -0
- data/ext/ruby_headers/215/thread_native.h +23 -0
- data/ext/ruby_headers/215/thread_pthread.h +56 -0
- data/ext/ruby_headers/215/thread_win32.h +45 -0
- data/ext/ruby_headers/215/vm_core.h +1042 -0
- data/ext/ruby_headers/215/vm_debug.h +37 -0
- data/ext/ruby_headers/215/vm_opts.h +56 -0
- data/ext/stackprofx.c +622 -0
- data/sample.rb +34 -0
- data/stackprofx.gemspec +20 -0
- data/test/test_stackprofx.rb +158 -0
- metadata +113 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
/**********************************************************************
|
2
|
+
|
3
|
+
vm_debug.h - YARV Debug function interface
|
4
|
+
|
5
|
+
$Author: nobu $
|
6
|
+
created at: 04/08/25 02:33:49 JST
|
7
|
+
|
8
|
+
Copyright (C) 2004-2007 Koichi Sasada
|
9
|
+
|
10
|
+
**********************************************************************/
|
11
|
+
|
12
|
+
#ifndef RUBY_DEBUG_H
|
13
|
+
#define RUBY_DEBUG_H
|
14
|
+
|
15
|
+
#include "ruby/ruby.h"
|
16
|
+
#include "node.h"
|
17
|
+
|
18
|
+
RUBY_SYMBOL_EXPORT_BEGIN
|
19
|
+
|
20
|
+
#define dpv(h,v) ruby_debug_print_value(-1, 0, (h), (v))
|
21
|
+
#define dp(v) ruby_debug_print_value(-1, 0, "", (v))
|
22
|
+
#define dpi(i) ruby_debug_print_id(-1, 0, "", (i))
|
23
|
+
#define dpn(n) ruby_debug_print_node(-1, 0, "", (n))
|
24
|
+
|
25
|
+
#define bp() ruby_debug_breakpoint()
|
26
|
+
|
27
|
+
VALUE ruby_debug_print_value(int level, int debug_level, const char *header, VALUE v);
|
28
|
+
ID ruby_debug_print_id(int level, int debug_level, const char *header, ID id);
|
29
|
+
NODE *ruby_debug_print_node(int level, int debug_level, const char *header, const NODE *node);
|
30
|
+
int ruby_debug_print_indent(int level, int debug_level, int indent_level);
|
31
|
+
void ruby_debug_breakpoint(void);
|
32
|
+
void ruby_debug_gc_check_func(void);
|
33
|
+
void ruby_set_debug_option(const char *str);
|
34
|
+
|
35
|
+
RUBY_SYMBOL_EXPORT_END
|
36
|
+
|
37
|
+
#endif /* RUBY_DEBUG_H */
|
@@ -0,0 +1,56 @@
|
|
1
|
+
/*-*-c-*-*/
|
2
|
+
/**********************************************************************
|
3
|
+
|
4
|
+
vm_opts.h - VM optimize option
|
5
|
+
|
6
|
+
$Author: tmm1 $
|
7
|
+
|
8
|
+
Copyright (C) 2004-2007 Koichi Sasada
|
9
|
+
|
10
|
+
**********************************************************************/
|
11
|
+
|
12
|
+
|
13
|
+
#ifndef RUBY_VM_OPTS_H
|
14
|
+
#define RUBY_VM_OPTS_H
|
15
|
+
|
16
|
+
/* Compile options.
|
17
|
+
* You can change these options at runtime by VM::CompileOption.
|
18
|
+
* Following definitions are default values.
|
19
|
+
*/
|
20
|
+
|
21
|
+
#define OPT_TRACE_INSTRUCTION 1
|
22
|
+
#define OPT_TAILCALL_OPTIMIZATION 0
|
23
|
+
#define OPT_PEEPHOLE_OPTIMIZATION 1
|
24
|
+
#define OPT_SPECIALISED_INSTRUCTION 1
|
25
|
+
#define OPT_INLINE_CONST_CACHE 1
|
26
|
+
|
27
|
+
|
28
|
+
/* Build Options.
|
29
|
+
* You can't change these options at runtime.
|
30
|
+
*/
|
31
|
+
|
32
|
+
/* C compiler dependent */
|
33
|
+
#define OPT_DIRECT_THREADED_CODE 1
|
34
|
+
#define OPT_TOKEN_THREADED_CODE 0
|
35
|
+
#define OPT_CALL_THREADED_CODE 0
|
36
|
+
|
37
|
+
/* VM running option */
|
38
|
+
#define OPT_CHECKED_RUN 1
|
39
|
+
#define OPT_INLINE_METHOD_CACHE 1
|
40
|
+
#define OPT_GLOBAL_METHOD_CACHE 1
|
41
|
+
#define OPT_BLOCKINLINING 0
|
42
|
+
|
43
|
+
/* architecture independent, affects generated code */
|
44
|
+
#define OPT_OPERANDS_UNIFICATION 1
|
45
|
+
#define OPT_INSTRUCTIONS_UNIFICATION 0
|
46
|
+
#define OPT_UNIFY_ALL_COMBINATION 0
|
47
|
+
#define OPT_STACK_CACHING 0
|
48
|
+
|
49
|
+
/* misc */
|
50
|
+
#define SUPPORT_JOKE 0
|
51
|
+
|
52
|
+
#ifndef VM_COLLECT_USAGE_DETAILS
|
53
|
+
#define VM_COLLECT_USAGE_DETAILS 0
|
54
|
+
#endif
|
55
|
+
|
56
|
+
#endif /* RUBY_VM_OPTS_H */
|
data/ext/stackprofx.c
ADDED
@@ -0,0 +1,622 @@
|
|
1
|
+
/**********************************************************************
|
2
|
+
|
3
|
+
stackprofx.c - Sampling call-stack frame profiler for MRI.
|
4
|
+
|
5
|
+
vim: setl 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 ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))
|
19
|
+
|
20
|
+
#include "vm_core.h"
|
21
|
+
#include "iseq.h"
|
22
|
+
|
23
|
+
static inline const rb_data_type_t *
|
24
|
+
threadptr_data_type(void)
|
25
|
+
{
|
26
|
+
static const rb_data_type_t *thread_data_type;
|
27
|
+
if (!thread_data_type) {
|
28
|
+
VALUE current_thread = rb_thread_current();
|
29
|
+
thread_data_type = RTYPEDDATA_TYPE(current_thread);
|
30
|
+
}
|
31
|
+
return thread_data_type;
|
32
|
+
}
|
33
|
+
|
34
|
+
#define ruby_thread_data_type *threadptr_data_type()
|
35
|
+
#define ruby_threadptr_data_type *threadptr_data_type()
|
36
|
+
|
37
|
+
#define BUF_SIZE 2048
|
38
|
+
|
39
|
+
typedef struct {
|
40
|
+
size_t total_samples;
|
41
|
+
size_t caller_samples;
|
42
|
+
st_table *edges;
|
43
|
+
st_table *lines;
|
44
|
+
} frame_data_t;
|
45
|
+
|
46
|
+
static struct {
|
47
|
+
int running;
|
48
|
+
int raw;
|
49
|
+
int aggregate;
|
50
|
+
|
51
|
+
VALUE mode;
|
52
|
+
VALUE interval;
|
53
|
+
VALUE out;
|
54
|
+
|
55
|
+
VALUE *raw_samples;
|
56
|
+
size_t raw_samples_len;
|
57
|
+
size_t raw_samples_capa;
|
58
|
+
size_t raw_sample_index;
|
59
|
+
|
60
|
+
size_t overall_signals;
|
61
|
+
size_t overall_samples;
|
62
|
+
size_t during_gc;
|
63
|
+
st_table *frames;
|
64
|
+
|
65
|
+
st_table *threads;
|
66
|
+
|
67
|
+
VALUE frames_buffer[BUF_SIZE];
|
68
|
+
int lines_buffer[BUF_SIZE];
|
69
|
+
} _stackprofx;
|
70
|
+
|
71
|
+
static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line, sym_threads;
|
72
|
+
static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
|
73
|
+
static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_frames, sym_out, sym_aggregate;
|
74
|
+
static VALUE sym_gc_samples, objtracer;
|
75
|
+
static VALUE gc_hook;
|
76
|
+
static VALUE rb_mStackProfx;
|
77
|
+
|
78
|
+
static void stackprofx_newobj_handler(VALUE, void*);
|
79
|
+
static void stackprofx_signal_handler(int sig, siginfo_t* sinfo, void* ucontext);
|
80
|
+
|
81
|
+
static VALUE
|
82
|
+
stackprofx_start(int argc, VALUE *argv, VALUE self)
|
83
|
+
{
|
84
|
+
struct sigaction sa;
|
85
|
+
struct itimerval timer;
|
86
|
+
VALUE opts = Qnil, mode = Qnil, interval = Qnil, out = Qfalse, threads = Qnil;
|
87
|
+
int raw = 0, aggregate = 1;
|
88
|
+
|
89
|
+
if (_stackprofx.running)
|
90
|
+
return Qfalse;
|
91
|
+
|
92
|
+
rb_scan_args(argc, argv, "0:", &opts);
|
93
|
+
|
94
|
+
if (RTEST(opts)) {
|
95
|
+
mode = rb_hash_aref(opts, sym_mode);
|
96
|
+
interval = rb_hash_aref(opts, sym_interval);
|
97
|
+
out = rb_hash_aref(opts, sym_out);
|
98
|
+
threads = rb_hash_aref(opts, sym_threads);
|
99
|
+
|
100
|
+
if (RTEST(rb_hash_aref(opts, sym_raw)))
|
101
|
+
raw = 1;
|
102
|
+
if (rb_hash_lookup2(opts, sym_aggregate, Qundef) == Qfalse)
|
103
|
+
aggregate = 0;
|
104
|
+
}
|
105
|
+
if (!RTEST(mode)) mode = sym_wall;
|
106
|
+
|
107
|
+
if (RTEST(threads))
|
108
|
+
{
|
109
|
+
_stackprofx.threads = st_init_numtable();
|
110
|
+
for (int i = 0; i < RARRAY_LEN(threads); i++)
|
111
|
+
{
|
112
|
+
VALUE thr = rb_ary_entry(threads, i);
|
113
|
+
st_add_direct(_stackprofx.threads, thr, 0);
|
114
|
+
rb_gc_mark(thr);
|
115
|
+
}
|
116
|
+
}
|
117
|
+
else
|
118
|
+
{
|
119
|
+
_stackprofx.threads = 0;
|
120
|
+
}
|
121
|
+
|
122
|
+
if (!_stackprofx.frames) {
|
123
|
+
_stackprofx.frames = st_init_numtable();
|
124
|
+
_stackprofx.overall_signals = 0;
|
125
|
+
_stackprofx.overall_samples = 0;
|
126
|
+
_stackprofx.during_gc = 0;
|
127
|
+
}
|
128
|
+
|
129
|
+
if (mode == sym_object) {
|
130
|
+
if (!RTEST(interval)) interval = INT2FIX(1);
|
131
|
+
|
132
|
+
objtracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, stackprofx_newobj_handler, 0);
|
133
|
+
rb_tracepoint_enable(objtracer);
|
134
|
+
} else if (mode == sym_wall || mode == sym_cpu) {
|
135
|
+
if (!RTEST(interval)) interval = INT2FIX(1000);
|
136
|
+
|
137
|
+
sa.sa_sigaction = stackprofx_signal_handler;
|
138
|
+
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
139
|
+
sigemptyset(&sa.sa_mask);
|
140
|
+
sigaction(mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
|
141
|
+
|
142
|
+
timer.it_interval.tv_sec = 0;
|
143
|
+
timer.it_interval.tv_usec = NUM2LONG(interval);
|
144
|
+
timer.it_value = timer.it_interval;
|
145
|
+
setitimer(mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
|
146
|
+
} else if (mode == sym_custom) {
|
147
|
+
/* sampled manually */
|
148
|
+
interval = Qnil;
|
149
|
+
} else {
|
150
|
+
rb_raise(rb_eArgError, "unknown profiler mode");
|
151
|
+
}
|
152
|
+
|
153
|
+
_stackprofx.running = 1;
|
154
|
+
_stackprofx.raw = raw;
|
155
|
+
_stackprofx.aggregate = aggregate;
|
156
|
+
_stackprofx.mode = mode;
|
157
|
+
_stackprofx.interval = interval;
|
158
|
+
_stackprofx.out = out;
|
159
|
+
|
160
|
+
return Qtrue;
|
161
|
+
}
|
162
|
+
|
163
|
+
static VALUE
|
164
|
+
stackprofx_stop(VALUE self)
|
165
|
+
{
|
166
|
+
struct sigaction sa;
|
167
|
+
struct itimerval timer;
|
168
|
+
|
169
|
+
if (!_stackprofx.running)
|
170
|
+
return Qfalse;
|
171
|
+
_stackprofx.running = 0;
|
172
|
+
|
173
|
+
st_free_table(_stackprofx.threads);
|
174
|
+
|
175
|
+
if (_stackprofx.mode == sym_object) {
|
176
|
+
rb_tracepoint_disable(objtracer);
|
177
|
+
} else if (_stackprofx.mode == sym_wall || _stackprofx.mode == sym_cpu) {
|
178
|
+
memset(&timer, 0, sizeof(timer));
|
179
|
+
setitimer(_stackprofx.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
|
180
|
+
|
181
|
+
sa.sa_handler = SIG_IGN;
|
182
|
+
sa.sa_flags = SA_RESTART;
|
183
|
+
sigemptyset(&sa.sa_mask);
|
184
|
+
sigaction(_stackprofx.mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
|
185
|
+
} else if (_stackprofx.mode == sym_custom) {
|
186
|
+
/* sampled manually */
|
187
|
+
} else {
|
188
|
+
rb_raise(rb_eArgError, "unknown profiler mode");
|
189
|
+
}
|
190
|
+
|
191
|
+
return Qtrue;
|
192
|
+
}
|
193
|
+
|
194
|
+
static int
|
195
|
+
frame_edges_i(st_data_t key, st_data_t val, st_data_t arg)
|
196
|
+
{
|
197
|
+
VALUE edges = (VALUE)arg;
|
198
|
+
|
199
|
+
intptr_t weight = (intptr_t)val;
|
200
|
+
rb_hash_aset(edges, rb_obj_id((VALUE)key), INT2FIX(weight));
|
201
|
+
return ST_CONTINUE;
|
202
|
+
}
|
203
|
+
|
204
|
+
static int
|
205
|
+
frame_lines_i(st_data_t key, st_data_t val, st_data_t arg)
|
206
|
+
{
|
207
|
+
VALUE lines = (VALUE)arg;
|
208
|
+
|
209
|
+
size_t weight = (size_t)val;
|
210
|
+
size_t total = weight & (~(size_t)0 << (8*SIZEOF_SIZE_T/2));
|
211
|
+
weight -= total;
|
212
|
+
total = total >> (8*SIZEOF_SIZE_T/2);
|
213
|
+
rb_hash_aset(lines, INT2FIX(key), rb_ary_new3(2, ULONG2NUM(total), ULONG2NUM(weight)));
|
214
|
+
return ST_CONTINUE;
|
215
|
+
}
|
216
|
+
|
217
|
+
static int
|
218
|
+
frame_i(st_data_t key, st_data_t val, st_data_t arg)
|
219
|
+
{
|
220
|
+
VALUE frame = (VALUE)key;
|
221
|
+
frame_data_t *frame_data = (frame_data_t *)val;
|
222
|
+
VALUE results = (VALUE)arg;
|
223
|
+
VALUE details = rb_hash_new();
|
224
|
+
VALUE name, file, edges, lines;
|
225
|
+
VALUE line;
|
226
|
+
|
227
|
+
rb_hash_aset(results, rb_obj_id(frame), details);
|
228
|
+
|
229
|
+
name = rb_profile_frame_full_label(frame);
|
230
|
+
rb_hash_aset(details, sym_name, name);
|
231
|
+
|
232
|
+
file = rb_profile_frame_absolute_path(frame);
|
233
|
+
if (NIL_P(file))
|
234
|
+
file = rb_profile_frame_path(frame);
|
235
|
+
rb_hash_aset(details, sym_file, file);
|
236
|
+
|
237
|
+
if ((line = rb_profile_frame_first_lineno(frame)) != INT2FIX(0))
|
238
|
+
rb_hash_aset(details, sym_line, line);
|
239
|
+
|
240
|
+
rb_hash_aset(details, sym_total_samples, SIZET2NUM(frame_data->total_samples));
|
241
|
+
rb_hash_aset(details, sym_samples, SIZET2NUM(frame_data->caller_samples));
|
242
|
+
|
243
|
+
if (frame_data->edges) {
|
244
|
+
edges = rb_hash_new();
|
245
|
+
rb_hash_aset(details, sym_edges, edges);
|
246
|
+
st_foreach(frame_data->edges, frame_edges_i, (st_data_t)edges);
|
247
|
+
st_free_table(frame_data->edges);
|
248
|
+
frame_data->edges = NULL;
|
249
|
+
}
|
250
|
+
|
251
|
+
if (frame_data->lines) {
|
252
|
+
lines = rb_hash_new();
|
253
|
+
rb_hash_aset(details, sym_lines, lines);
|
254
|
+
st_foreach(frame_data->lines, frame_lines_i, (st_data_t)lines);
|
255
|
+
st_free_table(frame_data->lines);
|
256
|
+
frame_data->lines = NULL;
|
257
|
+
}
|
258
|
+
|
259
|
+
xfree(frame_data);
|
260
|
+
return ST_DELETE;
|
261
|
+
}
|
262
|
+
|
263
|
+
static VALUE
|
264
|
+
stackprofx_results(int argc, VALUE *argv, VALUE self)
|
265
|
+
{
|
266
|
+
VALUE results, frames;
|
267
|
+
|
268
|
+
if (!_stackprofx.frames || _stackprofx.running)
|
269
|
+
return Qnil;
|
270
|
+
|
271
|
+
results = rb_hash_new();
|
272
|
+
rb_hash_aset(results, sym_version, DBL2NUM(1.1));
|
273
|
+
rb_hash_aset(results, sym_mode, _stackprofx.mode);
|
274
|
+
rb_hash_aset(results, sym_interval, _stackprofx.interval);
|
275
|
+
rb_hash_aset(results, sym_samples, SIZET2NUM(_stackprofx.overall_samples));
|
276
|
+
rb_hash_aset(results, sym_gc_samples, SIZET2NUM(_stackprofx.during_gc));
|
277
|
+
rb_hash_aset(results, sym_missed_samples, SIZET2NUM(_stackprofx.overall_signals - _stackprofx.overall_samples));
|
278
|
+
|
279
|
+
frames = rb_hash_new();
|
280
|
+
rb_hash_aset(results, sym_frames, frames);
|
281
|
+
st_foreach(_stackprofx.frames, frame_i, (st_data_t)frames);
|
282
|
+
|
283
|
+
st_free_table(_stackprofx.frames);
|
284
|
+
_stackprofx.frames = NULL;
|
285
|
+
|
286
|
+
if (_stackprofx.raw && _stackprofx.raw_samples_len) {
|
287
|
+
size_t len, n, o;
|
288
|
+
VALUE raw_samples = rb_ary_new_capa(_stackprofx.raw_samples_len);
|
289
|
+
|
290
|
+
for (n = 0; n < _stackprofx.raw_samples_len; n++) {
|
291
|
+
len = (size_t)_stackprofx.raw_samples[n];
|
292
|
+
rb_ary_push(raw_samples, SIZET2NUM(len));
|
293
|
+
|
294
|
+
for (o = 0, n++; o < len; n++, o++)
|
295
|
+
rb_ary_push(raw_samples, rb_obj_id(_stackprofx.raw_samples[n]));
|
296
|
+
rb_ary_push(raw_samples, SIZET2NUM((size_t)_stackprofx.raw_samples[n]));
|
297
|
+
}
|
298
|
+
|
299
|
+
free(_stackprofx.raw_samples);
|
300
|
+
_stackprofx.raw_samples = NULL;
|
301
|
+
_stackprofx.raw_samples_len = 0;
|
302
|
+
_stackprofx.raw_samples_capa = 0;
|
303
|
+
_stackprofx.raw_sample_index = 0;
|
304
|
+
_stackprofx.raw = 0;
|
305
|
+
|
306
|
+
rb_hash_aset(results, sym_raw, raw_samples);
|
307
|
+
}
|
308
|
+
|
309
|
+
if (argc == 1)
|
310
|
+
_stackprofx.out = argv[0];
|
311
|
+
|
312
|
+
if (RTEST(_stackprofx.out)) {
|
313
|
+
VALUE file;
|
314
|
+
if (RB_TYPE_P(_stackprofx.out, T_STRING)) {
|
315
|
+
file = rb_file_open_str(_stackprofx.out, "w");
|
316
|
+
} else {
|
317
|
+
file = rb_io_check_io(_stackprofx.out);
|
318
|
+
}
|
319
|
+
rb_marshal_dump(results, file);
|
320
|
+
rb_io_flush(file);
|
321
|
+
_stackprofx.out = Qnil;
|
322
|
+
return file;
|
323
|
+
} else {
|
324
|
+
return results;
|
325
|
+
}
|
326
|
+
}
|
327
|
+
|
328
|
+
static VALUE
|
329
|
+
stackprofx_run(int argc, VALUE *argv, VALUE self)
|
330
|
+
{
|
331
|
+
rb_need_block();
|
332
|
+
stackprofx_start(argc, argv, self);
|
333
|
+
rb_ensure(rb_yield, Qundef, stackprofx_stop, self);
|
334
|
+
return stackprofx_results(0, 0, self);
|
335
|
+
}
|
336
|
+
|
337
|
+
static VALUE
|
338
|
+
stackprofx_running_p(VALUE self)
|
339
|
+
{
|
340
|
+
return _stackprofx.running ? Qtrue : Qfalse;
|
341
|
+
}
|
342
|
+
|
343
|
+
static inline frame_data_t *
|
344
|
+
sample_for(VALUE frame)
|
345
|
+
{
|
346
|
+
st_data_t key = (st_data_t)frame, val = 0;
|
347
|
+
frame_data_t *frame_data;
|
348
|
+
|
349
|
+
if (st_lookup(_stackprofx.frames, key, &val)) {
|
350
|
+
frame_data = (frame_data_t *)val;
|
351
|
+
} else {
|
352
|
+
frame_data = ALLOC_N(frame_data_t, 1);
|
353
|
+
MEMZERO(frame_data, frame_data_t, 1);
|
354
|
+
val = (st_data_t)frame_data;
|
355
|
+
st_insert(_stackprofx.frames, key, val);
|
356
|
+
}
|
357
|
+
|
358
|
+
return frame_data;
|
359
|
+
}
|
360
|
+
|
361
|
+
static int
|
362
|
+
numtable_increment_callback(st_data_t *key, st_data_t *value, st_data_t arg, int existing)
|
363
|
+
{
|
364
|
+
size_t *weight = (size_t *)value;
|
365
|
+
size_t increment = (size_t)arg;
|
366
|
+
|
367
|
+
if (existing)
|
368
|
+
(*weight) += increment;
|
369
|
+
else
|
370
|
+
*weight = increment;
|
371
|
+
|
372
|
+
return ST_CONTINUE;
|
373
|
+
}
|
374
|
+
|
375
|
+
void
|
376
|
+
st_numtable_increment(st_table *table, st_data_t key, size_t increment)
|
377
|
+
{
|
378
|
+
st_update(table, key, numtable_increment_callback, (st_data_t)increment);
|
379
|
+
}
|
380
|
+
|
381
|
+
// thanks to https://bugs.ruby-lang.org/issues/10602
|
382
|
+
int
|
383
|
+
rb_profile_frames_thread(int start, int limit, VALUE *buff, int *lines, rb_thread_t *th)
|
384
|
+
{
|
385
|
+
int i;
|
386
|
+
rb_control_frame_t *cfp = th->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(th);
|
387
|
+
|
388
|
+
for (i=0; i<limit && cfp != end_cfp;) {
|
389
|
+
if (cfp->iseq && cfp->pc) { /* should be NORMAL_ISEQ */
|
390
|
+
if (start > 0) {
|
391
|
+
start--;
|
392
|
+
continue;
|
393
|
+
}
|
394
|
+
|
395
|
+
/* record frame info */
|
396
|
+
buff[i] = cfp->iseq->self;
|
397
|
+
if (lines) lines[i] = rb_iseq_line_no(cfp->iseq, cfp->pc - cfp->iseq->iseq_encoded);
|
398
|
+
i++;
|
399
|
+
}
|
400
|
+
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
|
401
|
+
}
|
402
|
+
|
403
|
+
return i;
|
404
|
+
}
|
405
|
+
|
406
|
+
int
|
407
|
+
stackprofx_record_sample_i(st_data_t key, st_data_t val, st_data_t arg)
|
408
|
+
{
|
409
|
+
int num, i, n;
|
410
|
+
VALUE prev_frame = Qnil;
|
411
|
+
|
412
|
+
rb_thread_t *th;
|
413
|
+
GetThreadPtr((VALUE)key, th);
|
414
|
+
if (th->status == THREAD_KILLED) return ST_CONTINUE;
|
415
|
+
|
416
|
+
num = rb_profile_frames_thread(0, sizeof(_stackprofx.frames_buffer) / sizeof(VALUE), _stackprofx.frames_buffer, _stackprofx.lines_buffer, th);
|
417
|
+
|
418
|
+
if (_stackprofx.raw) {
|
419
|
+
int found = 0;
|
420
|
+
|
421
|
+
if (!_stackprofx.raw_samples) {
|
422
|
+
_stackprofx.raw_samples_capa = num * 100;
|
423
|
+
_stackprofx.raw_samples = malloc(sizeof(VALUE) * _stackprofx.raw_samples_capa);
|
424
|
+
}
|
425
|
+
|
426
|
+
if (_stackprofx.raw_samples_capa <= _stackprofx.raw_samples_len + num) {
|
427
|
+
_stackprofx.raw_samples_capa *= 2;
|
428
|
+
_stackprofx.raw_samples = realloc(_stackprofx.raw_samples, sizeof(VALUE) * _stackprofx.raw_samples_capa);
|
429
|
+
}
|
430
|
+
|
431
|
+
if (_stackprofx.raw_samples_len > 0 && _stackprofx.raw_samples[_stackprofx.raw_sample_index] == (VALUE)num) {
|
432
|
+
for (i = num-1, n = 0; i >= 0; i--, n++) {
|
433
|
+
VALUE frame = _stackprofx.frames_buffer[i];
|
434
|
+
if (_stackprofx.raw_samples[_stackprofx.raw_sample_index + 1 + n] != frame)
|
435
|
+
break;
|
436
|
+
}
|
437
|
+
if (i == -1) {
|
438
|
+
_stackprofx.raw_samples[_stackprofx.raw_samples_len-1] += 1;
|
439
|
+
found = 1;
|
440
|
+
}
|
441
|
+
}
|
442
|
+
|
443
|
+
if (!found) {
|
444
|
+
_stackprofx.raw_sample_index = _stackprofx.raw_samples_len;
|
445
|
+
_stackprofx.raw_samples[_stackprofx.raw_samples_len++] = (VALUE)num;
|
446
|
+
for (i = num-1; i >= 0; i--) {
|
447
|
+
VALUE frame = _stackprofx.frames_buffer[i];
|
448
|
+
_stackprofx.raw_samples[_stackprofx.raw_samples_len++] = frame;
|
449
|
+
}
|
450
|
+
_stackprofx.raw_samples[_stackprofx.raw_samples_len++] = (VALUE)1;
|
451
|
+
}
|
452
|
+
}
|
453
|
+
|
454
|
+
for (i = 0; i < num; i++) {
|
455
|
+
int line = _stackprofx.lines_buffer[i];
|
456
|
+
VALUE frame = _stackprofx.frames_buffer[i];
|
457
|
+
frame_data_t *frame_data = sample_for(frame);
|
458
|
+
|
459
|
+
frame_data->total_samples++;
|
460
|
+
|
461
|
+
if (i == 0) {
|
462
|
+
frame_data->caller_samples++;
|
463
|
+
} else if (_stackprofx.aggregate) {
|
464
|
+
if (!frame_data->edges)
|
465
|
+
frame_data->edges = st_init_numtable();
|
466
|
+
st_numtable_increment(frame_data->edges, (st_data_t)prev_frame, 1);
|
467
|
+
}
|
468
|
+
|
469
|
+
if (_stackprofx.aggregate && line > 0) {
|
470
|
+
if (!frame_data->lines)
|
471
|
+
frame_data->lines = st_init_numtable();
|
472
|
+
size_t half = (size_t)1<<(8*SIZEOF_SIZE_T/2);
|
473
|
+
size_t increment = i == 0 ? half + 1 : half;
|
474
|
+
st_numtable_increment(frame_data->lines, (st_data_t)line, increment);
|
475
|
+
}
|
476
|
+
|
477
|
+
prev_frame = frame;
|
478
|
+
}
|
479
|
+
|
480
|
+
return ST_CONTINUE;
|
481
|
+
}
|
482
|
+
|
483
|
+
void
|
484
|
+
stackprofx_record_sample()
|
485
|
+
{
|
486
|
+
_stackprofx.overall_samples++;
|
487
|
+
st_table *tbl = _stackprofx.threads ?: GET_THREAD()->vm->living_threads;
|
488
|
+
st_foreach(tbl, stackprofx_record_sample_i, 0);
|
489
|
+
}
|
490
|
+
|
491
|
+
static void
|
492
|
+
stackprofx_job_handler(void *data)
|
493
|
+
{
|
494
|
+
static int in_signal_handler = 0;
|
495
|
+
if (in_signal_handler) return;
|
496
|
+
if (!_stackprofx.running) return;
|
497
|
+
|
498
|
+
in_signal_handler++;
|
499
|
+
stackprofx_record_sample();
|
500
|
+
in_signal_handler--;
|
501
|
+
}
|
502
|
+
|
503
|
+
static void
|
504
|
+
stackprofx_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
|
505
|
+
{
|
506
|
+
_stackprofx.overall_signals++;
|
507
|
+
if (rb_during_gc())
|
508
|
+
_stackprofx.during_gc++, _stackprofx.overall_samples++;
|
509
|
+
else
|
510
|
+
rb_postponed_job_register_one(0, stackprofx_job_handler, 0);
|
511
|
+
}
|
512
|
+
|
513
|
+
static void
|
514
|
+
stackprofx_newobj_handler(VALUE tpval, void *data)
|
515
|
+
{
|
516
|
+
_stackprofx.overall_signals++;
|
517
|
+
if (RTEST(_stackprofx.interval) && _stackprofx.overall_signals % NUM2LONG(_stackprofx.interval))
|
518
|
+
return;
|
519
|
+
stackprofx_job_handler(0);
|
520
|
+
}
|
521
|
+
|
522
|
+
static VALUE
|
523
|
+
stackprofx_sample(VALUE self)
|
524
|
+
{
|
525
|
+
if (!_stackprofx.running)
|
526
|
+
return Qfalse;
|
527
|
+
|
528
|
+
_stackprofx.overall_signals++;
|
529
|
+
stackprofx_job_handler(0);
|
530
|
+
return Qtrue;
|
531
|
+
}
|
532
|
+
|
533
|
+
static int
|
534
|
+
frame_mark_i(st_data_t key, st_data_t val, st_data_t arg)
|
535
|
+
{
|
536
|
+
VALUE frame = (VALUE)key;
|
537
|
+
rb_gc_mark(frame);
|
538
|
+
return ST_CONTINUE;
|
539
|
+
}
|
540
|
+
|
541
|
+
static void
|
542
|
+
stackprofx_gc_mark(void *data)
|
543
|
+
{
|
544
|
+
if (RTEST(_stackprofx.out))
|
545
|
+
rb_gc_mark(_stackprofx.out);
|
546
|
+
|
547
|
+
if (_stackprofx.frames)
|
548
|
+
st_foreach(_stackprofx.frames, frame_mark_i, 0);
|
549
|
+
}
|
550
|
+
|
551
|
+
static void
|
552
|
+
stackprofx_atfork_prepare(void)
|
553
|
+
{
|
554
|
+
struct itimerval timer;
|
555
|
+
if (_stackprofx.running) {
|
556
|
+
if (_stackprofx.mode == sym_wall || _stackprofx.mode == sym_cpu) {
|
557
|
+
memset(&timer, 0, sizeof(timer));
|
558
|
+
setitimer(_stackprofx.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
|
559
|
+
}
|
560
|
+
}
|
561
|
+
}
|
562
|
+
|
563
|
+
static void
|
564
|
+
stackprofx_atfork_parent(void)
|
565
|
+
{
|
566
|
+
struct itimerval timer;
|
567
|
+
if (_stackprofx.running) {
|
568
|
+
if (_stackprofx.mode == sym_wall || _stackprofx.mode == sym_cpu) {
|
569
|
+
timer.it_interval.tv_sec = 0;
|
570
|
+
timer.it_interval.tv_usec = NUM2LONG(_stackprofx.interval);
|
571
|
+
timer.it_value = timer.it_interval;
|
572
|
+
setitimer(_stackprofx.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
|
573
|
+
}
|
574
|
+
}
|
575
|
+
}
|
576
|
+
|
577
|
+
static void
|
578
|
+
stackprofx_atfork_child(void)
|
579
|
+
{
|
580
|
+
stackprofx_stop(rb_mStackProfx);
|
581
|
+
}
|
582
|
+
|
583
|
+
void
|
584
|
+
Init_stackprofx(void)
|
585
|
+
{
|
586
|
+
#define S(name) sym_##name = ID2SYM(rb_intern(#name));
|
587
|
+
S(object);
|
588
|
+
S(custom);
|
589
|
+
S(wall);
|
590
|
+
S(threads);
|
591
|
+
S(cpu);
|
592
|
+
S(name);
|
593
|
+
S(file);
|
594
|
+
S(line);
|
595
|
+
S(total_samples);
|
596
|
+
S(gc_samples);
|
597
|
+
S(missed_samples);
|
598
|
+
S(samples);
|
599
|
+
S(edges);
|
600
|
+
S(lines);
|
601
|
+
S(version);
|
602
|
+
S(mode);
|
603
|
+
S(interval);
|
604
|
+
S(raw);
|
605
|
+
S(out);
|
606
|
+
S(frames);
|
607
|
+
S(aggregate);
|
608
|
+
#undef S
|
609
|
+
|
610
|
+
gc_hook = Data_Wrap_Struct(rb_cObject, stackprofx_gc_mark, NULL, NULL);
|
611
|
+
rb_global_variable(&gc_hook);
|
612
|
+
|
613
|
+
rb_mStackProfx = rb_define_module("StackProfx");
|
614
|
+
rb_define_singleton_method(rb_mStackProfx, "running?", stackprofx_running_p, 0);
|
615
|
+
rb_define_singleton_method(rb_mStackProfx, "run", stackprofx_run, -1);
|
616
|
+
rb_define_singleton_method(rb_mStackProfx, "start", stackprofx_start, -1);
|
617
|
+
rb_define_singleton_method(rb_mStackProfx, "stop", stackprofx_stop, 0);
|
618
|
+
rb_define_singleton_method(rb_mStackProfx, "results", stackprofx_results, -1);
|
619
|
+
rb_define_singleton_method(rb_mStackProfx, "sample", stackprofx_sample, 0);
|
620
|
+
|
621
|
+
pthread_atfork(stackprofx_atfork_prepare, stackprofx_atfork_parent, stackprofx_atfork_child);
|
622
|
+
}
|