vernier 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []