Browse Source

Add Rack+Sequel benchmarks (#2515)

Mike Pastore 8 years ago
parent
commit
9a40bf8b6d

+ 1 - 0
.travis.yml

@@ -166,6 +166,7 @@ env:
     - "TESTDIR=Ruby/ngx_mruby"
     - "TESTDIR=Ruby/padrino"
     - "TESTDIR=Ruby/rack"
+    - "TESTDIR=Ruby/rack-sequel"
     - "TESTDIR=Ruby/rails"
     - "TESTDIR=Ruby/rails-stripped"
     - "TESTDIR=Ruby/sinatra"

+ 4 - 0
frameworks/Ruby/rack-sequel/.gitignore

@@ -0,0 +1,4 @@
+.bundle
+vendor/bundle
+passenger.*
+Gemfile.lock

+ 22 - 0
frameworks/Ruby/rack-sequel/Gemfile

@@ -0,0 +1,22 @@
+source 'https://rubygems.org'
+
+gem 'json', '~> 2.0'
+gem 'passenger', '~> 5.1', :platforms=>[:ruby, :mswin], :require=>false
+gem 'puma', '~> 3.6', :require=>false
+gem 'sequel', '~> 4.40',
+  :git=>'https://github.com/jeremyevans/sequel.git', :branch=>'master'
+gem 'rack', '~> 2.0'
+gem 'sysrandom', '~> 1.0', :require=>'sysrandom/securerandom'
+gem 'torquebox-web', '>= 4.0.0.beta3', '< 5', :platforms=>:jruby, :require=>false
+gem 'unicorn', '~> 5.2', :platforms=>[:ruby, :mswin], :require=>false
+
+group :mysql do
+  gem 'jdbc-mysql', '~> 5.1', :platforms=>:jruby, :require=>'jdbc/mysql'
+  gem 'mysql2', '~> 0.4', :platforms=>[:ruby, :mswin]
+end
+
+group :postgresql do
+  gem 'jdbc-postgres', '~> 9.4', :platforms=>:jruby, :require=>'jdbc/postgres'
+  gem 'pg', '~> 0.19', :platforms=>[:ruby, :mswin]
+  gem 'sequel_pg', '~> 1.6', :platforms=>:ruby, :require=>false
+end

+ 2 - 0
frameworks/Ruby/rack-sequel/Makefile

@@ -0,0 +1,2 @@
+json: benchmark_config.yaml
+	ruby -r json -r yaml -e 'puts JSON.pretty_generate(YAML.load(ARGF.read))' <benchmark_config.yaml >benchmark_config.json

+ 51 - 0
frameworks/Ruby/rack-sequel/README.md

@@ -0,0 +1,51 @@
+# Ruby [Rack](http://rack.rubyforge.org)-[Sequel](http://sequel.jeremyevans.net) Benchmarking Test
+
+The information below contains information specific to the Sequel rewrite of
+the Rack benchmarking test. For further guidance, review the
+[documentation](http://frameworkbenchmarks.readthedocs.org/en/latest/).  Also
+note the additional information provided in the [Ruby README](../).
+
+This is the Ruby Rack portion of a [benchmarking test suite](../../) comparing
+a variety of web platforms.
+
+## Infrastructure Software Versions
+
+The tests will be run with:
+
+* [Ruby 2.4](http://www.ruby-lang.org)
+* [JRuby 9.1](http://jruby.org)
+* [Rubinius 3](https://rubinius.com)\*
+* [Puma 3.6](http://puma.io)
+* [Passenger 5.1](https://www.phusionpassenger.com)
+* [Unicorn 5.2](https://bogomips.org/unicorn/)
+* [TorqueBox 4.0](http://torquebox.org)
+* [Rack 2.0](http://rack.rubyforge.org)
+* [Sequel 4.43](http://sequel.jeremyevans.net)
+* [MySQL 5.5](https://www.mysql.com)
+* [Postgres 9.3](https://www.postgresql.org)
+
+\* - Tests are developed but currently disabled due to compatibility issues.
+
+## Paths & Source for Tests
+
+* [JSON Serialization](hello_world.rb): "/json"
+* [Single Database Query](hello_world.rb): "/db"
+* [Multiple Database Queries](hello_world.rb): "/db?queries={#}"
+* [Fortunes](hello_world.rb): "/fortune"
+* [Database Updates](hello_world.rb): "/update?queries={#}"
+* [Plaintext](hello_world.rb): "/plaintext"
+
+## Get Help
+
+### Experts
+
+_No experts listed, yet. If you're an expert, add yourself!_
+
+### Community
+
+* [Rack Google Group](https://groups.google.com/forum/#!forum/rack-devel)
+* `#rack` IRC Channel ([irc.freenode.net](http://freenode.net/))
+
+### Resources
+
+* [Rack Source Code](https://github.com/rack/rack)

+ 237 - 0
frameworks/Ruby/rack-sequel/benchmark_config.json

@@ -0,0 +1,237 @@
+{
+  "framework": "rack-sequel",
+  "tests": [
+    {
+      "default": {
+        "setup_file": "run_mri_puma",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "Rack",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-puma-mri",
+        "versus": "rack-puma-mri",
+        "notes": ""
+      },
+      "postgres": {
+        "setup_file": "run_mri_puma",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "Rack",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-postgres-puma-mri",
+        "versus": null,
+        "notes": ""
+      },
+      "puma-jruby": {
+        "setup_file": "run_jruby_puma",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "JRuby",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-puma-jruby",
+        "versus": "rack-puma-jruby",
+        "notes": ""
+      },
+      "postgres-puma-jruby": {
+        "setup_file": "run_jruby_puma",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "JRuby",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-postgres-puma-jruby",
+        "versus": null,
+        "notes": ""
+      },
+      "passenger-mri": {
+        "setup_file": "run_mri_passenger",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "Rack",
+        "webserver": "Passenger Standalone",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-passenger-mri",
+        "versus": null,
+        "notes": ""
+      },
+      "postgres-passenger-mri": {
+        "setup_file": "run_mri_passenger",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "Rack",
+        "webserver": "Passenger Standalone",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-postgres-passenger-mri",
+        "versus": null,
+        "notes": ""
+      },
+      "unicorn-mri": {
+        "setup_file": "run_mri_unicorn",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "Rack",
+        "webserver": "Unicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-unicorn-mri",
+        "versus": "rack-unicorn",
+        "notes": ""
+      },
+      "postgres-unicorn-mri": {
+        "setup_file": "run_mri_unicorn",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "Rack",
+        "webserver": "Unicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-postgres-unicorn-mri",
+        "versus": null,
+        "notes": ""
+      },
+      "torquebox-jruby": {
+        "setup_file": "run_jruby_torquebox",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "JRuby",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-torquebox-jruby",
+        "versus": "rack-torqbox-jruby",
+        "notes": ""
+      },
+      "postgres-torquebox-jruby": {
+        "setup_file": "run_jruby_torquebox",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Stripped",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "rack-sequel",
+        "language": "Ruby",
+        "orm": "Micro",
+        "platform": "JRuby",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "rack-sequel-postgres-torquebox-jruby",
+        "versus": null,
+        "notes": ""
+      }
+    }
+  ]
+}

+ 82 - 0
frameworks/Ruby/rack-sequel/benchmark_config.yaml

@@ -0,0 +1,82 @@
+---
+framework: rack-sequel
+tests:
+  - default: &default
+      setup_file: run_mri_puma
+      json_url: /json
+      db_url: /db
+      query_url: /queries?queries=
+      fortune_url: /fortunes
+      update_url: /updates?queries=
+      plaintext_url: /plaintext
+      port: 8080
+      approach: Stripped
+      classification: Micro
+      database: MySQL
+      framework: rack-sequel
+      language: Ruby
+      orm: Micro
+      platform: Rack
+      webserver: Puma
+      os: Linux
+      database_os: Linux
+      display_name: rack-sequel-puma-mri
+      versus: rack-puma-mri
+      notes: ""
+    postgres:
+      <<: *default
+      database: Postgres
+      display_name: rack-sequel-postgres-puma-mri
+      versus: ~
+    puma-jruby:
+      <<: *default
+      setup_file: run_jruby_puma
+      platform: JRuby
+      display_name: rack-sequel-puma-jruby
+      versus: rack-puma-jruby
+    postgres-puma-jruby:
+      <<: *default
+      setup_file: run_jruby_puma
+      database: Postgres
+      platform: JRuby
+      display_name: rack-sequel-postgres-puma-jruby
+      versus: ~
+    passenger-mri:
+      <<: *default
+      setup_file: run_mri_passenger
+      webserver: Passenger Standalone
+      display_name: rack-sequel-passenger-mri
+      versus: ~
+    postgres-passenger-mri:
+      <<: *default
+      setup_file: run_mri_passenger
+      database: Postgres
+      webserver: Passenger Standalone
+      display_name: rack-sequel-postgres-passenger-mri
+      versus: ~
+    unicorn-mri:
+      <<: *default
+      setup_file: run_mri_unicorn
+      webserver: Unicorn
+      display_name: rack-sequel-unicorn-mri
+      versus: rack-unicorn
+    postgres-unicorn-mri:
+      <<: *default
+      setup_file: run_mri_unicorn
+      database: Postgres
+      webserver: Unicorn
+      display_name: rack-sequel-postgres-unicorn-mri
+      versus: ~
+    torquebox-jruby:
+      <<: *default
+      setup_file: run_jruby_torquebox
+      platform: JRuby
+      display_name: rack-sequel-torquebox-jruby
+      versus: rack-torqbox-jruby
+    postgres-torquebox-jruby:
+      <<: *default
+      setup_file: run_jruby_torquebox
+      database: Postgres
+      platform: JRuby
+      display_name: rack-sequel-postgres-torquebox-jruby
+      versus: ~

+ 4 - 0
frameworks/Ruby/rack-sequel/config.ru

@@ -0,0 +1,4 @@
+require_relative 'hello_world'
+use Rack::ContentLength
+use Rack::Chunked
+run HelloWorld.new

+ 41 - 0
frameworks/Ruby/rack-sequel/config/auto_tune.rb

@@ -0,0 +1,41 @@
+#!/usr/bin/env ruby
+# Instantiate about one process per X MiB of available memory, scaling up to as
+# close to MAX_THREADS as possible while observing an upper bound based on the
+# number of virtual/logical CPUs. If there are fewer processes than
+# MAX_THREADS, add threads per process to reach MAX_THREADS.
+require 'etc'
+
+KB_PER_WORKER = 64 * 1_024 # average of peak PSS of single-threaded processes (watch smem -k)
+MIN_WORKERS = 2
+MAX_WORKERS_PER_VCPU = 1.25 # virtual/logical
+MIN_THREADS_PER_WORKER = 4
+MAX_THREADS = Integer(ENV['MAX_CONCURRENCY'] || 256)
+
+def meminfo(arg)
+  File.open('/proc/meminfo') do |f|
+    f.each_line do |line|
+      key, value = line.split(/:\s+/)
+      return value.split(/\s+/).first.to_i if key == arg
+    end
+  end
+
+  fail "Unable to find `#{arg}' in /proc/meminfo!"
+end
+
+def auto_tune
+  avail_mem = meminfo('MemAvailable') * 0.8 - MAX_THREADS * 1_024
+
+  workers = [
+    [(1.0 * avail_mem / KB_PER_WORKER).floor, MIN_WORKERS].max,
+    (Etc.nprocessors * MAX_WORKERS_PER_VCPU).ceil
+  ].min
+
+  threads_per_worker = [
+    workers < MAX_THREADS ? (1.0 * MAX_THREADS / workers).ceil : -Float::INFINITY,
+    MIN_THREADS_PER_WORKER
+  ].max
+
+  [workers, threads_per_worker]
+end
+
+p auto_tune if $0 == __FILE__

+ 6 - 0
frameworks/Ruby/rack-sequel/config/bundle_install.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Ensure we don't accidentally (try to) use gems for the wrong platform.
+rm -f $TROOT/Gemfile.lock
+
+bundle install --jobs=4 --gemfile=$TROOT/Gemfile --path=vendor/bundle

+ 13 - 0
frameworks/Ruby/rack-sequel/config/common_run.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [[ $LOGDIR == *postgres* ]] ; then
+  DBTYPE=postgresql
+else
+  DBTYPE=mysql
+fi
+
+export DBTYPE
+
+if [[ $LOGDIR == *jruby* ]] ; then
+  . $(dirname $0)/config/java_tune.sh
+fi

+ 24 - 0
frameworks/Ruby/rack-sequel/config/java_tune.sh

@@ -0,0 +1,24 @@
+#!/bin/sh
+stack_size=1
+cache_size=240
+meta_size=192
+
+# Factor in Ruby's per-thread overhead...
+avail_mem=$(awk '/^MemAvailable/ { print int(0.7 * $2 / 1024) - ( \
+  $ENVIRON["MAX_CONCURRENCY"] * $ENVIRON["THREAD_FACTOR"] \
+); exit }' /proc/meminfo)
+
+# ...as well as Java's per-thread stack size.
+heap_size=$(( avail_mem - meta_size - cache_size - (
+  stack_size * MAX_CONCURRENCY * THREAD_FACTOR
+) ))
+
+JRUBY_OPTS="-J-server -J-XX:+AggressiveOpts -J-Djava.net.preferIPv4Stack=true"
+JRUBY_OPTS="$JRUBY_OPTS -J-XX:+UseSerialGC"
+#JRUBY_OPTS="$JRUBY_OPTS -J-XX:+UseG1GC -J-XX:MaxGCPauseMillis=100"
+JRUBY_OPTS="$JRUBY_OPTS -J-Xms${heap_size}m -J-Xmx${heap_size}m"
+JRUBY_OPTS="$JRUBY_OPTS -J-Xss${stack_size}m"
+JRUBY_OPTS="$JRUBY_OPTS -J-XX:MaxMetaspaceSize=${meta_size}m"
+JRUBY_OPTS="$JRUBY_OPTS -J-XX:ReservedCodeCacheSize=${cache_size}m"
+
+export JRUBY_OPTS

+ 7 - 0
frameworks/Ruby/rack-sequel/config/mri_puma.rb

@@ -0,0 +1,7 @@
+require_relative 'auto_tune'
+
+# FWBM only... use the puma_auto_tune gem in production!
+num_workers, num_threads = auto_tune
+
+workers num_workers
+threads num_threads, num_threads

+ 6 - 0
frameworks/Ruby/rack-sequel/config/mri_unicorn.rb

@@ -0,0 +1,6 @@
+require_relative 'auto_tune'
+
+# FWBM only...
+num_workers, = auto_tune
+
+worker_processes num_workers

+ 192 - 0
frameworks/Ruby/rack-sequel/hello_world.rb

@@ -0,0 +1,192 @@
+# frozen_string_literal: true
+require 'bundler'
+require 'time'
+
+MAX_PK = 10_000
+QUERIES_MIN = 1
+QUERIES_MAX = 500
+SEQUEL_NO_ASSOCIATIONS = true
+
+Bundler.require(:default) # Load core modules
+
+def connect(dbtype)
+  Bundler.require(dbtype) # Load database-specific modules
+
+  adapters = {
+    :mysql=>{ :jruby=>'jdbc:mysql', :mri=>'mysql2' },
+    :postgresql=>{ :jruby=>'jdbc:postgresql', :mri=>'postgres' }
+  }
+
+  opts = {}
+
+  # Determine threading/thread pool size and timeout
+  if defined?(JRUBY_VERSION)
+    opts[:max_connections] = Integer(ENV.fetch('MAX_CONCURRENCY'))
+    opts[:pool_timeout] = 10
+  elsif defined?(Puma)
+    opts[:max_connections] = Puma.cli_config.options.fetch(:max_threads)
+    opts[:pool_timeout] = 10
+  else
+    Sequel.single_threaded = true
+  end
+
+  Sequel.connect \
+    '%<adapter>s://%<host>s/%<database>s?user=%<user>s&password=%<password>s' % {
+      :adapter=>adapters.fetch(dbtype).fetch(defined?(JRUBY_VERSION) ? :jruby : :mri),
+      :host=>ENV.fetch('DBHOST', '127.0.0.1'),
+      :database=>'hello_world',
+      :user=>'benchmarkdbuser',
+      :password=>'benchmarkdbpass'
+    }, opts
+end
+
+DB = connect(ENV.fetch('DBTYPE').to_sym).tap do |db|
+  db.extension(:freeze_datasets)
+  db.freeze
+end
+
+# Define ORM models
+class World < Sequel::Model(:World)
+  def_column_alias(:randomnumber, :randomNumber) if DB.database_type == :mysql
+end
+
+class Fortune < Sequel::Model(:Fortune)
+  # Allow setting id to zero (0) per benchmark requirements
+  unrestrict_primary_key
+end
+
+# Our Rack application to be executed by rackup
+class HelloWorld
+  DEFAULT_HEADERS = {}.tap do |h|
+    server_string =
+      if defined?(PhusionPassenger)
+        [
+          PhusionPassenger::SharedConstants::SERVER_TOKEN_NAME,
+          PhusionPassenger::VERSION_STRING
+        ].join('/').freeze
+      elsif defined?(Puma)
+        Puma::Const::PUMA_SERVER_STRING
+      elsif defined?(Unicorn)
+        Unicorn::HttpParser::DEFAULTS['SERVER_SOFTWARE']
+      end
+
+    h['Server'] = server_string if server_string
+  end.freeze
+
+  def bounded_queries(env)
+    params = Rack::Utils.parse_query(env['QUERY_STRING'])
+
+    queries = params['queries'].to_i
+    return QUERIES_MIN if queries < QUERIES_MIN
+    return QUERIES_MAX if queries > QUERIES_MAX
+    queries
+  end
+
+  # Return a random number between 1 and MAX_PK
+  def rand1
+    Random.rand(MAX_PK).succ
+  end
+
+  # Return an array of `n' unique random numbers between 1 and MAX_PK
+  def randn(n)
+    (1..MAX_PK).to_a.shuffle!.take(n)
+  end
+
+  def db
+    World.with_pk(rand1).values
+  end
+
+  def queries(env)
+    # Benchmark requirements explicitly forbid a WHERE..IN here, so be good
+    randn(bounded_queries(env))
+      .map! { |id| World.with_pk(id).values }
+  end
+
+  def fortunes
+    fortunes = Fortune.all
+    fortunes << Fortune.new(
+      :id=>0,
+      :message=>'Additional fortune added at request time.'
+    )
+    fortunes.sort_by!(&:message)
+
+    html = <<~'HTML'
+      <!DOCTYPE html>
+      <html>
+      <head>
+        <title>Fortunes</title>
+      </head>
+
+      <body>
+
+      <table>
+      <tr>
+        <th>id</th>
+        <th>message</th>
+      </tr>
+    HTML
+
+    fortunes.each do |fortune|
+      html += <<~"HTML"
+      <tr>
+        <td>#{Rack::Utils.escape_html(fortune.id)}</td>
+        <td>#{Rack::Utils.escape_html(fortune.message)}</td>
+      </tr>
+      HTML
+    end
+
+    html += <<~'HTML'
+      </table>
+
+      </body>
+      </html>
+    HTML
+  end
+
+  WORLD_BY_ID_FOR_UPDATE = World.naked.for_update.where(:id=>:$id).prepare(:first, :world_by_id_for_update)
+  WORLD_UPDATE = World.where(:id=>:$id).prepare(:update, :world_update, :randomnumber=>:$randomnumber)
+
+  def updates(env)
+    # Benchmark requirements explicitly forbid a WHERE..IN here, transactions
+    # are optional, batch updates are allowed (but each transaction can only
+    # read and write a single record?), so... be good
+    randn(bounded_queries(env)).map! do |id|
+      DB.transaction do
+        world = WORLD_BY_ID_FOR_UPDATE.(:id=>id)
+        WORLD_UPDATE.(:id=>id, :randomnumber=>(world[:randomnumber] = rand1))
+        world
+      end
+    end
+  end
+
+  def call(env)
+    content_type, *body =
+      case env['PATH_INFO']
+      when '/json'
+        # Test type 1: JSON serialization
+        ['application/json', JSON.fast_generate(:message=>'Hello, World!')]
+      when '/db'
+        # Test type 2: Single database query
+        ['application/json', JSON.fast_generate(db)]
+      when '/queries'
+        # Test type 3: Multiple database queries
+        ['application/json', JSON.fast_generate(queries(env))]
+      when '/fortunes'
+        # Test type 4: Fortunes
+        ['text/html; charset=utf-8', fortunes]
+      when '/updates'
+        # Test type 5: Database updates
+        ['application/json', JSON.fast_generate(updates(env))]
+      when '/plaintext'
+        # Test type 6: Plaintext
+        ['text/plain', 'Hello, World!']
+      end
+
+    return 200,
+      DEFAULT_HEADERS.merge(
+        'Content-Type'=>content_type,
+        'Date'=>Time.now.httpdate
+      ),
+      body
+  end
+end

+ 13 - 0
frameworks/Ruby/rack-sequel/run_jruby_puma.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+
+THREAD_FACTOR=1
+
+. $(dirname $0)/config/common_run.sh
+
+fw_depends $DBTYPE rvm jruby-9.1
+
+rvm use jruby-$JRUBY_VERSION
+
+. $(dirname $0)/config/bundle_install.sh
+
+bundle exec puma -t $MAX_CONCURRENCY:$MAX_CONCURRENCY -b tcp://0.0.0.0:8080 -e production &

+ 13 - 0
frameworks/Ruby/rack-sequel/run_jruby_torquebox.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+
+THREAD_FACTOR=2
+
+. $(dirname $0)/config/common_run.sh
+
+fw_depends $DBTYPE rvm jruby-9.1
+
+rvm use jruby-$JRUBY_VERSION
+
+. $(dirname $0)/config/bundle_install.sh
+
+bundle exec torquebox run --io-threads $MAX_CONCURRENCY --worker-threads $MAX_CONCURRENCY -b 0.0.0.0 -p 8080 -e production &

+ 20 - 0
frameworks/Ruby/rack-sequel/run_mri_passenger.sh

@@ -0,0 +1,20 @@
+#!/bin/bash
+
+. $(dirname $0)/config/common_run.sh
+
+fw_depends $DBTYPE rvm ruby-2.4
+
+rvm use ruby-$MRI_VERSION
+
+. $(dirname $0)/config/bundle_install.sh
+
+# TODO: https://github.com/phusion/passenger/issues/1916
+export _PASSENGER_FORCE_HTTP_SESSION=true
+
+# FWBM only... Passenger will auto-tune itself in production!
+instances=$(ruby -r$(dirname $0)/config/auto_tune -e 'puts auto_tune.first')
+
+bundle exec passenger start --log-level 1 \
+  --engine builtin --disable-turbocaching --disable-security-update-check \
+  --spawn-method direct --max-pool-size $instances --min-instances $instances --max-request-queue-size 1024 \
+  --address 0.0.0.0 --port 8080 --environment production &

+ 11 - 0
frameworks/Ruby/rack-sequel/run_mri_puma.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+. $(dirname $0)/config/common_run.sh
+
+fw_depends $DBTYPE rvm ruby-2.4
+
+rvm use ruby-$MRI_VERSION
+
+. $(dirname $0)/config/bundle_install.sh
+
+bundle exec puma -C config/mri_puma.rb -b tcp://0.0.0.0:8080 -e production &

+ 11 - 0
frameworks/Ruby/rack-sequel/run_mri_unicorn.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+. $(dirname $0)/config/common_run.sh
+
+fw_depends $DBTYPE rvm ruby-2.4
+
+rvm use ruby-$MRI_VERSION
+
+. $(dirname $0)/config/bundle_install.sh
+
+bundle exec unicorn -c config/mri_unicorn.rb -o 0.0.0.0 -p 8080 -E production &

+ 1 - 0
frameworks/Ruby/rack-sequel/source_code

@@ -0,0 +1 @@
+./rack-sequel/hello_world.rb