Skip to content

Commit 1fe3301

Browse files
committed
Roda: add framework
1 parent 6f5864d commit 1fe3301

5 files changed

Lines changed: 166 additions & 0 deletions

File tree

frameworks/roda/Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
FROM ruby:3.4-slim
2+
3+
RUN apt-get update && \
4+
apt-get install -y --no-install-recommends build-essential libsqlite3-dev libjemalloc2 && \
5+
rm -rf /var/lib/apt/lists/*
6+
7+
# Use Jemalloc
8+
ENV LD_PRELOAD=libjemalloc.so.2
9+
10+
ENV RUBY_YJIT_ENABLE=1
11+
ENV RACK_ENV=production
12+
ENV WEB_CONCURRENCY=auto
13+
14+
WORKDIR /app
15+
16+
COPY Gemfile .
17+
RUN bundle install --jobs=$(nproc)
18+
19+
COPY . .
20+
21+
EXPOSE 8080
22+
23+
CMD ["bundle", "exec", "iodine", "-p", "8080"]
24+

frameworks/roda/Gemfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
source 'https://rubygems.org'
2+
3+
gem 'roda', '~> 3.100'
4+
gem 'iodine'
5+
gem 'sqlite3', '~> 2.9'
6+
gem 'json'

frameworks/roda/app.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# frozen_string_literal: true
2+
3+
require 'bundler/setup'
4+
Bundler.require(:default)
5+
6+
require 'zlib'
7+
8+
class App < Roda
9+
# Load dataset
10+
dataset_path = ENV.fetch('DATASET_PATH', '/data/dataset.json')
11+
if File.exist?(dataset_path)
12+
opts[:dataset_items] = JSON.parse(File.read(dataset_path))
13+
end
14+
15+
# Large dataset for compression
16+
large_path = '/data/dataset-large.json'
17+
if File.exist?(large_path)
18+
raw = JSON.parse(File.read(large_path))
19+
items = raw.map do |d|
20+
d.merge('total' => (d['price'] * d['quantity'] * 100).round / 100.0)
21+
end
22+
opts[:large_json_payload] = JSON.generate({ 'items' => items, 'count' => items.length })
23+
end
24+
25+
# SQLite
26+
opts[:db_available] = File.exist?('/data/benchmark.db')
27+
28+
DB_QUERY = 'SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN ? AND ? LIMIT 50'
29+
30+
plugin :default_headers, 'Server' => 'roda'
31+
plugin :halt
32+
33+
route do |r|
34+
r.is 'pipeline' do
35+
response[RodaResponseHeaders::CONTENT_TYPE] = 'text/plain'
36+
'ok'
37+
end
38+
39+
r.is('baseline11') { handle_baseline11 }
40+
41+
r.is 'baseline2' do
42+
total = 0
43+
request.GET.each do |_k, v|
44+
total += v.to_i
45+
end
46+
response[RodaResponseHeaders::CONTENT_TYPE] = 'text/plain'
47+
total.to_s
48+
end
49+
50+
r.is 'json' do
51+
dataset = opts[:dataset_items]
52+
r.halt 500, 'No dataset' unless dataset
53+
items = dataset.map do |d|
54+
d.merge('total' => (d['price'] * d['quantity'] * 100).round / 100.0)
55+
end
56+
response[RodaResponseHeaders::CONTENT_TYPE] = 'application/json'
57+
JSON.generate({ 'items' => items, 'count' => items.length })
58+
end
59+
60+
r.is '/compression' do
61+
payload = opts[:large_json_payload]
62+
r.halt 500, 'No dataset' unless payload
63+
sio = StringIO.new
64+
gz = Zlib::GzipWriter.new(sio, 1)
65+
gz.write(payload)
66+
gz.close
67+
response[RodaResponseHeaders::CONTENT_TYPE] = 'application/json'
68+
response[RodaResponseHeaders::CONTENT_ENCODING] = 'gzip'
69+
sio.string
70+
end
71+
72+
r.is 'db' do
73+
unless opts[:db_available]
74+
response[RodaResponseHeaders::CONTENT_TYPE] = 'application/json'
75+
return '{"items":[],"count":0}'
76+
end
77+
min_val = (request.params['min'] || 10).to_i
78+
max_val = (request.params['max'] || 50).to_i
79+
db = get_db
80+
rows = db.execute(DB_QUERY, [min_val, max_val])
81+
items = rows.map do |row|
82+
{
83+
'id' => row['id'], 'name' => row['name'], 'category' => row['category'],
84+
'price' => row['price'], 'quantity' => row['quantity'], 'active' => row['active'] == 1,
85+
'tags' => JSON.parse(row['tags']),
86+
'rating' => { 'score' => row['rating_score'], 'count' => row['rating_count'] }
87+
}
88+
end
89+
response[RodaResponseHeaders::CONTENT_TYPE] = 'application/json'
90+
JSON.generate({ 'items' => items, 'count' => items.length })
91+
end
92+
end
93+
94+
def handle_baseline11
95+
total = 0
96+
request.GET.each do |_k, v|
97+
total += v.to_i
98+
end
99+
if request.post?
100+
request.body.rewind
101+
body_str = request.body.read.strip
102+
total += body_str.to_i
103+
end
104+
response[RodaResponseHeaders::CONTENT_TYPE] = 'text/plain'
105+
total.to_s
106+
end
107+
108+
def get_db
109+
Thread.current[:roda_db] ||= begin
110+
db = SQLite3::Database.new('/data/benchmark.db', readonly: true)
111+
db.execute('PRAGMA mmap_size=268435456')
112+
db.results_as_hash = true
113+
db
114+
end
115+
end
116+
end

frameworks/roda/config.ru

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require_relative 'app'
2+
run App

frameworks/roda/meta.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"display_name": "Roda",
3+
"language": "Ruby",
4+
"type": "framework",
5+
"engine": "iodine",
6+
"description": "Roda routing tree web toolkit on Iodine",
7+
"repo": "https://github.com/jeremyevans/roda",
8+
"enabled": true,
9+
"tests": [
10+
"baseline",
11+
"pipelined",
12+
"noisy",
13+
"limited-conn",
14+
"json",
15+
"compression",
16+
"mixed"
17+
]
18+
}

0 commit comments

Comments
 (0)