Last active
May 16, 2024 13:12
-
-
Save jgaskins/8fa816a78dc15b7c210fb0503b9ab1ff to your computer and use it in GitHub Desktop.
Rack request stats
This file contains 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
#!/usr/bin/env ruby | |
%w(lib shared).each do |dir| | |
$LOAD_PATH.unshift(__dir__) unless $LOAD_PATH.include?(__dir__) | |
end | |
Gem::Specification.new do |spec| | |
spec.name = "rack-request_stats" | |
spec.version = "0.1.0" | |
spec.authors = ["Jamie Gaskins"] | |
spec.email = ["jgaskins@hey.com"] | |
spec.summary = %q{Rack middleware to log request stats} | |
spec.description = spec.summary | |
spec.homepage = "https://gist.github.com/jgaskins/8fa816a78dc15b7c210fb0503b9ab1ff" | |
spec.license = "MIT" | |
spec.files = Dir["*.rb"] | |
spec.executables = [] | |
spec.test_files = [] | |
spec.require_paths = ["."] | |
spec.add_runtime_dependency "rack" | |
end |
This file contains 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
require "rack" | |
require "logger" | |
require "thread" | |
class Rack::RequestStats | |
VERSION = "0.1.0" | |
attr_reader :logger | |
Stats = Struct.new(:count, :requests_per_sec, :concurrent, keyword_init: true) | |
def initialize(app, logger: Logger.new(STDOUT, level: Logger::INFO), log_every: nil, start_immediately: true) | |
@app = app | |
@logger = logger | |
@count = 0 | |
@concurrent = 0 | |
@mutex = Mutex.new | |
@log_every = if (log_every = log_every.to_i) == 0 | |
1 | |
else | |
log_every | |
end | |
mark_last! | |
start if start_immediately | |
end | |
def call(env) | |
sync do | |
@count += 1 | |
@concurrent += 1 | |
end | |
@app.call env | |
ensure | |
sync { @concurrent -= 1 } | |
end | |
def start | |
Thread.new do | |
loop do | |
sleep @log_every | |
@logger.info stats | |
rescue ex | |
@logger.error ex.message | |
end | |
end | |
end | |
def stats | |
last = sync { @last.tap { mark_last! } } | |
stats = Stats.new( | |
count: @count, | |
requests_per_sec: @count.to_f / (@last - last), | |
concurrent: @concurrent, | |
) | |
@count = 0 | |
stats | |
end | |
private def sync | |
@mutex.synchronize { yield } | |
end | |
private def mark_last! | |
@last = timestamp | |
end | |
private def timestamp | |
Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Indeed. In fact, I’m even the one that added it to Bundler. 😄 rubygems/bundler@a3a3efa