Skip to content
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

Feature request: add hooks to enable compression metrics #40

Open
jschaf opened this issue Mar 22, 2023 · 1 comment
Open

Feature request: add hooks to enable compression metrics #40

jschaf opened this issue Mar 22, 2023 · 1 comment

Comments

@jschaf
Copy link

jschaf commented Mar 22, 2023

Hi there. First off, thank you for the high-quality library. I rolled my own fork of the https://github.com/nytimes/gziphandler and was contemplating a brotli version before happily discovering your repo.

I recently did an optimization pass on my server. I noticed the server was spending 200 ms to encode large RPC responses using the Brotli default compression level of 6. It took a while to track down because I had to infer the time spent on compression by adding logs to the handler before and after the httpcompression handler. I figured other folks would benefit from metrics, hence this feature request.

I'd like to be able to emit traces and metrics for compression to answer the following questions:

  • How long did the compression take?
  • Which compression algorithm was used, and at what compression level?
  • What was the compression ratio?
  • What was the input size and output size?

I've seen a few approaches to support tracing.

The struct is advantageous because you can add new methods without breaking existing clients. With interfaces, the library author needs to add new interfaces for additional functionality and check to see if the user-provided tracer supports the new interface, i.e., interface smuggling.

I like the httptrace approach of using a single struct with optional methods. As a straw man, maybe something like:

func handleRequest() {
	compressHandler, err := httpcompression.Adapter(
		httpcompression.BrotliCompressionLevel(brotli.BestSpeed),
		// Need a factory here to create a new trace for each request.
		httpcompression.TraceProvider(func(ctx context.Context) *CompressTrace {
			span := trace.SpanFromContext(ctx)
			return &CompressTrace{
				CompressStart: func(info CompressStartInfo) {
					span.AddEvent("compress start",
						trace.String("content_type", info.ContentType),
						trace.String("content_encoding", info.ContentEncoding),
					)
				},
				CompressDone: func(info CompressDoneInfo) {
					span.AddEvent("compress done",
						trace.Int("bytes_read", info.BytesRead),
						trace.Int("bytes_written", info.BytesWritten),
						trace.Float64("compression_ratio", float64(info.BytesWritten)/float64(info.BytesRead)),
					)
				},
			}
		}),
	)
}

// NOTE: structs are a bit overkill compared to inlining the args into the function call.
// The benefit is the library can add new fields without breaking existing clients.
// httptrace uses a mixture of structs and inlined args.
type CompressStartInfo struct {
	// ContentType is the content type of the response.
	ContentType string
	// ContentEncoding is the encoding used for the response.
	ContentEncoding string
}

type CompressDoneInfo struct {
	BytesRead    int
	BytesWritten int
	// Others:
	// * ContentEncoding - if it can change from CompressStartInfo
	// * allocations - not sure if feasible
}

type CompressTrace struct {
	// StartCompress is called when compression starts.
	CompressStart func(info CompressStartInfo)
	CompressDone  func(info CompressDoneInfo)
	// Others:
	// * WroteHeaders
	// * Close
}
@jschaf
Copy link
Author

jschaf commented Mar 25, 2023

I'll test out an implementation here: https://github.com/simple-circle/httpcompression/commit/134d732e749ba38a26997fb36b380c02b71ba245

Let me know if you're interested in a PR, and I'll send it your way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant