-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
feat: Support zstd compression #1496
base: main
Are you sure you want to change the base?
Conversation
d9dd903
to
12e6e53
Compare
6a82962
to
18f74ec
Compare
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.
Nice! I am supportive. I think adding zstd to some places in Prometheus turned out to be sometimes controversial due to higher CPU, but from the client side... we are happy to support more AS LONG as the dependency is not too problematic (zstd should be fine).
Thanks! I have some suggestions to the implementation though, see comments.
18f74ec
to
562606c
Compare
Thanks for the feedback! I haven't thought about enhancing the Accept-Encoding header further and have replaced it now with an internal copy of the archived https://github.com/golang/gddo/blob/master/httputil/negotiate.go#L19 I see prometheus is using an internal copy of goautoneg: https://github.com/prometheus/common/blob/main/internal/bitbucket.org/ww/goautoneg/autoneg.go this might be an option to switch it to the gddo/httputil implementation and move all the code into prometheus/common. Users of the library can now control what compression they offer via opts.EncodingOffers as well. Cross-linking these issues from internal TODOs: Open question: |
0d37c09
to
03af4da
Compare
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.
Beautiful work, had to find time to review it
If the user of the client supplies an not-yet implemented compression, should this be visible via an error (and if so, how)?
Good question. First of all I would use enum so it's less likely it happens. Then.. I propose something in comment, should be good enough. WDYT?
Thanks! Almost there!
48c16ce
to
ce851d0
Compare
bd79bd6
to
b1dbb01
Compare
1716212
to
789178e
Compare
175c33d
to
70157ae
Compare
5d1b662
to
7cfc1f3
Compare
7cfc1f3
to
ad8f8a3
Compare
ad8f8a3
to
99037c1
Compare
prometheus/promhttp/http.go
Outdated
w, encodingHeader, closeWriter, err := NegotiateEncodingWriter(req, rsp, opts.DisableCompression, compressions) | ||
|
||
if closeWriter != nil { | ||
defer func() { |
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.
@bwplotka Test will fail if you comment out this defer.
Needed to do it that way instead of calling the function directly for golangci-lint's errcheck.
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 think there is potentially better way (do _ :=
if we don't care about this err, or pack opt.Err log in helper func), but that's already good enough, Thanks!
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.
Still not resolved, plus we can kill this if != nil check for close Writer I assume (let's ensure negotiateEncodingWriter
always sets it, even if noop).
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.
Sounds good, I added the empty functions you suggested also if an error is returned.
@bwplotka I believe I addressed everything, so this is ready for another round of review. :) |
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.
Amazing, thank you!
I think this is good enough to merge in this state. I think there are some things e.g. number of if statements to handle response from Negotiate yields more code vs if we just inline this function (: But I think there might be ways to simplify this, mostly for readability.
Happy to merge, and allow others and myself to simplify bit in another PR, or propose PR to your PR, what do you prefer? Or if you want you could try to simplify too if you know what I mean (added some suggestions), but happy to help here to save your time! 🤗
Thanks!
prometheus/promhttp/http.go
Outdated
w, encodingHeader, closeWriter, err := NegotiateEncodingWriter(req, rsp, opts.DisableCompression, compressions) | ||
|
||
if closeWriter != nil { | ||
defer func() { |
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 think there is potentially better way (do _ :=
if we don't care about this err, or pack opt.Err log in helper func), but that's already good enough, Thanks!
} | ||
}() | ||
} | ||
if err != 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.
Technically no defer is needed on this error, no? So we can move closeWriter to after err != nil and skip closerWriter if nil check?
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 one isn't the deferred one, this is the one from NegotiateEncodingWriter. I reordered it for better readability.
prometheus/promhttp/http.go
Outdated
if opts.ErrorLog != nil { | ||
opts.ErrorLog.Println("error getting writer", err) | ||
} | ||
// Since the writer received from NegotiateEncodingWriter will be nil, in case there's an error, we set it here |
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.
No need for this comment TBH
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.
Removed it. :)
prometheus/promhttp/http.go
Outdated
|
||
w = gz | ||
if encodingHeader == "" { |
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.
After err check we expect this to be always valid, can we ensure in func?
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.
Good idea, I moved it into the err check.
Can we keep the NegotiateEncodingWriter func separate and not inline it? I want to reuse it in kube-state-metrics to avoid code duplication there (unfortunately we cannot fully use ServeHTTP there). If there are any other bits that could make the code simpler and easier to read, please let me know, happy to implement them as well. Do you want me to squash the commits into a single one? |
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.
Great work! Let's keep going then ;p
I think I found one more bug 🙈 And added some suggestions inline. No need to squash, we will do it while merging. Function can be separate, added suggestions, but I think we have to make it private... sorry. Exporting NegotiateEncodingWriter
would be not ideal. Commented why.
prometheus/promhttp/http.go
Outdated
w, encodingHeader, closeWriter, err := NegotiateEncodingWriter(req, rsp, opts.DisableCompression, compressions) | ||
|
||
if closeWriter != nil { | ||
defer func() { |
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.
Still not resolved, plus we can kill this if != nil check for close Writer I assume (let's ensure negotiateEncodingWriter
always sets it, even if noop).
prometheus/promhttp/http.go
Outdated
// compressions. It returns a writer implementing the compression and an the | ||
// correct value that the caller can set in the response header. | ||
func NegotiateEncodingWriter(r *http.Request, rw io.Writer, disableCompression bool, compressions []string) (_ io.Writer, encodingHeaderValue string, closeWriter func() error, _ error) { | ||
w := rw |
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.
Hm.. I feel this can be removed? We can return those z
and gz
things?
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, removed it. :)
return w, compression, z.Close, nil | ||
case "gzip": | ||
gz := gzipPool.Get().(*gzip.Writer) | ||
defer gzipPool.Put(gz) |
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 has to be done on Close. It's a subtle bug, but concurrent Negotiate calls might corrupt gzip writers (hard to repro on tests I guess). "Put" means "I won't use gz anymore", which will happen on return of this function. But that's not true is it?
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.
Good catch! I was wondering about that as well. Thanks for the suggestions on returning a function here that calls both. TIL :)
1209cbd
to
88ad686
Compare
This allows endpoints to respond with zstd compressed metric data, if the requester supports it. I have imported a content-encoding parser from https://github.com/golang/gddo which is an archived repository to support different content-encoding headers. Signed-off-by: Manuel Rüger <manuel@rueg.eu>
Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com> Signed-off-by: Manuel Rüger <manuel@rueg.eu>
Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com> Signed-off-by: Manuel Rüger <manuel@rueg.eu>
Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com> Signed-off-by: Manuel Rüger <manuel@rueg.eu>
* String typed enum Signed-off-by: Manuel Rüger <manuel@rueg.eu>
88ad686
to
fac3bb7
Compare
Signed-off-by: Manuel Rüger <manuel@rueg.eu>
Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com> Signed-off-by: Manuel Rüger <manuel@rueg.eu>
Signed-off-by: Manuel Rüger <manuel@rueg.eu>
Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com> Signed-off-by: Manuel Rüger <manuel@rueg.eu>
fac3bb7
to
140db7a
Compare
This allows endpoints to respond with zstd compressed metric data, if the requester supports it. For backwards compatibility, gzip compression will take precedence.
I added a benchmark to the http_test.go, that will test with a differently sized synthetic metric series.
See also: prometheus/prometheus#13866
/kind feature
Release note: