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.
@@ -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.generate(data)
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
- @frame_implementations = filenames.zip(lines).map do |filename, line|
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
- # FIXME: We need to get upstream support for JIT frames
247
- if line == -1
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
- @frame_categories = filenames.map do |filename|
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: 0, # FIXME
287
- processShutdownTime: nil, # FIXME
288
- registerTime: (@started_at - 0) / 1_000_000.0,
289
- unregisterTime: ((@stopped_at - 0) / 1_000_000.0 if @stopped_at),
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 = case name
328
- when /\AGC/ then gc_category.idx
329
- when /\AThread/ then thread_category.idx
330
- else
331
- 0
332
- end
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
@@ -1,6 +1,17 @@
1
1
  module Vernier
2
2
  class Result
3
- attr_reader :stack_table, :frame_table, :func_table
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
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
- def self.trace(mode: :wall, out: nil, **collector_options)
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
- def self.trace_retained(out: nil, gc: true)
32
- 3.times { GC.start } if gc
31
+ class << self
32
+ alias_method :trace, :profile
33
+ alias_method :run, :profile
34
+ end
33
35
 
34
- collector = Vernier::Collector.new(:retained)
35
- collector.start
36
+ @collector = nil
36
37
 
37
- result = nil
38
- begin
39
- yield collector
40
- ensure
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
- if out
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
@@ -31,4 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.extensions = ["ext/vernier/extconf.rb"]
32
32
 
33
33
  spec.add_development_dependency "activesupport"
34
+ spec.add_development_dependency "rack"
34
35
  end
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.6.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-03-29 00:00:00.000000000 Z
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.10
106
+ rubygems_version: 3.4.19
89
107
  signing_key:
90
108
  specification_version: 4
91
109
  summary: A next generation CRuby profiler