Skip to content

Commit

Permalink
Test and make file listing work for live
Browse files Browse the repository at this point in the history
  • Loading branch information
jhawthorn committed Dec 14, 2024
1 parent ba8aef7 commit 5a3bd45
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 33 deletions.
2 changes: 2 additions & 0 deletions lib/vernier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
require_relative "vernier/vernier"
require_relative "vernier/output/firefox"
require_relative "vernier/output/top"
require_relative "vernier/output/file_listing"
require_relative "vernier/output/filename_filter"

module Vernier
class Error < StandardError; end
Expand Down
33 changes: 20 additions & 13 deletions lib/vernier/output/file_listing.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require_relative "filename_filter"

module Vernier
module Output
class FileListing
Expand All @@ -25,21 +27,26 @@ def output
output = +""

thread = @profile.main_thread
main = thread.data
stack_table =
if thread.respond_to?(:stack_table)
thread.stack_table
else
@profile._stack_table
end
if Hash === thread
# live profile
stack_table = @profile._stack_table
weights = thread[:weights]
samples = thread[:samples]
filename_filter = FilenameFilter.new
else
stack_table = thread.stack_table
weights = thread.weights
samples = thread.samples
filename_filter = ->(x) { x }
end

self_samples_by_frame = Hash.new do |h, k|
h[k] = SamplesByLocation.new
end

total = main["samples"]["weight"].sum
total = weights.sum

main["samples"]["stack"].zip(main["samples"]["weight"]).each do |stack_idx, weight|
samples.zip(weights).each do |stack_idx, weight|
# self time
top_frame_index = stack_table.stack_frame_idx(stack_idx)
self_samples_by_frame[top_frame_index].self += weight
Expand All @@ -58,17 +65,17 @@ def output
end
end

frame_lines = main["frameTable"]["line"]
func_filenames = main["funcTable"]["fileName"]
self_samples_by_frame.each do |frame, samples|
line = stack_table.frame_line_no(frame)
func_index = stack_table.frame_func_idx(frame)
filename = stack_table.func_filename_idx(func_index)
filename = stack_table.func_filename(func_index)

samples_by_file[filename][line] += samples
end

samples_by_file.transform_keys! { stack_table.strings[_1] }
samples_by_file.transform_keys! do |filename|
filename_filter.call(filename)
end

relevant_files = samples_by_file.select do |k, v|
next if k.start_with?("gem:")
Expand Down
30 changes: 30 additions & 0 deletions lib/vernier/output/filename_filter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module Vernier
module Output
class FilenameFilter
def initialize
@pwd = "#{Dir.pwd}/"
@gem_regex = %r{\A#{Regexp.union(Gem.path)}/gems/}
@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}
@rubylibdir = "#{RbConfig::CONFIG["rubylibdir"]}/"
end

attr_reader :pwd, :gem_regex, :gem_match_regex, :rubylibdir

def call(filename)
if filename.match?(gem_regex)
gem_match_regex =~ filename
"gem:#$1-#$2:#$3"
elsif filename.start_with?(pwd)
filename.delete_prefix(pwd)
elsif filename.start_with?(rubylibdir)
path = filename.delete_prefix(rubylibdir)
"rubylib:#{RUBY_VERSION}:#{path}"
else
filename
end
end
end
end
end
20 changes: 4 additions & 16 deletions lib/vernier/output/firefox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
require "json"
require "rbconfig"

require_relative "filename_filter"

module Vernier
module Output
# https://profiler.firefox.com/
Expand Down Expand Up @@ -324,23 +326,9 @@ def initialize(ruby_thread_id, profile, categorizer, name:, tid:, samples:, weig
end

def filter_filenames(filenames)
pwd = "#{Dir.pwd}/"
gem_regex = %r{\A#{Regexp.union(Gem.path)}/gems/}
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}
rubylibdir = "#{RbConfig::CONFIG["rubylibdir"]}/"

filter = FilenameFilter.new
filenames.map do |filename|
if filename.match?(gem_regex)
gem_match_regex =~ filename
"gem:#$1-#$2:#$3"
elsif filename.start_with?(pwd)
filename.delete_prefix(pwd)
elsif filename.start_with?(rubylibdir)
path = filename.delete_prefix(rubylibdir)
"rubylib:#{RUBY_VERSION}:#{path}"
else
filename
end
filter.call(filename)
end
end

Expand Down
40 changes: 40 additions & 0 deletions test/output/test_file_listing.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require "test_helper"

class TestOutputFileListing < Minitest::Test
def test_complex_profile
result = Vernier.trace do
# Proper Ruby sleep
sleep 0.01

# Sleep inside rb_thread_call_without_gvl
GVLTest.sleep_without_gvl(0.01)

# Sleep with GVL held
GVLTest.sleep_holding_gvl(0.01)

# Ruby busy sleep
target = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 0.01
while Process.clock_gettime(Process::CLOCK_MONOTONIC) < target
end
end

output = Vernier::Output::FileListing.new(result).output
assert_match(/\d+\.\d% \| *\d\.\d% \| *\d+ +sleep 0\.01/, output)
assert_match(/\d+\.\d% \| *\d\.\d% \| *\d+ +GVLTest\.sleep_without_gvl/, output)
assert_match(/\d+\.\d% \| *\d\.\d% \| *\d+ +GVLTest\.sleep_holding_gvl/, output)
assert_match(/\d+\.\d% \| *\d\.\d% \| *\d+ +while Process\.clock_gettime/, output)
end

def test_parsed_profile
profile = Vernier::ParsedProfile.read_file(fixture_path("gvl_sleep.vernier.json"))
output = Vernier::Output::FileListing.new(profile).output
assert_includes output, <<TEXT
24.8% | 0.0% | 44 run(:cfunc_sleep_gvl)
24.7% | 0.0% | 45 run(:cfunc_sleep_idle)
24.6% | 0.0% | 46 run(:ruby_sleep_gvl)
24.7% | 0.0% | 47 run(:sleep_idle)
TEXT
end
end
8 changes: 4 additions & 4 deletions test/output/test_top.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ def test_complex_profile
end

output = Vernier::Output::Top.new(result).output
assert_match(/^| \d+ *| \d+\.\d *| GVLTest\.sleep_without_gvl *|$/, output)
assert_match(/^| \d+ *| \d+\.\d *| GVLTest\.sleep_holding_gvl *|$/, output)
assert_match(/^| \d+ *| \d+\.\d *| Kernel#sleep *|$/, output)
assert_match(/^| \d+ *| \d+\.\d *| Process\.clock_gettime *|$/, output)
assert_match(/^| \d+ *\| \d+\.\d *\| GVLTest\.sleep_without_gvl *\|$/, output)
assert_match(/^| \d+ *\| \d+\.\d *\| GVLTest\.sleep_holding_gvl *\|$/, output)
assert_match(/^| \d+ *\| \d+\.\d *\| Kernel#sleep *\|$/, output)
assert_match(/^| \d+ *\| \d+\.\d *\| Process\.clock_gettime *\|$/, output)
end

def test_parsed_profile
Expand Down

0 comments on commit 5a3bd45

Please sign in to comment.