stackprof 0.2.26 → 0.2.28

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: 3165c34411b9287741eb15e8c84bec239792a81aaf30695d2e735b0bee41a9d8
4
- data.tar.gz: 6cf3bd55b6b14fcd880facfb281ded85e03d1ec9d79fc1b0c4fcb5b592df014d
3
+ metadata.gz: 79a87e448ec4d0147b81316179de34549210e7d0e4dc5ed88a0e29ac2a29f78e
4
+ data.tar.gz: 7379be750f5818a56a00e8170802bfb56444efff17b2a663a4da86789405e329
5
5
  SHA512:
6
- metadata.gz: '086238ddefcd029b17a7de36d0bdce624b8cda367c1dd5ab55348f9332388c49fc326f01ba66d7d11d31d0014210d1474013aa106d1432da4605334551772ba2'
7
- data.tar.gz: a4c203dc55aa3da32744114a2f899982d548ce1c4c90daf4da3808bcae725fcff21775a4fee3d303724f96418b5bd1c748807dce7d28e84dcde8fbd61ea90932
6
+ metadata.gz: 3f87238f0afb0690fc26e09eb4d8c3d4ac4da173080852aee353967239dd98058be39303072db2e0f192311b4aabb58691765ae1a38d16cc0c45a7674aba2954
7
+ data.tar.gz: 03babedcf6978a18f801bd88c804924468c379692617cb5429b6453bae6db34c76293ef0f4aff7ca74cb415031a5c6476dc92f873e4cfe2b1ab0379f3edf465a
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
11
- ruby: [ ruby-head, '3.2', '3.1', '3.0', '2.7', truffleruby ]
11
+ ruby: [ ruby-head, '3.4', '3.3','3.2', '3.1', '3.0', truffleruby ]
12
12
  steps:
13
13
  - name: Checkout
14
14
  uses: actions/checkout@v2
@@ -5,7 +5,8 @@ if RUBY_ENGINE == 'truffleruby'
5
5
  return
6
6
  end
7
7
 
8
- if have_func('rb_postponed_job_register_one') &&
8
+ if (have_func('rb_postponed_job_preregister') ||
9
+ have_func('rb_postponed_job_register_one')) &&
9
10
  have_func('rb_profile_frames') &&
10
11
  have_func('rb_tracepoint_new') &&
11
12
  have_const('RUBY_INTERNAL_EVENT_NEWOBJ')
@@ -26,6 +26,16 @@
26
26
  #define FAKE_FRAME_MARK INT2FIX(1)
27
27
  #define FAKE_FRAME_SWEEP INT2FIX(2)
28
28
 
29
+ #ifdef HAVE_RB_POSTPONED_JOB_PREREGISTER
30
+ static rb_postponed_job_handle_t job_record_gc, job_sample_and_record, job_record_buffer;
31
+
32
+ # define preregister_job(job) (job = rb_postponed_job_preregister(0, stackprof_##job, (void*)0))
33
+ # define trigger_job(job) rb_postponed_job_trigger(job)
34
+ #else
35
+ # define preregister_job(job) ((void*)0)
36
+ # define trigger_job(job) rb_postponed_job_register_one(0, stackprof_##job, (void*)0)
37
+ #endif
38
+
29
39
  static const char *fake_frame_cstrs[] = {
30
40
  "(garbage collection)",
31
41
  "(marking)",
@@ -91,7 +101,19 @@ typedef struct {
91
101
  int64_t delta_usec;
92
102
  } sample_time_t;
93
103
 
104
+ /* We need to ensure that various memory operations are visible across
105
+ * threads. Ruby doesn't offer a portable way to do this sort of detection
106
+ * across all the Ruby versions we support, so we use something that casts a
107
+ * wide net (Clang, along with ICC, defines __GNUC__). */
108
+ #if defined(__GNUC__) && defined(__ATOMIC_SEQ_CST)
109
+ #define STACKPROF_HAVE_ATOMICS 1
110
+ #else
111
+ #define STACKPROF_HAVE_ATOMICS 0
112
+ #endif
113
+
94
114
  static struct {
115
+ /* Access this field with the `STACKPROF_RUNNING` macro, below, since we
116
+ * can't properly express that this field has an atomic type. */
95
117
  int running;
96
118
  int raw;
97
119
  int aggregate;
@@ -133,6 +155,12 @@ static struct {
133
155
  pthread_t target_thread;
134
156
  } _stackprof;
135
157
 
158
+ #if STACKPROF_HAVE_ATOMICS
159
+ #define STACKPROF_RUNNING() __atomic_load_n(&_stackprof.running, __ATOMIC_ACQUIRE)
160
+ #else
161
+ #define STACKPROF_RUNNING() _stackprof.running
162
+ #endif
163
+
136
164
  static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
137
165
  static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
138
166
  static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_raw_lines, sym_metadata, sym_frames, sym_ignore_gc, sym_out;
@@ -154,7 +182,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
154
182
  int raw = 0, aggregate = 1;
155
183
  VALUE metadata_val;
156
184
 
157
- if (_stackprof.running)
185
+ if (STACKPROF_RUNNING())
158
186
  return Qfalse;
159
187
 
160
188
  rb_scan_args(argc, argv, "0:", &opts);
@@ -207,7 +235,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
207
235
  sigaction(mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
208
236
 
209
237
  timer.it_interval.tv_sec = 0;
210
- timer.it_interval.tv_usec = NUM2LONG(interval);
238
+ timer.it_interval.tv_usec = NUM2UINT(interval);
211
239
  timer.it_value = timer.it_interval;
212
240
  setitimer(mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
213
241
  } else if (mode == sym_custom) {
@@ -217,7 +245,6 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
217
245
  rb_raise(rb_eArgError, "unknown profiler mode");
218
246
  }
219
247
 
220
- _stackprof.running = 1;
221
248
  _stackprof.raw = raw;
222
249
  _stackprof.aggregate = aggregate;
223
250
  _stackprof.mode = mode;
@@ -226,6 +253,13 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
226
253
  _stackprof.metadata = metadata;
227
254
  _stackprof.out = out;
228
255
  _stackprof.target_thread = pthread_self();
256
+ /* We need to ensure previous initialization stores are visible across
257
+ * threads. */
258
+ #if STACKPROF_HAVE_ATOMICS
259
+ __atomic_store_n(&_stackprof.running, 1, __ATOMIC_SEQ_CST);
260
+ #else
261
+ _stackprof.running = 1;
262
+ #endif
229
263
 
230
264
  if (raw) {
231
265
  capture_timestamp(&_stackprof.last_sample_at);
@@ -240,9 +274,15 @@ stackprof_stop(VALUE self)
240
274
  struct sigaction sa;
241
275
  struct itimerval timer;
242
276
 
277
+ #if STACKPROF_HAVE_ATOMICS
278
+ int was_running = __atomic_exchange_n(&_stackprof.running, 0, __ATOMIC_SEQ_CST);
279
+ if (!was_running)
280
+ return Qfalse;
281
+ #else
243
282
  if (!_stackprof.running)
244
283
  return Qfalse;
245
284
  _stackprof.running = 0;
285
+ #endif
246
286
 
247
287
  if (_stackprof.mode == sym_object) {
248
288
  rb_tracepoint_disable(objtracer);
@@ -351,7 +391,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
351
391
  {
352
392
  VALUE results, frames;
353
393
 
354
- if (!_stackprof.frames || _stackprof.running)
394
+ if (!_stackprof.frames || STACKPROF_RUNNING())
355
395
  return Qnil;
356
396
 
357
397
  results = rb_hash_new();
@@ -455,7 +495,7 @@ stackprof_run(int argc, VALUE *argv, VALUE self)
455
495
  static VALUE
456
496
  stackprof_running_p(VALUE self)
457
497
  {
458
- return _stackprof.running ? Qtrue : Qfalse;
498
+ return STACKPROF_RUNNING() ? Qtrue : Qfalse;
459
499
  }
460
500
 
461
501
  static inline frame_data_t *
@@ -719,7 +759,7 @@ stackprof_sample_and_record(void)
719
759
  static void
720
760
  stackprof_job_record_gc(void *data)
721
761
  {
722
- if (!_stackprof.running) return;
762
+ if (!STACKPROF_RUNNING()) return;
723
763
 
724
764
  stackprof_record_gc_samples();
725
765
  }
@@ -727,7 +767,7 @@ stackprof_job_record_gc(void *data)
727
767
  static void
728
768
  stackprof_job_sample_and_record(void *data)
729
769
  {
730
- if (!_stackprof.running) return;
770
+ if (!STACKPROF_RUNNING()) return;
731
771
 
732
772
  stackprof_sample_and_record();
733
773
  }
@@ -735,7 +775,7 @@ stackprof_job_sample_and_record(void *data)
735
775
  static void
736
776
  stackprof_job_record_buffer(void *data)
737
777
  {
738
- if (!_stackprof.running) return;
778
+ if (!STACKPROF_RUNNING()) return;
739
779
 
740
780
  stackprof_record_buffer();
741
781
  }
@@ -747,7 +787,7 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
747
787
 
748
788
  _stackprof.overall_signals++;
749
789
 
750
- if (!_stackprof.running) return;
790
+ if (!STACKPROF_RUNNING()) return;
751
791
 
752
792
  // There's a possibility that the signal handler is invoked *after* the Ruby
753
793
  // VM has been shut down (e.g. after ruby_cleanup(0)). In this case, things
@@ -783,16 +823,16 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
783
823
  capture_timestamp(&_stackprof.gc_start_timestamp);
784
824
  }
785
825
  _stackprof.unrecorded_gc_samples++;
786
- rb_postponed_job_register_one(0, stackprof_job_record_gc, (void*)0);
826
+ trigger_job(job_record_gc);
787
827
  } else {
788
828
  if (stackprof_use_postponed_job) {
789
- rb_postponed_job_register_one(0, stackprof_job_sample_and_record, (void*)0);
829
+ trigger_job(job_sample_and_record);
790
830
  } else {
791
831
  // Buffer a sample immediately, if an existing sample exists this will
792
832
  // return immediately
793
833
  stackprof_buffer_sample();
794
834
  // Enqueue a job to record the sample
795
- rb_postponed_job_register_one(0, stackprof_job_record_buffer, (void*)0);
835
+ trigger_job(job_record_buffer);
796
836
  }
797
837
  }
798
838
  pthread_mutex_unlock(&lock);
@@ -810,7 +850,7 @@ stackprof_newobj_handler(VALUE tpval, void *data)
810
850
  static VALUE
811
851
  stackprof_sample(VALUE self)
812
852
  {
813
- if (!_stackprof.running)
853
+ if (!STACKPROF_RUNNING())
814
854
  return Qfalse;
815
855
 
816
856
  _stackprof.overall_signals++;
@@ -854,7 +894,7 @@ static void
854
894
  stackprof_atfork_prepare(void)
855
895
  {
856
896
  struct itimerval timer;
857
- if (_stackprof.running) {
897
+ if (STACKPROF_RUNNING()) {
858
898
  if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
859
899
  memset(&timer, 0, sizeof(timer));
860
900
  setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
@@ -866,10 +906,10 @@ static void
866
906
  stackprof_atfork_parent(void)
867
907
  {
868
908
  struct itimerval timer;
869
- if (_stackprof.running) {
909
+ if (STACKPROF_RUNNING()) {
870
910
  if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
871
911
  timer.it_interval.tv_sec = 0;
872
- timer.it_interval.tv_usec = NUM2LONG(_stackprof.interval);
912
+ timer.it_interval.tv_usec = NUM2UINT(_stackprof.interval);
873
913
  timer.it_value = timer.it_interval;
874
914
  setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
875
915
  }
@@ -980,5 +1020,9 @@ Init_stackprof(void)
980
1020
  rb_define_singleton_method(rb_mStackProf, "sample", stackprof_sample, 0);
981
1021
  rb_define_singleton_method(rb_mStackProf, "use_postponed_job!", stackprof_use_postponed_job_l, 0);
982
1022
 
1023
+ preregister_job(job_record_gc);
1024
+ preregister_job(job_sample_and_record);
1025
+ preregister_job(job_record_buffer);
1026
+
983
1027
  pthread_atfork(stackprof_atfork_prepare, stackprof_atfork_parent, stackprof_atfork_child);
984
1028
  }
@@ -10,10 +10,14 @@ module StackProf
10
10
 
11
11
  class << self
12
12
  def from_file(file)
13
- if (content = IO.binread(file)).start_with?(MARSHAL_SIGNATURE)
14
- new(Marshal.load(content))
15
- else
16
- from_json(JSON.parse(content))
13
+ File.open(file, 'rb') do |f|
14
+ signature_bytes = f.read(2)
15
+ f.rewind
16
+ if signature_bytes == MARSHAL_SIGNATURE
17
+ new(Marshal.load(f))
18
+ else
19
+ from_json(JSON.parse(f.read))
20
+ end
17
21
  end
18
22
  end
19
23
 
data/lib/stackprof.rb CHANGED
@@ -18,7 +18,7 @@ elsif RUBY_VERSION == "3.2.0"
18
18
  end
19
19
 
20
20
  module StackProf
21
- VERSION = '0.2.26'
21
+ VERSION = '0.2.28'
22
22
  end
23
23
 
24
24
  StackProf.autoload :Report, "stackprof/report.rb"
data/stackprof.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'stackprof'
3
- s.version = '0.2.26'
3
+ s.version = '0.2.28'
4
4
  s.homepage = 'http://github.com/tmm1/stackprof'
5
5
 
6
6
  s.authors = 'Aman Gupta'
@@ -51,9 +51,16 @@ class StackProfTest < Minitest::Test
51
51
 
52
52
  frame = profile[:frames].values.first
53
53
  assert_includes frame[:name], "StackProfTest#test_object_allocation"
54
- assert_equal 2, frame[:samples]
54
+ if RUBY_VERSION >= '4'
55
+ assert_equal 4, frame[:samples]
56
+ else
57
+ assert_equal 2, frame[:samples]
58
+ end
55
59
  assert_includes [profile_base_line - 2, profile_base_line], frame[:line]
56
- if RUBY_VERSION >= '3'
60
+ if RUBY_VERSION >= '4'
61
+ assert_equal [2, 2], frame[:lines][profile_base_line+1]
62
+ assert_equal [2, 2], frame[:lines][profile_base_line+2]
63
+ elsif RUBY_VERSION >= '3'
57
64
  assert_equal [2, 1], frame[:lines][profile_base_line+1]
58
65
  assert_equal [2, 1], frame[:lines][profile_base_line+2]
59
66
  else
@@ -151,7 +158,7 @@ class StackProfTest < Minitest::Test
151
158
  assert_equal 10, raw_lines[-1] # seen 10 times
152
159
 
153
160
  offset = RUBY_VERSION >= '3' ? -3 : -2
154
- assert_equal 140, raw_lines[offset] # sample caller is on 140
161
+ assert_equal 147, raw_lines[offset] # sample caller is on 140
155
162
  assert_includes profile[:frames][raw[offset]][:name], 'StackProfTest#test_raw'
156
163
 
157
164
  assert_equal 10, profile[:raw_sample_timestamps].size
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.26
4
+ version: 0.2.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aman Gupta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-15 00:00:00.000000000 Z
11
+ date: 2026-02-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -85,9 +85,9 @@ licenses:
85
85
  - MIT
86
86
  metadata:
87
87
  bug_tracker_uri: https://github.com/tmm1/stackprof/issues
88
- changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.26/CHANGELOG.md
89
- documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.26
90
- source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.26
88
+ changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.28/CHANGELOG.md
89
+ documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.28
90
+ source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.28
91
91
  post_install_message:
92
92
  rdoc_options: []
93
93
  require_paths: