stackprof 0.2.25 → 0.2.26
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/stackprof.c +53 -10
- data/lib/stackprof.rb +6 -2
- data/stackprof.gemspec +1 -2
- data/test/test_middleware.rb +30 -17
- data/test/test_report.rb +2 -2
- data/test/test_stackprof.rb +13 -3
- data/test/test_truffleruby.rb +1 -1
- metadata +5 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3165c34411b9287741eb15e8c84bec239792a81aaf30695d2e735b0bee41a9d8
|
4
|
+
data.tar.gz: 6cf3bd55b6b14fcd880facfb281ded85e03d1ec9d79fc1b0c4fcb5b592df014d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '086238ddefcd029b17a7de36d0bdce624b8cda367c1dd5ab55348f9332388c49fc326f01ba66d7d11d31d0014210d1474013aa106d1432da4605334551772ba2'
|
7
|
+
data.tar.gz: a4c203dc55aa3da32744114a2f899982d548ce1c4c90daf4da3808bcae725fcff21775a4fee3d303724f96418b5bd1c748807dce7d28e84dcde8fbd61ea90932
|
data/.github/workflows/ci.yml
CHANGED
data/ext/stackprof/stackprof.c
CHANGED
@@ -102,7 +102,7 @@ static struct {
|
|
102
102
|
VALUE metadata;
|
103
103
|
int ignore_gc;
|
104
104
|
|
105
|
-
|
105
|
+
uint64_t *raw_samples;
|
106
106
|
size_t raw_samples_len;
|
107
107
|
size_t raw_samples_capa;
|
108
108
|
size_t raw_sample_index;
|
@@ -120,6 +120,8 @@ static struct {
|
|
120
120
|
size_t unrecorded_gc_sweeping_samples;
|
121
121
|
st_table *frames;
|
122
122
|
|
123
|
+
timestamp_t gc_start_timestamp;
|
124
|
+
|
123
125
|
VALUE fake_frame_names[TOTAL_FAKE_FRAMES];
|
124
126
|
VALUE empty_string;
|
125
127
|
|
@@ -133,7 +135,7 @@ static struct {
|
|
133
135
|
|
134
136
|
static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
|
135
137
|
static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
|
136
|
-
static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_metadata, sym_frames, sym_ignore_gc, sym_out;
|
138
|
+
static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_raw_lines, sym_metadata, sym_frames, sym_ignore_gc, sym_out;
|
137
139
|
static VALUE sym_aggregate, sym_raw_sample_timestamps, sym_raw_timestamp_deltas, sym_state, sym_marking, sym_sweeping;
|
138
140
|
static VALUE sym_gc_samples, objtracer;
|
139
141
|
static VALUE gc_hook;
|
@@ -374,14 +376,23 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
|
|
374
376
|
size_t len, n, o;
|
375
377
|
VALUE raw_sample_timestamps, raw_timestamp_deltas;
|
376
378
|
VALUE raw_samples = rb_ary_new_capa(_stackprof.raw_samples_len);
|
379
|
+
VALUE raw_lines = rb_ary_new_capa(_stackprof.raw_samples_len);
|
377
380
|
|
378
381
|
for (n = 0; n < _stackprof.raw_samples_len; n++) {
|
379
382
|
len = (size_t)_stackprof.raw_samples[n];
|
380
383
|
rb_ary_push(raw_samples, SIZET2NUM(len));
|
384
|
+
rb_ary_push(raw_lines, SIZET2NUM(len));
|
385
|
+
|
386
|
+
for (o = 0, n++; o < len; n++, o++) {
|
387
|
+
// Line is in the upper 16 bits
|
388
|
+
rb_ary_push(raw_lines, INT2NUM(_stackprof.raw_samples[n] >> 48));
|
389
|
+
|
390
|
+
VALUE frame = _stackprof.raw_samples[n] & ~((uint64_t)0xFFFF << 48);
|
391
|
+
rb_ary_push(raw_samples, PTR2NUM(frame));
|
392
|
+
}
|
381
393
|
|
382
|
-
for (o = 0, n++; o < len; n++, o++)
|
383
|
-
rb_ary_push(raw_samples, PTR2NUM(_stackprof.raw_samples[n]));
|
384
394
|
rb_ary_push(raw_samples, SIZET2NUM((size_t)_stackprof.raw_samples[n]));
|
395
|
+
rb_ary_push(raw_lines, SIZET2NUM((size_t)_stackprof.raw_samples[n]));
|
385
396
|
}
|
386
397
|
|
387
398
|
free(_stackprof.raw_samples);
|
@@ -391,6 +402,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
|
|
391
402
|
_stackprof.raw_sample_index = 0;
|
392
403
|
|
393
404
|
rb_hash_aset(results, sym_raw, raw_samples);
|
405
|
+
rb_hash_aset(results, sym_raw_lines, raw_lines);
|
394
406
|
|
395
407
|
raw_sample_timestamps = rb_ary_new_capa(_stackprof.raw_sample_times_len);
|
396
408
|
raw_timestamp_deltas = rb_ary_new_capa(_stackprof.raw_sample_times_len);
|
@@ -520,7 +532,12 @@ stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t ti
|
|
520
532
|
* in the frames buffer that came from Ruby. */
|
521
533
|
for (i = num-1, n = 0; i >= 0; i--, n++) {
|
522
534
|
VALUE frame = _stackprof.frames_buffer[i];
|
523
|
-
|
535
|
+
int line = _stackprof.lines_buffer[i];
|
536
|
+
|
537
|
+
// Encode the line in to the upper 16 bits.
|
538
|
+
uint64_t key = ((uint64_t)line << 48) | (uint64_t)frame;
|
539
|
+
|
540
|
+
if (_stackprof.raw_samples[_stackprof.raw_sample_index + 1 + n] != key)
|
524
541
|
break;
|
525
542
|
}
|
526
543
|
if (i == -1) {
|
@@ -538,7 +555,12 @@ stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t ti
|
|
538
555
|
_stackprof.raw_samples[_stackprof.raw_samples_len++] = (VALUE)num;
|
539
556
|
for (i = num-1; i >= 0; i--) {
|
540
557
|
VALUE frame = _stackprof.frames_buffer[i];
|
541
|
-
_stackprof.
|
558
|
+
int line = _stackprof.lines_buffer[i];
|
559
|
+
|
560
|
+
// Encode the line in to the upper 16 bits.
|
561
|
+
uint64_t key = ((uint64_t)line << 48) | (uint64_t)frame;
|
562
|
+
|
563
|
+
_stackprof.raw_samples[_stackprof.raw_samples_len++] = key;
|
542
564
|
}
|
543
565
|
_stackprof.raw_samples[_stackprof.raw_samples_len++] = (VALUE)1;
|
544
566
|
}
|
@@ -626,6 +648,7 @@ stackprof_buffer_sample(void)
|
|
626
648
|
_stackprof.buffer_time.delta_usec = timestamp_delta;
|
627
649
|
}
|
628
650
|
|
651
|
+
// Postponed job
|
629
652
|
void
|
630
653
|
stackprof_record_gc_samples(void)
|
631
654
|
{
|
@@ -633,8 +656,7 @@ stackprof_record_gc_samples(void)
|
|
633
656
|
uint64_t start_timestamp = 0;
|
634
657
|
size_t i;
|
635
658
|
if (_stackprof.raw) {
|
636
|
-
struct timestamp_t t;
|
637
|
-
capture_timestamp(&t);
|
659
|
+
struct timestamp_t t = _stackprof.gc_start_timestamp;
|
638
660
|
start_timestamp = timestamp_usec(&t);
|
639
661
|
|
640
662
|
// We don't know when the GC samples were actually marked, so let's
|
@@ -756,6 +778,10 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
|
|
756
778
|
} else if (mode == sym_sweeping) {
|
757
779
|
_stackprof.unrecorded_gc_sweeping_samples++;
|
758
780
|
}
|
781
|
+
if(!_stackprof.unrecorded_gc_samples) {
|
782
|
+
// record start
|
783
|
+
capture_timestamp(&_stackprof.gc_start_timestamp);
|
784
|
+
}
|
759
785
|
_stackprof.unrecorded_gc_samples++;
|
760
786
|
rb_postponed_job_register_one(0, stackprof_job_record_gc, (void*)0);
|
761
787
|
} else {
|
@@ -812,11 +838,18 @@ stackprof_gc_mark(void *data)
|
|
812
838
|
if (_stackprof.frames)
|
813
839
|
st_foreach(_stackprof.frames, frame_mark_i, 0);
|
814
840
|
|
815
|
-
|
841
|
+
int i;
|
842
|
+
for (i = 0; i < _stackprof.buffer_count; i++) {
|
816
843
|
rb_gc_mark(_stackprof.frames_buffer[i]);
|
817
844
|
}
|
818
845
|
}
|
819
846
|
|
847
|
+
static size_t
|
848
|
+
stackprof_memsize(const void *data)
|
849
|
+
{
|
850
|
+
return sizeof(_stackprof);
|
851
|
+
}
|
852
|
+
|
820
853
|
static void
|
821
854
|
stackprof_atfork_prepare(void)
|
822
855
|
{
|
@@ -862,6 +895,15 @@ stackprof_at_exit(ruby_vm_t* vm)
|
|
862
895
|
ruby_vm_running = 0;
|
863
896
|
}
|
864
897
|
|
898
|
+
static const rb_data_type_t stackprof_type = {
|
899
|
+
"StackProf",
|
900
|
+
{
|
901
|
+
stackprof_gc_mark,
|
902
|
+
NULL,
|
903
|
+
stackprof_memsize,
|
904
|
+
}
|
905
|
+
};
|
906
|
+
|
865
907
|
void
|
866
908
|
Init_stackprof(void)
|
867
909
|
{
|
@@ -893,6 +935,7 @@ Init_stackprof(void)
|
|
893
935
|
S(mode);
|
894
936
|
S(interval);
|
895
937
|
S(raw);
|
938
|
+
S(raw_lines);
|
896
939
|
S(raw_sample_timestamps);
|
897
940
|
S(raw_timestamp_deltas);
|
898
941
|
S(out);
|
@@ -908,8 +951,8 @@ Init_stackprof(void)
|
|
908
951
|
/* Need to run this to warm the symbol table before we call this during GC */
|
909
952
|
rb_gc_latest_gc_info(sym_state);
|
910
953
|
|
911
|
-
gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, &_stackprof);
|
912
954
|
rb_global_variable(&gc_hook);
|
955
|
+
gc_hook = TypedData_Wrap_Struct(rb_cObject, &stackprof_type, &_stackprof);
|
913
956
|
|
914
957
|
_stackprof.raw_samples = NULL;
|
915
958
|
_stackprof.raw_samples_len = 0;
|
data/lib/stackprof.rb
CHANGED
@@ -5,7 +5,11 @@ else
|
|
5
5
|
end
|
6
6
|
|
7
7
|
if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
|
8
|
-
|
8
|
+
if RUBY_VERSION < "3.3"
|
9
|
+
# On 3.3 we don't need postponed jobs:
|
10
|
+
# https://github.com/ruby/ruby/commit/a1dc1a3de9683daf5a543d6f618e17aabfcb8708
|
11
|
+
StackProf.use_postponed_job!
|
12
|
+
end
|
9
13
|
elsif RUBY_VERSION == "3.2.0"
|
10
14
|
# 3.2.0 crash is the signal is received at the wrong time.
|
11
15
|
# Fixed in https://github.com/ruby/ruby/pull/7116
|
@@ -14,7 +18,7 @@ elsif RUBY_VERSION == "3.2.0"
|
|
14
18
|
end
|
15
19
|
|
16
20
|
module StackProf
|
17
|
-
VERSION = '0.2.
|
21
|
+
VERSION = '0.2.26'
|
18
22
|
end
|
19
23
|
|
20
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.
|
3
|
+
s.version = '0.2.26'
|
4
4
|
s.homepage = 'http://github.com/tmm1/stackprof'
|
5
5
|
|
6
6
|
s.authors = 'Aman Gupta'
|
@@ -29,6 +29,5 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.license = 'MIT'
|
30
30
|
|
31
31
|
s.add_development_dependency 'rake-compiler', '~> 0.9'
|
32
|
-
s.add_development_dependency 'mocha', '~> 0.14'
|
33
32
|
s.add_development_dependency 'minitest', '~> 5.0'
|
34
33
|
end
|
data/test/test_middleware.rb
CHANGED
@@ -2,9 +2,9 @@ $:.unshift File.expand_path('../../lib', __FILE__)
|
|
2
2
|
require 'stackprof'
|
3
3
|
require 'stackprof/middleware'
|
4
4
|
require 'minitest/autorun'
|
5
|
-
require '
|
5
|
+
require 'tmpdir'
|
6
6
|
|
7
|
-
class StackProf::MiddlewareTest <
|
7
|
+
class StackProf::MiddlewareTest < Minitest::Test
|
8
8
|
|
9
9
|
def test_path_default
|
10
10
|
StackProf::Middleware.new(Object.new)
|
@@ -19,23 +19,36 @@ class StackProf::MiddlewareTest < MiniTest::Test
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_save_default
|
22
|
-
StackProf::Middleware.new(Object.new
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
middleware = StackProf::Middleware.new(->(env) { 100.times { Object.new } },
|
23
|
+
save_every: 1,
|
24
|
+
enabled: true)
|
25
|
+
Dir.mktmpdir do |dir|
|
26
|
+
Dir.chdir(dir) { middleware.call({}) }
|
27
|
+
dir = File.join(dir, "tmp")
|
28
|
+
assert File.directory? dir
|
29
|
+
profile = Dir.entries(dir).reject { |x| File.directory?(x) }.first
|
30
|
+
assert profile
|
31
|
+
assert_equal "stackprof", profile.split("-")[0]
|
32
|
+
assert_equal "cpu", profile.split("-")[1]
|
33
|
+
assert_equal Process.pid.to_s, profile.split("-")[2]
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
31
37
|
def test_save_custom
|
32
|
-
StackProf::Middleware.new(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
middleware = StackProf::Middleware.new(->(env) { 100.times { Object.new } },
|
39
|
+
path: "foo/",
|
40
|
+
save_every: 1,
|
41
|
+
enabled: true)
|
42
|
+
Dir.mktmpdir do |dir|
|
43
|
+
Dir.chdir(dir) { middleware.call({}) }
|
44
|
+
dir = File.join(dir, "foo")
|
45
|
+
assert File.directory? dir
|
46
|
+
profile = Dir.entries(dir).reject { |x| File.directory?(x) }.first
|
47
|
+
assert profile
|
48
|
+
assert_equal "stackprof", profile.split("-")[0]
|
49
|
+
assert_equal "cpu", profile.split("-")[1]
|
50
|
+
assert_equal Process.pid.to_s, profile.split("-")[2]
|
51
|
+
end
|
39
52
|
end
|
40
53
|
|
41
54
|
def test_enabled_should_use_a_proc_if_passed
|
@@ -70,4 +83,4 @@ class StackProf::MiddlewareTest < MiniTest::Test
|
|
70
83
|
StackProf::Middleware.new(Object.new, metadata: metadata)
|
71
84
|
assert_equal metadata, StackProf::Middleware.metadata
|
72
85
|
end
|
73
|
-
end
|
86
|
+
end unless RUBY_ENGINE == 'truffleruby'
|
data/test/test_report.rb
CHANGED
@@ -2,7 +2,7 @@ $:.unshift File.expand_path('../../lib', __FILE__)
|
|
2
2
|
require 'stackprof'
|
3
3
|
require 'minitest/autorun'
|
4
4
|
|
5
|
-
class ReportDumpTest <
|
5
|
+
class ReportDumpTest < Minitest::Test
|
6
6
|
require 'stringio'
|
7
7
|
|
8
8
|
def test_dump_to_stdout
|
@@ -33,7 +33,7 @@ class ReportDumpTest < MiniTest::Test
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
class ReportReadTest <
|
36
|
+
class ReportReadTest < Minitest::Test
|
37
37
|
require 'pathname'
|
38
38
|
|
39
39
|
def test_from_file_read_json
|
data/test/test_stackprof.rb
CHANGED
@@ -4,7 +4,7 @@ require 'minitest/autorun'
|
|
4
4
|
require 'tempfile'
|
5
5
|
require 'pathname'
|
6
6
|
|
7
|
-
class StackProfTest <
|
7
|
+
class StackProfTest < Minitest::Test
|
8
8
|
def setup
|
9
9
|
Object.new # warm some caches to avoid flakiness
|
10
10
|
end
|
@@ -93,6 +93,7 @@ class StackProfTest < MiniTest::Test
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def test_walltime
|
96
|
+
GC.disable
|
96
97
|
profile = StackProf.run(mode: :wall) do
|
97
98
|
idle
|
98
99
|
end
|
@@ -104,6 +105,8 @@ class StackProfTest < MiniTest::Test
|
|
104
105
|
assert_equal "StackProfTest#idle", frame[:name]
|
105
106
|
end
|
106
107
|
assert_in_delta 200, frame[:samples], 25
|
108
|
+
ensure
|
109
|
+
GC.enable
|
107
110
|
end
|
108
111
|
|
109
112
|
def test_custom
|
@@ -142,10 +145,13 @@ class StackProfTest < MiniTest::Test
|
|
142
145
|
after_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
143
146
|
|
144
147
|
raw = profile[:raw]
|
148
|
+
raw_lines = profile[:raw_lines]
|
145
149
|
assert_equal 10, raw[-1]
|
146
150
|
assert_equal raw[0] + 2, raw.size
|
151
|
+
assert_equal 10, raw_lines[-1] # seen 10 times
|
147
152
|
|
148
153
|
offset = RUBY_VERSION >= '3' ? -3 : -2
|
154
|
+
assert_equal 140, raw_lines[offset] # sample caller is on 140
|
149
155
|
assert_includes profile[:frames][raw[offset]][:name], 'StackProfTest#test_raw'
|
150
156
|
|
151
157
|
assert_equal 10, profile[:raw_sample_timestamps].size
|
@@ -241,8 +247,12 @@ class StackProfTest < MiniTest::Test
|
|
241
247
|
assert marking_frame
|
242
248
|
assert sweeping_frame
|
243
249
|
|
244
|
-
|
245
|
-
|
250
|
+
# We can't guarantee a certain number of GCs to run, so just assert
|
251
|
+
# that it's within some kind of delta
|
252
|
+
assert_in_delta gc_frame[:total_samples], profile[:gc_samples], 2
|
253
|
+
|
254
|
+
# Lazy marking / sweeping can cause this math to not add up, so also use a delta
|
255
|
+
assert_in_delta profile[:gc_samples], [gc_frame, marking_frame, sweeping_frame].map{|x| x[:samples] }.inject(:+), 2
|
246
256
|
|
247
257
|
assert_operator profile[:gc_samples], :>, 0
|
248
258
|
assert_operator profile[:missed_samples], :<=, 25
|
data/test/test_truffleruby.rb
CHANGED
@@ -3,7 +3,7 @@ require 'stackprof'
|
|
3
3
|
require 'minitest/autorun'
|
4
4
|
|
5
5
|
if RUBY_ENGINE == 'truffleruby'
|
6
|
-
class StackProfTruffleRubyTest <
|
6
|
+
class StackProfTruffleRubyTest < Minitest::Test
|
7
7
|
def test_error
|
8
8
|
error = assert_raises RuntimeError do
|
9
9
|
StackProf.run(mode: :cpu) do
|
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.26
|
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: 2024-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.9'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: mocha
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.14'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.14'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: minitest
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -99,9 +85,9 @@ licenses:
|
|
99
85
|
- MIT
|
100
86
|
metadata:
|
101
87
|
bug_tracker_uri: https://github.com/tmm1/stackprof/issues
|
102
|
-
changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.
|
103
|
-
documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.
|
104
|
-
source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.
|
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
|
105
91
|
post_install_message:
|
106
92
|
rdoc_options: []
|
107
93
|
require_paths:
|