vernier 1.7.0 → 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 +4 -4
- data/.ruby-version +1 -0
- data/README.md +18 -2
- data/Rakefile +1 -0
- data/exe/vernier +6 -2
- data/lib/vernier/autorun.rb +7 -4
- data/lib/vernier/collector.rb +2 -1
- data/lib/vernier/middleware.rb +1 -1
- data/lib/vernier/output/cpuprofile.rb +145 -0
- data/lib/vernier/output/file_listing.rb +1 -1
- data/lib/vernier/output/firefox.rb +1 -1
- data/lib/vernier/result.rb +16 -4
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +1 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb2a2674d6f0654668b9fbfc03ec935fa21640585ae0b4dbd7567b472f975f26
|
4
|
+
data.tar.gz: 660264f955f4f2e9a9ae7e64acbb1b59d4d91f87d1a28d7a62aed0adabc9c51a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa249d63850b2ea08a3165e9b7045dcec5542173198aeef05c4c39047f604a5faa12b0d72c6f8fa7983e45a7011a3a5529dc4e4b7fbee426b2461f14b5d6b6c3
|
7
|
+
data.tar.gz: 93f82cd596d454db747afb11abe669b4ed53b6fd6691ebf9ab43ed53e5f2be9406ce93424e3b1179c9a7d13df28cdcbface7d2e80544aa1d2ba45d8a19385696
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.4.4
|
data/README.md
CHANGED
@@ -32,7 +32,7 @@ gem "vernier", "~> 1.0"
|
|
32
32
|
|
33
33
|
## Usage
|
34
34
|
|
35
|
-
The output can be viewed in the web app at https://vernier.prof, locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both lightly customized versions of the
|
35
|
+
The output can be viewed in the web app at https://vernier.prof, locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both lightly customized versions of the Firefox profiler frontend which profiles are compatible with), or by using the `vernier view` command in the CLI.
|
36
36
|
|
37
37
|
- **Flame Graph**: Shows proportionally how much time is spent within particular stack frames. Frames are grouped together, which means that x-axis / left-to-right order is not meaningful.
|
38
38
|
- **Stack Chart**: Shows the stack at each sample with the x-axis representing time and can be read left-to-right.
|
@@ -133,10 +133,26 @@ ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "i
|
|
133
133
|
| `mode` | N/A | Sampling mode: `:wall`, `:retained`, or `:custom`. | `:wall` (`:wall`) |
|
134
134
|
| `out` | N/A | File to write the profile to. | N/A (Auto-generated) |
|
135
135
|
| `interval` | `vernier_interval` | Sampling interval (µs). Only in `:wall` mode. | `500` (`200`) |
|
136
|
-
| `allocation_interval` | `vernier_allocation_interval` | Allocation sampling interval. Only in `:wall` mode. | `0
|
136
|
+
| `allocation_interval` | `vernier_allocation_interval` | Allocation sampling interval. Only in `:wall` mode. | `0` i.e. disabled (`200`) |
|
137
137
|
| `gc` | N/A | Run full GC cycle before profiling. Only in `:retained` mode. | `true` (N/A) |
|
138
138
|
| `metadata` | N/A | Metadata key-value pairs to include in the profile. | `{}` (N/A) |
|
139
139
|
|
140
|
+
#### Hook options
|
141
|
+
|
142
|
+
Vernier offers some optional hooks:
|
143
|
+
|
144
|
+
- `:rails` / `:activesupport` - Adds selected Active Support event markers to the Firefox output
|
145
|
+
|
146
|
+
- `:memory_usage` - Adds a memory usage counter to the Firefox output
|
147
|
+
|
148
|
+
Usage example:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
Vernier.profile(out: "time_profile.json", hooks: [:rails, :memory_usage]) do
|
152
|
+
# ...
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
140
156
|
## Development
|
141
157
|
|
142
158
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
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
|
-
|
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?
|
data/lib/vernier/autorun.rb
CHANGED
@@ -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(
|
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 = "
|
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}")
|
data/lib/vernier/collector.rb
CHANGED
@@ -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
|
data/lib/vernier/middleware.rb
CHANGED
@@ -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.
|
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
|
data/lib/vernier/result.rb
CHANGED
@@ -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
|
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
|
37
|
-
|
38
|
-
|
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
|
data/lib/vernier/version.rb
CHANGED
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.
|
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:
|
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.
|
130
|
+
rubygems_version: 3.6.7
|
129
131
|
specification_version: 4
|
130
132
|
summary: A next generation CRuby profiler
|
131
133
|
test_files: []
|