-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from lessonnine/TNT-2624/add-queue-time-measur…
…ement Add request queue time middleware
- Loading branch information
Showing
8 changed files
with
158 additions
and
7 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
require: | ||
- rubocop-performance | ||
- rubocop-rspec | ||
|
||
AllCops: | ||
TargetRubyVersion: 2.6 | ||
|
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
module Puma | ||
class Plugin | ||
module Telemetry | ||
VERSION = "0.3.1" | ||
VERSION = "1.0.0" | ||
end | ||
end | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# frozen_string_literal: true | ||
|
||
# Measures the queue time (= time between receiving the request in downstream | ||
# load balancer and starting request in ruby process) | ||
class RequestQueueTimeMiddleware | ||
ENV_KEY = "rack.request_queue_time" | ||
|
||
def initialize(app, statsd:, process: Process) | ||
@app = app | ||
@statsd = statsd | ||
@process = process | ||
end | ||
|
||
def call(env) | ||
queue_time = measure_queue_time(env) | ||
|
||
report_queue_time(queue_time) | ||
|
||
env[ENV_KEY] = queue_time | ||
|
||
@app.call(env) | ||
end | ||
|
||
private | ||
|
||
def measure_queue_time(env) | ||
start_time = queue_start(env) | ||
|
||
return unless start_time | ||
|
||
queue_time = request_start.to_f - start_time.to_f | ||
|
||
queue_time unless queue_time.negative? | ||
end | ||
|
||
# Get the content of the x-amzn-trace-id header, the epoch time in seconds. | ||
# see also: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html | ||
def queue_start(env) | ||
value = env["HTTP_X_AMZN_TRACE_ID"] | ||
value&.split("Root=")&.last&.split("-")&.fetch(1)&.to_i(16) | ||
end | ||
|
||
def request_start | ||
@process.clock_gettime(Process::CLOCK_REALTIME) | ||
end | ||
|
||
def report_queue_time(queue_time) | ||
return if queue_time.nil? | ||
|
||
@statsd.timing("queue.time", queue_time) | ||
|
||
return unless defined?(Datadog) && Datadog.respond_to?(:tracer) | ||
|
||
span = Datadog.tracer.active_root_span | ||
span&.set_tag("request.queue_time", queue_time) | ||
end | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative "../../lib/rack/request_queue_time_middleware" | ||
require "rack" | ||
require "datadog/statsd" | ||
|
||
# Provide mock as Timecop doesn't support such case | ||
class MockedProcess | ||
def initialize | ||
@clock = Process.clock_gettime(Process::CLOCK_REALTIME) | ||
end | ||
|
||
def microseconds | ||
@clock - @clock.floor | ||
end | ||
|
||
def amz_ago(ago) | ||
(@clock - ago).to_i.to_s(16) | ||
end | ||
|
||
def clock_gettime(_arg) | ||
@clock | ||
end | ||
end | ||
|
||
RSpec.describe RequestQueueTimeMiddleware do | ||
subject(:make_request) { request.get("/some/path", headers) } | ||
|
||
let(:request) do | ||
Rack::MockRequest.new(described_class.new(->(env) { [200, env, "Bazinga!"] }, | ||
statsd: statsd, | ||
process: process)) | ||
end | ||
|
||
let(:process) { MockedProcess.new } | ||
let(:statsd) { instance_double(Datadog::Statsd) } | ||
let(:headers) { { "HTTP_X_AMZN_TRACE_ID" => header } } | ||
let(:header) do | ||
%W[ | ||
Self=1-#{process.amz_ago(expected_duration - 6)}-12456789abcdef012345678 | ||
Root=1-#{process.amz_ago(expected_duration)}-abcdef012345678912345678 | ||
].join(";") | ||
end | ||
|
||
let(:expected_duration) { 12 + process.microseconds } | ||
|
||
context "when correct header" do | ||
it "reports queue time" do | ||
expect(statsd).to receive(:timing).with("queue.time", expected_duration) | ||
expect(make_request.status).to eq(200) | ||
end | ||
end | ||
|
||
context "when header missing" do | ||
let(:headers) { {} } | ||
|
||
it "doesn't report anything" do | ||
expect(statsd).not_to receive(:timing) | ||
expect(make_request.status).to eq(200) | ||
end | ||
end | ||
|
||
context "when header in the future" do | ||
let(:expected_duration) { -(12 + process.microseconds) } | ||
|
||
it "doesn't report anything" do | ||
expect(statsd).not_to receive(:timing) | ||
expect(make_request.status).to eq(200) | ||
end | ||
end | ||
end |