vernier 1.10.1 → 1.11.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/Gemfile +2 -0
- data/lib/vernier/collector.rb +2 -0
- data/lib/vernier/output/cpuprofile.rb +6 -0
- data/lib/vernier/output/file_listing.rb +56 -27
- data/lib/vernier/output/firefox.rb +4 -12
- data/lib/vernier/output/top.rb +8 -4
- data/lib/vernier/output_helpers.rb +33 -0
- data/lib/vernier/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e8d610b954485d3bccefa47408932dddd5806b618caa2958d689f8c0c8fbb3d
|
|
4
|
+
data.tar.gz: 2423bf5e06dd1168e21f0a5063050e897137c37bd09ce68a540af9d84b0a7546
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 942b097d843b63457650254b2e9717698939df9ee40c02ff367832d344121e6ffdafffbc2e0824d70229bf5d4187d1cf0eb64491402a4c54c4c5f6dd8fb0b98f
|
|
7
|
+
data.tar.gz: 2f710c6b9eeb8e4e352290902aed1a4142ea5b48df7039ee49de66dc7c9f7411e25cb77319addba69a15f072f08b3820a51535fe19070a4e77341aa6c000a57b
|
data/Gemfile
CHANGED
data/lib/vernier/collector.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Vernier
|
|
|
29
29
|
result.instance_variable_set(:@threads, {
|
|
30
30
|
0 => {
|
|
31
31
|
tid: 0,
|
|
32
|
+
is_main: true,
|
|
32
33
|
name: "custom",
|
|
33
34
|
started_at: @started_at,
|
|
34
35
|
samples: @samples,
|
|
@@ -80,6 +81,7 @@ module Vernier
|
|
|
80
81
|
result.instance_variable_set(:@threads, {
|
|
81
82
|
0 => {
|
|
82
83
|
tid: 0,
|
|
84
|
+
is_main: true,
|
|
83
85
|
name: "retained memory",
|
|
84
86
|
started_at: @started_at,
|
|
85
87
|
samples: samples,
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
|
+
require_relative "../output_helpers"
|
|
4
|
+
|
|
3
5
|
module Vernier
|
|
4
6
|
module Output
|
|
5
7
|
class Cpuprofile
|
|
8
|
+
include OutputHelpers
|
|
9
|
+
|
|
6
10
|
def initialize(profile)
|
|
7
11
|
@profile = profile
|
|
8
12
|
end
|
|
@@ -98,7 +102,9 @@ module Vernier
|
|
|
98
102
|
line = stack_table.frame_line_no(frame_idx) - 1
|
|
99
103
|
|
|
100
104
|
func_name = stack_table.func_name(func_idx)
|
|
105
|
+
func_name = sanitize_string(func_name) if func_name
|
|
101
106
|
filename = stack_table.func_filename(func_idx)
|
|
107
|
+
filename = sanitize_string(filename) if filename
|
|
102
108
|
|
|
103
109
|
node_id = nodes.length
|
|
104
110
|
node = {
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "filename_filter"
|
|
4
|
+
require_relative "../output_helpers"
|
|
4
5
|
require "cgi/escape"
|
|
5
6
|
|
|
6
7
|
module Vernier
|
|
7
8
|
module Output
|
|
8
9
|
class FileListing
|
|
10
|
+
include OutputHelpers
|
|
11
|
+
|
|
9
12
|
class SamplesByLocation
|
|
10
13
|
attr_accessor :self, :total
|
|
11
14
|
def initialize
|
|
12
15
|
@self = @total = 0
|
|
13
16
|
end
|
|
14
|
-
|
|
15
|
-
def +(other)
|
|
16
|
-
ret = SamplesByLocation.new
|
|
17
|
-
ret.self = @self + other.self
|
|
18
|
-
ret.total = @total + other.total
|
|
19
|
-
ret
|
|
20
|
-
end
|
|
21
17
|
end
|
|
22
18
|
|
|
23
19
|
def initialize(profile)
|
|
@@ -25,6 +21,8 @@ module Vernier
|
|
|
25
21
|
end
|
|
26
22
|
|
|
27
23
|
def samples_by_file
|
|
24
|
+
return @samples_by_file if defined?(@samples_by_file)
|
|
25
|
+
|
|
28
26
|
thread = @profile.main_thread
|
|
29
27
|
if Hash === thread
|
|
30
28
|
# live profile
|
|
@@ -38,22 +36,9 @@ module Vernier
|
|
|
38
36
|
weights = thread[:weights]
|
|
39
37
|
samples = thread[:samples]
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
h[k] = SamplesByLocation.new
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
samples.zip(weights).each do |stack_idx, weight|
|
|
46
|
-
# self time
|
|
47
|
-
top_frame_index = stack_table.stack_frame_idx(stack_idx)
|
|
48
|
-
self_samples_by_frame[top_frame_index].self += weight
|
|
39
|
+
stack_weights = collapse_stack_weights(samples, weights)
|
|
49
40
|
|
|
50
|
-
|
|
51
|
-
while stack_idx
|
|
52
|
-
frame_idx = stack_table.stack_frame_idx(stack_idx)
|
|
53
|
-
self_samples_by_frame[frame_idx].total += weight
|
|
54
|
-
stack_idx = stack_table.stack_parent_idx(stack_idx)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
41
|
+
self_by_frame, total_by_frame = frame_weights(stack_table, stack_weights)
|
|
57
42
|
|
|
58
43
|
samples_by_file = Hash.new do |h, k|
|
|
59
44
|
h[k] = Hash.new do |h2, k2|
|
|
@@ -61,19 +46,64 @@ module Vernier
|
|
|
61
46
|
end
|
|
62
47
|
end
|
|
63
48
|
|
|
64
|
-
|
|
49
|
+
self_by_frame.each_with_index do |self_weight, frame|
|
|
50
|
+
total_weight = total_by_frame[frame]
|
|
51
|
+
next if self_weight == 0 && total_weight == 0
|
|
52
|
+
|
|
65
53
|
line = stack_table.frame_line_no(frame)
|
|
66
54
|
func_index = stack_table.frame_func_idx(frame)
|
|
67
55
|
filename = stack_table.func_filename(func_index)
|
|
68
56
|
|
|
69
|
-
samples_by_file[filename][line]
|
|
57
|
+
location = samples_by_file[filename][line]
|
|
58
|
+
location.self += self_weight
|
|
59
|
+
location.total += total_weight
|
|
70
60
|
end
|
|
71
61
|
|
|
72
|
-
samples_by_file.transform_keys! do |filename|
|
|
62
|
+
@samples_by_file = samples_by_file.transform_keys! do |filename|
|
|
73
63
|
filename_filter.call(filename)
|
|
74
64
|
end
|
|
75
65
|
end
|
|
76
66
|
|
|
67
|
+
# Returns [self_weight, total_weight] arrays indexed by frame, from a
|
|
68
|
+
# hash of per-stack weights.
|
|
69
|
+
#
|
|
70
|
+
# The stack table is a prefix tree whose parent is always interned
|
|
71
|
+
# before its children (parent idx < child idx), so total weights are
|
|
72
|
+
# accumulated bottom-up in a single reverse pass.
|
|
73
|
+
private def frame_weights(stack_table, stack_weights)
|
|
74
|
+
# Scatter the sparse weights into an array indexed by stack.
|
|
75
|
+
stack_totals = Array.new(stack_table.stack_count, 0)
|
|
76
|
+
stack_weights.each do |stack_idx, weight|
|
|
77
|
+
stack_totals[stack_idx] = weight
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Accumulate each stack's total into its parent, children first,
|
|
81
|
+
# so a single descending pass completes every subtree's total.
|
|
82
|
+
(stack_totals.length - 1).downto(0) do |stack_idx|
|
|
83
|
+
parent_idx = stack_table.stack_parent_idx(stack_idx)
|
|
84
|
+
next unless parent_idx
|
|
85
|
+
|
|
86
|
+
if parent_idx >= stack_idx
|
|
87
|
+
raise "Invalid profile: stack table is not parent-before-child ordered"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
stack_totals[parent_idx] += stack_totals[stack_idx]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
self_by_frame = Array.new(stack_table.frame_count, 0)
|
|
94
|
+
total_by_frame = Array.new(stack_table.frame_count, 0)
|
|
95
|
+
stack_weights.each do |stack_idx, weight|
|
|
96
|
+
self_by_frame[stack_table.stack_frame_idx(stack_idx)] += weight
|
|
97
|
+
end
|
|
98
|
+
stack_totals.each_with_index do |total, stack_idx|
|
|
99
|
+
next if total == 0
|
|
100
|
+
|
|
101
|
+
total_by_frame[stack_table.stack_frame_idx(stack_idx)] += total
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
[self_by_frame, total_by_frame]
|
|
105
|
+
end
|
|
106
|
+
|
|
77
107
|
def output(template: nil)
|
|
78
108
|
output = +""
|
|
79
109
|
|
|
@@ -98,8 +128,7 @@ module Vernier
|
|
|
98
128
|
end
|
|
99
129
|
|
|
100
130
|
def total
|
|
101
|
-
|
|
102
|
-
thread[:weights].sum
|
|
131
|
+
@total ||= @profile.main_thread[:weights].sum
|
|
103
132
|
end
|
|
104
133
|
|
|
105
134
|
def format_file(output, filename, all_samples, total:)
|
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "rbconfig"
|
|
5
5
|
|
|
6
6
|
require_relative "filename_filter"
|
|
7
|
+
require_relative "../output_helpers"
|
|
7
8
|
|
|
8
9
|
module Vernier
|
|
9
10
|
module Output
|
|
@@ -267,6 +268,8 @@ module Vernier
|
|
|
267
268
|
end
|
|
268
269
|
|
|
269
270
|
class Thread
|
|
271
|
+
include OutputHelpers
|
|
272
|
+
|
|
270
273
|
SAMPLE_CATEGORY_NAMES = {
|
|
271
274
|
1 => "Idle",
|
|
272
275
|
2 => "Stalled"
|
|
@@ -638,18 +641,7 @@ module Vernier
|
|
|
638
641
|
|
|
639
642
|
def string_table
|
|
640
643
|
@strings.keys.map do |string|
|
|
641
|
-
|
|
642
|
-
string
|
|
643
|
-
elsif string.encoding == Encoding::UTF_8
|
|
644
|
-
if string.valid_encoding?
|
|
645
|
-
string
|
|
646
|
-
else
|
|
647
|
-
string.scrub
|
|
648
|
-
end
|
|
649
|
-
else
|
|
650
|
-
# TODO: We might want to guess UTF-8 and escape the binary more explicitly
|
|
651
|
-
string.dup.force_encoding("UTF-8").scrub
|
|
652
|
-
end
|
|
644
|
+
sanitize_string(string)
|
|
653
645
|
end
|
|
654
646
|
end
|
|
655
647
|
|
data/lib/vernier/output/top.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../output_helpers"
|
|
4
|
+
|
|
3
5
|
module Vernier
|
|
4
6
|
module Output
|
|
5
7
|
class Top
|
|
8
|
+
include OutputHelpers
|
|
9
|
+
|
|
6
10
|
def initialize(profile, row_limit)
|
|
7
11
|
@profile = profile
|
|
8
12
|
@row_limit = row_limit
|
|
@@ -57,10 +61,10 @@ module Vernier
|
|
|
57
61
|
@profile._stack_table
|
|
58
62
|
end
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
thread[:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
samples = thread[:samples]
|
|
65
|
+
weights = thread[:weights]
|
|
66
|
+
|
|
67
|
+
stack_weights = collapse_stack_weights(samples, weights)
|
|
64
68
|
|
|
65
69
|
total = stack_weights.values.sum
|
|
66
70
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vernier
|
|
4
|
+
module OutputHelpers
|
|
5
|
+
# Collapses parallel samples/weights arrays into a hash of total weight
|
|
6
|
+
# per stack index. Retained-mode profiles have one sample per object and
|
|
7
|
+
# many samples share the same stack, so collapsing first keeps later
|
|
8
|
+
# aggregation proportional to the number of unique stacks.
|
|
9
|
+
def collapse_stack_weights(samples, weights)
|
|
10
|
+
stack_weights = Hash.new(0)
|
|
11
|
+
samples.each_with_index do |stack_idx, idx|
|
|
12
|
+
stack_weights[stack_idx] += weights[idx]
|
|
13
|
+
end
|
|
14
|
+
stack_weights
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns a string safe to embed in JSON output: valid UTF-8 with any
|
|
18
|
+
# invalid bytes replaced. Method names and paths can carry arbitrary
|
|
19
|
+
# encodings (e.g. BINARY or Shift_JIS source), which would otherwise
|
|
20
|
+
# raise JSON::GeneratorError.
|
|
21
|
+
def sanitize_string(string)
|
|
22
|
+
if string.ascii_only?
|
|
23
|
+
string
|
|
24
|
+
elsif string.encoding == Encoding::UTF_8
|
|
25
|
+
string.valid_encoding? ? string : string.scrub
|
|
26
|
+
else
|
|
27
|
+
# We don't know the real encoding; interpret the bytes as UTF-8
|
|
28
|
+
# and replace anything invalid.
|
|
29
|
+
string.dup.force_encoding(Encoding::UTF_8).scrub
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/vernier/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vernier
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Hawthorn
|
|
@@ -106,6 +106,7 @@ files:
|
|
|
106
106
|
- lib/vernier/output/firefox.rb
|
|
107
107
|
- lib/vernier/output/markdown.rb
|
|
108
108
|
- lib/vernier/output/top.rb
|
|
109
|
+
- lib/vernier/output_helpers.rb
|
|
109
110
|
- lib/vernier/parsed_profile.rb
|
|
110
111
|
- lib/vernier/result.rb
|
|
111
112
|
- lib/vernier/stack_table.rb
|
|
@@ -134,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
134
135
|
- !ruby/object:Gem::Version
|
|
135
136
|
version: '0'
|
|
136
137
|
requirements: []
|
|
137
|
-
rubygems_version: 4.0.
|
|
138
|
+
rubygems_version: 4.0.16
|
|
138
139
|
specification_version: 4
|
|
139
140
|
summary: A next generation CRuby profiler
|
|
140
141
|
test_files: []
|