vernier 1.1.2 → 1.2.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/exe/vernier +121 -32
  3. data/lib/vernier/version.rb +1 -1
  4. metadata +6 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e53177832167777357ffdfc79c26f1957a8707804cb85349560faa6d433fd49
4
- data.tar.gz: bc89ef70d31f160e8a874c27a2b9bb6479c1a4e631549dd4ac583b7137177660
3
+ metadata.gz: 56faced9b5a5fe99a124ab7743609eb23a981ca27888a5a8957e509d28f508ef
4
+ data.tar.gz: 0b258dc2f1f4c143568527213a3f39055a44aed3c983c84a14f71d6af68c900a
5
5
  SHA512:
6
- metadata.gz: 642f607cc4998decb398e88dd196b920ae9592c139fdf7527ad7feb383579b22ff697fa4dede77d62866784415f798d8b020cdb0f8122cb30ea02a6fa66cc7cb
7
- data.tar.gz: '03749312aad61f1c336863dece9417afc01df487a6f3f44f7c55e94fcb81b9df32e2aabd232b61a22b7add89f969cfe2a685816ea38cfd66447ac2f7813ed96a'
6
+ metadata.gz: '096133a98cfc38236c89025c51f70a0c4def9c6208031775d11640291d08b17d53fb2817d6f9b7fea1e7d0551ac0e58471d757fd5568c732969d73134ca20fc3'
7
+ data.tar.gz: 063ce46032770d8d2cd161071587dea386af0497a9437d8bc19148c54263e32c2d4d00b164c926ff19f1236f704f1c76aabff8be63bb2b66cd7409f69742004b
data/exe/vernier CHANGED
@@ -3,45 +3,134 @@
3
3
  require "optparse"
4
4
  require "vernier/version"
5
5
 
6
- banner = <<-END
6
+ module Vernier
7
+ module CLI
8
+ def self.run(options)
9
+ banner = <<-END
7
10
  Usage: vernier run [FLAGS] -- COMMAND
8
11
 
9
12
  FLAGS:
10
- END
13
+ END
11
14
 
12
- options = {}
13
- parser = OptionParser.new(banner) do |o|
14
- o.version = Vernier::VERSION
15
+ OptionParser.new(banner) do |o|
16
+ o.version = Vernier::VERSION
15
17
 
16
- o.on('--output [FILENAME]', String, "output filename") do |s|
17
- options[:output] = s
18
- end
19
- o.on('--interval [MICROSECONDS]', Integer, "sampling interval (default 500)") do |i|
20
- options[:interval] = i
21
- end
22
- o.on('--allocation_sample_rate [ALLOCATIONS]', Integer, "allocation sampling interval (default 0 disabled)") do |i|
23
- options[:allocation_sample_rate] = i
24
- end
25
- o.on('--signal [NAME]', String, "specify a signal to start and stop the profiler") do |s|
26
- options[:signal] = s
27
- end
28
- o.on('--start-paused', "don't automatically start the profiler") do
29
- options[:start_paused] = true
30
- end
31
- o.on('--hooks [HOOKS]', String, "Enable instrumentation hooks. Currently supported: rails") do |s|
32
- options[:hooks] = s
18
+ o.on('--output [FILENAME]', String, "output filename") do |s|
19
+ options[:output] = s
20
+ end
21
+ o.on('--interval [MICROSECONDS]', Integer, "sampling interval (default 500)") do |i|
22
+ options[:interval] = i
23
+ end
24
+ o.on('--allocation_sample_rate [ALLOCATIONS]', Integer, "allocation sampling interval (default 0 disabled)") do |i|
25
+ options[:allocation_sample_rate] = i
26
+ end
27
+ o.on('--signal [NAME]', String, "specify a signal to start and stop the profiler") do |s|
28
+ options[:signal] = s
29
+ end
30
+ o.on('--start-paused', "don't automatically start the profiler") do
31
+ options[:start_paused] = true
32
+ end
33
+ o.on('--hooks [HOOKS]', String, "Enable instrumentation hooks. Currently supported: rails") do |s|
34
+ options[:hooks] = s
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.view(options)
40
+ banner = <<-END
41
+ Usage: vernier view [FLAGS] FILENAME
42
+
43
+ FLAGS:
44
+ END
45
+
46
+ OptionParser.new(banner) do |o|
47
+ o.on('--top [COUNT]', Integer, "number of frames to show (default 20)") do |i|
48
+ options[:top] = i
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.inverted_tree(top, file)
54
+ # Print the inverted tree from a Vernier profile
55
+ require "json"
56
+
57
+ is_gzip = File.binread(file, 2) == "\x1F\x8B".b # check for gzip header
58
+
59
+ json = if is_gzip
60
+ require "zlib"
61
+ Zlib::GzipReader.open(file) { |gz| gz.read }
62
+ else
63
+ File.read file
64
+ end
65
+
66
+ info = JSON.load json
67
+
68
+ main = info["threads"].find { |thread| thread["isMainThread"] }
69
+
70
+ weight_by_frame = Hash.new(0)
71
+
72
+ stack_frames = main["stackTable"]["frame"]
73
+ frame_table = main["frameTable"]["func"]
74
+ func_table = main["funcTable"]["name"]
75
+ string_array = main["stringArray"]
76
+
77
+ main["samples"]["stack"].zip(main["samples"]["weight"]).each do |stack, weight|
78
+ top_frame_index = stack_frames[stack]
79
+ func_index = frame_table[top_frame_index]
80
+ string_index = func_table[func_index]
81
+ str = string_array[string_index]
82
+ weight_by_frame[str] += weight
83
+ end
84
+
85
+ total = weight_by_frame.values.inject :+
86
+
87
+ header = ["Samples", "%", ""]
88
+ widths = header.map(&:bytesize)
89
+
90
+ columns = weight_by_frame.sort_by { |k,v| v }.reverse.first(top).map { |k,v|
91
+ entry = [v.to_s, ((v / total.to_f) * 100).round(1).to_s, k]
92
+ entry.each_with_index { |str, i| widths[i] = str.bytesize if widths[i] < str.bytesize }
93
+ entry
94
+ }
95
+
96
+ print_separator widths
97
+ print_row header, widths
98
+ print_separator widths
99
+ columns.each { print_row(_1, widths) }
100
+ print_separator widths
101
+ end
102
+
103
+ def self.print_row(list, widths)
104
+ puts("|" + list.map.with_index { |str, i| " " + str.ljust(widths[i] + 1) }.join("|") + "|")
105
+ end
106
+
107
+ def self.print_separator(widths)
108
+ puts("+" + widths.map { |i| "-" * (i + 2) }.join("+") + "+")
109
+ end
33
110
  end
34
111
  end
35
112
 
36
- parser.parse!
37
- parser.abort(parser.help) if ARGV.shift != "run"
38
- parser.abort(parser.help) if ARGV.empty?
113
+ options = {}
114
+ run = Vernier::CLI.run(options)
115
+ view = Vernier::CLI.view(options)
39
116
 
40
- env = {}
41
- options.each do |k, v|
42
- env["VERNIER_#{k.to_s.upcase}"] = v.to_s
43
- end
44
- vernier_path = File.expand_path('../lib', __dir__)
45
- env['RUBYOPT'] = "-I #{vernier_path} -r vernier/autorun #{ENV['RUBYOPT']}"
117
+ case ARGV.shift
118
+ when "run"
119
+ run.parse!
120
+ run.abort(run.help) if ARGV.empty?
46
121
 
47
- Kernel.exec(env, *ARGV)
122
+ env = {}
123
+ options.each do |k, v|
124
+ env["VERNIER_#{k.to_s.upcase}"] = v.to_s
125
+ end
126
+ vernier_path = File.expand_path('../lib', __dir__)
127
+ env['RUBYOPT'] = "-I #{vernier_path} -r vernier/autorun #{ENV['RUBYOPT']}"
128
+
129
+ Kernel.exec(env, *ARGV)
130
+ when "view"
131
+ view.parse!
132
+ view.abort(view.help) if ARGV.empty?
133
+ Vernier::CLI.inverted_tree(options[:top] || 20, ARGV.shift)
134
+ else
135
+ run.abort(run.help + "\n" + view.help)
136
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "1.1.2"
4
+ VERSION = "1.2.0"
5
5
  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: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hawthorn
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-18 00:00:00.000000000 Z
11
+ date: 2024-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -102,7 +102,7 @@ metadata:
102
102
  homepage_uri: https://github.com/jhawthorn/vernier
103
103
  source_code_uri: https://github.com/jhawthorn/vernier
104
104
  changelog_uri: https://github.com/jhawthorn/vernier
105
- post_install_message:
105
+ post_install_message:
106
106
  rdoc_options: []
107
107
  require_paths:
108
108
  - lib
@@ -117,8 +117,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
117
  - !ruby/object:Gem::Version
118
118
  version: '0'
119
119
  requirements: []
120
- rubygems_version: 3.5.11
121
- signing_key:
120
+ rubygems_version: 3.5.16
121
+ signing_key:
122
122
  specification_version: 4
123
123
  summary: A next generation CRuby profiler
124
124
  test_files: []