Skip to content

Commit 145de57

Browse files
committed
Extract some logic to parsed profiler
1 parent 77cc51d commit 145de57

File tree

4 files changed

+126
-34
lines changed

4 files changed

+126
-34
lines changed

exe/vernier

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env ruby
22

3+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4+
35
require "optparse"
46
require "vernier/version"
57

@@ -69,37 +71,23 @@ FLAGS:
6971

7072
def self.inverted_tree(top, file)
7173
# Print the inverted tree from a Vernier profile
72-
require "json"
73-
74-
is_gzip = File.binread(file, 2) == "\x1F\x8B".b # check for gzip header
75-
76-
json = if is_gzip
77-
require "zlib"
78-
Zlib::GzipReader.open(file) { |gz| gz.read }
79-
else
80-
File.read file
81-
end
74+
require "vernier/parsed_profile"
8275

83-
info = JSON.load json
84-
85-
main = info["threads"].find { |thread| thread["isMainThread"] }
76+
parsed_profile = Vernier::ParsedProfile.read_file(file)
77+
main_thread = parsed_profile.main_thread
78+
main = main_thread.data
79+
stack_table = main_thread.stack_table
8680

8781
weight_by_frame = Hash.new(0)
8882

89-
stack_frames = main["stackTable"]["frame"]
90-
frame_func_table = main["frameTable"]["func"]
91-
func_name_table = main["funcTable"]["name"]
92-
string_array = main["stringArray"]
93-
9483
main["samples"]["stack"].zip(main["samples"]["weight"]).each do |stack, weight|
95-
top_frame_index = stack_frames[stack]
96-
func_index = frame_func_table[top_frame_index]
97-
string_index = func_name_table[func_index]
98-
str = string_array[string_index]
84+
top_frame_index = stack_table.stack_frame_idx(stack)
85+
func_index = stack_table.frame_func_idx(top_frame_index)
86+
str = stack_table.func_name(func_index)
9987
weight_by_frame[str] += weight
10088
end
10189

102-
total = weight_by_frame.values.inject :+
90+
total = weight_by_frame.values.sum
10391

10492
header = ["Samples", "%", ""]
10593
widths = header.map(&:bytesize)
@@ -120,17 +108,16 @@ FLAGS:
120108
h[k] = SamplesByLocation.new
121109
end
122110

123-
stack_parents = main["stackTable"]["prefix"]
124111
main["samples"]["stack"].zip(main["samples"]["weight"]).each do |stack_idx, weight|
125112
# self time
126-
top_frame_index = stack_frames[stack_idx]
113+
top_frame_index = stack_table.stack_frame_idx(stack_idx)
127114
self_samples_by_frame[top_frame_index].self += weight
128115

129116
# total time
130117
while stack_idx
131-
frame_idx = stack_frames[stack_idx]
118+
frame_idx = stack_table.stack_frame_idx(stack_idx)
132119
self_samples_by_frame[frame_idx].total += weight
133-
stack_idx = stack_parents[stack_idx]
120+
stack_idx = stack_table.stack_parent_idx(stack_idx)
134121
end
135122
end
136123

@@ -143,18 +130,28 @@ FLAGS:
143130
frame_lines = main["frameTable"]["line"]
144131
func_filenames = main["funcTable"]["fileName"]
145132
self_samples_by_frame.each do |frame, samples|
146-
line = frame_lines[frame]
147-
func_index = frame_func_table[frame]
148-
filename = func_filenames[func_index]
149-
#string_index = func_name_table[func_index]
133+
line = stack_table.frame_line_no(frame)
134+
func_index = stack_table.frame_func_idx(frame)
135+
filename = stack_table.func_filename_idx(func_index)
150136

151137
samples_by_file[filename][line] += samples
152138
end
153139

154-
samples_by_file.transform_keys! { string_array[_1] }
140+
samples_by_file.transform_keys! { stack_table.strings[_1] }
155141

156-
#FIXME
157-
print_file("examples/gvl_sleep.rb", samples_by_file)
142+
relevant_files = samples_by_file.select do |k, v|
143+
next if k.start_with?("gem:")
144+
next if k.start_with?("rubylib:")
145+
next if k.start_with?("<")
146+
v.values.map(&:total).sum > total * 0.01
147+
end
148+
relevant_files.keys.sort.each do |filename|
149+
puts "="*80
150+
puts filename
151+
puts "-"*80
152+
print_file(filename, samples_by_file)
153+
end
154+
puts "="*80
158155
end
159156

160157
def self.print_file(filename, all_samples)

lib/vernier/parsed_profile.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
5+
module Vernier
6+
class ParsedProfile
7+
def self.read_file(filename)
8+
# Print the inverted tree from a Vernier profile
9+
is_gzip = File.binread(filename, 2) == "\x1F\x8B".b # check for gzip header
10+
11+
json = if is_gzip
12+
require "zlib"
13+
Zlib::GzipReader.open(filename) { |gz| gz.read }
14+
else
15+
File.read filename
16+
end
17+
18+
info = JSON.load json
19+
20+
new(info)
21+
end
22+
23+
class StackTable
24+
def initialize(thread_data)
25+
@stack_parents = thread_data["stackTable"]["prefix"]
26+
@stack_frames = thread_data["stackTable"]["frame"]
27+
@frame_funcs = thread_data["frameTable"]["func"]
28+
@frame_lines = thread_data["frameTable"]["line"]
29+
@func_names = thread_data["funcTable"]["name"]
30+
@func_filenames = thread_data["funcTable"]["fileName"]
31+
#@func_first_linenos = thread_data["funcTable"]["first"]
32+
@strings = thread_data["stringArray"]
33+
end
34+
35+
attr_reader :strings
36+
37+
def stack_parent_idx(idx) = @stack_parents[idx]
38+
def stack_frame_idx(idx) = @stack_frames[idx]
39+
40+
def frame_func_idx(idx) = @frame_funcs[idx]
41+
def frame_line_no(idx) = @frame_lines[idx]
42+
43+
def func_name_idx(idx) = @func_names[idx]
44+
def func_filename_idx(idx) = @func_filenames[idx]
45+
def func_name(idx) = @strings[func_name_idx(idx)]
46+
def func_filename(idx) = @strings[func_filename_idx(idx)]
47+
def func_first_lineno(idx) = @func_first_lineno[idx]
48+
end
49+
50+
class Thread
51+
attr_reader :data
52+
53+
def initialize(data)
54+
@data = data
55+
end
56+
57+
def stack_table
58+
@stack_table ||= StackTable.new(@data)
59+
end
60+
61+
def main_thread?
62+
@data["isMainThread"]
63+
end
64+
end
65+
66+
attr_reader :data
67+
def initialize(data)
68+
@data = data
69+
end
70+
71+
def threads
72+
@threads ||=
73+
@data["threads"].map do |thread_data|
74+
Thread.new(thread_data)
75+
end
76+
end
77+
78+
def main_thread
79+
threads.detect(&:main_thread?)
80+
end
81+
end
82+
end

test/fixtures/gvl_sleep.vernier.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

test/test_parsed_profile.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
require "vernier/parsed_profile"
5+
6+
class TestParsedProfile < Minitest::Test
7+
def test_gvl_sleep
8+
path = File.expand_path("../fixtures/gvl_sleep.vernier.json", __FILE__)
9+
parsed_profile = Vernier::ParsedProfile.read_file(path)
10+
main_thread = parsed_profile.main_thread
11+
end
12+
end

0 commit comments

Comments
 (0)