Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track request queue time based on available request headers #34

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions lib/rorvswild/plugin/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,52 @@
module RorVsWild
module Plugin
class Middleware
module RequestQueueTime
REQUEST_START_HEADER = 'HTTP_X_REQUEST_START'.freeze
QUEUE_START_HEADER = 'HTTP_X_QUEUE_START'.freeze
MIDDLEWARE_START_HEADER = 'HTTP_X_MIDDLEWARE_START'.freeze

ACCEPTABLE_HEADERS = [
REQUEST_START_HEADER,
QUEUE_START_HEADER,
MIDDLEWARE_START_HEADER
].freeze

MINIMUM_TIMESTAMP = 1577836800.freeze # 2020/01/01 UTC
DIVISORS = [1_000_000, 1_000, 1].freeze

def parse_queue_time_header(headers)
return unless headers

earliest = nil

ACCEPTABLE_HEADERS.each do |header|
next unless headers[header]

timestamp = parse_timestamp(headers[header].gsub("t=", ""))
if timestamp && (!earliest || timestamp < earliest)
earliest = timestamp
end
end

[earliest, Time.now.to_f].min if earliest
end

private

def parse_timestamp(timestamp)
DIVISORS.each do |divisor|
begin
t = (timestamp.to_f / divisor)
return t if t > MINIMUM_TIMESTAMP
rescue RangeError
end
end
end
end

include RequestQueueTime

def self.setup
return if @installed
Rails.application.config.middleware.unshift(RorVsWild::Plugin::Middleware, nil) if defined?(Rails)
Expand All @@ -16,6 +62,7 @@ def initialize(app, config)
def call(env)
RorVsWild.agent.start_request
RorVsWild.agent.current_data[:path] = env["ORIGINAL_FULLPATH"]
RorVsWild.agent.current_data[:queue_time] = calculate_queue_time(env)
section = RorVsWild::Section.start
section.file, section.line = rails_engine_location
section.commands << "Rails::Engine#call"
Expand All @@ -28,6 +75,12 @@ def call(env)

private

def calculate_queue_time(headers)
queue_time_from_header = parse_queue_time_header(headers)

((Time.now.to_f - queue_time_from_header) * 1000).round if queue_time_from_header
end

def rails_engine_location
@rails_engine_location = ::Rails::Engine.instance_method(:call).source_location
end
Expand Down
43 changes: 41 additions & 2 deletions test/plugin/middleware_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,53 @@ class RorVsWild::Plugin::MiddlewareTest < Minitest::Test

def test_callback
agent # Load agent
request = {"ORIGINAL_FULLPATH" => "/foo/bar"}
request = { "ORIGINAL_FULLPATH" => "/foo/bar" }
app = mock(call: nil)
middleware = RorVsWild::Plugin::Middleware.new(app, nil)
middleware.stubs(rails_engine_location: ["/rails/lib/engine.rb", 12])
middleware.call(request)
assert_equal("/foo/bar", agent.current_data[:path])
assert_equal(1, agent.current_data[:sections].size)
assert_equal("Rails::Engine#call", agent.current_data[:sections][0].command)
assert_nil(agent.current_data[:queue_time])
end
end

def test_queue_time_secs
agent # Load agent
request_start = unix_timestamp_seconds - 0.123
request = {"HTTP_X_REQUEST_START" => request_start.to_s}
app = mock(call: nil)
middleware = RorVsWild::Plugin::Middleware.new(app, nil)
middleware.stubs(rails_engine_location: ["/rails/lib/engine.rb", 12])
middleware.call(request)
assert_in_delta(123, agent.current_data[:queue_time], 10)
end

def test_queue_time_millis
agent # Load agent
request_start = unix_timestamp_seconds * 1000 - 234
request = { "HTTP_X_QUEUE_START" => request_start.to_s }
app = mock(call: nil)
middleware = RorVsWild::Plugin::Middleware.new(app, nil)
middleware.stubs(rails_engine_location: ["/rails/lib/engine.rb", 12])
middleware.call(request)
assert_in_delta(234, agent.current_data[:queue_time], 10)
end

def test_queue_time_micros
agent # Load agent
request_start = unix_timestamp_seconds * 1_000_000 - 345_000
request = { "HTTP_X_MIDDLEWARE_START" => request_start.to_s }
app = mock(call: nil)
middleware = RorVsWild::Plugin::Middleware.new(app, nil)
middleware.stubs(rails_engine_location: ["/rails/lib/engine.rb", 12])
middleware.call(request)
assert_in_delta(345, agent.current_data[:queue_time], 10)
end

private

def unix_timestamp_seconds
Time.now.to_f
end
end