Revert "Improving Performance on the API Gzip Handler (#12363)"
This reverts commit dfae954dc1.
Signed-off-by: Julien Pivotto <roidelapluie@o11y.eu>
			
			
This commit is contained in:
		
							parent
							
								
									652fb03081
								
							
						
					
					
						commit
						d19fa62476
					
				
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -34,7 +34,6 @@ require ( | |||
| 	github.com/hetznercloud/hcloud-go v1.45.1 | ||||
| 	github.com/ionos-cloud/sdk-go/v6 v6.1.7 | ||||
| 	github.com/json-iterator/go v1.1.12 | ||||
| 	github.com/klauspost/compress v1.16.5 | ||||
| 	github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b | ||||
| 	github.com/linode/linodego v1.17.0 | ||||
| 	github.com/miekg/dns v1.1.54 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							|  | @ -507,8 +507,6 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW | |||
| github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | ||||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||||
| github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | ||||
| github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= | ||||
| github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | ||||
| github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= | ||||
| github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
|  |  | |||
|  | @ -14,11 +14,11 @@ | |||
| package httputil | ||||
| 
 | ||||
| import ( | ||||
| 	"compress/gzip" | ||||
| 	"compress/zlib" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/klauspost/compress/gzhttp" | ||||
| 	"github.com/klauspost/compress/zlib" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
|  | @ -28,28 +28,54 @@ const ( | |||
| 	deflateEncoding       = "deflate" | ||||
| ) | ||||
| 
 | ||||
| // Wrapper around http.ResponseWriter which adds deflate compression
 | ||||
| type deflatedResponseWriter struct { | ||||
| // Wrapper around http.Handler which adds suitable response compression based
 | ||||
| // on the client's Accept-Encoding headers.
 | ||||
| type compressedResponseWriter struct { | ||||
| 	http.ResponseWriter | ||||
| 	writer *zlib.Writer | ||||
| 	writer io.Writer | ||||
| } | ||||
| 
 | ||||
| // Writes HTTP response content data.
 | ||||
| func (c *deflatedResponseWriter) Write(p []byte) (int, error) { | ||||
| func (c *compressedResponseWriter) Write(p []byte) (int, error) { | ||||
| 	return c.writer.Write(p) | ||||
| } | ||||
| 
 | ||||
| // Close Closes the deflatedResponseWriter and ensures to flush all data before.
 | ||||
| func (c *deflatedResponseWriter) Close() { | ||||
| 	c.writer.Close() | ||||
| // Closes the compressedResponseWriter and ensures to flush all data before.
 | ||||
| func (c *compressedResponseWriter) Close() { | ||||
| 	if zlibWriter, ok := c.writer.(*zlib.Writer); ok { | ||||
| 		zlibWriter.Flush() | ||||
| 	} | ||||
| 	if gzipWriter, ok := c.writer.(*gzip.Writer); ok { | ||||
| 		gzipWriter.Flush() | ||||
| 	} | ||||
| 	if closer, ok := c.writer.(io.Closer); ok { | ||||
| 		defer closer.Close() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Constructs a new deflatedResponseWriter to compress the original writer using 'deflate' compression.
 | ||||
| func newDeflateResponseWriter(writer http.ResponseWriter) *deflatedResponseWriter { | ||||
| 	return &deflatedResponseWriter{ | ||||
| // Constructs a new compressedResponseWriter based on client request headers.
 | ||||
| func newCompressedResponseWriter(writer http.ResponseWriter, req *http.Request) *compressedResponseWriter { | ||||
| 	encodings := strings.Split(req.Header.Get(acceptEncodingHeader), ",") | ||||
| 	for _, encoding := range encodings { | ||||
| 		switch strings.TrimSpace(encoding) { | ||||
| 		case gzipEncoding: | ||||
| 			writer.Header().Set(contentEncodingHeader, gzipEncoding) | ||||
| 			return &compressedResponseWriter{ | ||||
| 				ResponseWriter: writer, | ||||
| 				writer:         gzip.NewWriter(writer), | ||||
| 			} | ||||
| 		case deflateEncoding: | ||||
| 			writer.Header().Set(contentEncodingHeader, deflateEncoding) | ||||
| 			return &compressedResponseWriter{ | ||||
| 				ResponseWriter: writer, | ||||
| 				writer:         zlib.NewWriter(writer), | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return &compressedResponseWriter{ | ||||
| 		ResponseWriter: writer, | ||||
| 		writer:         writer, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CompressionHandler is a wrapper around http.Handler which adds suitable
 | ||||
|  | @ -60,21 +86,7 @@ type CompressionHandler struct { | |||
| 
 | ||||
| // ServeHTTP adds compression to the original http.Handler's ServeHTTP() method.
 | ||||
| func (c CompressionHandler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { | ||||
| 	encodings := strings.Split(req.Header.Get(acceptEncodingHeader), ",") | ||||
| 	for _, encoding := range encodings { | ||||
| 		switch strings.TrimSpace(encoding) { | ||||
| 		case gzipEncoding: | ||||
| 			gzhttp.GzipHandler(c.Handler).ServeHTTP(writer, req) | ||||
| 			return | ||||
| 		case deflateEncoding: | ||||
| 			compWriter := newDeflateResponseWriter(writer) | ||||
| 			writer.Header().Set(contentEncodingHeader, deflateEncoding) | ||||
| 	compWriter := newCompressedResponseWriter(writer, req) | ||||
| 	c.Handler.ServeHTTP(compWriter, req) | ||||
| 	compWriter.Close() | ||||
| 			return | ||||
| 		default: | ||||
| 			c.Handler.ServeHTTP(writer, req) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -17,30 +17,23 @@ import ( | |||
| 	"bytes" | ||||
| 	"compress/gzip" | ||||
| 	"compress/zlib" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 
 | ||||
| 	"github.com/prometheus/prometheus/model/labels" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	mux    *http.ServeMux | ||||
| 	server *httptest.Server | ||||
| 	respBody = strings.Repeat("Hello World!", 500) | ||||
| ) | ||||
| 
 | ||||
| func setup() func() { | ||||
| 	mux = http.NewServeMux() | ||||
| 	server = httptest.NewServer(mux) | ||||
| 	return func() { | ||||
| 		server.CloseClientConnections() | ||||
| 		server.Close() | ||||
| 	} | ||||
| } | ||||
|  | @ -48,7 +41,7 @@ func setup() func() { | |||
| func getCompressionHandlerFunc() CompressionHandler { | ||||
| 	hf := func(w http.ResponseWriter, r *http.Request) { | ||||
| 		w.WriteHeader(http.StatusOK) | ||||
| 		w.Write([]byte(respBody)) | ||||
| 		w.Write([]byte("Hello World!")) | ||||
| 	} | ||||
| 	return CompressionHandler{ | ||||
| 		Handler: http.HandlerFunc(hf), | ||||
|  | @ -74,8 +67,9 @@ func TestCompressionHandler_PlainText(t *testing.T) { | |||
| 	contents, err := io.ReadAll(resp.Body) | ||||
| 	require.NoError(t, err, "unexpected error while creating the response body reader") | ||||
| 
 | ||||
| 	expected := "Hello World!" | ||||
| 	actual := string(contents) | ||||
| 	require.Equal(t, respBody, actual, "expected response with content") | ||||
| 	require.Equal(t, expected, actual, "expected response with content") | ||||
| } | ||||
| 
 | ||||
| func TestCompressionHandler_Gzip(t *testing.T) { | ||||
|  | @ -109,7 +103,8 @@ func TestCompressionHandler_Gzip(t *testing.T) { | |||
| 	require.NoError(t, err, "unexpected error while reading the response body") | ||||
| 
 | ||||
| 	actual := buf.String() | ||||
| 	require.Equal(t, respBody, actual, "unexpected response content") | ||||
| 	expected := "Hello World!" | ||||
| 	require.Equal(t, expected, actual, "unexpected response content") | ||||
| } | ||||
| 
 | ||||
| func TestCompressionHandler_Deflate(t *testing.T) { | ||||
|  | @ -143,98 +138,6 @@ func TestCompressionHandler_Deflate(t *testing.T) { | |||
| 	require.NoError(t, err, "unexpected error while reading the response body") | ||||
| 
 | ||||
| 	actual := buf.String() | ||||
| 	require.Equal(t, respBody, actual, "expected response with content") | ||||
| } | ||||
| 
 | ||||
| func Benchmark_compression(b *testing.B) { | ||||
| 	client := &http.Client{ | ||||
| 		Transport: &http.Transport{ | ||||
| 			DisableCompression: true, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	cases := map[string]struct { | ||||
| 		enc            string | ||||
| 		numberOfLabels int | ||||
| 	}{ | ||||
| 		"gzip-10-labels": { | ||||
| 			enc:            gzipEncoding, | ||||
| 			numberOfLabels: 10, | ||||
| 		}, | ||||
| 		"gzip-100-labels": { | ||||
| 			enc:            gzipEncoding, | ||||
| 			numberOfLabels: 100, | ||||
| 		}, | ||||
| 		"gzip-1K-labels": { | ||||
| 			enc:            gzipEncoding, | ||||
| 			numberOfLabels: 1000, | ||||
| 		}, | ||||
| 		"gzip-10K-labels": { | ||||
| 			enc:            gzipEncoding, | ||||
| 			numberOfLabels: 10000, | ||||
| 		}, | ||||
| 		"gzip-100K-labels": { | ||||
| 			enc:            gzipEncoding, | ||||
| 			numberOfLabels: 100000, | ||||
| 		}, | ||||
| 		"gzip-1M-labels": { | ||||
| 			enc:            gzipEncoding, | ||||
| 			numberOfLabels: 1000000, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for name, tc := range cases { | ||||
| 		b.Run(name, func(b *testing.B) { | ||||
| 			tearDown := setup() | ||||
| 			defer tearDown() | ||||
| 			labels := labels.ScratchBuilder{} | ||||
| 
 | ||||
| 			for i := 0; i < tc.numberOfLabels; i++ { | ||||
| 				labels.Add(fmt.Sprintf("Name%v", i), fmt.Sprintf("Value%v", i)) | ||||
| 			} | ||||
| 
 | ||||
| 			respBody, err := json.Marshal(labels.Labels()) | ||||
| 			require.NoError(b, err) | ||||
| 
 | ||||
| 			hf := func(w http.ResponseWriter, r *http.Request) { | ||||
| 				w.WriteHeader(http.StatusOK) | ||||
| 				w.Write(respBody) | ||||
| 			} | ||||
| 			h := CompressionHandler{ | ||||
| 				Handler: http.HandlerFunc(hf), | ||||
| 			} | ||||
| 
 | ||||
| 			mux.Handle("/foo_endpoint", h) | ||||
| 
 | ||||
| 			req, _ := http.NewRequest("GET", server.URL+"/foo_endpoint", nil) | ||||
| 			req.Header.Set(acceptEncodingHeader, tc.enc) | ||||
| 
 | ||||
| 			b.ReportAllocs() | ||||
| 			b.ResetTimer() | ||||
| 
 | ||||
| 			// Reusing the array to read the body and avoid allocation on the test
 | ||||
| 			encRespBody := make([]byte, len(respBody)) | ||||
| 
 | ||||
| 			for i := 0; i < b.N; i++ { | ||||
| 				resp, err := client.Do(req) | ||||
| 
 | ||||
| 				require.NoError(b, err) | ||||
| 
 | ||||
| 				require.NoError(b, err, "client get failed with unexpected error") | ||||
| 				responseBodySize := 0 | ||||
| 				for { | ||||
| 					n, err := resp.Body.Read(encRespBody) | ||||
| 					responseBodySize += n | ||||
| 					if err == io.EOF { | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				b.ReportMetric(float64(responseBodySize), "ContentLength") | ||||
| 				resp.Body.Close() | ||||
| 			} | ||||
| 
 | ||||
| 			client.CloseIdleConnections() | ||||
| 		}) | ||||
| 	} | ||||
| 	expected := "Hello World!" | ||||
| 	require.Equal(t, expected, actual, "expected response with content") | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue