FEATURE: Snapshots by OsamaSayegh · Pull Request #459 · MiniProfiler/rack-mini-profiler

2 min read Original article ↗

Thanks @SamSaffron!

I wrote a little script to compare the impact of the "should take snapshot" check when snapshotting is enabled and disabled, and I've found that when snapshotting is disabled (i.e. snapshot_every_n_requests is 0), a request takes on average 0.01 milliseconds. And when it's enabled, a request takes on average 0.21 milliseconds.

So the impact is under a millisecond on my machine, but it'll probably be higher on production if redis lives in a different machine. If we to absolutely optimize here, I think (I have not tested this) it's possible to do the "should take snapshot" check in ruby and avoid redis entirely (like the MemoryStore implementation). There would be some downsides such as not perfect sampling (especially in multi-server environment), but the trade-off may be worth it.

This is the benchmark script I used in case you want to review it:

Benchmark script
#!/usr/bin/env ruby

pid = fork do
  require 'bundler/inline'

  gemfile do
    source 'https://rubygems.org'

    gem 'rack'
    gem 'redis'
    gem 'rack-mini-profiler', path: "~/rack-mini-profiler"
  end

  class BenchMiddleware
    def initialize(app)
      @app = app
    end

    def call(env)
      t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
      @app.call(env)
      bench_res = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - t1
      [200, {'Content-Type' => 'text/plain'}, ["#{bench_res}"]]
    end
  end

  Rack::MiniProfiler.config.storage = Rack::MiniProfiler::RedisStore
  Rack::MiniProfiler.config.snapshot_every_n_requests = ENV['SNAPSHOTS_PERIOD'].to_i
  Rack::MiniProfiler.config.authorization_mode = :whitelist

  module MPPatch
    def take_snapshot?(*args)
      super
      false
    end
  end
  Rack::MiniProfiler.prepend(MPPatch)

  app = Rack::Builder.app do
    use BenchMiddleware
    use Rack::MiniProfiler
    run lambda { |env|
      [200, {'Content-Type' => 'text/plain'}, ['OK']]
    }
  end

  Rack::Handler.default.run(
    app,
    Port: 4321,
    BindAddress: "127.0.0.1",
    Logger: WEBrick::Log.new("/dev/null"),
    AccessLog: []
  )
end

begin
  require 'net/http'
  sleep(0.5)
  Net::HTTP.get(URI("http://127.0.0.1:4321"))
  iterations = ENV['ITERATIONS'] ? ENV['ITERATIONS'].to_i : 10
  sum = 0
  iterations.times do
    sum += Net::HTTP.get(URI("http://127.0.0.1:4321")).to_f / (1000 * 1000)
  end
  puts "Average: #{sum / iterations} (ms)"
ensure
  Process.kill("TERM", pid)
end