stackprof 0.2.18 → 0.2.21

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: 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: []