mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-25 15:23:06 +00:00
Setup benchmarking structure
- Setup dummy app files in `test/dummy` - Setup dummy test server `bin/serve_dummy - Note: Serializer caching can be completely disabled by passing in `CACHE_ON=off bin/serve_dummy start` since Serializer#_cache is only set at boot. - run with - ./bin/bench - `bin/bench` etc adapted from ruby-bench-suite - target files are `test/dummy/bm_*.rb`. Just add another to run it. - benchmark cache/no cache - remove rake dependency that loads unnecessary files - remove git gem dependency - Running over revisions to be added in subsequent PR
This commit is contained in:
170
bin/bench
Executable file
170
bin/bench
Executable file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env ruby
|
||||
# ActiveModelSerializers Benchmark driver
|
||||
# Adapted from
|
||||
# https://github.com/ruby-bench/ruby-bench-suite/blob/8ad567f7e43a044ae48c36833218423bb1e2bd9d/rails/benchmarks/driver.rb
|
||||
require 'bundler'
|
||||
Bundler.setup
|
||||
require 'json'
|
||||
require 'pathname'
|
||||
require 'optparse'
|
||||
require 'digest'
|
||||
require 'pathname'
|
||||
require 'shellwords'
|
||||
require 'logger'
|
||||
require 'English'
|
||||
|
||||
class BenchmarkDriver
|
||||
ROOT = Pathname File.expand_path(File.join('..', '..'), __FILE__)
|
||||
BASE = ENV.fetch('BASE') { ROOT.join('test', 'dummy') }
|
||||
ESCAPED_BASE = Shellwords.shellescape(BASE)
|
||||
|
||||
def self.benchmark(options)
|
||||
new(options).run
|
||||
end
|
||||
|
||||
def self.parse_argv_and_run(argv = ARGV, options = {})
|
||||
options = {
|
||||
repeat_count: 1,
|
||||
pattern: [],
|
||||
env: 'CACHE_ON=on'
|
||||
}.merge!(options)
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = 'Usage: bin/bench [options]'
|
||||
|
||||
opts.on('-r', '--repeat-count [NUM]', 'Run benchmarks [NUM] times taking the best result') do |value|
|
||||
options[:repeat_count] = value.to_i
|
||||
end
|
||||
|
||||
opts.on('-p', '--pattern <PATTERN1,PATTERN2,PATTERN3>', 'Benchmark name pattern') do |value|
|
||||
options[:pattern] = value.split(',')
|
||||
end
|
||||
|
||||
opts.on('-e', '--env <var1=val1,var2=val2,var3=vale>', 'ENV variables to pass in') do |value|
|
||||
options[:env] = value.split(',')
|
||||
end
|
||||
end.parse!(argv)
|
||||
|
||||
benchmark(options)
|
||||
end
|
||||
|
||||
attr_reader :commit_hash, :base
|
||||
|
||||
# Based on logfmt:
|
||||
# https://www.brandur.org/logfmt
|
||||
# For more complete implementation see:
|
||||
# see https://github.com/arachnid-cb/logfmtr/blob/master/lib/logfmtr/base.rb
|
||||
# For usage see:
|
||||
# https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write/
|
||||
# https://engineering.heroku.com/blogs/2014-09-05-hutils-explore-your-structured-data-logs/
|
||||
# For Ruby parser see:
|
||||
# https://github.com/cyberdelia/logfmt-ruby
|
||||
def self.summary_logger(device = 'output.txt')
|
||||
require 'time'
|
||||
logger = Logger.new(device)
|
||||
logger.level = Logger::INFO
|
||||
logger.formatter = proc { |severity, datetime, progname, msg|
|
||||
msg = "'#{msg}'"
|
||||
"level=#{severity} time=#{datetime.utc.iso8601(6)} pid=#{Process.pid} progname=#{progname} msg=#{msg}#{$INPUT_RECORD_SEPARATOR}"
|
||||
}
|
||||
logger
|
||||
end
|
||||
|
||||
def self.stdout_logger
|
||||
logger = Logger.new(STDOUT)
|
||||
logger.level = Logger::INFO
|
||||
logger.formatter = proc { |_, _, _, msg| "#{msg}#{$INPUT_RECORD_SEPARATOR}" }
|
||||
logger
|
||||
end
|
||||
|
||||
def initialize(options)
|
||||
@writer = ENV['SUMMARIZE'] ? self.class.summary_logger : self.class.stdout_logger
|
||||
@repeat_count = options[:repeat_count]
|
||||
@pattern = options[:pattern]
|
||||
@commit_hash = options.fetch(:commit_hash) { `git rev-parse --short HEAD`.chomp }
|
||||
@base = options.fetch(:base) { ESCAPED_BASE }
|
||||
@env = Array(options[:env]).join(' ')
|
||||
@rubyopt = options[:rubyopt] # TODO: rename
|
||||
end
|
||||
|
||||
def run
|
||||
files.each do |path|
|
||||
next if !@pattern.empty? && /#{@pattern.join('|')}/ !~ File.basename(path)
|
||||
run_single(Shellwords.shellescape(path))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def files
|
||||
Dir[File.join(base, 'bm_*')]
|
||||
end
|
||||
|
||||
def run_single(path)
|
||||
script = "RAILS_ENV=production #{@env} ruby #{@rubyopt} #{path}"
|
||||
environment = `ruby -v`.chomp.strip[/\d+\.\d+\.\d+\w+/]
|
||||
|
||||
runs_output = measure(script)
|
||||
if runs_output.empty?
|
||||
results = { error: :no_results }
|
||||
return
|
||||
end
|
||||
|
||||
results = {}
|
||||
results['commit_hash'] = commit_hash
|
||||
results['version'] = runs_output.first['version']
|
||||
results['benchmark_run[environment]'] = environment
|
||||
results['runs'] = []
|
||||
|
||||
runs_output.each do |output|
|
||||
results['runs'] << {
|
||||
'benchmark_type[category]' => output['label'],
|
||||
'benchmark_run[result][iterations_per_second]' => output['iterations_per_second'].round(3),
|
||||
'benchmark_run[result][total_allocated_objects_per_iteration]' => output['total_allocated_objects_per_iteration']
|
||||
}
|
||||
end
|
||||
ensure
|
||||
results && report(results)
|
||||
end
|
||||
|
||||
def report(results)
|
||||
@writer.info { 'Benchmark results:' }
|
||||
@writer.info { JSON.pretty_generate(results) }
|
||||
end
|
||||
|
||||
def summarize(result)
|
||||
puts "#{result['label']} #{result['iterations_per_second']}/ips; #{result['total_allocated_objects_per_iteration']} objects"
|
||||
end
|
||||
|
||||
# FIXME: ` provides the full output but it'll return failed output as well.
|
||||
def measure(script)
|
||||
results = Hash.new { |h, k| h[k] = [] }
|
||||
|
||||
@repeat_count.times do
|
||||
output = sh(script)
|
||||
output.each_line do |line|
|
||||
next if line.nil?
|
||||
begin
|
||||
result = JSON.parse(line)
|
||||
rescue JSON::ParserError
|
||||
result = { error: line } # rubocop:disable Lint/UselessAssignment
|
||||
else
|
||||
summarize(result)
|
||||
results[result['label']] << result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
results.map do |_, bm_runs|
|
||||
bm_runs.sort_by do |run|
|
||||
run['iterations_per_second']
|
||||
end.last
|
||||
end
|
||||
end
|
||||
|
||||
def sh(cmd)
|
||||
`#{cmd}`
|
||||
end
|
||||
end
|
||||
|
||||
BenchmarkDriver.parse_argv_and_run if $PROGRAM_NAME == __FILE__
|
||||
39
bin/serve_dummy
Executable file
39
bin/serve_dummy
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
|
||||
start)
|
||||
config="${CONFIG_RU:-test/dummy/config.ru}"
|
||||
bundle exec ruby -Ilib -S rackup "$config" --daemonize --pid tmp/dummy_app.pid --warn --server webrick
|
||||
until [ -f 'tmp/dummy_app.pid' ]; do
|
||||
sleep 0.1 # give it time to start.. I don't know a better way
|
||||
done
|
||||
cat tmp/dummy_app.pid
|
||||
true
|
||||
;;
|
||||
|
||||
stop)
|
||||
if [ -f 'tmp/dummy_app.pid' ]; then
|
||||
kill -TERM $(cat tmp/dummy_app.pid)
|
||||
else
|
||||
echo 'No pidfile'
|
||||
false
|
||||
fi
|
||||
;;
|
||||
|
||||
status)
|
||||
if [ -f 'tmp/dummy_app.pid' ]; then
|
||||
kill -0 $(cat tmp/dummy_app.pid)
|
||||
[ "$?" -eq 0 ]
|
||||
else
|
||||
echo 'No pidfile'
|
||||
false
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 [start|stop|status]"
|
||||
;;
|
||||
|
||||
esac
|
||||
Reference in New Issue
Block a user