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 +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/Rakefile +14 -4
- data/bin/stackprof +1 -1
- data/ext/stackprof/extconf.rb +6 -0
- data/ext/stackprof/stackprof.c +33 -25
- data/lib/stackprof/report.rb +37 -3
- data/lib/stackprof/truffleruby.rb +37 -0
- data/lib/stackprof.rb +10 -2
- data/stackprof.gemspec +1 -1
- data/test/fixtures/profile.dump +1 -0
- data/test/fixtures/profile.json +1 -0
- data/test/test_report.rb +24 -0
- data/test/test_stackprof.rb +10 -1
- data/test/test_truffleruby.rb +18 -0
- metadata +13 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33a342cae97be870781375a647e95936c6a6016ae711f27d60e41a891cb809f4
|
4
|
+
data.tar.gz: 867d55b7c7cdfc928ae35b3c36474bdda41ed4c4dc8e8b661b0c6580890b0b28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 255835983ab93b52f7d1118a076f1881511c27b82a6db9e3f5db909f888bb563b26b855fc49dacbd0ef0b41f77af6c7638bf15721ff5085d012e916d4871592f
|
7
|
+
data.tar.gz: 7520e06a5bdf8fb240538b9a333f265675cc4056bc24b88c06d0d8c2e7cd5185346be6268707fe00f7f41bf6f8c8d70abc3925e53ac6684889ef4dc9e3ef6afd
|
data/.github/workflows/ci.yml
CHANGED
@@ -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
|
-
|
10
|
+
if RUBY_ENGINE == "truffleruby"
|
11
|
+
task :compile do
|
12
|
+
# noop
|
13
|
+
end
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
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.
|
45
|
+
reports << StackProf::Report.from_file(file)
|
46
46
|
rescue TypeError => e
|
47
47
|
STDERR.puts "** error parsing #{file}: #{e.inspect}"
|
48
48
|
end
|
data/ext/stackprof/extconf.rb
CHANGED
data/ext/stackprof/stackprof.c
CHANGED
@@ -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
|
-
|
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(&
|
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(&
|
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
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
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
|
}
|
data/lib/stackprof/report.rb
CHANGED
@@ -1,10 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'pp'
|
4
|
-
require 'digest/
|
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::
|
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
|
-
|
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.
|
12
|
+
VERSION = '0.2.21'
|
5
13
|
end
|
6
14
|
|
7
15
|
StackProf.autoload :Report, "stackprof/report.rb"
|
data/stackprof.gemspec
CHANGED
@@ -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
|
data/test/test_stackprof.rb
CHANGED
@@ -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.
|
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-
|
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.
|
98
|
-
documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.
|
99
|
-
source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.
|
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.
|
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: []
|