Use sync.Pool for gzipWriter

Signed-off-by: glefloch <glfloch@gmail.com>
This commit is contained in:
glefloch 2018-10-17 13:17:33 +02:00
parent 0a8115f42e
commit fdf4cbc87b
1 changed files with 48 additions and 44 deletions

View File

@ -53,19 +53,10 @@ const (
acceptEncodingHeader = "Accept-Encoding" acceptEncodingHeader = "Accept-Encoding"
) )
var bufPool sync.Pool var gzipPool = sync.Pool{
New: func() interface{} {
func getBuf() *bytes.Buffer { return gzip.NewWriter(nil)
buf := bufPool.Get() },
if buf == nil {
return &bytes.Buffer{}
}
return buf.(*bytes.Buffer)
}
func giveBuf(buf *bytes.Buffer) {
buf.Reset()
bufPool.Put(buf)
} }
// Handler returns an http.Handler for the prometheus.DefaultGatherer, using // Handler returns an http.Handler for the prometheus.DefaultGatherer, using
@ -133,10 +124,9 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
} }
contentType := expfmt.Negotiate(req.Header) contentType := expfmt.Negotiate(req.Header)
buf := getBuf() buf := &bytes.Buffer{}
defer giveBuf(buf) enc := expfmt.NewEncoder(buf, contentType)
writer, encoding := decorateWriter(req, buf, opts.DisableCompression)
enc := expfmt.NewEncoder(writer, contentType)
var lastErr error var lastErr error
for _, mf := range mfs { for _, mf := range mfs {
if err := enc.Encode(mf); err != nil { if err := enc.Encode(mf); err != nil {
@ -155,29 +145,50 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
} }
} }
} }
if closer, ok := writer.(io.Closer); ok {
closer.Close()
}
if lastErr != nil && buf.Len() == 0 { if lastErr != nil && buf.Len() == 0 {
http.Error(w, "No metrics encoded, last error:\n\n"+lastErr.Error(), http.StatusInternalServerError) http.Error(w, "No metrics encoded, last error:\n\n"+lastErr.Error(), http.StatusInternalServerError)
return return
} }
header := w.Header()
header.Set(contentTypeHeader, string(contentType)) w.Header().Set(contentTypeHeader, string(contentType))
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
if encoding != "" {
header.Set(contentEncodingHeader, encoding)
}
if _, err := w.Write(buf.Bytes()); err != nil && opts.ErrorLog != nil { if _, err := w.Write(buf.Bytes()); err != nil && opts.ErrorLog != nil {
opts.ErrorLog.Println("error while sending encoded metrics:", err) opts.ErrorLog.Println("error while sending encoded metrics:", err)
} }
// TODO(beorn7): Consider streaming serving of metrics. // TODO(beorn7): Consider streaming serving of metrics.
}) })
gzipHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if opts.DisableCompression {
h.ServeHTTP(w, req)
return
}
header := req.Header.Get(acceptEncodingHeader)
parts := strings.Split(header, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
w.Header().Set(contentEncodingHeader, "gzip")
gz := gzipPool.Get().(*gzip.Writer)
defer gzipPool.Put(gz)
gz.Reset(w)
defer gz.Close()
h.ServeHTTP(gzipResponseWriter{gz, w}, req)
return
}
}
h.ServeHTTP(w, req)
return
})
if opts.Timeout <= 0 { if opts.Timeout <= 0 {
return h return gzipHandler
} }
return http.TimeoutHandler(h, opts.Timeout, fmt.Sprintf( return http.TimeoutHandler(gzipHandler, opts.Timeout, fmt.Sprintf(
"Exceeded configured timeout of %v.\n", "Exceeded configured timeout of %v.\n",
opts.Timeout, opts.Timeout,
)) ))
@ -292,20 +303,13 @@ type HandlerOpts struct {
Timeout time.Duration Timeout time.Duration
} }
// decorateWriter wraps a writer to handle gzip compression if requested. It // gzipResponseWriter in charge of wrapping io.Writer and http.ReponseWriter
// returns the decorated writer and the appropriate "Content-Encoding" header // together, allowing to get a single struct which implements both interface.
// (which is empty if no compression is enabled). type gzipResponseWriter struct {
func decorateWriter(request *http.Request, writer io.Writer, compressionDisabled bool) (io.Writer, string) { io.Writer
if compressionDisabled { http.ResponseWriter
return writer, "" }
}
header := request.Header.Get(acceptEncodingHeader) func (w gzipResponseWriter) Write(b []byte) (int, error) {
parts := strings.Split(header, ",") return w.Writer.Write(b)
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
return gzip.NewWriter(writer), "gzip"
}
}
return writer, ""
} }