mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-07 07:10:10 +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
85 lines
2.8 KiB
Ruby
85 lines
2.8 KiB
Ruby
require "fileutils"
|
|
require "json"
|
|
require "open3"
|
|
require "optparse"
|
|
|
|
module BenchmarkSupport
|
|
ROOT = File.expand_path("..", __dir__)
|
|
WORK = File.join(ROOT, "tmp/rails-optimization")
|
|
|
|
def parse_options(description, defaults)
|
|
options = { baseline: nil, seed: nil, image: "campfire-reference:app", cpus: "8-11", rounds: 2 }.merge(defaults)
|
|
parser = OptionParser.new do |parser|
|
|
parser.banner = "Usage: ruby #{$PROGRAM_NAME} --baseline PATH --seed PATH [options]\n#{description}"
|
|
options.each do |name, default|
|
|
type = case default
|
|
when Integer then Integer
|
|
when Float then Float
|
|
else String
|
|
end
|
|
parser.on("--#{name.to_s.tr('_', '-')} VALUE", type) { |value| options[name] = value }
|
|
end
|
|
parser.on("-h", "--help") { puts parser; exit }
|
|
end
|
|
parser.parse!
|
|
raise OptionParser::InvalidArgument, "unexpected arguments: #{ARGV.join(' ')}" unless ARGV.empty?
|
|
raise OptionParser::MissingArgument, "--baseline and --seed are required" unless options[:baseline] && options[:seed]
|
|
raise OptionParser::InvalidArgument, "--rounds must be even and at least 2" unless options[:rounds] >= 2 && options[:rounds].even?
|
|
%i[baseline seed output].each { |name| options[name] = File.expand_path(options.fetch(name)) }
|
|
options
|
|
rescue OptionParser::ParseError => error
|
|
abort "#{error.message}\n#{parser}"
|
|
end
|
|
|
|
def run(*command, input: nil)
|
|
output, errors, status = Open3.capture3(*command, stdin_data: input, binmode: true)
|
|
raise "#{command.first} failed (#{status.exitstatus}): #{errors}" unless status.success?
|
|
output
|
|
end
|
|
|
|
def remove_container(name)
|
|
Open3.capture3("docker", "rm", "-f", name)
|
|
end
|
|
|
|
def prepare_storage(seed, destination)
|
|
FileUtils.rm_rf(destination)
|
|
FileUtils.mkdir_p(destination)
|
|
FileUtils.cp_r(File.join(seed, "db"), File.join(destination, "db"))
|
|
FileUtils.cp_r(File.join(seed, "storage"), File.join(destination, "files"))
|
|
end
|
|
|
|
def prepare_assets(image)
|
|
assets = File.join(WORK, "runtime/assets")
|
|
FileUtils.mkdir_p(assets)
|
|
if Dir.empty?(assets)
|
|
archive = run("docker", "run", "--rm", "--entrypoint", "", image,
|
|
"tar", "-C", "/rails/public/assets", "-cf", "-", ".")
|
|
run("tar", "-xf", "-", "-C", assets, input: archive)
|
|
end
|
|
assets
|
|
end
|
|
|
|
def mounts(paths)
|
|
paths.flat_map { |host, target| [ "-v", "#{host}:#{target}" ] }
|
|
end
|
|
|
|
def environment(values)
|
|
values.flat_map { |name, value| [ "-e", "#{name}=#{value}" ] }
|
|
end
|
|
|
|
def write_json(path, data)
|
|
FileUtils.mkdir_p(File.dirname(path))
|
|
File.write(path, JSON.pretty_generate(data) + "\n")
|
|
end
|
|
|
|
def median(values)
|
|
values = values.sort
|
|
middle = values.length / 2
|
|
values.length.odd? ? values[middle] : (values[middle - 1] + values[middle]) / 2.0
|
|
end
|
|
|
|
def clock
|
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
end
|
|
end
|