vernier 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/fiber_stalls.rb +51 -0
- data/exe/vernier +11 -52
- data/ext/vernier/extconf.rb +2 -0
- data/ext/vernier/memory.cc +144 -0
- data/ext/vernier/periodic_thread.hh +141 -0
- data/ext/vernier/signal_safe_semaphore.hh +72 -0
- data/ext/vernier/timestamp.hh +138 -0
- data/ext/vernier/vernier.cc +138 -338
- data/ext/vernier/vernier.hh +4 -0
- data/lib/vernier/autorun.rb +17 -1
- data/lib/vernier/collector.rb +37 -9
- data/lib/vernier/hooks/memory_usage.rb +37 -0
- data/lib/vernier/hooks.rb +1 -0
- data/lib/vernier/marker.rb +2 -0
- data/lib/vernier/output/file_listing.rb +113 -0
- data/lib/vernier/output/filename_filter.rb +30 -0
- data/lib/vernier/output/firefox.rb +29 -20
- data/lib/vernier/output/top.rb +60 -8
- data/lib/vernier/parsed_profile.rb +102 -0
- data/lib/vernier/result.rb +4 -92
- data/lib/vernier/stack_table.rb +3 -42
- data/lib/vernier/stack_table_helpers.rb +129 -0
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +3 -0
- metadata +12 -2
data/lib/vernier/autorun.rb
CHANGED
@@ -29,8 +29,24 @@ module Vernier
|
|
29
29
|
def self.stop
|
30
30
|
result = @collector.stop
|
31
31
|
@collector = nil
|
32
|
+
|
32
33
|
output_path = options[:output]
|
33
|
-
output_path
|
34
|
+
unless output_path
|
35
|
+
output_dir = options[:output_dir]
|
36
|
+
unless output_dir
|
37
|
+
if File.writable?(".")
|
38
|
+
output_dir = "."
|
39
|
+
else
|
40
|
+
output_dir = Dir.tmpdir
|
41
|
+
end
|
42
|
+
end
|
43
|
+
prefix = "profile-"
|
44
|
+
timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
|
45
|
+
suffix = ".vernier.json.gz"
|
46
|
+
|
47
|
+
output_path = File.expand_path("#{output_dir}/#{prefix}#{timestamp}-#{$$}#{suffix}")
|
48
|
+
end
|
49
|
+
|
34
50
|
result.write(out: output_path)
|
35
51
|
|
36
52
|
STDERR.puts(result.inspect)
|
data/lib/vernier/collector.rb
CHANGED
@@ -31,6 +31,8 @@ module Vernier
|
|
31
31
|
case hook.to_sym
|
32
32
|
when :rails, :activesupport
|
33
33
|
@hooks << Vernier::Hooks::ActiveSupport.new(self)
|
34
|
+
when :memory_usage
|
35
|
+
@hooks << Vernier::Hooks::MemoryUsage.new(self)
|
34
36
|
else
|
35
37
|
warn "unknown hook: #{hook.inspect}"
|
36
38
|
end
|
@@ -97,18 +99,44 @@ module Vernier
|
|
97
99
|
|
98
100
|
marker_strings = Marker.name_table
|
99
101
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
102
|
+
markers_by_thread_id = (@markers || []).group_by(&:first)
|
103
|
+
|
104
|
+
result.threads.each do |tid, thread|
|
105
|
+
last_fiber = nil
|
106
|
+
markers = []
|
107
|
+
|
108
|
+
markers.concat markers_by_thread_id.fetch(tid, [])
|
109
|
+
|
110
|
+
original_markers = thread[:markers] || []
|
111
|
+
original_markers += result.gc_markers || []
|
112
|
+
original_markers.each do |data|
|
113
|
+
type, phase, ts, te, stack, extra_info = data
|
114
|
+
if type == Marker::Type::FIBER_SWITCH
|
115
|
+
if last_fiber
|
116
|
+
start_event = markers[last_fiber]
|
117
|
+
markers << [nil, "Fiber Running", start_event[2], ts, Marker::Phase::INTERVAL, start_event[5].merge(type: "Fiber Running", cause: nil)]
|
118
|
+
end
|
119
|
+
last_fiber = markers.size
|
120
|
+
end
|
121
|
+
name = marker_strings[type]
|
122
|
+
sym = Marker::MARKER_SYMBOLS[type]
|
123
|
+
data = { type: sym }
|
124
|
+
data[:cause] = { stack: stack } if stack
|
125
|
+
data.merge!(extra_info) if extra_info
|
126
|
+
markers << [tid, name, ts, te, phase, data]
|
127
|
+
end
|
128
|
+
if last_fiber
|
129
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
130
|
+
start_event = markers[last_fiber]
|
131
|
+
markers << [nil, "Fiber Running", start_event[2], end_time, Marker::Phase::INTERVAL, start_event[5].merge(type: "Fiber Running", cause: nil)]
|
132
|
+
end
|
133
|
+
|
134
|
+
thread[:markers] = markers
|
107
135
|
end
|
108
136
|
|
109
|
-
markers.concat @markers
|
137
|
+
#markers.concat @markers
|
110
138
|
|
111
|
-
result.instance_variable_set(:@markers, markers)
|
139
|
+
#result.instance_variable_set(:@markers, markers)
|
112
140
|
|
113
141
|
if @out
|
114
142
|
result.write(out: @out)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vernier
|
4
|
+
module Hooks
|
5
|
+
class MemoryUsage
|
6
|
+
def initialize(collector)
|
7
|
+
@collector = collector
|
8
|
+
@tracker = Vernier::MemoryTracker.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def enable
|
12
|
+
@tracker.start
|
13
|
+
end
|
14
|
+
|
15
|
+
def disable
|
16
|
+
@tracker.stop
|
17
|
+
end
|
18
|
+
|
19
|
+
def firefox_counters
|
20
|
+
timestamps, memory = @tracker.results
|
21
|
+
memory = ([0] + memory).each_cons(2).map { _2 - _1 }
|
22
|
+
{
|
23
|
+
name: "memory",
|
24
|
+
category: "Memory",
|
25
|
+
description: "Memory usage in bytes",
|
26
|
+
pid: Process.pid,
|
27
|
+
mainThreadIndex: 0,
|
28
|
+
samples: {
|
29
|
+
time: timestamps.map { _1 / 1_000_000.0 },
|
30
|
+
count: memory,
|
31
|
+
length: timestamps.length
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/vernier/hooks.rb
CHANGED
data/lib/vernier/marker.rb
CHANGED
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "filename_filter"
|
4
|
+
|
5
|
+
module Vernier
|
6
|
+
module Output
|
7
|
+
class FileListing
|
8
|
+
class SamplesByLocation
|
9
|
+
attr_accessor :self, :total
|
10
|
+
def initialize
|
11
|
+
@self = @total = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def +(other)
|
15
|
+
ret = SamplesByLocation.new
|
16
|
+
ret.self = @self + other.self
|
17
|
+
ret.total = @total + other.total
|
18
|
+
ret
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(profile)
|
23
|
+
@profile = profile
|
24
|
+
end
|
25
|
+
|
26
|
+
def output
|
27
|
+
output = +""
|
28
|
+
|
29
|
+
thread = @profile.main_thread
|
30
|
+
if Hash === thread
|
31
|
+
# live profile
|
32
|
+
stack_table = @profile._stack_table
|
33
|
+
weights = thread[:weights]
|
34
|
+
samples = thread[:samples]
|
35
|
+
filename_filter = FilenameFilter.new
|
36
|
+
else
|
37
|
+
stack_table = thread.stack_table
|
38
|
+
weights = thread.weights
|
39
|
+
samples = thread.samples
|
40
|
+
filename_filter = ->(x) { x }
|
41
|
+
end
|
42
|
+
|
43
|
+
self_samples_by_frame = Hash.new do |h, k|
|
44
|
+
h[k] = SamplesByLocation.new
|
45
|
+
end
|
46
|
+
|
47
|
+
total = weights.sum
|
48
|
+
|
49
|
+
samples.zip(weights).each do |stack_idx, weight|
|
50
|
+
# self time
|
51
|
+
top_frame_index = stack_table.stack_frame_idx(stack_idx)
|
52
|
+
self_samples_by_frame[top_frame_index].self += weight
|
53
|
+
|
54
|
+
# total time
|
55
|
+
while stack_idx
|
56
|
+
frame_idx = stack_table.stack_frame_idx(stack_idx)
|
57
|
+
self_samples_by_frame[frame_idx].total += weight
|
58
|
+
stack_idx = stack_table.stack_parent_idx(stack_idx)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
samples_by_file = Hash.new do |h, k|
|
63
|
+
h[k] = Hash.new do |h2, k2|
|
64
|
+
h2[k2] = SamplesByLocation.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
self_samples_by_frame.each do |frame, samples|
|
69
|
+
line = stack_table.frame_line_no(frame)
|
70
|
+
func_index = stack_table.frame_func_idx(frame)
|
71
|
+
filename = stack_table.func_filename(func_index)
|
72
|
+
|
73
|
+
samples_by_file[filename][line] += samples
|
74
|
+
end
|
75
|
+
|
76
|
+
samples_by_file.transform_keys! do |filename|
|
77
|
+
filename_filter.call(filename)
|
78
|
+
end
|
79
|
+
|
80
|
+
relevant_files = samples_by_file.select do |k, v|
|
81
|
+
next if k.start_with?("gem:")
|
82
|
+
next if k.start_with?("rubylib:")
|
83
|
+
next if k.start_with?("<")
|
84
|
+
v.values.map(&:total).sum > total * 0.01
|
85
|
+
end
|
86
|
+
relevant_files.keys.sort.each do |filename|
|
87
|
+
output << "="*80 << "\n"
|
88
|
+
output << filename << "\n"
|
89
|
+
output << "-"*80 << "\n"
|
90
|
+
format_file(output, filename, samples_by_file, total: total)
|
91
|
+
end
|
92
|
+
output << "="*80 << "\n"
|
93
|
+
end
|
94
|
+
|
95
|
+
def format_file(output, filename, all_samples, total:)
|
96
|
+
samples = all_samples[filename]
|
97
|
+
|
98
|
+
# file_name, lines, file_wall, file_cpu, file_idle, file_sort
|
99
|
+
output << sprintf(" TOTAL | SELF | LINE SOURCE\n")
|
100
|
+
File.readlines(filename).each_with_index do |line, i|
|
101
|
+
lineno = i + 1
|
102
|
+
calls = samples[lineno]
|
103
|
+
|
104
|
+
if calls && calls.total > 0
|
105
|
+
output << sprintf("%5.1f%% | %5.1f%% | % 4i %s", 100 * calls.total / total.to_f, 100 * calls.self / total.to_f, lineno, line)
|
106
|
+
else
|
107
|
+
output << sprintf(" | | % 4i %s", lineno, line)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vernier
|
4
|
+
module Output
|
5
|
+
class FilenameFilter
|
6
|
+
def initialize
|
7
|
+
@pwd = "#{Dir.pwd}/"
|
8
|
+
@gem_regex = %r{\A#{Regexp.union(Gem.path)}/gems/}
|
9
|
+
@gem_match_regex = %r{\A#{Regexp.union(Gem.path)}/gems/([a-zA-Z](?:[a-zA-Z0-9\.\_]|-[a-zA-Z])*)-([0-9][0-9A-Za-z\-_\.]*)/(.*)\z}
|
10
|
+
@rubylibdir = "#{RbConfig::CONFIG["rubylibdir"]}/"
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :pwd, :gem_regex, :gem_match_regex, :rubylibdir
|
14
|
+
|
15
|
+
def call(filename)
|
16
|
+
if filename.match?(gem_regex)
|
17
|
+
gem_match_regex =~ filename
|
18
|
+
"gem:#$1-#$2:#$3"
|
19
|
+
elsif filename.start_with?(pwd)
|
20
|
+
filename.delete_prefix(pwd)
|
21
|
+
elsif filename.start_with?(rubylibdir)
|
22
|
+
path = filename.delete_prefix(rubylibdir)
|
23
|
+
"rubylib:#{RUBY_VERSION}:#{path}"
|
24
|
+
else
|
25
|
+
filename
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require "json"
|
4
4
|
require "rbconfig"
|
5
5
|
|
6
|
+
require_relative "filename_filter"
|
7
|
+
|
6
8
|
module Vernier
|
7
9
|
module Output
|
8
10
|
# https://profiler.firefox.com/
|
@@ -104,15 +106,15 @@ module Vernier
|
|
104
106
|
attr_reader :profile
|
105
107
|
|
106
108
|
def data
|
107
|
-
markers_by_thread = profile.markers.group_by { |marker| marker[0] }
|
109
|
+
#markers_by_thread = profile.markers.group_by { |marker| marker[0] }
|
108
110
|
|
109
111
|
threads = profile.threads.map do |ruby_thread_id, thread_info|
|
110
|
-
markers = markers_by_thread[ruby_thread_id] || []
|
112
|
+
#markers = markers_by_thread[ruby_thread_id] || []
|
111
113
|
Thread.new(
|
112
114
|
ruby_thread_id,
|
113
115
|
profile,
|
114
116
|
@categorizer,
|
115
|
-
markers: markers,
|
117
|
+
#markers: markers,
|
116
118
|
**thread_info,
|
117
119
|
)
|
118
120
|
end
|
@@ -126,7 +128,7 @@ module Vernier
|
|
126
128
|
product: "Ruby/Vernier",
|
127
129
|
stackwalk: 1,
|
128
130
|
version: 28,
|
129
|
-
preprocessedProfileVersion:
|
131
|
+
preprocessedProfileVersion: 48,
|
130
132
|
symbolicated: true,
|
131
133
|
markerSchema: marker_schema,
|
132
134
|
sampleUnits: {
|
@@ -145,11 +147,20 @@ module Vernier
|
|
145
147
|
initialVisibleThreads: threads.each_index.to_a,
|
146
148
|
initialSelectedThreads: Array(threads.find_index(&:is_start))
|
147
149
|
},
|
150
|
+
counters: counter_data,
|
148
151
|
libs: [],
|
149
152
|
threads: threads.map(&:data)
|
150
153
|
}
|
151
154
|
end
|
152
155
|
|
156
|
+
def counter_data
|
157
|
+
profile.hooks.flat_map do |hook|
|
158
|
+
if hook.respond_to?(:firefox_counters)
|
159
|
+
hook.firefox_counters
|
160
|
+
end
|
161
|
+
end.compact
|
162
|
+
end
|
163
|
+
|
153
164
|
def marker_schema
|
154
165
|
hook_additions = profile.hooks.flat_map do |hook|
|
155
166
|
if hook.respond_to?(:firefox_marker_schema)
|
@@ -201,6 +212,18 @@ module Vernier
|
|
201
212
|
{ key: "gc_by", format: "string" },
|
202
213
|
]
|
203
214
|
},
|
215
|
+
{
|
216
|
+
name: "FIBER_SWITCH",
|
217
|
+
display: [ "marker-chart", "marker-table", "timeline-overview" ],
|
218
|
+
tooltipLabel: "{marker.name} - {marker.data.fiber_id}",
|
219
|
+
data: [
|
220
|
+
{
|
221
|
+
label: "Description",
|
222
|
+
value: "Switch running Fiber"
|
223
|
+
},
|
224
|
+
{ key: "fiber_id", format: "integer" },
|
225
|
+
]
|
226
|
+
},
|
204
227
|
*hook_additions
|
205
228
|
]
|
206
229
|
end
|
@@ -303,23 +326,9 @@ module Vernier
|
|
303
326
|
end
|
304
327
|
|
305
328
|
def filter_filenames(filenames)
|
306
|
-
|
307
|
-
gem_regex = %r{\A#{Regexp.union(Gem.path)}/gems/}
|
308
|
-
gem_match_regex = %r{\A#{Regexp.union(Gem.path)}/gems/([a-zA-Z](?:[a-zA-Z0-9\.\_]|-[a-zA-Z])*)-([0-9][0-9A-Za-z\-_\.]*)/(.*)\z}
|
309
|
-
rubylibdir = "#{RbConfig::CONFIG["rubylibdir"]}/"
|
310
|
-
|
329
|
+
filter = FilenameFilter.new
|
311
330
|
filenames.map do |filename|
|
312
|
-
|
313
|
-
gem_match_regex =~ filename
|
314
|
-
"gem:#$1-#$2:#$3"
|
315
|
-
elsif filename.start_with?(pwd)
|
316
|
-
filename.delete_prefix(pwd)
|
317
|
-
elsif filename.start_with?(rubylibdir)
|
318
|
-
path = filename.delete_prefix(rubylibdir)
|
319
|
-
"rubylib:#{RUBY_VERSION}:#{path}"
|
320
|
-
else
|
321
|
-
filename
|
322
|
-
end
|
331
|
+
filter.call(filename)
|
323
332
|
end
|
324
333
|
end
|
325
334
|
|
data/lib/vernier/output/top.rb
CHANGED
@@ -7,23 +7,75 @@ module Vernier
|
|
7
7
|
@profile = profile
|
8
8
|
end
|
9
9
|
|
10
|
+
class Table
|
11
|
+
def initialize(header)
|
12
|
+
@header = header
|
13
|
+
@rows = []
|
14
|
+
yield self
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(row)
|
18
|
+
@rows << row
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
(
|
23
|
+
[
|
24
|
+
row_separator,
|
25
|
+
format_row(@header),
|
26
|
+
row_separator
|
27
|
+
] + @rows.map do |row|
|
28
|
+
format_row(row)
|
29
|
+
end + [row_separator]
|
30
|
+
).join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
def widths
|
34
|
+
@widths ||=
|
35
|
+
(@rows + [@header]).transpose.map do |col|
|
36
|
+
col.map(&:size).max
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def row_separator
|
41
|
+
@row_separator = "+" + widths.map { |i| "-" * (i + 2) }.join("+") + "+"
|
42
|
+
end
|
43
|
+
|
44
|
+
def format_row(row)
|
45
|
+
"|" + row.map.with_index { |str, i| " " + str.ljust(widths[i] + 1) }.join("|") + "|"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
10
49
|
def output
|
50
|
+
thread = @profile.main_thread
|
51
|
+
stack_table =
|
52
|
+
if thread.respond_to?(:stack_table)
|
53
|
+
thread.stack_table
|
54
|
+
else
|
55
|
+
@profile._stack_table
|
56
|
+
end
|
57
|
+
|
11
58
|
stack_weights = Hash.new(0)
|
12
|
-
|
59
|
+
thread[:samples].zip(thread[:weights]) do |stack_idx, weight|
|
13
60
|
stack_weights[stack_idx] += weight
|
14
61
|
end
|
15
62
|
|
63
|
+
total = stack_weights.values.sum
|
64
|
+
|
16
65
|
top_by_self = Hash.new(0)
|
17
66
|
stack_weights.each do |stack_idx, weight|
|
18
|
-
|
19
|
-
|
67
|
+
frame_idx = stack_table.stack_frame_idx(stack_idx)
|
68
|
+
func_idx = stack_table.frame_func_idx(frame_idx)
|
69
|
+
name = stack_table.func_name(func_idx)
|
70
|
+
top_by_self[name] += weight
|
20
71
|
end
|
21
72
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
73
|
+
Table.new %w[Samples % name] do |t|
|
74
|
+
top_by_self.sort_by(&:last).reverse.each do |frame, samples|
|
75
|
+
pct = 100.0 * samples / total
|
76
|
+
t << [samples.to_s, pct.round(1).to_s, frame]
|
77
|
+
end
|
78
|
+
end.to_s
|
27
79
|
end
|
28
80
|
end
|
29
81
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require_relative "stack_table_helpers"
|
5
|
+
|
6
|
+
module Vernier
|
7
|
+
class ParsedProfile
|
8
|
+
def self.read_file(filename)
|
9
|
+
# Print the inverted tree from a Vernier profile
|
10
|
+
is_gzip = File.binread(filename, 2) == "\x1F\x8B".b # check for gzip header
|
11
|
+
|
12
|
+
json = if is_gzip
|
13
|
+
require "zlib"
|
14
|
+
Zlib::GzipReader.open(filename) { |gz| gz.read }
|
15
|
+
else
|
16
|
+
File.read filename
|
17
|
+
end
|
18
|
+
|
19
|
+
info = JSON.load json
|
20
|
+
|
21
|
+
new(info)
|
22
|
+
end
|
23
|
+
|
24
|
+
class StackTable
|
25
|
+
def initialize(thread_data)
|
26
|
+
@stack_parents = thread_data["stackTable"]["prefix"]
|
27
|
+
@stack_frames = thread_data["stackTable"]["frame"]
|
28
|
+
@frame_funcs = thread_data["frameTable"]["func"]
|
29
|
+
@frame_lines = thread_data["frameTable"]["line"]
|
30
|
+
@func_names = thread_data["funcTable"]["name"]
|
31
|
+
@func_filenames = thread_data["funcTable"]["fileName"]
|
32
|
+
#@func_first_linenos = thread_data["funcTable"]["first"]
|
33
|
+
@strings = thread_data["stringArray"]
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :strings
|
37
|
+
|
38
|
+
def stack_count = @stack_parents.length
|
39
|
+
def frame_count = @frame_funcs.length
|
40
|
+
def func_count = @func_names.length
|
41
|
+
|
42
|
+
def stack_parent_idx(idx) = @stack_parents[idx]
|
43
|
+
def stack_frame_idx(idx) = @stack_frames[idx]
|
44
|
+
|
45
|
+
def frame_func_idx(idx) = @frame_funcs[idx]
|
46
|
+
def frame_line_no(idx) = @frame_lines[idx]
|
47
|
+
|
48
|
+
def func_name_idx(idx) = @func_names[idx]
|
49
|
+
def func_filename_idx(idx) = @func_filenames[idx]
|
50
|
+
def func_name(idx) = @strings[func_name_idx(idx)]
|
51
|
+
def func_filename(idx) = @strings[func_filename_idx(idx)]
|
52
|
+
def func_first_lineno(idx) = @func_first_lineno[idx]
|
53
|
+
|
54
|
+
include StackTableHelpers
|
55
|
+
end
|
56
|
+
|
57
|
+
class Thread
|
58
|
+
attr_reader :data
|
59
|
+
|
60
|
+
def initialize(data)
|
61
|
+
@data = data
|
62
|
+
end
|
63
|
+
|
64
|
+
def stack_table
|
65
|
+
@stack_table ||= StackTable.new(@data)
|
66
|
+
end
|
67
|
+
|
68
|
+
def main_thread?
|
69
|
+
@data["isMainThread"]
|
70
|
+
end
|
71
|
+
|
72
|
+
def samples
|
73
|
+
@data["samples"]["stack"]
|
74
|
+
end
|
75
|
+
|
76
|
+
def weights
|
77
|
+
@data["samples"]["weight"]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Emulate hash
|
81
|
+
def [](name)
|
82
|
+
send(name)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_reader :data
|
87
|
+
def initialize(data)
|
88
|
+
@data = data
|
89
|
+
end
|
90
|
+
|
91
|
+
def threads
|
92
|
+
@threads ||=
|
93
|
+
@data["threads"].map do |thread_data|
|
94
|
+
Thread.new(thread_data)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def main_thread
|
99
|
+
threads.detect(&:main_thread?)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/vernier/result.rb
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
module Vernier
|
2
2
|
class Result
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
def _stack_table
|
8
|
-
@stack_table
|
9
|
-
end
|
3
|
+
attr_accessor :stack_table
|
4
|
+
alias _stack_table stack_table
|
10
5
|
|
11
|
-
attr_reader :
|
6
|
+
attr_reader :gc_markers
|
12
7
|
|
13
8
|
attr_accessor :hooks
|
14
9
|
|
@@ -60,91 +55,8 @@ module Vernier
|
|
60
55
|
end
|
61
56
|
end
|
62
57
|
|
63
|
-
class BaseType
|
64
|
-
attr_reader :result, :idx
|
65
|
-
def initialize(result, idx)
|
66
|
-
@result = result
|
67
|
-
@idx = idx
|
68
|
-
end
|
69
|
-
|
70
|
-
def to_s
|
71
|
-
idx.to_s
|
72
|
-
end
|
73
|
-
|
74
|
-
def inspect
|
75
|
-
"#<#{self.class}\n#{to_s}>"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
class Func < BaseType
|
80
|
-
def label
|
81
|
-
result._stack_table.func_name(idx)
|
82
|
-
end
|
83
|
-
alias name label
|
84
|
-
|
85
|
-
def filename
|
86
|
-
result._stack_table.func_filename(idx)
|
87
|
-
end
|
88
|
-
|
89
|
-
def to_s
|
90
|
-
"#{name} at #{filename}"
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
class Frame < BaseType
|
95
|
-
def label; func.label; end
|
96
|
-
def filename; func.filename; end
|
97
|
-
alias name label
|
98
|
-
|
99
|
-
def func
|
100
|
-
func_idx = result._stack_table.frame_func_idx(idx)
|
101
|
-
Func.new(result, func_idx)
|
102
|
-
end
|
103
|
-
|
104
|
-
def line
|
105
|
-
result._stack_table.frame_line_no(idx)
|
106
|
-
end
|
107
|
-
|
108
|
-
def to_s
|
109
|
-
"#{func}:#{line}"
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
class Stack < BaseType
|
114
|
-
def each_frame
|
115
|
-
return enum_for(__method__) unless block_given?
|
116
|
-
|
117
|
-
stack_idx = idx
|
118
|
-
while stack_idx
|
119
|
-
frame_idx = result._stack_table.stack_frame_idx(stack_idx)
|
120
|
-
yield Frame.new(result, frame_idx)
|
121
|
-
stack_idx = result._stack_table.stack_parent_idx(stack_idx)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def leaf_frame_idx
|
126
|
-
result._stack_table.stack_frame_idx(idx)
|
127
|
-
end
|
128
|
-
|
129
|
-
def leaf_frame
|
130
|
-
Frame.new(result, leaf_frame_idx)
|
131
|
-
end
|
132
|
-
|
133
|
-
def frames
|
134
|
-
each_frame.to_a
|
135
|
-
end
|
136
|
-
|
137
|
-
def to_s
|
138
|
-
arr = []
|
139
|
-
each_frame do |frame|
|
140
|
-
arr << frame.to_s
|
141
|
-
end
|
142
|
-
arr.join("\n")
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
58
|
def stack(idx)
|
147
|
-
|
59
|
+
stack_table.stack(idx)
|
148
60
|
end
|
149
61
|
|
150
62
|
def total_bytes
|