stackprof 0.2.11 → 0.2.25

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'
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