pf2 0.12.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/CHANGELOG.md +0 -72
- data/README.md +0 -24
- data/Rakefile +0 -6
- data/ext/pf2/configuration.c +0 -14
- data/ext/pf2/configuration.h +0 -3
- data/ext/pf2/extconf.rb +2 -17
- data/ext/pf2/pf2.c +8 -8
- data/ext/pf2/sample.c +3 -1
- data/ext/pf2/sample.h +4 -5
- data/ext/pf2/serializer.c +8 -18
- data/ext/pf2/serializer.h +4 -8
- data/ext/pf2/session.c +86 -138
- data/ext/pf2/session.h +2 -5
- data/lib/pf2/cli.rb +1 -1
- data/lib/pf2/reporter/stack_weaver.rb +1 -1
- data/lib/pf2/session.rb +9 -0
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +4 -11
- data/vendor/libbacktrace/atomic.c +1 -1
- data/vendor/libbacktrace/configure +4 -12
- data/vendor/libbacktrace/configure.ac +1 -6
- data/vendor/libbacktrace/elf.c +4 -4
- data/vendor/libbacktrace/fileline.c +1 -35
- data/vendor/libbacktrace/filetype.awk +0 -1
- metadata +3 -33
- data/.document +0 -3
- data/.rdoc_options +0 -6
- data/ext/patches/libbacktrace/0001-Support-MACH_O_MH_BUNDLE.patch +0 -32
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/CHANGELOG.md
CHANGED
|
@@ -1,74 +1,5 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
-
## [0.12.0] - 2026-01-09
|
|
4
|
-
|
|
5
|
-
### Added
|
|
6
|
-
|
|
7
|
-
- `Pf2.profile` now accepts the same options as `Pf2.start`.
|
|
8
|
-
- The resulting profile now has `collected_sample_count` and `dropped_sample_count` fields.
|
|
9
|
-
|
|
10
|
-
### Fixed
|
|
11
|
-
|
|
12
|
-
- Samples captured after the collector thread was stopped now get included in the profile.
|
|
13
|
-
- This shouldn't matter in practice (this all happens after `Pf2.stop` is called).
|
|
14
|
-
|
|
15
|
-
### Changed
|
|
16
|
-
|
|
17
|
-
- Accepted max stack depth is expanded to 1024 for Ruby (was 200) and 512 for native (was 300).
|
|
18
|
-
- This is not configurable, but should be sufficient for most use cases. Please open an issue if you need higher limits.
|
|
19
|
-
- Pf2.profile now accepts the same parameters as Pf2.start.
|
|
20
|
-
- Internal changes
|
|
21
|
-
- Updated libbacktrace to the latest version as of 2026/1/8.
|
|
22
|
-
- Tests are now much more stabilized.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## [0.11.3] - 2025-12-28
|
|
26
|
-
|
|
27
|
-
This version is for testing the new release process through [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/). All code is identical to 0.11.2.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
## [0.11.2] - 2025-12-28
|
|
31
|
-
|
|
32
|
-
0.11.1 was yanked since it was accidentally published without libbacktrace vendored. Use 0.11.2.
|
|
33
|
-
|
|
34
|
-
### Fixed
|
|
35
|
-
|
|
36
|
-
- Fixed issues preventing builds on macOS.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
## [0.11.0] - 2025-12-27
|
|
40
|
-
|
|
41
|
-
### Added
|
|
42
|
-
|
|
43
|
-
- RDoc documentation is now online - https://osyoyu.github.io/pf2/
|
|
44
|
-
- Native stack consolidation now supports LTO-ed binaries (@hanazuki)
|
|
45
|
-
|
|
46
|
-
### Changed
|
|
47
|
-
|
|
48
|
-
- `Pf2c` module is now completely removed. `Pf2c::Session` has been merged as `Pf2::Session`.
|
|
49
|
-
|
|
50
|
-
### Fixed
|
|
51
|
-
|
|
52
|
-
- Fixed an bug where the program crashes when a `Pf2::Session` is GC'd before profiling starts.
|
|
53
|
-
- Fixed an bug where the program crashes when the native stack was more than 200 frames deep.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
## [0.10.0] - 2025-12-26
|
|
57
|
-
|
|
58
|
-
### Added
|
|
59
|
-
|
|
60
|
-
**This version contains a complete rewrite of the profiler!**
|
|
61
|
-
|
|
62
|
-
- The default sample collection backend has been switched to the new C-based backend.
|
|
63
|
-
- The previous Rust-based backed has been removed. Use v0.9.0 if you need it.
|
|
64
|
-
- macOS / non-Linux platform support!
|
|
65
|
-
- 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.
|
|
66
|
-
|
|
67
|
-
### Changed
|
|
68
|
-
|
|
69
|
-
- `logger` is now declared as a dependency (Ruby 4.0 compat).
|
|
70
|
-
|
|
71
|
-
|
|
72
3
|
## [0.9.0] - 2025-03-22
|
|
73
4
|
|
|
74
5
|
## Added
|
|
@@ -80,7 +11,6 @@ This version is for testing the new release process through [Trusted Publishing]
|
|
|
80
11
|
|
|
81
12
|
- Set SA_RESTART flag to reduce EINTRs in profiled code
|
|
82
13
|
|
|
83
|
-
|
|
84
14
|
## [0.8.0] - 2025-01-27
|
|
85
15
|
|
|
86
16
|
## Added
|
|
@@ -89,14 +19,12 @@ This version is for testing the new release process through [Trusted Publishing]
|
|
|
89
19
|
- This serializer is more efficient and has a smaller memory footprint than the default serializer.
|
|
90
20
|
- Ser2 still lacks some features, such as weaving of native stacks.
|
|
91
21
|
|
|
92
|
-
|
|
93
22
|
## [0.7.1] - 2025-01-02
|
|
94
23
|
|
|
95
24
|
### Fixed
|
|
96
25
|
|
|
97
26
|
- Reverted Cargo.lock version to 3 to support older versions of Rust (<1.78).
|
|
98
27
|
|
|
99
|
-
|
|
100
28
|
## [0.7.0] - 2025-01-03
|
|
101
29
|
|
|
102
30
|
### Changed
|
data/README.md
CHANGED
|
@@ -3,11 +3,6 @@ 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
|
-
|
|
9
|
-
NOTE: This README contains some outdated information!
|
|
10
|
-
|
|
11
6
|
Notable Capabilites
|
|
12
7
|
--------
|
|
13
8
|
|
|
@@ -18,25 +13,6 @@ Notable Capabilites
|
|
|
18
13
|
Usage
|
|
19
14
|
--------
|
|
20
15
|
|
|
21
|
-
### Installation
|
|
22
|
-
|
|
23
|
-
You will need a C compiler to build the native extension.
|
|
24
|
-
|
|
25
|
-
Add this line to your application's Gemfile:
|
|
26
|
-
|
|
27
|
-
```ruby
|
|
28
|
-
gem 'pf2'
|
|
29
|
-
|
|
30
|
-
# When using the main branch, specify submodules: true
|
|
31
|
-
gem 'pf2', git: 'https://github.com/osyoyu/pf2.git', submodules: true
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Pf2 can be installed as a standalone CLI tool as well.
|
|
35
|
-
|
|
36
|
-
```console
|
|
37
|
-
gem install pf2
|
|
38
|
-
```
|
|
39
|
-
|
|
40
16
|
### Quickstart
|
|
41
17
|
|
|
42
18
|
Run your Ruby program through `pf2 serve`.
|
data/Rakefile
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
2
|
require 'rake/extensiontask'
|
|
3
3
|
require 'minitest/test_task'
|
|
4
|
-
require 'rdoc/task'
|
|
5
4
|
|
|
6
5
|
task default: %i[]
|
|
7
6
|
|
|
@@ -15,9 +14,4 @@ Minitest::TestTask.create(:test) do |t|
|
|
|
15
14
|
t.libs << "lib"
|
|
16
15
|
t.warning = false
|
|
17
16
|
t.test_globs = ["test/**/*_test.rb"]
|
|
18
|
-
t.extra_args << "--verbose"
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
RDoc::Task.new do |doc|
|
|
22
|
-
doc.rdoc_dir = "_site" # for GitHub pages
|
|
23
17
|
end
|
data/ext/pf2/configuration.c
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
2
|
#include <stdlib.h>
|
|
3
|
-
#include <stdbool.h>
|
|
4
3
|
|
|
5
4
|
#include "configuration.h"
|
|
6
5
|
|
|
7
6
|
static int extract_interval_ms(VALUE options_hash);
|
|
8
7
|
static enum pf2_time_mode extract_time_mode(VALUE options_hash);
|
|
9
|
-
static bool extract__test_no_install_timer(VALUE options_hash);
|
|
10
8
|
|
|
11
9
|
struct pf2_configuration *
|
|
12
10
|
pf2_configuration_new_from_options_hash(VALUE options_hash)
|
|
@@ -18,7 +16,6 @@ pf2_configuration_new_from_options_hash(VALUE options_hash)
|
|
|
18
16
|
|
|
19
17
|
config->interval_ms = extract_interval_ms(options_hash);
|
|
20
18
|
config->time_mode = extract_time_mode(options_hash);
|
|
21
|
-
config->_test_no_install_timer = extract__test_no_install_timer(options_hash);
|
|
22
19
|
|
|
23
20
|
return config;
|
|
24
21
|
}
|
|
@@ -60,17 +57,6 @@ extract_time_mode(VALUE options_hash)
|
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
static bool
|
|
64
|
-
extract__test_no_install_timer(VALUE options_hash)
|
|
65
|
-
{
|
|
66
|
-
if (options_hash == Qnil) {
|
|
67
|
-
return PF2_DEFAULT__TEST_NO_INSTALL_TIMER;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
VALUE _test_no_install_timer = rb_hash_aref(options_hash, ID2SYM(rb_intern("_test_no_install_timer")));
|
|
71
|
-
return RTEST(_test_no_install_timer);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
60
|
void
|
|
75
61
|
pf2_configuration_free(struct pf2_configuration *config)
|
|
76
62
|
{
|
data/ext/pf2/configuration.h
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
#define PF2_CONFIGURATION_H
|
|
3
3
|
|
|
4
4
|
#include <ruby.h>
|
|
5
|
-
#include <stdbool.h>
|
|
6
5
|
|
|
7
6
|
enum pf2_time_mode {
|
|
8
7
|
PF2_TIME_MODE_CPU_TIME,
|
|
@@ -12,12 +11,10 @@ enum pf2_time_mode {
|
|
|
12
11
|
struct pf2_configuration {
|
|
13
12
|
int interval_ms;
|
|
14
13
|
enum pf2_time_mode time_mode;
|
|
15
|
-
bool _test_no_install_timer; // for testing only
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
#define PF2_DEFAULT_INTERVAL_MS 9
|
|
19
17
|
#define PF2_DEFAULT_TIME_MODE PF2_TIME_MODE_CPU_TIME
|
|
20
|
-
#define PF2_DEFAULT__TEST_NO_INSTALL_TIMER false
|
|
21
18
|
|
|
22
19
|
struct pf2_configuration *pf2_configuration_new_from_options_hash(VALUE options_hash);
|
|
23
20
|
void pf2_configuration_free(struct pf2_configuration *config);
|
data/ext/pf2/extconf.rb
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
require 'mkmf'
|
|
2
2
|
require 'mini_portile2'
|
|
3
|
-
require 'fileutils'
|
|
4
|
-
|
|
5
|
-
gem_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
|
6
3
|
|
|
7
4
|
libbacktrace = MiniPortile.new('libbacktrace', '1.0.0')
|
|
8
|
-
libbacktrace.source_directory = File.join(
|
|
9
|
-
libbacktrace.patch_files = Dir.glob(File.join(gem_root, 'ext', 'patches', 'libbacktrace', '*.patch'))
|
|
5
|
+
libbacktrace.source_directory = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'vendor', 'libbacktrace'))
|
|
10
6
|
libbacktrace.configure_options << 'CFLAGS=-fPIC'
|
|
11
|
-
|
|
12
|
-
# Expand 'libbacktrace.cook' to call #patch on source_directory files
|
|
13
|
-
libbacktrace.prepare_build_directory
|
|
14
|
-
# Added: Copy source to build_directory
|
|
15
|
-
build_directory = libbacktrace.send(:work_path)
|
|
16
|
-
FileUtils.cp_r(File.join(libbacktrace.source_directory, '.'), build_directory)
|
|
17
|
-
libbacktrace.patch
|
|
18
|
-
libbacktrace.configure unless libbacktrace.configured?
|
|
19
|
-
libbacktrace.compile
|
|
20
|
-
libbacktrace.install unless libbacktrace.installed?
|
|
21
|
-
# END expand 'libbacktrace.cook'
|
|
22
|
-
|
|
7
|
+
libbacktrace.cook
|
|
23
8
|
libbacktrace.mkmf_config
|
|
24
9
|
|
|
25
10
|
if !have_func('backtrace_full', 'backtrace.h')
|
data/ext/pf2/pf2.c
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
#include "session.h"
|
|
4
4
|
|
|
5
|
-
VALUE
|
|
5
|
+
VALUE rb_mPf2c;
|
|
6
6
|
|
|
7
7
|
RUBY_FUNC_EXPORTED void
|
|
8
8
|
Init_pf2(void)
|
|
9
9
|
{
|
|
10
|
-
|
|
11
|
-
VALUE
|
|
12
|
-
rb_define_alloc_func(
|
|
13
|
-
rb_define_method(
|
|
14
|
-
rb_define_method(
|
|
15
|
-
rb_define_method(
|
|
16
|
-
rb_define_method(
|
|
10
|
+
rb_mPf2c = rb_define_module("Pf2c");
|
|
11
|
+
VALUE rb_mPf2c_cSession = rb_define_class_under(rb_mPf2c, "Session", rb_cObject);
|
|
12
|
+
rb_define_alloc_func(rb_mPf2c_cSession, pf2_session_alloc);
|
|
13
|
+
rb_define_method(rb_mPf2c_cSession, "initialize", rb_pf2_session_initialize, -1);
|
|
14
|
+
rb_define_method(rb_mPf2c_cSession, "start", rb_pf2_session_start, 0);
|
|
15
|
+
rb_define_method(rb_mPf2c_cSession, "stop", rb_pf2_session_stop, 0);
|
|
16
|
+
rb_define_method(rb_mPf2c_cSession, "configuration", rb_pf2_session_configuration, 0);
|
|
17
17
|
}
|
data/ext/pf2/sample.c
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
#include "backtrace_state.h"
|
|
10
10
|
#include "sample.h"
|
|
11
11
|
|
|
12
|
+
const int PF2_SAMPLE_MAX_NATIVE_DEPTH = 300;
|
|
13
|
+
|
|
12
14
|
static int capture_native_backtrace(struct pf2_sample *sample);
|
|
13
15
|
static int backtrace_on_ok(void *data, uintptr_t pc);
|
|
14
16
|
|
|
@@ -27,7 +29,7 @@ pf2_sample_capture(struct pf2_sample *sample)
|
|
|
27
29
|
sample->context_pthread = pthread_self();
|
|
28
30
|
|
|
29
31
|
// Obtain the current stack from Ruby
|
|
30
|
-
sample->depth = rb_profile_frames(0,
|
|
32
|
+
sample->depth = rb_profile_frames(0, 200, sample->cmes, sample->linenos);
|
|
31
33
|
|
|
32
34
|
// Capture C-level backtrace
|
|
33
35
|
sample->native_stack_depth = capture_native_backtrace(sample);
|
data/ext/pf2/sample.h
CHANGED
|
@@ -5,18 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
#include <ruby.h>
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
#define PF2_SAMPLE_MAX_NATIVE_DEPTH 512
|
|
8
|
+
extern const int PF2_SAMPLE_MAX_NATIVE_DEPTH;
|
|
10
9
|
|
|
11
10
|
struct pf2_sample {
|
|
12
11
|
pthread_t context_pthread;
|
|
13
12
|
|
|
14
13
|
int depth;
|
|
15
|
-
VALUE cmes[
|
|
16
|
-
int linenos[
|
|
14
|
+
VALUE cmes[200];
|
|
15
|
+
int linenos[200];
|
|
17
16
|
|
|
18
17
|
size_t native_stack_depth;
|
|
19
|
-
uintptr_t native_stack[
|
|
18
|
+
uintptr_t native_stack[200];
|
|
20
19
|
|
|
21
20
|
uint64_t consumed_time_ns;
|
|
22
21
|
uint64_t timestamp_ns;
|
data/ext/pf2/serializer.c
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#include <time.h>
|
|
2
2
|
#include <stdint.h>
|
|
3
3
|
#include <string.h>
|
|
4
|
-
#include <stdatomic.h>
|
|
5
4
|
|
|
6
5
|
#include <ruby.h>
|
|
7
6
|
#include <ruby/debug.h>
|
|
@@ -17,8 +16,8 @@ static struct pf2_ser_function extract_function_from_ruby_frame(VALUE frame);
|
|
|
17
16
|
static struct pf2_ser_function extract_function_from_native_pc(uintptr_t pc);
|
|
18
17
|
// static int backtrace_pcinfo_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function);
|
|
19
18
|
static void pf2_backtrace_syminfo_callback(void *data, uintptr_t pc, const char *symname, uintptr_t symval, uintptr_t symsize);
|
|
20
|
-
static
|
|
21
|
-
static
|
|
19
|
+
static int function_index_for(struct pf2_ser *serializer, struct pf2_ser_function *function);
|
|
20
|
+
static int location_index_for(struct pf2_ser *serializer, int function_index, int32_t lineno);
|
|
22
21
|
static void ensure_samples_capacity(struct pf2_ser *serializer);
|
|
23
22
|
static void ensure_locations_capacity(struct pf2_ser *serializer);
|
|
24
23
|
static void ensure_functions_capacity(struct pf2_ser *serializer);
|
|
@@ -30,9 +29,6 @@ pf2_ser_new(void) {
|
|
|
30
29
|
ser->start_timestamp_ns = 0;
|
|
31
30
|
ser->duration_ns = 0;
|
|
32
31
|
|
|
33
|
-
ser->collected_sample_count = 0;
|
|
34
|
-
ser->dropped_sample_count = 0;
|
|
35
|
-
|
|
36
32
|
ser->samples = NULL;
|
|
37
33
|
ser->samples_count = 0;
|
|
38
34
|
ser->samples_capacity = 0;
|
|
@@ -80,10 +76,6 @@ pf2_ser_prepare(struct pf2_ser *serializer, struct pf2_session *session) {
|
|
|
80
76
|
(uint64_t)session->start_time_realtime.tv_sec * 1000000000ULL +
|
|
81
77
|
(uint64_t)session->start_time_realtime.tv_nsec;
|
|
82
78
|
serializer->duration_ns = session->duration_ns;
|
|
83
|
-
serializer->collected_sample_count =
|
|
84
|
-
atomic_load_explicit(&session->collected_sample_count, memory_order_relaxed);
|
|
85
|
-
serializer->dropped_sample_count =
|
|
86
|
-
atomic_load_explicit(&session->dropped_sample_count, memory_order_relaxed);
|
|
87
79
|
|
|
88
80
|
// Process samples
|
|
89
81
|
for (size_t i = 0; i < session->samples_index; i++) {
|
|
@@ -91,7 +83,7 @@ pf2_ser_prepare(struct pf2_ser *serializer, struct pf2_session *session) {
|
|
|
91
83
|
ensure_samples_capacity(serializer);
|
|
92
84
|
|
|
93
85
|
struct pf2_ser_sample *ser_sample = &serializer->samples[serializer->samples_count++];
|
|
94
|
-
ser_sample->ruby_thread_id =
|
|
86
|
+
ser_sample->ruby_thread_id = sample->context_pthread;
|
|
95
87
|
ser_sample->elapsed_ns = sample->timestamp_ns - serializer->start_timestamp_ns;
|
|
96
88
|
|
|
97
89
|
// Copy and process Ruby stack frames
|
|
@@ -135,8 +127,6 @@ pf2_ser_to_ruby_hash(struct pf2_ser *serializer) {
|
|
|
135
127
|
// Add metadata
|
|
136
128
|
rb_hash_aset(hash, ID2SYM(rb_intern("start_timestamp_ns")), ULL2NUM(serializer->start_timestamp_ns));
|
|
137
129
|
rb_hash_aset(hash, ID2SYM(rb_intern("duration_ns")), ULL2NUM(serializer->duration_ns));
|
|
138
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("collected_sample_count")), ULL2NUM(serializer->collected_sample_count));
|
|
139
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("dropped_sample_count")), ULL2NUM(serializer->dropped_sample_count));
|
|
140
130
|
|
|
141
131
|
// Add samples
|
|
142
132
|
VALUE samples = rb_ary_new_capa(serializer->samples_count);
|
|
@@ -155,7 +145,7 @@ pf2_ser_to_ruby_hash(struct pf2_ser *serializer) {
|
|
|
155
145
|
VALUE native_stack = rb_ary_new_capa(sample->native_stack_count);
|
|
156
146
|
if (sample->native_stack != NULL) {
|
|
157
147
|
for (size_t j = 0; j < sample->native_stack_count; j++) {
|
|
158
|
-
rb_ary_push(native_stack,
|
|
148
|
+
rb_ary_push(native_stack, ULL2NUM(sample->native_stack[j]));
|
|
159
149
|
}
|
|
160
150
|
}
|
|
161
151
|
rb_hash_aset(sample_hash, ID2SYM(rb_intern("native_stack")), native_stack);
|
|
@@ -164,7 +154,7 @@ pf2_ser_to_ruby_hash(struct pf2_ser *serializer) {
|
|
|
164
154
|
rb_hash_aset(
|
|
165
155
|
sample_hash,
|
|
166
156
|
ID2SYM(rb_intern("ruby_thread_id")),
|
|
167
|
-
sample->ruby_thread_id ?
|
|
157
|
+
sample->ruby_thread_id ? SIZET2NUM(sample->ruby_thread_id) : Qnil
|
|
168
158
|
);
|
|
169
159
|
rb_hash_aset(sample_hash, ID2SYM(rb_intern("elapsed_ns")), ULL2NUM(sample->elapsed_ns));
|
|
170
160
|
|
|
@@ -314,7 +304,7 @@ pf2_backtrace_syminfo_callback(void *data, uintptr_t pc, const char *symname, ui
|
|
|
314
304
|
|
|
315
305
|
// Returns the index of the function in `functions`.
|
|
316
306
|
// Calling this method will modify `serializer->profile` in place.
|
|
317
|
-
static
|
|
307
|
+
static int
|
|
318
308
|
function_index_for(struct pf2_ser *serializer, struct pf2_ser_function *function) {
|
|
319
309
|
for (size_t i = 0; i < serializer->functions_count; i++) {
|
|
320
310
|
struct pf2_ser_function *existing = &serializer->functions[i];
|
|
@@ -342,8 +332,8 @@ function_index_for(struct pf2_ser *serializer, struct pf2_ser_function *function
|
|
|
342
332
|
|
|
343
333
|
// Returns the index of the location in `locations`.
|
|
344
334
|
// Calling this method will modify `self.profile` in place.
|
|
345
|
-
static
|
|
346
|
-
location_index_for(struct pf2_ser *serializer,
|
|
335
|
+
static int
|
|
336
|
+
location_index_for(struct pf2_ser *serializer, int function_index, int32_t lineno) {
|
|
347
337
|
for (size_t i = 0; i < serializer->locations_count; i++) {
|
|
348
338
|
struct pf2_ser_location *existing = &serializer->locations[i];
|
|
349
339
|
if (existing->function_index == function_index && existing->lineno == lineno) {
|
data/ext/pf2/serializer.h
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
#ifndef PF2C_SERIALIZER_H
|
|
2
2
|
#define PF2C_SERIALIZER_H
|
|
3
3
|
|
|
4
|
-
#include <stdint.h>
|
|
5
|
-
|
|
6
4
|
#include <ruby.h>
|
|
7
5
|
|
|
8
6
|
#include "session.h"
|
|
9
7
|
|
|
10
8
|
struct pf2_ser_sample {
|
|
11
|
-
|
|
9
|
+
int *stack;
|
|
12
10
|
size_t stack_count;
|
|
13
|
-
|
|
11
|
+
int *native_stack;
|
|
14
12
|
size_t native_stack_count;
|
|
15
|
-
|
|
13
|
+
size_t ruby_thread_id;
|
|
16
14
|
uint64_t elapsed_ns;
|
|
17
15
|
};
|
|
18
16
|
|
|
19
17
|
struct pf2_ser_location {
|
|
20
|
-
|
|
18
|
+
int function_index;
|
|
21
19
|
int32_t lineno;
|
|
22
20
|
size_t address;
|
|
23
21
|
};
|
|
@@ -38,8 +36,6 @@ struct pf2_ser_function {
|
|
|
38
36
|
struct pf2_ser {
|
|
39
37
|
uint64_t start_timestamp_ns;
|
|
40
38
|
uint64_t duration_ns;
|
|
41
|
-
uint64_t collected_sample_count;
|
|
42
|
-
uint64_t dropped_sample_count;
|
|
43
39
|
|
|
44
40
|
struct pf2_ser_sample *samples;
|
|
45
41
|
size_t samples_count;
|
data/ext/pf2/session.c
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#include <bits/time.h>
|
|
1
2
|
#include <pthread.h>
|
|
2
3
|
#include <signal.h>
|
|
3
4
|
#include <stdatomic.h>
|
|
@@ -19,14 +20,14 @@
|
|
|
19
20
|
#include "session.h"
|
|
20
21
|
#include "serializer.h"
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
#ifndef HAVE_TIMER_CREATE
|
|
24
|
+
// Global session pointer for setitimer fallback
|
|
23
25
|
static struct pf2_session *global_current_session = NULL;
|
|
26
|
+
#endif
|
|
24
27
|
|
|
25
28
|
static void *sample_collector_thread(void *arg);
|
|
26
|
-
static void drain_ringbuffer(struct pf2_session *session);
|
|
27
29
|
static void sigprof_handler(int sig, siginfo_t *info, void *ucontext);
|
|
28
30
|
bool ensure_sample_capacity(struct pf2_session *session);
|
|
29
|
-
static void pf2_session_stop(struct pf2_session *session);
|
|
30
31
|
|
|
31
32
|
VALUE
|
|
32
33
|
rb_pf2_session_initialize(int argc, VALUE *argv, VALUE self)
|
|
@@ -39,11 +40,10 @@ rb_pf2_session_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
39
40
|
rb_scan_args(argc, argv, ":", &kwargs);
|
|
40
41
|
ID kwarg_labels[] = {
|
|
41
42
|
rb_intern("interval_ms"),
|
|
42
|
-
rb_intern("time_mode")
|
|
43
|
-
rb_intern("_test_no_install_timer")
|
|
43
|
+
rb_intern("time_mode")
|
|
44
44
|
};
|
|
45
45
|
VALUE *kwarg_values = NULL;
|
|
46
|
-
rb_get_kwargs(kwargs, kwarg_labels, 0,
|
|
46
|
+
rb_get_kwargs(kwargs, kwarg_labels, 0, 2, kwarg_values);
|
|
47
47
|
|
|
48
48
|
session->configuration = pf2_configuration_new_from_options_hash(kwargs);
|
|
49
49
|
|
|
@@ -56,9 +56,6 @@ rb_pf2_session_start(VALUE self)
|
|
|
56
56
|
struct pf2_session *session;
|
|
57
57
|
TypedData_Get_Struct(self, struct pf2_session, &pf2_session_type, session);
|
|
58
58
|
|
|
59
|
-
// Store pointer to current session for access from signal handlers
|
|
60
|
-
global_current_session = session;
|
|
61
|
-
|
|
62
59
|
session->is_running = true;
|
|
63
60
|
|
|
64
61
|
// Record start time
|
|
@@ -90,60 +87,58 @@ rb_pf2_session_start(VALUE self)
|
|
|
90
87
|
}
|
|
91
88
|
#endif
|
|
92
89
|
|
|
93
|
-
global_current_session = session;
|
|
94
|
-
|
|
95
|
-
if (!session->configuration->_test_no_install_timer) {
|
|
96
90
|
#ifdef HAVE_TIMER_CREATE
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
91
|
+
// Configure a kernel timer to send SIGPROF periodically
|
|
92
|
+
struct sigevent sev;
|
|
93
|
+
sev.sigev_notify = SIGEV_SIGNAL;
|
|
94
|
+
sev.sigev_signo = SIGPROF;
|
|
95
|
+
sev.sigev_value.sival_ptr = session; // Passed as info->si_value.sival_ptr
|
|
96
|
+
if (timer_create(
|
|
97
|
+
session->configuration->time_mode == PF2_TIME_MODE_CPU_TIME
|
|
98
|
+
? CLOCK_PROCESS_CPUTIME_ID
|
|
99
|
+
: CLOCK_MONOTONIC,
|
|
100
|
+
&sev,
|
|
101
|
+
&session->timer
|
|
102
|
+
) == -1) {
|
|
103
|
+
rb_raise(rb_eRuntimeError, "Failed to create timer");
|
|
104
|
+
}
|
|
105
|
+
struct itimerspec its = {
|
|
106
|
+
.it_value = {
|
|
107
|
+
.tv_sec = 0,
|
|
108
|
+
.tv_nsec = session->configuration->interval_ms * 1000000,
|
|
109
|
+
},
|
|
110
|
+
.it_interval = {
|
|
111
|
+
.tv_sec = 0,
|
|
112
|
+
.tv_nsec = session->configuration->interval_ms * 1000000,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
if (timer_settime(session->timer, 0, &its, NULL) == -1) {
|
|
116
|
+
rb_raise(rb_eRuntimeError, "Failed to start timer");
|
|
117
|
+
}
|
|
123
118
|
#else
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|
|
145
141
|
#endif
|
|
146
|
-
} // if !__test_no_install_timer
|
|
147
142
|
|
|
148
143
|
return Qtrue;
|
|
149
144
|
}
|
|
@@ -155,7 +150,17 @@ sample_collector_thread(void *arg)
|
|
|
155
150
|
|
|
156
151
|
while (session->is_running == true) {
|
|
157
152
|
// Take samples from the ring buffer
|
|
158
|
-
|
|
153
|
+
struct pf2_sample sample;
|
|
154
|
+
while (pf2_ringbuffer_pop(session->rbuf, &sample) == true) {
|
|
155
|
+
// Ensure we have capacity before adding a new sample
|
|
156
|
+
if (!ensure_sample_capacity(session)) {
|
|
157
|
+
// Failed to expand buffer
|
|
158
|
+
PF2_DEBUG_LOG("Failed to expand sample buffer. Dropping sample\n");
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
session->samples[session->samples_index++] = sample;
|
|
163
|
+
}
|
|
159
164
|
|
|
160
165
|
// Sleep for 100 ms
|
|
161
166
|
// TODO: Replace with high watermark callback
|
|
@@ -166,24 +171,6 @@ sample_collector_thread(void *arg)
|
|
|
166
171
|
return NULL;
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
static void
|
|
170
|
-
drain_ringbuffer(struct pf2_session *session)
|
|
171
|
-
{
|
|
172
|
-
struct pf2_sample sample;
|
|
173
|
-
while (pf2_ringbuffer_pop(session->rbuf, &sample) == true) {
|
|
174
|
-
// Ensure we have capacity before adding a new sample
|
|
175
|
-
if (!ensure_sample_capacity(session)) {
|
|
176
|
-
// Failed to expand buffer
|
|
177
|
-
atomic_fetch_add_explicit(&session->dropped_sample_count, 1, memory_order_relaxed);
|
|
178
|
-
PF2_DEBUG_LOG("Failed to expand sample buffer. Dropping sample\n");
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
session->samples[session->samples_index++] = sample;
|
|
183
|
-
atomic_fetch_add_explicit(&session->collected_sample_count, 1, memory_order_relaxed);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
174
|
// async-signal-safe
|
|
188
175
|
static void
|
|
189
176
|
sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
|
@@ -193,12 +180,16 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
|
|
193
180
|
clock_gettime(CLOCK_MONOTONIC, &sig_start_time);
|
|
194
181
|
#endif
|
|
195
182
|
|
|
196
|
-
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
|
|
197
189
|
|
|
198
190
|
// If garbage collection is in progress, don't collect samples.
|
|
199
191
|
if (atomic_load_explicit(&session->is_marking, memory_order_acquire)) {
|
|
200
192
|
PF2_DEBUG_LOG("Dropping sample: Garbage collection is in progress\n");
|
|
201
|
-
atomic_fetch_add_explicit(&session->dropped_sample_count, 1, memory_order_relaxed);
|
|
202
193
|
return;
|
|
203
194
|
}
|
|
204
195
|
|
|
@@ -206,7 +197,6 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
|
|
206
197
|
|
|
207
198
|
if (pf2_sample_capture(&sample) == false) {
|
|
208
199
|
PF2_DEBUG_LOG("Dropping sample: Failed to capture sample\n");
|
|
209
|
-
atomic_fetch_add_explicit(&session->dropped_sample_count, 1, memory_order_relaxed);
|
|
210
200
|
return;
|
|
211
201
|
}
|
|
212
202
|
|
|
@@ -214,7 +204,6 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
|
|
|
214
204
|
if (pf2_ringbuffer_push(session->rbuf, &sample) == false) {
|
|
215
205
|
// Copy failed. The sample buffer is full.
|
|
216
206
|
PF2_DEBUG_LOG("Dropping sample: Sample buffer is full\n");
|
|
217
|
-
atomic_fetch_add_explicit(&session->dropped_sample_count, 1, memory_order_relaxed);
|
|
218
207
|
return;
|
|
219
208
|
}
|
|
220
209
|
|
|
@@ -262,20 +251,6 @@ rb_pf2_session_stop(VALUE self)
|
|
|
262
251
|
struct pf2_session *session;
|
|
263
252
|
TypedData_Get_Struct(self, struct pf2_session, &pf2_session_type, session);
|
|
264
253
|
|
|
265
|
-
pf2_session_stop(session);
|
|
266
|
-
|
|
267
|
-
// Create serializer and serialize
|
|
268
|
-
struct pf2_ser *serializer = pf2_ser_new();
|
|
269
|
-
pf2_ser_prepare(serializer, session);
|
|
270
|
-
VALUE result = pf2_ser_to_ruby_hash(serializer);
|
|
271
|
-
pf2_ser_free(serializer);
|
|
272
|
-
|
|
273
|
-
return result;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
static void
|
|
277
|
-
pf2_session_stop(struct pf2_session *session)
|
|
278
|
-
{
|
|
279
254
|
// Calculate duration
|
|
280
255
|
struct timespec end_time;
|
|
281
256
|
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
|
@@ -285,10 +260,8 @@ pf2_session_stop(struct pf2_session *session)
|
|
|
285
260
|
|
|
286
261
|
// Disarm and delete the timer.
|
|
287
262
|
#ifdef HAVE_TIMER_CREATE
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
rb_raise(rb_eRuntimeError, "Failed to delete timer");
|
|
291
|
-
}
|
|
263
|
+
if (timer_delete(session->timer) == -1) {
|
|
264
|
+
rb_raise(rb_eRuntimeError, "Failed to delete timer");
|
|
292
265
|
}
|
|
293
266
|
#else
|
|
294
267
|
struct itimerval zero_timer = {{0, 0}, {0, 0}};
|
|
@@ -304,7 +277,14 @@ pf2_session_stop(struct pf2_session *session)
|
|
|
304
277
|
// Terminate the collector thread
|
|
305
278
|
session->is_running = false;
|
|
306
279
|
pthread_join(*session->collector_thread, NULL);
|
|
307
|
-
|
|
280
|
+
|
|
281
|
+
// Create serializer and serialize
|
|
282
|
+
struct pf2_ser *serializer = pf2_ser_new();
|
|
283
|
+
pf2_ser_prepare(serializer, session);
|
|
284
|
+
VALUE result = pf2_ser_to_ruby_hash(serializer);
|
|
285
|
+
pf2_ser_free(serializer);
|
|
286
|
+
|
|
287
|
+
return result;
|
|
308
288
|
}
|
|
309
289
|
|
|
310
290
|
VALUE
|
|
@@ -320,7 +300,7 @@ pf2_session_alloc(VALUE self)
|
|
|
320
300
|
{
|
|
321
301
|
// Initialize state for libbacktrace
|
|
322
302
|
if (global_backtrace_state == NULL) {
|
|
323
|
-
global_backtrace_state = backtrace_create_state(
|
|
303
|
+
global_backtrace_state = backtrace_create_state("pf2", 1, pf2_backtrace_print_error, NULL);
|
|
324
304
|
if (global_backtrace_state == NULL) {
|
|
325
305
|
rb_raise(rb_eRuntimeError, "Failed to initialize libbacktrace");
|
|
326
306
|
}
|
|
@@ -331,32 +311,19 @@ pf2_session_alloc(VALUE self)
|
|
|
331
311
|
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
332
312
|
}
|
|
333
313
|
|
|
334
|
-
// is_running
|
|
335
|
-
session->is_running = false;
|
|
336
|
-
|
|
337
|
-
// timer
|
|
338
|
-
#ifdef HAVE_TIMER_CREATE
|
|
339
|
-
session->timer = (timer_t)0;
|
|
340
|
-
#else
|
|
341
|
-
session->timer = (struct itimerval){0};
|
|
342
|
-
#endif
|
|
343
|
-
|
|
344
|
-
// rbuf
|
|
345
314
|
session->rbuf = pf2_ringbuffer_new(1000);
|
|
346
315
|
if (session->rbuf == NULL) {
|
|
347
316
|
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
348
317
|
}
|
|
349
318
|
|
|
350
|
-
// is_marking
|
|
351
319
|
atomic_store_explicit(&session->is_marking, false, memory_order_relaxed);
|
|
352
|
-
|
|
353
|
-
// collector_thread
|
|
354
320
|
session->collector_thread = malloc(sizeof(pthread_t));
|
|
355
321
|
if (session->collector_thread == NULL) {
|
|
356
322
|
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
357
323
|
}
|
|
358
324
|
|
|
359
|
-
|
|
325
|
+
session->duration_ns = 0;
|
|
326
|
+
|
|
360
327
|
session->samples_index = 0;
|
|
361
328
|
session->samples_capacity = 500; // 10 seconds worth of samples at 50 Hz
|
|
362
329
|
session->samples = malloc(sizeof(struct pf2_sample) * session->samples_capacity);
|
|
@@ -364,18 +331,6 @@ pf2_session_alloc(VALUE self)
|
|
|
364
331
|
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
365
332
|
}
|
|
366
333
|
|
|
367
|
-
// collected_sample_count, dropped_sample_count
|
|
368
|
-
atomic_store_explicit(&session->collected_sample_count, 0, memory_order_relaxed);
|
|
369
|
-
atomic_store_explicit(&session->dropped_sample_count, 0, memory_order_relaxed);
|
|
370
|
-
|
|
371
|
-
// start_time_realtime, start_time
|
|
372
|
-
session->start_time_realtime = (struct timespec){0};
|
|
373
|
-
session->start_time = (struct timespec){0};
|
|
374
|
-
|
|
375
|
-
// duration_ns
|
|
376
|
-
session->duration_ns = 0;
|
|
377
|
-
|
|
378
|
-
// configuration
|
|
379
334
|
session->configuration = NULL;
|
|
380
335
|
|
|
381
336
|
return TypedData_Wrap_Struct(self, &pf2_session_type, session);
|
|
@@ -418,15 +373,8 @@ pf2_session_dmark(void *sess)
|
|
|
418
373
|
void
|
|
419
374
|
pf2_session_dfree(void *sess)
|
|
420
375
|
{
|
|
376
|
+
// TODO: Ensure the uninstall process is complete before freeing the session
|
|
421
377
|
struct pf2_session *session = sess;
|
|
422
|
-
|
|
423
|
-
assert(session->is_running == false || session->is_running == true);
|
|
424
|
-
|
|
425
|
-
// Stop the session if it's still running
|
|
426
|
-
if (session->is_running) {
|
|
427
|
-
pf2_session_stop(session);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
378
|
pf2_configuration_free(session->configuration);
|
|
431
379
|
pf2_ringbuffer_free(session->rbuf);
|
|
432
380
|
free(session->samples);
|
data/ext/pf2/session.h
CHANGED
|
@@ -30,9 +30,6 @@ struct pf2_session {
|
|
|
30
30
|
struct timespec start_time; // When profiling started
|
|
31
31
|
uint64_t duration_ns; // Duration of profiling in nanoseconds
|
|
32
32
|
|
|
33
|
-
atomic_uint_fast64_t collected_sample_count; // Number of samples copied out of the ringbuffer
|
|
34
|
-
atomic_uint_fast64_t dropped_sample_count; // Number of samples dropped for any reason
|
|
35
|
-
|
|
36
33
|
struct pf2_configuration *configuration;
|
|
37
34
|
};
|
|
38
35
|
|
|
@@ -46,14 +43,14 @@ void pf2_session_dfree(void *sess);
|
|
|
46
43
|
size_t pf2_session_dsize(const void *sess);
|
|
47
44
|
|
|
48
45
|
static const rb_data_type_t pf2_session_type = {
|
|
49
|
-
.wrap_struct_name = "
|
|
46
|
+
.wrap_struct_name = "Pf2c::Session",
|
|
50
47
|
.function = {
|
|
51
48
|
.dmark = pf2_session_dmark,
|
|
52
49
|
.dfree = pf2_session_dfree,
|
|
53
50
|
.dsize = pf2_session_dsize,
|
|
54
51
|
},
|
|
55
52
|
.data = NULL,
|
|
56
|
-
.flags =
|
|
53
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
57
54
|
};
|
|
58
55
|
|
|
59
56
|
#endif // PF2_SESSION_H
|
data/lib/pf2/cli.rb
CHANGED
data/lib/pf2/session.rb
ADDED
data/lib/pf2/version.rb
CHANGED
data/lib/pf2.rb
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'pf2/pf2'
|
|
4
|
+
require_relative 'pf2/session'
|
|
4
5
|
require_relative 'pf2/version'
|
|
5
6
|
|
|
6
7
|
module Pf2
|
|
7
8
|
class Error < StandardError; end
|
|
8
9
|
|
|
9
10
|
def self.start(...)
|
|
10
|
-
@@session = Session.new(...)
|
|
11
|
+
@@session = Pf2c::Session.new(...)
|
|
11
12
|
@@session.start
|
|
12
13
|
end
|
|
13
14
|
|
|
@@ -15,17 +16,9 @@ module Pf2
|
|
|
15
16
|
@@session.stop
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
# Example:
|
|
21
|
-
#
|
|
22
|
-
# profile = Pf2.profile(interval_ms: 42) do
|
|
23
|
-
# your_code_here
|
|
24
|
-
# end
|
|
25
|
-
#
|
|
26
|
-
def self.profile(**kwargs, &block)
|
|
19
|
+
def self.profile(&block)
|
|
27
20
|
raise ArgumentError, "block required" unless block_given?
|
|
28
|
-
start(
|
|
21
|
+
start(threads: Thread.list)
|
|
29
22
|
yield
|
|
30
23
|
result = stop
|
|
31
24
|
@@session = nil # let GC clean up the session
|
|
@@ -812,7 +812,6 @@ enable_darwin_at_rpath
|
|
|
812
812
|
enable_largefile
|
|
813
813
|
enable_werror
|
|
814
814
|
with_system_libunwind
|
|
815
|
-
enable_host_pie
|
|
816
815
|
enable_host_shared
|
|
817
816
|
'
|
|
818
817
|
ac_precious_vars='build_alias
|
|
@@ -1462,7 +1461,6 @@ Optional Features:
|
|
|
1462
1461
|
rpaths to be added to executables
|
|
1463
1462
|
--disable-largefile omit support for large files
|
|
1464
1463
|
--disable-werror disable building with -Werror
|
|
1465
|
-
--enable-host-pie build host code as PIE
|
|
1466
1464
|
--enable-host-shared build host code as shared libraries
|
|
1467
1465
|
|
|
1468
1466
|
Optional Packages:
|
|
@@ -11397,7 +11395,7 @@ else
|
|
|
11397
11395
|
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
|
11398
11396
|
lt_status=$lt_dlunknown
|
|
11399
11397
|
cat > conftest.$ac_ext <<_LT_EOF
|
|
11400
|
-
#line
|
|
11398
|
+
#line 11398 "configure"
|
|
11401
11399
|
#include "confdefs.h"
|
|
11402
11400
|
|
|
11403
11401
|
#if HAVE_DLFCN_H
|
|
@@ -11503,7 +11501,7 @@ else
|
|
|
11503
11501
|
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
|
11504
11502
|
lt_status=$lt_dlunknown
|
|
11505
11503
|
cat > conftest.$ac_ext <<_LT_EOF
|
|
11506
|
-
#line
|
|
11504
|
+
#line 11504 "configure"
|
|
11507
11505
|
#include "confdefs.h"
|
|
11508
11506
|
|
|
11509
11507
|
#if HAVE_DLFCN_H
|
|
@@ -12192,18 +12190,12 @@ $as_echo "#define HAVE_GETIPINFO 1" >>confdefs.h
|
|
|
12192
12190
|
fi
|
|
12193
12191
|
fi
|
|
12194
12192
|
|
|
12195
|
-
# Enable --enable-host-pie.
|
|
12196
|
-
# Check whether --enable-host-pie was given.
|
|
12197
|
-
if test "${enable_host_pie+set}" = set; then :
|
|
12198
|
-
enableval=$enable_host_pie; PIC_FLAG=-fPIE
|
|
12199
|
-
else
|
|
12200
|
-
PIC_FLAG=
|
|
12201
|
-
fi
|
|
12202
|
-
|
|
12203
12193
|
# Enable --enable-host-shared.
|
|
12204
12194
|
# Check whether --enable-host-shared was given.
|
|
12205
12195
|
if test "${enable_host_shared+set}" = set; then :
|
|
12206
12196
|
enableval=$enable_host_shared; PIC_FLAG=-fPIC
|
|
12197
|
+
else
|
|
12198
|
+
PIC_FLAG=
|
|
12207
12199
|
fi
|
|
12208
12200
|
|
|
12209
12201
|
|
|
@@ -176,16 +176,11 @@ else
|
|
|
176
176
|
fi
|
|
177
177
|
fi
|
|
178
178
|
|
|
179
|
-
# Enable --enable-host-pie.
|
|
180
|
-
AC_ARG_ENABLE(host-pie,
|
|
181
|
-
[AS_HELP_STRING([--enable-host-pie],
|
|
182
|
-
[build host code as PIE])],
|
|
183
|
-
[PIC_FLAG=-fPIE], [PIC_FLAG=])
|
|
184
179
|
# Enable --enable-host-shared.
|
|
185
180
|
AC_ARG_ENABLE(host-shared,
|
|
186
181
|
[AS_HELP_STRING([--enable-host-shared],
|
|
187
182
|
[build host code as shared libraries])],
|
|
188
|
-
[PIC_FLAG=-fPIC])
|
|
183
|
+
[PIC_FLAG=-fPIC], [PIC_FLAG=])
|
|
189
184
|
AC_SUBST(PIC_FLAG)
|
|
190
185
|
|
|
191
186
|
# Test for __sync support.
|
data/vendor/libbacktrace/elf.c
CHANGED
|
@@ -160,10 +160,10 @@ dl_iterate_phdr (int (*callback) (struct dl_phdr_info *,
|
|
|
160
160
|
#undef EI_CLASS
|
|
161
161
|
#undef EI_DATA
|
|
162
162
|
#undef EI_VERSION
|
|
163
|
-
#undef
|
|
164
|
-
#undef
|
|
165
|
-
#undef
|
|
166
|
-
#undef
|
|
163
|
+
#undef ELF_MAG0
|
|
164
|
+
#undef ELF_MAG1
|
|
165
|
+
#undef ELF_MAG2
|
|
166
|
+
#undef ELF_MAG3
|
|
167
167
|
#undef ELFCLASS32
|
|
168
168
|
#undef ELFCLASS64
|
|
169
169
|
#undef ELFDATA2LSB
|
|
@@ -47,10 +47,6 @@ POSSIBILITY OF SUCH DAMAGE. */
|
|
|
47
47
|
#include <mach-o/dyld.h>
|
|
48
48
|
#endif
|
|
49
49
|
|
|
50
|
-
#ifdef __hpux__
|
|
51
|
-
#include <dl.h>
|
|
52
|
-
#endif
|
|
53
|
-
|
|
54
50
|
#ifdef HAVE_WINDOWS_H
|
|
55
51
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
56
52
|
#define WIN32_LEAN_AND_MEAN
|
|
@@ -70,33 +66,6 @@ POSSIBILITY OF SUCH DAMAGE. */
|
|
|
70
66
|
#define getexecname() NULL
|
|
71
67
|
#endif
|
|
72
68
|
|
|
73
|
-
#ifdef __hpux__
|
|
74
|
-
static char *
|
|
75
|
-
hpux_get_executable_path (struct backtrace_state *state,
|
|
76
|
-
backtrace_error_callback error_callback, void *data)
|
|
77
|
-
{
|
|
78
|
-
struct shl_descriptor *desc;
|
|
79
|
-
size_t len = sizeof (struct shl_descriptor);
|
|
80
|
-
|
|
81
|
-
desc = backtrace_alloc (state, len, error_callback, data);
|
|
82
|
-
if (desc == NULL)
|
|
83
|
-
return NULL;
|
|
84
|
-
|
|
85
|
-
if (shl_get_r (0, desc) == -1)
|
|
86
|
-
{
|
|
87
|
-
backtrace_free (state, desc, len, error_callback, data);
|
|
88
|
-
return NULL;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return desc->filename;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
#else
|
|
95
|
-
|
|
96
|
-
#define hpux_get_executable_path(state, error_callback, data) NULL
|
|
97
|
-
|
|
98
|
-
#endif
|
|
99
|
-
|
|
100
69
|
#if !defined (HAVE_KERN_PROC_ARGS) && !defined (HAVE_KERN_PROC)
|
|
101
70
|
|
|
102
71
|
#define sysctl_exec_name1(state, error_callback, data) NULL
|
|
@@ -276,7 +245,7 @@ fileline_initialize (struct backtrace_state *state,
|
|
|
276
245
|
|
|
277
246
|
descriptor = -1;
|
|
278
247
|
called_error_callback = 0;
|
|
279
|
-
for (pass = 0; pass <
|
|
248
|
+
for (pass = 0; pass < 10; ++pass)
|
|
280
249
|
{
|
|
281
250
|
int does_not_exist;
|
|
282
251
|
|
|
@@ -316,9 +285,6 @@ fileline_initialize (struct backtrace_state *state,
|
|
|
316
285
|
case 9:
|
|
317
286
|
filename = windows_get_executable_path (buf, error_callback, data);
|
|
318
287
|
break;
|
|
319
|
-
case 10:
|
|
320
|
-
filename = hpux_get_executable_path (state, error_callback, data);
|
|
321
|
-
break;
|
|
322
288
|
default:
|
|
323
289
|
abort ();
|
|
324
290
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
/^\177ELF\002/ { if (NR == 1) { print "elf64"; exit } }
|
|
4
4
|
/^\114\001/ { if (NR == 1) { print "pecoff"; exit } }
|
|
5
5
|
/^\144\206/ { if (NR == 1) { print "pecoff"; exit } }
|
|
6
|
-
/^\000\000\377\377/ { if (NR == 1) { print "pecoff"; exit } }
|
|
7
6
|
/^\001\337/ { if (NR == 1) { print "xcoff32"; exit } }
|
|
8
7
|
/^\001\367/ { if (NR == 1) { print "xcoff64"; exit } }
|
|
9
8
|
/^\376\355\372\316/ { if (NR == 1) { print "macho"; exit } }
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pf2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0.alpha1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daisuke Aritomo
|
|
@@ -9,20 +9,6 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: logger
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: rake-compiler
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -93,20 +79,6 @@ dependencies:
|
|
|
93
79
|
- - ">="
|
|
94
80
|
- !ruby/object:Gem::Version
|
|
95
81
|
version: '0'
|
|
96
|
-
- !ruby/object:Gem::Dependency
|
|
97
|
-
name: rdoc
|
|
98
|
-
requirement: !ruby/object:Gem::Requirement
|
|
99
|
-
requirements:
|
|
100
|
-
- - ">="
|
|
101
|
-
- !ruby/object:Gem::Version
|
|
102
|
-
version: '0'
|
|
103
|
-
type: :development
|
|
104
|
-
prerelease: false
|
|
105
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
-
requirements:
|
|
107
|
-
- - ">="
|
|
108
|
-
- !ruby/object:Gem::Version
|
|
109
|
-
version: '0'
|
|
110
82
|
email:
|
|
111
83
|
- osyoyu@osyoyu.com
|
|
112
84
|
executables:
|
|
@@ -115,8 +87,6 @@ extensions:
|
|
|
115
87
|
- ext/pf2/extconf.rb
|
|
116
88
|
extra_rdoc_files: []
|
|
117
89
|
files:
|
|
118
|
-
- ".document"
|
|
119
|
-
- ".rdoc_options"
|
|
120
90
|
- CHANGELOG.md
|
|
121
91
|
- LICENSE.txt
|
|
122
92
|
- README.md
|
|
@@ -125,7 +95,6 @@ files:
|
|
|
125
95
|
- examples/mandelbrot.rb
|
|
126
96
|
- examples/mandelbrot_ractor.rb
|
|
127
97
|
- exe/pf2
|
|
128
|
-
- ext/patches/libbacktrace/0001-Support-MACH_O_MH_BUNDLE.patch
|
|
129
98
|
- ext/pf2/backtrace_state.c
|
|
130
99
|
- ext/pf2/backtrace_state.h
|
|
131
100
|
- ext/pf2/configuration.c
|
|
@@ -149,6 +118,7 @@ files:
|
|
|
149
118
|
- lib/pf2/reporter/firefox_profiler_ser2.rb
|
|
150
119
|
- lib/pf2/reporter/stack_weaver.rb
|
|
151
120
|
- lib/pf2/serve.rb
|
|
121
|
+
- lib/pf2/session.rb
|
|
152
122
|
- lib/pf2/version.rb
|
|
153
123
|
- vendor/libbacktrace/.gitignore
|
|
154
124
|
- vendor/libbacktrace/Isaac.Newton-Opticks.txt
|
|
@@ -242,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
242
212
|
- !ruby/object:Gem::Version
|
|
243
213
|
version: '0'
|
|
244
214
|
requirements: []
|
|
245
|
-
rubygems_version:
|
|
215
|
+
rubygems_version: 3.8.0.dev
|
|
246
216
|
specification_version: 4
|
|
247
217
|
summary: Yet another Ruby profiler
|
|
248
218
|
test_files: []
|
data/.document
DELETED
data/.rdoc_options
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
From 4cd047583bc48ad0617fb6c036174de062573e68 Mon Sep 17 00:00:00 2001
|
|
2
|
-
From: Daisuke Aritomo <osyoyu@osyoyu.com>
|
|
3
|
-
Date: Wed, 7 Jan 2026 02:50:54 +0900
|
|
4
|
-
Subject: [PATCH] Support MACH_O_MH_BUNDLE
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
macho.c | 2 ++
|
|
8
|
-
1 file changed, 2 insertions(+)
|
|
9
|
-
|
|
10
|
-
diff --git a/macho.c b/macho.c
|
|
11
|
-
index 9f8738d..5ea07ae 100644
|
|
12
|
-
--- a/macho.c
|
|
13
|
-
+++ b/macho.c
|
|
14
|
-
@@ -92,6 +92,7 @@ struct macho_header_fat
|
|
15
|
-
|
|
16
|
-
#define MACH_O_MH_EXECUTE 0x02
|
|
17
|
-
#define MACH_O_MH_DYLIB 0x06
|
|
18
|
-
+#define MACH_O_MH_BUNDLE 0x08
|
|
19
|
-
#define MACH_O_MH_DSYM 0x0a
|
|
20
|
-
|
|
21
|
-
/* A component of a fat file. A fat file starts with a
|
|
22
|
-
@@ -1062,6 +1063,7 @@ macho_add (struct backtrace_state *state, const char *filename, int descriptor,
|
|
23
|
-
{
|
|
24
|
-
case MACH_O_MH_EXECUTE:
|
|
25
|
-
case MACH_O_MH_DYLIB:
|
|
26
|
-
+ case MACH_O_MH_BUNDLE:
|
|
27
|
-
case MACH_O_MH_DSYM:
|
|
28
|
-
break;
|
|
29
|
-
default:
|
|
30
|
-
--
|
|
31
|
-
2.39.5 (Apple Git-154)
|
|
32
|
-
|