Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions frameworks/sinatra/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM ruby:3.4-slim

RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential libsqlite3-dev && \
rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY Gemfile .
RUN bundle install --jobs=$(nproc)

COPY . .

EXPOSE 8080

CMD ["bundle", "exec", "puma", "-C", "puma.rb"]
6 changes: 6 additions & 0 deletions frameworks/sinatra/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source 'https://rubygems.org'

gem 'sinatra', '~> 4.1'
gem 'puma', '~> 6.5'
gem 'sqlite3', '~> 2.6'
gem 'json'
142 changes: 142 additions & 0 deletions frameworks/sinatra/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
require 'sinatra/base'
require 'json'
require 'zlib'
require 'stringio'
require 'sqlite3'

class App < Sinatra::Base
configure do
set :server, :puma
set :logging, false
set :show_exceptions, false

# Load dataset
dataset_path = ENV.fetch('DATASET_PATH', '/data/dataset.json')
if File.exist?(dataset_path)
raw = JSON.parse(File.read(dataset_path))
items = raw.map do |d|
d.merge('total' => (d['price'] * d['quantity'] * 100).round / 100.0)
end
set :dataset_items, raw
set :json_payload, JSON.generate({ 'items' => items, 'count' => items.length })
else
set :dataset_items, nil
set :json_payload, nil
end

# Large dataset for compression
large_path = '/data/dataset-large.json'
if File.exist?(large_path)
raw = JSON.parse(File.read(large_path))
items = raw.map do |d|
d.merge('total' => (d['price'] * d['quantity'] * 100).round / 100.0)
end
payload = JSON.generate({ 'items' => items, 'count' => items.length })
# Pre-compress with gzip level 1
sio = StringIO.new
gz = Zlib::GzipWriter.new(sio, 1)
gz.write(payload)
gz.close
set :compressed_payload, sio.string
else
set :compressed_payload, nil
end

# SQLite
set :db_available, File.exist?('/data/benchmark.db')
end

DB_QUERY = 'SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN ? AND ? LIMIT 50'

helpers do
def get_db
Thread.current[:sinatra_db] ||= begin
db = SQLite3::Database.new('/data/benchmark.db', readonly: true)
db.execute('PRAGMA mmap_size=268435456')
db.results_as_hash = true
db
end
end
end

get '/pipeline' do
content_type 'text/plain'
headers 'Server' => 'sinatra'
'ok'
end

def handle_baseline11
total = 0
request.GET.each do |_k, v|
total += v.to_i if v =~ /\A-?\d+\z/
end
if request.post?
request.body.rewind
body_str = request.body.read.strip
total += body_str.to_i if body_str =~ /\A-?\d+\z/
end
content_type 'text/plain'
headers 'Server' => 'sinatra'
total.to_s
end

get('/baseline11') { handle_baseline11 }
post('/baseline11') { handle_baseline11 }

get '/baseline2' do
total = 0
request.GET.each do |_k, v|
total += v.to_i if v =~ /\A-?\d+\z/
end
content_type 'text/plain'
headers 'Server' => 'sinatra'
total.to_s
end

get '/json' do
payload = settings.json_payload
halt 500, 'No dataset' unless payload
content_type 'application/json'
headers 'Server' => 'sinatra'
payload
end

get '/compression' do
compressed = settings.compressed_payload
halt 500, 'No dataset' unless compressed
content_type 'application/json'
headers 'Content-Encoding' => 'gzip', 'Server' => 'sinatra'
compressed
end

get '/db' do
unless settings.db_available
content_type 'application/json'
headers 'Server' => 'sinatra'
return '{"items":[],"count":0}'
end
min_val = (params['min'] || 10).to_f
max_val = (params['max'] || 50).to_f
db = get_db
rows = db.execute(DB_QUERY, [min_val, max_val])
items = rows.map do |r|
{
'id' => r['id'], 'name' => r['name'], 'category' => r['category'],
'price' => r['price'], 'quantity' => r['quantity'], 'active' => r['active'] == 1,
'tags' => JSON.parse(r['tags']),
'rating' => { 'score' => r['rating_score'], 'count' => r['rating_count'] }
}
end
content_type 'application/json'
headers 'Server' => 'sinatra'
JSON.generate({ 'items' => items, 'count' => items.length })
end

post '/upload' do
request.body.rewind
data = request.body.read
content_type 'text/plain'
headers 'Server' => 'sinatra'
data.bytesize.to_s
end
end
21 changes: 21 additions & 0 deletions frameworks/sinatra/config.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require_relative 'app'

# Rack middleware to handle unknown HTTP methods before Puma/Sinatra
class MethodGuard
KNOWN = %w[GET POST PUT DELETE PATCH HEAD OPTIONS TRACE CONNECT].freeze

def initialize(app)
@app = app
end

def call(env)
if KNOWN.include?(env['REQUEST_METHOD'])
@app.call(env)
else
[405, { 'content-type' => 'text/plain', 'server' => 'sinatra' }, ['Method Not Allowed']]
end
end
end

use MethodGuard
run App
19 changes: 19 additions & 0 deletions frameworks/sinatra/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"display_name": "Sinatra",
"language": "Ruby",
"type": "framework",
"engine": "puma",
"description": "Sinatra DSL web framework on Puma, multi-threaded with one worker per CPU core.",
"repo": "https://github.com/sinatra/sinatra",
"enabled": true,
"tests": [
"baseline",
"pipelined",
"noisy",
"limited-conn",
"json",
"upload",
"compression",
"mixed"
]
}
16 changes: 16 additions & 0 deletions frameworks/sinatra/puma.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require 'etc'

cores = Etc.nprocessors
workers cores
threads 4, 4

bind 'tcp://0.0.0.0:8080'

# Allow all HTTP methods so unknown ones reach Rack middleware (returned as 405)
supported_http_methods :any

preload_app!

before_fork do
# Close any inherited DB connections
end
Loading