From a3c7ee09b6dacfd2f67cfdacc9f597685548fa4c Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 19 Feb 2018 10:12:51 +1100 Subject: [PATCH] FIX: ruby bench not working properly - Remove thin which is no longer supported - Bypass admin api rate limiting in profile environment - Admin password was too short - Run by default in concurrency 1 mode - A skip bundle assets flag to speed up local testing --- lib/auth/default_current_user_provider.rb | 23 ++++---- script/bench.rb | 68 +++++++++++++++++++---- script/profile_db_generator.rb | 2 +- 3 files changed, 72 insertions(+), 21 deletions(-) diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb index c46ee178b33..cacfe81962d 100644 --- a/lib/auth/default_current_user_provider.rb +++ b/lib/auth/default_current_user_provider.rb @@ -5,14 +5,14 @@ require_dependency "rate_limiter" class Auth::DefaultCurrentUserProvider - CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER".freeze - API_KEY ||= "api_key".freeze - USER_API_KEY ||= "HTTP_USER_API_KEY".freeze - USER_API_CLIENT_ID ||= "HTTP_USER_API_CLIENT_ID".freeze - API_KEY_ENV ||= "_DISCOURSE_API".freeze - USER_API_KEY_ENV ||= "_DISCOURSE_USER_API".freeze - TOKEN_COOKIE ||= "_t".freeze - PATH_INFO ||= "PATH_INFO".freeze + CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER" + API_KEY ||= "api_key" + USER_API_KEY ||= "HTTP_USER_API_KEY" + USER_API_CLIENT_ID ||= "HTTP_USER_API_CLIENT_ID" + API_KEY_ENV ||= "_DISCOURSE_API" + USER_API_KEY_ENV ||= "_DISCOURSE_USER_API" + TOKEN_COOKIE ||= "_t" + PATH_INFO ||= "PATH_INFO" COOKIE_ATTEMPTS_PER_MIN ||= 10 # do all current user initialization here @@ -86,8 +86,11 @@ class Auth::DefaultCurrentUserProvider raise Discourse::InvalidAccess if current_user.suspended? || !current_user.active @env[API_KEY_ENV] = true - limiter_min = RateLimiter.new(nil, "admin_api_min_#{api_key}", GlobalSetting.max_admin_api_reqs_per_key_per_minute, 60) - limiter_min.performed! + # we do not run this rate limiter while profiling + if Rails.env != "profile" + limiter_min = RateLimiter.new(nil, "admin_api_min_#{api_key}", GlobalSetting.max_admin_api_reqs_per_key_per_minute, 60) + limiter_min.performed! + end end # user api key handling diff --git a/script/bench.rb b/script/bench.rb index ac6fe614fc8..385fd7e5879 100644 --- a/script/bench.rb +++ b/script/bench.rb @@ -11,6 +11,9 @@ require "fileutils" @mem_stats = false @unicorn = false @dump_heap = false +@concurrency = 1 +@skip_asset_bundle = false +@unicorn_workers = 3 opts = OptionParser.new do |o| o.banner = "Usage: ruby bench.rb [options]" @@ -35,9 +38,18 @@ opts = OptionParser.new do |o| o.on("-m", "--memory_stats") do @mem_stats = true end - o.on("-u", "--unicorn", "Use unicorn to serve pages as opposed to thin") do + o.on("-u", "--unicorn", "Use unicorn to serve pages as opposed to puma") do @unicorn = true end + o.on("-c", "--concurrency [NUM]", "Run benchmark with this number of concurrent requests (default: 1)") do |i| + @concurrency = i.to_i + end + o.on("-w", "--unicorn_workers [NUM]", "Run benchmark with this number of unicorn workers (default: 3)") do |i| + @unicorn_workers = i.to_i + end + o.on("-s", "--skip-bundle-assets", "Skip bundling assets") do + @skip_asset_bundle = true + end end opts.parse! @@ -106,19 +118,40 @@ end ENV["RAILS_ENV"] = "profile" -discourse_env_vars = %w(DISCOURSE_DUMP_HEAP RUBY_GC_HEAP_INIT_SLOTS RUBY_GC_HEAP_FREE_SLOTS RUBY_GC_HEAP_GROWTH_FACTOR RUBY_GC_HEAP_GROWTH_MAX_SLOTS RUBY_GC_MALLOC_LIMIT RUBY_GC_OLDMALLOC_LIMIT RUBY_GC_MALLOC_LIMIT_MAX RUBY_GC_OLDMALLOC_LIMIT_MAX RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR) +discourse_env_vars = %w( + DISCOURSE_DUMP_HEAP + RUBY_GC_HEAP_INIT_SLOTS + RUBY_GC_HEAP_FREE_SLOTS + RUBY_GC_HEAP_GROWTH_FACTOR + RUBY_GC_HEAP_GROWTH_MAX_SLOTS + RUBY_GC_MALLOC_LIMIT + RUBY_GC_OLDMALLOC_LIMIT + RUBY_GC_MALLOC_LIMIT_MAX + RUBY_GC_OLDMALLOC_LIMIT_MAX + RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR + RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR + RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR + RUBY_GLOBAL_METHOD_CACHE_SIZE +) if @include_env puts "Running with tuned environment" - discourse_env_vars - %w(RUBY_GC_MALLOC_LIMIT).each do |v| + discourse_env_vars.each do |v| ENV.delete v end + + ENV['RUBY_GLOBAL_METHOD_CACHE_SIZE'] = '131072' + ENV['RUBY_GC_HEAP_GROWTH_MAX_SLOTS'] = '40000' + ENV['RUBY_GC_HEAP_INIT_SLOTS'] = '400000' + ENV['RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR'] = '1.5' + else # clean env puts "Running with the following custom environment" - discourse_env_vars.each do |w| - puts "#{w}: #{ENV[w]}" - end +end + +discourse_env_vars.each do |w| + puts "#{w}: #{ENV[w]}" if ENV[w].to_s.length > 0 end def port_available?(port) @@ -153,8 +186,9 @@ api_key = `bundle exec rake api_key:get`.split("\n")[-1] def bench(path, name) puts "Running apache bench warmup" add = "" - add = "-c 3 " if @unicorn + add = "-c #{@concurrency} " if @concurrency > 1 `ab #{add} -n 20 -l "http://127.0.0.1:#{@port}#{path}"` + puts "Benchmarking #{name} @ #{path}" `ab #{add} -n #{@iterations} -l -e tmp/ab.csv "http://127.0.0.1:#{@port}#{path}"` @@ -168,16 +202,19 @@ end begin # critical cause cache may be incompatible - puts "precompiling assets" - run("bundle exec rake assets:precompile") + unless @skip_asset_bundle + puts "precompiling assets" + run("bundle exec rake assets:precompile") + end pid = if @unicorn ENV['UNICORN_PORT'] = @port.to_s + ENV['UNICORN_WORKERS'] = @unicorn_workers.to_s FileUtils.mkdir_p(File.join('tmp', 'pids')) spawn("bundle exec unicorn -c config/unicorn.conf.rb") else - spawn("bundle exec thin start -p #{@port}") + spawn("bundle exec puma -p #{@port}") end while port_available? @port @@ -223,6 +260,17 @@ begin puts "Your Results: (note for timings- percentile is first, duration is second in millisecs)" + if @unicorn + puts "Unicorn: (workers: #{@unicorn_workers})" + else + # TODO we want to also bench puma clusters + puts "Puma: (single threaded)" + end + puts "Include env: #{@include_env}" + puts "Iterations: #{@iterations}, Best of: #{@best_of}" + puts "Concurrency: #{@concurrency}" + puts + # Prevent using external facts because it breaks when running in the # discourse/discourse_bench docker container. Facter::Util::Config.external_facts_dirs = [] diff --git a/script/profile_db_generator.rb b/script/profile_db_generator.rb index 18cf34b5f68..37f757177d9 100644 --- a/script/profile_db_generator.rb +++ b/script/profile_db_generator.rb @@ -45,7 +45,7 @@ def create_admin(seq) User.new.tap { |admin| admin.email = "admin@localhost#{seq}.fake" admin.username = "admin#{seq}" - admin.password = "password" + admin.password = "password12345abc" admin.save! admin.grant_admin! admin.change_trust_level!(TrustLevel[4])