stackprof 0.2.12 → 0.2.26

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'
6
+
7
+ class StackProfTest < Minitest::Test
8
+ def setup
9
+ Object.new # warm some caches to avoid flakiness
10
+ end
5
11
 
6
- class StackProfTest < MiniTest::Test
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,18 +82,31 @@ 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
96
+ GC.disable
71
97
  profile = StackProf.run(mode: :wall) do
72
98
  idle
73
99
  end
74
100
 
75
101
  frame = profile[:frames].values.first
76
- assert_equal "StackProfTest#idle", frame[:name]
102
+ if RUBY_VERSION >= '3'
103
+ assert_equal "IO.select", frame[:name]
104
+ else
105
+ assert_equal "StackProfTest#idle", frame[:name]
106
+ end
77
107
  assert_in_delta 200, frame[:samples], 25
108
+ ensure
109
+ GC.enable
78
110
  end
79
111
 
80
112
  def test_custom
@@ -88,24 +120,84 @@ class StackProfTest < MiniTest::Test
88
120
  assert_equal :custom, profile[:mode]
89
121
  assert_equal 10, profile[:samples]
90
122
 
91
- frame = profile[:frames].values.first
123
+ offset = RUBY_VERSION >= '3' ? 1 : 0
124
+ frame = profile[:frames].values[offset]
92
125
  assert_includes frame[:name], "StackProfTest#test_custom"
93
126
  assert_includes [profile_base_line-2, profile_base_line+1], frame[:line]
94
- assert_equal [10, 10], frame[:lines][profile_base_line+2]
127
+
128
+ if RUBY_VERSION >= '3'
129
+ assert_equal [10, 0], frame[:lines][profile_base_line+2]
130
+ else
131
+ assert_equal [10, 10], frame[:lines][profile_base_line+2]
132
+ end
95
133
  end
96
134
 
97
135
  def test_raw
136
+ before_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
137
+
98
138
  profile = StackProf.run(mode: :custom, raw: true) do
99
139
  10.times do
100
140
  StackProf.sample
141
+ sleep 0.0001
101
142
  end
102
143
  end
103
144
 
145
+ after_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
146
+
104
147
  raw = profile[:raw]
148
+ raw_lines = profile[:raw_lines]
105
149
  assert_equal 10, raw[-1]
106
150
  assert_equal raw[0] + 2, raw.size
107
- assert_includes profile[:frames][raw[-2]][:name], 'StackProfTest#test_raw'
151
+ assert_equal 10, raw_lines[-1] # seen 10 times
152
+
153
+ offset = RUBY_VERSION >= '3' ? -3 : -2
154
+ assert_equal 140, raw_lines[offset] # sample caller is on 140
155
+ assert_includes profile[:frames][raw[offset]][:name], 'StackProfTest#test_raw'
156
+
157
+ assert_equal 10, profile[:raw_sample_timestamps].size
158
+ profile[:raw_sample_timestamps].each_cons(2) do |t1, t2|
159
+ assert_operator t1, :>, before_monotonic
160
+ assert_operator t2, :>=, t1
161
+ assert_operator t2, :<, after_monotonic
162
+ end
163
+
108
164
  assert_equal 10, profile[:raw_timestamp_deltas].size
165
+ total_duration = after_monotonic - before_monotonic
166
+ assert_operator profile[:raw_timestamp_deltas].inject(&:+), :<, total_duration
167
+
168
+ profile[:raw_timestamp_deltas].each do |delta|
169
+ assert_operator delta, :>, 0
170
+ end
171
+ end
172
+
173
+ def test_metadata
174
+ metadata = {
175
+ path: '/foo/bar',
176
+ revision: '5c0b01f1522ae8c194510977ae29377296dd236b',
177
+ }
178
+ profile = StackProf.run(mode: :cpu, metadata: metadata) do
179
+ math
180
+ end
181
+
182
+ assert_equal metadata, profile[:metadata]
183
+ end
184
+
185
+ def test_empty_metadata
186
+ profile = StackProf.run(mode: :cpu) do
187
+ math
188
+ end
189
+
190
+ assert_equal({}, profile[:metadata])
191
+ end
192
+
193
+ def test_raises_if_metadata_is_not_a_hash
194
+ exception = assert_raises ArgumentError do
195
+ StackProf.run(mode: :cpu, metadata: 'foobar') do
196
+ math
197
+ end
198
+ end
199
+
200
+ assert_equal 'metadata should be a hash', exception.message
109
201
  end
110
202
 
111
203
  def test_fork
@@ -147,12 +239,23 @@ class StackProfTest < MiniTest::Test
147
239
  end
148
240
  end
149
241
 
150
- raw = profile[:raw]
151
242
  gc_frame = profile[:frames].values.find{ |f| f[:name] == "(garbage collection)" }
243
+ marking_frame = profile[:frames].values.find{ |f| f[:name] == "(marking)" }
244
+ sweeping_frame = profile[:frames].values.find{ |f| f[:name] == "(sweeping)" }
245
+
152
246
  assert gc_frame
153
- assert_equal gc_frame[:samples], profile[:gc_samples]
247
+ assert marking_frame
248
+ assert sweeping_frame
249
+
250
+ # We can't guarantee a certain number of GCs to run, so just assert
251
+ # that it's within some kind of delta
252
+ assert_in_delta gc_frame[:total_samples], profile[:gc_samples], 2
253
+
254
+ # Lazy marking / sweeping can cause this math to not add up, so also use a delta
255
+ assert_in_delta profile[:gc_samples], [gc_frame, marking_frame, sweeping_frame].map{|x| x[:samples] }.inject(:+), 2
256
+
154
257
  assert_operator profile[:gc_samples], :>, 0
155
- assert_operator profile[:missed_samples], :<=, 10
258
+ assert_operator profile[:missed_samples], :<=, 25
156
259
  end
157
260
 
158
261
  def test_out
@@ -167,6 +270,41 @@ class StackProfTest < MiniTest::Test
167
270
  refute_empty profile[:frames]
168
271
  end
169
272
 
273
+ def test_out_to_path_string
274
+ tmpfile = Tempfile.new('stackprof-out')
275
+ ret = StackProf.run(mode: :custom, out: tmpfile.path) do
276
+ StackProf.sample
277
+ end
278
+
279
+ refute_equal tmpfile, ret
280
+ assert_equal tmpfile.path, ret.path
281
+ tmpfile.rewind
282
+ profile = Marshal.load(tmpfile.read)
283
+ refute_empty profile[:frames]
284
+ end
285
+
286
+ def test_pathname_out
287
+ tmpfile = Tempfile.new('stackprof-out')
288
+ pathname = Pathname.new(tmpfile.path)
289
+ ret = StackProf.run(mode: :custom, out: pathname) do
290
+ StackProf.sample
291
+ end
292
+
293
+ assert_equal tmpfile.path, ret.path
294
+ tmpfile.rewind
295
+ profile = Marshal.load(tmpfile.read)
296
+ refute_empty profile[:frames]
297
+ end
298
+
299
+ def test_min_max_interval
300
+ [-1, 0, 1_000_000, 1_000_001].each do |invalid_interval|
301
+ err = assert_raises(ArgumentError, "invalid interval #{invalid_interval}") do
302
+ StackProf.run(interval: invalid_interval, debug: true) {}
303
+ end
304
+ assert_match(/microseconds/, err.message)
305
+ end
306
+ end
307
+
170
308
  def math
171
309
  250_000.times do
172
310
  2 ** 10
@@ -180,4 +318,4 @@ class StackProfTest < MiniTest::Test
180
318
  r.close
181
319
  w.close
182
320
  end
183
- end
321
+ 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