Last active
November 6, 2025 08:34
-
-
Save alexisbernard/1ae2fee6f5b158858f0f5729a0fe7d72 to your computer and use it in GitHub Desktop.
Simple Rails app to measure latency and throughput when multithreaded given a specific IO rate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # frozen_string_literal: true | |
| # Simple Rails app to measure latency and throughput when multithreaded given a specific IO rate. | |
| # Start the server with: MAX_THREADS=10 RAILS_ENV=production ruby threads_contention_app.rb | |
| # Then run load tests with concurrency from 1 to 10: | |
| # ab -n 100 -c 1 -l http://localhost:3000/?io_rate=0.5 | |
| # ab -n 100 -c 2 -l http://localhost:3000/?io_rate=0.5 | |
| # ab -n 100 -c 3 -l http://localhost:3000/?io_rate=0.5 | |
| # ... | |
| # ab -n 100 -c 10 -l http://localhost:3000/?io_rate=0.5 | |
| require "bundler/inline" | |
| gemfile(true) do | |
| source "https://rubygems.org" | |
| gem "rails", "8.1" | |
| gem "puma" | |
| end | |
| require "action_controller/railtie" | |
| class App < Rails::Application | |
| config.secret_key_base = "i_am_a_secret" | |
| config.logger = Logger.new($stdout) | |
| routes.append do | |
| root to: "benchmark#index" | |
| end | |
| end | |
| class BenchmarkController < ActionController::Base | |
| WORK_DIFFICULTY = 46_368 | |
| WORKING_MS = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] | |
| def index | |
| working_ms = WORKING_MS.shuffle | |
| io_rate = params[:io_rate].to_f | |
| raise ArgumentError.new("params[:io_rate] (#{io_rate}) must be between 0 and 1") if !(0..1).include?(io_rate) | |
| cpu_rate = 1 - io_rate | |
| total_cpu_ms = total_io_ms = 0 | |
| while ms = working_ms.pop | |
| total_cpu_ms += cpu_ms = work(ms) | |
| total_io_ms += io_ms = io_rate * cpu_ms / cpu_rate | |
| sleep(io_ms / 1000) | |
| end | |
| real_io_rate = total_io_ms / (total_cpu_ms + total_io_ms) * 100 | |
| render inline: "CPU: #{total_cpu_ms.round.to_i}ms (#{100 - real_io_rate.round(2)}%)\nIO: #{total_io_ms.to_i}ms (#{real_io_rate.round(2)}%)" | |
| end | |
| private | |
| def work(target_ms) | |
| started_at = clock_ms | |
| fibonacci(WORK_DIFFICULTY) while (real_ms = clock_ms - started_at) < target_ms | |
| real_ms | |
| end | |
| def clock_ms | |
| Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) | |
| end | |
| # Curious about this super fast fibonacci? You should read that article: | |
| # https://www.rorvswild.com/blog/2025/fibonacci-ruby-algorithms | |
| def fibonacci(n) | |
| fib_hash = Hash.new do |h, k| | |
| h[k] = _fib(h, k) | |
| end | |
| fib_hash[0] = 0 | |
| fib_hash[1] = 1 | |
| fib_hash[2] = 1 | |
| fib_hash[n] | |
| end | |
| def _fib(hash, n) | |
| half_n = n / 2 | |
| a = hash[half_n + 1] | |
| b = hash[half_n] | |
| if n.odd? | |
| a * a + b * b | |
| else | |
| 2 * a * b - b * b | |
| end | |
| end | |
| end | |
| App.initialize! | |
| require "puma" | |
| require "puma/cli" | |
| # Choose port dynamically or fallback to 3000 | |
| port = ENV.fetch("PORT", 3000) | |
| # Temporary rackup file | |
| rackup_path = File.join(Dir.tmpdir, "tmp_rackup.ru") | |
| File.write(rackup_path, "run App\n") | |
| # Puma configuration | |
| puma_config = <<~PUMA | |
| workers #{ENV.fetch("WEB_CONCURRENCY", 1)} | |
| threads #{ENV.fetch("MAX_THREADS", 5)}, #{ENV.fetch("MAX_THREADS", 5)} | |
| port #{port} | |
| preload_app! | |
| rackup "#{rackup_path}" | |
| PUMA | |
| config_path = File.join(Dir.tmpdir, "tmp_puma.rb") | |
| File.write(config_path, puma_config) | |
| cli = Puma::CLI.new(["-C", config_path]) | |
| cli.run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment