stackprof 0.2.24 → 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/CHANGELOG.md +4 -0
- data/ext/stackprof/stackprof.c +56 -9
- 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/CHANGELOG.md
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 {
|
@@ -811,6 +837,17 @@ stackprof_gc_mark(void *data)
|
|
811
837
|
|
812
838
|
if (_stackprof.frames)
|
813
839
|
st_foreach(_stackprof.frames, frame_mark_i, 0);
|
840
|
+
|
841
|
+
int i;
|
842
|
+
for (i = 0; i < _stackprof.buffer_count; i++) {
|
843
|
+
rb_gc_mark(_stackprof.frames_buffer[i]);
|
844
|
+
}
|
845
|
+
}
|
846
|
+
|
847
|
+
static size_t
|
848
|
+
stackprof_memsize(const void *data)
|
849
|
+
{
|
850
|
+
return sizeof(_stackprof);
|
814
851
|
}
|
815
852
|
|
816
853
|
static void
|
@@ -858,6 +895,15 @@ stackprof_at_exit(ruby_vm_t* vm)
|
|
858
895
|
ruby_vm_running = 0;
|
859
896
|
}
|
860
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
|
+
|
861
907
|
void
|
862
908
|
Init_stackprof(void)
|
863
909
|
{
|
@@ -889,6 +935,7 @@ Init_stackprof(void)
|
|
889
935
|
S(mode);
|
890
936
|
S(interval);
|
891
937
|
S(raw);
|
938
|
+
S(raw_lines);
|
892
939
|
S(raw_sample_timestamps);
|
893
940
|
S(raw_timestamp_deltas);
|
894
941
|
S(out);
|
@@ -904,8 +951,8 @@ Init_stackprof(void)
|
|
904
951
|
/* Need to run this to warm the symbol table before we call this during GC */
|
905
952
|
rb_gc_latest_gc_info(sym_state);
|
906
953
|
|
907
|
-
gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, &_stackprof);
|
908
954
|
rb_global_variable(&gc_hook);
|
955
|
+
gc_hook = TypedData_Wrap_Struct(rb_cObject, &stackprof_type, &_stackprof);
|
909
956
|
|
910
957
|
_stackprof.raw_samples = NULL;
|
911
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:
|