2013-08-15 15:19:23 +10:00
require " socket "
require " csv "
2013-08-15 16:35:57 +10:00
require " yaml "
2013-12-11 10:32:23 +11:00
require " optparse "
@include_env = false
@result_file = nil
2013-12-30 15:15:30 +11:00
@iterations = 500
2014-01-09 15:56:03 +11:00
@best_of = 1
2014-02-14 15:43:08 +11:00
@mem_stats = false
2014-02-16 16:44:51 +11:00
@unicorn = false
2014-02-14 15:43:08 +11:00
2013-12-11 10:32:23 +11:00
opts = OptionParser . new do | o |
o . banner = " Usage: ruby bench.rb [options] "
o . on ( " -n " , " --with_default_env " , " Include recommended Discourse env " ) do
@include_env = true
end
o . on ( " -o " , " --output [FILE] " , " Output results to this file " ) do | f |
@result_file = f
end
2013-12-30 15:15:30 +11:00
o . on ( " -i " , " --iterations [ITERATIONS] " , " Number of iterations to run the bench for " ) do | i |
@iterations = i . to_i
end
2014-01-09 15:56:03 +11:00
o . on ( " -b " , " --best_of [NUM] " , " Number of times to run the bench taking best as result " ) do | i |
@best_of = i . to_i
end
2014-02-14 15:43:08 +11:00
o . on ( " -m " , " --memory_stats " ) do
@mem_stats = true
end
2014-02-16 16:44:51 +11:00
o . on ( " -u " , " --unicorn " , " Use unicorn to serve pages as opposed to thin " ) do
@unicorn = true
end
2013-12-11 10:32:23 +11:00
end
opts . parse!
2013-08-15 16:35:57 +10:00
2014-01-09 15:56:03 +11:00
def run ( command , opt = nil )
if opt == :quiet
system ( command , out : " /dev/null " , err : :out )
else
system ( command , out : $stdout , err : :out )
end
2013-08-15 15:32:07 +10:00
end
2013-08-29 21:23:00 +10:00
begin
require 'facter'
rescue LoadError
run " gem install facter "
2014-01-03 13:03:58 +11:00
puts " please rerun script "
2014-01-02 10:21:01 +11:00
exit
2013-08-29 21:23:00 +10:00
end
@timings = { }
2013-08-15 16:35:57 +10:00
def measure ( name )
start = Time . now
yield
@timings [ name ] = ( ( Time . now - start ) * 1000 ) . to_i
end
2013-08-15 15:37:33 +10:00
def prereqs
puts " Be sure to following packages are installed:
sudo tasksel install postgresql - server
sudo apt - get - y install build - essential libssl - dev libyaml - dev git libtool libxslt - dev libxml2 - dev libpq - dev gawk curl pngcrush python - software - properties
sudo apt - add - repository - y ppa : rwky / redis
sudo apt - get update
sudo apt - get install redis - server
"
end
2013-08-15 15:19:23 +10:00
puts " Running bundle "
2014-01-09 15:56:03 +11:00
if ! run ( " bundle " , :quiet )
2013-08-15 15:32:07 +10:00
puts " Quitting, some of the gems did not install "
2013-08-15 15:37:33 +10:00
prereqs
2013-08-15 15:32:07 +10:00
exit
end
2013-08-15 15:19:23 +10:00
puts " Ensuring config is setup "
2014-01-02 10:21:01 +11:00
%x{ which ab > /dev/null 2>&1 }
unless $? == 0
2013-08-15 15:19:23 +10:00
abort " Apache Bench is not installed. Try: apt-get install apache2-utils or brew install ab "
end
unless File . exists? ( " config/database.yml " )
puts " Copying database.yml.development.sample to database.yml "
` cp config/database.yml.development-sample config/database.yml `
end
unless File . exists? ( " config/redis.yml " )
puts " Copying redis.yml.sample to redis.yml "
` cp config/redis.yml.sample config/redis.yml `
end
ENV [ " RAILS_ENV " ] = " profile "
2013-10-13 08:06:45 +11:00
2013-12-11 10:32:23 +11:00
if @include_env
puts " Running with tuned environment "
ENV [ " RUBY_GC_MALLOC_LIMIT " ] = " 50_000_000 "
ENV . delete " RUBY_HEAP_SLOTS_GROWTH_FACTOR "
ENV . delete " RUBY_HEAP_MIN_SLOTS "
ENV . delete " RUBY_FREE_MIN "
else
# clean env
2013-12-30 15:15:30 +11:00
puts " Running with the following custom environment "
%w{ RUBY_GC_MALLOC_LIMIT RUBY_HEAP_MIN_SLOTS RUBY_FREE_MIN } . each do | w |
puts " #{ w } : #{ ENV [ w ] } "
end
2013-10-13 08:06:45 +11:00
end
2013-08-15 16:35:57 +10:00
2013-08-15 15:19:23 +10:00
def port_available? port
2013-08-17 11:36:41 +02:00
server = TCPServer . open ( " 0.0.0.0 " , port )
2013-08-15 15:19:23 +10:00
server . close
true
rescue Errno :: EADDRINUSE
false
end
2013-08-15 17:13:05 +10:00
@port = 60079
2013-08-15 15:19:23 +10:00
2013-08-15 17:13:05 +10:00
while ! port_available? @port
@port += 1
2013-08-15 15:19:23 +10:00
end
puts " Ensuring profiling DB exists and is migrated "
puts ` bundle exec rake db:create `
` bundle exec rake db:migrate `
2013-08-15 16:59:38 +10:00
puts " Timing loading Rails "
2013-08-15 16:35:57 +10:00
measure ( " load_rails " ) do
` bundle exec rake middleware `
end
2013-08-15 15:19:23 +10:00
2013-08-15 16:59:38 +10:00
puts " Populating Profile DB "
run ( " bundle exec ruby script/profile_db_generator.rb " )
2013-08-15 15:19:23 +10:00
2013-09-10 16:03:11 +10:00
puts " Getting api key "
2013-09-10 16:22:58 +10:00
api_key = ` bundle exec rake api_key:get ` . split ( " \n " ) [ - 1 ]
2013-09-10 16:03:11 +10:00
2013-08-15 17:13:05 +10:00
def bench ( path )
2013-08-15 15:19:23 +10:00
puts " Running apache bench warmup "
2014-02-16 16:44:51 +11:00
add = " "
add = " -c 3 " if @unicorn
` ab #{ add } -n 10 "http://127.0.0.1: #{ @port } #{ path } " `
2013-08-15 17:13:05 +10:00
puts " Benchmarking #{ path } "
2013-12-30 15:15:30 +11:00
` ab -n #{ @iterations } -e tmp/ab.csv "http://127.0.0.1: #{ @port } #{ path } " `
2013-08-15 15:19:23 +10:00
percentiles = Hash [ * [ 50 , 75 , 90 , 99 ] . zip ( [ ] ) . flatten ]
CSV . foreach ( " tmp/ab.csv " ) do | percent , time |
percentiles [ percent . to_i ] = time . to_i if percentiles . key? percent . to_i
end
2013-08-15 17:13:05 +10:00
percentiles
end
begin
2013-09-10 16:03:11 +10:00
# critical cause cache may be incompatible
puts " precompiling assets "
run ( " bundle exec rake assets:precompile " )
2014-02-16 16:44:51 +11:00
pid = if @unicorn
ENV [ 'UNICORN_PORT' ] = @port . to_s
spawn ( " bundle exec unicorn -c config/unicorn.conf.rb " )
else
spawn ( " bundle exec thin start -p #{ @port } " )
end
2013-08-15 17:13:05 +10:00
while port_available? @port
sleep 1
end
2013-08-17 11:36:41 +02:00
puts " Starting benchmark... "
2014-01-09 15:56:03 +11:00
append = " ?api_key= #{ api_key } &api_username=admin1 "
2013-08-17 11:36:41 +02:00
2013-09-03 18:58:56 +10:00
# asset precompilation is a dog, wget to force it
2014-01-09 16:39:30 +11:00
run " wget http://127.0.0.1: #{ @port } / -o /dev/null "
2013-08-15 17:13:05 +10:00
2014-01-09 15:56:03 +11:00
tests = [
2014-02-16 15:11:25 +11:00
[ " categories " , " /categories " ] ,
2014-01-09 15:56:03 +11:00
[ " home " , " / " ] ,
2014-02-16 15:11:25 +11:00
[ " topic " , " /t/oh-how-i-wish-i-could-shut-up-like-a-tunnel-for-so/69 " ]
2014-01-09 15:56:03 +11:00
# ["user", "/users/admin1/activity"],
]
2014-02-16 15:11:25 +11:00
tests = tests . map { | k , url | [ " #{ k } _admin " , " #{ url } #{ append } " ] } + tests
# NOTE: we run the most expensive page first in the bench
2014-01-09 15:56:03 +11:00
def best_of ( a , b )
return a unless b
return b unless a
a [ 50 ] < b [ 50 ] ? a : b
end
results = { }
@best_of . times do
tests . each do | name , url |
results [ name ] = best_of ( bench ( url ) , results [ name ] )
end
end
2013-09-10 16:22:58 +10:00
2013-09-10 16:03:11 +10:00
2013-08-15 17:48:11 +10:00
puts " Your Results: (note for timings- percentile is first, duration is second in millisecs) "
2013-08-15 15:19:23 +10:00
2013-08-29 21:34:32 +10:00
facts = Facter . to_hash
facts . delete_if { | k , v |
! [ " operatingsystem " , " architecture " , " kernelversion " ,
" memorysize " , " physicalprocessorcount " , " processor0 " ,
" virtual " ] . include? ( k )
}
2013-09-10 16:03:11 +10:00
run ( " RAILS_ENV=profile bundle exec rake assets:clean " )
2014-02-16 16:44:51 +11:00
def get_mem ( pid )
YAML . load ` ruby script/memstats.rb #{ pid } --yaml `
end
mem = get_mem ( pid )
2014-01-03 11:51:12 +11:00
2014-01-10 16:11:10 +11:00
results = results . merge ( {
2013-08-15 17:48:11 +10:00
" timings " = > @timings ,
2014-01-03 11:51:12 +11:00
" ruby-version " = > " #{ RUBY_VERSION } -p #{ RUBY_PATCHLEVEL } " ,
2014-02-16 16:44:51 +11:00
" rss_kb " = > mem [ " rss_kb " ] ,
" pss_kb " = > mem [ " pss_kb " ]
2014-01-09 15:56:03 +11:00
} ) . merge ( facts )
2013-12-11 10:32:23 +11:00
2014-02-16 16:44:51 +11:00
if @unicorn
child_pids = ` ps --ppid #{ pid } | awk '{ print $1; }' | grep -v PID ` . split ( " \n " )
child_pids . each do | child |
mem = get_mem ( child )
results [ " rss_kb_ #{ child } " ] = mem [ " rss_kb " ]
results [ " pss_kb_ #{ child } " ] = mem [ " pss_kb " ]
end
end
2014-01-09 15:56:03 +11:00
puts results . to_yaml
2013-12-11 10:32:23 +11:00
2014-02-14 15:43:08 +11:00
if @mem_stats
puts
puts open ( " http://127.0.0.1: #{ @port } /admin/memory_stats #{ append } " ) . read
end
2013-12-11 10:32:23 +11:00
if @result_file
File . open ( @result_file , " wb " ) do | f |
f . write ( results )
end
end
2013-08-15 15:19:23 +10:00
2013-08-29 21:23:00 +10:00
2013-09-10 16:03:11 +10:00
# TODO include Facter.to_hash ... for all facts
2013-08-15 15:19:23 +10:00
ensure
Process . kill " KILL " , pid
end