require "socket" require "csv" require "yaml" require "optparse" @include_env = false @result_file = nil 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 end opts.parse! def run(command) system(command, out: $stdout, err: :out) end begin require 'facter' rescue LoadError run "gem install facter" puts "just installed the facter gem, rerunning script" exec("ruby " + [ File.absolute_path(__FILE__), __FILE__ ], *ARGV) exit end @timings = {} def measure(name) start = Time.now yield @timings[name] = ((Time.now - start) * 1000).to_i end 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 puts "Running bundle" if !run("bundle") puts "Quitting, some of the gems did not install" prereqs exit end puts "Ensuring config is setup" unless %x{which ab > /dev/null 2>&1} 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" 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 puts "Running with default environment" ENV.delete "RUBY_GC_MALLOC_LIMIT" ENV.delete "RUBY_HEAP_SLOTS_GROWTH_FACTOR" ENV.delete "RUBY_HEAP_MIN_SLOTS" ENV.delete "RUBY_FREE_MIN" end def port_available? port server = TCPServer.open("0.0.0.0", port) server.close true rescue Errno::EADDRINUSE false end @port = 60079 while !port_available? @port @port += 1 end puts "Ensuring profiling DB exists and is migrated" puts `bundle exec rake db:create` `bundle exec rake db:migrate` puts "Timing loading Rails" measure("load_rails") do `bundle exec rake middleware` end puts "Populating Profile DB" run("bundle exec ruby script/profile_db_generator.rb") puts "Getting api key" api_key = `bundle exec rake api_key:get`.split("\n")[-1] def bench(path) puts "Running apache bench warmup" `ab -n 100 "http://127.0.0.1:#{@port}#{path}"` puts "Benchmarking #{path}" `ab -n 100 -e tmp/ab.csv "http://127.0.0.1:#{@port}#{path}"` 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 percentiles end begin # critical cause cache may be incompatible puts "precompiling assets" run("bundle exec rake assets:precompile") pid = spawn("bundle exec thin start -p #{@port}") while port_available? @port sleep 1 end puts "Starting benchmark..." # asset precompilation is a dog, wget to force it run "wget http://127.0.0.1:#{@port}/ -o tmp/test.html" home_page = bench("/") topic_page = bench("/t/oh-how-i-wish-i-could-shut-up-like-a-tunnel-for-so/69") append = "?api_key=#{api_key}&api_username=admin1" home_page_admin = bench("/#{append}") topic_page_admin = bench("/t/oh-how-i-wish-i-could-shut-up-like-a-tunnel-for-so/69#{append}") puts "Your Results: (note for timings- percentile is first, duration is second in millisecs)" facts = Facter.to_hash facts.delete_if{|k,v| !["operatingsystem","architecture","kernelversion", "memorysize", "physicalprocessorcount", "processor0", "virtual"].include?(k) } run("RAILS_ENV=profile bundle exec rake assets:clean") results = { "home_page" => home_page, "topic_page" => topic_page, "home_page_admin" => home_page_admin, "topic_page_admin" => topic_page_admin, "timings" => @timings, "ruby-version" => "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}" }.merge(facts).to_yaml puts results if @result_file File.open(@result_file,"wb") do |f| f.write(results) end end # TODO include Facter.to_hash ... for all facts ensure Process.kill "KILL", pid end