From 33e9aa45bedd542398a872f19383ada707fb2bf9 Mon Sep 17 00:00:00 2001 From: Thomas Klemm Date: Thu, 5 Nov 2026 18:43:24 +0000 Subject: [PATCH] Run lobsters with a fixed clock Lobsters' work per iteration changed with the calendar date: in its US Central time zone, lookbacks such as 70.days.ago that cross a DST change make ActiveSupport look up the zone period again, and two dates switch the logo. Time.now now returns a fixed instant in July, from which no lookback (up to 90 days) crosses a DST change. Harness code outside the benchmark block, including harnesses that HARNESS=chain loads late, keeps the real clock. TRACK_AR_TIME measures with the monotonic clock. --- benchmarks/lobsters/benchmark.rb | 51 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/benchmarks/lobsters/benchmark.rb b/benchmarks/lobsters/benchmark.rb index 9bf6c0e1..11b52d31 100644 --- a/benchmarks/lobsters/benchmark.rb +++ b/benchmarks/lobsters/benchmark.rb @@ -6,42 +6,53 @@ Dir.chdir __dir__ use_gemfile -require_relative 'config/environment' -require_relative "route_generator" - -# For an in-mem DB, we need to load all data on every boot -mem_db = ActiveRecord::Base.connection.raw_connection -file_db = SQLite3::Database.new('db/production.sqlite3') -b = SQLite3::Backup.new(mem_db, 'main', file_db, 'main') -b.step(-1) # import until finished -b.finish # destroy the Backup object -# The fixture has no planner statistics. Without them, SQLite misplans the -# hottest-stories query behind /, /recent and /rss. -mem_db.execute("ANALYZE") - -app = Rails.application -generator = RouteGenerator.new(app) -generator.routes # Make sure routes have been pregenerated +# Avoid calendar-dependent DST and holiday-logo work. +FROZEN_NOW = Time.utc(2026, 7, 1, 12, 0, 0, 123456.789r).getlocal.freeze +REAL_NOW = Time.method(:now) +def Time.now = $clock_frozen ? FROZEN_NOW.dup : REAL_NOW.call + +begin + $clock_frozen = true + require_relative 'config/environment' + require_relative "route_generator" + + # For an in-mem DB, we need to load all data on every boot + mem_db = ActiveRecord::Base.connection.raw_connection + file_db = SQLite3::Database.new('db/production.sqlite3') + b = SQLite3::Backup.new(mem_db, 'main', file_db, 'main') + b.step(-1) # import until finished + b.finish # destroy the Backup object + # The fixture has no planner statistics. Without them, SQLite misplans the + # hottest-stories query behind /, /recent and /rss. + mem_db.execute("ANALYZE") + + app = Rails.application + generator = RouteGenerator.new(app) + generator.routes # Make sure routes have been pregenerated +ensure + $clock_frozen = false +end # Track ActiveRecord time if ENV['TRACK_AR_TIME'] ar_total_duration = 0.0 - process_start_t = Time.now + process_start_t = Process.clock_gettime(Process::CLOCK_MONOTONIC) # Track sql.active_record events - ActiveSupport::Notifications.subscribe "sql.active_record" do |name, started, finished, unique_id, data| + ActiveSupport::Notifications.monotonic_subscribe "sql.active_record" do |name, started, finished, unique_id, data| duration = finished - started ar_total_duration += duration end at_exit do - process_duration = Time.now - process_start_t + process_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - process_start_t ar_percent = ar_total_duration * 100.0 / process_duration puts "ActiveRecord time: #{ar_total_duration.round(2)}s (#{ar_percent.round(2)}%) PID: #{Process.pid}" end end run_benchmark(10) do + $clock_frozen = true # Routes are from ./routes_generator.rb generator.routes.each_with_index do |env, idx| path = env["PATH_INFO"] # app.call mutates the path @@ -52,6 +63,8 @@ end response_array.last.close # Response might be a Rack::BodyProxy and MUST be closed. end +ensure + $clock_frozen = false end # This benchmark will keep writing the production log on every request. It adds up.