servertiming

package module
Version: v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 31, 2020 License: MIT Imports: 10 Imported by: 25

README

HTTP Server-Timing for Go

Godoc

This is a library including middleware for using HTTP Server-Timing with Go. This header allows a server to send timing information from the backend, such as database access time, file reads, etc. The timing information can be then be inspected in the standard browser developer tools:

Server Timing Example

Features

  • Middleware for injecting the server timing struct into the request Context and writing the Server-Timing header.

  • Concurrency-safe structures for easily recording timings of multiple concurrency tasks.

  • Parse Server-Timing headers as a client.

  • Note: No browser properly supports sending the Server-Timing header as an HTTP Trailer so the Middleware only supports a normal header currently.

Browser Support

Browser support is required to view server timings easily. Because server timings are sent as an HTTP header, there is no negative impact to sending the header to unsupported browsers.

  • Chrome 65 or higher is required to properly display server timings in the devtools.

  • Firefox is pending with an open bug report (ID 1403051)

  • IE, Opera, and others are unknown at this time.

Usage

Example usage is shown below. A fully runnable example is available in the example/ directory.

func main() {
	// Our handler. In a real application this might be your root router,
	// or some subset of your router. Wrapping this ensures that all routes
	// handled by this handler have access to the server timing header struct.
	var h http.Handler = http.HandlerFunc(handler)

	// Wrap our handler with the server timing middleware
	h = servertiming.Middleware(h, nil)

	// Start!
	http.ListenAndServe(":8080", h)
}

func handler(w http.ResponseWriter, r *http.Request) {
	// Get our timing header builder from the context
	timing := servertiming.FromContext(r.Context())

	// Imagine your handler performs some tasks in a goroutine, such as
	// accessing some remote service. timing is concurrency safe so we can
	// record how long that takes. Let's simulate making 5 concurrent requests
	// to various servicse.
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		name := fmt.Sprintf("service-%d", i)
		go func(name string) {
			// This creats a new metric and starts the timer. The Stop is
			// deferred so when the function exits it'll record the duration.
			defer timing.NewMetric(name).Start().Stop()
			time.Sleep(random(25, 75))
			wg.Done()
		}(name)
	}

	// Imagine this is just some blocking code in your main handler such
	// as a SQL query. Let's record that.
	m := timing.NewMetric("sql").WithDesc("SQL query").Start()
	time.Sleep(random(20, 50))
	m.Stop()

	// Wait for the goroutine to end
	wg.Wait()

	// You could continue recording more metrics, but let's just return now
	w.WriteHeader(200)
	w.Write([]byte("Done. Check your browser inspector timing details."))
}

func random(min, max int) time.Duration {
	return (time.Duration(rand.Intn(max-min) + min)) * time.Millisecond
}

Documentation

Index

Constants

View Source
const HeaderKey = "Server-Timing"

HeaderKey is the specified key for the Server-Timing header.

Variables

This section is empty.

Functions

func Middleware

func Middleware(next http.Handler, _ *MiddlewareOpts) http.Handler

Middleware wraps an http.Handler and provides a *Header in the request context that can be used to set Server-Timing headers. The *Header can be extracted from the context using FromContext.

The options supplied to this can be nil to use defaults.

The Server-Timing header will be written when the status is written only if there are non-empty number of metrics.

To control when Server-Timing is sent, the easiest approach is to wrap this middleware and only call it if the request should send server timings. For examples, see the README.

func NewContext

func NewContext(ctx context.Context, h *Header) context.Context

NewContext returns a new Context that carries the Header value h.

Types

type Header struct {
	// Metrics is the list of metrics in the header.
	Metrics []*Metric

	// The lock that is held when Metrics is being modified. This
	// ONLY NEEDS TO BE SET WHEN working with Metrics directly. If using
	// the functions on the struct, the lock is managed automatically.
	sync.Mutex
}

Header represents a collection of metrics that can be encoded as a Server-Timing header value.

The functions for working with metrics are concurrency-safe to make it easy to record metrics from goroutines. If you want to avoid the lock overhead, you can access the Metrics field directly.

The functions for working with metrics are also usable on a nil Header pointer. This allows functions that use FromContext to get the *Header value to skip nil-checking and use it as normal. On a nil *Header, Metrics are not recorded.

func FromContext

func FromContext(ctx context.Context) *Header

FromContext returns the *Header in the context, if any. If no Header value exists, nil is returned.

func ParseHeader

func ParseHeader(input string) (*Header, error)

ParseHeader parses a Server-Timing header value.

func (*Header) Add

func (h *Header) Add(m *Metric) *Metric

Add adds the given metric to the header.

This function is safe to call concurrently.

func (*Header) NewMetric

func (h *Header) NewMetric(name string) *Metric

NewMetric creates a new Metric and adds it to this header.

func (*Header) String

func (h *Header) String() string

String returns the valid Server-Timing header value that can be sent in an HTTP response.

type Metric

type Metric struct {
	// Name is the name of the metric. This must be a valid RFC7230 "token"
	// format. In a gist, this is an alphanumeric string that may contain
	// most common symbols but may not contain any whitespace. The exact
	// syntax can be found in RFC7230.
	//
	// It is common for Name to be a unique identifier (such as "sql-1") and
	// for a more human-friendly name to be used in the "desc" field.
	Name string

	// Duration is the duration of this Metric.
	Duration time.Duration

	// Desc is any string describing this metric. For example: "SQL Primary".
	// The specific format of this is `token | quoted-string` according to
	// RFC7230.
	Desc string

	// Extra is a set of extra parameters and values to send with the
	// metric. The specification states that unrecognized parameters are
	// to be ignored so it should be safe to add additional data here. The
	// key must be a valid "token" (same syntax as Name) and the value can
	// be any "token | quoted-string" (same as Desc field).
	//
	// If this map contains a key that would be sent by another field in this
	// struct (such as "desc"), then this value is prioritized over the
	// struct value.
	Extra map[string]string
	// contains filtered or unexported fields
}

Metric represents a single metric for the Server-Timing header.

The easiest way to use the Metric is to use NewMetric and chain it. This results in a single line defer at the top of a function time a function.

timing := FromContext(r.Context())
defer timing.NewMetric("sql").Start().Stop()

For timing around specific blocks of code:

m := timing.NewMetric("sql").Start()
// ... run your code being timed here
m.Stop()

A metric is expected to represent a single timing event. Therefore, no functions on the struct are safe for concurrency by default. If a single Metric is shared by multiple concurrenty goroutines, you must lock access manually.

func (*Metric) GoString

func (m *Metric) GoString() string

GoString is needed for fmt.GoStringer so %v works on pointer value.

func (*Metric) Start

func (m *Metric) Start() *Metric

Start starts a timer for recording the duration of some task. This must be paired with a Stop call to set the duration. Calling this again will reset the start time for a subsequent Stop call.

func (*Metric) Stop

func (m *Metric) Stop() *Metric

Stop ends the timer started with Start and records the duration in the Duration field. Calling this multiple times will modify the Duration based on the last time Start was called.

If Start was never called, this function has zero effect.

func (*Metric) String

func (m *Metric) String() string

String returns the valid Server-Timing metric entry value.

func (*Metric) WithDesc

func (m *Metric) WithDesc(desc string) *Metric

WithDesc is a chaining-friendly helper to set the Desc field on the Metric.

type MiddlewareOpts

type MiddlewareOpts struct {
}

MiddlewareOpts are options for the Middleware.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL