diff --git a/judoscale-ruby/lib/judoscale/request_metrics.rb b/judoscale-ruby/lib/judoscale/request_metrics.rb index 5b190fde..1787cec0 100644 --- a/judoscale-ruby/lib/judoscale/request_metrics.rb +++ b/judoscale-ruby/lib/judoscale/request_metrics.rb @@ -2,6 +2,10 @@ module Judoscale class RequestMetrics + MILLISECONDS_CUTOFF = Time.new(2000, 1, 1).to_i * 1000 + MICROSECONDS_CUTOFF = MILLISECONDS_CUTOFF * 1000 + NANOSECONDS_CUTOFF = MICROSECONDS_CUTOFF * 1000 + attr_reader :request_id, :size, :network_time def initialize(env, config = Config.instance) @@ -20,15 +24,23 @@ def started_at if @request_start_header # There are several variants of this header. We handle these: # - whole milliseconds (Heroku) + # - whole microseconds (???) # - whole nanoseconds (Render) # - fractional seconds (NGINX) # - preceeding "t=" (NGINX) value = @request_start_header.gsub(/[^0-9.]/, "").to_f - case value - when 0..100_000_000_000 then Time.at(value) - when 100_000_000_000..100_000_000_000_000 then Time.at(value / 1000.0) - else Time.at(value / 1_000_000.0) + # `value` could be seconds, milliseconds, microseconds or nanoseconds. + # We use some arbitrary cutoffs to determine which one it is. + + if value > NANOSECONDS_CUTOFF + Time.at(value / 1_000_000_000.0) + elsif value > MICROSECONDS_CUTOFF + Time.at(value / 1_000_000.0) + elsif value > MILLISECONDS_CUTOFF + Time.at(value / 1000.0) + else + Time.at(value) end end end diff --git a/judoscale-ruby/test/request_metrics_test.rb b/judoscale-ruby/test/request_metrics_test.rb index 19ce879d..42e59f78 100644 --- a/judoscale-ruby/test/request_metrics_test.rb +++ b/judoscale-ruby/test/request_metrics_test.rb @@ -27,20 +27,26 @@ module Judoscale ended_at = started_at + 1 env["HTTP_X_REQUEST_START"] = "t=#{format "%.3f", started_at.to_f}" - # The queue time might return 999 or 1000 depending to time + float + format conversion, - # even when freezing time. _(request.queue_time(ended_at)).must_be_within_delta 1000, 1 end end - it "handles X_REQUEST_START in nanoseconds" do + it "handles X_REQUEST_START in microseconds" do freeze_time do started_at = Time.now.utc - 2 ended_at = started_at + 1 env["HTTP_X_REQUEST_START"] = (started_at.to_f * 1_000_000).to_i.to_s - # The queue time might return 999 or 1000 depending to time + float + format conversion, - # even when freezing time. + _(request.queue_time(ended_at)).must_be_within_delta 1000, 1 + end + end + + it "handles X_REQUEST_START in nanoseconds" do + freeze_time do + started_at = Time.now.utc - 2 + ended_at = started_at + 1 + env["HTTP_X_REQUEST_START"] = (started_at.to_f * 1_000_000_000).to_i.to_s + _(request.queue_time(ended_at)).must_be_within_delta 1000, 1 end end