mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-06 23:00:11 +09:00
659f95748a
* Preload only uncached messages and reduce rendering overhead * Keep benchmark summaries without raw JSON results * Use Ruby benchmark drivers and keep generated results out of the repo
93 lines
4.2 KiB
Ruby
93 lines
4.2 KiB
Ruby
# Run through compare_message_hot_paths.rb against isolated production fixtures.
|
|
require "json"
|
|
require "digest"
|
|
require "active_support/testing/time_helpers"
|
|
|
|
include ActiveSupport::Testing::TimeHelpers
|
|
travel_to Time.utc(2026, 10, 4, 12)
|
|
|
|
# Keep framework/rendering measurements independent of Redis/network variance.
|
|
Rails.cache = ActiveSupport::Cache::MemoryStore.new
|
|
ActionView::PartialRenderer.collection_cache = Rails.cache
|
|
ApplicationController.cache_store = Rails.cache
|
|
ApplicationController.descendants.each do |controller|
|
|
controller.cache_store = Rails.cache
|
|
controller.allow_forgery_protection = false
|
|
end
|
|
ApplicationController.allow_forgery_protection = false
|
|
|
|
labels = JSON.parse(File.read(ENV.fetch("BENCH_LABELS")))
|
|
client = ActionDispatch::Integration::Session.new(Rails.application)
|
|
client.host! "campfire-benchmark.test"
|
|
client.post "/session", params: { email_address: labels.fetch("emails.david"), password: labels.fetch("passwords.all") }
|
|
raise "login failed" unless client.response.redirect?
|
|
|
|
paths = {
|
|
room: "/rooms/#{labels.fetch('rooms.watercooler')}",
|
|
messages: "/rooms/#{labels.fetch('rooms.watercooler')}/messages?before=#{labels.fetch('messages.busy_060')}",
|
|
sidebar: Rails.application.routes.url_helpers.user_sidebar_path,
|
|
search: "/searches?q=coffee"
|
|
}
|
|
queries = []
|
|
subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*, event|
|
|
queries << event[:sql] unless event[:cached] || event[:name] == "SCHEMA"
|
|
end
|
|
results = {}
|
|
paths.each do |name, path|
|
|
%w[cold warm].each do |cache|
|
|
2.times { client.get path }
|
|
times, counts, allocations, hashes, headers = [], [], [], [], []
|
|
Integer(ENV.fetch("BENCH_ITERATIONS", "20")).times do
|
|
Rails.cache.clear if cache == "cold"
|
|
queries.clear
|
|
start_allocations = GC.stat(:total_allocated_objects)
|
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
client.get path
|
|
times << (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
|
allocations << GC.stat(:total_allocated_objects) - start_allocations
|
|
raise "#{name}: HTTP #{client.response.status}" unless client.response.status == 200
|
|
counts << queries.length
|
|
hashes << Digest::SHA256.hexdigest(client.response.body)
|
|
headers << client.response.headers.slice("content-type", "etag", "cache-control", "vary")
|
|
end
|
|
results["#{name}_#{cache}"] = { milliseconds: times, queries: counts, allocations: allocations,
|
|
body_sha256: hashes.uniq, headers: headers.uniq, body_bytes: client.response.body.bytesize }
|
|
end
|
|
end
|
|
ActiveSupport::Notifications.unsubscribe(subscriber)
|
|
|
|
# Real Action Cable encoding/instrumentation, with adapter I/O removed. Keep fanout
|
|
# separate from network delivery capacity, which this probe does not measure.
|
|
room = Room.find(labels.fetch("rooms.watercooler"))
|
|
class BenchmarkPubsub
|
|
attr_reader :messages
|
|
def initialize
|
|
@messages = []
|
|
end
|
|
def broadcast(stream, payload)
|
|
@messages << [ stream, payload ]
|
|
end
|
|
end
|
|
adapter = BenchmarkPubsub.new
|
|
ActionCable.server.instance_variable_set(:@pubsub, adapter)
|
|
message = Message.new(room: room)
|
|
recipient_ids = (1..1000).to_a
|
|
memberships = Object.new
|
|
memberships.define_singleton_method(:pluck) { |_| recipient_ids }
|
|
room.define_singleton_method(:memberships) { memberships }
|
|
fanout_times, fanout_allocations = [], []
|
|
20.times do
|
|
adapter.messages.clear
|
|
allocated = GC.stat(:total_allocated_objects)
|
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
message.send(:broadcast_unread_room)
|
|
fanout_times << (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
|
fanout_allocations << GC.stat(:total_allocated_objects) - allocated
|
|
raise "wrong fanout count" unless adapter.messages.size == recipient_ids.size
|
|
raise "wrong payload" unless adapter.messages.all? { |_, body| JSON.parse(body) == { "roomId" => room.id } }
|
|
end
|
|
results["unread_fanout_1000"] = { milliseconds: fanout_times, allocations: fanout_allocations,
|
|
payload_sha256: Digest::SHA256.hexdigest(adapter.messages.first.last) }
|
|
puts JSON.pretty_generate({ ruby: RUBY_VERSION, rails: Rails.version, iterations: ENV.fetch("BENCH_ITERATIONS", "20").to_i,
|
|
cache: "MemoryStore", results: results })
|