Browse Source

Add additional benchmarks to sinatra-sequel (#2513)

* Generate JSON from YAML to make it easier to add benchmarks
* Fix connection pool sizing in sinatra-sequel
* Reduce object allocations
* Add Phusion Passenger
* Add Unicorn
* Add TorqueBox
* Performance tuning h/t Jeremy Evans

[ci fw-only Ruby/sinatra-sequel]
Mike Pastore 8 years ago
parent
commit
49d9029be4

+ 2 - 0
frameworks/Ruby/sinatra-sequel/.gitignore

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

+ 7 - 2
frameworks/Ruby/sinatra-sequel/Gemfile

@@ -1,11 +1,16 @@
 source 'https://rubygems.org'
 source 'https://rubygems.org'
 
 
 gem 'json', '~> 2.0'
 gem 'json', '~> 2.0'
+gem 'passenger', '~> 5.1', :platforms=>[:ruby, :mswin], :require=>false
 gem 'puma', '~> 3.6', :require=>false
 gem 'puma', '~> 3.6', :require=>false
-gem 'sequel', '~> 4.40'
-gem 'sinatra', '>= 2.0.0.beta2', '< 3.0', :require=>'sinatra/base'
+gem 'sequel', '~> 4.40',
+  :git=>'https://github.com/jeremyevans/sequel.git', :branch=>'master'
+gem 'sinatra', '>= 2.0.0.beta2', '< 3.0', :require=>'sinatra/base',
+  :git=>'https://github.com/sinatra/sinatra.git', :branch=>'master'
 gem 'slim', '~> 3.0'
 gem 'slim', '~> 3.0'
 gem 'sysrandom', '~> 1.0', :require=>'sysrandom/securerandom'
 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
 group :mysql do
   gem 'jdbc-mysql', '~> 5.1', :platforms=>:jruby, :require=>'jdbc/mysql'
   gem 'jdbc-mysql', '~> 5.1', :platforms=>:jruby, :require=>'jdbc/mysql'

+ 0 - 51
frameworks/Ruby/sinatra-sequel/Gemfile.lock

@@ -1,51 +0,0 @@
-GEM
-  remote: https://rubygems.org/
-  specs:
-    jdbc-mysql (5.1.40)
-    jdbc-postgres (9.4.1206)
-    json (2.0.3)
-    json (2.0.3-java)
-    mustermann (1.0.0.beta2)
-    mysql2 (0.4.5)
-    pg (0.19.0)
-    puma (3.6.2)
-    puma (3.6.2-java)
-    rack (2.0.1)
-    rack-protection (2.0.0.beta2)
-      rack
-    sequel (4.42.1)
-    sequel_pg (1.6.17)
-      pg (>= 0.8.0)
-      sequel (>= 4.0.0)
-    sinatra (2.0.0.beta2)
-      mustermann (= 1.0.0.beta2)
-      rack (~> 2.0)
-      rack-protection (= 2.0.0.beta2)
-      tilt (~> 2.0)
-    slim (3.0.7)
-      temple (~> 0.7.6)
-      tilt (>= 1.3.3, < 2.1)
-    sysrandom (1.0.4)
-    sysrandom (1.0.4-java)
-    temple (0.7.7)
-    tilt (2.0.5)
-
-PLATFORMS
-  java
-  ruby
-
-DEPENDENCIES
-  jdbc-mysql (~> 5.1)
-  jdbc-postgres (~> 9.4)
-  json (~> 2.0)
-  mysql2 (~> 0.4)
-  pg (~> 0.19)
-  puma (~> 3.6)
-  sequel (~> 4.40)
-  sequel_pg (~> 1.6)
-  sinatra (>= 2.0.0.beta2, < 3.0)
-  slim (~> 3.0)
-  sysrandom (= 1.0.4)
-
-BUNDLED WITH
-   1.13.7

+ 2 - 0
frameworks/Ruby/sinatra-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

+ 5 - 1
frameworks/Ruby/sinatra-sequel/README.md

@@ -16,8 +16,12 @@ The tests will be run with:
 * [JRuby 9.1](http://jruby.org)
 * [JRuby 9.1](http://jruby.org)
 * [Rubinius 3](https://rubinius.com)\*
 * [Rubinius 3](https://rubinius.com)\*
 * [Puma 3.6](http://puma.io)
 * [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)
 * [Sinatra 2.0](http://www.sinatrarb.com)
 * [Sinatra 2.0](http://www.sinatrarb.com)
-* [Sequel 4.40](http://sequel.jeremyevans.net)
+* [Sequel 4.43](http://sequel.jeremyevans.net)
+* [Slim 3.0](http://slim-lang.com)
 * [MySQL 5.5](https://www.mysql.com)
 * [MySQL 5.5](https://www.mysql.com)
 * [Postgres 9.3](https://www.postgresql.org)
 * [Postgres 9.3](https://www.postgresql.org)
 
 

+ 233 - 93
frameworks/Ruby/sinatra-sequel/benchmark_config.json

@@ -1,97 +1,237 @@
 {
 {
   "framework": "sinatra-sequel",
   "framework": "sinatra-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": "Realistic",
-      "classification": "Micro",
-      "database": "MySQL",
-      "framework": "sinatra-sequel",
-      "language": "Ruby",
-      "orm": "Full",
-      "platform": "Rack",
-      "webserver": "Puma",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "sinatra-sequel-puma-mri",
-      "notes": "",
-      "versus": "rack-puma-mri"
-    },
-    "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": "Realistic",
-      "classification": "Micro",
-      "database": "Postgres",
-      "framework": "sinatra-sequel",
-      "language": "Ruby",
-      "orm": "Full",
-      "platform": "Rack",
-      "webserver": "Puma",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "sinatra-sequel-postgres-puma-mri",
-      "notes": "",
-      "versus": "rack-puma-mri"
-    },
-    "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": "Realistic",
-      "classification": "Micro",
-      "database": "MySQL",
-      "framework": "sinatra-sequel",
-      "language": "Ruby",
-      "orm": "Full",
-      "platform": "JRuby",
-      "webserver": "Puma",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "sinatra-sequel-puma-jruby",
-      "notes": "",
-      "versus": "sinatra-sequel-puma-mri"
-    },
-    "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": "Realistic",
-      "classification": "Micro",
-      "database": "Postgres",
-      "framework": "sinatra-sequel",
-      "language": "Ruby",
-      "orm": "Full",
-      "platform": "JRuby",
-      "webserver": "Puma",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "sinatra-sequel-postgres-puma-jruby",
-      "notes": "",
-      "versus": "sinatra-sequel-postgres-puma-mri"
+  "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": "Realistic",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-puma-mri",
+        "versus": "rack-sequel-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": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-postgres-puma-mri",
+        "versus": "rack-sequel-postgres-puma-mri",
+        "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": "Realistic",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "JRuby",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-puma-jruby",
+        "versus": "rack-sequel-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": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "JRuby",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-postgres-puma-jruby",
+        "versus": "rack-sequel-postgres-puma-jruby",
+        "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": "Realistic",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Passenger Standalone",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-passenger-mri",
+        "versus": "rack-sequel-passenger-mri",
+        "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": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Passenger Standalone",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-postgres-passenger-mri",
+        "versus": "rack-sequel-postgres-passenger-mri",
+        "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": "Realistic",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Unicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-unicorn-mri",
+        "versus": "rack-sequel-unicorn-mri",
+        "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": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Unicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-postgres-unicorn-mri",
+        "versus": "rack-sequel-postgres-unicorn-mri",
+        "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": "Realistic",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "JRuby",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-torquebox-jruby",
+        "versus": "rack-sequel-torquebox-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": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "sinatra-sequel",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "JRuby",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-sequel-postgres-torquebox-jruby",
+        "versus": "rack-sequel-postgres-torquebox-jruby",
+        "notes": ""
+      }
     }
     }
-  }]
+  ]
 }
 }

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

@@ -0,0 +1,82 @@
+---
+framework: sinatra-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: Realistic
+      classification: Micro
+      database: MySQL
+      framework: sinatra-sequel
+      language: Ruby
+      orm: Full
+      platform: Rack
+      webserver: Puma
+      os: Linux
+      database_os: Linux
+      display_name: sinatra-sequel-puma-mri
+      versus: rack-sequel-puma-mri
+      notes: ""
+    postgres:
+      <<: *default
+      database: Postgres
+      display_name: sinatra-sequel-postgres-puma-mri
+      versus: rack-sequel-postgres-puma-mri
+    puma-jruby:
+      <<: *default
+      setup_file: run_jruby_puma
+      platform: JRuby
+      display_name: sinatra-sequel-puma-jruby
+      versus: rack-sequel-puma-jruby
+    postgres-puma-jruby:
+      <<: *default
+      setup_file: run_jruby_puma
+      database: Postgres
+      platform: JRuby
+      display_name: sinatra-sequel-postgres-puma-jruby
+      versus: rack-sequel-postgres-puma-jruby
+    passenger-mri:
+      <<: *default
+      setup_file: run_mri_passenger
+      webserver: Passenger Standalone
+      display_name: sinatra-sequel-passenger-mri
+      versus: rack-sequel-passenger-mri
+    postgres-passenger-mri:
+      <<: *default
+      setup_file: run_mri_passenger
+      database: Postgres
+      webserver: Passenger Standalone
+      display_name: sinatra-sequel-postgres-passenger-mri
+      versus: rack-sequel-postgres-passenger-mri
+    unicorn-mri:
+      <<: *default
+      setup_file: run_mri_unicorn
+      webserver: Unicorn
+      display_name: sinatra-sequel-unicorn-mri
+      versus: rack-sequel-unicorn-mri
+    postgres-unicorn-mri:
+      <<: *default
+      setup_file: run_mri_unicorn
+      database: Postgres
+      webserver: Unicorn
+      display_name: sinatra-sequel-postgres-unicorn-mri
+      versus: rack-sequel-postgres-unicorn-mri
+    torquebox-jruby:
+      <<: *default
+      setup_file: run_jruby_torquebox
+      platform: JRuby
+      display_name: sinatra-sequel-torquebox-jruby
+      versus: rack-sequel-torquebox-jruby
+    postgres-torquebox-jruby:
+      <<: *default
+      setup_file: run_jruby_torquebox
+      database: Postgres
+      platform: JRuby
+      display_name: sinatra-sequel-postgres-torquebox-jruby
+      versus: rack-sequel-postgres-torquebox-jruby

+ 1 - 2
frameworks/Ruby/sinatra-sequel/config.ru

@@ -1,3 +1,2 @@
-# frozen_string_literal: true
 require_relative 'hello_world'
 require_relative 'hello_world'
-run HelloWorld
+run HelloWorld.new

+ 41 - 0
frameworks/Ruby/sinatra-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/sinatra-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/sinatra-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/sinatra-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

+ 3 - 22
frameworks/Ruby/sinatra-sequel/config/mri_puma.rb

@@ -1,26 +1,7 @@
-# Enable Puma's clustered mode with about one process per 128 MiB of available
-# memory, scaling up to as close to MAX_THREADS as possible. If there are fewer
-# processes than MAX_THREADS, add threads per process to reach MAX_THREADS.
-RESERVE_KB = 262_144
-KB_PER_WORKER = 131_072 # average of peak PSS of puma processes (watch smem -U testrunner -k)
-MIN_WORKERS = 2
-MIN_THREADS_PER_WORKER = 4
-MAX_THREADS = (ENV['MAX_CONCURRENCY'] || 256).to_i
+require_relative 'auto_tune'
 
 
-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
-end
-
-# FWBM only... use puma_auto_tune in production!
-avail_mem = meminfo('MemAvailable') - RESERVE_KB
-num_workers = [[avail_mem / KB_PER_WORKER, MIN_WORKERS].max, MAX_THREADS].min
-num_threads = num_workers < MAX_THREADS ? (1.0 * MAX_THREADS / num_workers).ceil : 0
-num_threads = [num_threads, MIN_THREADS_PER_WORKER].max
+# FWBM only... use the puma_auto_tune gem in production!
+num_workers, num_threads = auto_tune
 
 
 workers num_workers
 workers num_workers
 threads num_threads, num_threads
 threads num_threads, num_threads

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

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

+ 0 - 158
frameworks/Ruby/sinatra-sequel/config/nginx.conf

@@ -1,158 +0,0 @@
-# This is example contains the bare mininum to get nginx going with
-# Unicorn or Rainbows! servers.  Generally these configuration settings
-# are applicable to other HTTP application servers (and not just Ruby
-# ones), so if you have one working well for proxying another app
-# server, feel free to continue using it.
-#
-# The only setting we feel strongly about is the fail_timeout=0
-# directive in the "upstream" block.  max_fails=0 also has the same
-# effect as fail_timeout=0 for current versions of nginx and may be
-# used in its place.
-#
-# Users are strongly encouraged to refer to nginx documentation for more
-# details and search for other example configs.
-
-# you generally only need one nginx worker unless you're serving
-# large amounts of static files which require blocking disk reads
-worker_processes 8;
-
-# # drop privileges, root is needed on most systems for binding to port 80
-# # (or anything < 1024).  Capability-based security may be available for
-# # your system and worth checking out so you won't need to be root to
-# # start nginx to bind on 80
-# user nobody nogroup; # for systems with a "nogroup"
-#user nobody nobody; # for systems with "nobody" as a group instead
-
-# Feel free to change all paths to suite your needs here, of course
-pid /tmp/nginx.pid;
-#error_log /tmp/nginx.error.log;
-error_log /dev/null error;
-
-events {
-  worker_connections 4096; # increase if you have lots of clients
-  accept_mutex off; # "on" if nginx worker_processes > 1
-  use epoll; # enable for Linux 2.6+
-  # use kqueue; # enable for FreeBSD, OSX
-}
-
-http {
-  # nginx will find this file in the config directory set at nginx build time
-  include /usr/local/nginx/conf/mime.types;
-
-  # fallback in case we can't determine a type
-  default_type application/octet-stream;
-
-  # click tracking!
-  #access_log /tmp/nginx.access.log combined;
-  access_log off;
-
-  # you generally want to serve static files with nginx since neither
-  # Unicorn nor Rainbows! is optimized for it at the moment
-  sendfile on;
-
-  tcp_nopush on; # off may be better for *some* Comet/long-poll stuff
-  tcp_nodelay off; # on may be better for some Comet/long-poll stuff
-
-  # we haven't checked to see if Rack::Deflate on the app server is
-  # faster or not than doing compression via nginx.  It's easier
-  # to configure it all in one place here for static files and also
-  # to disable gzip for clients who don't get gzip/deflate right.
-  # There are other gzip settings that may be needed used to deal with
-  # bad clients out there, see http://wiki.nginx.org/NginxHttpGzipModule
-  #gzip on;
-  #gzip_http_version 1.0;
-  #gzip_proxied any;
-  #gzip_min_length 500;
-  #gzip_disable "MSIE [1-6]\.";
-  #gzip_types text/plain text/html text/xml text/css
-  #           text/comma-separated-values
-  #           text/javascript application/x-javascript
-  #           application/atom+xml;
-
-  # this can be any application server, not just Unicorn/Rainbows!
-  upstream app_server {
-    # fail_timeout=0 means we always retry an upstream even if it failed
-    # to return a good HTTP response (in case the Unicorn master nukes a
-    # single worker for timing out).
-
-    # for UNIX domain socket setups:
-    server unix:/tmp/.sock fail_timeout=0;
-
-    # for TCP setups, point these to your backend servers
-    # server 192.168.0.7:8080 fail_timeout=0;
-    # server 192.168.0.8:8080 fail_timeout=0;
-    # server 192.168.0.9:8080 fail_timeout=0;
-  }
-
-  server {
-    # enable one of the following if you're on Linux or FreeBSD
-    listen 8080 default deferred; # for Linux
-    # listen 80 default accept_filter=httpready; # for FreeBSD
-
-    # If you have IPv6, you'll likely want to have two separate listeners.
-    # One on IPv4 only (the default), and another on IPv6 only instead
-    # of a single dual-stack listener.  A dual-stack listener will make
-    # for ugly IPv4 addresses in $remote_addr (e.g ":ffff:10.0.0.1"
-    # instead of just "10.0.0.1") and potentially trigger bugs in
-    # some software.
-    # listen [::]:80 ipv6only=on; # deferred or accept_filter recommended
-
-    client_max_body_size 4G;
-    server_name _;
-
-    # ~2 seconds is often enough for most folks to parse HTML/CSS and
-    # retrieve needed images/icons/frames, connections are cheap in
-    # nginx so increasing this is generally safe...
-    keepalive_timeout 10;
-
-    # path for static files
-    root /path/to/app/current/public;
-
-    # Prefer to serve static files directly from nginx to avoid unnecessary
-    # data copies from the application server.
-    #
-    # try_files directive appeared in in nginx 0.7.27 and has stabilized
-    # over time.  Older versions of nginx (e.g. 0.6.x) requires
-    # "if (!-f $request_filename)" which was less efficient:
-    # http://bogomips.org/unicorn.git/tree/examples/nginx.conf?id=v3.3.1#n127
-    try_files $uri/index.html $uri.html $uri @app;
-
-    location @app {
-      # an HTTP header important enough to have its own Wikipedia entry:
-      #   http://en.wikipedia.org/wiki/X-Forwarded-For
-      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-
-      # enable this if you forward HTTPS traffic to unicorn,
-      # this helps Rack set the proper URL scheme for doing redirects:
-      # proxy_set_header X-Forwarded-Proto $scheme;
-
-      # pass the Host: header from the client right along so redirects
-      # can be set properly within the Rack application
-      proxy_set_header Host $http_host;
-
-      # we don't want nginx trying to do something clever with
-      # redirects, we set the Host: header above already.
-      proxy_redirect off;
-
-      # set "proxy_buffering off" *only* for Rainbows! when doing
-      # Comet/long-poll/streaming.  It's also safe to set if you're using
-      # only serving fast clients with Unicorn + nginx, but not slow
-      # clients.  You normally want nginx to buffer responses to slow
-      # clients, even with Rails 3.1 streaming because otherwise a slow
-      # client can become a bottleneck of Unicorn.
-      #
-      # The Rack application may also set "X-Accel-Buffering (yes|no)"
-      # in the response headers do disable/enable buffering on a
-      # per-response basis.
-      # proxy_buffering off;
-
-      proxy_pass http://app_server;
-    }
-
-    # Rails error pages
-    error_page 500 502 503 504 /500.html;
-    location = /500.html {
-      root /path/to/app/current/public;
-    }
-  }
-}

+ 44 - 18
frameworks/Ruby/sinatra-sequel/hello_world.rb

@@ -1,4 +1,5 @@
 # frozen_string_literal: true
 # frozen_string_literal: true
+require 'bundler'
 require 'time'
 require 'time'
 
 
 MAX_PK = 10_000
 MAX_PK = 10_000
@@ -16,6 +17,19 @@ def connect(dbtype)
     :postgresql=>{ :jruby=>'jdbc:postgresql', :mri=>'postgres' }
     :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 \
   Sequel.connect \
     '%<adapter>s://%<host>s/%<database>s?user=%<user>s&password=%<password>s' % {
     '%<adapter>s://%<host>s/%<database>s?user=%<user>s&password=%<password>s' % {
       :adapter=>adapters.fetch(dbtype).fetch(defined?(JRUBY_VERSION) ? :jruby : :mri),
       :adapter=>adapters.fetch(dbtype).fetch(defined?(JRUBY_VERSION) ? :jruby : :mri),
@@ -23,12 +37,13 @@ def connect(dbtype)
       :database=>'hello_world',
       :database=>'hello_world',
       :user=>'benchmarkdbuser',
       :user=>'benchmarkdbuser',
       :password=>'benchmarkdbpass'
       :password=>'benchmarkdbpass'
-    },
-    :max_connections=>Puma.cli_config.options.fetch(:max_threads),
-    :pool_timeout=>10
+    }, opts
 end
 end
 
 
-DB = connect ENV.fetch('DBTYPE').to_sym
+DB = connect(ENV.fetch('DBTYPE').to_sym).tap do |db|
+  db.extension(:freeze_datasets)
+  db.freeze
+end
 
 
 # Define ORM models
 # Define ORM models
 class World < Sequel::Model(:World)
 class World < Sequel::Model(:World)
@@ -57,6 +72,18 @@ class HelloWorld < Sinatra::Base
 
 
     # Only add the charset parameter to specific content types per the requirements
     # Only add the charset parameter to specific content types per the requirements
     set :add_charset, [mime_type(:html)]
     set :add_charset, [mime_type(:html)]
+
+    set :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
   end
   end
 
 
   helpers do
   helpers do
@@ -84,11 +111,13 @@ class HelloWorld < Sinatra::Base
   end
   end
 
 
   after do
   after do
-    # Add mandatory HTTP headers to every response
-    response['Server'] ||= Puma::Const::PUMA_SERVER_STRING
-    response['Date'] ||= Time.now.httpdate
+    response['Date'] = Time.now.httpdate
   end
   end
 
 
+  after do
+    response['Server'] = settings.server_string
+  end if server_string
+
   # Test type 1: JSON serialization
   # Test type 1: JSON serialization
   get '/json' do
   get '/json' do
      json :message=>'Hello, World!'
      json :message=>'Hello, World!'
@@ -96,15 +125,14 @@ class HelloWorld < Sinatra::Base
 
 
   # Test type 2: Single database query
   # Test type 2: Single database query
   get '/db' do
   get '/db' do
-    json World[rand1].values
+    json World.with_pk(rand1).values
   end
   end
 
 
   # Test type 3: Multiple database queries
   # Test type 3: Multiple database queries
   get '/queries' do
   get '/queries' do
     # Benchmark requirements explicitly forbid a WHERE..IN here, so be good
     # Benchmark requirements explicitly forbid a WHERE..IN here, so be good
-    worlds = randn(bounded_queries).map! { |id| World[id] }
-
-    json worlds.map!(&:values)
+    json randn(bounded_queries)
+      .map! { |id| World.with_pk(id).values }
   end
   end
 
 
   # Test type 4: Fortunes
   # Test type 4: Fortunes
@@ -124,15 +152,13 @@ class HelloWorld < Sinatra::Base
     # Benchmark requirements explicitly forbid a WHERE..IN here, transactions
     # Benchmark requirements explicitly forbid a WHERE..IN here, transactions
     # are optional, batch updates are allowed (but each transaction can only
     # are optional, batch updates are allowed (but each transaction can only
     # read and write a single record?), so... be good
     # read and write a single record?), so... be good
-    worlds = randn(bounded_queries).map! do |id|
+    json(randn(bounded_queries).map! do |id|
       DB.transaction do
       DB.transaction do
-        world = World.for_update[id]
-        world.update :randomnumber=>rand1
-        world
+        world = World.for_update.with_pk(id)
+        world.update(:randomnumber=>rand1)
+        world.values
       end
       end
-    end
-
-    json worlds.map!(&:values)
+    end)
   end
   end
 
 
   # Test type 6: Plaintext
   # Test type 6: Plaintext

+ 0 - 0
frameworks/Ruby/sinatra-sequel/public/.gitkeep


+ 0 - 1
frameworks/Ruby/sinatra-sequel/public/500.html

@@ -1 +0,0 @@
- 

+ 6 - 11
frameworks/Ruby/sinatra-sequel/run_jruby_puma.sh

@@ -1,18 +1,13 @@
 #!/bin/bash
 #!/bin/bash
 
 
-if [[ $LOGDIR == *postgres* ]] ; then
-  DBTYPE=postgresql
-else
-  DBTYPE=mysql
-fi
+THREAD_FACTOR=1
+
+. $(dirname $0)/config/common_run.sh
 
 
 fw_depends $DBTYPE rvm jruby-9.1
 fw_depends $DBTYPE rvm jruby-9.1
 
 
-rvm jruby-$JRUBY_VERSION do \
-  bundle install --jobs=4 --gemfile=$TROOT/Gemfile --path=vendor/bundle
+rvm use jruby-$JRUBY_VERSION
 
 
-export DBTYPE
-export JRUBY_OPTS="-J-Xmn512m -J-Xms2048m -J-Xmx2048m -J-server -J-XX:+UseSerialGC -J-Djava.net.preferIPv4Stack=true"
+. $(dirname $0)/config/bundle_install.sh
 
 
-rvm jruby-$JRUBY_VERSION do \
-  bundle exec puma -t $MAX_CONCURRENCY:$MAX_CONCURRENCY -b tcp://0.0.0.0:8080 -e production &
+bundle exec puma -t $MAX_CONCURRENCY:$MAX_CONCURRENCY -b tcp://0.0.0.0:8080 -e production &

+ 13 - 0
frameworks/Ruby/sinatra-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/sinatra-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 &

+ 4 - 11
frameworks/Ruby/sinatra-sequel/run_mri_puma.sh

@@ -1,18 +1,11 @@
 #!/bin/bash
 #!/bin/bash
 
 
-if [[ $LOGDIR == *postgres* ]] ; then
-  DBTYPE=postgresql
-else
-  DBTYPE=mysql
-fi
+. $(dirname $0)/config/common_run.sh
 
 
 fw_depends $DBTYPE rvm ruby-2.4
 fw_depends $DBTYPE rvm ruby-2.4
 
 
-rvm ruby-$MRI_VERSION do \
-  bundle install --jobs=4 --gemfile=$TROOT/Gemfile --path=vendor/bundle
+rvm use ruby-$MRI_VERSION
 
 
-export DBTYPE
-export MAX_CONCURRENCY # used by config/mri_puma.rb
+. $(dirname $0)/config/bundle_install.sh
 
 
-rvm ruby-$MRI_VERSION do \
-  bundle exec puma -C config/mri_puma.rb -b tcp://0.0.0.0:8080 -e production &
+bundle exec puma -C config/mri_puma.rb -b tcp://0.0.0.0:8080 -e production &

+ 11 - 0
frameworks/Ruby/sinatra-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 &