vernier 1.1.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f250b62a547d9c79e6d7beb4382c4d8bdde8e2245e868c3ac5b8d5354e2929b
4
- data.tar.gz: e900e786f82b759edc4dc83d5c9bfe37048ca3eca62a1352cef54e1ad2822069
3
+ metadata.gz: 56faced9b5a5fe99a124ab7743609eb23a981ca27888a5a8957e509d28f508ef
4
+ data.tar.gz: 0b258dc2f1f4c143568527213a3f39055a44aed3c983c84a14f71d6af68c900a
5
5
  SHA512:
6
- metadata.gz: 49bf98de660939233cfdd7c256d4976dea3438ebaaf1bfd3b31dfa273d5b4d9da07e86cef98336fdc2178cfa56c368f5d68593c0449e424c8f9982dcb8d4155f
7
- data.tar.gz: 5a78302e03fb52cb2e8ad69019ed3ed3be4cdc13f9ab86a86231013d8ca574595da34c00bc65ee6336a624cebad7d0ccd1b761eaffa3f6e3bc711fdcdb5cbbfc
6
+ metadata.gz: '096133a98cfc38236c89025c51f70a0c4def9c6208031775d11640291d08b17d53fb2817d6f9b7fea1e7d0551ac0e58471d757fd5568c732969d73134ca20fc3'
7
+ data.tar.gz: 063ce46032770d8d2cd161071587dea386af0497a9437d8bc19148c54263e32c2d4d00b164c926ff19f1236f704f1c76aabff8be63bb2b66cd7409f69742004b
data/exe/vernier CHANGED
@@ -1,44 +1,136 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "optparse"
4
+ require "vernier/version"
4
5
 
5
- banner = <<-END
6
+ module Vernier
7
+ module CLI
8
+ def self.run(options)
9
+ banner = <<-END
6
10
  Usage: vernier run [FLAGS] -- COMMAND
7
11
 
8
12
  FLAGS:
9
- END
13
+ END
10
14
 
11
- options = {}
12
- parser = OptionParser.new(banner) do |o|
13
- o.on('--output [FILENAME]', String, "output filename") do |s|
14
- options[:output] = s
15
- end
16
- o.on('--interval [MICROSECONDS]', Integer, "sampling interval (default 500)") do |i|
17
- options[:interval] = i
18
- end
19
- o.on('--allocation_sample_rate [ALLOCATIONS]', Integer, "allocation sampling interval (default 0 disabled)") do |i|
20
- options[:allocation_sample_rate] = i
21
- end
22
- o.on('--signal [NAME]', String, "specify a signal to start and stop the profiler") do |s|
23
- options[:signal] = s
24
- end
25
- o.on('--start-paused', "don't automatically start the profiler") do
26
- options[:start_paused] = true
27
- end
28
- o.on('--hooks [HOOKS]', String, "Enable instrumentation hooks. Currently supported: rails") do |s|
29
- options[:hooks] = s
15
+ OptionParser.new(banner) do |o|
16
+ o.version = Vernier::VERSION
17
+
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
30
110
  end
31
111
  end
32
112
 
33
- parser.parse!
34
- parser.abort(parser.help) if ARGV.shift != "run"
35
- parser.abort(parser.help) if ARGV.empty?
113
+ options = {}
114
+ run = Vernier::CLI.run(options)
115
+ view = Vernier::CLI.view(options)
36
116
 
37
- env = {}
38
- options.each do |k, v|
39
- env["VERNIER_#{k.to_s.upcase}"] = v.to_s
40
- end
41
- vernier_path = File.expand_path('../lib', __dir__)
42
- 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?
43
121
 
44
- 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
@@ -672,10 +672,6 @@ StackTable::stack_table_convert(VALUE self, VALUE original_tableval, VALUE origi
672
672
  StackTable *original_table = get_stack_table(original_tableval);
673
673
  int original_idx = NUM2INT(original_idxval);
674
674
 
675
- if (original_idx == -1) {
676
- return original_idxval;
677
- }
678
-
679
675
  int original_size;
680
676
  {
681
677
  const std::lock_guard<std::mutex> lock(original_table->stack_mutex);
@@ -1027,13 +1023,15 @@ class SampleList {
1027
1023
  }
1028
1024
 
1029
1025
  void record_sample(int stack_index, TimeStamp time, Category category) {
1030
- if (
1031
- !empty() &&
1032
- stacks.back() == stack_index &&
1033
- categories.back() == category)
1034
- {
1035
- // We don't compare timestamps for de-duplication
1036
- weights.back() += 1;
1026
+ // FIXME: probably better to avoid generating -1 higher up.
1027
+ // Currently this happens when we measure an empty stack. Ideally we would have a better representation
1028
+ if (stack_index < 0)
1029
+ return;
1030
+
1031
+ if (!empty() && stacks.back() == stack_index &&
1032
+ categories.back() == category) {
1033
+ // We don't compare timestamps for de-duplication
1034
+ weights.back() += 1;
1037
1035
  } else {
1038
1036
  stacks.push_back(stack_index);
1039
1037
  timestamps.push_back(time);
@@ -102,7 +102,7 @@ module Vernier
102
102
  end
103
103
 
104
104
  def line
105
- result.frame_line_idx(idx)
105
+ result._stack_table.frame_line_no(idx)
106
106
  end
107
107
 
108
108
  def to_s
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vernier
4
- VERSION = "1.1.1"
4
+ VERSION = "1.2.0"
5
5
  end
data/vernier.gemspec CHANGED
@@ -12,7 +12,10 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "Next-generation Ruby 3.2.1+ sampling profiler. Tracks multiple threads, GVL activity, GC pauses, idle time, and more."
13
13
  spec.homepage = "https://github.com/jhawthorn/vernier"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 3.2.1"
15
+
16
+ unless ENV["IGNORE_REQUIRED_RUBY_VERSION"]
17
+ spec.required_ruby_version = ">= 3.2.1"
18
+ end
16
19
 
17
20
  spec.metadata["homepage_uri"] = spec.homepage
18
21
  spec.metadata["source_code_uri"] = spec.homepage
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.1
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-07-07 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.9
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: []