stackprof 0.2.18 → 0.2.21

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: 2c802ba8d0dfa813e939679507d70376e7dc33924369bb9d767523cfa5faed31
4
- data.tar.gz: 5defbe29a222a82e3b6901ed10b0c73d962ed6a650b14b0c3cd3c231c27c0e39
3
+ metadata.gz: 33a342cae97be870781375a647e95936c6a6016ae711f27d60e41a891cb809f4
4
+ data.tar.gz: 867d55b7c7cdfc928ae35b3c36474bdda41ed4c4dc8e8b661b0c6580890b0b28
5
5
  SHA512:
6
- metadata.gz: 0e585cf066f4008907975726f22185184210e375ca6fe28e51fe44fca7fe53e2cdc414482dcba3e413da2238a879cb5fa58530106ad5f8cb217fc6cee1bf4a2b
7
- data.tar.gz: 7b2d08fbd2d70c803f06f42c15e7978871f01c343f3d149f2a63b781d4e074542f445e9a809bdaab1921e779ec9d868159fedc752103faf86c45ddfe7239b74a
6
+ metadata.gz: 255835983ab93b52f7d1118a076f1881511c27b82a6db9e3f5db909f888bb563b26b855fc49dacbd0ef0b41f77af6c7638bf15721ff5085d012e916d4871592f
7
+ data.tar.gz: 7520e06a5bdf8fb240538b9a333f265675cc4056bc24b88c06d0d8c2e7cd5185346be6268707fe00f7f41bf6f8c8d70abc3925e53ac6684889ef4dc9e3ef6afd
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
11
- ruby: [ ruby-head, '3.0', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2' ]
11
+ ruby: [ ruby-head, '3.1', '3.0', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2', truffleruby ]
12
12
  steps:
13
13
  - name: Checkout
14
14
  uses: actions/checkout@v2
data/Rakefile CHANGED
@@ -7,11 +7,21 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList["test/**/test_*.rb"]
8
8
  end
9
9
 
10
- require "rake/extensiontask"
10
+ if RUBY_ENGINE == "truffleruby"
11
+ task :compile do
12
+ # noop
13
+ end
11
14
 
12
- Rake::ExtensionTask.new("stackprof") do |ext|
13
- ext.ext_dir = "ext/stackprof"
14
- ext.lib_dir = "lib/stackprof"
15
+ task :clean do
16
+ # noop
17
+ end
18
+ else
19
+ require "rake/extensiontask"
20
+
21
+ Rake::ExtensionTask.new("stackprof") do |ext|
22
+ ext.ext_dir = "ext/stackprof"
23
+ ext.lib_dir = "lib/stackprof"
24
+ end
15
25
  end
16
26
 
17
27
  task default: %i(compile test)
data/bin/stackprof CHANGED
@@ -42,7 +42,7 @@ reports = []
42
42
  while ARGV.size > 0
43
43
  begin
44
44
  file = ARGV.pop
45
- reports << StackProf::Report.new(Marshal.load(IO.binread(file)))
45
+ reports << StackProf::Report.from_file(file)
46
46
  rescue TypeError => e
47
47
  STDERR.puts "** error parsing #{file}: #{e.inspect}"
48
48
  end
@@ -1,4 +1,10 @@
1
1
  require 'mkmf'
2
+
3
+ if RUBY_ENGINE == 'truffleruby'
4
+ File.write('Makefile', dummy_makefile($srcdir).join(""))
5
+ return
6
+ end
7
+
2
8
  if have_func('rb_postponed_job_register_one') &&
3
9
  have_func('rb_profile_frames') &&
4
10
  have_func('rb_tracepoint_new') &&
@@ -25,20 +25,14 @@
25
25
  #define FAKE_FRAME_MARK INT2FIX(1)
26
26
  #define FAKE_FRAME_SWEEP INT2FIX(2)
27
27
 
28
- /*
29
- * As of Ruby 3.0, it should be safe to read stack frames at any time
30
- * See https://github.com/ruby/ruby/commit/0e276dc458f94d9d79a0f7c7669bde84abe80f21
31
- */
32
- #if RUBY_API_VERSION_MAJOR < 3
33
- #define USE_POSTPONED_JOB
34
- #endif
35
-
36
28
  static const char *fake_frame_cstrs[] = {
37
29
  "(garbage collection)",
38
30
  "(marking)",
39
31
  "(sweeping)",
40
32
  };
41
33
 
34
+ static int stackprof_use_postponed_job = 1;
35
+
42
36
  #define TOTAL_FAKE_FRAMES (sizeof(fake_frame_cstrs) / sizeof(char *))
43
37
 
44
38
  #ifdef _POSIX_MONOTONIC_CLOCK
@@ -152,6 +146,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
152
146
  VALUE opts = Qnil, mode = Qnil, interval = Qnil, metadata = rb_hash_new(), out = Qfalse;
153
147
  int ignore_gc = 0;
154
148
  int raw = 0, aggregate = 1;
149
+ VALUE metadata_val;
155
150
 
156
151
  if (_stackprof.running)
157
152
  return Qfalse;
@@ -166,7 +161,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
166
161
  ignore_gc = 1;
167
162
  }
168
163
 
169
- VALUE metadata_val = rb_hash_aref(opts, sym_metadata);
164
+ metadata_val = rb_hash_aref(opts, sym_metadata);
170
165
  if (RTEST(metadata_val)) {
171
166
  if (!RB_TYPE_P(metadata_val, T_HASH))
172
167
  rb_raise(rb_eArgError, "metadata should be a hash");
@@ -603,19 +598,20 @@ stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t ti
603
598
  void
604
599
  stackprof_buffer_sample(void)
605
600
  {
601
+ uint64_t start_timestamp = 0;
602
+ int64_t timestamp_delta = 0;
603
+ int num;
604
+
606
605
  if (_stackprof.buffer_count > 0) {
607
606
  // Another sample is already pending
608
607
  return;
609
608
  }
610
609
 
611
- uint64_t start_timestamp = 0;
612
- int64_t timestamp_delta = 0;
613
- int num;
614
610
  if (_stackprof.raw) {
615
611
  struct timestamp_t t;
616
612
  capture_timestamp(&t);
617
613
  start_timestamp = timestamp_usec(&t);
618
- timestamp_delta = delta_usec(&t, &_stackprof.last_sample_at);
614
+ timestamp_delta = delta_usec(&_stackprof.last_sample_at, &t);
619
615
  }
620
616
 
621
617
  num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer) / sizeof(VALUE), _stackprof.frames_buffer, _stackprof.lines_buffer);
@@ -638,7 +634,7 @@ stackprof_record_gc_samples(void)
638
634
 
639
635
  // We don't know when the GC samples were actually marked, so let's
640
636
  // assume that they were marked at a perfectly regular interval.
641
- delta_to_first_unrecorded_gc_sample = delta_usec(&t, &_stackprof.last_sample_at) - (_stackprof.unrecorded_gc_samples - 1) * NUM2LONG(_stackprof.interval);
637
+ delta_to_first_unrecorded_gc_sample = delta_usec(&_stackprof.last_sample_at, &t) - (_stackprof.unrecorded_gc_samples - 1) * NUM2LONG(_stackprof.interval);
642
638
  if (delta_to_first_unrecorded_gc_sample < 0) {
643
639
  delta_to_first_unrecorded_gc_sample = 0;
644
640
  }
@@ -701,7 +697,6 @@ stackprof_job_record_gc(void *data)
701
697
  stackprof_record_gc_samples();
702
698
  }
703
699
 
704
- #ifdef USE_POSTPONED_JOB
705
700
  static void
706
701
  stackprof_job_sample_and_record(void *data)
707
702
  {
@@ -709,7 +704,6 @@ stackprof_job_sample_and_record(void *data)
709
704
 
710
705
  stackprof_sample_and_record();
711
706
  }
712
- #endif
713
707
 
714
708
  static void
715
709
  stackprof_job_record_buffer(void *data)
@@ -740,15 +734,15 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
740
734
  _stackprof.unrecorded_gc_samples++;
741
735
  rb_postponed_job_register_one(0, stackprof_job_record_gc, (void*)0);
742
736
  } else {
743
- #ifdef USE_POSTPONED_JOB
744
- rb_postponed_job_register_one(0, stackprof_job_sample_and_record, (void*)0);
745
- #else
746
- // Buffer a sample immediately, if an existing sample exists this will
747
- // return immediately
748
- stackprof_buffer_sample();
749
- // Enqueue a job to record the sample
750
- rb_postponed_job_register_one(0, stackprof_job_record_buffer, (void*)0);
751
- #endif
737
+ if (stackprof_use_postponed_job) {
738
+ rb_postponed_job_register_one(0, stackprof_job_sample_and_record, (void*)0);
739
+ } else {
740
+ // Buffer a sample immediately, if an existing sample exists this will
741
+ // return immediately
742
+ stackprof_buffer_sample();
743
+ // Enqueue a job to record the sample
744
+ rb_postponed_job_register_one(0, stackprof_job_record_buffer, (void*)0);
745
+ }
752
746
  }
753
747
  pthread_mutex_unlock(&lock);
754
748
  }
@@ -826,10 +820,23 @@ stackprof_atfork_child(void)
826
820
  stackprof_stop(rb_mStackProf);
827
821
  }
828
822
 
823
+ static VALUE
824
+ stackprof_use_postponed_job_l(VALUE self)
825
+ {
826
+ stackprof_use_postponed_job = 1;
827
+ return Qnil;
828
+ }
829
+
829
830
  void
830
831
  Init_stackprof(void)
831
832
  {
832
833
  size_t i;
834
+ /*
835
+ * As of Ruby 3.0, it should be safe to read stack frames at any time, unless YJIT is enabled
836
+ * See https://github.com/ruby/ruby/commit/0e276dc458f94d9d79a0f7c7669bde84abe80f21
837
+ */
838
+ stackprof_use_postponed_job = RUBY_API_VERSION_MAJOR < 3;
839
+
833
840
  #define S(name) sym_##name = ID2SYM(rb_intern(#name));
834
841
  S(object);
835
842
  S(custom);
@@ -890,6 +897,7 @@ Init_stackprof(void)
890
897
  rb_define_singleton_method(rb_mStackProf, "stop", stackprof_stop, 0);
891
898
  rb_define_singleton_method(rb_mStackProf, "results", stackprof_results, -1);
892
899
  rb_define_singleton_method(rb_mStackProf, "sample", stackprof_sample, 0);
900
+ rb_define_singleton_method(rb_mStackProf, "use_postponed_job!", stackprof_use_postponed_job_l, 0);
893
901
 
894
902
  pthread_atfork(stackprof_atfork_prepare, stackprof_atfork_parent, stackprof_atfork_child);
895
903
  }
@@ -1,10 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pp'
4
- require 'digest/md5'
4
+ require 'digest/sha2'
5
+ require 'json'
5
6
 
6
7
  module StackProf
7
8
  class Report
9
+ MARSHAL_SIGNATURE = "\x04\x08"
10
+
11
+ class << self
12
+ def from_file(file)
13
+ if (content = IO.binread(file)).start_with?(MARSHAL_SIGNATURE)
14
+ new(Marshal.load(content))
15
+ else
16
+ from_json(JSON.parse(content))
17
+ end
18
+ end
19
+
20
+ def from_json(json)
21
+ new(parse_json(json))
22
+ end
23
+
24
+ def parse_json(json)
25
+ json.keys.each do |key|
26
+ value = json.delete(key)
27
+ from_json(value) if value.is_a?(Hash)
28
+
29
+ new_key = case key
30
+ when /\A[0-9]*\z/
31
+ key.to_i
32
+ else
33
+ key.to_sym
34
+ end
35
+
36
+ json[new_key] = value
37
+ end
38
+ json
39
+ end
40
+ end
41
+
8
42
  def initialize(data)
9
43
  @data = data
10
44
  end
@@ -18,7 +52,7 @@ module StackProf
18
52
  def normalized_frames
19
53
  id2hash = {}
20
54
  @data[:frames].each do |frame, info|
21
- id2hash[frame.to_s] = info[:hash] = Digest::MD5.hexdigest("#{info[:name]}#{info[:file]}#{info[:line]}")
55
+ id2hash[frame.to_s] = info[:hash] = Digest::SHA256.hexdigest("#{info[:name]}#{info[:file]}#{info[:line]}")
22
56
  end
23
57
  @data[:frames].inject(Hash.new) do |hash, (frame, info)|
24
58
  info = hash[id2hash[frame.to_s]] = info.dup
@@ -191,7 +225,7 @@ module StackProf
191
225
  end
192
226
  else
193
227
  frame = @data[:frames][val]
194
- child_name = "#{ frame[:name] } : #{ frame[:file] }"
228
+ child_name = "#{ frame[:name] } : #{ frame[:file] } : #{ frame[:line] }"
195
229
  child_data = convert_to_d3_flame_graph_format(child_name, child_stacks, depth + 1)
196
230
  weight += child_data["value"]
197
231
  children << child_data
@@ -0,0 +1,37 @@
1
+ module StackProf
2
+ # Define the same methods as stackprof.c
3
+ class << self
4
+ def running?
5
+ false
6
+ end
7
+
8
+ def run(*args)
9
+ unimplemented
10
+ end
11
+
12
+ def start(*args)
13
+ unimplemented
14
+ end
15
+
16
+ def stop
17
+ unimplemented
18
+ end
19
+
20
+ def results(*args)
21
+ unimplemented
22
+ end
23
+
24
+ def sample
25
+ unimplemented
26
+ end
27
+
28
+ def use_postponed_job!
29
+ # noop
30
+ end
31
+
32
+ private def unimplemented
33
+ raise "Use --cpusampler=flamegraph or --cpusampler instead of StackProf on TruffleRuby.\n" \
34
+ "See https://www.graalvm.org/tools/profiling/ and `ruby --help:cpusampler` for more details."
35
+ end
36
+ end
37
+ end
data/lib/stackprof.rb CHANGED
@@ -1,7 +1,15 @@
1
- require "stackprof/stackprof"
1
+ if RUBY_ENGINE == 'truffleruby'
2
+ require "stackprof/truffleruby"
3
+ else
4
+ require "stackprof/stackprof"
5
+ end
6
+
7
+ if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
8
+ StackProf.use_postponed_job!
9
+ end
2
10
 
3
11
  module StackProf
4
- VERSION = '0.2.18'
12
+ VERSION = '0.2.21'
5
13
  end
6
14
 
7
15
  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.18'
3
+ s.version = '0.2.21'
4
4
  s.homepage = 'http://github.com/tmm1/stackprof'
5
5
 
6
6
  s.authors = 'Aman Gupta'
@@ -0,0 +1 @@
1
+ {: modeI"cpu:ET
@@ -0,0 +1 @@
1
+ { "mode": "cpu" }
data/test/test_report.rb CHANGED
@@ -32,3 +32,27 @@ class ReportDumpTest < MiniTest::Test
32
32
  assert_equal expected, Marshal.load(marshal_data)
33
33
  end
34
34
  end
35
+
36
+ class ReportReadTest < MiniTest::Test
37
+ require 'pathname'
38
+
39
+ def test_from_file_read_json
40
+ file = fixture("profile.json")
41
+ report = StackProf::Report.from_file(file)
42
+
43
+ assert_equal({ mode: "cpu" }, report.data)
44
+ end
45
+
46
+ def test_from_file_read_marshal
47
+ file = fixture("profile.dump")
48
+ report = StackProf::Report.from_file(file)
49
+
50
+ assert_equal({ mode: "cpu" }, report.data)
51
+ end
52
+
53
+ private
54
+
55
+ def fixture(name)
56
+ Pathname.new(__dir__).join("fixtures", name)
57
+ end
58
+ end
@@ -5,6 +5,10 @@ require 'tempfile'
5
5
  require 'pathname'
6
6
 
7
7
  class StackProfTest < MiniTest::Test
8
+ def setup
9
+ Object.new # warm some caches to avoid flakiness
10
+ end
11
+
8
12
  def test_info
9
13
  profile = StackProf.run{}
10
14
  assert_equal 1.2, profile[:version]
@@ -131,6 +135,7 @@ class StackProfTest < MiniTest::Test
131
135
  profile = StackProf.run(mode: :custom, raw: true) do
132
136
  10.times do
133
137
  StackProf.sample
138
+ sleep 0.0001
134
139
  end
135
140
  end
136
141
 
@@ -153,6 +158,10 @@ class StackProfTest < MiniTest::Test
153
158
  assert_equal 10, profile[:raw_timestamp_deltas].size
154
159
  total_duration = after_monotonic - before_monotonic
155
160
  assert_operator profile[:raw_timestamp_deltas].inject(&:+), :<, total_duration
161
+
162
+ profile[:raw_timestamp_deltas].each do |delta|
163
+ assert_operator delta, :>, 0
164
+ end
156
165
  end
157
166
 
158
167
  def test_metadata
@@ -299,4 +308,4 @@ class StackProfTest < MiniTest::Test
299
308
  r.close
300
309
  w.close
301
310
  end
302
- end
311
+ end unless RUBY_ENGINE == 'truffleruby'
@@ -0,0 +1,18 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'stackprof'
3
+ require 'minitest/autorun'
4
+
5
+ if RUBY_ENGINE == 'truffleruby'
6
+ class StackProfTruffleRubyTest < MiniTest::Test
7
+ def test_error
8
+ error = assert_raises RuntimeError do
9
+ StackProf.run(mode: :cpu) do
10
+ unreacheable
11
+ end
12
+ end
13
+
14
+ assert_match(/TruffleRuby/, error.message)
15
+ assert_match(/--cpusampler/, error.message)
16
+ end
17
+ end
18
+ end
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.18
4
+ version: 0.2.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aman Gupta
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-16 00:00:00.000000000 Z
11
+ date: 2022-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -80,11 +80,15 @@ files:
80
80
  - lib/stackprof/flamegraph/viewer.html
81
81
  - lib/stackprof/middleware.rb
82
82
  - lib/stackprof/report.rb
83
+ - lib/stackprof/truffleruby.rb
83
84
  - sample.rb
84
85
  - stackprof.gemspec
86
+ - test/fixtures/profile.dump
87
+ - test/fixtures/profile.json
85
88
  - test/test_middleware.rb
86
89
  - test/test_report.rb
87
90
  - test/test_stackprof.rb
91
+ - test/test_truffleruby.rb
88
92
  - vendor/FlameGraph/README
89
93
  - vendor/FlameGraph/flamegraph.pl
90
94
  - vendor/gprof2dot/gprof2dot.py
@@ -94,10 +98,10 @@ licenses:
94
98
  - MIT
95
99
  metadata:
96
100
  bug_tracker_uri: https://github.com/tmm1/stackprof/issues
97
- changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.18/CHANGELOG.md
98
- documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.18
99
- source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.18
100
- post_install_message:
101
+ changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.21/CHANGELOG.md
102
+ documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.21
103
+ source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.21
104
+ post_install_message:
101
105
  rdoc_options: []
102
106
  require_paths:
103
107
  - lib
@@ -112,8 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
116
  - !ruby/object:Gem::Version
113
117
  version: '0'
114
118
  requirements: []
115
- rubygems_version: 3.0.3.1
116
- signing_key:
119
+ rubygems_version: 3.4.0.dev
120
+ signing_key:
117
121
  specification_version: 4
118
122
  summary: sampling callstack-profiler for ruby 2.2+
119
123
  test_files: []