pf2 0.11.3 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86fd284a999c583522ded7f75ac859162e5f125cf699f5a8eba8a3fac968874b
4
- data.tar.gz: ae12d4bf742da3ede82a4e4c3229410ab8a06ef24b020e9d2232f923d64c2c70
3
+ metadata.gz: ef67bdd8a362c1f5cd3dd37a47e13b64f5ee0663a204908d0f075d41acc94da3
4
+ data.tar.gz: 535d1fe8efb9a408ce47218910d6ad3ddfe88c2a4e4f42ac42c01ab8dceceacd
5
5
  SHA512:
6
- metadata.gz: f0d7cc6bf5436ca522e00ec2eabe31a00030df584365b4a3cf5847d7b1d48d287304520e85884635373c025237562951e80e45e121250c72437e27db88687547
7
- data.tar.gz: 19cb2e6fcb953134cde12e45dbc861129cd25c325ad3f44492e5808dbb7a10e2acb386e8d8d8041fe4a4a773bd9f8538989b1f9bbfd8bb6b4aedda3b4c4db8ea
6
+ metadata.gz: 15a65711f8c1a804b06d8b2195a9e620646972b7905a714fdab67561f9d796d7c92a1f332f9169ffa27c14b6b91d9f33ba254d84d47c281f66f7a069cc660d58
7
+ data.tar.gz: 411bdf85ef4db94bed73840224132498621f389e4f6092a45e222bf5834dd5d1e6aa6940cafcb56cdc86148163d1b9bda4eee45501cc9c307ce729894954613d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
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
+
3
25
  ## [0.11.3] - 2025-12-28
4
26
 
5
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.
data/README.md CHANGED
@@ -6,6 +6,8 @@ A experimental sampling-based profiler for Ruby 3.3+.
6
6
  - GitHub: https://github.com/osyoyu/pf2
7
7
  - Documentation: https://osyoyu.github.io/pf2/
8
8
 
9
+ NOTE: This README contains some outdated information!
10
+
9
11
  Notable Capabilites
10
12
  --------
11
13
 
data/Rakefile CHANGED
@@ -15,6 +15,7 @@ Minitest::TestTask.create(:test) do |t|
15
15
  t.libs << "lib"
16
16
  t.warning = false
17
17
  t.test_globs = ["test/**/*_test.rb"]
18
+ t.extra_args << "--verbose"
18
19
  end
19
20
 
20
21
  RDoc::Task.new do |doc|
@@ -0,0 +1,32 @@
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
+
@@ -1,10 +1,12 @@
1
1
  #include <ruby.h>
2
2
  #include <stdlib.h>
3
+ #include <stdbool.h>
3
4
 
4
5
  #include "configuration.h"
5
6
 
6
7
  static int extract_interval_ms(VALUE options_hash);
7
8
  static enum pf2_time_mode extract_time_mode(VALUE options_hash);
9
+ static bool extract__test_no_install_timer(VALUE options_hash);
8
10
 
9
11
  struct pf2_configuration *
10
12
  pf2_configuration_new_from_options_hash(VALUE options_hash)
@@ -16,6 +18,7 @@ pf2_configuration_new_from_options_hash(VALUE options_hash)
16
18
 
17
19
  config->interval_ms = extract_interval_ms(options_hash);
18
20
  config->time_mode = extract_time_mode(options_hash);
21
+ config->_test_no_install_timer = extract__test_no_install_timer(options_hash);
19
22
 
20
23
  return config;
21
24
  }
@@ -57,6 +60,17 @@ extract_time_mode(VALUE options_hash)
57
60
  }
58
61
  }
59
62
 
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
+
60
74
  void
61
75
  pf2_configuration_free(struct pf2_configuration *config)
62
76
  {
@@ -2,6 +2,7 @@
2
2
  #define PF2_CONFIGURATION_H
3
3
 
4
4
  #include <ruby.h>
5
+ #include <stdbool.h>
5
6
 
6
7
  enum pf2_time_mode {
7
8
  PF2_TIME_MODE_CPU_TIME,
@@ -11,10 +12,12 @@ enum pf2_time_mode {
11
12
  struct pf2_configuration {
12
13
  int interval_ms;
13
14
  enum pf2_time_mode time_mode;
15
+ bool _test_no_install_timer; // for testing only
14
16
  };
15
17
 
16
18
  #define PF2_DEFAULT_INTERVAL_MS 9
17
19
  #define PF2_DEFAULT_TIME_MODE PF2_TIME_MODE_CPU_TIME
20
+ #define PF2_DEFAULT__TEST_NO_INSTALL_TIMER false
18
21
 
19
22
  struct pf2_configuration *pf2_configuration_new_from_options_hash(VALUE options_hash);
20
23
  void pf2_configuration_free(struct pf2_configuration *config);
data/ext/pf2/extconf.rb CHANGED
@@ -1,10 +1,25 @@
1
1
  require 'mkmf'
2
2
  require 'mini_portile2'
3
+ require 'fileutils'
4
+
5
+ gem_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
3
6
 
4
7
  libbacktrace = MiniPortile.new('libbacktrace', '1.0.0')
5
- libbacktrace.source_directory = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'vendor', 'libbacktrace'))
8
+ libbacktrace.source_directory = File.join(gem_root, 'vendor', 'libbacktrace')
9
+ libbacktrace.patch_files = Dir.glob(File.join(gem_root, 'ext', 'patches', 'libbacktrace', '*.patch'))
6
10
  libbacktrace.configure_options << 'CFLAGS=-fPIC'
7
- libbacktrace.cook
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
+
8
23
  libbacktrace.mkmf_config
9
24
 
10
25
  if !have_func('backtrace_full', 'backtrace.h')
data/ext/pf2/sample.h CHANGED
@@ -5,8 +5,8 @@
5
5
 
6
6
  #include <ruby.h>
7
7
 
8
- #define PF2_SAMPLE_MAX_RUBY_DEPTH 200
9
- #define PF2_SAMPLE_MAX_NATIVE_DEPTH 300
8
+ #define PF2_SAMPLE_MAX_RUBY_DEPTH 1024
9
+ #define PF2_SAMPLE_MAX_NATIVE_DEPTH 512
10
10
 
11
11
  struct pf2_sample {
12
12
  pthread_t context_pthread;
data/ext/pf2/serializer.c CHANGED
@@ -1,6 +1,7 @@
1
1
  #include <time.h>
2
2
  #include <stdint.h>
3
3
  #include <string.h>
4
+ #include <stdatomic.h>
4
5
 
5
6
  #include <ruby.h>
6
7
  #include <ruby/debug.h>
@@ -29,6 +30,9 @@ pf2_ser_new(void) {
29
30
  ser->start_timestamp_ns = 0;
30
31
  ser->duration_ns = 0;
31
32
 
33
+ ser->collected_sample_count = 0;
34
+ ser->dropped_sample_count = 0;
35
+
32
36
  ser->samples = NULL;
33
37
  ser->samples_count = 0;
34
38
  ser->samples_capacity = 0;
@@ -76,6 +80,10 @@ pf2_ser_prepare(struct pf2_ser *serializer, struct pf2_session *session) {
76
80
  (uint64_t)session->start_time_realtime.tv_sec * 1000000000ULL +
77
81
  (uint64_t)session->start_time_realtime.tv_nsec;
78
82
  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);
79
87
 
80
88
  // Process samples
81
89
  for (size_t i = 0; i < session->samples_index; i++) {
@@ -127,6 +135,8 @@ pf2_ser_to_ruby_hash(struct pf2_ser *serializer) {
127
135
  // Add metadata
128
136
  rb_hash_aset(hash, ID2SYM(rb_intern("start_timestamp_ns")), ULL2NUM(serializer->start_timestamp_ns));
129
137
  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));
130
140
 
131
141
  // Add samples
132
142
  VALUE samples = rb_ary_new_capa(serializer->samples_count);
data/ext/pf2/serializer.h CHANGED
@@ -38,6 +38,8 @@ struct pf2_ser_function {
38
38
  struct pf2_ser {
39
39
  uint64_t start_timestamp_ns;
40
40
  uint64_t duration_ns;
41
+ uint64_t collected_sample_count;
42
+ uint64_t dropped_sample_count;
41
43
 
42
44
  struct pf2_ser_sample *samples;
43
45
  size_t samples_count;
data/ext/pf2/session.c CHANGED
@@ -19,12 +19,11 @@
19
19
  #include "session.h"
20
20
  #include "serializer.h"
21
21
 
22
- #ifndef HAVE_TIMER_CREATE
23
- // Global session pointer for setitimer fallback
22
+ // Pointer to current active session, for access from signal handlers
24
23
  static struct pf2_session *global_current_session = NULL;
25
- #endif
26
24
 
27
25
  static void *sample_collector_thread(void *arg);
26
+ static void drain_ringbuffer(struct pf2_session *session);
28
27
  static void sigprof_handler(int sig, siginfo_t *info, void *ucontext);
29
28
  bool ensure_sample_capacity(struct pf2_session *session);
30
29
  static void pf2_session_stop(struct pf2_session *session);
@@ -40,10 +39,11 @@ rb_pf2_session_initialize(int argc, VALUE *argv, VALUE self)
40
39
  rb_scan_args(argc, argv, ":", &kwargs);
41
40
  ID kwarg_labels[] = {
42
41
  rb_intern("interval_ms"),
43
- rb_intern("time_mode")
42
+ rb_intern("time_mode"),
43
+ rb_intern("_test_no_install_timer")
44
44
  };
45
45
  VALUE *kwarg_values = NULL;
46
- rb_get_kwargs(kwargs, kwarg_labels, 0, 2, kwarg_values);
46
+ rb_get_kwargs(kwargs, kwarg_labels, 0, 3, kwarg_values);
47
47
 
48
48
  session->configuration = pf2_configuration_new_from_options_hash(kwargs);
49
49
 
@@ -56,6 +56,9 @@ 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
+
59
62
  session->is_running = true;
60
63
 
61
64
  // Record start time
@@ -87,58 +90,60 @@ rb_pf2_session_start(VALUE self)
87
90
  }
88
91
  #endif
89
92
 
90
- #ifdef HAVE_TIMER_CREATE
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
- }
118
- #else
119
- // Use setitimer as fallback
120
- // Some platforms (e.g. macOS) do not have timer_create(3).
121
- // setitimer(3) can be used as a alternative, but has limited functionality.
122
93
  global_current_session = session;
123
94
 
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
- }
95
+ if (!session->configuration->_test_no_install_timer) {
96
+ #ifdef HAVE_TIMER_CREATE
97
+ // Configure a kernel timer to send SIGPROF periodically
98
+ struct sigevent sev;
99
+ sev.sigev_notify = SIGEV_SIGNAL;
100
+ sev.sigev_signo = SIGPROF;
101
+ if (timer_create(
102
+ session->configuration->time_mode == PF2_TIME_MODE_CPU_TIME
103
+ ? CLOCK_PROCESS_CPUTIME_ID
104
+ : CLOCK_MONOTONIC,
105
+ &sev,
106
+ &session->timer
107
+ ) == -1) {
108
+ rb_raise(rb_eRuntimeError, "Failed to create timer");
109
+ }
110
+ struct itimerspec its = {
111
+ .it_value = {
112
+ .tv_sec = 0,
113
+ .tv_nsec = session->configuration->interval_ms * 1000000,
114
+ },
115
+ .it_interval = {
116
+ .tv_sec = 0,
117
+ .tv_nsec = session->configuration->interval_ms * 1000000,
118
+ },
119
+ };
120
+ if (timer_settime(session->timer, 0, &its, NULL) == -1) {
121
+ rb_raise(rb_eRuntimeError, "Failed to start timer");
122
+ }
123
+ #else
124
+ // Use setitimer as fallback
125
+ // Some platforms (e.g. macOS) do not have timer_create(3).
126
+ // setitimer(3) can be used as a alternative, but has limited functionality.
127
+
128
+ struct itimerval itv = {
129
+ .it_value = {
130
+ .tv_sec = 0,
131
+ .tv_usec = session->configuration->interval_ms * 1000,
132
+ },
133
+ .it_interval = {
134
+ .tv_sec = 0,
135
+ .tv_usec = session->configuration->interval_ms * 1000,
136
+ },
137
+ };
138
+ int which_timer = session->configuration->time_mode == PF2_TIME_MODE_CPU_TIME
139
+ ? ITIMER_PROF // CPU time (sends SIGPROF)
140
+ : ITIMER_REAL; // Wall time (sends SIGALRM)
141
+
142
+ if (setitimer(which_timer, &itv, NULL) == -1) {
143
+ rb_raise(rb_eRuntimeError, "Failed to start timer");
144
+ }
141
145
  #endif
146
+ } // if !__test_no_install_timer
142
147
 
143
148
  return Qtrue;
144
149
  }
@@ -150,17 +155,7 @@ sample_collector_thread(void *arg)
150
155
 
151
156
  while (session->is_running == true) {
152
157
  // Take samples from the ring buffer
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
- }
158
+ drain_ringbuffer(session);
164
159
 
165
160
  // Sleep for 100 ms
166
161
  // TODO: Replace with high watermark callback
@@ -171,6 +166,24 @@ sample_collector_thread(void *arg)
171
166
  return NULL;
172
167
  }
173
168
 
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
+
174
187
  // async-signal-safe
175
188
  static void
176
189
  sigprof_handler(int sig, siginfo_t *info, void *ucontext)
@@ -180,16 +193,12 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
180
193
  clock_gettime(CLOCK_MONOTONIC, &sig_start_time);
181
194
  #endif
182
195
 
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
196
+ struct pf2_session *session = global_current_session;
189
197
 
190
198
  // If garbage collection is in progress, don't collect samples.
191
199
  if (atomic_load_explicit(&session->is_marking, memory_order_acquire)) {
192
200
  PF2_DEBUG_LOG("Dropping sample: Garbage collection is in progress\n");
201
+ atomic_fetch_add_explicit(&session->dropped_sample_count, 1, memory_order_relaxed);
193
202
  return;
194
203
  }
195
204
 
@@ -197,6 +206,7 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
197
206
 
198
207
  if (pf2_sample_capture(&sample) == false) {
199
208
  PF2_DEBUG_LOG("Dropping sample: Failed to capture sample\n");
209
+ atomic_fetch_add_explicit(&session->dropped_sample_count, 1, memory_order_relaxed);
200
210
  return;
201
211
  }
202
212
 
@@ -204,6 +214,7 @@ sigprof_handler(int sig, siginfo_t *info, void *ucontext)
204
214
  if (pf2_ringbuffer_push(session->rbuf, &sample) == false) {
205
215
  // Copy failed. The sample buffer is full.
206
216
  PF2_DEBUG_LOG("Dropping sample: Sample buffer is full\n");
217
+ atomic_fetch_add_explicit(&session->dropped_sample_count, 1, memory_order_relaxed);
207
218
  return;
208
219
  }
209
220
 
@@ -274,8 +285,10 @@ pf2_session_stop(struct pf2_session *session)
274
285
 
275
286
  // Disarm and delete the timer.
276
287
  #ifdef HAVE_TIMER_CREATE
277
- if (timer_delete(session->timer) == -1) {
278
- rb_raise(rb_eRuntimeError, "Failed to delete timer");
288
+ if (!session->configuration->_test_no_install_timer) {
289
+ if (timer_delete(session->timer) == -1) {
290
+ rb_raise(rb_eRuntimeError, "Failed to delete timer");
291
+ }
279
292
  }
280
293
  #else
281
294
  struct itimerval zero_timer = {{0, 0}, {0, 0}};
@@ -291,6 +304,7 @@ pf2_session_stop(struct pf2_session *session)
291
304
  // Terminate the collector thread
292
305
  session->is_running = false;
293
306
  pthread_join(*session->collector_thread, NULL);
307
+ drain_ringbuffer(session);
294
308
  }
295
309
 
296
310
  VALUE
@@ -306,7 +320,7 @@ pf2_session_alloc(VALUE self)
306
320
  {
307
321
  // Initialize state for libbacktrace
308
322
  if (global_backtrace_state == NULL) {
309
- global_backtrace_state = backtrace_create_state("pf2", 1, pf2_backtrace_print_error, NULL);
323
+ global_backtrace_state = backtrace_create_state(NULL, 1, pf2_backtrace_print_error, NULL);
310
324
  if (global_backtrace_state == NULL) {
311
325
  rb_raise(rb_eRuntimeError, "Failed to initialize libbacktrace");
312
326
  }
@@ -350,6 +364,10 @@ pf2_session_alloc(VALUE self)
350
364
  rb_raise(rb_eNoMemError, "Failed to allocate memory");
351
365
  }
352
366
 
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
+
353
371
  // start_time_realtime, start_time
354
372
  session->start_time_realtime = (struct timespec){0};
355
373
  session->start_time = (struct timespec){0};
data/ext/pf2/session.h CHANGED
@@ -30,6 +30,9 @@ 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
+
33
36
  struct pf2_configuration *configuration;
34
37
  };
35
38
 
data/lib/pf2/cli.rb CHANGED
@@ -26,7 +26,7 @@ module Pf2
26
26
  when 'version'
27
27
  puts VERSION
28
28
  return 0
29
- when '--help'
29
+ when nil, '--help'
30
30
  STDERR.puts <<~__EOS__
31
31
  Usage: #{program_name} COMMAND [options]
32
32
 
data/lib/pf2/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pf2
4
- VERSION = '0.11.3'
4
+ VERSION = '0.12.0'
5
5
  end
data/lib/pf2.rb CHANGED
@@ -15,9 +15,17 @@ module Pf2
15
15
  @@session.stop
16
16
  end
17
17
 
18
- def self.profile(&block)
18
+ # Profiles the given block of code.
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
27
  raise ArgumentError, "block required" unless block_given?
20
- start(threads: Thread.list)
28
+ start(**kwargs)
21
29
  yield
22
30
  result = stop
23
31
  @@session = nil # let GC clean up the session
@@ -103,7 +103,7 @@ backtrace_atomic_store_size_t (size_t *p, size_t v)
103
103
  void
104
104
  backtrace_atomic_store_int (int *p, int v)
105
105
  {
106
- size_t old;
106
+ int old;
107
107
 
108
108
  old = *p;
109
109
  while (!__sync_bool_compare_and_swap (p, old, v))
@@ -812,6 +812,7 @@ enable_darwin_at_rpath
812
812
  enable_largefile
813
813
  enable_werror
814
814
  with_system_libunwind
815
+ enable_host_pie
815
816
  enable_host_shared
816
817
  '
817
818
  ac_precious_vars='build_alias
@@ -1461,6 +1462,7 @@ Optional Features:
1461
1462
  rpaths to be added to executables
1462
1463
  --disable-largefile omit support for large files
1463
1464
  --disable-werror disable building with -Werror
1465
+ --enable-host-pie build host code as PIE
1464
1466
  --enable-host-shared build host code as shared libraries
1465
1467
 
1466
1468
  Optional Packages:
@@ -11395,7 +11397,7 @@ else
11395
11397
  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
11396
11398
  lt_status=$lt_dlunknown
11397
11399
  cat > conftest.$ac_ext <<_LT_EOF
11398
- #line 11398 "configure"
11400
+ #line 11400 "configure"
11399
11401
  #include "confdefs.h"
11400
11402
 
11401
11403
  #if HAVE_DLFCN_H
@@ -11501,7 +11503,7 @@ else
11501
11503
  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
11502
11504
  lt_status=$lt_dlunknown
11503
11505
  cat > conftest.$ac_ext <<_LT_EOF
11504
- #line 11504 "configure"
11506
+ #line 11506 "configure"
11505
11507
  #include "confdefs.h"
11506
11508
 
11507
11509
  #if HAVE_DLFCN_H
@@ -12190,12 +12192,18 @@ $as_echo "#define HAVE_GETIPINFO 1" >>confdefs.h
12190
12192
  fi
12191
12193
  fi
12192
12194
 
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
+
12193
12203
  # Enable --enable-host-shared.
12194
12204
  # Check whether --enable-host-shared was given.
12195
12205
  if test "${enable_host_shared+set}" = set; then :
12196
12206
  enableval=$enable_host_shared; PIC_FLAG=-fPIC
12197
- else
12198
- PIC_FLAG=
12199
12207
  fi
12200
12208
 
12201
12209
 
@@ -176,11 +176,16 @@ 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=])
179
184
  # Enable --enable-host-shared.
180
185
  AC_ARG_ENABLE(host-shared,
181
186
  [AS_HELP_STRING([--enable-host-shared],
182
187
  [build host code as shared libraries])],
183
- [PIC_FLAG=-fPIC], [PIC_FLAG=])
188
+ [PIC_FLAG=-fPIC])
184
189
  AC_SUBST(PIC_FLAG)
185
190
 
186
191
  # Test for __sync support.
@@ -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 ELF_MAG0
164
- #undef ELF_MAG1
165
- #undef ELF_MAG2
166
- #undef ELF_MAG3
163
+ #undef ELFMAG0
164
+ #undef ELFMAG1
165
+ #undef ELFMAG2
166
+ #undef ELFMAG3
167
167
  #undef ELFCLASS32
168
168
  #undef ELFCLASS64
169
169
  #undef ELFDATA2LSB
@@ -47,6 +47,10 @@ 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
+
50
54
  #ifdef HAVE_WINDOWS_H
51
55
  #ifndef WIN32_LEAN_AND_MEAN
52
56
  #define WIN32_LEAN_AND_MEAN
@@ -66,6 +70,33 @@ POSSIBILITY OF SUCH DAMAGE. */
66
70
  #define getexecname() NULL
67
71
  #endif
68
72
 
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
+
69
100
  #if !defined (HAVE_KERN_PROC_ARGS) && !defined (HAVE_KERN_PROC)
70
101
 
71
102
  #define sysctl_exec_name1(state, error_callback, data) NULL
@@ -245,7 +276,7 @@ fileline_initialize (struct backtrace_state *state,
245
276
 
246
277
  descriptor = -1;
247
278
  called_error_callback = 0;
248
- for (pass = 0; pass < 10; ++pass)
279
+ for (pass = 0; pass < 11; ++pass)
249
280
  {
250
281
  int does_not_exist;
251
282
 
@@ -285,6 +316,9 @@ fileline_initialize (struct backtrace_state *state,
285
316
  case 9:
286
317
  filename = windows_get_executable_path (buf, error_callback, data);
287
318
  break;
319
+ case 10:
320
+ filename = hpux_get_executable_path (state, error_callback, data);
321
+ break;
288
322
  default:
289
323
  abort ();
290
324
  }
@@ -3,6 +3,7 @@
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 } }
6
7
  /^\001\337/ { if (NR == 1) { print "xcoff32"; exit } }
7
8
  /^\001\367/ { if (NR == 1) { print "xcoff64"; exit } }
8
9
  /^\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.11.3
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daisuke Aritomo
@@ -125,6 +125,7 @@ files:
125
125
  - examples/mandelbrot.rb
126
126
  - examples/mandelbrot_ractor.rb
127
127
  - exe/pf2
128
+ - ext/patches/libbacktrace/0001-Support-MACH_O_MH_BUNDLE.patch
128
129
  - ext/pf2/backtrace_state.c
129
130
  - ext/pf2/backtrace_state.h
130
131
  - ext/pf2/configuration.c