pf2 0.9.0 → 1.0.0.alpha1
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/README.md +9 -4
- data/Rakefile +3 -9
- data/doc/development.md +6 -0
- data/ext/pf2/debug.h +12 -0
- data/ext/pf2/extconf.rb +23 -6
- data/ext/{pf2c → pf2}/sample.c +6 -0
- data/ext/{pf2c → pf2}/sample.h +4 -0
- data/ext/{pf2c → pf2}/serializer.c +1 -1
- data/ext/{pf2c → pf2}/session.c +70 -20
- data/ext/{pf2c → pf2}/session.h +5 -0
- data/lib/pf2/cli.rb +3 -11
- data/lib/pf2/reporter/firefox_profiler_ser2.rb +17 -13
- data/lib/pf2/reporter/stack_weaver.rb +8 -0
- data/lib/pf2/reporter.rb +0 -1
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +1 -1
- metadata +18 -135
- data/Cargo.lock +0 -630
- data/Cargo.toml +0 -3
- data/crates/backtrace-sys2/.gitignore +0 -1
- data/crates/backtrace-sys2/Cargo.toml +0 -9
- data/crates/backtrace-sys2/build.rs +0 -45
- data/crates/backtrace-sys2/src/lib.rs +0 -5
- data/crates/backtrace-sys2/src/libbacktrace/.gitignore +0 -15
- data/crates/backtrace-sys2/src/libbacktrace/Isaac.Newton-Opticks.txt +0 -9286
- data/crates/backtrace-sys2/src/libbacktrace/LICENSE +0 -29
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +0 -708
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +0 -2820
- data/crates/backtrace-sys2/src/libbacktrace/README.md +0 -46
- data/crates/backtrace-sys2/src/libbacktrace/aclocal.m4 +0 -864
- data/crates/backtrace-sys2/src/libbacktrace/alloc.c +0 -167
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +0 -136
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +0 -104
- data/crates/backtrace-sys2/src/libbacktrace/atomic.c +0 -113
- data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +0 -66
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +0 -129
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +0 -189
- data/crates/backtrace-sys2/src/libbacktrace/btest.c +0 -517
- data/crates/backtrace-sys2/src/libbacktrace/compile +0 -348
- data/crates/backtrace-sys2/src/libbacktrace/config/enable.m4 +0 -38
- data/crates/backtrace-sys2/src/libbacktrace/config/lead-dot.m4 +0 -31
- data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +0 -7545
- data/crates/backtrace-sys2/src/libbacktrace/config/ltoptions.m4 +0 -369
- data/crates/backtrace-sys2/src/libbacktrace/config/ltsugar.m4 +0 -123
- data/crates/backtrace-sys2/src/libbacktrace/config/ltversion.m4 +0 -23
- data/crates/backtrace-sys2/src/libbacktrace/config/lt~obsolete.m4 +0 -98
- data/crates/backtrace-sys2/src/libbacktrace/config/multi.m4 +0 -68
- data/crates/backtrace-sys2/src/libbacktrace/config/override.m4 +0 -117
- data/crates/backtrace-sys2/src/libbacktrace/config/unwind_ipinfo.m4 +0 -37
- data/crates/backtrace-sys2/src/libbacktrace/config/warnings.m4 +0 -227
- data/crates/backtrace-sys2/src/libbacktrace/config.guess +0 -1700
- data/crates/backtrace-sys2/src/libbacktrace/config.h.in +0 -185
- data/crates/backtrace-sys2/src/libbacktrace/config.sub +0 -1885
- data/crates/backtrace-sys2/src/libbacktrace/configure +0 -15929
- data/crates/backtrace-sys2/src/libbacktrace/configure.ac +0 -632
- data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +0 -4409
- data/crates/backtrace-sys2/src/libbacktrace/edtest.c +0 -120
- data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +0 -43
- data/crates/backtrace-sys2/src/libbacktrace/elf.c +0 -7465
- data/crates/backtrace-sys2/src/libbacktrace/fileline.c +0 -407
- data/crates/backtrace-sys2/src/libbacktrace/filenames.h +0 -52
- data/crates/backtrace-sys2/src/libbacktrace/filetype.awk +0 -13
- data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +0 -65
- data/crates/backtrace-sys2/src/libbacktrace/install-sh +0 -501
- data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +0 -114
- data/crates/backtrace-sys2/src/libbacktrace/internal.h +0 -428
- data/crates/backtrace-sys2/src/libbacktrace/ltmain.sh +0 -8636
- data/crates/backtrace-sys2/src/libbacktrace/macho.c +0 -1361
- data/crates/backtrace-sys2/src/libbacktrace/missing +0 -215
- data/crates/backtrace-sys2/src/libbacktrace/mmap.c +0 -331
- data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +0 -110
- data/crates/backtrace-sys2/src/libbacktrace/move-if-change +0 -83
- data/crates/backtrace-sys2/src/libbacktrace/mtest.c +0 -410
- data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +0 -66
- data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +0 -1123
- data/crates/backtrace-sys2/src/libbacktrace/posix.c +0 -104
- data/crates/backtrace-sys2/src/libbacktrace/print.c +0 -117
- data/crates/backtrace-sys2/src/libbacktrace/read.c +0 -110
- data/crates/backtrace-sys2/src/libbacktrace/simple.c +0 -108
- data/crates/backtrace-sys2/src/libbacktrace/sort.c +0 -108
- data/crates/backtrace-sys2/src/libbacktrace/state.c +0 -72
- data/crates/backtrace-sys2/src/libbacktrace/stest.c +0 -137
- data/crates/backtrace-sys2/src/libbacktrace/test-driver +0 -148
- data/crates/backtrace-sys2/src/libbacktrace/test_format.c +0 -55
- data/crates/backtrace-sys2/src/libbacktrace/testlib.c +0 -234
- data/crates/backtrace-sys2/src/libbacktrace/testlib.h +0 -110
- data/crates/backtrace-sys2/src/libbacktrace/ttest.c +0 -161
- data/crates/backtrace-sys2/src/libbacktrace/unittest.c +0 -92
- data/crates/backtrace-sys2/src/libbacktrace/unknown.c +0 -65
- data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +0 -1617
- data/crates/backtrace-sys2/src/libbacktrace/xztest.c +0 -508
- data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +0 -523
- data/crates/backtrace-sys2/src/libbacktrace/ztest.c +0 -541
- data/ext/pf2/Cargo.toml +0 -25
- data/ext/pf2/build.rs +0 -10
- data/ext/pf2/src/backtrace.rs +0 -127
- data/ext/pf2/src/lib.rs +0 -22
- data/ext/pf2/src/profile.rs +0 -69
- data/ext/pf2/src/profile_serializer.rs +0 -241
- data/ext/pf2/src/ringbuffer.rs +0 -150
- data/ext/pf2/src/ruby_c_api_helper.c +0 -6
- data/ext/pf2/src/ruby_init.rs +0 -40
- data/ext/pf2/src/ruby_internal_apis.rs +0 -77
- data/ext/pf2/src/sample.rs +0 -67
- data/ext/pf2/src/scheduler.rs +0 -10
- data/ext/pf2/src/serialization/profile.rs +0 -48
- data/ext/pf2/src/serialization/serializer.rs +0 -329
- data/ext/pf2/src/serialization.rs +0 -2
- data/ext/pf2/src/session/configuration.rs +0 -114
- data/ext/pf2/src/session/new_thread_watcher.rs +0 -80
- data/ext/pf2/src/session/ruby_object.rs +0 -90
- data/ext/pf2/src/session.rs +0 -248
- data/ext/pf2/src/siginfo_t.c +0 -5
- data/ext/pf2/src/signal_scheduler.rs +0 -201
- data/ext/pf2/src/signal_scheduler_unsupported_platform.rs +0 -39
- data/ext/pf2/src/timer_thread_scheduler.rs +0 -179
- data/ext/pf2/src/util.rs +0 -31
- data/ext/pf2c/extconf.rb +0 -21
- data/lib/pf2/reporter/firefox_profiler.rb +0 -397
- data/rust-toolchain.toml +0 -2
- data/rustfmt.toml +0 -1
- /data/ext/{pf2c → pf2}/backtrace_state.c +0 -0
- /data/ext/{pf2c → pf2}/backtrace_state.h +0 -0
- /data/ext/{pf2c → pf2}/configuration.c +0 -0
- /data/ext/{pf2c → pf2}/configuration.h +0 -0
- /data/ext/{pf2c → pf2}/pf2.c +0 -0
- /data/ext/{pf2c → pf2}/pf2.h +0 -0
- /data/ext/{pf2c → pf2}/ringbuffer.c +0 -0
- /data/ext/{pf2c → pf2}/ringbuffer.h +0 -0
- /data/ext/{pf2c → pf2}/serializer.h +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f219b8aa5b5281a6ed662a085e00f82636ab52365d2b0f7a8d61a3e06bfc9c6
|
4
|
+
data.tar.gz: 162e1eae488afe17e33291f63f807642aa304d7df8069cb165e3a25ce6f10895
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37b6a1aa4f6ab0753d86983d76cbdfae4b3dfbb7464afb0388aa113d728725f09eabcabb2ae7c2ffadc5ab797be75675b5b27b465c20565d5d69f30291d4837f
|
7
|
+
data.tar.gz: 7b69aea55c8873cfd6e28fba3bdddd09325e4c73e1e3e44a5be48fcb9ad776b03e811c707fc049ad8ccc1590e7c011032be7f4053e3d15a5ba5b50c4344db656
|
data/README.md
CHANGED
@@ -116,7 +116,7 @@ Schedulers determine when to execute sample collection, based on configuration (
|
|
116
116
|
|
117
117
|
The first is the `SignalScheduler`, based on POSIX timers. Pf2 will use this scheduler when possible. SignalScheduler creates a POSIX timer for each Ruby Thread (the underlying pthread to be more accurate) using `timer_create(2)`. This leaves the actual time-keeping to the OS, which is capable of tracking accurate per-thread CPU time usage.
|
118
118
|
|
119
|
-
When the specified interval has arrived (the timer has _expired_), the OS delivers us a
|
119
|
+
When the specified interval has arrived (the timer has _expired_), the OS delivers us a SIGPROF signal. This is why the scheduler is named SignalScheduler.
|
120
120
|
|
121
121
|
Signals are directed to Ruby Threads' underlying pthread, effectively "pausing" the Thread's activity. This routing is done using `SIGEV_THREAD_ID`, which is a Linux-only feature. Sample collection is done in the signal handler, which is expected to be more _accurate_, capturing the paused Thread's activity.
|
122
122
|
|
@@ -128,11 +128,16 @@ Another scheduler is the `TimerThreadScheduler`, which maintains a time-keeping
|
|
128
128
|
|
129
129
|
This scheduler is wall-time only, and does not support CPU-time based profiling.
|
130
130
|
|
131
|
-
|
131
|
+
#### macOS Support
|
132
|
+
|
133
|
+
On platforms where `timer_create()` is not supported (namely macOS), Pf2 falls back to `setitimer()`.
|
134
|
+
|
135
|
+
|
136
|
+
Wishlist
|
132
137
|
--------
|
133
138
|
|
134
|
-
-
|
135
|
-
-
|
139
|
+
- [Flame Scopes](https://www.brendangregg.com/flamescope.html)
|
140
|
+
- More unit/e2e tests
|
136
141
|
- more
|
137
142
|
|
138
143
|
Development
|
data/Rakefile
CHANGED
@@ -4,15 +4,9 @@ require 'minitest/test_task'
|
|
4
4
|
|
5
5
|
task default: %i[]
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
ext.lib_dir = 'lib/pf2'
|
11
|
-
end
|
12
|
-
else
|
13
|
-
Rake::ExtensionTask.new 'pf2' do |ext|
|
14
|
-
ext.lib_dir = 'lib/pf2'
|
15
|
-
end
|
7
|
+
Rake::ExtensionTask.new 'pf2' do |ext|
|
8
|
+
ext.name = 'pf2'
|
9
|
+
ext.lib_dir = 'lib/pf2'
|
16
10
|
end
|
17
11
|
|
18
12
|
Minitest::TestTask.create(:test) do |t|
|
data/doc/development.md
CHANGED
data/ext/pf2/debug.h
ADDED
data/ext/pf2/extconf.rb
CHANGED
@@ -1,10 +1,27 @@
|
|
1
1
|
require 'mkmf'
|
2
|
-
require '
|
2
|
+
require 'mini_portile2'
|
3
3
|
|
4
|
-
|
4
|
+
libbacktrace = MiniPortile.new('libbacktrace', '1.0.0')
|
5
|
+
libbacktrace.source_directory = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'vendor', 'libbacktrace'))
|
6
|
+
libbacktrace.configure_options << 'CFLAGS=-fPIC'
|
7
|
+
libbacktrace.cook
|
8
|
+
libbacktrace.mkmf_config
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
+
if !have_func('backtrace_full', 'backtrace.h')
|
11
|
+
raise 'libbacktrace has not been properly configured'
|
12
|
+
end
|
13
|
+
|
14
|
+
append_ldflags('-lrt') # for timer_create
|
15
|
+
append_cflags('-fvisibility=hidden')
|
16
|
+
append_cflags('-DPF2_DEBUG') if ENV['PF2_DEBUG'] == '1'
|
17
|
+
|
18
|
+
# Check for timer functions
|
19
|
+
have_timer_create = have_func('timer_create')
|
20
|
+
have_setitimer = have_func('setitimer')
|
21
|
+
|
22
|
+
if have_timer_create || have_setitimer
|
23
|
+
$srcs = Dir.glob("#{File.join(File.dirname(__FILE__), '*.c')}")
|
24
|
+
create_makefile 'pf2/pf2'
|
25
|
+
else
|
26
|
+
raise 'Neither timer_create nor setitimer is available'
|
10
27
|
end
|
data/ext/{pf2c → pf2}/sample.c
RENAMED
@@ -1,5 +1,6 @@
|
|
1
1
|
#include <stdbool.h>
|
2
2
|
#include <time.h>
|
3
|
+
#include <pthread.h>
|
3
4
|
|
4
5
|
#include <backtrace.h>
|
5
6
|
#include <ruby.h>
|
@@ -17,11 +18,16 @@ static int backtrace_on_ok(void *data, uintptr_t pc);
|
|
17
18
|
bool
|
18
19
|
pf2_sample_capture(struct pf2_sample *sample)
|
19
20
|
{
|
21
|
+
// Initialize sample
|
22
|
+
memset(sample, 0, sizeof(struct pf2_sample));
|
23
|
+
|
20
24
|
// Record the current time
|
21
25
|
struct timespec now;
|
22
26
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
23
27
|
sample->timestamp_ns = (uint64_t)now.tv_sec * 1000000000ULL + (uint64_t)now.tv_nsec;
|
24
28
|
|
29
|
+
sample->context_pthread = pthread_self();
|
30
|
+
|
25
31
|
// Obtain the current stack from Ruby
|
26
32
|
sample->depth = rb_profile_frames(0, 200, sample->cmes, sample->linenos);
|
27
33
|
|
data/ext/{pf2c → pf2}/sample.h
RENAMED
@@ -1,11 +1,15 @@
|
|
1
1
|
#ifndef PF2_SAMPLE_H
|
2
2
|
#define PF2_SAMPLE_H
|
3
3
|
|
4
|
+
#include <pthread.h>
|
5
|
+
|
4
6
|
#include <ruby.h>
|
5
7
|
|
6
8
|
extern const int PF2_SAMPLE_MAX_NATIVE_DEPTH;
|
7
9
|
|
8
10
|
struct pf2_sample {
|
11
|
+
pthread_t context_pthread;
|
12
|
+
|
9
13
|
int depth;
|
10
14
|
VALUE cmes[200];
|
11
15
|
int linenos[200];
|
@@ -83,7 +83,7 @@ pf2_ser_prepare(struct pf2_ser *serializer, struct pf2_session *session) {
|
|
83
83
|
ensure_samples_capacity(serializer);
|
84
84
|
|
85
85
|
struct pf2_ser_sample *ser_sample = &serializer->samples[serializer->samples_count++];
|
86
|
-
ser_sample->ruby_thread_id =
|
86
|
+
ser_sample->ruby_thread_id = sample->context_pthread;
|
87
87
|
ser_sample->elapsed_ns = sample->timestamp_ns - serializer->start_timestamp_ns;
|
88
88
|
|
89
89
|
// Copy and process Ruby stack frames
|
data/ext/{pf2c → pf2}/session.c
RENAMED
@@ -1,11 +1,12 @@
|
|
1
1
|
#include <bits/time.h>
|
2
|
+
#include <pthread.h>
|
3
|
+
#include <signal.h>
|
2
4
|
#include <stdatomic.h>
|
3
5
|
#include <stdbool.h>
|
4
6
|
#include <stdio.h>
|
5
7
|
#include <stdlib.h>
|
6
|
-
#include <
|
8
|
+
#include <sys/time.h>
|
7
9
|
#include <time.h>
|
8
|
-
#include <pthread.h>
|
9
10
|
|
10
11
|
#include <ruby.h>
|
11
12
|
#include <ruby/debug.h>
|
@@ -14,10 +15,16 @@
|
|
14
15
|
|
15
16
|
#include "backtrace_state.h"
|
16
17
|
#include "configuration.h"
|
18
|
+
#include "debug.h"
|
17
19
|
#include "sample.h"
|
18
20
|
#include "session.h"
|
19
21
|
#include "serializer.h"
|
20
22
|
|
23
|
+
#ifndef HAVE_TIMER_CREATE
|
24
|
+
// Global session pointer for setitimer fallback
|
25
|
+
static struct pf2_session *global_current_session = NULL;
|
26
|
+
#endif
|
27
|
+
|
21
28
|
static void *sample_collector_thread(void *arg);
|
22
29
|
static void sigprof_handler(int sig, siginfo_t *info, void *ucontext);
|
23
30
|
bool ensure_sample_capacity(struct pf2_session *session);
|
@@ -60,17 +67,28 @@ rb_pf2_session_start(VALUE self)
|
|
60
67
|
rb_raise(rb_eRuntimeError, "Failed to spawn sample collector thread");
|
61
68
|
}
|
62
69
|
|
63
|
-
//
|
70
|
+
// Install signal handler for SIGPROF
|
64
71
|
struct sigaction sa;
|
65
72
|
sa.sa_sigaction = sigprof_handler;
|
66
73
|
sigemptyset(&sa.sa_mask);
|
67
74
|
sigaddset(&sa.sa_mask, SIGPROF); // Mask SIGPROFs when handler is running
|
68
75
|
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
69
76
|
if (sigaction(SIGPROF, &sa, NULL) == -1) {
|
70
|
-
rb_raise(rb_eRuntimeError, "Failed to install
|
77
|
+
rb_raise(rb_eRuntimeError, "Failed to install SIGPROF handler");
|
71
78
|
}
|
72
79
|
|
73
|
-
|
80
|
+
#ifndef HAVE_TIMER_CREATE
|
81
|
+
// Install signal handler for SIGALRM if using wall time mode with setitimer
|
82
|
+
if (session->configuration->time_mode != PF2_TIME_MODE_CPU_TIME) {
|
83
|
+
sigaddset(&sa.sa_mask, SIGALRM);
|
84
|
+
if (sigaction(SIGALRM, &sa, NULL) == -1) {
|
85
|
+
rb_raise(rb_eRuntimeError, "Failed to install SIGALRM handler");
|
86
|
+
}
|
87
|
+
}
|
88
|
+
#endif
|
89
|
+
|
90
|
+
#ifdef HAVE_TIMER_CREATE
|
91
|
+
// Configure a kernel timer to send SIGPROF periodically
|
74
92
|
struct sigevent sev;
|
75
93
|
sev.sigev_notify = SIGEV_SIGNAL;
|
76
94
|
sev.sigev_signo = SIGPROF;
|
@@ -97,6 +115,30 @@ rb_pf2_session_start(VALUE self)
|
|
97
115
|
if (timer_settime(session->timer, 0, &its, NULL) == -1) {
|
98
116
|
rb_raise(rb_eRuntimeError, "Failed to start timer");
|
99
117
|
}
|
118
|
+
#else
|
119
|
+
// Use setitimer as fallback
|
120
|
+
// Some platforms (e.g. macOS) do not have timer_create(3).
|
121
|
+
// setitimer(3) can be used as a alternative, but has limited functionality.
|
122
|
+
global_current_session = session;
|
123
|
+
|
124
|
+
struct itimerval itv = {
|
125
|
+
.it_value = {
|
126
|
+
.tv_sec = 0,
|
127
|
+
.tv_usec = session->configuration->interval_ms * 1000,
|
128
|
+
},
|
129
|
+
.it_interval = {
|
130
|
+
.tv_sec = 0,
|
131
|
+
.tv_usec = session->configuration->interval_ms * 1000,
|
132
|
+
},
|
133
|
+
};
|
134
|
+
int which_timer = session->configuration->time_mode == PF2_TIME_MODE_CPU_TIME
|
135
|
+
? ITIMER_PROF // CPU time (sends SIGPROF)
|
136
|
+
: ITIMER_REAL; // Wall time (sends SIGALRM)
|
137
|
+
|
138
|
+
if (setitimer(which_timer, &itv, NULL) == -1) {
|
139
|
+
rb_raise(rb_eRuntimeError, "Failed to start timer");
|
140
|
+
}
|
141
|
+
#endif
|
100
142
|
|
101
143
|
return Qtrue;
|
102
144
|
}
|
@@ -113,9 +155,7 @@ sample_collector_thread(void *arg)
|
|
113
155
|
// Ensure we have capacity before adding a new sample
|
114
156
|
if (!ensure_sample_capacity(session)) {
|
115
157
|
// Failed to expand buffer
|
116
|
-
|
117
|
-
printf("Failed to expand sample buffer. Dropping sample\n");
|
118
|
-
#endif
|
158
|
+
PF2_DEBUG_LOG("Failed to expand sample buffer. Dropping sample\n");
|
119
159
|
break;
|
120
160
|
}
|
121
161
|
|
@@ -140,31 +180,30 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
|
140
180
|
clock_gettime(CLOCK_MONOTONIC, &sig_start_time);
|
141
181
|
#endif
|
142
182
|
|
143
|
-
struct pf2_session *session
|
183
|
+
struct pf2_session *session;
|
184
|
+
#ifdef HAVE_TIMER_CREATE
|
185
|
+
session = info->si_value.sival_ptr;
|
186
|
+
#else
|
187
|
+
session = global_current_session;
|
188
|
+
#endif
|
144
189
|
|
145
190
|
// If garbage collection is in progress, don't collect samples.
|
146
191
|
if (atomic_load_explicit(&session->is_marking, memory_order_acquire)) {
|
147
|
-
|
148
|
-
printf("Dropping sample: Garbage collection is in progress\n");
|
149
|
-
#endif
|
192
|
+
PF2_DEBUG_LOG("Dropping sample: Garbage collection is in progress\n");
|
150
193
|
return;
|
151
194
|
}
|
152
195
|
|
153
|
-
struct pf2_sample sample
|
196
|
+
struct pf2_sample sample;
|
154
197
|
|
155
198
|
if (pf2_sample_capture(&sample) == false) {
|
156
|
-
|
157
|
-
printf("Dropping sample: Failed to capture sample\n");
|
158
|
-
#endif
|
199
|
+
PF2_DEBUG_LOG("Dropping sample: Failed to capture sample\n");
|
159
200
|
return;
|
160
201
|
}
|
161
202
|
|
162
203
|
// Copy the sample to the ringbuffer
|
163
204
|
if (pf2_ringbuffer_push(session->rbuf, &sample) == false) {
|
164
205
|
// Copy failed. The sample buffer is full.
|
165
|
-
|
166
|
-
printf("Dropping sample: Sample buffer is full\n");
|
167
|
-
#endif
|
206
|
+
PF2_DEBUG_LOG("Dropping sample: Sample buffer is full\n");
|
168
207
|
return;
|
169
208
|
}
|
170
209
|
|
@@ -177,7 +216,7 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
|
177
216
|
(sig_end_time.tv_sec - sig_start_time.tv_sec) * 1000000000L +
|
178
217
|
(sig_end_time.tv_nsec - sig_start_time.tv_nsec);
|
179
218
|
|
180
|
-
|
219
|
+
PF2_DEBUG_LOG("sigprof_handler: consumed_time_ns: %lu\n", sample.consumed_time_ns);
|
181
220
|
#endif
|
182
221
|
}
|
183
222
|
|
@@ -220,9 +259,20 @@ rb_pf2_session_stop(VALUE self)
|
|
220
259
|
session->duration_ns = end_ns - start_ns;
|
221
260
|
|
222
261
|
// Disarm and delete the timer.
|
262
|
+
#ifdef HAVE_TIMER_CREATE
|
223
263
|
if (timer_delete(session->timer) == -1) {
|
224
264
|
rb_raise(rb_eRuntimeError, "Failed to delete timer");
|
225
265
|
}
|
266
|
+
#else
|
267
|
+
struct itimerval zero_timer = {{0, 0}, {0, 0}};
|
268
|
+
int which_timer = session->configuration->time_mode == PF2_TIME_MODE_CPU_TIME
|
269
|
+
? ITIMER_PROF
|
270
|
+
: ITIMER_REAL;
|
271
|
+
if (setitimer(which_timer, &zero_timer, NULL) == -1) {
|
272
|
+
rb_raise(rb_eRuntimeError, "Failed to stop timer");
|
273
|
+
}
|
274
|
+
global_current_session = NULL;
|
275
|
+
#endif
|
226
276
|
|
227
277
|
// Terminate the collector thread
|
228
278
|
session->is_running = false;
|
data/ext/{pf2c → pf2}/session.h
RENAMED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
#include <pthread.h>
|
5
5
|
#include <stdatomic.h>
|
6
|
+
#include <sys/time.h>
|
6
7
|
|
7
8
|
#include <ruby.h>
|
8
9
|
|
@@ -12,7 +13,11 @@
|
|
12
13
|
|
13
14
|
struct pf2_session {
|
14
15
|
bool is_running;
|
16
|
+
#ifdef HAVE_TIMER_CREATE
|
15
17
|
timer_t timer;
|
18
|
+
#else
|
19
|
+
struct itimerval timer;
|
20
|
+
#endif
|
16
21
|
struct pf2_ringbuffer *rbuf;
|
17
22
|
atomic_bool is_marking; // Whether garbage collection is in progress
|
18
23
|
pthread_t *collector_thread;
|
data/lib/pf2/cli.rb
CHANGED
@@ -55,20 +55,12 @@ module Pf2
|
|
55
55
|
opts.on('-o', '--output FILE', 'Output file') do |path|
|
56
56
|
options[:output_file] = path
|
57
57
|
end
|
58
|
-
opts.on('--experimental-serializer', 'Enable the experimental serializer mode') do
|
59
|
-
options[:experimental_serializer] = true
|
60
|
-
end
|
61
58
|
end
|
62
59
|
option_parser.parse!(argv)
|
63
60
|
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
+
profile = Marshal.load(File.read(argv[0]))
|
62
|
+
report = Pf2::Reporter::FirefoxProfilerSer2.new(profile).emit
|
63
|
+
report = JSON.generate(report)
|
72
64
|
|
73
65
|
if options[:output_file]
|
74
66
|
File.write(options[:output_file], report)
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
|
5
|
+
require_relative 'stack_weaver'
|
6
|
+
|
5
7
|
module Pf2
|
6
8
|
module Reporter
|
7
9
|
# Generates Firefox Profiler's "processed profile format"
|
@@ -59,7 +61,7 @@ module Pf2
|
|
59
61
|
counters: [],
|
60
62
|
threads: thread_reports,
|
61
63
|
}
|
62
|
-
|
64
|
+
FirefoxProfilerSer2.deep_camelize_keys(report)
|
63
65
|
end
|
64
66
|
|
65
67
|
class ThreadReport
|
@@ -72,6 +74,7 @@ module Pf2
|
|
72
74
|
@stack_tree = { :stack_id => nil }
|
73
75
|
@reverse_stack_tree = []
|
74
76
|
@string_table = { nil => 0 }
|
77
|
+
@stack_weaver = StackWeaver.new(@profile)
|
75
78
|
end
|
76
79
|
|
77
80
|
def inspect
|
@@ -79,9 +82,6 @@ module Pf2
|
|
79
82
|
end
|
80
83
|
|
81
84
|
def emit
|
82
|
-
# TODO: weave?
|
83
|
-
# @thread[:stack_tree] = x
|
84
|
-
|
85
85
|
# Build func table from profile[:functions]
|
86
86
|
func_table = build_func_table
|
87
87
|
# Build frame table from profile[:locations]
|
@@ -136,8 +136,10 @@ module Pf2
|
|
136
136
|
}
|
137
137
|
|
138
138
|
@samples.each do |sample|
|
139
|
-
stack
|
140
|
-
|
139
|
+
# Weave Ruby stack and native stack
|
140
|
+
woven_stack = @stack_weaver.weave(sample[:stack], sample[:native_stack] || [])
|
141
|
+
woven_stack.reverse! # StackWeaver returns in root->leaf order, we need leaf->root
|
142
|
+
stack_id = @stack_tree.dig(*woven_stack, :stack_id)
|
141
143
|
|
142
144
|
ret[:stack] << stack_id
|
143
145
|
ret[:time] << sample[:elapsed_ns] / 1_000_000 # ns -> ms
|
@@ -165,9 +167,11 @@ module Pf2
|
|
165
167
|
}
|
166
168
|
|
167
169
|
@profile[:locations].each.with_index do |location, i|
|
170
|
+
function = @profile[:functions][location[:function_index]]
|
171
|
+
|
168
172
|
ret[:address] << location[:address]
|
169
|
-
ret[:category] <<
|
170
|
-
ret[:subcategory] <<
|
173
|
+
ret[:category] << (function[:implementation] == :native ? 3 : 2)
|
174
|
+
ret[:subcategory] << nil
|
171
175
|
ret[:func] << location[:function_index]
|
172
176
|
ret[:inner_window_id] << nil
|
173
177
|
ret[:implementation] << nil
|
@@ -218,15 +222,15 @@ module Pf2
|
|
218
222
|
}
|
219
223
|
|
220
224
|
@profile[:samples].each do |sample|
|
221
|
-
#
|
222
|
-
|
223
|
-
|
225
|
+
# Weave Ruby stack and native stack
|
226
|
+
woven_stack = @stack_weaver.weave(sample[:stack], sample[:native_stack] || [])
|
227
|
+
woven_stack.reverse! # StackWeaver returns in root->leaf order, we need leaf->root
|
224
228
|
|
225
229
|
# Build the stack_table Array which Firefox Profiler requires.
|
226
230
|
# At the same time, build the stack tree for efficient traversal.
|
227
231
|
|
228
232
|
current_node = @stack_tree # the stack tree root
|
229
|
-
|
233
|
+
woven_stack.each do |location_index|
|
230
234
|
if current_node[location_index].nil?
|
231
235
|
# The tree node is unknown. Create it.
|
232
236
|
new_stack_id = ret[:frame].length # The position of the new stack in the stack_table array
|
@@ -238,7 +242,7 @@ module Pf2
|
|
238
242
|
|
239
243
|
# Register the stack in the stack_table Array
|
240
244
|
ret[:frame] << location_index
|
241
|
-
ret[:category] << (function[:implementation] == :
|
245
|
+
ret[:category] << (function[:implementation] == :native ? 3 : 2)
|
242
246
|
ret[:subcategory] << nil
|
243
247
|
ret[:prefix] << current_node[:stack_id] # the parent's position in the stack_table array
|
244
248
|
end
|
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
module Pf2
|
4
4
|
module Reporter
|
5
|
+
# "Weaves" the native stack into the Ruby stack.
|
6
|
+
#
|
7
|
+
# Strategy:
|
8
|
+
# - Split the stack into Ruby and Native parts
|
9
|
+
# - Start from the root of the Native stack
|
10
|
+
# - Dig in to the native stack until we hit a rb_vm_exec(), which marks a call into Ruby code
|
11
|
+
# - Switch to Ruby stack. Keep digging until we hit a Cfunc call, then switch back to Native stack
|
12
|
+
# - Repeat until we consume the entire stack
|
5
13
|
class StackWeaver
|
6
14
|
def initialize(profile)
|
7
15
|
@profile = profile
|
data/lib/pf2/reporter.rb
CHANGED
data/lib/pf2/version.rb
CHANGED