vernier 0.6.0 → 0.7.0
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 +4 -4
- data/Gemfile +1 -0
- data/README.md +20 -4
- data/examples/gvl_sleep.rb +62 -0
- data/examples/measure_overhead.rb +39 -0
- data/ext/vernier/vernier.cc +366 -147
- data/lib/vernier/collector.rb +12 -1
- data/lib/vernier/middleware.rb +32 -0
- data/lib/vernier/output/firefox.rb +26 -20
- data/lib/vernier/result.rb +12 -1
- data/lib/vernier/stack_table.rb +42 -0
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +29 -16
- data/vernier.gemspec +1 -0
- metadata +21 -3
data/lib/vernier/collector.rb
CHANGED
@@ -5,8 +5,14 @@ require_relative "thread_names"
|
|
5
5
|
|
6
6
|
module Vernier
|
7
7
|
class Collector
|
8
|
-
def initialize(mode, options={})
|
8
|
+
def initialize(mode, options = {})
|
9
|
+
if options.fetch(:gc, true) && (mode == :retained)
|
10
|
+
GC.start
|
11
|
+
end
|
12
|
+
|
9
13
|
@mode = mode
|
14
|
+
@out = options[:out]
|
15
|
+
|
10
16
|
@markers = []
|
11
17
|
@hooks = []
|
12
18
|
|
@@ -69,6 +75,7 @@ module Vernier
|
|
69
75
|
def stop
|
70
76
|
result = finish
|
71
77
|
|
78
|
+
result.instance_variable_set("@stack_table", stack_table.to_h)
|
72
79
|
@thread_names.finish
|
73
80
|
|
74
81
|
@hooks.each do |hook|
|
@@ -99,6 +106,10 @@ module Vernier
|
|
99
106
|
|
100
107
|
result.instance_variable_set(:@markers, markers)
|
101
108
|
|
109
|
+
if @out
|
110
|
+
result.write(out: @out)
|
111
|
+
end
|
112
|
+
|
102
113
|
result
|
103
114
|
end
|
104
115
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Vernier
|
2
|
+
class Middleware
|
3
|
+
def initialize(app, permit: ->(_) { true })
|
4
|
+
@app = app
|
5
|
+
@permit = permit
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
request = Rack::Request.new(env)
|
10
|
+
return @app.call(env) unless request.GET.has_key?("vernier")
|
11
|
+
|
12
|
+
permitted = @permit.call(request)
|
13
|
+
return @app.call(env) unless permitted
|
14
|
+
|
15
|
+
interval = request.GET.fetch(:vernier_interval, 200).to_i
|
16
|
+
allocation_sample_rate = request.GET.fetch(:vernier_allocation_sample_rate, 200).to_i
|
17
|
+
|
18
|
+
result = Vernier.trace(interval:, allocation_sample_rate:, hooks: [:rails]) do
|
19
|
+
@app.call(env)
|
20
|
+
end
|
21
|
+
body = result.to_gecko
|
22
|
+
filename = "#{request.path.gsub("/", "_")}_#{DateTime.now.strftime("%Y-%m-%d-%H-%M-%S")}.vernier.json"
|
23
|
+
headers = {
|
24
|
+
"Content-Type" => "application/json; charset=utf-8",
|
25
|
+
"Content-Disposition" => "attachment; filename=\"#{filename}\"",
|
26
|
+
"Content-Length" => body.bytesize.to_s
|
27
|
+
}
|
28
|
+
|
29
|
+
Rack::Response.new(body, 200, headers).finish
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -90,7 +90,7 @@ module Vernier
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def output
|
93
|
-
::JSON.
|
93
|
+
::JSON.fast_generate(data)
|
94
94
|
end
|
95
95
|
|
96
96
|
private
|
@@ -237,25 +237,26 @@ module Vernier
|
|
237
237
|
|
238
238
|
lines = profile.frame_table.fetch(:line)
|
239
239
|
|
240
|
-
|
240
|
+
func_implementations = filenames.map do |filename|
|
241
241
|
# Must match strings in `src/profile-logic/profile-data.js`
|
242
242
|
# inside the firefox profiler. See `getFriendlyStackTypeName`
|
243
243
|
if filename == "<cfunc>"
|
244
244
|
@strings["native"]
|
245
245
|
else
|
246
|
-
#
|
247
|
-
|
248
|
-
@strings["yjit"]
|
249
|
-
else
|
250
|
-
# nil means interpreter
|
251
|
-
nil
|
252
|
-
end
|
246
|
+
# nil means interpreter
|
247
|
+
nil
|
253
248
|
end
|
254
249
|
end
|
250
|
+
@frame_implementations = profile.frame_table.fetch(:func).map do |func_idx|
|
251
|
+
func_implementations[func_idx]
|
252
|
+
end
|
255
253
|
|
256
|
-
|
254
|
+
func_categories = filenames.map do |filename|
|
257
255
|
@categorizer.categorize(filename)
|
258
256
|
end
|
257
|
+
@frame_categories = profile.frame_table.fetch(:func).map do |func_idx|
|
258
|
+
func_categories[func_idx]
|
259
|
+
end
|
259
260
|
end
|
260
261
|
|
261
262
|
def filter_filenames(filenames)
|
@@ -280,13 +281,16 @@ module Vernier
|
|
280
281
|
end
|
281
282
|
|
282
283
|
def data
|
284
|
+
started_at = (@started_at - 0) / 1_000_000.0
|
285
|
+
stopped_at = (@stopped_at - 0) / 1_000_000.0 if @stopped_at
|
286
|
+
|
283
287
|
{
|
284
288
|
name: @name,
|
285
289
|
isMainThread: @is_main,
|
286
|
-
processStartupTime:
|
287
|
-
processShutdownTime:
|
288
|
-
registerTime:
|
289
|
-
unregisterTime:
|
290
|
+
processStartupTime: started_at,
|
291
|
+
processShutdownTime: stopped_at,
|
292
|
+
registerTime: started_at,
|
293
|
+
unregisterTime: stopped_at,
|
290
294
|
pausedRanges: [],
|
291
295
|
pid: profile.pid || Process.pid,
|
292
296
|
tid: @tid,
|
@@ -324,12 +328,14 @@ module Vernier
|
|
324
328
|
end_times << (finish&./(1_000_000.0))
|
325
329
|
phases << phase
|
326
330
|
|
327
|
-
category =
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
331
|
+
category =
|
332
|
+
if name.start_with?("GC")
|
333
|
+
gc_category.idx
|
334
|
+
elsif name.start_with?("Thread")
|
335
|
+
thread_category.idx
|
336
|
+
else
|
337
|
+
0
|
338
|
+
end
|
333
339
|
|
334
340
|
categories << category
|
335
341
|
data << datum
|
data/lib/vernier/result.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
module Vernier
|
2
2
|
class Result
|
3
|
-
|
3
|
+
def stack_table
|
4
|
+
@stack_table[:stack_table]
|
5
|
+
end
|
6
|
+
|
7
|
+
def frame_table
|
8
|
+
@stack_table[:frame_table]
|
9
|
+
end
|
10
|
+
|
11
|
+
def func_table
|
12
|
+
@stack_table[:func_table]
|
13
|
+
end
|
14
|
+
|
4
15
|
attr_reader :markers
|
5
16
|
|
6
17
|
attr_accessor :hooks
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Vernier
|
2
|
+
class StackTable
|
3
|
+
def to_h
|
4
|
+
{
|
5
|
+
stack_table: {
|
6
|
+
parent: stack_count.times.map { stack_parent_idx(_1) },
|
7
|
+
frame: stack_count.times.map { stack_frame_idx(_1) }
|
8
|
+
},
|
9
|
+
frame_table: {
|
10
|
+
func: frame_count.times.map { frame_func_idx(_1) },
|
11
|
+
line: frame_count.times.map { frame_line_no(_1) }
|
12
|
+
},
|
13
|
+
func_table: {
|
14
|
+
name: func_count.times.map { func_name(_1) },
|
15
|
+
filename: func_count.times.map { func_filename(_1) },
|
16
|
+
first_line: func_count.times.map { func_first_lineno(_1) }
|
17
|
+
}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def backtrace(stack_idx)
|
22
|
+
full_stack(stack_idx).map do |stack_idx|
|
23
|
+
frame_idx = stack_frame_idx(stack_idx)
|
24
|
+
func_idx = frame_func_idx(frame_idx)
|
25
|
+
line = frame_line_no(frame_idx)
|
26
|
+
name = func_name(func_idx);
|
27
|
+
filename = func_filename(func_idx);
|
28
|
+
|
29
|
+
"#{filename}:#{line}:in '#{name}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def full_stack(stack_idx)
|
34
|
+
full_stack = []
|
35
|
+
while stack_idx
|
36
|
+
full_stack << stack_idx
|
37
|
+
stack_idx = stack_parent_idx(stack_idx)
|
38
|
+
end
|
39
|
+
full_stack
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/vernier/version.rb
CHANGED
data/lib/vernier.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative "vernier/version"
|
4
4
|
require_relative "vernier/collector"
|
5
|
+
require_relative "vernier/stack_table"
|
5
6
|
require_relative "vernier/result"
|
6
7
|
require_relative "vernier/hooks"
|
7
8
|
require_relative "vernier/vernier"
|
@@ -11,7 +12,9 @@ require_relative "vernier/output/top"
|
|
11
12
|
module Vernier
|
12
13
|
class Error < StandardError; end
|
13
14
|
|
14
|
-
|
15
|
+
autoload :Middleware, "vernier/middleware"
|
16
|
+
|
17
|
+
def self.profile(mode: :wall, **collector_options)
|
15
18
|
collector = Vernier::Collector.new(mode, collector_options)
|
16
19
|
collector.start
|
17
20
|
|
@@ -20,33 +23,43 @@ module Vernier
|
|
20
23
|
yield collector
|
21
24
|
ensure
|
22
25
|
result = collector.stop
|
23
|
-
if out
|
24
|
-
File.write(out, Output::Firefox.new(result).output)
|
25
|
-
end
|
26
26
|
end
|
27
27
|
|
28
28
|
result
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
class << self
|
32
|
+
alias_method :trace, :profile
|
33
|
+
alias_method :run, :profile
|
34
|
+
end
|
33
35
|
|
34
|
-
|
35
|
-
collector.start
|
36
|
+
@collector = nil
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
result = collector.stop
|
42
|
-
end
|
38
|
+
def self.start_profile(mode: :wall, **collector_options)
|
39
|
+
if @collector
|
40
|
+
@collector.stop
|
41
|
+
@collector = nil
|
43
42
|
|
44
|
-
|
45
|
-
result.write(out:)
|
43
|
+
raise "Profile already started, stopping..."
|
46
44
|
end
|
45
|
+
|
46
|
+
@collector = Vernier::Collector.new(mode, collector_options)
|
47
|
+
@collector.start
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.stop_profile
|
51
|
+
raise "No profile started" unless @collector
|
52
|
+
|
53
|
+
result = @collector.stop
|
54
|
+
@collector = nil
|
55
|
+
|
47
56
|
result
|
48
57
|
end
|
49
58
|
|
59
|
+
def self.trace_retained(out: nil, gc: true, &block)
|
60
|
+
profile(mode: :retained, out:, gc:, &block)
|
61
|
+
end
|
62
|
+
|
50
63
|
class Collector
|
51
64
|
def self.new(mode, options = {})
|
52
65
|
_new(mode, options)
|
data/vernier.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vernier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
description: Next-generation Ruby 3.2.1+ sampling profiler. Tracks multiple threads,
|
28
42
|
GVL activity, GC pauses, idle time, and more.
|
29
43
|
email:
|
@@ -42,6 +56,8 @@ files:
|
|
42
56
|
- bin/console
|
43
57
|
- bin/setup
|
44
58
|
- bin/vernier
|
59
|
+
- examples/gvl_sleep.rb
|
60
|
+
- examples/measure_overhead.rb
|
45
61
|
- examples/minitest.rb
|
46
62
|
- examples/ractor.rb
|
47
63
|
- examples/rails.rb
|
@@ -57,9 +73,11 @@ files:
|
|
57
73
|
- lib/vernier/hooks.rb
|
58
74
|
- lib/vernier/hooks/active_support.rb
|
59
75
|
- lib/vernier/marker.rb
|
76
|
+
- lib/vernier/middleware.rb
|
60
77
|
- lib/vernier/output/firefox.rb
|
61
78
|
- lib/vernier/output/top.rb
|
62
79
|
- lib/vernier/result.rb
|
80
|
+
- lib/vernier/stack_table.rb
|
63
81
|
- lib/vernier/thread_names.rb
|
64
82
|
- lib/vernier/version.rb
|
65
83
|
- vernier.gemspec
|
@@ -85,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
103
|
- !ruby/object:Gem::Version
|
86
104
|
version: '0'
|
87
105
|
requirements: []
|
88
|
-
rubygems_version: 3.4.
|
106
|
+
rubygems_version: 3.4.19
|
89
107
|
signing_key:
|
90
108
|
specification_version: 4
|
91
109
|
summary: A next generation CRuby profiler
|