cloudflared-mirror/vendor/github.com/mitchellh/go-server-timing/metric.go

126 lines
3.8 KiB
Go

package servertiming
import (
"fmt"
"strconv"
"strings"
"time"
)
// 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.
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
// startTime is the time that this metric recording was started if
// Start() was called.
startTime time.Time
}
// WithDesc is a chaining-friendly helper to set the Desc field on the Metric.
func (m *Metric) WithDesc(desc string) *Metric {
m.Desc = desc
return m
}
// 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 (m *Metric) Start() *Metric {
m.startTime = time.Now()
return m
}
// 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 (m *Metric) Stop() *Metric {
// Only record if we have a start time set with Start()
if !m.startTime.IsZero() {
m.Duration = time.Since(m.startTime)
}
return m
}
// String returns the valid Server-Timing metric entry value.
func (m *Metric) String() string {
// Begin building parts, expected capacity is length of extra
// fields plus id, desc, dur.
parts := make([]string, 1, len(m.Extra)+3)
parts[0] = m.Name
// Description
if _, ok := m.Extra[paramNameDesc]; !ok && m.Desc != "" {
parts = append(parts, headerEncodeParam(paramNameDesc, m.Desc))
}
// Duration
if _, ok := m.Extra[paramNameDur]; !ok && m.Duration > 0 {
parts = append(parts, headerEncodeParam(
paramNameDur,
strconv.FormatFloat(float64(m.Duration)/float64(time.Millisecond), 'f', -1, 64),
))
}
// All remaining extra params
for k, v := range m.Extra {
parts = append(parts, headerEncodeParam(k, v))
}
return strings.Join(parts, ";")
}
// GoString is needed for fmt.GoStringer so %v works on pointer value.
func (m *Metric) GoString() string {
if m == nil {
return "nil"
}
return fmt.Sprintf("*%#v", *m)
}