Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect ratio_in_yjit from YJIT stats if avalible #54

Merged
merged 3 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions bin/yjit_intergration_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env ruby

require "bundler/setup"
require "promenade"
require "promenade/yjit/stats"
require "prometheus/client"
require "prometheus/client/formats/text"

Promenade.setup
Promenade::YJIT::Stats.instrument
puts Prometheus::Client::Formats::Text.marshal_multiprocess
13 changes: 12 additions & 1 deletion lib/promenade/yjit/stats.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
module Promenade
module YJIT
class Stats
RUNTIME_STATS = %i(
code_region_size
ratio_in_yjit
).freeze

Promenade.gauge :ruby_yjit_code_region_size do
doc "Ruby YJIT code size"
end

Promenade.gauge :ruby_yjit_ratio_in_yjit do
doc "Shows the ratio of YJIT-executed instructions in %"
end

def self.instrument
return unless defined?(::RubyVM::YJIT) && ::RubyVM::YJIT.enabled?

Promenade.metric(:ruby_yjit_code_region_size).set({}, ::RubyVM::YJIT.runtime_stats[:code_region_size])
::RubyVM::YJIT.runtime_stats.select { |stat, _| RUNTIME_STATS.include? stat }.each do |stat, value|
Promenade.metric(:"ruby_yjit_#{stat}").set({}, value)
end
end
end
end
Expand Down
69 changes: 48 additions & 21 deletions spec/yjit_spec.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,58 @@
require "promenade/yjit/stats"
require "open3"

RSpec.describe Promenade::YJIT::Stats do
describe "recording yjit stats" do
it "records code_region_size" do
# This method should not blow up in any case
it "doesn't explode" do
# This method should not blow up in any case, on any version of ruby
expect { described_class.instrument }.not_to raise_error

if defined?(RubyVM::YJIT) && defined?(RubyVM::YJIT.enable)

# We want to test that this doesn't blow up when yjit is present but isn't enabled yet
# you need to run the testsuite with yjit disabled for this to work
expect(RubyVM::YJIT.enabled?).to be_falsey
expect { described_class.instrument }.not_to raise_error

# Then we enable yjit to test the instrumentation
RubyVM::YJIT.enable
described_class.instrument

expect(Promenade.metric(:ruby_yjit_code_region_size).get).to eq RubyVM::YJIT.runtime_stats[:code_region_size]
else
version = RUBY_VERSION.match(/(\d).(\d).\d/)
major = version[1].to_i
minor = version[2].to_i
if major >= 3 && minor >= 3
flunk "YJIT must be avalibe to test properly in ruby 3.3+"
end
metrics = run_yjit_metrics("")
expect(metrics).to be_empty
end

it "records yjit stats" do
version = RUBY_VERSION.match(/(\d).(\d).\d/)
major = version[1].to_i
minor = version[2].to_i
unless major >= 3 && minor >= 3
pending "YJIT metrics are only expected to work in ruby 3.3.0+"
end

metrics = run_yjit_metrics("--yjit")
expect(metrics[:ruby_yjit_code_region_size]).to satisfy("be nonzero") { |n| n > 0 }
# ratio_in_yjit is only set when --yjit-stats is enabled
expect(metrics[:ruby_yjit_ratio_in_yjit]).to be_nil

metrics = run_yjit_metrics("--yjit --yjit-stats=quiet")
expect(metrics[:ruby_yjit_code_region_size]).to satisfy("be nonzero") { |n| n > 0 }
expect(metrics[:ruby_yjit_ratio_in_yjit]).to satisfy("be nonzero") { |n| n > 0 }
end
end

def run_yjit_metrics(rubyopt)
dir = Dir.mktmpdir
begin
output, status = Open3.capture2e({ "PROMETHEUS_MULTIPROC_DIR" => dir, "RUBYOPT" => rubyopt }, "bin/yjit_intergration_test")
expect(status).to eq 0
parse_metrics(output)
ensure
FileUtils.remove_entry dir
end
end

def parse_metrics(output)
output.lines.reject { |line| line.match("#") }.filter_map do |line|
match = line.match(/([a-z_]+)\{.+\} (\d+\.?\d*)/)
next unless match

[match[1].to_sym, parse_number(match[2])]
end.to_h
end

def parse_number(string)
Integer(string)
rescue StandardError
string.to_f
end
end
Loading