0.2.79 - Slow api fixes
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package timing
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Transport records every upstream HTTP call against a stage.
|
||||
//
|
||||
// It is a RoundTripper rather than a helper each client calls because there are eight
|
||||
// upstream clients here and the one thing they all genuinely share is that they make
|
||||
// HTTP requests. Wrapping the transport means a new integration is instrumented by
|
||||
// being constructed rather than by somebody remembering to measure it.
|
||||
//
|
||||
// The context it reads is the request's, so a call made outside a traced request —
|
||||
// a scheduled sync, a health probe — records nothing and costs one nil check.
|
||||
type Transport struct {
|
||||
Stage string
|
||||
Base http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
base := t.Base
|
||||
if base == nil {
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
trace := From(r.Context())
|
||||
if trace == nil {
|
||||
return base.RoundTrip(r)
|
||||
}
|
||||
stage := LabelFrom(r.Context(), t.Stage)
|
||||
began := time.Now()
|
||||
resp, err := base.RoundTrip(r)
|
||||
trace.Record(stage, time.Since(began))
|
||||
if err != nil || resp == nil || resp.Body == nil {
|
||||
return resp, err
|
||||
}
|
||||
// RoundTrip returns once the headers are in, and a home row is several hundred
|
||||
// kilobytes of JSON still on the wire at that point. Reading the body is part of
|
||||
// what the upstream cost, so the stage keeps accruing until the caller closes it —
|
||||
// otherwise the largest responses are the ones the breakdown under-reports.
|
||||
resp.Body = &timedBody{ReadCloser: resp.Body, trace: trace, stage: stage}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type timedBody struct {
|
||||
io.ReadCloser
|
||||
trace *Trace
|
||||
stage string
|
||||
spent time.Duration
|
||||
done bool
|
||||
}
|
||||
|
||||
func (b *timedBody) Read(p []byte) (int, error) {
|
||||
began := time.Now()
|
||||
n, err := b.ReadCloser.Read(p)
|
||||
b.spent += time.Since(began)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (b *timedBody) Close() error {
|
||||
err := b.ReadCloser.Close()
|
||||
if !b.done {
|
||||
b.done = true
|
||||
b.trace.Record(b.stage, b.spent)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Instrument wraps a client's transport in place. Called during construction, before
|
||||
// anything is serving, so it needs no synchronisation — the stance the Emby client's
|
||||
// other setters take.
|
||||
func Instrument(client *http.Client, stage string) *http.Client {
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
client.Transport = &Transport{Stage: stage, Base: client.Transport}
|
||||
return client
|
||||
}
|
||||
Reference in New Issue
Block a user