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 +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/ext/stackprof/extconf.rb +2 -1
- data/ext/stackprof/stackprof.c +60 -16
- data/lib/stackprof/report.rb +8 -4
- data/lib/stackprof.rb +1 -1
- data/stackprof.gemspec +1 -1
- data/test/test_stackprof.rb +10 -3
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79a87e448ec4d0147b81316179de34549210e7d0e4dc5ed88a0e29ac2a29f78e
|
|
4
|
+
data.tar.gz: 7379be750f5818a56a00e8170802bfb56444efff17b2a663a4da86789405e329
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3f87238f0afb0690fc26e09eb4d8c3d4ac4da173080852aee353967239dd98058be39303072db2e0f192311b4aabb58691765ae1a38d16cc0c45a7674aba2954
|
|
7
|
+
data.tar.gz: 03babedcf6978a18f801bd88c804924468c379692617cb5429b6453bae6db34c76293ef0f4aff7ca74cb415031a5c6476dc92f873e4cfe2b1ab0379f3edf465a
|
data/.github/workflows/ci.yml
CHANGED
data/ext/stackprof/extconf.rb
CHANGED
|
@@ -5,7 +5,8 @@ if RUBY_ENGINE == 'truffleruby'
|
|
|
5
5
|
return
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
if have_func('
|
|
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')
|
data/ext/stackprof/stackprof.c
CHANGED
|
@@ -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 (
|
|
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 =
|
|
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 ||
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
826
|
+
trigger_job(job_record_gc);
|
|
787
827
|
} else {
|
|
788
828
|
if (stackprof_use_postponed_job) {
|
|
789
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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
|
}
|
data/lib/stackprof/report.rb
CHANGED
|
@@ -10,10 +10,14 @@ module StackProf
|
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
12
|
def from_file(file)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
data/stackprof.gemspec
CHANGED
data/test/test_stackprof.rb
CHANGED
|
@@ -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
|
-
|
|
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 >= '
|
|
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
|
|
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.
|
|
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:
|
|
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.
|
|
89
|
-
documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.
|
|
90
|
-
source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.
|
|
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:
|