stackprof 0.2.12 → 0.2.26

Sign up to get free protection for your applications and to get access to all the features.
@@ -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