vernier 1.1.1 → 1.2.0

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