Răsfoiți Sursa

Bring sinatra into alignment with other Ruby frameworks (#2523)

[ci fw-only Ruby/sinatra]
Mike Pastore 8 ani în urmă
părinte
comite
2152e95627
36 a modificat fișierele cu 630 adăugiri și 504 ștergeri
  1. 3 2
      frameworks/Ruby/sinatra/.gitignore
  2. 18 20
      frameworks/Ruby/sinatra/Gemfile
  3. 0 88
      frameworks/Ruby/sinatra/Gemfile.lock
  4. 2 0
      frameworks/Ruby/sinatra/Makefile
  5. 23 17
      frameworks/Ruby/sinatra/README.md
  6. 142 71
      frameworks/Ruby/sinatra/benchmark_config.json
  7. 8 0
      frameworks/Ruby/sinatra/benchmark_config.rb
  8. 86 0
      frameworks/Ruby/sinatra/benchmark_config.yaml
  9. 64 0
      frameworks/Ruby/sinatra/boot.rb
  10. 3 2
      frameworks/Ruby/sinatra/config.ru
  11. 41 0
      frameworks/Ruby/sinatra/config/auto_tune.rb
  12. 6 0
      frameworks/Ruby/sinatra/config/bundle_install.sh
  13. 13 0
      frameworks/Ruby/sinatra/config/common_run.sh
  14. 24 0
      frameworks/Ruby/sinatra/config/java_tune.sh
  15. 7 0
      frameworks/Ruby/sinatra/config/mri_puma.rb
  16. 6 0
      frameworks/Ruby/sinatra/config/mri_unicorn.rb
  17. 0 158
      frameworks/Ruby/sinatra/config/nginx.conf
  18. 0 3
      frameworks/Ruby/sinatra/config/puma.rb
  19. 0 10
      frameworks/Ruby/sinatra/config/thin.yml
  20. 0 8
      frameworks/Ruby/sinatra/config/trinidad.yml
  21. 0 7
      frameworks/Ruby/sinatra/config/unicorn.rb
  22. 94 81
      frameworks/Ruby/sinatra/hello_world.rb
  23. 0 0
      frameworks/Ruby/sinatra/public/.gitkeep
  24. 0 1
      frameworks/Ruby/sinatra/public/500.html
  25. 13 0
      frameworks/Ruby/sinatra/run_jruby_puma.sh
  26. 13 0
      frameworks/Ruby/sinatra/run_jruby_torquebox.sh
  27. 20 0
      frameworks/Ruby/sinatra/run_mri_passenger.sh
  28. 7 3
      frameworks/Ruby/sinatra/run_mri_puma.sh
  29. 11 0
      frameworks/Ruby/sinatra/run_mri_unicorn.sh
  30. 0 7
      frameworks/Ruby/sinatra/run_thin.sh
  31. 0 11
      frameworks/Ruby/sinatra/run_unicorn.sh
  32. 3 1
      frameworks/Ruby/sinatra/source_code
  33. 12 0
      frameworks/Ruby/sinatra/views/fortunes.erb
  34. 0 8
      frameworks/Ruby/sinatra/views/fortunes.slim
  35. 11 0
      frameworks/Ruby/sinatra/views/layout.erb
  36. 0 6
      frameworks/Ruby/sinatra/views/layout.slim

+ 3 - 2
frameworks/Ruby/sinatra/.gitignore

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

+ 18 - 20
frameworks/Ruby/sinatra/Gemfile

@@ -1,24 +1,22 @@
-source 'http://rubygems.org'
+source 'https://rubygems.org'
 
-platforms :jruby do
-  gem "activerecord-jdbcmysql-adapter", "~> 1.3.9", :require => false
-  gem 'torqbox', '0.1.7'
-  gem "trinidad", '1.4.6'
-end
+gem 'activerecord', '~> 5.0', :require=>'active_record'
+gem 'activerecord-jdbc-adapter', '>= 5.0.pre1', '< 6.0', :platforms=>:jruby
+gem 'json', '~> 2.0'
+gem 'passenger', '~> 5.1', :platforms=>[:ruby, :mswin], :require=>false
+gem 'puma', '~> 3.6', :require=>false
+gem 'sinatra', '>= 2.0.0.beta2', '< 3.0', :require=>'sinatra/base',
+  :git=>'https://github.com/sinatra/sinatra.git', :branch=>'master'
+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
 
-platforms :ruby do
-  gem 'mysql2', '0.3.16'
-  gem "unicorn", '4.8.3'
-  gem "thin", '1.6.2'
+group :mysql do
+  gem 'jdbc-mysql', '~> 5.1', :platforms=>:jruby, :require=>'jdbc/mysql'
+  gem 'mysql2', '~> 0.4', :platforms=>[:ruby, :mswin]
 end
 
-gem "puma", '2.9.0'
-
-gem "activerecord-import", '0.5.0'
-gem 'activerecord', '4.1.4', :require => 'active_record'
-
-gem "sinatra-activerecord", '2.0.2'
-gem 'sinatra', '1.4.5'
-gem 'sinatra-contrib', '1.4.2'
-
-gem 'slim', '2.0.3'
+group :postgresql do
+  gem 'jdbc-postgres', '~> 9.4', :platforms=>:jruby, :require=>'jdbc/postgres'
+  gem 'pg', '~> 0.19', :platforms=>[:ruby, :mswin]
+end

+ 0 - 88
frameworks/Ruby/sinatra/Gemfile.lock

@@ -1,88 +0,0 @@
-GEM
-  remote: http://rubygems.org/
-  specs:
-    activemodel (4.1.4)
-      activesupport (= 4.1.4)
-      builder (~> 3.1)
-    activerecord (4.1.4)
-      activemodel (= 4.1.4)
-      activesupport (= 4.1.4)
-      arel (~> 5.0.0)
-    activerecord-import (0.5.0)
-      activerecord (>= 3.0)
-    activesupport (4.1.4)
-      i18n (~> 0.6, >= 0.6.9)
-      json (~> 1.7, >= 1.7.7)
-      minitest (~> 5.1)
-      thread_safe (~> 0.1)
-      tzinfo (~> 1.1)
-    arel (5.0.1.20140414130214)
-    backports (3.6.8)
-    builder (3.2.2)
-    daemons (1.2.4)
-    eventmachine (1.2.0.1)
-    i18n (0.7.0)
-    json (1.8.3)
-    kgio (2.10.0)
-    minitest (5.9.0)
-    multi_json (1.12.1)
-    mysql2 (0.3.16)
-    puma (2.9.0)
-      rack (>= 1.1, < 2.0)
-    rack (1.6.4)
-    rack-protection (1.5.3)
-      rack
-    rack-test (0.6.3)
-      rack (>= 1.0)
-    raindrops (0.17.0)
-    sinatra (1.4.5)
-      rack (~> 1.4)
-      rack-protection (~> 1.4)
-      tilt (~> 1.3, >= 1.3.4)
-    sinatra-activerecord (2.0.2)
-      activerecord (>= 3.2)
-      sinatra (~> 1.0)
-    sinatra-contrib (1.4.2)
-      backports (>= 2.0)
-      multi_json
-      rack-protection
-      rack-test
-      sinatra (~> 1.4.0)
-      tilt (~> 1.3)
-    slim (2.0.3)
-      temple (~> 0.6.6)
-      tilt (>= 1.3.3, < 2.1)
-    temple (0.6.10)
-    thin (1.6.2)
-      daemons (>= 1.0.9)
-      eventmachine (>= 1.0.0)
-      rack (>= 1.0.0)
-    thread_safe (0.3.5)
-    tilt (1.4.1)
-    tzinfo (1.2.2)
-      thread_safe (~> 0.1)
-    unicorn (4.8.3)
-      kgio (~> 2.6)
-      rack
-      raindrops (~> 0.7)
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  activerecord (= 4.1.4)
-  activerecord-import (= 0.5.0)
-  activerecord-jdbcmysql-adapter (~> 1.3.9)
-  mysql2 (= 0.3.16)
-  puma (= 2.9.0)
-  sinatra (= 1.4.5)
-  sinatra-activerecord (= 2.0.2)
-  sinatra-contrib (= 1.4.2)
-  slim (= 2.0.3)
-  thin (= 1.6.2)
-  torqbox (= 0.1.7)
-  trinidad (= 1.4.6)
-  unicorn (= 4.8.3)
-
-BUNDLED WITH
-   1.13.1

+ 2 - 0
frameworks/Ruby/sinatra/Makefile

@@ -0,0 +1,2 @@
+json: benchmark_config.yaml
+	ruby benchmark_config.rb benchmark_config.yaml >benchmark_config.json

+ 23 - 17
frameworks/Ruby/sinatra/README.md

@@ -1,24 +1,30 @@
-# Ruby [Sinatra](http://www.sinatrarb.com/) Benchmarking Test
+# Ruby [Sinatra](http://www.sinatrarb.com) Benchmarking Test
 
-The information below contains information specific to Sinatra. 
-For further guidance, review the 
-[documentation](http://frameworkbenchmarks.readthedocs.org/en/latest/). 
-Also note the additional information provided in the [Ruby README](../).
+The information below contains information specific to the Sinatra 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 Sinatra portion of a [benchmarking test suite](../../) 
+This is the Ruby Sinatra portion of a [benchmarking test suite](../../)
 comparing a variety of web platforms.
 
 ## Infrastructure Software Versions
-The tests were run with:
-* [Ruby 2.0.0-p0](http://www.ruby-lang.org/)
-* [JRuby 1.7.8](http://jruby.org/)
-* [Rubinius 2.2.10](http://rubini.us/)
-* [Sinatra 1.3.4](http://www.sinatrarb.com/)
-* [Unicorn 4.6.2](http://unicorn.bogomips.org/)
-* [TorqBox 0.1.7](http://torquebox.org/torqbox/)
-* [Puma 2.9.0](http://puma.io/)
-* [Thin 1.6.2](http://code.macournoyer.com/thin/)
-* [MySQL 5.5.29](https://dev.mysql.com/)
+
+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)\*
+* [Sinatra 2.0](http://www.sinatrarb.com)
+* [ActiveRecord 5.0](https://github.com/rails/rails/tree/master/activerecord)
+* [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
 
@@ -39,8 +45,8 @@ _No experts listed, yet. If you're an expert, add yourself!_
 
 * [Sinatra Google Group](https://groups.google.com/forum/#!forum/sinatrarb)
 * `#sinatra` IRC Channel ([irc.freenode.net](http://freenode.net/))
+* `#sinatrarb` on the [Sinatra and Friends](http://sinatra-slack.herokuapp.com) Slack
 
 ### Resources
 
 * [Sinatra Source Code](https://github.com/sinatra/sinatra)
-* [PR: passenger-install-apache2-module doesn't work on ruby 2.0](https://github.com/FooBarWidget/passenger/pull/71)

+ 142 - 71
frameworks/Ruby/sinatra/benchmark_config.json

@@ -1,74 +1,145 @@
 {
   "framework": "sinatra",
-  "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",
-      "language": "Ruby",
-      "orm": "Full",
-      "platform": "Rack",
-      "webserver": "Puma",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "sinatra-puma-mri",
-      "notes": "",
-      "versus": "rack-puma-mri"
-    },
-    "thin": {
-      "setup_file": "run_thin",
-      "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",
-      "language": "Ruby",
-      "orm": "Full",
-      "platform": "Rack",
-      "webserver": "Thin",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "sinatra-thin",
-      "notes": "",
-      "versus": "rack-thin"
-    },
-    "unicorn": {
-      "setup_file": "run_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",
-      "language": "Ruby",
-      "orm": "Full",
-      "platform": "Rack",
-      "webserver": "Unicorn",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "sinatra-unicorn",
-      "notes": "",
-      "versus": "rack-unicorn"
-    }    
-  }]
+  "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",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-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": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "sinatra",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Puma",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-postgres-puma-mri",
+        "versus": "rack-postgres-puma-mri",
+        "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",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Passenger Standalone",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-passenger-mri",
+        "versus": "rack-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",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Passenger Standalone",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-postgres-passenger-mri",
+        "versus": "rack-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",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Unicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-unicorn-mri",
+        "versus": "rack-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",
+        "language": "Ruby",
+        "orm": "Full",
+        "platform": "Rack",
+        "webserver": "Unicorn",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "sinatra-postgres-unicorn-mri",
+        "versus": "rack-postgres-unicorn-mri",
+        "notes": ""
+      }
+    }
+  ]
 }

+ 8 - 0
frameworks/Ruby/sinatra/benchmark_config.rb

@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require 'json'
+require 'yaml'
+
+yaml = YAML.load(ARGF.read)
+yaml["tests"][0].delete_if { |_, v| v["disabled"] }
+puts JSON.pretty_generate(yaml)

+ 86 - 0
frameworks/Ruby/sinatra/benchmark_config.yaml

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

+ 64 - 0
frameworks/Ruby/sinatra/boot.rb

@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+require 'bundler'
+require 'time'
+
+MAX_PK = 10_000
+QUERIES_MIN = 1
+QUERIES_MAX = 500
+
+Bundler.require(:default) # Load core modules
+
+def connect(dbtype)
+  Bundler.require(dbtype) # Load database-specific modules
+
+  opts = {
+    :adapter=>(dbtype == :mysql ? 'mysql2' : 'postgresql'),
+    :username=>'benchmarkdbuser',
+    :password=>'benchmarkdbpass',
+    :host=>ENV.fetch('DBHOST', '127.0.0.1'),
+    :database=>'hello_world'
+  }
+
+  # Determine threading/thread pool size and timeout
+  if defined?(JRUBY_VERSION)
+    opts[:pool] = Integer(ENV.fetch('MAX_CONCURRENCY'))
+    opts[:check_timeout] = 10
+  elsif defined?(Puma)
+    opts[:pool] = Puma.cli_config.options.fetch(:max_threads)
+    opts[:check_timeout] = 10
+  else
+    # TODO: ActiveRecord doesn't have a single-threaded mode?
+    opts[:pool] = 1
+    opts[:check_timeout] = 0
+  end
+
+  ActiveRecord::Base.establish_connection(opts)
+end
+
+connect ENV.fetch('DBTYPE').to_sym
+
+# Define ORM models
+class World < ActiveRecord::Base
+  self.table_name = name
+
+  alias_attribute(:randomnumber, :randomNumber) \
+    if ActiveRecord::Base.connection.adapter_name.downcase.start_with?('mysql')
+end
+
+class Fortune < ActiveRecord::Base
+  self.table_name = name
+end
+
+ActiveRecord::Base.clear_active_connections!
+
+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

+ 3 - 2
frameworks/Ruby/sinatra/config.ru

@@ -1,2 +1,3 @@
-require './hello_world'
-run Sinatra::Application
+require_relative 'boot'
+require_relative 'hello_world'
+run HelloWorld.new

+ 41 - 0
frameworks/Ruby/sinatra/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 = 96 * 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/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/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/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/sinatra/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/sinatra/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/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;
-    }
-  }
-}

+ 0 - 3
frameworks/Ruby/sinatra/config/puma.rb

@@ -1,3 +0,0 @@
-environment 'production'
-threads 8, 32
-bind 'tcp://0.0.0.0:8080'

+ 0 - 10
frameworks/Ruby/sinatra/config/thin.yml

@@ -1,10 +0,0 @@
---- 
-timeout: 30
-wait: 30
-max_conns: 1024
-max_persistent_conns: 512
-environment: production
-port: 8080
-servers: 8
-log: /tmp/thin.log
-quiet: true

+ 0 - 8
frameworks/Ruby/sinatra/config/trinidad.yml

@@ -1,8 +0,0 @@
----
-  port: 8080
-  threadsafe: true
-  environment: production
-  http:
-    address: '*'
-  logging:
-    level: SEVERE

+ 0 - 7
frameworks/Ruby/sinatra/config/unicorn.rb

@@ -1,7 +0,0 @@
-worker_processes 8
-listen "/tmp/.sock", :backlog => 256
-preload_app true
-GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true
-
-before_fork { |server, worker| }
-after_fork { |server, worker| }

+ 94 - 81
frameworks/Ruby/sinatra/hello_world.rb

@@ -1,97 +1,110 @@
-require 'active_record'
-Bundler.require :default
-
-set :logging, false
-ActiveRecord::Base.logger = nil
-set :activerecord_logger, nil
-set :static, false
-set :template_engine, :slim
-Slim::Engine.set_default_options format: :html5, sort_attrs: false
-
-# Specify the encoder - otherwise, sinatra/json inefficiently
-# attempts to load one of several on each request
-set :json_encoder => :to_json
-
-# Don't prefix JSON results with { "world": {...} }
-ActiveRecord::Base.include_root_in_json = false
-
-db_config = { :database => 'hello_world', :username => 'benchmarkdbuser', :password => 'benchmarkdbpass', :pool => 256, :timeout => 5000 }
-adapter = RUBY_PLATFORM == 'java' ? 'jdbcmysql' : 'mysql2'
-set :database, db_config.merge(:adapter => adapter, :host => ENV['DB_HOST'])
-
-# The sinatra-activerecord gem registers before and after filters that
-# call expensive synchronized ActiveRecord methods on every request to
-# verify every connection in the pool, even for routes that don't use
-# the database. Clear those filters and handle connection management
-# ourselves, which is what applications seeking high throughput with
-# ActiveRecord need to do anyway.
-settings.filters[:before].clear
-settings.filters[:after].clear
-
-after do
-  # Add mandatory HTTP headers to every response
-  response['Server'] ||= ENV['WEB_SERVER'].freeze
-  response['Date'] ||= Time.now.to_s
-end
+# frozen_string_literal: true
 
-class World < ActiveRecord::Base
-  self.table_name = "World"
-end
+# Our Rack application to be executed by rackup
+class HelloWorld < Sinatra::Base
+  configure do
+    # Static file serving is ostensibly disabled in modular mode but Sinatra
+    # still calls an expensive Proc on every request...
+    disable :static
 
-class Fortune < ActiveRecord::Base
-  self.table_name = "Fortune"
-end
+    # XSS, CSRF, IP spoofing, etc. protection are not explicitly required
+    disable :protection
 
-get '/json' do
-  json :message => 'Hello, World!'
-end
+    # Only add the charset parameter to specific content types per the requirements
+    set :add_charset, [mime_type(:html)]
+  end
 
-get '/plaintext' do
-  response['Content-type'] = 'text/plain'
-  'Hello, World!'
-end
+  helpers do
+    def bounded_queries
+      queries = params[:queries].to_i
+      return QUERIES_MIN if queries < QUERIES_MIN
+      return QUERIES_MAX if queries > QUERIES_MAX
+      queries
+    end
 
-get '/db' do
-  worlds = ActiveRecord::Base.connection_pool.with_connection do
-    World.find(Random.rand(10000) + 1)
+    def json(data)
+      content_type :json
+      JSON.fast_generate(data)
+    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
+  end
+
+  after do
+    response['Date'] = Time.now.httpdate
   end
-  json(worlds)
-end
 
-get '/queries' do
-  queries = (params[:queries] || 1).to_i
-  queries = 1 if queries < 1
-  queries = 500 if queries > 500
+  after do
+    response['Server'] = SERVER_STRING
+  end if SERVER_STRING
 
-  worlds = ActiveRecord::Base.connection_pool.with_connection do
-    (1..queries).map do
-      World.find(Random.rand(10000) + 1)
+  # Test type 1: JSON serialization
+  get '/json' do
+     json :message=>'Hello, World!'
+  end
+
+  # Test type 2: Single database query
+  get '/db' do
+    world = ActiveRecord::Base.connection_pool.with_connection do
+      World.find(rand1)
     end
+
+    json world.attributes
   end
-  json(worlds)
-end
 
-get '/fortunes' do
-  @fortunes = Fortune.all
-  @fortunes << Fortune.new(:id => 0, :message => "Additional fortune added at request time.")
-  @fortunes = @fortunes.sort_by { |x| x.message }
+  # Test type 3: Multiple database queries
+  get '/queries' do
+    worlds = ActiveRecord::Base.connection_pool.with_connection do
+      # Benchmark requirements explicitly forbid a WHERE..IN here, so be good
+      randn(bounded_queries).map! { |id| World.find(id) }
+    end
 
-  slim :fortunes
-end
+    json worlds.map!(&:attributes)
+  end
 
-get '/updates' do
-  queries = (params[:queries] || 1).to_i
-  queries = 1 if queries < 1
-  queries = 500 if queries > 500
-
-  worlds = ActiveRecord::Base.connection_pool.with_connection do
-    worlds = (1..queries).map do
-      world = World.find(Random.rand(10000) + 1)
-      world.randomNumber = Random.rand(10000) + 1
-      World.update(world.id, :randomNumber => world.randomNumber)
-      world
+  # Test type 4: Fortunes
+  get '/fortunes' do
+    @fortunes = ActiveRecord::Base.connection_pool.with_connection do
+      Fortune.all.to_a
     end
-    worlds
+    @fortunes << Fortune.new(
+      :id=>0,
+      :message=>'Additional fortune added at request time.'
+    )
+    @fortunes.sort_by!(&:message)
+
+    erb :fortunes, :layout=>true
+  end
+
+  # Test type 5: Database updates
+  get '/updates' do
+    worlds = ActiveRecord::Base.connection_pool.with_connection do |conn|
+      # 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).map! do |id|
+        conn.transaction do
+          world = World.lock.find(id)
+          world.update(:randomnumber=>rand1)
+          world
+        end
+      end
+    end
+
+    json worlds.map!(&:attributes)
+  end
+
+  # Test type 6: Plaintext
+  get '/plaintext' do
+    content_type :text
+    'Hello, World!'
   end
-  json(worlds)
 end

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


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

@@ -1 +0,0 @@
- 

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

+ 7 - 3
frameworks/Ruby/sinatra/run_mri_puma.sh

@@ -1,7 +1,11 @@
 #!/bin/bash
 
-fw_depends mysql rvm ruby-2.0
+. $(dirname $0)/config/common_run.sh
 
-rvm ruby-$MRI_VERSION do bundle install --jobs=4 --gemfile=$TROOT/Gemfile --path=vendor/bundle
+fw_depends $DBTYPE rvm ruby-2.4
 
-WEB_SERVER=Puma DB_HOST=${DBHOST} rvm ruby-$MRI_VERSION do bundle exec puma -C config/puma.rb -w 8 --preload &
+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/sinatra/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 &

+ 0 - 7
frameworks/Ruby/sinatra/run_thin.sh

@@ -1,7 +0,0 @@
-#!/bin/bash
-
-fw_depends mysql rvm ruby-2.0
-
-rvm ruby-$MRI_VERSION do bundle install --jobs=4 --gemfile=$TROOT/Gemfile --path=vendor/bundle
-
-WEB_SERVER=Thin DB_HOST=${DBHOST} rvm ruby-$MRI_VERSION do bundle exec thin start -C config/thin.yml &

+ 0 - 11
frameworks/Ruby/sinatra/run_unicorn.sh

@@ -1,11 +0,0 @@
-#!/bin/bash
-
-fw_depends mysql rvm ruby-2.0 nginx
-
-sed -i 's|/usr/local/nginx/|'"${IROOT}"'/nginx/|g' config/nginx.conf
-
-rvm ruby-$MRI_VERSION do bundle install --jobs=4 --gemfile=$TROOT/Gemfile --path=vendor/bundle
-
-nginx -c $TROOT/config/nginx.conf
-
-WEB_SERVER=Unicorn DB_HOST=${DBHOST} rvm ruby-$MRI_VERSION do bundle exec unicorn -E production -c config/unicorn.rb &

+ 3 - 1
frameworks/Ruby/sinatra/source_code

@@ -1 +1,3 @@
-./hello_world.rb
+./sinatra/config.ru
+./sinatra/boot.rb
+./sinatra/hello_world.rb

+ 12 - 0
frameworks/Ruby/sinatra/views/fortunes.erb

@@ -0,0 +1,12 @@
+<table>
+<tr>
+  <th>id</th>
+  <th>message</th>
+</tr>
+<% @fortunes.each do |fortune| %>
+<tr>
+  <td><%= Rack::Utils.escape_html(fortune.id) %></td>
+  <td><%= Rack::Utils.escape_html(fortune.message) %></td>
+</tr>
+<% end %>
+</table>

+ 0 - 8
frameworks/Ruby/sinatra/views/fortunes.slim

@@ -1,8 +0,0 @@
-table
-  tr
-    th id
-    th message
-  - for fortune in @fortunes
-    tr
-      td =fortune.id
-      td =fortune.message

+ 11 - 0
frameworks/Ruby/sinatra/views/layout.erb

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Fortunes</title>
+</head>
+
+<body>
+<%= yield %>
+</body>
+
+</html>

+ 0 - 6
frameworks/Ruby/sinatra/views/layout.slim

@@ -1,6 +0,0 @@
-doctype html
-html
-  head
-    title Fortunes
-  body
-    == yield