Description
For the purpose of accurate request tracing when serving HTTP requests (for both HTTP/1.x and HTTP/2), it would be ideal if http.Handler
s could obtain a time.Time
representing the time of first byte of the request.
In this context, time of first byte would be the best estimate of the instant when the go process reads the first byte of the HTTP request line from the socket (i.e. ignoring the time spent in hardware queues and in kernel buffers).
Between the time of first byte and the time the first http.Handler
is invoked for that request quite some time can pass since in that interval all headers must be received (and parsed) and the request body must start. If the client is slow then this interval can be extremely long.
Furthermore, when using the middleware pattern for tracing, the order of middleware can influence the recorded time at which the request starts to be handled. This can skew metrics and the accuracy of tracing, leading to hard-to-debug performance issues.
With this proposal implemented, all of the above issues would be solved:
- request parsing would be correctly accounted for
- slow clients can be correctly accounted for
- request start time can be reliably detected irrespective of middleware order
This information could be included either in the http.Request
struct, returned by a method on http.Request
, or attached to the request context.Context
(perhaps via something like the httptrace.ServerTrace
suggestion below by @odeke-em).
update: rephrased according to @odeke-em's suggestions
Metadata
Metadata
Assignees
Type
Projects
Status
Activity
[-]net/http: time of first byte/start of request[/-][+]net/http: record time of first byte/start of http.Request[/+]odeke-em commentedon Apr 6, 2020
Thank you for filing this issue @CAFxX!
So in regards to Transports and HTTP clients, have you perhaps encountered httptrace.ClientTrace https://golang.org/pkg/net/http/httptrace/#ClientTrace
It provides hooks for a bunch of events like:
a) WroteRequest
b) WroteHeaders
c) GotFirstResponseByte
Direction
You talk about
It seems to me that this issue is a feature request for http.Server to attach timing information for processing of requests? If so, let’s modify the title and the wording to make this clear and that’ll perhaps aid in proposal review.
Proposal
Building off your suggestions and comments, perhaps:
This issue perhaps could be solved if net/http/httptrace had:
a) func ContextServerTrace(context.Context) *ServerTrace
b) A net/http/httptrace.ServerTrace struct
If perhaps this suits the requirements, we can make the issue title “proposal: net/http, net/http/httptrace: add mechanism for tracing request serving”
seankhliao commentedon Apr 6, 2020
Earlier httptrace ServerTrace proposal #18997
[-]net/http: record time of first byte/start of http.Request[/-][+]proposal: net/http, net/http/httptrace: add mechanism for tracing request serving[/+]CAFxX commentedon Apr 6, 2020
@odeke-em yes, your interpretation is absolutely correct (sorry, I jotted it down in a haste, and clarity was definitely lacking). I changed the title and tried to clarify things a bit.
I also incorporated a pointer to your suggested interface. I just have a couple of question about it though:
time.Duration
in this case? What would be the start and end instants of these two durations? Put otherwise: how can I obtain atime.Time
representing the instant in time when the corresponding events occurred?BufferSize
for?odeke-em commentedon Apr 7, 2020
Great question, so I was thinking that each timing would be an event offset from an origin time aka time the request was first received. We could have the origin time e.g.
ReceivedRequestAt time.Time
orRequestStartTime time.Time
, instead of having bothReceivedRequestAt time.Time
andTimeToFirstRequestByte time.Time
. Offsets from a single origin time keep the data in the form of an event log and reduce unnecessary computations needed by users to detail the story of the request.BufferSize is meant to capture how much data was needed to be buffered before a full response could be read and the handler was initiated, for example it could be the length of the initial data stream for HTTP/2.
Thank you @seankhliao for finding that ServerTrace proposal. @CAFxX the proposal that @seankhliao referred to has been open for a while and has even more discussion and context. Perhaps let's close this one and transfer the discussion there?
CAFxX commentedon Apr 7, 2020
After going through #18997 I am not sure about whether that's sensible: the proposal scope in #18997 is significantly wider, and as a result the design proposed there has run into problems (circular dependencies between net/http and net/http/httputil) that seemingly caused that proposal to get hopelessly stuck and see no significant activity for the last 2.5 years.
Most likely this proposal could be implemented on the framework of #18997, but as that proposal seem to be not going anywhere due to scope, I don't think increasing the scope further will be of benefit to either of them.
Note that this proposal does not necessarily require the specific solution of #18997 (httputil.ServerTrace): there are other pragmatic solutions mentioned above.
So, if possible, I'd vote to keep the two proposals separate. I can definitely mention the use case in #18997, but merging seems unnecessary.
gopherbot commentedon Apr 7, 2020
Change https://golang.org/cl/227438 mentions this issue:
net/http: prototype ServerTraceState to capture state of a request being handled
odeke-em commentedon Apr 7, 2020
Gotcha, thank you! I couldn't sleep so I instead prototyped something with CL https://go-review.googlesource.com/c/go/+/227438, please take a look. From the ideas I was able to spin up, we'll have code such as:
Code
Output
It is meant to show how this can be done and as a starting point to further the proposal and discussions.
CAFxX commentedon Apr 8, 2020
Functionally (w.r.t. the problem described in this issue) LGTM. This is quite more than I would expect from an MVP. :D
While I'm not sure whether we need all that granularity right away, looking at a full-blown prototype like this is very useful though:
odeke-em commentedon Apr 8, 2020
Thank you for the feedback, and for taking a look, @CAFxX!
In response to your points:
ts.ReceivedAt.Add(ts.TLSHandshakeStartAt + ts.TLSHandshakeEndAt)
17 remaining items