pf2 0.9.0 → 0.11.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/.document +3 -0
- data/.rdoc_options +6 -0
- data/CHANGELOG.md +36 -0
- data/README.md +31 -4
- data/Rakefile +8 -9
- data/doc/development.md +6 -0
- data/ext/pf2/debug.h +12 -0
- data/ext/pf2/extconf.rb +23 -6
- data/ext/pf2/pf2.c +17 -0
- data/ext/{pf2c → pf2}/sample.c +7 -3
- data/ext/pf2/sample.h +27 -0
- data/ext/{pf2c → pf2}/serializer.c +1 -1
- data/ext/{pf2c → pf2}/session.c +116 -31
- data/ext/{pf2c → pf2}/session.h +7 -2
- data/lib/pf2/cli.rb +3 -11
- data/lib/pf2/reporter/firefox_profiler_ser2.rb +17 -13
- data/lib/pf2/reporter/stack_weaver.rb +9 -1
- data/lib/pf2/reporter.rb +0 -1
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +1 -2
- metadata +41 -129
- 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/ext/pf2c/pf2.c +0 -17
- data/ext/pf2c/sample.h +0 -22
- data/lib/pf2/reporter/firefox_profiler.rb +0 -397
- data/lib/pf2/session.rb +0 -9
- 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.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: 74e3ac2900007b7750e3c3274833e1f713b6e85570db0aa738761ea99b517fe8
|
|
4
|
+
data.tar.gz: 68ffc8d681328e4d5542e4bd4b3a98edfd08d69fa1b54342fdccbe8f9495bcf0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4f1e67870eac32734a7532f46f29b74589ec6d71c59c925fc02456f473a516218fbbf46fed730558f0b9525c585dbb2fbc088fcf9cbfb47e7f4b89adda7f4974
|
|
7
|
+
data.tar.gz: 8f5ec74f112eb65a512dbcd7002b0db9a4da0d3a2124d3bd2e7106412c71009b36f92ff442efc87910a5a8829094c06e1ee93169c829ab893dd8e1bd86a81a4f
|
data/.document
ADDED
data/.rdoc_options
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2025-12-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- RDoc documentation is now online - https://osyoyu.github.io/pf2/
|
|
8
|
+
- Native stack consolidation now supports LTO-ed binaries (@hanazuki)
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- `Pf2c` module is now completely removed. `Pf2c::Session` has been merged as `Pf2::Session`.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fixed an bug where the program crashes when a `Pf2::Session` is GC'd before profiling starts.
|
|
17
|
+
- Fixed an bug where the program crashes when the native stack was more than 200 frames deep.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## [0.10.0] - 2025-12-26
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
**This version contains a complete rewrite of the profiler!**
|
|
25
|
+
|
|
26
|
+
- The default sample collection backend has been switched to the new C-based backend.
|
|
27
|
+
- The previous Rust-based backed has been removed. Use v0.9.0 if you need it.
|
|
28
|
+
- macOS / non-Linux platform support!
|
|
29
|
+
- 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.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- `logger` is now declared as a dependency (Ruby 4.0 compat).
|
|
34
|
+
|
|
35
|
+
|
|
3
36
|
## [0.9.0] - 2025-03-22
|
|
4
37
|
|
|
5
38
|
## Added
|
|
@@ -11,6 +44,7 @@
|
|
|
11
44
|
|
|
12
45
|
- Set SA_RESTART flag to reduce EINTRs in profiled code
|
|
13
46
|
|
|
47
|
+
|
|
14
48
|
## [0.8.0] - 2025-01-27
|
|
15
49
|
|
|
16
50
|
## Added
|
|
@@ -19,12 +53,14 @@
|
|
|
19
53
|
- This serializer is more efficient and has a smaller memory footprint than the default serializer.
|
|
20
54
|
- Ser2 still lacks some features, such as weaving of native stacks.
|
|
21
55
|
|
|
56
|
+
|
|
22
57
|
## [0.7.1] - 2025-01-02
|
|
23
58
|
|
|
24
59
|
### Fixed
|
|
25
60
|
|
|
26
61
|
- Reverted Cargo.lock version to 3 to support older versions of Rust (<1.78).
|
|
27
62
|
|
|
63
|
+
|
|
28
64
|
## [0.7.0] - 2025-01-03
|
|
29
65
|
|
|
30
66
|
### Changed
|
data/README.md
CHANGED
|
@@ -3,6 +3,9 @@ Pf2
|
|
|
3
3
|
|
|
4
4
|
A experimental sampling-based profiler for Ruby 3.3+.
|
|
5
5
|
|
|
6
|
+
- GitHub: https://github.com/osyoyu/pf2
|
|
7
|
+
- Documentation: https://osyoyu.github.io/pf2/
|
|
8
|
+
|
|
6
9
|
Notable Capabilites
|
|
7
10
|
--------
|
|
8
11
|
|
|
@@ -13,6 +16,25 @@ Notable Capabilites
|
|
|
13
16
|
Usage
|
|
14
17
|
--------
|
|
15
18
|
|
|
19
|
+
### Installation
|
|
20
|
+
|
|
21
|
+
You will need a C compiler to build the native extension.
|
|
22
|
+
|
|
23
|
+
Add this line to your application's Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'pf2'
|
|
27
|
+
|
|
28
|
+
# When using the main branch, specify submodules: true
|
|
29
|
+
gem 'pf2', git: 'https://github.com/osyoyu/pf2.git', submodules: true
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Pf2 can be installed as a standalone CLI tool as well.
|
|
33
|
+
|
|
34
|
+
```console
|
|
35
|
+
gem install pf2
|
|
36
|
+
```
|
|
37
|
+
|
|
16
38
|
### Quickstart
|
|
17
39
|
|
|
18
40
|
Run your Ruby program through `pf2 serve`.
|
|
@@ -116,7 +138,7 @@ Schedulers determine when to execute sample collection, based on configuration (
|
|
|
116
138
|
|
|
117
139
|
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
140
|
|
|
119
|
-
When the specified interval has arrived (the timer has _expired_), the OS delivers us a
|
|
141
|
+
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
142
|
|
|
121
143
|
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
144
|
|
|
@@ -128,11 +150,16 @@ Another scheduler is the `TimerThreadScheduler`, which maintains a time-keeping
|
|
|
128
150
|
|
|
129
151
|
This scheduler is wall-time only, and does not support CPU-time based profiling.
|
|
130
152
|
|
|
131
|
-
|
|
153
|
+
#### macOS Support
|
|
154
|
+
|
|
155
|
+
On platforms where `timer_create()` is not supported (namely macOS), Pf2 falls back to `setitimer()`.
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
Wishlist
|
|
132
159
|
--------
|
|
133
160
|
|
|
134
|
-
-
|
|
135
|
-
-
|
|
161
|
+
- [Flame Scopes](https://www.brendangregg.com/flamescope.html)
|
|
162
|
+
- More unit/e2e tests
|
|
136
163
|
- more
|
|
137
164
|
|
|
138
165
|
Development
|
data/Rakefile
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
2
|
require 'rake/extensiontask'
|
|
3
3
|
require 'minitest/test_task'
|
|
4
|
+
require 'rdoc/task'
|
|
4
5
|
|
|
5
6
|
task default: %i[]
|
|
6
7
|
|
|
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
|
|
8
|
+
Rake::ExtensionTask.new 'pf2' do |ext|
|
|
9
|
+
ext.name = 'pf2'
|
|
10
|
+
ext.lib_dir = 'lib/pf2'
|
|
16
11
|
end
|
|
17
12
|
|
|
18
13
|
Minitest::TestTask.create(:test) do |t|
|
|
@@ -21,3 +16,7 @@ Minitest::TestTask.create(:test) do |t|
|
|
|
21
16
|
t.warning = false
|
|
22
17
|
t.test_globs = ["test/**/*_test.rb"]
|
|
23
18
|
end
|
|
19
|
+
|
|
20
|
+
RDoc::Task.new do |doc|
|
|
21
|
+
doc.rdoc_dir = "_site" # for GitHub pages
|
|
22
|
+
end
|
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/pf2/pf2.c
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
|
|
3
|
+
#include "session.h"
|
|
4
|
+
|
|
5
|
+
VALUE rb_mPf2;
|
|
6
|
+
|
|
7
|
+
RUBY_FUNC_EXPORTED void
|
|
8
|
+
Init_pf2(void)
|
|
9
|
+
{
|
|
10
|
+
rb_mPf2 = rb_define_module("Pf2");
|
|
11
|
+
VALUE rb_mPf2_cSession = rb_define_class_under(rb_mPf2, "Session", rb_cObject);
|
|
12
|
+
rb_define_alloc_func(rb_mPf2_cSession, pf2_session_alloc);
|
|
13
|
+
rb_define_method(rb_mPf2_cSession, "initialize", rb_pf2_session_initialize, -1);
|
|
14
|
+
rb_define_method(rb_mPf2_cSession, "start", rb_pf2_session_start, 0);
|
|
15
|
+
rb_define_method(rb_mPf2_cSession, "stop", rb_pf2_session_stop, 0);
|
|
16
|
+
rb_define_method(rb_mPf2_cSession, "configuration", rb_pf2_session_configuration, 0);
|
|
17
|
+
}
|
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>
|
|
@@ -8,8 +9,6 @@
|
|
|
8
9
|
#include "backtrace_state.h"
|
|
9
10
|
#include "sample.h"
|
|
10
11
|
|
|
11
|
-
const int PF2_SAMPLE_MAX_NATIVE_DEPTH = 300;
|
|
12
|
-
|
|
13
12
|
static int capture_native_backtrace(struct pf2_sample *sample);
|
|
14
13
|
static int backtrace_on_ok(void *data, uintptr_t pc);
|
|
15
14
|
|
|
@@ -17,13 +16,18 @@ static int backtrace_on_ok(void *data, uintptr_t pc);
|
|
|
17
16
|
bool
|
|
18
17
|
pf2_sample_capture(struct pf2_sample *sample)
|
|
19
18
|
{
|
|
19
|
+
// Initialize sample
|
|
20
|
+
memset(sample, 0, sizeof(struct pf2_sample));
|
|
21
|
+
|
|
20
22
|
// Record the current time
|
|
21
23
|
struct timespec now;
|
|
22
24
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
23
25
|
sample->timestamp_ns = (uint64_t)now.tv_sec * 1000000000ULL + (uint64_t)now.tv_nsec;
|
|
24
26
|
|
|
27
|
+
sample->context_pthread = pthread_self();
|
|
28
|
+
|
|
25
29
|
// Obtain the current stack from Ruby
|
|
26
|
-
sample->depth = rb_profile_frames(0,
|
|
30
|
+
sample->depth = rb_profile_frames(0, PF2_SAMPLE_MAX_RUBY_DEPTH, sample->cmes, sample->linenos);
|
|
27
31
|
|
|
28
32
|
// Capture C-level backtrace
|
|
29
33
|
sample->native_stack_depth = capture_native_backtrace(sample);
|
data/ext/pf2/sample.h
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#ifndef PF2_SAMPLE_H
|
|
2
|
+
#define PF2_SAMPLE_H
|
|
3
|
+
|
|
4
|
+
#include <pthread.h>
|
|
5
|
+
|
|
6
|
+
#include <ruby.h>
|
|
7
|
+
|
|
8
|
+
#define PF2_SAMPLE_MAX_RUBY_DEPTH 200
|
|
9
|
+
#define PF2_SAMPLE_MAX_NATIVE_DEPTH 300
|
|
10
|
+
|
|
11
|
+
struct pf2_sample {
|
|
12
|
+
pthread_t context_pthread;
|
|
13
|
+
|
|
14
|
+
int depth;
|
|
15
|
+
VALUE cmes[PF2_SAMPLE_MAX_RUBY_DEPTH];
|
|
16
|
+
int linenos[PF2_SAMPLE_MAX_RUBY_DEPTH];
|
|
17
|
+
|
|
18
|
+
size_t native_stack_depth;
|
|
19
|
+
uintptr_t native_stack[PF2_SAMPLE_MAX_NATIVE_DEPTH];
|
|
20
|
+
|
|
21
|
+
uint64_t consumed_time_ns;
|
|
22
|
+
uint64_t timestamp_ns;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
bool pf2_sample_capture(struct pf2_sample *sample);
|
|
26
|
+
|
|
27
|
+
#endif // PF2_SAMPLE_H
|
|
@@ -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
|
|
@@ -261,19 +318,32 @@ pf2_session_alloc(VALUE self)
|
|
|
261
318
|
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
262
319
|
}
|
|
263
320
|
|
|
321
|
+
// is_running
|
|
322
|
+
session->is_running = false;
|
|
323
|
+
|
|
324
|
+
// timer
|
|
325
|
+
#ifdef HAVE_TIMER_CREATE
|
|
326
|
+
session->timer = (timer_t)0;
|
|
327
|
+
#else
|
|
328
|
+
session->timer = (struct itimerval){0};
|
|
329
|
+
#endif
|
|
330
|
+
|
|
331
|
+
// rbuf
|
|
264
332
|
session->rbuf = pf2_ringbuffer_new(1000);
|
|
265
333
|
if (session->rbuf == NULL) {
|
|
266
334
|
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
267
335
|
}
|
|
268
336
|
|
|
337
|
+
// is_marking
|
|
269
338
|
atomic_store_explicit(&session->is_marking, false, memory_order_relaxed);
|
|
339
|
+
|
|
340
|
+
// collector_thread
|
|
270
341
|
session->collector_thread = malloc(sizeof(pthread_t));
|
|
271
342
|
if (session->collector_thread == NULL) {
|
|
272
343
|
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
273
344
|
}
|
|
274
345
|
|
|
275
|
-
|
|
276
|
-
|
|
346
|
+
// samples, samples_index, samples_capacity
|
|
277
347
|
session->samples_index = 0;
|
|
278
348
|
session->samples_capacity = 500; // 10 seconds worth of samples at 50 Hz
|
|
279
349
|
session->samples = malloc(sizeof(struct pf2_sample) * session->samples_capacity);
|
|
@@ -281,6 +351,14 @@ pf2_session_alloc(VALUE self)
|
|
|
281
351
|
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
282
352
|
}
|
|
283
353
|
|
|
354
|
+
// start_time_realtime, start_time
|
|
355
|
+
session->start_time_realtime = (struct timespec){0};
|
|
356
|
+
session->start_time = (struct timespec){0};
|
|
357
|
+
|
|
358
|
+
// duration_ns
|
|
359
|
+
session->duration_ns = 0;
|
|
360
|
+
|
|
361
|
+
// configuration
|
|
284
362
|
session->configuration = NULL;
|
|
285
363
|
|
|
286
364
|
return TypedData_Wrap_Struct(self, &pf2_session_type, session);
|
|
@@ -323,8 +401,15 @@ pf2_session_dmark(void *sess)
|
|
|
323
401
|
void
|
|
324
402
|
pf2_session_dfree(void *sess)
|
|
325
403
|
{
|
|
326
|
-
// TODO: Ensure the uninstall process is complete before freeing the session
|
|
327
404
|
struct pf2_session *session = sess;
|
|
405
|
+
|
|
406
|
+
assert(session->is_running == false || session->is_running == true);
|
|
407
|
+
|
|
408
|
+
// Stop the session if it's still running
|
|
409
|
+
if (session->is_running) {
|
|
410
|
+
pf2_session_stop(session);
|
|
411
|
+
}
|
|
412
|
+
|
|
328
413
|
pf2_configuration_free(session->configuration);
|
|
329
414
|
pf2_ringbuffer_free(session->rbuf);
|
|
330
415
|
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;
|
|
@@ -38,14 +43,14 @@ void pf2_session_dfree(void *sess);
|
|
|
38
43
|
size_t pf2_session_dsize(const void *sess);
|
|
39
44
|
|
|
40
45
|
static const rb_data_type_t pf2_session_type = {
|
|
41
|
-
.wrap_struct_name = "
|
|
46
|
+
.wrap_struct_name = "Pf2::Session",
|
|
42
47
|
.function = {
|
|
43
48
|
.dmark = pf2_session_dmark,
|
|
44
49
|
.dfree = pf2_session_dfree,
|
|
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)
|