pf2 0.7.1 → 0.9.0
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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +11 -0
- data/Rakefile +9 -2
- data/doc/development.md +11 -0
- data/examples/mandelbrot.rb +69 -0
- data/examples/mandelbrot_ractor.rb +77 -0
- data/ext/pf2/build.rs +7 -0
- data/ext/pf2/src/ruby_c_api_helper.c +6 -0
- data/ext/pf2/src/serialization/profile.rs +1 -0
- data/ext/pf2/src/serialization/serializer.rs +4 -0
- data/ext/pf2/src/signal_scheduler.rs +1 -1
- data/ext/pf2/src/util.rs +2 -1
- data/ext/pf2c/backtrace_state.c +10 -0
- data/ext/pf2c/backtrace_state.h +10 -0
- data/ext/pf2c/configuration.c +90 -0
- data/ext/pf2c/configuration.h +23 -0
- data/ext/pf2c/extconf.rb +21 -0
- data/ext/pf2c/pf2.c +17 -0
- data/ext/pf2c/pf2.h +8 -0
- data/ext/pf2c/ringbuffer.c +74 -0
- data/ext/pf2c/ringbuffer.h +24 -0
- data/ext/pf2c/sample.c +70 -0
- data/ext/pf2c/sample.h +22 -0
- data/ext/pf2c/serializer.c +377 -0
- data/ext/pf2c/serializer.h +58 -0
- data/ext/pf2c/session.c +344 -0
- data/ext/pf2c/session.h +51 -0
- data/lib/pf2/cli.rb +33 -2
- data/lib/pf2/reporter/annotate.rb +101 -0
- data/lib/pf2/reporter/firefox_profiler.rb +1 -1
- data/lib/pf2/reporter/firefox_profiler_ser2.rb +308 -0
- data/lib/pf2/reporter.rb +2 -0
- data/lib/pf2/version.rb +1 -1
- data/vendor/libbacktrace/.gitignore +5 -0
- data/vendor/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
- data/vendor/libbacktrace/LICENSE +29 -0
- data/vendor/libbacktrace/Makefile.am +708 -0
- data/vendor/libbacktrace/Makefile.in +2820 -0
- data/vendor/libbacktrace/README.md +46 -0
- data/vendor/libbacktrace/aclocal.m4 +864 -0
- data/vendor/libbacktrace/alloc.c +167 -0
- data/vendor/libbacktrace/allocfail.c +136 -0
- data/vendor/libbacktrace/allocfail.sh +104 -0
- data/vendor/libbacktrace/atomic.c +113 -0
- data/vendor/libbacktrace/backtrace-supported.h.in +66 -0
- data/vendor/libbacktrace/backtrace.c +129 -0
- data/vendor/libbacktrace/backtrace.h +189 -0
- data/vendor/libbacktrace/btest.c +517 -0
- data/vendor/libbacktrace/compile +348 -0
- data/vendor/libbacktrace/config/enable.m4 +38 -0
- data/vendor/libbacktrace/config/lead-dot.m4 +31 -0
- data/vendor/libbacktrace/config/libtool.m4 +7545 -0
- data/vendor/libbacktrace/config/ltoptions.m4 +369 -0
- data/vendor/libbacktrace/config/ltsugar.m4 +123 -0
- data/vendor/libbacktrace/config/ltversion.m4 +23 -0
- data/vendor/libbacktrace/config/lt~obsolete.m4 +98 -0
- data/vendor/libbacktrace/config/multi.m4 +68 -0
- data/vendor/libbacktrace/config/override.m4 +117 -0
- data/vendor/libbacktrace/config/unwind_ipinfo.m4 +37 -0
- data/vendor/libbacktrace/config/warnings.m4 +227 -0
- data/vendor/libbacktrace/config.guess +1700 -0
- data/vendor/libbacktrace/config.h.in +185 -0
- data/vendor/libbacktrace/config.sub +1885 -0
- data/vendor/libbacktrace/configure +15952 -0
- data/vendor/libbacktrace/configure.ac +642 -0
- data/vendor/libbacktrace/dwarf.c +4593 -0
- data/vendor/libbacktrace/edtest.c +120 -0
- data/vendor/libbacktrace/edtest2.c +43 -0
- data/vendor/libbacktrace/elf.c +7471 -0
- data/vendor/libbacktrace/fileline.c +407 -0
- data/vendor/libbacktrace/filenames.h +52 -0
- data/vendor/libbacktrace/filetype.awk +13 -0
- data/vendor/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
- data/vendor/libbacktrace/install-sh +501 -0
- data/vendor/libbacktrace/instrumented_alloc.c +114 -0
- data/vendor/libbacktrace/internal.h +428 -0
- data/vendor/libbacktrace/ltmain.sh +8636 -0
- data/vendor/libbacktrace/macho.c +1361 -0
- data/vendor/libbacktrace/missing +215 -0
- data/vendor/libbacktrace/mmap.c +331 -0
- data/vendor/libbacktrace/mmapio.c +110 -0
- data/vendor/libbacktrace/move-if-change +83 -0
- data/vendor/libbacktrace/mtest.c +410 -0
- data/vendor/libbacktrace/nounwind.c +66 -0
- data/vendor/libbacktrace/pecoff.c +1123 -0
- data/vendor/libbacktrace/posix.c +104 -0
- data/vendor/libbacktrace/print.c +117 -0
- data/vendor/libbacktrace/read.c +110 -0
- data/vendor/libbacktrace/simple.c +108 -0
- data/vendor/libbacktrace/sort.c +108 -0
- data/vendor/libbacktrace/state.c +72 -0
- data/vendor/libbacktrace/stest.c +137 -0
- data/vendor/libbacktrace/test-driver +148 -0
- data/vendor/libbacktrace/test_format.c +55 -0
- data/vendor/libbacktrace/testlib.c +234 -0
- data/vendor/libbacktrace/testlib.h +110 -0
- data/vendor/libbacktrace/ttest.c +161 -0
- data/vendor/libbacktrace/unittest.c +92 -0
- data/vendor/libbacktrace/unknown.c +65 -0
- data/vendor/libbacktrace/xcoff.c +1617 -0
- data/vendor/libbacktrace/xztest.c +508 -0
- data/vendor/libbacktrace/zstdtest.c +523 -0
- data/vendor/libbacktrace/ztest.c +541 -0
- metadata +122 -3
data/ext/pf2c/session.c
ADDED
@@ -0,0 +1,344 @@
|
|
1
|
+
#include <bits/time.h>
|
2
|
+
#include <stdatomic.h>
|
3
|
+
#include <stdbool.h>
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
#include <signal.h>
|
7
|
+
#include <time.h>
|
8
|
+
#include <pthread.h>
|
9
|
+
|
10
|
+
#include <ruby.h>
|
11
|
+
#include <ruby/debug.h>
|
12
|
+
|
13
|
+
#include <backtrace.h>
|
14
|
+
|
15
|
+
#include "backtrace_state.h"
|
16
|
+
#include "configuration.h"
|
17
|
+
#include "sample.h"
|
18
|
+
#include "session.h"
|
19
|
+
#include "serializer.h"
|
20
|
+
|
21
|
+
static void *sample_collector_thread(void *arg);
|
22
|
+
static void sigprof_handler(int sig, siginfo_t *info, void *ucontext);
|
23
|
+
bool ensure_sample_capacity(struct pf2_session *session);
|
24
|
+
|
25
|
+
VALUE
|
26
|
+
rb_pf2_session_initialize(int argc, VALUE *argv, VALUE self)
|
27
|
+
{
|
28
|
+
struct pf2_session *session;
|
29
|
+
TypedData_Get_Struct(self, struct pf2_session, &pf2_session_type, session);
|
30
|
+
|
31
|
+
// Create configuration from options hash
|
32
|
+
VALUE kwargs = Qnil;
|
33
|
+
rb_scan_args(argc, argv, ":", &kwargs);
|
34
|
+
ID kwarg_labels[] = {
|
35
|
+
rb_intern("interval_ms"),
|
36
|
+
rb_intern("time_mode")
|
37
|
+
};
|
38
|
+
VALUE *kwarg_values = NULL;
|
39
|
+
rb_get_kwargs(kwargs, kwarg_labels, 0, 2, kwarg_values);
|
40
|
+
|
41
|
+
session->configuration = pf2_configuration_new_from_options_hash(kwargs);
|
42
|
+
|
43
|
+
return self;
|
44
|
+
}
|
45
|
+
|
46
|
+
VALUE
|
47
|
+
rb_pf2_session_start(VALUE self)
|
48
|
+
{
|
49
|
+
struct pf2_session *session;
|
50
|
+
TypedData_Get_Struct(self, struct pf2_session, &pf2_session_type, session);
|
51
|
+
|
52
|
+
session->is_running = true;
|
53
|
+
|
54
|
+
// Record start time
|
55
|
+
clock_gettime(CLOCK_REALTIME, &session->start_time_realtime);
|
56
|
+
clock_gettime(CLOCK_MONOTONIC, &session->start_time);
|
57
|
+
|
58
|
+
// Spawn a collector thread which periodically wakes up and collects samples
|
59
|
+
if (pthread_create(session->collector_thread, NULL, sample_collector_thread, session) != 0) {
|
60
|
+
rb_raise(rb_eRuntimeError, "Failed to spawn sample collector thread");
|
61
|
+
}
|
62
|
+
|
63
|
+
// Configure signal handler
|
64
|
+
struct sigaction sa;
|
65
|
+
sa.sa_sigaction = sigprof_handler;
|
66
|
+
sigemptyset(&sa.sa_mask);
|
67
|
+
sigaddset(&sa.sa_mask, SIGPROF); // Mask SIGPROFs when handler is running
|
68
|
+
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
69
|
+
if (sigaction(SIGPROF, &sa, NULL) == -1) {
|
70
|
+
rb_raise(rb_eRuntimeError, "Failed to install signal handler");
|
71
|
+
}
|
72
|
+
|
73
|
+
// Configure a timer to send SIGPROF every 10 ms of CPU time
|
74
|
+
struct sigevent sev;
|
75
|
+
sev.sigev_notify = SIGEV_SIGNAL;
|
76
|
+
sev.sigev_signo = SIGPROF;
|
77
|
+
sev.sigev_value.sival_ptr = session; // Passed as info->si_value.sival_ptr
|
78
|
+
if (timer_create(
|
79
|
+
session->configuration->time_mode == PF2_TIME_MODE_CPU_TIME
|
80
|
+
? CLOCK_PROCESS_CPUTIME_ID
|
81
|
+
: CLOCK_MONOTONIC,
|
82
|
+
&sev,
|
83
|
+
&session->timer
|
84
|
+
) == -1) {
|
85
|
+
rb_raise(rb_eRuntimeError, "Failed to create timer");
|
86
|
+
}
|
87
|
+
struct itimerspec its = {
|
88
|
+
.it_value = {
|
89
|
+
.tv_sec = 0,
|
90
|
+
.tv_nsec = session->configuration->interval_ms * 1000000,
|
91
|
+
},
|
92
|
+
.it_interval = {
|
93
|
+
.tv_sec = 0,
|
94
|
+
.tv_nsec = session->configuration->interval_ms * 1000000,
|
95
|
+
},
|
96
|
+
};
|
97
|
+
if (timer_settime(session->timer, 0, &its, NULL) == -1) {
|
98
|
+
rb_raise(rb_eRuntimeError, "Failed to start timer");
|
99
|
+
}
|
100
|
+
|
101
|
+
return Qtrue;
|
102
|
+
}
|
103
|
+
|
104
|
+
static void *
|
105
|
+
sample_collector_thread(void *arg)
|
106
|
+
{
|
107
|
+
struct pf2_session *session = arg;
|
108
|
+
|
109
|
+
while (session->is_running == true) {
|
110
|
+
// Take samples from the ring buffer
|
111
|
+
struct pf2_sample sample;
|
112
|
+
while (pf2_ringbuffer_pop(session->rbuf, &sample) == true) {
|
113
|
+
// Ensure we have capacity before adding a new sample
|
114
|
+
if (!ensure_sample_capacity(session)) {
|
115
|
+
// Failed to expand buffer
|
116
|
+
#ifdef PF2_DEBUG
|
117
|
+
printf("Failed to expand sample buffer. Dropping sample\n");
|
118
|
+
#endif
|
119
|
+
break;
|
120
|
+
}
|
121
|
+
|
122
|
+
session->samples[session->samples_index++] = sample;
|
123
|
+
}
|
124
|
+
|
125
|
+
// Sleep for 100 ms
|
126
|
+
// TODO: Replace with high watermark callback
|
127
|
+
struct timespec ts = { .tv_sec = 0, .tv_nsec = 10 * 1000000, }; // 10 ms
|
128
|
+
nanosleep(&ts, NULL);
|
129
|
+
}
|
130
|
+
|
131
|
+
return NULL;
|
132
|
+
}
|
133
|
+
|
134
|
+
// async-signal-safe
|
135
|
+
static void
|
136
|
+
sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
137
|
+
{
|
138
|
+
#ifdef PF2_DEBUG
|
139
|
+
struct timespec sig_start_time;
|
140
|
+
clock_gettime(CLOCK_MONOTONIC, &sig_start_time);
|
141
|
+
#endif
|
142
|
+
|
143
|
+
struct pf2_session *session = info->si_value.sival_ptr;
|
144
|
+
|
145
|
+
// If garbage collection is in progress, don't collect samples.
|
146
|
+
if (atomic_load_explicit(&session->is_marking, memory_order_acquire)) {
|
147
|
+
#ifdef PF2_DEBUG
|
148
|
+
printf("Dropping sample: Garbage collection is in progress\n");
|
149
|
+
#endif
|
150
|
+
return;
|
151
|
+
}
|
152
|
+
|
153
|
+
struct pf2_sample sample = { 0 };
|
154
|
+
|
155
|
+
if (pf2_sample_capture(&sample) == false) {
|
156
|
+
#ifdef PF2_DEBUG
|
157
|
+
printf("Dropping sample: Failed to capture sample\n");
|
158
|
+
#endif
|
159
|
+
return;
|
160
|
+
}
|
161
|
+
|
162
|
+
// Copy the sample to the ringbuffer
|
163
|
+
if (pf2_ringbuffer_push(session->rbuf, &sample) == false) {
|
164
|
+
// Copy failed. The sample buffer is full.
|
165
|
+
#ifdef PF2_DEBUG
|
166
|
+
printf("Dropping sample: Sample buffer is full\n");
|
167
|
+
#endif
|
168
|
+
return;
|
169
|
+
}
|
170
|
+
|
171
|
+
#ifdef PF2_DEBUG
|
172
|
+
struct timespec sig_end_time;
|
173
|
+
clock_gettime(CLOCK_MONOTONIC, &sig_end_time);
|
174
|
+
|
175
|
+
// Calculate elapsed time in nanoseconds
|
176
|
+
sample.consumed_time_ns =
|
177
|
+
(sig_end_time.tv_sec - sig_start_time.tv_sec) * 1000000000L +
|
178
|
+
(sig_end_time.tv_nsec - sig_start_time.tv_nsec);
|
179
|
+
|
180
|
+
printf("sigprof_handler: consumed_time_ns: %lu\n", sample.consumed_time_ns);
|
181
|
+
#endif
|
182
|
+
}
|
183
|
+
|
184
|
+
// Ensures that the session's sample array has capacity for at least one more sample
|
185
|
+
// Returns true if successful, false if memory allocation failed
|
186
|
+
bool
|
187
|
+
ensure_sample_capacity(struct pf2_session *session)
|
188
|
+
{
|
189
|
+
// Check if we need to expand
|
190
|
+
if (session->samples_index < session->samples_capacity) {
|
191
|
+
return true;
|
192
|
+
}
|
193
|
+
|
194
|
+
// Calculate new size (double the current size)
|
195
|
+
size_t new_capacity = session->samples_capacity * 2;
|
196
|
+
|
197
|
+
// Reallocate the array
|
198
|
+
struct pf2_sample *new_samples = realloc(session->samples, new_capacity * sizeof(struct pf2_sample));
|
199
|
+
if (new_samples == NULL) {
|
200
|
+
return false;
|
201
|
+
}
|
202
|
+
|
203
|
+
session->samples = new_samples;
|
204
|
+
session->samples_capacity = new_capacity;
|
205
|
+
|
206
|
+
return true;
|
207
|
+
}
|
208
|
+
|
209
|
+
VALUE
|
210
|
+
rb_pf2_session_stop(VALUE self)
|
211
|
+
{
|
212
|
+
struct pf2_session *session;
|
213
|
+
TypedData_Get_Struct(self, struct pf2_session, &pf2_session_type, session);
|
214
|
+
|
215
|
+
// Calculate duration
|
216
|
+
struct timespec end_time;
|
217
|
+
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
218
|
+
uint64_t start_ns = (uint64_t)session->start_time.tv_sec * 1000000000ULL + (uint64_t)session->start_time.tv_nsec;
|
219
|
+
uint64_t end_ns = (uint64_t)end_time.tv_sec * 1000000000ULL + (uint64_t)end_time.tv_nsec;
|
220
|
+
session->duration_ns = end_ns - start_ns;
|
221
|
+
|
222
|
+
// Disarm and delete the timer.
|
223
|
+
if (timer_delete(session->timer) == -1) {
|
224
|
+
rb_raise(rb_eRuntimeError, "Failed to delete timer");
|
225
|
+
}
|
226
|
+
|
227
|
+
// Terminate the collector thread
|
228
|
+
session->is_running = false;
|
229
|
+
pthread_join(*session->collector_thread, NULL);
|
230
|
+
|
231
|
+
// Create serializer and serialize
|
232
|
+
struct pf2_ser *serializer = pf2_ser_new();
|
233
|
+
pf2_ser_prepare(serializer, session);
|
234
|
+
VALUE result = pf2_ser_to_ruby_hash(serializer);
|
235
|
+
pf2_ser_free(serializer);
|
236
|
+
|
237
|
+
return result;
|
238
|
+
}
|
239
|
+
|
240
|
+
VALUE
|
241
|
+
rb_pf2_session_configuration(VALUE self)
|
242
|
+
{
|
243
|
+
struct pf2_session *session;
|
244
|
+
TypedData_Get_Struct(self, struct pf2_session, &pf2_session_type, session);
|
245
|
+
return pf2_configuration_to_ruby_hash(session->configuration);
|
246
|
+
}
|
247
|
+
|
248
|
+
VALUE
|
249
|
+
pf2_session_alloc(VALUE self)
|
250
|
+
{
|
251
|
+
// Initialize state for libbacktrace
|
252
|
+
if (global_backtrace_state == NULL) {
|
253
|
+
global_backtrace_state = backtrace_create_state("pf2", 1, pf2_backtrace_print_error, NULL);
|
254
|
+
if (global_backtrace_state == NULL) {
|
255
|
+
rb_raise(rb_eRuntimeError, "Failed to initialize libbacktrace");
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
struct pf2_session *session = malloc(sizeof(struct pf2_session));
|
260
|
+
if (session == NULL) {
|
261
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
262
|
+
}
|
263
|
+
|
264
|
+
session->rbuf = pf2_ringbuffer_new(1000);
|
265
|
+
if (session->rbuf == NULL) {
|
266
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
267
|
+
}
|
268
|
+
|
269
|
+
atomic_store_explicit(&session->is_marking, false, memory_order_relaxed);
|
270
|
+
session->collector_thread = malloc(sizeof(pthread_t));
|
271
|
+
if (session->collector_thread == NULL) {
|
272
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
273
|
+
}
|
274
|
+
|
275
|
+
session->duration_ns = 0;
|
276
|
+
|
277
|
+
session->samples_index = 0;
|
278
|
+
session->samples_capacity = 500; // 10 seconds worth of samples at 50 Hz
|
279
|
+
session->samples = malloc(sizeof(struct pf2_sample) * session->samples_capacity);
|
280
|
+
if (session->samples == NULL) {
|
281
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
282
|
+
}
|
283
|
+
|
284
|
+
session->configuration = NULL;
|
285
|
+
|
286
|
+
return TypedData_Wrap_Struct(self, &pf2_session_type, session);
|
287
|
+
}
|
288
|
+
|
289
|
+
void
|
290
|
+
pf2_session_dmark(void *sess)
|
291
|
+
{
|
292
|
+
struct pf2_session *session = sess;
|
293
|
+
|
294
|
+
// Disallow sample collection during marking
|
295
|
+
atomic_store_explicit(&session->is_marking, true, memory_order_release);
|
296
|
+
|
297
|
+
// Iterate over all samples in the ringbuffer and mark them
|
298
|
+
struct pf2_ringbuffer *rbuf = session->rbuf;
|
299
|
+
struct pf2_sample *sample;
|
300
|
+
int head = atomic_load_explicit(&rbuf->head, memory_order_acquire);
|
301
|
+
int tail = atomic_load_explicit(&rbuf->tail, memory_order_acquire);
|
302
|
+
while (head != tail) {
|
303
|
+
sample = &rbuf->samples[head];
|
304
|
+
// TODO: Move this to mark function in pf2_sample
|
305
|
+
for (int i = 0; i < sample->depth; i++) {
|
306
|
+
rb_gc_mark(sample->cmes[i]);
|
307
|
+
}
|
308
|
+
head = (head + 1) % rbuf->size;
|
309
|
+
}
|
310
|
+
|
311
|
+
// Iterate over all samples in the samples array and mark them
|
312
|
+
for (size_t i = 0; i < session->samples_index; i++) {
|
313
|
+
sample = &session->samples[i];
|
314
|
+
for (int i = 0; i < sample->depth; i++) {
|
315
|
+
rb_gc_mark(sample->cmes[i]);
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
// Allow sample collection
|
320
|
+
atomic_store_explicit(&session->is_marking, false, memory_order_release);
|
321
|
+
}
|
322
|
+
|
323
|
+
void
|
324
|
+
pf2_session_dfree(void *sess)
|
325
|
+
{
|
326
|
+
// TODO: Ensure the uninstall process is complete before freeing the session
|
327
|
+
struct pf2_session *session = sess;
|
328
|
+
pf2_configuration_free(session->configuration);
|
329
|
+
pf2_ringbuffer_free(session->rbuf);
|
330
|
+
free(session->samples);
|
331
|
+
free(session->collector_thread);
|
332
|
+
free(session);
|
333
|
+
}
|
334
|
+
|
335
|
+
size_t
|
336
|
+
pf2_session_dsize(const void *sess)
|
337
|
+
{
|
338
|
+
const struct pf2_session *session = sess;
|
339
|
+
return (
|
340
|
+
sizeof(struct pf2_session)
|
341
|
+
+ sizeof(struct pf2_sample) * session->samples_capacity
|
342
|
+
+ sizeof(struct pf2_sample) * session->rbuf->size
|
343
|
+
);
|
344
|
+
}
|
data/ext/pf2c/session.h
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#ifndef PF2_SESSION_H
|
2
|
+
#define PF2_SESSION_H
|
3
|
+
|
4
|
+
#include <pthread.h>
|
5
|
+
#include <stdatomic.h>
|
6
|
+
|
7
|
+
#include <ruby.h>
|
8
|
+
|
9
|
+
#include "configuration.h"
|
10
|
+
#include "ringbuffer.h"
|
11
|
+
#include "sample.h"
|
12
|
+
|
13
|
+
struct pf2_session {
|
14
|
+
bool is_running;
|
15
|
+
timer_t timer;
|
16
|
+
struct pf2_ringbuffer *rbuf;
|
17
|
+
atomic_bool is_marking; // Whether garbage collection is in progress
|
18
|
+
pthread_t *collector_thread;
|
19
|
+
|
20
|
+
struct pf2_sample *samples; // Dynamic array of samples
|
21
|
+
size_t samples_index;
|
22
|
+
size_t samples_capacity; // Current capacity of the samples array
|
23
|
+
|
24
|
+
struct timespec start_time_realtime;
|
25
|
+
struct timespec start_time; // When profiling started
|
26
|
+
uint64_t duration_ns; // Duration of profiling in nanoseconds
|
27
|
+
|
28
|
+
struct pf2_configuration *configuration;
|
29
|
+
};
|
30
|
+
|
31
|
+
VALUE rb_pf2_session_initialize(int argc, VALUE *argv, VALUE self);
|
32
|
+
VALUE rb_pf2_session_start(VALUE self);
|
33
|
+
VALUE rb_pf2_session_stop(VALUE self);
|
34
|
+
VALUE rb_pf2_session_configuration(VALUE self);
|
35
|
+
VALUE pf2_session_alloc(VALUE self);
|
36
|
+
void pf2_session_dmark(void *sess);
|
37
|
+
void pf2_session_dfree(void *sess);
|
38
|
+
size_t pf2_session_dsize(const void *sess);
|
39
|
+
|
40
|
+
static const rb_data_type_t pf2_session_type = {
|
41
|
+
.wrap_struct_name = "Pf2c::Session",
|
42
|
+
.function = {
|
43
|
+
.dmark = pf2_session_dmark,
|
44
|
+
.dfree = pf2_session_dfree,
|
45
|
+
.dsize = pf2_session_dsize,
|
46
|
+
},
|
47
|
+
.data = NULL,
|
48
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
49
|
+
};
|
50
|
+
|
51
|
+
#endif // PF2_SESSION_H
|
data/lib/pf2/cli.rb
CHANGED
@@ -19,6 +19,8 @@ module Pf2
|
|
19
19
|
case subcommand
|
20
20
|
when 'report'
|
21
21
|
subcommand_report(argv)
|
22
|
+
when 'annotate'
|
23
|
+
subcommand_annotate(argv)
|
22
24
|
when 'serve'
|
23
25
|
subcommand_serve(argv)
|
24
26
|
when 'version'
|
@@ -53,11 +55,20 @@ module Pf2
|
|
53
55
|
opts.on('-o', '--output FILE', 'Output file') do |path|
|
54
56
|
options[:output_file] = path
|
55
57
|
end
|
58
|
+
opts.on('--experimental-serializer', 'Enable the experimental serializer mode') do
|
59
|
+
options[:experimental_serializer] = true
|
60
|
+
end
|
56
61
|
end
|
57
62
|
option_parser.parse!(argv)
|
58
63
|
|
59
|
-
|
60
|
-
|
64
|
+
if options[:experimental_serializer]
|
65
|
+
profile = Marshal.load(File.read(argv[0]))
|
66
|
+
report = Pf2::Reporter::FirefoxProfilerSer2.new(profile).emit
|
67
|
+
report = JSON.generate(report)
|
68
|
+
else
|
69
|
+
profile = JSON.parse(File.read(argv[0]), symbolize_names: true, max_nesting: false)
|
70
|
+
report = JSON.generate(Pf2::Reporter::FirefoxProfiler.new(profile).emit)
|
71
|
+
end
|
61
72
|
|
62
73
|
if options[:output_file]
|
63
74
|
File.write(options[:output_file], report)
|
@@ -68,6 +79,26 @@ module Pf2
|
|
68
79
|
return 0
|
69
80
|
end
|
70
81
|
|
82
|
+
def subcommand_annotate(argv)
|
83
|
+
options = {}
|
84
|
+
option_parser = OptionParser.new do |opts|
|
85
|
+
opts.banner = "Usage: pf2 report [options] COMMAND"
|
86
|
+
opts.on('-h', '--help', 'Prints this help') do
|
87
|
+
puts opts
|
88
|
+
return 0
|
89
|
+
end
|
90
|
+
opts.on('-d', '--source-directory DIR', 'Path to the source directory') do |dir|
|
91
|
+
options[:source_directory] = dir
|
92
|
+
end
|
93
|
+
end
|
94
|
+
option_parser.parse!(argv)
|
95
|
+
|
96
|
+
profile = Marshal.load(File.binread(argv[0]))
|
97
|
+
Pf2::Reporter::Annotate.new(profile, options[:source_directory] || '.').annotate
|
98
|
+
|
99
|
+
return 0
|
100
|
+
end
|
101
|
+
|
71
102
|
def subcommand_serve(argv)
|
72
103
|
options = {}
|
73
104
|
option_parser = OptionParser.new do |opts|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pf2
|
4
|
+
module Reporter
|
5
|
+
class Annotate
|
6
|
+
HitCount = Struct.new(:self, :total, keyword_init: false)
|
7
|
+
SourceCodeHits = Struct.new(:path, :line_count, keyword_init: false)
|
8
|
+
|
9
|
+
# @param profile [Hash]
|
10
|
+
# @param source_directory [String]
|
11
|
+
def initialize(profile, source_directory)
|
12
|
+
@profile = profile
|
13
|
+
@source_directory = source_directory
|
14
|
+
end
|
15
|
+
|
16
|
+
def annotate
|
17
|
+
tallied = tally_by_source_code_line(@profile)
|
18
|
+
|
19
|
+
# Print the source code with hit counts
|
20
|
+
tallied.each do |path, source_code_hits|
|
21
|
+
expanded_path = File.expand_path(path, @source_directory)
|
22
|
+
if !File.exist?(expanded_path)
|
23
|
+
if ignorable_path?(path)
|
24
|
+
puts "Ignoring file: #{path}"
|
25
|
+
else
|
26
|
+
puts "File not found: #{path}"
|
27
|
+
end
|
28
|
+
puts ""
|
29
|
+
puts ""
|
30
|
+
next
|
31
|
+
end
|
32
|
+
source_file = File.open(expanded_path, "r")
|
33
|
+
|
34
|
+
puts expanded_path
|
35
|
+
puts ""
|
36
|
+
|
37
|
+
# Print in tabular format
|
38
|
+
|
39
|
+
# Header row
|
40
|
+
puts " ttl self │"
|
41
|
+
|
42
|
+
source_file.each_line.with_index(1) do |line, lineno|
|
43
|
+
hits = source_code_hits.line_count[lineno]
|
44
|
+
|
45
|
+
if !hits.nil?
|
46
|
+
# If any samples are captured for this line
|
47
|
+
puts "%5d %5d │ %s" % [hits.total, hits.self, line.chomp]
|
48
|
+
else
|
49
|
+
puts "%5s %5s │ %s" % ["", "", line.chomp]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
puts ""
|
54
|
+
puts ""
|
55
|
+
ensure
|
56
|
+
source_file.close if source_file
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Array<SourceCodeHits>]
|
61
|
+
private def tally_by_source_code_line(profile)
|
62
|
+
# Iterate over all samples and tally self hits and total hits by location
|
63
|
+
hits_per_location = {}
|
64
|
+
@profile[:samples].each do |sample|
|
65
|
+
# Record a total hit for all locations in the stack
|
66
|
+
sample[:stack].each do |location_id|
|
67
|
+
hits_per_location[location_id] ||= HitCount.new(0, 0)
|
68
|
+
hits_per_location[location_id].total += 1
|
69
|
+
end
|
70
|
+
|
71
|
+
# Record a self hit for the topmost stack frame, which is the first element in the array
|
72
|
+
topmost_location_id = sample[:stack][0]
|
73
|
+
hits_per_location[topmost_location_id].self += 1
|
74
|
+
end
|
75
|
+
|
76
|
+
# Associate a filename and lineno for each location
|
77
|
+
hits_per_file = {}
|
78
|
+
hits_per_location.each do |location_id, hits|
|
79
|
+
location = @profile[:locations][location_id]
|
80
|
+
function = @profile[:functions][location[:function_index]]
|
81
|
+
|
82
|
+
filename = function[:filename]
|
83
|
+
# Some locations simply cannot be associated to a specific file.
|
84
|
+
# We just ignore them.
|
85
|
+
next if filename.nil?
|
86
|
+
lineno = location[:lineno]
|
87
|
+
|
88
|
+
hits_per_file[filename] ||= SourceCodeHits.new(filename, {})
|
89
|
+
hits_per_file[filename].line_count[lineno] = hits
|
90
|
+
end
|
91
|
+
|
92
|
+
hits_per_file
|
93
|
+
end
|
94
|
+
|
95
|
+
private def ignorable_path?(path)
|
96
|
+
return true if path.start_with?("<internal:")
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|