Skip to content

Conversation

@dingsdax
Copy link
Contributor

@dingsdax dingsdax commented Jan 15, 2026

Captures how long requests wait for a e.g. Puma thread from X-Request-Start headers and attaches it to transactions as http.queue_time_ms. Sentry only measures after Puma picks up the request. Under load, requests might wait long in the queue but only take a fraction to process. Similar to what judoscale and scout do.

works with all major reverse proxies:

  • Nginx: X-Request-Start: t=1234567890.123 (seconds)
  • Heroku: X-Request-Start: t=1234567890123456 (microseconds)
  • HAProxy 1.9+: X-Request-Start: t=1234567890123456 (microseconds)
  • HAProxy < 1.9: X-Request-Start: t=1234567890 (seconds)
  • Generic: Raw timestamps in seconds/milliseconds/microseconds

subtracts puma.request_body_wait for accuracy (excludes slow client uploads from queue time).

Usage

works 🤞 if your reverse proxy sets the header:

# Nginx
proxy_set_header X-Request-Start "t=${msec}";

opt-out

config.capture_queue_time = false
sentry being sentry

@linear
Copy link

linear bot commented Jan 15, 2026

@github-actions
Copy link

github-actions bot commented Jan 15, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 00cc056

@dingsdax dingsdax requested review from sl0thentr0py and solnic and removed request for solnic January 15, 2026 10:22
@dingsdax dingsdax marked this pull request as ready for review January 15, 2026 10:23
MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count"

# Time in ms the request spent in the server queue before processing began.
HTTP_QUEUE_TIME_MS = "http.queue_time_ms"
Copy link
Member

@sl0thentr0py sl0thentr0py Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbhiPrasad @lcian is there an attribute convention for time spent in queue waiting for the http request to be picked up in an HTTP server?

The closest I could find is http.client.request.time_in_queue but that's for clients.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm I cannot find anything like that. Maybe we introduce our own http.server.request.time_in_queue counterpart?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good; shall I add to sentry-conventions too?

Copy link
Member

@sl0thentr0py sl0thentr0py left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some small stuff, and mainly want to wait for feedback on the attribute convention name

total_time_ms = ((Time.now.to_f - request_start) * 1000).round(2)

# reject negative (clock skew between proxy & app server) or very large values (> 60 seconds)
return nil unless total_time_ms >= 0 && total_time_ms < 60_000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about the upper bound here, would still be useful to know right?

Copy link
Contributor Author

@dingsdax dingsdax Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdym? remove upper bound here? should be fine too

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes let's remove the upper bound

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes let's remove the upper bound

header_value.to_f
else
return nil
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-numeric t= prefixed header values produce incorrect queue time

Medium Severity

The parse_request_start_header function validates format with a regex for raw numeric timestamps but not for t= prefixed values. When receiving a malformed header like t=invalid, "invalid".to_f silently returns 0.0 (Ruby's behavior for non-numeric strings). This results in a timestamp of Unix epoch (1970), causing the queue time calculation to return ~56 years of queue time instead of nil. The issue affects any malformed t= prefixed header value including t=, t=abc, etc.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Puma request queue time as a transaction attribute

4 participants