stackprof 0.2.24 → 0.2.26

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90e33127951d53147b6a65704446ec812999df9525d868865029ee1c7450445d
4
- data.tar.gz: 19a5f104482f355cb1957b71b18b15b9ca9bb981cfcc109688366e53704c5711
3
+ metadata.gz: 3165c34411b9287741eb15e8c84bec239792a81aaf30695d2e735b0bee41a9d8
4
+ data.tar.gz: 6cf3bd55b6b14fcd880facfb281ded85e03d1ec9d79fc1b0c4fcb5b592df014d
5
5
  SHA512:
6
- metadata.gz: 17610238200852f97afcc730efd9171c46c3a001ff7d5b82cb65fc92f96d9d2b206474bcecc7eeee1981365d2a07fd4dad460edc54a8bc9c8b1e6c7412f7cde3
7
- data.tar.gz: 35cc1394b526edd0782468b06d54f66eb1ff10e5741becf486f68f0b141511fad9db622816004a4ae2e1a59cff048abe2d5697848eff127640663e9246f33cca
6
+ metadata.gz: '086238ddefcd029b17a7de36d0bdce624b8cda367c1dd5ab55348f9332388c49fc326f01ba66d7d11d31d0014210d1474013aa106d1432da4605334551772ba2'
7
+ data.tar.gz: a4c203dc55aa3da32744114a2f899982d548ce1c4c90daf4da3808bcae725fcff21775a4fee3d303724f96418b5bd1c748807dce7d28e84dcde8fbd61ea90932
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
11
- ruby: [ ruby-head, '3.1', '3.0', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2', truffleruby ]
11
+ ruby: [ ruby-head, '3.2', '3.1', '3.0', '2.7', truffleruby ]
12
12
  steps:
13
13
  - name: Checkout
14
14
  uses: actions/checkout@v2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 0.2.25
2
+
3
+ * Fix GC marking
4
+
1
5
  # 0.2.16
2
6
 
3
7
  * [flamegraph.pl] Update to latest version
@@ -102,7 +102,7 @@ static struct {
102
102
  VALUE metadata;
103
103
  int ignore_gc;
104
104
 
105
- VALUE *raw_samples;
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
- if (_stackprof.raw_samples[_stackprof.raw_sample_index + 1 + n] != frame)
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.raw_samples[_stackprof.raw_samples_len++] = frame;
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
- StackProf.use_postponed_job!
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.24'
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.24'
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
@@ -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 'mocha/setup'
5
+ require 'tmpdir'
6
6
 
7
- class StackProf::MiddlewareTest < MiniTest::Test
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
- StackProf.stubs(:results).returns({ mode: 'foo' })
25
- FileUtils.expects(:mkdir_p).with('tmp/')
26
- File.expects(:open).with(regexp_matches(/^tmp\/stackprof-foo/), 'wb')
27
-
28
- StackProf::Middleware.save
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(Object.new, { path: 'foo/' })
33
-
34
- StackProf.stubs(:results).returns({ mode: 'foo' })
35
- FileUtils.expects(:mkdir_p).with('foo/')
36
- File.expects(:open).with(regexp_matches(/^foo\/stackprof-foo/), 'wb')
37
-
38
- StackProf::Middleware.save
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 < MiniTest::Test
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 < MiniTest::Test
36
+ class ReportReadTest < Minitest::Test
37
37
  require 'pathname'
38
38
 
39
39
  def test_from_file_read_json
@@ -4,7 +4,7 @@ require 'minitest/autorun'
4
4
  require 'tempfile'
5
5
  require 'pathname'
6
6
 
7
- class StackProfTest < MiniTest::Test
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
- assert_equal gc_frame[:total_samples], profile[:gc_samples]
245
- assert_equal profile[:gc_samples], [gc_frame, marking_frame, sweeping_frame].map{|x| x[:samples] }.inject(:+)
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
@@ -3,7 +3,7 @@ require 'stackprof'
3
3
  require 'minitest/autorun'
4
4
 
5
5
  if RUBY_ENGINE == 'truffleruby'
6
- class StackProfTruffleRubyTest < MiniTest::Test
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.24
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: 2023-03-20 00:00:00.000000000 Z
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.24/CHANGELOG.md
103
- documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.24
104
- source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.24
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: