pf2 0.9.0 → 1.0.0.alpha1

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