diff --git a/lib/promenade.rb b/lib/promenade.rb index 884210a..32a61c2 100644 --- a/lib/promenade.rb +++ b/lib/promenade.rb @@ -1,7 +1,6 @@ require "promenade/version" require "promenade/setup" require "promenade/configuration" -require "promenade/railtie" if defined? Rails::Railtie require "promenade/prometheus" module Promenade @@ -25,3 +24,5 @@ def configure end end end + +require "promenade/railtie" if defined? Rails::Railtie diff --git a/lib/promenade/railtie.rb b/lib/promenade/railtie.rb index 6032ffa..a473eb0 100644 --- a/lib/promenade/railtie.rb +++ b/lib/promenade/railtie.rb @@ -2,11 +2,13 @@ require "promenade/engine" require "promenade/client/rack/http_request_duration_collector" require "promenade/client/rack/http_request_queue_time_collector" +require "promenade/yjit/middleware" module Promenade class Railtie < ::Rails::Railtie initializer "promenade.configure_rails_initialization" do Promenade.setup + Rails.application.config.middleware.use Promenade::YJIT::Middlware if defined? ::RubyVM::YJIT Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions, Promenade::Client::Rack::HTTPRequestDurationCollector Rails.application.config.middleware.insert 0, diff --git a/lib/promenade/yjit/middleware.rb b/lib/promenade/yjit/middleware.rb new file mode 100644 index 0000000..06bfd91 --- /dev/null +++ b/lib/promenade/yjit/middleware.rb @@ -0,0 +1,22 @@ +require "promenade/yjit/stats" + +module Promenade + module YJIT + class Middlware + RACK_AFTER_REPLY = "rack.after_reply".freeze + + def initialize(app) + @app = app + end + + def call(env) + if env.key?(RACK_AFTER_REPLY) + env[RACK_AFTER_REPLY] << -> { + ::Promenade::YJIT::Stats.instrument + } + end + @app.call(env) + end + end + end +end diff --git a/lib/promenade/yjit/stats.rb b/lib/promenade/yjit/stats.rb new file mode 100644 index 0000000..d249f05 --- /dev/null +++ b/lib/promenade/yjit/stats.rb @@ -0,0 +1,15 @@ +module Promenade + module YJIT + class Stats + Promenade.gauge :ruby_yjit_code_region_size do + doc "Ruby YJIT code size" + 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]) + end + end + end +end diff --git a/spec/yjit_spec.rb b/spec/yjit_spec.rb new file mode 100644 index 0000000..f1e29df --- /dev/null +++ b/spec/yjit_spec.rb @@ -0,0 +1,31 @@ +require "promenade/yjit/stats" + +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 + 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 + end + end + end +end