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.
@@ -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
- assert_equal 2, profile[:samples]
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
- assert_equal [1, 1], frame[:lines][profile_base_line+1]
48
- assert_equal [1, 1], frame[:lines][profile_base_line+2]
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
- assert_equal [2, 0], frame[:lines][profile_base_line]
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
- frame = profile[:frames].values.first
67
- assert_includes frame[:name], "StackProfTest#math"
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
- assert_equal "StackProfTest#idle", frame[:name]
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
- frame = profile[:frames].values.first
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
- assert_equal [10, 10], frame[:lines][profile_base_line+2]
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
- assert_includes profile[:frames][raw[-2]][:name], 'StackProfTest#test_raw'
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
- assert_equal gc_frame[:samples], profile[:gc_samples]
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], :<=, 10
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