├── results └── .keep ├── .ruby-version ├── .gitignore ├── apps ├── syro.ru ├── nyny.ru ├── cuba.ru ├── hobbit.ru ├── nancy.ru ├── brooklyn.ru ├── sinatra.ru ├── lotus-router.ru ├── scorched.broken ├── roda.ru ├── ramaze.ru ├── gin.ru ├── rambutan.ru ├── mustermann.ru ├── rack-response.ru ├── rack.ru └── rails.ru ├── Gemfile ├── bench ├── summary-memory ├── summary-speed └── support │ ├── allocation_summary_table.rb │ ├── table_helpers.rb │ ├── speed_summary_table.rb │ ├── speed_benchmarker.rb │ └── allocation_benchmarker.rb ├── Gemfile.lock └── README.md /results/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.3 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | /.bundle 3 | /log 4 | /results/* 5 | /vendor/bundle 6 | -------------------------------------------------------------------------------- /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/nyny.ru: -------------------------------------------------------------------------------- 1 | require "nyny" 2 | 3 | class HelloWorld < NYNY::App 4 | get "/" do 5 | "Hello World!" 6 | end 7 | end 8 | 9 | APP = HelloWorld.new 10 | run APP 11 | -------------------------------------------------------------------------------- /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/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/nancy.ru: -------------------------------------------------------------------------------- 1 | require "nancy" 2 | 3 | class HelloWorld < Nancy::Base 4 | get "/" do 5 | "Hello World!" 6 | end 7 | end 8 | 9 | APP = HelloWorld.new 10 | run APP 11 | -------------------------------------------------------------------------------- /apps/brooklyn.ru: -------------------------------------------------------------------------------- 1 | require "brooklyn" 2 | 3 | class HelloWorld < Brooklyn::App 4 | get "/" do 5 | "Hello World!" 6 | end 7 | end 8 | 9 | APP = HelloWorld.new 10 | run APP 11 | -------------------------------------------------------------------------------- /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/lotus-router.ru: -------------------------------------------------------------------------------- 1 | require "lotus/router" 2 | 3 | APP = Lotus::Router.new do 4 | get "/", to: ->(env) { [200, {'Content-Type'=>'text/html'}, ["Hello World!"]] } 5 | end 6 | 7 | run APP 8 | -------------------------------------------------------------------------------- /apps/scorched.broken: -------------------------------------------------------------------------------- 1 | require "scorched" 2 | 3 | class HelloWorld < Scorched::Controller 4 | get "/" do 5 | "Hello World!" 6 | end 7 | end 8 | 9 | APP = HelloWorld 10 | run APP 11 | -------------------------------------------------------------------------------- /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/ramaze.ru: -------------------------------------------------------------------------------- 1 | require "ramaze" 2 | 3 | class HelloWorld < Ramaze::Controller 4 | map "/" 5 | 6 | def index 7 | "Hello World!" 8 | end 9 | end 10 | 11 | 12 | APP = Ramaze.core 13 | run APP 14 | -------------------------------------------------------------------------------- /apps/gin.ru: -------------------------------------------------------------------------------- 1 | require 'gin' 2 | 3 | class HelloWorldCtrl < Gin::Controller 4 | def index 5 | "Hello World!" 6 | end 7 | end 8 | 9 | class MyApp < Gin::App 10 | mount HelloWorldCtrl, "/" 11 | end 12 | 13 | APP = MyApp.new 14 | run APP 15 | -------------------------------------------------------------------------------- /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/mustermann.ru: -------------------------------------------------------------------------------- 1 | require "mustermann/router/rack" 2 | 3 | APP = Mustermann::Router::Rack.new do 4 | on '/' do |env| 5 | if env['REQUEST_METHOD'] == 'GET' 6 | [200, {'Content-Type' => 'text/html'}, ['Hello World!']] 7 | else 8 | [404, {'Content-Type' => 'text/html'}, []] 9 | end 10 | end 11 | end 12 | 13 | run APP 14 | -------------------------------------------------------------------------------- /apps/rack-response.ru: -------------------------------------------------------------------------------- 1 | class HelloWorld 2 | def call(env) 3 | if env['REQUEST_METHOD'] == 'GET' && env['PATH_INFO'] == '/' 4 | Rack::Response.new('Hello World!', 200, { 'Content-Type' => 'text/html' }).finish 5 | else 6 | Rack::Response.new('', 404, { 'Content-Type' => 'text/html' }).finish 7 | end 8 | end 9 | end 10 | 11 | APP = HelloWorld.new 12 | run APP 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby "2.2.3" 4 | 5 | # webservers 6 | gem "puma", "~> 2.15" 7 | 8 | # frameworks 9 | gem "brooklyn", git: "https://github.com/luislavena/brooklyn.git", branch: "master" 10 | gem "cuba" 11 | gem "hobbit" 12 | gem "lotus-router" 13 | gem "nancy", git: "https://github.com/guilleiguaran/nancy.git", branch: "master" 14 | gem "nyny" 15 | gem "ramaze" 16 | gem "rambutan" 17 | gem "roda" 18 | gem "scorched" 19 | gem "sinatra" 20 | gem "syro" 21 | gem "mustermann-simple" 22 | gem "gin" 23 | 24 | gem "actionpack", "~> 4.2" 25 | gem "railties", "~> 4.2" 26 | gem "tzinfo" 27 | 28 | # debugging 29 | gem "allocation_stats" 30 | -------------------------------------------------------------------------------- /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 | Object.send(:remove_const, :APP) 10 | Object.send(:remove_const, :HelloWorld) if Object.constants.include?(:HelloWorld) 11 | Object.send(:remove_const, :HelloController) if Object.constants.include?(:HelloController) 12 | tracker.machine_result 13 | end 14 | 15 | summary_table = AllocationSummaryTable.new(results) 16 | puts summary_table.render 17 | 18 | File.open('results/memory.md', 'w') do |f| 19 | f.puts summary_table.render_markdown 20 | end 21 | summary_table.replace_readme('mem_table') 22 | -------------------------------------------------------------------------------- /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 | Benchmarker.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_summary_table.rb: -------------------------------------------------------------------------------- 1 | require_relative 'table_helpers' 2 | class AllocationSummaryTable 3 | include TableHelpers 4 | attr_accessor :results 5 | 6 | def initialize(results) 7 | @results = results 8 | end 9 | 10 | def tab_props 11 | [15,10,11] 12 | end 13 | 14 | def summary_table 15 | summary = [] 16 | summary << table_row("Framework", "Allocs/Req", 'Memsize/Req') 17 | summary << table_separator 18 | summary << table 19 | end 20 | 21 | def render 22 | summary_table.join("\n") 23 | end 24 | 25 | def sorted_results 26 | results.sort_by{|x| x[:total_allocations]} 27 | end 28 | 29 | def table 30 | sorted_results.map do |res| 31 | name = res[:frameworkname].downcase 32 | allocations = res[:total_allocations] 33 | memory = res[:total_memsize] 34 | table_row(name, allocations, memory) 35 | end.join("\n") 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /apps/rails.ru: -------------------------------------------------------------------------------- 1 | require "securerandom" 2 | require "action_controller/railtie" 3 | require "rails/test_unit/railtie" 4 | 5 | class HelloWorld < Rails::Application 6 | routes.append do 7 | get "/", to: "hello#world" 8 | end 9 | 10 | config.cache_classes = true 11 | config.eager_load = true 12 | config.secret_key_base = SecureRandom.hex(64) 13 | 14 | ["Rack::Lock", "ActionDispatch::Flash", "ActionDispatch::BestStandardsSupport", 15 | "Rack::Sendfile", "ActionDispatch::Static", "Rack::MethodOverride", 16 | "ActionDispatch::RequestId", "Rails::Rack::Logger", 17 | "ActionDispatch::ShowExceptions", "ActionDispatch::DebugExceptions", 18 | "ActionDispatch::RemoteIp", "ActionDispatch::Callbacks", 19 | "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", 20 | "ActionDispatch::ParamsParser", "Rack::Head", "Rack::ConditionalGet", 21 | "Rack::ETag"].each do |middleware| 22 | config.middleware.delete(middleware) 23 | end 24 | end 25 | 26 | class HelloController < ActionController::Metal 27 | def world 28 | self.response_body = "Hello World!" 29 | end 30 | end 31 | 32 | APP = HelloWorld.initialize! 33 | run APP 34 | -------------------------------------------------------------------------------- /bench/support/speed_summary_table.rb: -------------------------------------------------------------------------------- 1 | require_relative 'table_helpers' 2 | class SpeedSummaryTable 3 | include TableHelpers 4 | attr_accessor :content # content: output from wrk, stripped a bit 5 | 6 | def initialize(content) 7 | @content = content 8 | end 9 | 10 | def summary_table 11 | summary = [] 12 | summary << table_row("Framework", "Requests/sec", '% from best') 13 | summary << table_separator 14 | summary << table 15 | end 16 | 17 | def render 18 | summary_table.join("\n") 19 | end 20 | 21 | def lines 22 | content.split("\n") 23 | end 24 | 25 | def results 26 | lines.each_slice(2).map do |pair| 27 | name = pair[0].gsub("*", '').strip.downcase 28 | number = pair[1].match(/(\d|\.)+/)[0] 29 | [name, number] 30 | end 31 | end 32 | 33 | def sorted_results 34 | @sorted_results ||= results.sort_by{|x| - x[1].to_i} 35 | end 36 | 37 | def max_rps 38 | sorted_results.first[1].to_f 39 | end 40 | 41 | def table 42 | sorted_results.map do |pair| 43 | name = pair[0] 44 | rps = pair[1] 45 | num = (pair[1].to_f/max_rps) 46 | round = ((num * 10000).round*1.0 ) / 100 47 | table_row(name, rps, round.to_s + "%") 48 | end.join("\n") 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /bench/support/speed_benchmarker.rb: -------------------------------------------------------------------------------- 1 | class Benchmarker 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 | 48 | def start_command 49 | "puma --environment production --threads 16:16 #{config}" 50 | end 51 | 52 | def wrk_command 53 | "wrk --threads 2 --duration 10 http://localhost:9292/" 54 | end 55 | 56 | require 'pty' 57 | def execute(cmd) 58 | puts cmd 59 | begin 60 | PTY.spawn( cmd ) do |stdin, stdout, pid| 61 | begin 62 | stdin.each { |line| print line } 63 | rescue Errno::EIO 64 | end 65 | end 66 | rescue PTY::ChildExited 67 | puts "The child process exited!" 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /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 | def initialize(config) 12 | @config = config 13 | load "./#{config}" 14 | end 15 | 16 | def human_output 17 | puts frameworkname 18 | puts "total allocations: #{total_allocations}" 19 | puts "total memsize: #{total_memsize}" 20 | detailed_allocations 21 | puts 22 | end 23 | 24 | def total_allocations 25 | stats.allocations.all.size 26 | end 27 | 28 | def total_memsize 29 | stats.allocations.bytes.to_a.inject(&:+) 30 | end 31 | 32 | def machine_result 33 | { 34 | :frameworkname => frameworkname, 35 | :total_allocations => total_allocations, 36 | :total_memsize => total_memsize 37 | } 38 | end 39 | 40 | 41 | def detailed_allocations 42 | puts 'allocations by source file and class:' 43 | puts stats.allocations(alias_paths: true). 44 | group_by(:sourcefile, :class_plus). 45 | sort_by_count. 46 | to_text 47 | end 48 | 49 | def request 50 | Rack::MockRequest.env_for('/') 51 | end 52 | 53 | def frameworkname 54 | File.basename(config).gsub(".ru", '').upcase 55 | end 56 | 57 | def stats 58 | @stats ||= AllocationStats.new(burn: 5).trace do 59 | app.call(request) 60 | end 61 | end 62 | 63 | def app 64 | APP 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/guilleiguaran/nancy.git 3 | revision: b3d9e74edee49298a17f360a4567d90c63ad0c55 4 | branch: master 5 | specs: 6 | nancy (0.4.0) 7 | rack 8 | 9 | GIT 10 | remote: https://github.com/luislavena/brooklyn.git 11 | revision: 3698aae362d68da77dd23ccf91a68ea022b48381 12 | branch: master 13 | specs: 14 | brooklyn (0.0.1) 15 | rack (>= 1.5.2, < 2.0) 16 | 17 | GEM 18 | remote: https://rubygems.org/ 19 | specs: 20 | actionpack (4.2.5) 21 | actionview (= 4.2.5) 22 | activesupport (= 4.2.5) 23 | rack (~> 1.6) 24 | rack-test (~> 0.6.2) 25 | rails-dom-testing (~> 1.0, >= 1.0.5) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 27 | actionview (4.2.5) 28 | activesupport (= 4.2.5) 29 | builder (~> 3.1) 30 | erubis (~> 2.7.0) 31 | rails-dom-testing (~> 1.0, >= 1.0.5) 32 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 33 | activesupport (4.2.5) 34 | i18n (~> 0.7) 35 | json (~> 1.7, >= 1.7.7) 36 | minitest (~> 5.1) 37 | thread_safe (~> 0.3, >= 0.3.4) 38 | tzinfo (~> 1.1) 39 | allocation_stats (0.1.5) 40 | blankslate (3.1.3) 41 | builder (3.2.2) 42 | cuba (3.4.0) 43 | rack 44 | erubis (2.7.0) 45 | gin (1.2.1) 46 | rack (~> 1.1) 47 | rack-protection (~> 1.0) 48 | tilt (~> 1.4) 49 | hobbit (0.6.0) 50 | rack 51 | http_router (0.11.2) 52 | rack (>= 1.0.0) 53 | url_mount (~> 0.2.1) 54 | i18n (0.7.0) 55 | innate (2015.10.28) 56 | rack (~> 1.6.4) 57 | json (1.8.3) 58 | loofah (2.0.3) 59 | nokogiri (>= 1.5.9) 60 | lotus-router (0.4.3) 61 | http_router (~> 0.11) 62 | lotus-utils (~> 0.5, >= 0.5.1) 63 | lotus-utils (0.5.2) 64 | mini_portile2 (2.0.0) 65 | minitest (5.8.3) 66 | mustermann (0.4.0) 67 | tool (~> 0.2) 68 | mustermann-simple (0.4.0) 69 | mustermann (= 0.4.0) 70 | nokogiri (1.6.7) 71 | mini_portile2 (~> 2.0.0.rc2) 72 | nyny (2.2.1) 73 | rack 74 | tilt 75 | parslet (1.7.1) 76 | blankslate (>= 2.0, <= 4.0) 77 | puma (2.15.3) 78 | rack (1.6.4) 79 | rack-accept (0.4.5) 80 | rack (>= 0.4) 81 | rack-protection (1.5.3) 82 | rack 83 | rack-test (0.6.3) 84 | rack (>= 1.0) 85 | rails-deprecated_sanitizer (1.0.3) 86 | activesupport (>= 4.2.0.alpha) 87 | rails-dom-testing (1.0.7) 88 | activesupport (>= 4.2.0.beta, < 5.0) 89 | nokogiri (~> 1.6.0) 90 | rails-deprecated_sanitizer (>= 1.0.1) 91 | rails-html-sanitizer (1.0.2) 92 | loofah (~> 2.0) 93 | railties (4.2.5) 94 | actionpack (= 4.2.5) 95 | activesupport (= 4.2.5) 96 | rake (>= 0.8.7) 97 | thor (>= 0.18.1, < 2.0) 98 | rake (10.4.2) 99 | ramaze (2012.12.08) 100 | innate (>= 2012.12) 101 | rake 102 | rambutan (0.0.1) 103 | http_router 104 | roda (2.8.0) 105 | rack 106 | scorched (0.24) 107 | rack (~> 1.4) 108 | rack-accept (~> 0.4) 109 | scorched-accept (~> 0.1) 110 | tilt (~> 1.4) 111 | scorched-accept (0.1) 112 | parslet (~> 1.7) 113 | seg (0.0.1) 114 | sinatra (1.4.6) 115 | rack (~> 1.4) 116 | rack-protection (~> 1.4) 117 | tilt (>= 1.3, < 3) 118 | syro (2.0.0) 119 | rack (~> 1.6.0) 120 | seg 121 | thor (0.19.1) 122 | thread_safe (0.3.5) 123 | tilt (1.4.1) 124 | tool (0.2.3) 125 | tzinfo (1.2.2) 126 | thread_safe (~> 0.1) 127 | url_mount (0.2.1) 128 | rack 129 | 130 | PLATFORMS 131 | ruby 132 | 133 | DEPENDENCIES 134 | actionpack (~> 4.2) 135 | allocation_stats 136 | brooklyn! 137 | cuba 138 | gin 139 | hobbit 140 | lotus-router 141 | mustermann-simple 142 | nancy! 143 | nyny 144 | puma (~> 2.15) 145 | railties (~> 4.2) 146 | ramaze 147 | rambutan 148 | roda 149 | scorched 150 | sinatra 151 | syro 152 | tzinfo 153 | 154 | BUNDLED WITH 155 | 1.10.6 156 | -------------------------------------------------------------------------------- /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 | The following microframeworks were considered when doing this research: 20 | 21 | - [Brooklyn](https://github.com/luislavena/brooklyn) - [brooklyn.ru](apps/brooklyn.ru) 22 | - [Cuba](https://github.com/soveran/cuba) - [cuba.ru](apps/cuba.ru) 23 | - [Gin](https://github.com/jcasts/gin) - [gin.ru](apps/gin.ru) 24 | - [Hobbit](https://github.com/patriciomacadden/hobbit) - [hobbit.ru](apps/hobbit.ru) 25 | - [Lotus (Router)](https://github.com/lotus/router) - [lotus-router.ru](apps/lotus-router.ru) 26 | - [Nancy](https://github.com/heapsource/nancy) - [nancy.ru](apps/nancy.ru) 27 | - [NYNY](https://github.com/alisnic/nyny) - [nyny.ru](apps/nyny.ru) 28 | - [Mustermann](https://github.com/rkh/mustermann) - [mustermann.ru](apps/mustermann.ru) 29 | - [Rack](https://github.com/rack/rack) - [rack.ru](apps/rack.ru) + [rack-response.ru](apps/rack-response.ru) 30 | - ~~[Rails](https://github.com/rails/rails) - [rails.ru](apps/rails.ru)~~ 31 | - [Ramaze](https://github.com/Ramaze/ramaze) - [ramaze.ru](apps/ramaze.ru) 32 | - [Rambutan](https://github.com/NewRosies/rambutan) - [rambutan.ru](apps/rambutan.ru) 33 | - [Roda](https://github.com/jeremyevans/roda) - [roda.ru](apps/roda.ru) 34 | - [Scorched](https://github.com/Wardrop/Scorched) - [scorched.ru](apps/scorched.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 | 38 | Please note that while Rails has been added to the list, it is just a 39 | minimalistic representation (using Metal, no full middleware stack, etc). You 40 | shouldn't take the performance numbers mentioned here about Rails (or any 41 | other) as scientific and decision-taking references. 42 | 43 | ## How? 44 | 45 | Used [wrk](https://github.com/wg/wrk) to benchmark, locally, a burst of 46 | requests (in 2 threads) over 10 seconds. The command line used was: 47 | 48 | ```console 49 | $ wrk -t 2 http://localhost:9292/ 50 | ``` 51 | 52 | All the frameworks were run using [Puma](https://github.com/puma/puma) on 53 | Ruby 2.1, in production mode and using 16 threads: 54 | 55 | ```console 56 | $ puma -e production -t 16:16 apps/ 57 | ``` 58 | 59 | ## Run benchmark for all frameworks 60 | 61 | ```console 62 | # use `bundle exec`, if needed 63 | $ bench/summary-memory 64 | $ bench/summary-speed 65 | ``` 66 | 67 | ### Have some numbers around? 68 | 69 | Yup, I do: 70 | 71 | #### Requests/sec 72 | 73 | ``` 74 | Framework Requests/sec % from best 75 | ---------------------------------------------- 76 | rack 19138.63 100.0% 77 | mustermann 15877.18 82.96% 78 | roda 15773.44 82.42% 79 | syro 15677.54 81.92% 80 | cuba 14664.93 76.62% 81 | hobbit 14346.23 74.96% 82 | lotus-router 13298.08 69.48% 83 | rack-response 12980.98 67.83% 84 | rambutan 10817.10 56.52% 85 | brooklyn 10116.29 52.86% 86 | nancy 9118.49 47.64% 87 | nyny 8170.25 42.69% 88 | rails 5847.11 30.55% 89 | gin 5689.37 29.73% 90 | sinatra 4559.17 23.82% 91 | ramaze 1986.19 10.38% 92 | ``` 93 | 94 | 95 | #### Memory Allocation/Request 96 | 97 | ``` 98 | Framework Allocs/Req Memsize/Req 99 | -------------------------------------- 100 | rack 38 3256 101 | syro 43 3536 102 | hobbit 45 3808 103 | roda 45 3752 104 | mustermann 51 4112 105 | cuba 53 4072 106 | rack-response 56 5312 107 | lotus-router 66 4944 108 | brooklyn 69 6412 109 | nancy 75 7644 110 | nyny 82 8396 111 | rambutan 85 6848 112 | rails 113 10311 113 | gin 198 17219 114 | sinatra 212 15783 115 | ramaze 454 39670 116 | ``` 117 | 118 | 119 | 120 | These numbers were collected on: 121 | 122 | - Ubuntu 15.10 64-bit (kernel: 4.2.0-19-generic) 123 | - Dell XPS 13 (9343, QHD, Developer Edition) 124 | - Intel® Core™ i7-5500U CPU @ 2.40GHz × 4 125 | - 8GB RAM 126 | - 256GB SSD 127 | - ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux] 128 | --------------------------------------------------------------------------------