Merge branch 'use-gzip-pool' of github.com:glefloch/client_golang into beorn7/http

This commit is contained in:
beorn7 2018-10-20 00:51:31 +02:00
commit 752f50d366
1 changed files with 54 additions and 34 deletions

View File

@ -53,19 +53,16 @@ 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) { var bufPool = sync.Pool{
buf.Reset() New: func() interface{} {
bufPool.Put(buf) return &bytes.Buffer{}
},
} }
// Handler returns an http.Handler for the prometheus.DefaultGatherer, using // Handler returns an http.Handler for the prometheus.DefaultGatherer, using
@ -112,7 +109,6 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
return return
} }
} }
mfs, err := reg.Gather() mfs, err := reg.Gather()
if err != nil { if err != nil {
if opts.ErrorLog != nil { if opts.ErrorLog != nil {
@ -133,10 +129,12 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
} }
contentType := expfmt.Negotiate(req.Header) contentType := expfmt.Negotiate(req.Header)
buf := getBuf() buf := bufPool.Get().(*bytes.Buffer)
defer giveBuf(buf) buf.Reset()
writer, encoding := decorateWriter(req, buf, opts.DisableCompression) enc := expfmt.NewEncoder(buf, contentType)
enc := expfmt.NewEncoder(writer, contentType)
defer bufPool.Put(buf)
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,9 +153,7 @@ 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
@ -165,12 +161,20 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
header := w.Header() header := w.Header()
header.Set(contentTypeHeader, string(contentType)) header.Set(contentTypeHeader, string(contentType))
header.Set(contentLengthHeader, fmt.Sprint(buf.Len())) header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
if encoding != "" {
header.Set(contentEncodingHeader, encoding) if !opts.DisableCompression && gzipAccepted(req.Header) {
} header.Set(contentEncodingHeader, "gzip")
if _, err := w.Write(buf.Bytes()); err != nil && opts.ErrorLog != nil { gz := gzipPool.Get().(*gzip.Writer)
opts.ErrorLog.Println("error while sending encoded metrics:", err) defer gzipPool.Put(gz)
gz.Reset(w)
defer gz.Close()
zipWriter := gzipResponseWriter{gz, w}
writeResult(zipWriter, buf, opts)
return
} }
writeResult(w, buf, opts)
// TODO(beorn7): Consider streaming serving of metrics. // TODO(beorn7): Consider streaming serving of metrics.
}) })
@ -292,20 +296,36 @@ 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, "" }
func (w gzipResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
// writeResult to buf using http.ResponseWriter.
// If ErrorLog is enabled, err is logged in.
func writeResult(w http.ResponseWriter, buf *bytes.Buffer, opts HandlerOpts) {
if _, err := w.Write(buf.Bytes()); err != nil && opts.ErrorLog != nil {
opts.ErrorLog.Println("error while sending encoded metrics:", err)
} }
header := request.Header.Get(acceptEncodingHeader) }
parts := strings.Split(header, ",")
// gzipHandler return a http.HandlerFunc in charge of compressing the content
// of the given http.HandlerFunc
func gzipAccepted(header http.Header) bool {
a := header.Get(acceptEncodingHeader)
parts := strings.Split(a, ",")
for _, part := range parts { for _, part := range parts {
part = strings.TrimSpace(part) part = strings.TrimSpace(part)
if part == "gzip" || strings.HasPrefix(part, "gzip;") { if part == "gzip" || strings.HasPrefix(part, "gzip;") {
return gzip.NewWriter(writer), "gzip" return true
} }
} }
return writer, "" return false
} }