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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b299eb696cf0c2748e931532afab3f90bab1c94447ff1c844bce0eda878a93c6
4
- data.tar.gz: bd2c389baa8253fc06beda425abc87762b9bca425c6bc5046655610fb9852e79
3
+ metadata.gz: 3165c34411b9287741eb15e8c84bec239792a81aaf30695d2e735b0bee41a9d8
4
+ data.tar.gz: 6cf3bd55b6b14fcd880facfb281ded85e03d1ec9d79fc1b0c4fcb5b592df014d
5
5
  SHA512:
6
- metadata.gz: 322e506fc77bd964f39e7b3f0c5584e57efdec8e1dcc432cdea924536e334234d916a1d5e1ff085e2e5210a49751897ca36a4b1bd007468a5b16184151129be3
7
- data.tar.gz: 3234d4159c78119a9238200d1f4516280cbd1e0cfe3affca1c4fa4827b805ecf154e38857c917fafe7aed042d67525bb7e6c5fce85a039e30cde4672ae08f97e
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
@@ -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 {
@@ -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
- for (int i = 0; i < _stackprof.buffer_count; i++) {
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
- 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.25'
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.25'
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.25
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-04-06 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.25/CHANGELOG.md
103
- documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.25
104
- source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.25
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: