pf2 0.9.0 → 0.10.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 +18 -1
- 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 +93 -29
- data/ext/{pf2c → pf2}/session.h +6 -1
- 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 +25 -128
- 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: 22269113784c160efa2ef33412444646afdb33d58215f0a7f0ec2e4e05ad2708
|
|
4
|
+
data.tar.gz: cc1b783cab40fc4469b5bc09fc3a3a5dec4a76357d14accb7f6a4458ea7cc7bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 551b033c17201f6f21d741e53bf5b5c43cce3adb3f5daa0e1f17679176c23b87d727cbd162b67f855a39b9fd344ed499ec7d283598f95de30c25d2b3ac7d179e
|
|
7
|
+
data.tar.gz: ffa342dcfd1c467737485c0aba8497ff98c144a53ae5c0e50566ef1ba58e6fc9e0cfbe0ad870e64788b2a93f31eb8011c0fa1194e674f092dfb9b662d7b9db71
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.10.0] - 2025-12-26
|
|
2
|
+
|
|
3
|
+
## Added
|
|
4
|
+
|
|
5
|
+
**This version contains a complete rewrite of the profiler!**
|
|
6
|
+
|
|
7
|
+
- The default sample collection backend has been switched to the new C-based backend.
|
|
8
|
+
- The previous Rust-based backed has been removed. Use v0.9.0 if you need it.
|
|
9
|
+
- macOS / non-Linux platform support!
|
|
10
|
+
- On platforms which lack `timer_create(3)` such as macOS, Pf2 now fall backs to `setitimer(3)` based sampling. This mode does not support per-thread CPU time sampling.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `logger` is now declared as a dependency (Ruby 4.0 compat).
|
|
15
|
+
|
|
2
16
|
|
|
3
17
|
## [0.9.0] - 2025-03-22
|
|
4
18
|
|
|
@@ -11,6 +25,7 @@
|
|
|
11
25
|
|
|
12
26
|
- Set SA_RESTART flag to reduce EINTRs in profiled code
|
|
13
27
|
|
|
28
|
+
|
|
14
29
|
## [0.8.0] - 2025-01-27
|
|
15
30
|
|
|
16
31
|
## Added
|
|
@@ -19,12 +34,14 @@
|
|
|
19
34
|
- This serializer is more efficient and has a smaller memory footprint than the default serializer.
|
|
20
35
|
- Ser2 still lacks some features, such as weaving of native stacks.
|
|
21
36
|
|
|
37
|
+
|
|
22
38
|
## [0.7.1] - 2025-01-02
|
|
23
39
|
|
|
24
40
|
### Fixed
|
|
25
41
|
|
|
26
42
|
- Reverted Cargo.lock version to 3 to support older versions of Rust (<1.78).
|
|
27
43
|
|
|
44
|
+
|
|
28
45
|
## [0.7.0] - 2025-01-03
|
|
29
46
|
|
|
30
47
|
### Changed
|
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,13 +15,20 @@
|
|
|
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);
|
|
31
|
+
static void pf2_session_stop(struct pf2_session *session);
|
|
24
32
|
|
|
25
33
|
VALUE
|
|
26
34
|
rb_pf2_session_initialize(int argc, VALUE *argv, VALUE self)
|
|
@@ -60,17 +68,28 @@ rb_pf2_session_start(VALUE self)
|
|
|
60
68
|
rb_raise(rb_eRuntimeError, "Failed to spawn sample collector thread");
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
//
|
|
71
|
+
// Install signal handler for SIGPROF
|
|
64
72
|
struct sigaction sa;
|
|
65
73
|
sa.sa_sigaction = sigprof_handler;
|
|
66
74
|
sigemptyset(&sa.sa_mask);
|
|
67
75
|
sigaddset(&sa.sa_mask, SIGPROF); // Mask SIGPROFs when handler is running
|
|
68
76
|
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
69
77
|
if (sigaction(SIGPROF, &sa, NULL) == -1) {
|
|
70
|
-
rb_raise(rb_eRuntimeError, "Failed to install
|
|
78
|
+
rb_raise(rb_eRuntimeError, "Failed to install SIGPROF handler");
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
|
|
81
|
+
#ifndef HAVE_TIMER_CREATE
|
|
82
|
+
// Install signal handler for SIGALRM if using wall time mode with setitimer
|
|
83
|
+
if (session->configuration->time_mode != PF2_TIME_MODE_CPU_TIME) {
|
|
84
|
+
sigaddset(&sa.sa_mask, SIGALRM);
|
|
85
|
+
if (sigaction(SIGALRM, &sa, NULL) == -1) {
|
|
86
|
+
rb_raise(rb_eRuntimeError, "Failed to install SIGALRM handler");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
#endif
|
|
90
|
+
|
|
91
|
+
#ifdef HAVE_TIMER_CREATE
|
|
92
|
+
// Configure a kernel timer to send SIGPROF periodically
|
|
74
93
|
struct sigevent sev;
|
|
75
94
|
sev.sigev_notify = SIGEV_SIGNAL;
|
|
76
95
|
sev.sigev_signo = SIGPROF;
|
|
@@ -97,6 +116,30 @@ rb_pf2_session_start(VALUE self)
|
|
|
97
116
|
if (timer_settime(session->timer, 0, &its, NULL) == -1) {
|
|
98
117
|
rb_raise(rb_eRuntimeError, "Failed to start timer");
|
|
99
118
|
}
|
|
119
|
+
#else
|
|
120
|
+
// Use setitimer as fallback
|
|
121
|
+
// Some platforms (e.g. macOS) do not have timer_create(3).
|
|
122
|
+
// setitimer(3) can be used as a alternative, but has limited functionality.
|
|
123
|
+
global_current_session = session;
|
|
124
|
+
|
|
125
|
+
struct itimerval itv = {
|
|
126
|
+
.it_value = {
|
|
127
|
+
.tv_sec = 0,
|
|
128
|
+
.tv_usec = session->configuration->interval_ms * 1000,
|
|
129
|
+
},
|
|
130
|
+
.it_interval = {
|
|
131
|
+
.tv_sec = 0,
|
|
132
|
+
.tv_usec = session->configuration->interval_ms * 1000,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
int which_timer = session->configuration->time_mode == PF2_TIME_MODE_CPU_TIME
|
|
136
|
+
? ITIMER_PROF // CPU time (sends SIGPROF)
|
|
137
|
+
: ITIMER_REAL; // Wall time (sends SIGALRM)
|
|
138
|
+
|
|
139
|
+
if (setitimer(which_timer, &itv, NULL) == -1) {
|
|
140
|
+
rb_raise(rb_eRuntimeError, "Failed to start timer");
|
|
141
|
+
}
|
|
142
|
+
#endif
|
|
100
143
|
|
|
101
144
|
return Qtrue;
|
|
102
145
|
}
|
|
@@ -113,9 +156,7 @@ sample_collector_thread(void *arg)
|
|
|
113
156
|
// Ensure we have capacity before adding a new sample
|
|
114
157
|
if (!ensure_sample_capacity(session)) {
|
|
115
158
|
// Failed to expand buffer
|
|
116
|
-
|
|
117
|
-
printf("Failed to expand sample buffer. Dropping sample\n");
|
|
118
|
-
#endif
|
|
159
|
+
PF2_DEBUG_LOG("Failed to expand sample buffer. Dropping sample\n");
|
|
119
160
|
break;
|
|
120
161
|
}
|
|
121
162
|
|
|
@@ -140,31 +181,30 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
|
|
140
181
|
clock_gettime(CLOCK_MONOTONIC, &sig_start_time);
|
|
141
182
|
#endif
|
|
142
183
|
|
|
143
|
-
struct pf2_session *session
|
|
184
|
+
struct pf2_session *session;
|
|
185
|
+
#ifdef HAVE_TIMER_CREATE
|
|
186
|
+
session = info->si_value.sival_ptr;
|
|
187
|
+
#else
|
|
188
|
+
session = global_current_session;
|
|
189
|
+
#endif
|
|
144
190
|
|
|
145
191
|
// If garbage collection is in progress, don't collect samples.
|
|
146
192
|
if (atomic_load_explicit(&session->is_marking, memory_order_acquire)) {
|
|
147
|
-
|
|
148
|
-
printf("Dropping sample: Garbage collection is in progress\n");
|
|
149
|
-
#endif
|
|
193
|
+
PF2_DEBUG_LOG("Dropping sample: Garbage collection is in progress\n");
|
|
150
194
|
return;
|
|
151
195
|
}
|
|
152
196
|
|
|
153
|
-
struct pf2_sample sample
|
|
197
|
+
struct pf2_sample sample;
|
|
154
198
|
|
|
155
199
|
if (pf2_sample_capture(&sample) == false) {
|
|
156
|
-
|
|
157
|
-
printf("Dropping sample: Failed to capture sample\n");
|
|
158
|
-
#endif
|
|
200
|
+
PF2_DEBUG_LOG("Dropping sample: Failed to capture sample\n");
|
|
159
201
|
return;
|
|
160
202
|
}
|
|
161
203
|
|
|
162
204
|
// Copy the sample to the ringbuffer
|
|
163
205
|
if (pf2_ringbuffer_push(session->rbuf, &sample) == false) {
|
|
164
206
|
// Copy failed. The sample buffer is full.
|
|
165
|
-
|
|
166
|
-
printf("Dropping sample: Sample buffer is full\n");
|
|
167
|
-
#endif
|
|
207
|
+
PF2_DEBUG_LOG("Dropping sample: Sample buffer is full\n");
|
|
168
208
|
return;
|
|
169
209
|
}
|
|
170
210
|
|
|
@@ -177,7 +217,7 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
|
|
177
217
|
(sig_end_time.tv_sec - sig_start_time.tv_sec) * 1000000000L +
|
|
178
218
|
(sig_end_time.tv_nsec - sig_start_time.tv_nsec);
|
|
179
219
|
|
|
180
|
-
|
|
220
|
+
PF2_DEBUG_LOG("sigprof_handler: consumed_time_ns: %lu\n", sample.consumed_time_ns);
|
|
181
221
|
#endif
|
|
182
222
|
}
|
|
183
223
|
|
|
@@ -212,6 +252,20 @@ rb_pf2_session_stop(VALUE self)
|
|
|
212
252
|
struct pf2_session *session;
|
|
213
253
|
TypedData_Get_Struct(self, struct pf2_session, &pf2_session_type, session);
|
|
214
254
|
|
|
255
|
+
pf2_session_stop(session);
|
|
256
|
+
|
|
257
|
+
// Create serializer and serialize
|
|
258
|
+
struct pf2_ser *serializer = pf2_ser_new();
|
|
259
|
+
pf2_ser_prepare(serializer, session);
|
|
260
|
+
VALUE result = pf2_ser_to_ruby_hash(serializer);
|
|
261
|
+
pf2_ser_free(serializer);
|
|
262
|
+
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
static void
|
|
267
|
+
pf2_session_stop(struct pf2_session *session)
|
|
268
|
+
{
|
|
215
269
|
// Calculate duration
|
|
216
270
|
struct timespec end_time;
|
|
217
271
|
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
|
@@ -220,21 +274,24 @@ rb_pf2_session_stop(VALUE self)
|
|
|
220
274
|
session->duration_ns = end_ns - start_ns;
|
|
221
275
|
|
|
222
276
|
// Disarm and delete the timer.
|
|
277
|
+
#ifdef HAVE_TIMER_CREATE
|
|
223
278
|
if (timer_delete(session->timer) == -1) {
|
|
224
279
|
rb_raise(rb_eRuntimeError, "Failed to delete timer");
|
|
225
280
|
}
|
|
281
|
+
#else
|
|
282
|
+
struct itimerval zero_timer = {{0, 0}, {0, 0}};
|
|
283
|
+
int which_timer = session->configuration->time_mode == PF2_TIME_MODE_CPU_TIME
|
|
284
|
+
? ITIMER_PROF
|
|
285
|
+
: ITIMER_REAL;
|
|
286
|
+
if (setitimer(which_timer, &zero_timer, NULL) == -1) {
|
|
287
|
+
rb_raise(rb_eRuntimeError, "Failed to stop timer");
|
|
288
|
+
}
|
|
289
|
+
global_current_session = NULL;
|
|
290
|
+
#endif
|
|
226
291
|
|
|
227
292
|
// Terminate the collector thread
|
|
228
293
|
session->is_running = false;
|
|
229
294
|
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
295
|
}
|
|
239
296
|
|
|
240
297
|
VALUE
|
|
@@ -323,8 +380,15 @@ pf2_session_dmark(void *sess)
|
|
|
323
380
|
void
|
|
324
381
|
pf2_session_dfree(void *sess)
|
|
325
382
|
{
|
|
326
|
-
// TODO: Ensure the uninstall process is complete before freeing the session
|
|
327
383
|
struct pf2_session *session = sess;
|
|
384
|
+
|
|
385
|
+
assert(session->is_running == false || session->is_running == true);
|
|
386
|
+
|
|
387
|
+
// Stop the session if it's still running
|
|
388
|
+
if (session->is_running) {
|
|
389
|
+
pf2_session_stop(session);
|
|
390
|
+
}
|
|
391
|
+
|
|
328
392
|
pf2_configuration_free(session->configuration);
|
|
329
393
|
pf2_ringbuffer_free(session->rbuf);
|
|
330
394
|
free(session->samples);
|
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;
|
|
@@ -45,7 +50,7 @@ static const rb_data_type_t pf2_session_type = {
|
|
|
45
50
|
.dsize = pf2_session_dsize,
|
|
46
51
|
},
|
|
47
52
|
.data = NULL,
|
|
48
|
-
.flags =
|
|
53
|
+
.flags = 0,
|
|
49
54
|
};
|
|
50
55
|
|
|
51
56
|
#endif // PF2_SESSION_H
|
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