vernier 1.7.1 → 1.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 356fbbcbb8c60616fe3a92c4eed26d7cac97f3a1e230e4904571f9666f4b64aa
4
- data.tar.gz: cb7f9542080591e8fe680ab7f1b5688f0c5265e8d32d8ce2d8d56efab639ded0
3
+ metadata.gz: fb2a2674d6f0654668b9fbfc03ec935fa21640585ae0b4dbd7567b472f975f26
4
+ data.tar.gz: 660264f955f4f2e9a9ae7e64acbb1b59d4d91f87d1a28d7a62aed0adabc9c51a
5
5
  SHA512:
6
- metadata.gz: 2bf9a5882a9494b23c6a695836ee253a04054c501bab46b20b12b73516d7b21c103d1bb306ccb2a6ac3b5dce61d882620e6abbc53e056d0fd3e9260e68e97c44
7
- data.tar.gz: e82f4becfb5a9e30cafebbe5271bd6b2e9883c3422eb5e61ee2ffae8589a0861f55a19b190c9d5a829894755a227438c7f4d2aa2225d55697f0f954738a49f10
6
+ metadata.gz: aa249d63850b2ea08a3165e9b7045dcec5542173198aeef05c4c39047f604a5faa12b0d72c6f8fa7983e45a7011a3a5529dc4e4b7fbee426b2461f14b5d6b6c3
7
+ data.tar.gz: 93f82cd596d454db747afb11abe669b4ed53b6fd6691ebf9ab43ed53e5f2be9406ce93424e3b1179c9a7d13df28cdcbface7d2e80544aa1d2ba45d8a19385696
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.4
data/Rakefile CHANGED
@@ -7,6 +7,7 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.libs << "lib"
9
9
  t.test_files = FileList["test/**/test_*.rb"]
10
+ t.deps << :compile
10
11
  end
11
12
 
12
13
  task :console => :compile do
data/exe/vernier CHANGED
@@ -9,10 +9,9 @@ module Vernier
9
9
  module CLI
10
10
  class Metadata < Array
11
11
  require 'json'
12
- require 'base64'
13
12
 
14
13
  def to_s
15
- Base64.encode64(to_json)
14
+ [to_json].pack("m")
16
15
  end
17
16
  end
18
17
 
@@ -52,6 +51,9 @@ FLAGS:
52
51
  options[:metadata] ||= Metadata.new
53
52
  options[:metadata] << [key, value]
54
53
  end
54
+ o.on('--format [FORMAT]', String, "output format: firefox (default) or cpuprofile") do |output_format|
55
+ options[:format] = output_format
56
+ end
55
57
  end
56
58
  end
57
59
 
@@ -88,6 +90,8 @@ run = Vernier::CLI.run(options)
88
90
  view = Vernier::CLI.view(options)
89
91
 
90
92
  case ARGV.shift
93
+ when "-v", "--version"
94
+ puts Vernier::VERSION
91
95
  when "run"
92
96
  run.parse!
93
97
  run.abort(run.help) if ARGV.empty?
@@ -1,6 +1,5 @@
1
1
  require "tempfile"
2
2
  require "vernier"
3
- require "base64"
4
3
  require "json"
5
4
 
6
5
  module Vernier
@@ -22,7 +21,7 @@ module Vernier
22
21
  allocation_interval = options.fetch(:allocation_interval, 0).to_i
23
22
  hooks = options.fetch(:hooks, "").split(",")
24
23
  metadata = if options[:metadata]
25
- JSON.parse(Base64.decode64(@options[:metadata])).to_h { |k, v| [k.to_sym, v] }
24
+ JSON.parse(@options[:metadata].unpack1("m")).to_h { |k, v| [k.to_sym, v] }
26
25
  else
27
26
  {}
28
27
  end
@@ -49,12 +48,16 @@ module Vernier
49
48
  end
50
49
  prefix = "profile-"
51
50
  timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
52
- suffix = ".vernier.json.gz"
51
+ suffix = if options[:format] == "cpuprofile"
52
+ ".vernier.cpuprofile"
53
+ else
54
+ ".vernier.json.gz"
55
+ end
53
56
 
54
57
  output_path = File.expand_path("#{output_dir}/#{prefix}#{timestamp}-#{$$}#{suffix}")
55
58
  end
56
59
 
57
- result.write(out: output_path)
60
+ result.write(out: output_path, format: options[:format] || "firefox")
58
61
 
59
62
  STDERR.puts(result.inspect)
60
63
  STDERR.puts("written to #{output_path}")
@@ -11,6 +11,7 @@ module Vernier
11
11
 
12
12
  @mode = mode
13
13
  @out = options[:out]
14
+ @format = options[:format]
14
15
 
15
16
  @markers = []
16
17
  @hooks = []
@@ -142,7 +143,7 @@ module Vernier
142
143
  #result.instance_variable_set(:@markers, markers)
143
144
 
144
145
  if @out
145
- result.write(out: @out)
146
+ result.write(out: @out, format: @format)
146
147
  end
147
148
 
148
149
  result
@@ -18,7 +18,7 @@ module Vernier
18
18
  result = Vernier.trace(interval:, allocation_interval:, hooks: [:rails]) do
19
19
  @app.call(env)
20
20
  end
21
- body = result.to_gecko(gzip: true)
21
+ body = result.to_firefox(gzip: true)
22
22
  filename = "#{request.path.gsub("/", "_")}_#{DateTime.now.strftime("%Y-%m-%d-%H-%M-%S")}.vernier.json.gz"
23
23
  headers = {
24
24
  "Content-Type" => "application/octet-stream",
@@ -0,0 +1,145 @@
1
+ require "json"
2
+
3
+ module Vernier
4
+ module Output
5
+ class Cpuprofile
6
+ def initialize(profile)
7
+ @profile = profile
8
+ end
9
+
10
+ def output
11
+ JSON.generate(data)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :profile
17
+
18
+ def ns_to_us(timestamp)
19
+ (timestamp / 1_000.0).to_i
20
+ end
21
+
22
+ def data
23
+ # Get the main thread data (cpuprofile format is single-threaded)
24
+ main_thread = profile.main_thread
25
+ return empty_profile if main_thread.nil?
26
+
27
+ samples = main_thread[:samples]
28
+ timestamps = main_thread[:timestamps] || []
29
+
30
+ nodes = build_nodes
31
+ sample_node_ids = samples.map { |stack_idx| stack_to_node_id(stack_idx) }
32
+ time_deltas = calculate_time_deltas(timestamps)
33
+
34
+ {
35
+ nodes: nodes,
36
+ startTime: ns_to_us(profile.started_at),
37
+ endTime: ns_to_us(profile.end_time),
38
+ samples: sample_node_ids,
39
+ timeDeltas: time_deltas
40
+ }
41
+ end
42
+
43
+ def empty_profile
44
+ {
45
+ nodes: [root_node],
46
+ startTime: 0,
47
+ endTime: 0,
48
+ samples: [],
49
+ timeDeltas: []
50
+ }
51
+ end
52
+
53
+ def build_nodes
54
+ stack_table = profile.stack_table
55
+
56
+ nodes = []
57
+ @node_id_map = {}
58
+
59
+ root = root_node
60
+ nodes << root
61
+ @node_id_map[nil] = 0
62
+
63
+ stack_table.stack_count.times do |stack_idx|
64
+ create_node_for_stack(stack_idx, nodes, stack_table)
65
+ end
66
+
67
+ nodes
68
+ end
69
+
70
+ def root_node
71
+ {
72
+ id: 0,
73
+ callFrame: {
74
+ functionName: "(root)",
75
+ scriptId: "0",
76
+ url: "",
77
+ lineNumber: -1,
78
+ columnNumber: -1,
79
+ },
80
+ hitCount: 0,
81
+ children: []
82
+ }
83
+ end
84
+
85
+ def create_node_for_stack(stack_idx, nodes, stack_table)
86
+ return @node_id_map[stack_idx] if @node_id_map.key?(stack_idx)
87
+
88
+ frame_idx = stack_table.stack_frame_idx(stack_idx)
89
+ parent_stack_idx = stack_table.stack_parent_idx(stack_idx)
90
+
91
+ parent_node_id = if parent_stack_idx.nil?
92
+ 0 # root node
93
+ else
94
+ create_node_for_stack(parent_stack_idx, nodes, stack_table)
95
+ end
96
+
97
+ func_idx = stack_table.frame_func_idx(frame_idx)
98
+ line = stack_table.frame_line_no(frame_idx) - 1
99
+
100
+ func_name = stack_table.func_name(func_idx)
101
+ filename = stack_table.func_filename(func_idx)
102
+
103
+ node_id = nodes.length
104
+ node = {
105
+ id: node_id,
106
+ callFrame: {
107
+ functionName: func_name || "(anonymous)",
108
+ scriptId: func_idx.to_s,
109
+ url: filename || "",
110
+ lineNumber: line || 0,
111
+ columnNumber: 0
112
+ },
113
+ hitCount: 0,
114
+ children: []
115
+ }
116
+
117
+ nodes << node
118
+ @node_id_map[stack_idx] = node_id
119
+
120
+ parent_node = nodes[parent_node_id]
121
+ parent_node[:children] << node_id unless parent_node[:children].include?(node_id)
122
+ end
123
+
124
+ def stack_to_node_id(stack_idx)
125
+ @node_id_map[stack_idx] || 0
126
+ end
127
+
128
+ def calculate_time_deltas(timestamps)
129
+ return [] if timestamps.empty?
130
+
131
+ deltas = []
132
+
133
+ timestamps.each_with_index do |timestamp, i|
134
+ if i == 0
135
+ deltas << 0
136
+ else
137
+ deltas << ns_to_us(timestamp - timestamps[i - 1])
138
+ end
139
+ end
140
+
141
+ deltas
142
+ end
143
+ end
144
+ end
145
+ end
@@ -29,13 +29,25 @@ module Vernier
29
29
  (current_time_real_ns - current_time_mono_ns + started_at_mono_ns)
30
30
  end
31
31
 
32
- def to_gecko(gzip: false)
32
+ def to_firefox(gzip: false)
33
33
  Output::Firefox.new(self).output(gzip:)
34
34
  end
35
+ alias_method :to_gecko, :to_firefox
35
36
 
36
- def write(out:)
37
- gzip = out.end_with?(".gz")
38
- File.binwrite(out, to_gecko(gzip:))
37
+ def to_cpuprofile
38
+ Output::Cpuprofile.new(self).output
39
+ end
40
+
41
+ def write(out:, format: "firefox")
42
+ case format
43
+ when "cpuprofile"
44
+ File.binwrite(out, to_cpuprofile)
45
+ when nil, "firefox"
46
+ gzip = out.end_with?(".gz")
47
+ File.binwrite(out, to_firefox(gzip:))
48
+ else
49
+ raise ArgumentError, "unknown format: #{format}"
50
+ end
39
51
  end
40
52
 
41
53
  def elapsed_seconds
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "1.7.1"
4
+ VERSION = "1.8.0"
5
5
  end
data/lib/vernier.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "vernier/result"
8
8
  require_relative "vernier/hooks"
9
9
  require_relative "vernier/vernier"
10
10
  require_relative "vernier/output/firefox"
11
+ require_relative "vernier/output/cpuprofile"
11
12
  require_relative "vernier/output/top"
12
13
  require_relative "vernier/output/file_listing"
13
14
  require_relative "vernier/output/filename_filter"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vernier
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hawthorn
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-05-16 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -61,6 +61,7 @@ extensions:
61
61
  - ext/vernier/extconf.rb
62
62
  extra_rdoc_files: []
63
63
  files:
64
+ - ".ruby-version"
64
65
  - CODE_OF_CONDUCT.md
65
66
  - Gemfile
66
67
  - LICENSE.txt
@@ -93,6 +94,7 @@ files:
93
94
  - lib/vernier/hooks/memory_usage.rb
94
95
  - lib/vernier/marker.rb
95
96
  - lib/vernier/middleware.rb
97
+ - lib/vernier/output/cpuprofile.rb
96
98
  - lib/vernier/output/file_listing.rb
97
99
  - lib/vernier/output/filename_filter.rb
98
100
  - lib/vernier/output/firefox.rb
@@ -125,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
127
  - !ruby/object:Gem::Version
126
128
  version: '0'
127
129
  requirements: []
128
- rubygems_version: 3.6.2
130
+ rubygems_version: 3.6.7
129
131
  specification_version: 4
130
132
  summary: A next generation CRuby profiler
131
133
  test_files: []