-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add Metrics to RetryableHTTPClient #4545
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
base: main
Are you sure you want to change the base?
Add Metrics to RetryableHTTPClient #4545
Conversation
…tedTransport to RetryableHTTPClient
pkg/common/http.go
Outdated
|
|
||
| // wrap the response body to count bytes read | ||
| resp.Body = &responseSizeCounterReadCloser{ | ||
| ReadCloser: resp.Body, | ||
| recordSizeFunc: func(size int) { | ||
| // Record response body size | ||
| recordResponseBodySize(sanitizedURL, size) | ||
| }, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would also start generating metrics for SaneHTTPClient. Just wanna make sure if this is intentional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is intended. We also want to add this metric to SaneHTTPClient
rosecodym
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty interesting, but I did leave an inline question.
Also, please add doc comments for your new exported functions. Thanks!
pkg/common/http.go
Outdated
| if r.recordSizeFunc != nil { | ||
| if r.bytesRead == nil { | ||
| // this means downstream code never consumed the body, we must drain the body to get its size | ||
| _, err := io.Copy(io.Discard, r) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this cause Read to be called? If so, can you put that in a comment?
pkg/common/http.go
Outdated
|
|
||
| func (r *responseSizeCounterReadCloser) Close() error { | ||
| if r.recordSizeFunc != nil { | ||
| if r.bytesRead == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a previous call to Read didn't read the entire payload, then the leftover size won't be counted, right? E.g.
buf := make([]byte, 10)
reader.Read(buf) // bytesRead is now 10
reader.Close() // bytesRead is not updated
This is kind of a niche case, so I'm not sure how much of a problem it is, but if I'm correct, this should at least be called out in a comment somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that's an interesting case. You're right, a partial read will cause us to not have a correct size. But do you think this is a problem? I can't think of any case where a detector or any piece of code would partially read a response.
I can surely call this out in a comment though.
pkg/common/http_test.go
Outdated
| name string | ||
| path string | ||
| expectedStatusCode int | ||
| expectsNon200 bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this field for? It looks unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, good catch. I basically copy-pasted the test for SaneHTTPClient so this got copied over as well. Will remove it
|
|
||
| // Get initial metric values | ||
| sanitizedURL := sanitizeURL(requestURL) | ||
| initialRequestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoa I didn't know we could test Prometheus metrics like this!
camgunz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like how this is super accurate, which wasn't easy to do! But instead of counting what we read (and potentially manually reading), I think we should start out by just using ContentLength from the Response object. It's definitely less reliable, but I don't want to get out the big hammer until we see how much less reliable it really is.
I really liked that code 🥹. But I guess you're right, it is overkill for just metrics. I'll scrap it |
| // Record response body size if known | ||
| if contentLength >= 0 { | ||
| httpResponseBodySizeBytes.WithLabelValues(sanitizedURL).Observe(float64(contentLength)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this if will always be true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this check to ensure that the contentLength value being passed into the function is valid (i.e. >0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's what net/http documentation says about the ContentLength field:
// ContentLength records the length of the associated content. The
// value -1 indicates that the length is unknown. Unless Request.Method
// is "HEAD", values >= 0 indicate that the given number of bytes may
// be read from Body.
ContentLength int64
So there is a possibility of the value being -1
nabeelalam
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @mustansir14
Description:
This PR adds a new HTTP response body size metric to
InstrumentedTransport, which is the currentTransportforSaneHTTPClient.InstrumentedTransportis also set to be used as theTransportforRetryableHTTPClient.Challenge with Response Body Size Calculation:
Responseobject has aContentLengthfield that may have exactly what we're looking for, but it is not guaranteed to be provided by the server. Also it is never available for a chunked response.RoundTrip()call, and would require restoring the body so that downstream code can read it again.Solution:
To address this, a
responseSizeCounterReadCloseris introduced to measure the size of the response body WHILE the downstream code reads it. This ensures no overhead is added in theRoundTrip()call. This however will not work if the downstream code never reads the body (e.g. detectors where only theStatusCodeis used to verify), so to handle this, we add a check in theClose()method of theReadCloserto drain the body if it's not consumed.Checklist:
make test-community)?make lintthis requires golangci-lint)?