├── .gitignore ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── README.md ├── apps ├── cuba.ru ├── flame.ru ├── grape.ru ├── hanami-router.ru ├── hobbit.ru ├── hobby.ru ├── newark.ru ├── plezi.ru ├── rack-app.ru ├── rack-response.ru ├── rack.ru ├── rackstep.ru ├── rails-api.ru ├── rails-metal.ru ├── rambutan.ru ├── roda.ru ├── sinatra.ru ├── syro.ru └── watts.ru ├── bench ├── summary-memory ├── summary-speed └── support │ ├── allocation_benchmarker.rb │ ├── allocation_summary_table.rb │ ├── speed_benchmarker.rb │ ├── speed_summary_table.rb │ └── table_helpers.rb └── results └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | /.bundle 3 | /log 4 | /results/* 5 | /vendor/bundle 6 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.4.1 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby "~> 2.4" 4 | 5 | # webservers 6 | gem "puma" 7 | 8 | # frameworks 9 | gem "cuba" 10 | gem "flame" 11 | gem "grape" 12 | gem "hanami-router" 13 | gem "hobbit" 14 | gem "hobby" 15 | gem "newark" 16 | gem "plezi" 17 | gem "rack" 18 | gem "rack-app" 19 | gem "rackstep" 20 | gem "rambutan" 21 | gem "roda" 22 | gem "sinatra" 23 | gem "syro" 24 | gem "watts" 25 | 26 | gem "actionpack" 27 | gem "railties" 28 | 29 | # debugging 30 | gem "allocation_stats" 31 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionpack (5.1.3) 5 | actionview (= 5.1.3) 6 | activesupport (= 5.1.3) 7 | rack (~> 2.0) 8 | rack-test (~> 0.6.3) 9 | rails-dom-testing (~> 2.0) 10 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 11 | actionview (5.1.3) 12 | activesupport (= 5.1.3) 13 | builder (~> 3.1) 14 | erubi (~> 1.4) 15 | rails-dom-testing (~> 2.0) 16 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 17 | activesupport (5.1.3) 18 | concurrent-ruby (~> 1.0, >= 1.0.2) 19 | i18n (~> 0.7) 20 | minitest (~> 5.1) 21 | tzinfo (~> 1.1) 22 | allocation_stats (0.1.5) 23 | axiom-types (0.1.1) 24 | descendants_tracker (~> 0.0.4) 25 | ice_nine (~> 0.11.0) 26 | thread_safe (~> 0.3, >= 0.3.1) 27 | builder (3.2.4) 28 | coercible (1.0.0) 29 | descendants_tracker (~> 0.0.1) 30 | concurrent-ruby (1.1.6) 31 | crass (1.0.6) 32 | cuba (3.8.1) 33 | rack (>= 1.6.0) 34 | descendants_tracker (0.0.4) 35 | thread_safe (~> 0.3, >= 0.3.1) 36 | equalizer (0.0.11) 37 | erubi (1.6.1) 38 | flame (4.18.1) 39 | gorilla-patch (~> 2, >= 2.0.0) 40 | rack (~> 2) 41 | thor (~> 0) 42 | tilt (>= 2.0, < 3) 43 | gorilla-patch (2.4.0) 44 | grape (1.1.0) 45 | activesupport 46 | builder 47 | mustermann-grape (~> 1.0.0) 48 | rack (>= 1.3.0) 49 | rack-accept 50 | virtus (>= 1.0.0) 51 | hanami-router (1.0.1) 52 | hanami-utils (~> 1.0) 53 | http_router (= 0.11.2) 54 | rack (~> 2.0) 55 | hanami-utils (1.0.2) 56 | transproc (~> 1.0) 57 | hobbit (0.6.1) 58 | rack 59 | hobby (0.0.12) 60 | rack 61 | http_router (0.11.2) 62 | rack (>= 1.0.0) 63 | url_mount (~> 0.2.1) 64 | i18n (0.9.5) 65 | concurrent-ruby (~> 1.0) 66 | ice_nine (0.11.2) 67 | iodine (0.7.38) 68 | loofah (2.4.0) 69 | crass (~> 1.0.2) 70 | nokogiri (>= 1.5.9) 71 | method_source (0.8.2) 72 | mini_portile2 (2.4.0) 73 | minitest (5.14.0) 74 | mustermann (1.1.1) 75 | ruby2_keywords (~> 0.0.1) 76 | mustermann-grape (1.0.1) 77 | mustermann (>= 1.0.0) 78 | newark (0.0.8) 79 | activesupport 80 | rack (>= 1.5.2) 81 | nokogiri (1.10.9) 82 | mini_portile2 (~> 2.4.0) 83 | plezi (0.15.0) 84 | bundler (~> 1.14) 85 | iodine (~> 0.4) 86 | rack (>= 2.0.0) 87 | puma (3.12.6) 88 | rack (2.0.8) 89 | rack-accept (0.4.5) 90 | rack (>= 0.4) 91 | rack-app (7.3.2) 92 | rack 93 | rack-protection (2.0.2) 94 | rack 95 | rack-test (0.6.3) 96 | rack (>= 1.0) 97 | rackstep (0.0.9) 98 | rails-dom-testing (2.0.3) 99 | activesupport (>= 4.2.0) 100 | nokogiri (>= 1.6) 101 | rails-html-sanitizer (1.3.0) 102 | loofah (~> 2.3) 103 | railties (5.1.3) 104 | actionpack (= 5.1.3) 105 | activesupport (= 5.1.3) 106 | method_source 107 | rake (>= 0.8.7) 108 | thor (>= 0.18.1, < 2.0) 109 | rake (13.0.1) 110 | rambutan (0.0.1) 111 | http_router 112 | roda (2.29.0) 113 | rack 114 | ruby2_keywords (0.0.2) 115 | seg (1.2.0) 116 | sinatra (2.0.2) 117 | mustermann (~> 1.0) 118 | rack (~> 2.0) 119 | rack-protection (= 2.0.2) 120 | tilt (~> 2.0) 121 | syro (3.0.1) 122 | rack (>= 1.6.0) 123 | seg 124 | thor (0.20.0) 125 | thread_safe (0.3.6) 126 | tilt (2.0.10) 127 | transproc (1.0.2) 128 | tzinfo (1.2.6) 129 | thread_safe (~> 0.1) 130 | url_mount (0.2.1) 131 | rack 132 | virtus (1.0.5) 133 | axiom-types (~> 0.1) 134 | coercible (~> 1.0) 135 | descendants_tracker (~> 0.0, >= 0.0.3) 136 | equalizer (~> 0.0, >= 0.0.9) 137 | watts (1.0.5) 138 | rack 139 | 140 | PLATFORMS 141 | ruby 142 | 143 | DEPENDENCIES 144 | actionpack 145 | allocation_stats 146 | cuba 147 | flame 148 | grape 149 | hanami-router 150 | hobbit 151 | hobby 152 | newark 153 | plezi 154 | puma 155 | rack 156 | rack-app 157 | rackstep 158 | railties 159 | rambutan 160 | roda 161 | sinatra 162 | syro 163 | watts 164 | 165 | RUBY VERSION 166 | ruby 2.4.0p0 167 | 168 | BUNDLED WITH 169 | 1.15.4 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bench some web microframeworks (Ruby) 2 | 3 | Because we don't have enough drama, let's bench some *hello world* apps! 4 | 5 | ## Why? 6 | 7 | As part of some research, collected some benchmark around Ruby web 8 | microframeworks. 9 | 10 | The idea is bench the performance from [Rack](https://github.com/rack/rack), 11 | throught the stack of the framework to the minimum representation of an 12 | application (*Hello World* apps). 13 | 14 | It is not the goal to bechmark hits to the database or external services, 15 | nor to do a silly *fibonacci-as-a-service*, ok? 16 | 17 | ## What? 18 | 19 | Rack 2 compatible frameworks 20 | 21 | - [Cuba](https://github.com/soveran/cuba) - [cuba.ru](apps/cuba.ru) 22 | - [Flame](https://github.com/AlexWayfer/flame) - [flame.ru](apps/flame.ru) 23 | - [Grape](https://github.com/ruby-grape/grape) - [grape.ru](apps/grape.ru) 24 | - [Hanami (Router)](https://github.com/hanami/router) - [hanami-router.ru](apps/hanami-router.ru) 25 | - [Hobbit](https://github.com/patriciomacadden/hobbit) - [hobbit.ru](apps/hobbit.ru) 26 | - [Hobby](https://github.com/ch1c0t/hobby) - [hobby.ru](apps/hobby.ru) 27 | - [Newark](https://github.com/mje113/newark) - [newark.ru](apps/newark.ru) 28 | - [Plezi](https://github.com/boazsegev/plezi) - [plezi.ru](apps/plezi.ru) 29 | - [Rack](https://github.com/rack/rack) - [rack.ru](apps/rack.ru) + [rack-response.ru](apps/rack-response.ru) 30 | - [Rack-App](https://github.com/rack-app/rack-app) - [rack-app.ru](apps/rack-app.ru) 31 | - [RackStep](https://github.com/mfdavid/rackstep) - [rackstep.ru](apps/rackstep.ru) 32 | - [Rails (API + Metal)](https://github.com/rails/rails) - [rails-api.ru](apps/rails-api.ru) + [rails-metal.ru](apps/rails-metal.ru) 33 | - [Rambutan](https://github.com/NewRosies/rambutan) - [rambutan.ru](apps/rambutan.ru) 34 | - [Roda](https://github.com/jeremyevans/roda) - [roda.ru](apps/roda.ru) 35 | - [Sinatra](https://github.com/sinatra/sinatra) - [sinatra.ru](apps/sinatra.ru) 36 | - [Syro](https://github.com/soveran/syro) - [syro](apps/syro.ru) 37 | - [Watts](https://github.com/pete/watts) - [watts](apps/watts.ru) 38 | 39 | Please note that while Rails has been added to the list, it is just a 40 | minimalistic representation with ActionPack gem (using API and Metal, no full middleware stack, etc). You 41 | shouldn't take the performance numbers mentioned here about Rails (or any 42 | other) as scientific and decision-taking references. 43 | 44 | ## How? 45 | 46 | Used [wrk](https://github.com/wg/wrk) to benchmark, locally, a burst of 47 | requests (in 2 threads) over 10 seconds. The command line used was: 48 | 49 | ```console 50 | $ wrk -t 2 http://localhost:9292/ 51 | ``` 52 | 53 | All the frameworks using [Puma](https://github.com/puma/puma) on 54 | Ruby 2.4, in production mode with 16 threads: 55 | 56 | ```console 57 | $ puma -e production -t 16:16 apps/ 58 | ``` 59 | 60 | ## Run benchmark for all frameworks 61 | 62 | ```console 63 | # use `bundle exec`, if needed 64 | $ bench/summary-memory 65 | $ bench/summary-speed 66 | ``` 67 | 68 | ### Have some numbers around? 69 | 70 | Yup, I do: 71 | 72 | #### Requests/sec 73 | 74 | ``` 75 | Framework Requests/sec % from best 76 | ---------------------------------------------- 77 | rack 15839.64 100.00% 78 | watts 13585.65 85.77% 79 | syro 13306.01 84.00% 80 | roda 11776.27 74.35% 81 | cuba 11501.96 72.62% 82 | rack-response 11480.11 72.48% 83 | hobbit 11140.22 70.33% 84 | hobby 10606.80 66.96% 85 | hanami-router 10247.61 64.70% 86 | newark 8896.35 56.17% 87 | rambutan 8466.83 53.45% 88 | plezi 8240.07 52.02% 89 | rackstep 8187.32 51.69% 90 | rack-app 7473.52 47.18% 91 | rails-metal 6598.65 41.66% 92 | flame 5454.46 34.44% 93 | sinatra 3977.30 25.11% 94 | grape 2937.03 18.54% 95 | rails-api 1211.33 7.65% 96 | ``` 97 | 98 | 99 | #### Memory Allocation/Request 100 | 101 | ``` 102 | Framework Allocs/Req Memsize/Req 103 | -------------------------------------- 104 | rack 41 2920 105 | roda 45 3608 106 | syro 45 3608 107 | cuba 47 3688 108 | watts 48 3200 109 | hobbit 49 3928 110 | hobby 53 4240 111 | rack-response 56 4136 112 | rails-metal 60 5728 113 | plezi 66 5040 114 | hanami-router 68 4880 115 | newark 72 5928 116 | rackstep 81 4944 117 | rack-app 83 7184 118 | rambutan 83 5944 119 | flame 125 8536 120 | sinatra 191 12512 121 | grape 272 20800 122 | rails-api 385 28017 123 | ``` 124 | 125 | 126 | #### Hardware 127 | 128 | These numbers were collected on: 129 | 130 | - Ubuntu 16.10 64-bit (kernel: 4.8.0-56-generic) 131 | - Dell XPS 13 (9343, QHD, Developer Edition) 132 | - Intel® Core™ i7-5500U CPU @ 2.40GHz × 4 133 | - 8GB RAM 134 | - 256GB SSD 135 | - ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux] 136 | -------------------------------------------------------------------------------- /apps/cuba.ru: -------------------------------------------------------------------------------- 1 | require "cuba" 2 | 3 | HelloWorld = Cuba.new do 4 | on get, root do 5 | res.write "Hello World!" 6 | end 7 | end 8 | 9 | APP = HelloWorld 10 | run APP 11 | -------------------------------------------------------------------------------- /apps/flame.ru: -------------------------------------------------------------------------------- 1 | require "flame" 2 | 3 | class HelloController < Flame::Controller 4 | def world 5 | "Hello World!" 6 | end 7 | end 8 | 9 | class HelloWorld < Flame::Application 10 | mount HelloController do 11 | get "/", :world 12 | end 13 | end 14 | 15 | APP = HelloWorld.new 16 | run APP 17 | -------------------------------------------------------------------------------- /apps/grape.ru: -------------------------------------------------------------------------------- 1 | require "grape" 2 | 3 | class APP < Grape::API 4 | get "/" do 5 | "Hello World!" 6 | end 7 | end 8 | 9 | run APP 10 | -------------------------------------------------------------------------------- /apps/hanami-router.ru: -------------------------------------------------------------------------------- 1 | require "hanami/router" 2 | 3 | APP = Hanami::Router.new do 4 | get "/", to: ->(env) { [200, {"Content-Type" => "text/html"}, ["Hello World!"]] } 5 | end 6 | 7 | run APP 8 | -------------------------------------------------------------------------------- /apps/hobbit.ru: -------------------------------------------------------------------------------- 1 | require "hobbit" 2 | 3 | class HelloWorld < Hobbit::Base 4 | get "/" do 5 | "Hello World!" 6 | end 7 | end 8 | 9 | APP = HelloWorld.new 10 | run APP 11 | -------------------------------------------------------------------------------- /apps/hobby.ru: -------------------------------------------------------------------------------- 1 | require 'hobby' 2 | 3 | class App 4 | include Hobby 5 | get { "Hello World!" } 6 | end 7 | 8 | APP = App.new 9 | run APP 10 | -------------------------------------------------------------------------------- /apps/newark.ru: -------------------------------------------------------------------------------- 1 | require "newark" 2 | 3 | class HelloWorld 4 | include Newark 5 | 6 | get "/" do 7 | "Hello World!" 8 | end 9 | end 10 | 11 | APP = HelloWorld.new 12 | run APP 13 | -------------------------------------------------------------------------------- /apps/plezi.ru: -------------------------------------------------------------------------------- 1 | require 'plezi' 2 | 3 | class HelloWorld 4 | def index 5 | "Hello World!".freeze 6 | end 7 | end 8 | 9 | Plezi.route "/", HelloWorld 10 | APP = Plezi.app 11 | run APP 12 | -------------------------------------------------------------------------------- /apps/rack-app.ru: -------------------------------------------------------------------------------- 1 | require "rack/app" 2 | 3 | class HelloWorld < Rack::App 4 | desc "Hello World" 5 | get "/" do 6 | "Hello World!" 7 | end 8 | end 9 | 10 | APP = HelloWorld 11 | run APP 12 | -------------------------------------------------------------------------------- /apps/rack-response.ru: -------------------------------------------------------------------------------- 1 | require "rack" 2 | 3 | class HelloWorld 4 | def call(env) 5 | if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/" 6 | Rack::Response.new("Hello World!", 200, { "Content-Type" => "text/html" }).finish 7 | else 8 | Rack::Response.new("", 404, { "Content-Type" => "text/html" }).finish 9 | end 10 | end 11 | end 12 | 13 | APP = HelloWorld.new 14 | run APP 15 | -------------------------------------------------------------------------------- /apps/rack.ru: -------------------------------------------------------------------------------- 1 | class HelloWorld 2 | def call(env) 3 | if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/" 4 | [ 5 | 200, 6 | {"Content-Type" => "text/html"}, 7 | ["Hello World!"] 8 | ] 9 | else 10 | [ 11 | 404, 12 | {"Content-Type" => "text/html"}, 13 | [""] 14 | ] 15 | end 16 | end 17 | end 18 | 19 | APP = HelloWorld.new 20 | run APP 21 | -------------------------------------------------------------------------------- /apps/rackstep.ru: -------------------------------------------------------------------------------- 1 | require "rackstep" 2 | 3 | class HelloController < RackStep::Controller 4 | def process_request 5 | response.body = ["Hello World!"] 6 | end 7 | end 8 | 9 | class HelloWorld < RackStep::App 10 | add_route("GET", "", HelloController) 11 | end 12 | 13 | APP = HelloWorld 14 | 15 | run APP 16 | -------------------------------------------------------------------------------- /apps/rails-api.ru: -------------------------------------------------------------------------------- 1 | require "securerandom" 2 | require "action_controller/railtie" 3 | 4 | class HelloWorld < Rails::Application 5 | routes.append do 6 | get "/", to: "hello#world" 7 | end 8 | 9 | config.api_only = true 10 | config.cache_classes = true 11 | config.eager_load = true 12 | config.secret_key_base = SecureRandom.hex(64) 13 | config.action_controller.perform_caching = true 14 | end 15 | 16 | class HelloController < ActionController::API 17 | def world 18 | self.response_body = "Hello World!" 19 | end 20 | end 21 | 22 | APP = HelloWorld.initialize! 23 | run APP 24 | -------------------------------------------------------------------------------- /apps/rails-metal.ru: -------------------------------------------------------------------------------- 1 | require "securerandom" 2 | require "action_controller/railtie" 3 | 4 | class HelloWorld < Rails::Application 5 | routes.append do 6 | get "/", to: "hello#world" 7 | end 8 | 9 | config.api_only = true 10 | config.cache_classes = true 11 | config.eager_load = true 12 | config.secret_key_base = SecureRandom.hex(64) 13 | 14 | [Rack::Sendfile, ActionDispatch::Static, 15 | ActionDispatch::Executor, ActiveSupport::Cache::Strategy::LocalCache::Middleware, 16 | ActionDispatch::RequestId, ActionDispatch::RemoteIp, 17 | Rails::Rack::Logger, ActionDispatch::ShowExceptions, 18 | ActionDispatch::DebugExceptions, ActionDispatch::Callbacks, 19 | Rack::ETag, Rack::Head 20 | ].each do |middleware| 21 | config.middleware.delete(middleware) 22 | end 23 | end 24 | 25 | class HelloController < ActionController::Metal 26 | def world 27 | self.response_body = "Hello World!" 28 | end 29 | end 30 | 31 | APP = HelloWorld.initialize!(:metal) 32 | run APP 33 | -------------------------------------------------------------------------------- /apps/rambutan.ru: -------------------------------------------------------------------------------- 1 | require 'rambutan' 2 | 3 | class HelloController < Rambutan::Base 4 | def world 5 | "Hello World!" 6 | end 7 | end 8 | 9 | app = Rambutan::RoutesSet.new do 10 | get "/" => "hello#world" 11 | end 12 | 13 | APP = app.router 14 | run APP 15 | -------------------------------------------------------------------------------- /apps/roda.ru: -------------------------------------------------------------------------------- 1 | require "roda" 2 | 3 | class HelloWorld < Roda 4 | route do |r| 5 | r.root do 6 | "Hello World!" 7 | end 8 | end 9 | end 10 | 11 | APP = HelloWorld.app 12 | run APP 13 | -------------------------------------------------------------------------------- /apps/sinatra.ru: -------------------------------------------------------------------------------- 1 | require "sinatra/base" 2 | 3 | class HelloWorld < Sinatra::Base 4 | get "/" do 5 | "Hello World!" 6 | end 7 | end 8 | 9 | APP = HelloWorld 10 | run APP 11 | -------------------------------------------------------------------------------- /apps/syro.ru: -------------------------------------------------------------------------------- 1 | require "syro" 2 | 3 | APP = Syro.new { 4 | get { 5 | res.write "Hello World!" 6 | } 7 | } 8 | 9 | run APP 10 | -------------------------------------------------------------------------------- /apps/watts.ru: -------------------------------------------------------------------------------- 1 | require "watts" 2 | 3 | class HelloWorld < Watts::App 4 | class HelloController < Watts::Resource 5 | get { "Hello, World!" } 6 | end 7 | resource("/", HelloController) 8 | end 9 | 10 | APP = HelloWorld.new 11 | run APP 12 | -------------------------------------------------------------------------------- /bench/summary-memory: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "support/allocation_benchmarker" 4 | require_relative "support/allocation_summary_table" 5 | 6 | results = Dir['apps/*.ru'].sort.map do |file| 7 | tracker = AllocationsBenchmarker.new(file) 8 | tracker.human_output 9 | tracker.machine_result 10 | end 11 | 12 | summary_table = AllocationSummaryTable.new(results) 13 | puts summary_table.render 14 | 15 | File.open('results/memory.md', 'w') do |f| 16 | f.puts summary_table.render_markdown 17 | end 18 | summary_table.replace_readme('mem_table') 19 | -------------------------------------------------------------------------------- /bench/summary-speed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "support/speed_benchmarker" 4 | require_relative "support/speed_summary_table" 5 | 6 | def redirect_stream(stream, path) 7 | old_stream = stream.dup 8 | stream.reopen(path) 9 | stream.sync = true 10 | yield 11 | ensure 12 | stream.reopen(old_stream) 13 | end 14 | 15 | apps = Dir["apps/*.ru"].sort 16 | apps = apps.grep(%r(#{ARGV[0]})) if ARGV[0] 17 | warn "please wait... the output will come, when finished" 18 | redirect_stream(STDOUT, 'test.log') do 19 | apps.each do |config_file| 20 | warn config_file 21 | SpeedBenchmarker.new(config_file).run 22 | end 23 | end 24 | 25 | content = %x(egrep '\\*|Request' test.log) 26 | 27 | summary_table = SpeedSummaryTable.new(content) 28 | File.open('results/speed.md', 'w') do |f| 29 | f.puts summary_table.render_markdown 30 | end 31 | puts summary_table.render 32 | summary_table.replace_readme('speed_table') 33 | -------------------------------------------------------------------------------- /bench/support/allocation_benchmarker.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.setup 3 | # dummy run method 4 | def run(*args); ; end 5 | 6 | require 'rack/mock' 7 | require 'allocation_stats' 8 | 9 | class AllocationsBenchmarker 10 | attr_accessor :config 11 | 12 | def initialize(config) 13 | @config = config 14 | 15 | load_script 16 | end 17 | 18 | def load_script 19 | Object.send(:remove_const, :APP) if Object.constants.include?(:APP) 20 | Object.send(:remove_const, :HelloWorld) if Object.constants.include?(:HelloWorld) 21 | Object.send(:remove_const, :HelloController) if Object.constants.include?(:HelloController) 22 | 23 | load "./#{config}" 24 | end 25 | 26 | def human_output 27 | puts frameworkname 28 | puts "total allocations: #{total_allocations}" 29 | puts "total memsize: #{total_memsize}" 30 | detailed_allocations 31 | puts 32 | end 33 | 34 | def total_allocations 35 | stats.allocations.all.size 36 | end 37 | 38 | def total_memsize 39 | stats.allocations.bytes.to_a.inject(&:+) 40 | end 41 | 42 | def machine_result 43 | { 44 | :frameworkname => frameworkname, 45 | :total_allocations => total_allocations, 46 | :total_memsize => total_memsize 47 | } 48 | end 49 | 50 | 51 | def detailed_allocations 52 | puts 'allocations by source file and class:' 53 | puts stats.allocations(alias_paths: true). 54 | group_by(:sourcefile, :class_plus). 55 | sort_by_count. 56 | to_text 57 | end 58 | 59 | def request 60 | Rack::MockRequest.env_for('/') 61 | end 62 | 63 | def frameworkname 64 | File.basename(config).gsub(".ru", '').upcase 65 | end 66 | 67 | def stats 68 | @stats ||= AllocationStats.new(burn: 5).trace do 69 | APP.call(request) 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /bench/support/allocation_summary_table.rb: -------------------------------------------------------------------------------- 1 | require_relative 'table_helpers' 2 | 3 | class AllocationSummaryTable 4 | include TableHelpers 5 | attr_accessor :results 6 | 7 | def initialize(results) 8 | @results = results 9 | end 10 | 11 | def tab_props 12 | [15,10,11] 13 | end 14 | 15 | def summary_table 16 | summary = [] 17 | summary << table_row("Framework", "Allocs/Req", 'Memsize/Req') 18 | summary << table_separator 19 | summary << table 20 | end 21 | 22 | def render 23 | summary_table.join("\n") 24 | end 25 | 26 | def sorted_results 27 | results.sort_by{|x| x[:total_allocations]} 28 | end 29 | 30 | def table 31 | sorted_results.map do |res| 32 | name = res[:frameworkname].downcase 33 | allocations = res[:total_allocations] 34 | memory = res[:total_memsize] 35 | table_row(name, allocations, memory) 36 | end.join("\n") 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /bench/support/speed_benchmarker.rb: -------------------------------------------------------------------------------- 1 | class SpeedBenchmarker 2 | attr_reader :config 3 | 4 | def initialize(config) 5 | @config = config 6 | end 7 | 8 | def run 9 | announce 10 | server_pid = start_puma 11 | start_wrk 12 | stop_puma(server_pid) 13 | end 14 | 15 | def announce 16 | puts banner(frameworkname) 17 | end 18 | 19 | def banner(str) 20 | "*" * 30 + " #{str.center(18)} " + "*" * 30 21 | end 22 | 23 | def frameworkname 24 | File.basename(config).gsub(".ru", '').upcase 25 | end 26 | 27 | def start_puma 28 | pid = Process.spawn(start_command, err: :out, out: IO::NULL) 29 | 30 | # wait for process to load 31 | sleep 3 32 | 33 | pid 34 | end 35 | 36 | def stop_puma(server_pid) 37 | Process.kill("TERM", server_pid) 38 | 39 | # wait before stop 40 | Process.wait(server_pid) 41 | end 42 | 43 | def start_wrk 44 | execute(wrk_command) 45 | end 46 | 47 | def start_command 48 | "puma --environment production --threads 16:16 #{config}" 49 | end 50 | 51 | def wrk_command 52 | "wrk --threads 2 --duration 10 http://localhost:9292/" 53 | end 54 | 55 | require 'pty' 56 | def execute(cmd) 57 | puts cmd 58 | begin 59 | PTY.spawn( cmd ) do |stdin, stdout, pid| 60 | begin 61 | stdin.each { |line| print line } 62 | rescue Errno::EIO 63 | end 64 | end 65 | rescue PTY::ChildExited 66 | puts "The child process exited!" 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /bench/support/speed_summary_table.rb: -------------------------------------------------------------------------------- 1 | require_relative 'table_helpers' 2 | 3 | class SpeedSummaryTable 4 | include TableHelpers 5 | attr_accessor :content # content: output from wrk, stripped a bit 6 | 7 | def initialize(content) 8 | @content = content 9 | end 10 | 11 | def summary_table 12 | summary = [] 13 | summary << table_row("Framework", "Requests/sec", '% from best') 14 | summary << table_separator 15 | summary << table 16 | end 17 | 18 | def render 19 | summary_table.join("\n") 20 | end 21 | 22 | def lines 23 | content.split("\n") 24 | end 25 | 26 | def results 27 | lines.each_slice(2).map do |pair| 28 | name = pair[0].gsub("*", '').strip.downcase 29 | number = pair[1].match(/(\d|\.)+/)[0] 30 | [name, number] 31 | end 32 | end 33 | 34 | def sorted_results 35 | @sorted_results ||= results.sort_by{|x| - x[1].to_i} 36 | end 37 | 38 | def max_rps 39 | sorted_results.first[1].to_f 40 | end 41 | 42 | def table 43 | sorted_results.map do |pair| 44 | name = pair[0] 45 | rps = pair[1] 46 | num = (pair[1].to_f/max_rps) 47 | round = "%2.02f%%" % (num * 100) 48 | table_row(name, rps, round) 49 | end.join("\n") 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /bench/support/table_helpers.rb: -------------------------------------------------------------------------------- 1 | module TableHelpers 2 | def tab_props 3 | [20,12,12] 4 | end 5 | 6 | def table_row(a,b,c) 7 | "#{a.ljust(tab_props[0])} #{b.to_s.rjust(tab_props[1])} #{c.to_s.rjust(tab_props[2])}" 8 | end 9 | 10 | def table_separator 11 | "-"* (tab_props.reduce(:+) + 2) 12 | end 13 | 14 | def render_markdown 15 | [%Q(```), render, %Q(```)].join("\n") 16 | end 17 | 18 | def replace_readme(comment_signature) 19 | regex_start = %Q() 20 | regex_end = %Q() 21 | regex_string = %Q(\<\!-- #{comment_signature} --\>(.*)\<\!-- #{comment_signature}_end --\>) 22 | regex = Regexp::new(regex_string, Regexp::MULTILINE) 23 | readme = File.read('README.md') 24 | readme_table = [regex_start, render_markdown, regex_end].join("\n") 25 | new_readme = readme.gsub(regex, readme_table) 26 | File.open('README.md', 'w') do |f| 27 | f.puts new_readme 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /results/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luislavena/bench-micro/e335bb714551c5ade4b1935e91993b572e7f94bf/results/.keep --------------------------------------------------------------------------------