stackprof 0.2.11 → 0.2.25
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 +5 -5
- data/.github/workflows/ci.yml +43 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +18 -0
- data/README.md +87 -68
- data/Rakefile +21 -25
- data/bin/stackprof +115 -71
- data/ext/stackprof/extconf.rb +6 -0
- data/ext/stackprof/stackprof.c +352 -90
- data/lib/stackprof/autorun.rb +19 -0
- data/lib/stackprof/middleware.rb +23 -7
- data/lib/stackprof/report.rb +282 -18
- data/lib/stackprof/truffleruby.rb +37 -0
- data/lib/stackprof.rb +18 -1
- data/stackprof.gemspec +11 -2
- data/test/fixtures/profile.dump +1 -0
- data/test/fixtures/profile.json +1 -0
- data/test/test_middleware.rb +13 -7
- data/test/test_report.rb +24 -0
- data/test/test_stackprof.rb +163 -14
- data/test/test_truffleruby.rb +18 -0
- data/vendor/FlameGraph/flamegraph.pl +751 -85
- metadata +17 -9
- data/.travis.yml +0 -8
- data/Gemfile.lock +0 -27
data/test/test_stackprof.rb
CHANGED
@@ -2,8 +2,13 @@ $:.unshift File.expand_path('../../lib', __FILE__)
|
|
2
2
|
require 'stackprof'
|
3
3
|
require 'minitest/autorun'
|
4
4
|
require 'tempfile'
|
5
|
+
require 'pathname'
|
5
6
|
|
6
7
|
class StackProfTest < MiniTest::Test
|
8
|
+
def setup
|
9
|
+
Object.new # warm some caches to avoid flakiness
|
10
|
+
end
|
11
|
+
|
7
12
|
def test_info
|
8
13
|
profile = StackProf.run{}
|
9
14
|
assert_equal 1.2, profile[:version]
|
@@ -38,16 +43,30 @@ class StackProfTest < MiniTest::Test
|
|
38
43
|
end
|
39
44
|
assert_equal :object, profile[:mode]
|
40
45
|
assert_equal 1, profile[:interval]
|
41
|
-
|
46
|
+
if RUBY_VERSION >= '3'
|
47
|
+
assert_equal 4, profile[:samples]
|
48
|
+
else
|
49
|
+
assert_equal 2, profile[:samples]
|
50
|
+
end
|
42
51
|
|
43
52
|
frame = profile[:frames].values.first
|
44
53
|
assert_includes frame[:name], "StackProfTest#test_object_allocation"
|
45
54
|
assert_equal 2, frame[:samples]
|
46
55
|
assert_includes [profile_base_line - 2, profile_base_line], frame[:line]
|
47
|
-
|
48
|
-
|
56
|
+
if RUBY_VERSION >= '3'
|
57
|
+
assert_equal [2, 1], frame[:lines][profile_base_line+1]
|
58
|
+
assert_equal [2, 1], frame[:lines][profile_base_line+2]
|
59
|
+
else
|
60
|
+
assert_equal [1, 1], frame[:lines][profile_base_line+1]
|
61
|
+
assert_equal [1, 1], frame[:lines][profile_base_line+2]
|
62
|
+
end
|
49
63
|
frame = profile[:frames].values[1] if RUBY_VERSION < '2.3'
|
50
|
-
|
64
|
+
|
65
|
+
if RUBY_VERSION >= '3'
|
66
|
+
assert_equal [4, 0], frame[:lines][profile_base_line]
|
67
|
+
else
|
68
|
+
assert_equal [2, 0], frame[:lines][profile_base_line]
|
69
|
+
end
|
51
70
|
end
|
52
71
|
|
53
72
|
def test_object_allocation_interval
|
@@ -63,8 +82,14 @@ class StackProfTest < MiniTest::Test
|
|
63
82
|
end
|
64
83
|
|
65
84
|
assert_operator profile[:samples], :>=, 1
|
66
|
-
|
67
|
-
|
85
|
+
if RUBY_VERSION >= '3'
|
86
|
+
assert profile[:frames].values.take(2).map { |f|
|
87
|
+
f[:name].include? "StackProfTest#math"
|
88
|
+
}.any?
|
89
|
+
else
|
90
|
+
frame = profile[:frames].values.first
|
91
|
+
assert_includes frame[:name], "StackProfTest#math"
|
92
|
+
end
|
68
93
|
end
|
69
94
|
|
70
95
|
def test_walltime
|
@@ -73,7 +98,11 @@ class StackProfTest < MiniTest::Test
|
|
73
98
|
end
|
74
99
|
|
75
100
|
frame = profile[:frames].values.first
|
76
|
-
|
101
|
+
if RUBY_VERSION >= '3'
|
102
|
+
assert_equal "IO.select", frame[:name]
|
103
|
+
else
|
104
|
+
assert_equal "StackProfTest#idle", frame[:name]
|
105
|
+
end
|
77
106
|
assert_in_delta 200, frame[:samples], 25
|
78
107
|
end
|
79
108
|
|
@@ -88,24 +117,81 @@ class StackProfTest < MiniTest::Test
|
|
88
117
|
assert_equal :custom, profile[:mode]
|
89
118
|
assert_equal 10, profile[:samples]
|
90
119
|
|
91
|
-
|
120
|
+
offset = RUBY_VERSION >= '3' ? 1 : 0
|
121
|
+
frame = profile[:frames].values[offset]
|
92
122
|
assert_includes frame[:name], "StackProfTest#test_custom"
|
93
123
|
assert_includes [profile_base_line-2, profile_base_line+1], frame[:line]
|
94
|
-
|
124
|
+
|
125
|
+
if RUBY_VERSION >= '3'
|
126
|
+
assert_equal [10, 0], frame[:lines][profile_base_line+2]
|
127
|
+
else
|
128
|
+
assert_equal [10, 10], frame[:lines][profile_base_line+2]
|
129
|
+
end
|
95
130
|
end
|
96
131
|
|
97
132
|
def test_raw
|
133
|
+
before_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
134
|
+
|
98
135
|
profile = StackProf.run(mode: :custom, raw: true) do
|
99
136
|
10.times do
|
100
137
|
StackProf.sample
|
138
|
+
sleep 0.0001
|
101
139
|
end
|
102
140
|
end
|
103
141
|
|
142
|
+
after_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
143
|
+
|
104
144
|
raw = profile[:raw]
|
105
145
|
assert_equal 10, raw[-1]
|
106
146
|
assert_equal raw[0] + 2, raw.size
|
107
|
-
|
147
|
+
|
148
|
+
offset = RUBY_VERSION >= '3' ? -3 : -2
|
149
|
+
assert_includes profile[:frames][raw[offset]][:name], 'StackProfTest#test_raw'
|
150
|
+
|
151
|
+
assert_equal 10, profile[:raw_sample_timestamps].size
|
152
|
+
profile[:raw_sample_timestamps].each_cons(2) do |t1, t2|
|
153
|
+
assert_operator t1, :>, before_monotonic
|
154
|
+
assert_operator t2, :>=, t1
|
155
|
+
assert_operator t2, :<, after_monotonic
|
156
|
+
end
|
157
|
+
|
108
158
|
assert_equal 10, profile[:raw_timestamp_deltas].size
|
159
|
+
total_duration = after_monotonic - before_monotonic
|
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
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_metadata
|
168
|
+
metadata = {
|
169
|
+
path: '/foo/bar',
|
170
|
+
revision: '5c0b01f1522ae8c194510977ae29377296dd236b',
|
171
|
+
}
|
172
|
+
profile = StackProf.run(mode: :cpu, metadata: metadata) do
|
173
|
+
math
|
174
|
+
end
|
175
|
+
|
176
|
+
assert_equal metadata, profile[:metadata]
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_empty_metadata
|
180
|
+
profile = StackProf.run(mode: :cpu) do
|
181
|
+
math
|
182
|
+
end
|
183
|
+
|
184
|
+
assert_equal({}, profile[:metadata])
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_raises_if_metadata_is_not_a_hash
|
188
|
+
exception = assert_raises ArgumentError do
|
189
|
+
StackProf.run(mode: :cpu, metadata: 'foobar') do
|
190
|
+
math
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
assert_equal 'metadata should be a hash', exception.message
|
109
195
|
end
|
110
196
|
|
111
197
|
def test_fork
|
@@ -119,6 +205,27 @@ class StackProfTest < MiniTest::Test
|
|
119
205
|
end
|
120
206
|
end
|
121
207
|
|
208
|
+
def foo(n = 10)
|
209
|
+
if n == 0
|
210
|
+
StackProf.sample
|
211
|
+
return
|
212
|
+
end
|
213
|
+
foo(n - 1)
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_recursive_total_samples
|
217
|
+
profile = StackProf.run(mode: :cpu, raw: true) do
|
218
|
+
10.times do
|
219
|
+
foo
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
frame = profile[:frames].values.find do |frame|
|
224
|
+
frame[:name] == "StackProfTest#foo"
|
225
|
+
end
|
226
|
+
assert_equal 10, frame[:total_samples]
|
227
|
+
end
|
228
|
+
|
122
229
|
def test_gc
|
123
230
|
profile = StackProf.run(interval: 100, raw: true) do
|
124
231
|
5.times do
|
@@ -126,12 +233,19 @@ class StackProfTest < MiniTest::Test
|
|
126
233
|
end
|
127
234
|
end
|
128
235
|
|
129
|
-
raw = profile[:raw]
|
130
236
|
gc_frame = profile[:frames].values.find{ |f| f[:name] == "(garbage collection)" }
|
237
|
+
marking_frame = profile[:frames].values.find{ |f| f[:name] == "(marking)" }
|
238
|
+
sweeping_frame = profile[:frames].values.find{ |f| f[:name] == "(sweeping)" }
|
239
|
+
|
131
240
|
assert gc_frame
|
132
|
-
|
241
|
+
assert marking_frame
|
242
|
+
assert sweeping_frame
|
243
|
+
|
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(:+)
|
246
|
+
|
133
247
|
assert_operator profile[:gc_samples], :>, 0
|
134
|
-
assert_operator profile[:missed_samples], :<=,
|
248
|
+
assert_operator profile[:missed_samples], :<=, 25
|
135
249
|
end
|
136
250
|
|
137
251
|
def test_out
|
@@ -146,6 +260,41 @@ class StackProfTest < MiniTest::Test
|
|
146
260
|
refute_empty profile[:frames]
|
147
261
|
end
|
148
262
|
|
263
|
+
def test_out_to_path_string
|
264
|
+
tmpfile = Tempfile.new('stackprof-out')
|
265
|
+
ret = StackProf.run(mode: :custom, out: tmpfile.path) do
|
266
|
+
StackProf.sample
|
267
|
+
end
|
268
|
+
|
269
|
+
refute_equal tmpfile, ret
|
270
|
+
assert_equal tmpfile.path, ret.path
|
271
|
+
tmpfile.rewind
|
272
|
+
profile = Marshal.load(tmpfile.read)
|
273
|
+
refute_empty profile[:frames]
|
274
|
+
end
|
275
|
+
|
276
|
+
def test_pathname_out
|
277
|
+
tmpfile = Tempfile.new('stackprof-out')
|
278
|
+
pathname = Pathname.new(tmpfile.path)
|
279
|
+
ret = StackProf.run(mode: :custom, out: pathname) do
|
280
|
+
StackProf.sample
|
281
|
+
end
|
282
|
+
|
283
|
+
assert_equal tmpfile.path, ret.path
|
284
|
+
tmpfile.rewind
|
285
|
+
profile = Marshal.load(tmpfile.read)
|
286
|
+
refute_empty profile[:frames]
|
287
|
+
end
|
288
|
+
|
289
|
+
def test_min_max_interval
|
290
|
+
[-1, 0, 1_000_000, 1_000_001].each do |invalid_interval|
|
291
|
+
err = assert_raises(ArgumentError, "invalid interval #{invalid_interval}") do
|
292
|
+
StackProf.run(interval: invalid_interval, debug: true) {}
|
293
|
+
end
|
294
|
+
assert_match(/microseconds/, err.message)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
149
298
|
def math
|
150
299
|
250_000.times do
|
151
300
|
2 ** 10
|
@@ -159,4 +308,4 @@ class StackProfTest < MiniTest::Test
|
|
159
308
|
r.close
|
160
309
|
w.close
|
161
310
|
end
|
162
|
-
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
|