diff --git a/frameworks/rage/Dockerfile b/frameworks/rage/Dockerfile new file mode 100644 index 00000000..7228f057 --- /dev/null +++ b/frameworks/rage/Dockerfile @@ -0,0 +1,22 @@ +FROM ruby:4.0-slim + +RUN apt-get update && \ + apt-get install -y --no-install-recommends build-essential libsqlite3-dev libyaml-dev libjemalloc2 && \ + rm -rf /var/lib/apt/lists/* + +# Use Jemalloc +ENV LD_PRELOAD=libjemalloc.so.2 + +ENV RUBY_YJIT_ENABLE=1 +ENV RACK_ENV=production + +WORKDIR /app + +COPY Gemfile . +RUN bundle install --jobs=$(nproc) + +COPY . . + +EXPOSE 8080 + +CMD ["bundle", "exec", "rage", "server", "-p", "8080", "-b", "0.0.0.0"] diff --git a/frameworks/rage/Gemfile b/frameworks/rage/Gemfile new file mode 100644 index 00000000..4caa9fd3 --- /dev/null +++ b/frameworks/rage/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem "rage-rb", "~> 1.19" +gem 'sqlite3', '~> 2.6' diff --git a/frameworks/rage/app/controllers/benchmark_controller.rb b/frameworks/rage/app/controllers/benchmark_controller.rb new file mode 100644 index 00000000..c325ffe9 --- /dev/null +++ b/frameworks/rage/app/controllers/benchmark_controller.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'zlib' + +class BenchmarkController < RageController::API + # Pre-load datasets at class level (shared across workers via preload) + DATASET_PATH = ENV.fetch('DATASET_PATH', '/data/dataset.json') + LARGE_DATASET_PATH = '/data/dataset-large.json' + + @db_available = File.exist?('/data/benchmark.db') + + if File.exist?(DATASET_PATH) + @dataset_items = JSON.parse(File.read(DATASET_PATH)) + end + + if File.exist?(LARGE_DATASET_PATH) + raw = JSON.parse(File.read(LARGE_DATASET_PATH)) + items = raw.map { |d| d.merge('total' => (d['price'] * d['quantity'] * 100).round / 100.0) } + @large_json_payload = JSON.generate({ 'items' => items, 'count' => items.length }) + end + + DB_QUERY = 'SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN ? AND ? LIMIT 50' + + def self.db_available = @db_available + def self.large_json_payload = @large_json_payload + def self.dataset_items = @dataset_items + + before_action do + headers["server"] = "rage" + end + + def pipeline + render plain: 'ok' + end + + def baseline_one + total = 0 + params.each_value do |v| + total += v.to_i + end + if request.post? + body_str = request.send(:rack_request).body.read.to_s.strip + total += body_str.to_i + end + render plain: total.to_s + end + + def baseline_two + total = 0 + params.each_value do |v| + total += v.to_i + end + render plain: total.to_s + end + + def json_endpoint + if self.class.dataset_items + items = self.class.dataset_items.map { |d| d.merge('total' => (d['price'] * d['quantity'] * 100).round / 100.0) } + render json: { 'items' => items, 'count' => items.length } + else + head 500 + end + end + + def compression + if self.class.large_json_payload + sio = StringIO.new + gz = Zlib::GzipWriter.new(sio, 1) + gz.write(self.class.large_json_payload) + gz.close + response.headers['Content-Type'] = 'application/json' + response.headers['Content-Encoding'] = 'gzip' + render plain: sio.string + else + head 500 + end + end + + def db + unless self.class.db_available + render json: { items: [], count: 0 } + return + end + + min_val = (params[:min] || 10).to_f + max_val = (params[:max] || 50).to_f + conn = get_db + rows = conn.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 + render json: { items: items, count: items.length } + end + + def upload + data = request.send(:rack_request).body.read + render plain: data.bytesize.to_s + end + + def not_found + head 404 + end + + private + + def get_db + Fiber.current[:rage_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 diff --git a/frameworks/rage/config.ru b/frameworks/rage/config.ru new file mode 100644 index 00000000..8cbaf770 --- /dev/null +++ b/frameworks/rage/config.ru @@ -0,0 +1,2 @@ +require_relative 'config/application' +run Rage.application diff --git a/frameworks/rage/config/application.rb b/frameworks/rage/config/application.rb new file mode 100644 index 00000000..64f71da0 --- /dev/null +++ b/frameworks/rage/config/application.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'bundler/setup' +Bundler.require(:default) + +require 'rage/all' + +Rage.configure do + # use this to add settings that are constant across all environments +end + +require "rage/setup" diff --git a/frameworks/rage/config/environments/development.rb b/frameworks/rage/config/environments/development.rb new file mode 100644 index 00000000..35d9e7ae --- /dev/null +++ b/frameworks/rage/config/environments/development.rb @@ -0,0 +1,4 @@ +Rage.configure do + config.server.workers_count = -1 + config.logger = Rage::Logger.new(STDOUT) +end diff --git a/frameworks/rage/config/environments/production.rb b/frameworks/rage/config/environments/production.rb new file mode 100644 index 00000000..e84095fc --- /dev/null +++ b/frameworks/rage/config/environments/production.rb @@ -0,0 +1,3 @@ +Rage.configure do + config.logger = Rage::Logger.new(STDOUT) +end diff --git a/frameworks/rage/config/routes.rb b/frameworks/rage/config/routes.rb new file mode 100644 index 00000000..c870a983 --- /dev/null +++ b/frameworks/rage/config/routes.rb @@ -0,0 +1,13 @@ +Rage.routes.draw do + get '/pipeline', to: 'benchmark#pipeline' + get '/baseline11', to: 'benchmark#baseline_one' + post '/baseline11', to: 'benchmark#baseline_one' + get '/baseline2', to: 'benchmark#baseline_two' + get '/json', to: 'benchmark#json_endpoint' + get '/compression', to: 'benchmark#compression' + get '/db', to: 'benchmark#db' + post '/upload', to: 'benchmark#upload' + + # Catch-all for unknown paths → 404 + get '*', to: 'benchmark#not_found' +end diff --git a/frameworks/rage/meta.json b/frameworks/rage/meta.json new file mode 100644 index 00000000..a16d6bb1 --- /dev/null +++ b/frameworks/rage/meta.json @@ -0,0 +1,19 @@ +{ + "display_name": "Rage", + "language": "Ruby", + "type": "framework", + "engine": "iodine", + "description": "Rage is a modern Ruby framework designed for non-blocking I/O and simpler infrastructure", + "repo": "https://github.com/rage-rb/rage", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "noisy", + "limited-conn", + "json", + "upload", + "compression", + "mixed" + ] +}