Help us shape the future of Go! Take the 2026 Go Developer Survey

metrics

package module
v0.10.154 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 18 Imported by: 6

README

metrics

Go Reference

A small instrumentation library for Go services: counters, gauges and timing samples emitted through one interface, delivered to Prometheus, AWS CloudWatch, memory, or several of them at once.

go get github.com/effective-security/metrics

Requires Go 1.27.

Why another metrics package

This is a trimmed fork of the well-known armon/go-metrics shape, with the parts that do not survive production removed:

  • The Provider interface has four methods and no …WithValues variants.
  • EmitKey is gone: Prometheus, the most common backend, has no type that retains an arbitrary number of values.
  • The AllowedLabels config is gone. It is hard to configure correctly and costs on every emission; decide which labels you publish instead, and keep their cardinality bounded.
  • Keys are plain strings, not []string, and tags are explicit Tag values.
type Provider interface {
	SetGauge(key string, val float64, tags ...Tag)
	IncrCounter(key string, val float64, tags ...Tag)
	AddSample(key string, val float64, tags ...Tag)
	MeasureSince(key string, start time.Time, tags ...Tag)
}

Quick start

Install a global provider once, during startup, then emit from anywhere.

package main

import (
	"net/http"
	"time"

	"github.com/effective-security/metrics"
	esprom "github.com/effective-security/metrics/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
	cfg := metrics.DefaultConfig("billing")
	cfg.GlobalTags = []metrics.Tag{{Name: "env", Value: "prod"}}

	sink, err := esprom.NewSinkFrom(esprom.Opts{
		Name:       "billing",
		Expiration: time.Minute,
		Help:       cfg.Help(chargeMetrics),
	})
	if err != nil {
		panic(err)
	}
	if _, err = metrics.NewGlobal(cfg, sink); err != nil {
		panic(err)
	}

	http.Handle("/metrics", promhttp.Handler())
	_ = http.ListenAndServe(":8080", nil)
}

Emitting, with the package-level functions:

func (s *Service) Charge(ctx context.Context, amount float64) error {
	defer metrics.MeasureSince("charge_duration", time.Now(),
		metrics.Tag{Name: "currency", Value: "usd"})

	metrics.IncrCounter("charge_total", 1)
	metrics.SetGauge("charge_amount", amount)
	return nil
}

Or against an instance, when you do not want global state — useful in tests:

sink := metrics.NewInmemSink(10*time.Second, time.Minute)
prov, err := metrics.New(metrics.DefaultConfig("billing"), sink)
if err != nil {
	return err
}
prov.IncrCounter("charge_total", 1)

Declaring metrics

Declaring a metric with Describe keeps its name, help text and tag names in one place, validates the tag count on every call, and feeds the help text to the Prometheus sink through Config.Help.

var (
	chargeDuration = &metrics.Describe{
		Type:         metrics.TypeSample,
		Name:         "charge_duration",
		Help:         "charge_duration provides the time spent charging a card",
		RequiredTags: []string{"currency", "provider"},
	}
	chargeTotal = &metrics.Describe{
		Type:         metrics.TypeCounter,
		Name:         "charge_total",
		Help:         "charge_total provides the number of charges",
		RequiredTags: []string{"currency", "provider"},
	}

	// pass to Config.Help, so /metrics carries the HELP text
	chargeMetrics = []*metrics.Describe{chargeDuration, chargeTotal}
)

func (s *Service) Charge(ctx context.Context) error {
	defer chargeDuration.MeasureSince(time.Now(), "usd", "stripe")
	chargeTotal.IncrCounter(1, "usd", "stripe")
	return nil
}

A call with the wrong number of values logs an error and emits with a single invalid_tags tag, so the mistake shows up in the backend instead of silently producing a differently-shaped series. Describe.Type must be one of metrics.TypeCounter, metrics.TypeGauge or metrics.TypeSample; the legacy value "summary" is accepted as an alias of metrics.TypeSample, since that is how the Prometheus sink exports samples. Any other value is logged and used as given.

Configuration

Field Default (DefaultConfig) Effect
ServiceName the argument prefixes every key, or becomes the service tag
HostName os.Hostname() prefixes every key, or becomes the host tag
EnableHostname false include the hostname as a key prefix
EnableHostnameLabel false include the hostname as a tag instead (wins over the prefix)
EnableServiceLabel false include the service as a tag instead of a prefix
EnableTypePrefix false prefix the key with counter, gauge or sample
EnableRuntimeMetrics true emit runtime_* gauges and GC samples in the background, until Close
ProfileInterval 1s how often runtime metrics are collected
TimerGranularity 1ms the unit MeasureSince reports
GlobalPrefix empty prefixed before everything else
GlobalTags empty tags added to every metric
NumberLabelPrefix empty prefix for tag values of 15+ digits, so large IDs are not coerced to floats
AllowedPrefixes / BlockedPrefixes empty prefix filters, applied to the final key; a block wins over an allow
FilterDefault true verdict for keys that match no prefix rule

The final metric name is assembled in this order, including only the parts that are configured:

<GlobalPrefix>_<ServiceName>_<type>_<HostName>_<key>

For example, GlobalPrefix: "es", ServiceName: "billing", EnableTypePrefix: true turns IncrCounter("charge_total", 1) into es_billing_counter_charge_total.

Filtering

Filters are applied to the assembled key, before the sink is called. A key matching BlockedPrefixes is dropped, a key matching AllowedPrefixes is emitted, and a key matching neither falls back to FilterDefault. A block always wins over an allow.

cfg := metrics.DefaultConfig("billing")
cfg.BlockedPrefixes = []string{"billing_runtime_"} // drop runtime noise
cfg.FilterDefault = true                           // everything else passes

To publish only a known set of metrics, list them and turn the default off:

cfg.AllowedPrefixes = []string{"billing_charge_", "billing_refund_"}
cfg.FilterDefault = false // anything else is dropped

The rules can be replaced at runtime, from any goroutine, while others emit:

metrics.UpdateFilter(nil, []string{"billing_runtime_"})

UpdateFilter swaps the whole rule set atomically, so an emission sees either the old rules or the new ones. It does not write back to the Config the provider was built from; Metrics.AllowMetric and Metrics.Help report the rules in effect.

Tags

Tags become Prometheus labels, CloudWatch dimensions, or part of the key in the in-memory sink.

metrics.IncrCounter("charge_total", 1,
	metrics.Tag{Name: "currency", Value: "usd"},
	metrics.Tag{Name: "provider", Value: "stripe"})

Every distinct combination of values creates a new series. Tag with bounded values — a status, a region, a provider name — never with user ids, request ids, or error strings. The Prometheus sink keeps counters for the lifetime of the process unless Opts.CounterExpiration is set, so an unbounded tag grows memory until then.

Sinks

Sink Package Use for
prometheus.Sink metrics/prometheus scraped /metrics endpoint
prometheus.PushSink metrics/prometheus batch jobs, through a Pushgateway
cloudwatch.Sink metrics/cloudwatch AWS CloudWatch custom metrics
metrics.InmemSink root in-process aggregation, debugging, tests
metrics.FanoutSink root several sinks at once
metrics.BlackholeSink root discard everything (the default)
Prometheus
sink, err := esprom.NewSinkFrom(esprom.Opts{
	Name:       "billing",
	Expiration: time.Minute,          // drop gauges and summaries idle for a minute
	Registerer: prom.DefaultRegisterer,
	Help:       cfg.Help(chargeMetrics),
	CounterDefinitions: []esprom.CounterDefinition{
		{
			Name: "billing_charge_total",
			Help: "billing_charge_total provides the number of charges",
		},
	},
})

Pre-declared series are initialized at zero, are never expired, and appear in the first scrape even before anything is emitted. Metric names are sanitized ( , ., =, -, / become _). Samples are exported as summaries with the 0.5, 0.9 and 0.99 quantiles.

A series the client library would reject (an empty name, a label name starting with __, invalid UTF-8) is dropped and logged instead of being registered: one such series would otherwise fail every scrape with HTTP 500 until it expires. Two sinks on the same registerer need different Names; NewSinkFrom returns an error, and no sink, for a duplicate.

Expiration covers gauges and summaries. Counters are kept for the lifetime of the process, because deleting one resets the series and breaks rate() across the gap. If a counter label is unbounded and a reset is the lesser problem, set CounterExpiration:

opts := esprom.Opts{
	Expiration:        time.Minute,
	CounterExpiration: time.Hour, // drop counters idle for an hour
}

To push instead of being scraped, use NewPushSinkFrom, which takes the same options, and Shutdown to stop the push loop:

sink, err := esprom.NewPushSinkFrom(gatewayAddr, time.Minute, "nightly-job", opts)
if err != nil {
	return err
}
defer sink.Shutdown()
CloudWatch
sink, err := cloudwatch.NewSink(&cloudwatch.Config{
	AwsRegion:       "us-west-2",
	Namespace:       "billing",
	PublishInterval: time.Minute,
	MetricsExpiry:   time.Hour,
	WithSampleCount: true, // also publish _count, _sum and _avg per sample
	WithCleanup:     true, // each publish covers one interval
})
if err != nil {
	return err
}
go sink.Run(ctx) // publishes until ctx is cancelled
if _, err = metrics.NewGlobal(cfg, sink); err != nil {
	return err
}

Metrics are aggregated in memory and published with PutMetricData on the interval, in batches of 1000. Credentials come from the standard AWS chain; AWS_REGION or AWS_DEFAULT_REGION is used when AwsRegion is empty. Prefer WithCleanup: true — without it each publish repeats the running total.

In memory, and dumping on a signal
inm := metrics.NewInmemSink(10*time.Second, time.Minute) // bucket, retention
sig := metrics.DefaultInmemSignal(inm)                   // SIGUSR1, SIGBREAK on Windows
defer sig.Stop()

if _, err := metrics.NewGlobal(metrics.DefaultConfig("billing"), inm); err != nil {
	return err
}

// a JSON-shaped snapshot of the last finished interval, for a debug endpoint
summary, err := inm.DisplayMetrics()

retain must be at least twice interval, since one interval is always still being aggregated. Values outside that range are clamped, with an error logged naming the parameter; factory.NewMetricSinkFromURL rejects them instead. Data returns a snapshot, so it is safe to read while other goroutines emit.

On the signal, the retained intervals are written to stderr:

[2026-09-19 14:57:33 +0000 UTC][G] "billing_charge_amount": 42.000
[2026-09-19 14:57:33 +0000 UTC][C] "billing_charge_total": Count: 3 Sum: 3.000 LastUpdated: …
[2026-09-19 14:57:33 +0000 UTC][S] "billing_charge_duration.usd": Count: 3 Min: 1.000 Mean: 41.000 Max: 80.000 Stddev: 39.509 Sum: 123.000 LastUpdated: …
Several backends at once
fan := metrics.NewFanoutSink(promSink, cwSink)
_, err := metrics.NewGlobal(cfg, fan)

Emission is sequential, so a slow sink slows down the caller.

From a URL
sink, err := factory.NewMetricSinkFromURL("inmem://localhost?interval=10s&retain=1m")

Only the inmem scheme is registered; the other sinks need options that do not map onto a URL.

Runtime metrics

With EnableRuntimeMetrics (on in DefaultConfig) a background goroutine emits, every ProfileInterval:

runtime_num_goroutines, runtime_alloc_bytes, runtime_sys_bytes, runtime_malloc_count, runtime_free_count, runtime_heap_objects, runtime_total_gc_pause_ns, runtime_total_gc_runs, and runtime_gc_pause_ns as a sample per GC cycle.

The collector calls runtime.ReadMemStats, which stops the world; on a large heap, raise ProfileInterval or turn the collector off (FINDINGS #14). Call Close to stop it:

prov, err := metrics.New(cfg, sink)
if err != nil {
	return err
}
defer prov.Close()

Testing instrumentation

Use an InmemSink and read it back:

inm := metrics.NewInmemSink(time.Second, time.Minute)
prov, err := metrics.New(&metrics.Config{FilterDefault: true}, inm)
require.NoError(t, err)
defer prov.Close()

prov.IncrCounter("charge_total", 1, metrics.Tag{Name: "currency", Value: "usd"})

data := inm.Data()
require.Len(t, data, 1)
assert.Contains(t, data[0].Counters, "charge_total;currency=usd")

The aggregation key is the metric name with its tags appended as ;name=value. For assertions on what reaches the sink, implement the three-method metrics.Sink interface with a testify mock.

Development

make test     # go test ./...
make covtest  # tests + coverage report (CI gate: 92%)
make test RACE=true  # tests under the race detector
make lint     # golangci-lint
make all      # clean, tools, generate, covtest

License

MIT

Documentation

Overview

Package metrics provides a lightweight facade to instrument Go code with counters, gauges and samples, and to deliver them to a pluggable backend.

Model

A Metrics instance owns a Config and a single Sink. Application code emits through the Provider interface (or the package-level proxies that forward to the global instance), and the sink converts the emission into a backend-specific representation:

Every emission carries zero or more Tag values, which become labels (Prometheus), dimensions (CloudWatch) or key suffixes (InmemSink).

Key composition

Config.Prepare builds the final metric key from the configured parts, in this order (innermost last):

<GlobalPrefix>_<ServiceName>_<type>_<HostName>_<key>

Each part is included only when the matching option is set: Config.GlobalPrefix, Config.ServiceName (unless Config.EnableServiceLabel turns it into a label), Config.EnableTypePrefix, and Config.HostName (unless Config.EnableHostnameLabel turns it into a label). The result is then matched against Config.AllowedPrefixes and Config.BlockedPrefixes by Config.AllowMetric; blocked metrics are dropped before reaching the sink.

Usage

Create a sink, wire it into the global provider and emit:

sink := metrics.NewInmemSink(10*time.Second, time.Minute)
cfg := metrics.DefaultConfig("billing")
cfg.GlobalTags = []metrics.Tag{{Name: "env", Value: "prod"}}
if _, err := metrics.NewGlobal(cfg, sink); err != nil {
	return err
}

defer metrics.MeasureSince("charge_duration", time.Now(),
	metrics.Tag{Name: "currency", Value: "usd"})

Prefer declaring metrics up-front with Describe, which validates the tag count on every emission and feeds help text to the Prometheus sink:

var chargeDuration = &metrics.Describe{
	Type:         metrics.TypeSample,
	Name:         "charge_duration",
	Help:         "charge_duration provides the time spent charging a card",
	RequiredTags: []string{"currency"},
}

defer chargeDuration.MeasureSince(time.Now(), "usd")

Sinks

The root package ships InmemSink (in-memory aggregation, optionally dumped on a signal by InmemSignal), BlackholeSink and FanoutSink. Network backends live in subpackages: `prometheus` and `cloudwatch`. The `factory` subpackage builds a sink from a URL.

Concurrency

All sinks in this module are safe for concurrent emission, and Metrics.UpdateFilter may be called while other goroutines emit: it replaces the filter rules as a whole. Writing to the fields of a Config that is in use is not synchronized; construct it, pass it to New, and change the filters through Metrics.UpdateFilter afterwards.

Metrics.Close stops the runtime metrics collector started by New.

Index

Constants

View Source
const (
	TypeCounter = "counter"
	TypeSample  = "sample"
	TypeGauge   = "gauge"
)

Metric types, used as the type prefix when Config.EnableTypePrefix is set, and as Describe.Type.

View Source
const (
	// DefaultSignal is used with DefaultInmemSignal
	DefaultSignal = syscall.SIGUSR1
)

Variables

This section is empty.

Functions

func AddSample

func AddSample(key string, val float64, tags ...Tag)

AddSample emits a sample on the global instance, for timing information where quantiles are used.

func IncrCounter

func IncrCounter(key string, val float64, tags ...Tag)

IncrCounter emits a counter increment on the global instance.

func MeasureSince

func MeasureSince(key string, start time.Time, tags ...Tag)

MeasureSince emits the time elapsed since start as a sample on the global instance, in units of Config.TimerGranularity.

func SetGauge

func SetGauge(key string, val float64, tags ...Tag)

SetGauge emits a gauge on the global instance, which should retain the last value it is set to.

func StringStartsWithOneOf added in v0.2.0

func StringStartsWithOneOf(value string, items []string) bool

StringStartsWithOneOf returns true if one of items slice is a prefix of specified value.

func UpdateFilter

func UpdateFilter(allow, block []string)

UpdateFilter replaces the prefix filters of the global instance.

Types

type AggregateSample

type AggregateSample struct {
	Count       int       // The count of emitted pairs
	Rate        float64   // The values rate per time unit (usually 1 second)
	Sum         float64   // The sum of values
	SumSq       float64   `json:"-"` // The sum of squared values
	Min         float64   // Minimum value
	Max         float64   // Maximum value
	LastUpdated time.Time `json:"-"` // When value was last updated
}

AggregateSample is used to hold aggregate metrics about a sample.

It is not safe for concurrent use: callers must serialize Ingest against readers, which InmemSink does with the interval lock.

func (*AggregateSample) Ingest

func (a *AggregateSample) Ingest(v float64, rateDenom float64)

Ingest is used to update a sample with a new value. rateDenom is the length of the aggregation interval in the rate time unit, and must not be zero.

func (*AggregateSample) Mean

func (a *AggregateSample) Mean() float64

Mean computes the mean of the ingested values, or 0 when there are none.

func (*AggregateSample) Stddev

func (a *AggregateSample) Stddev() float64

Stddev computes the standard deviation of the ingested values. It returns 0 for fewer than two values.

The result is derived from the running sums, which loses precision when the values are large relative to their spread.

func (*AggregateSample) String

func (a *AggregateSample) String() string

String returns a human readable summary of the sample, used by InmemSignal when dumping metrics.

type BlackholeSink

type BlackholeSink struct{}

BlackholeSink is used to just blackhole messages. It is the sink installed on the global instance until NewGlobal is called.

func (*BlackholeSink) AddSample

func (*BlackholeSink) AddSample(_ string, _ float64, _ []Tag)

AddSample discards the value.

func (*BlackholeSink) IncrCounter

func (*BlackholeSink) IncrCounter(_ string, _ float64, _ []Tag)

IncrCounter discards the value.

func (*BlackholeSink) SetGauge

func (*BlackholeSink) SetGauge(_ string, _ float64, _ []Tag)

SetGauge discards the value.

type Config

type Config struct {
	// ServiceName is prefixed to keys to separate services,
	// or emitted as the "service" label when EnableServiceLabel is set.
	ServiceName string
	// HostName to use. If not provided and EnableHostname is set,
	// DefaultConfig uses os.Hostname.
	HostName string
	// EnableHostname enables prefixing keys with HostName.
	// Ignored when EnableHostnameLabel is set.
	EnableHostname bool
	// EnableHostnameLabel enables adding HostName as the "host" tag.
	// Takes precedence over EnableHostname.
	EnableHostnameLabel bool
	// EnableServiceLabel enables adding ServiceName as the "service" tag
	// instead of prefixing it to the key.
	EnableServiceLabel bool
	// EnableRuntimeMetrics enables profiling of runtime metrics
	// (GC, Goroutines, Memory). New starts a background goroutine for it,
	// which runs until Metrics.Close is called.
	EnableRuntimeMetrics bool
	// EnableTypePrefix prefixes the key with the metric type:
	// TypeCounter, TypeGauge or TypeSample.
	EnableTypePrefix bool
	// TimerGranularity is the unit reported by MeasureSince.
	// Defaults to time.Millisecond.
	TimerGranularity time.Duration
	// ProfileInterval is the interval at which runtime metrics are collected,
	// when EnableRuntimeMetrics is set. Defaults to time.Second.
	ProfileInterval time.Duration
	// GlobalTags are added to every metric.
	GlobalTags []Tag
	// GlobalPrefix is prepended to every metric key, before all other prefixes.
	GlobalPrefix string
	// NumberLabelPrefix is prepended to tag values that are 64-bit numbers,
	// to keep backends from coercing large IDs into floats.
	// A value is treated as a number when it has at least 15 digits.
	NumberLabelPrefix string

	// AllowedPrefixes is a list of metric key prefixes to allow.
	// A key matching one of them is emitted regardless of FilterDefault.
	AllowedPrefixes []string
	// BlockedPrefixes is a list of metric key prefixes to block.
	// A blocked prefix always wins over an allowed one.
	BlockedPrefixes []string
	// FilterDefault is the verdict for keys that no prefix rule matches:
	// true allows them, false drops them.
	FilterDefault bool
}

Config is used to configure metrics settings.

The zero value emits nothing: FilterDefault must be set to true, or an explicit AllowedPrefixes list must be provided, for metrics to reach the sink. See Prepare for how the fields combine into the final metric key.

A Config is read on every emission. Copy it into a Metrics with New and change the filters through Metrics.UpdateFilter afterwards; writing to the fields of a Config that is in use is not synchronized.

func DefaultConfig

func DefaultConfig(serviceName string) *Config

DefaultConfig provides a sane default configuration: runtime metrics enabled, millisecond timers, no filtering, and the local hostname resolved but not emitted.

func (*Config) AllowMetric added in v0.4.0

func (m *Config) AllowMetric(key string) bool

AllowMetric returns whether the metric should be allowed based on the configured prefix filters.

A key matching BlockedPrefixes is dropped, a key matching AllowedPrefixes is emitted, and a key matching neither falls back to FilterDefault.

func (*Config) Help added in v0.4.0

func (m *Config) Help(providers ...[]*Describe) map[string]string

Help returns the help text of the described metrics, keyed by the final metric name that this configuration produces.

The result is meant for prometheus.Opts.Help, so the exported HELP line matches the metric. Metrics blocked by the configured filters are omitted; Metrics.Help applies the filters in effect instead.

func (*Config) Prepare added in v0.4.0

func (m *Config) Prepare(typ string, key string, tags ...Tag) (bool, string, []Tag)

Prepare returns the final metric name and tags to emit, and whether the metric passes the configured filters.

The key is assembled from the configured parts, in this order:

<GlobalPrefix>_<ServiceName>_<typ>_<HostName>_<key>

and the returned tags are the given ones followed by GlobalTags plus the optional "host" and "service" labels. The caller's slice is never modified: when tags have to be added, or rewritten for NumberLabelPrefix, the result is a new slice.

type Describe added in v0.4.0

type Describe struct {
	// Type of the metric: TypeCounter, TypeGauge or TypeSample.
	// "summary" is accepted as an alias of TypeSample.
	Type string
	// Name is the metric name
	Name string
	// Help provides description
	Help string
	// RequiredTags is a list of metric tags
	RequiredTags []string
}

Describe provides metric description.

Declaring metrics as package-level Describe values keeps the name, help text and tag names in one place, and validates on every emission that the caller supplies exactly the expected tag values. Its emit methods always go to the global instance.

func (*Describe) AddSample added in v0.4.0

func (d *Describe) AddSample(val float64, tags ...string)

AddSample emits the described sample on the global instance. The values must match Describe.RequiredTags in size and order.

func (*Describe) IncrCounter added in v0.4.0

func (d *Describe) IncrCounter(val float64, tags ...string)

IncrCounter emits the described counter increment on the global instance. The values must match Describe.RequiredTags in size and order.

func (*Describe) MeasureSince added in v0.4.0

func (d *Describe) MeasureSince(start time.Time, tags ...string)

MeasureSince emits the time elapsed since start as the described sample on the global instance. The values must match Describe.RequiredTags in size and order.

func (*Describe) SetGauge added in v0.4.0

func (d *Describe) SetGauge(val float64, tags ...string)

SetGauge emits the described gauge on the global instance. The values must match Describe.RequiredTags in size and order.

func (*Describe) Tags added in v0.4.0

func (d *Describe) Tags(vals ...string) []Tag

Tags constructs tags. The size and order of the vals must match the ones in the description.

On mismatch it logs an error and returns a single "invalid_tags" tag, so a miscounted call is visible in the backend instead of silently emitting under the wrong label set.

type FanoutSink

type FanoutSink []Sink

FanoutSink is used to fan out values to multiple sinks, for example to expose metrics to Prometheus and CloudWatch at once.

Emission is sequential and synchronous, so a slow sink slows down every caller, and a panicking sink prevents the remaining sinks from being called.

func NewFanoutSink

func NewFanoutSink(sinks ...Sink) FanoutSink

NewFanoutSink creates fan-out sink.

func (FanoutSink) AddSample

func (fh FanoutSink) AddSample(key string, val float64, tags []Tag)

AddSample forwards the sample to every sink.

func (FanoutSink) IncrCounter

func (fh FanoutSink) IncrCounter(key string, val float64, tags []Tag)

IncrCounter forwards the counter increment to every sink.

func (FanoutSink) SetGauge

func (fh FanoutSink) SetGauge(key string, val float64, tags []Tag)

SetGauge forwards the gauge to every sink.

type GaugeValue

type GaugeValue struct {
	// Name is the metric name, without the flattened tags.
	Name string
	// Hash is the aggregation key: the name with its tags appended.
	Hash string `json:"-"`
	// Value is the last value set in the interval.
	Value float64

	// Labels are the tags the value was emitted with.
	Labels []Tag `json:"-"`
	// DisplayLabels is the serializable form of Labels,
	// populated by DisplayMetrics.
	DisplayLabels map[string]string `json:"Labels"`
}

GaugeValue provides gauge value.

type InmemSignal

type InmemSignal struct {
	// contains filtered or unexported fields
}

InmemSignal is used to listen for a given signal, and when received, to dump the current metrics from the InmemSink to an io.Writer.

It is a debugging aid: sending the signal to a running process prints the finished aggregation intervals, without requiring a metrics backend.

func DefaultInmemSignal

func DefaultInmemSignal(inmem *InmemSink) *InmemSignal

DefaultInmemSignal returns a new InmemSignal that responds to SIGUSR1 and writes output to stderr. Windows uses SIGBREAK.

func NewInmemSignal

func NewInmemSignal(inmem *InmemSink, sig syscall.Signal, w io.Writer) *InmemSignal

NewInmemSignal creates a new InmemSignal which listens for a given signal, and dumps the current metrics out to a writer.

It starts a goroutine that runs until Stop is called.

func (*InmemSignal) Stop

func (i *InmemSignal) Stop()

Stop is used to stop the InmemSignal from listening. It is idempotent and safe for concurrent use.

type InmemSink

type InmemSink struct {
	// contains filtered or unexported fields
}

InmemSink provides a Sink that does in-memory aggregation without sending metrics over a network. It can be embedded within an application to provide profiling information.

Values are bucketed into fixed aggregation intervals and the oldest buckets are discarded, so memory is bounded by retain/interval times the number of distinct key and tag combinations. Read the aggregated data with Data or DisplayMetrics, or dump it on a signal with InmemSignal.

func NewInmemSink

func NewInmemSink(interval, retain time.Duration) *InmemSink

NewInmemSink is used to construct a new in-memory sink, with an aggregation interval and a maximum retention period.

interval must be greater than zero and retain must be at least twice the interval. Values outside that range are clamped, with an error logged naming the parameter, so a misconfiguration degrades the reported window instead of panicking at the first emission.

func (*InmemSink) AddSample

func (i *InmemSink) AddSample(key string, val float64, tags []Tag)

AddSample records an observation within the current interval, rolled up into an AggregateSample.

func (*InmemSink) Data

func (i *InmemSink) Data() []*IntervalMetrics

Data is used to retrieve all the aggregated metrics, oldest interval first. The last entry is the interval currently being written to.

Every interval is an independent copy, taken under its own lock, including the AggregateSample behind each counter and sample. Reading the result needs no lock and it does not change afterwards. Even a finished interval has to be copied: an emitter can resolve its interval, be descheduled across the bucket rollover, and write to it after it stopped being the current one.

The Tag slices in Labels are shared with the sink and must be treated as read-only.

func (*InmemSink) DisplayMetrics

func (i *InmemSink) DisplayMetrics() (*Summary, error)

DisplayMetrics returns a summary of the metrics from the most recent finished interval, with the values sorted by aggregation key.

It returns an error when no interval has been recorded yet. The values come from the snapshot taken by Data, which is an independent copy, so no lock is needed while it is formatted and the result cannot change under the caller.

func (*InmemSink) IncrCounter

func (i *InmemSink) IncrCounter(key string, val float64, tags []Tag)

IncrCounter accumulates values within the current interval.

func (*InmemSink) SetGauge

func (i *InmemSink) SetGauge(key string, val float64, tags []Tag)

SetGauge retains the last value it is set to, within the current interval.

type IntervalMetrics

type IntervalMetrics struct {
	sync.RWMutex

	// The start time of the interval
	Interval time.Time

	// Gauges maps the key to the last set value
	Gauges map[string]GaugeValue

	// Counters maps the string key to a sum of the counter
	// values
	Counters map[string]SampledValue

	// Samples maps the key to an AggregateSample,
	// which has the rolled up view of a sample
	Samples map[string]SampledValue
}

IntervalMetrics stores the aggregated metrics for a specific interval.

The embedded RWMutex guards the maps; hold it while reading them.

func NewIntervalMetrics

func NewIntervalMetrics(intv time.Time) *IntervalMetrics

NewIntervalMetrics creates a new IntervalMetrics for a given interval.

type Metrics

type Metrics struct {
	Config
	// contains filtered or unexported fields
}

Metrics represents an instance of a metrics sink that can be used to emit.

A Metrics value is safe for concurrent emission, and for concurrent UpdateFilter. It must not be copied.

func New

func New(conf *Config, sink Sink) (*Metrics, error)

New is used to create a new instance of Metrics.

The configuration is copied, including its tag and prefix slices, so later changes to conf do not affect the returned instance; use Metrics.UpdateFilter to change the filters. TimerGranularity and ProfileInterval fall back to their defaults when they are not positive. When conf.EnableRuntimeMetrics is set, New starts a collector goroutine that Metrics.Close stops. The error is always nil and exists for API compatibility.

func NewGlobal

func NewGlobal(conf *Config, sink Sink) (*Metrics, error)

NewGlobal is the same as New, but it assigns the metrics object to be used globally as well as returning it.

The instance previously installed as global is not closed; close it first if it was collecting runtime metrics.

func (*Metrics) AddSample

func (m *Metrics) AddSample(key string, val float64, tags ...Tag)

AddSample emits a sample, for timing information where quantiles are used. The metric is dropped when the configured filters block the key.

func (*Metrics) AllowMetric added in v0.10.151

func (m *Metrics) AllowMetric(key string) bool

AllowMetric reports whether the key passes the filters currently in effect.

func (*Metrics) Close added in v0.10.151

func (m *Metrics) Close()

Close stops the runtime metrics collector started by New. It is idempotent and safe for concurrent use. Emission keeps working after Close; only the background collector is stopped.

func (*Metrics) Help added in v0.10.151

func (m *Metrics) Help(providers ...[]*Describe) map[string]string

Help returns the help text of the described metrics, keyed by the final metric name, omitting the metrics blocked by the filters currently in effect. It differs from Config.Help only after UpdateFilter has been called.

func (*Metrics) IncrCounter

func (m *Metrics) IncrCounter(key string, val float64, tags ...Tag)

IncrCounter emits a counter increment, which should accumulate values. The metric is dropped when the configured filters block the key.

func (*Metrics) MeasureSince

func (m *Metrics) MeasureSince(key string, start time.Time, tags ...Tag)

MeasureSince emits the time elapsed since start as a sample, in units of Config.TimerGranularity (milliseconds by default).

It is typically deferred at the top of the measured call:

defer m.MeasureSince("handler_duration", time.Now())

func (*Metrics) Prepare added in v0.10.151

func (m *Metrics) Prepare(typ string, key string, tags ...Tag) (bool, string, []Tag)

Prepare returns the final metric name and tags to emit, and whether the metric passes the filters currently in effect.

It assembles the key exactly like Config.Prepare, but takes the filter verdict from the rules installed by UpdateFilter rather than from the embedded Config, which keeps the rules it was constructed with.

func (*Metrics) SetGauge

func (m *Metrics) SetGauge(key string, val float64, tags ...Tag)

SetGauge emits a gauge, which should retain the last value it is set to. The metric is dropped when the configured filters block the key.

func (*Metrics) UpdateFilter

func (m *Metrics) UpdateFilter(allow, block []string)

UpdateFilter overwrites the existing filter with the given rules. It is safe to call while other goroutines emit metrics: the rules are replaced as a whole, so an emission sees either the old or the new set.

The rules are copied. They do not update the embedded Config, which keeps the values Metrics was constructed with; Metrics.AllowMetric and Metrics.Prepare report the rules in effect.

type PointValue

type PointValue struct {
	Name   string
	Points []float64
}

PointValue provides point value.

It is unused: EmitKey is not part of the Sink interface, because Prometheus has no type that retains an arbitrary number of values. The type is retained for API compatibility only.

type Provider

type Provider interface {
	// SetGauge should retain the last value it is set to
	SetGauge(key string, val float64, tags ...Tag)
	// IncrCounter should accumulate values
	IncrCounter(key string, val float64, tags ...Tag)
	// AddSample is for timing information, where quantiles are used
	AddSample(key string, val float64, tags ...Tag)
	// MeasureSince emits the time elapsed since start as a sample
	MeasureSince(key string, start time.Time, tags ...Tag)
}

Provider is the interface application code should depend on to emit metrics. It is implemented by *Metrics, and mirrored by the package-level functions that emit to the global instance.

type SampledValue

type SampledValue struct {
	// Name is the metric name, without the flattened tags.
	Name string
	// Hash is the aggregation key: the name with its tags appended.
	Hash string `json:"-"`
	*AggregateSample
	// Mean of the aggregated values, computed by DisplayMetrics.
	Mean float64
	// Stddev of the aggregated values, computed by DisplayMetrics.
	Stddev float64

	// Labels are the tags the value was emitted with.
	Labels []Tag `json:"-"`
	// DisplayLabels is the serializable form of Labels,
	// populated by DisplayMetrics.
	DisplayLabels map[string]string `json:"Labels"`
}

SampledValue provides sample value.

Mean and Stddev are derived once, when DisplayMetrics formats the value, from the AggregateSample it points to. In a value obtained from InmemSink.Data or DisplayMetrics that AggregateSample is an independent copy, so the three stay consistent with each other and do not change afterwards.

type Sink

type Sink interface {
	// SetGauge should retain the last value it is set to
	SetGauge(key string, val float64, tags []Tag)
	// IncrCounter should accumulate values
	IncrCounter(key string, val float64, tags []Tag)
	// AddSample is for timing information, where quantiles are used
	AddSample(key string, val float64, tags []Tag)
}

The Sink interface is used to transmit metrics information to an external system.

Implementations receive the final key, after Config.Prepare has applied the prefixes and filters, and must be safe for concurrent use. The tags slice may be retained by the sink — InmemSink keeps it as the labels of the aggregated value — so callers must not modify a tags slice after passing it to an emit method.

func NewInmemSinkFromURL

func NewInmemSinkFromURL(u *url.URL) (Sink, error)

NewInmemSinkFromURL creates an InmemSink from a URL. It is used (and tested) from factory.NewMetricSinkFromURL.

The "interval" and "retain" query parameters are required and must parse as durations, for example:

inmem://localhost?interval=10s&retain=1m

Unlike NewInmemSink, an interval that is not positive or a retention shorter than two intervals is rejected with an error instead of being clamped.

type Summary

type Summary struct {
	Timestamp string
	Gauges    []GaugeValue
	Counters  []SampledValue
	Samples   []SampledValue
}

Summary holds a roll-up of metrics info for a given interval, in a shape suitable for JSON serialization on a debug endpoint.

type Tag

type Tag struct {
	// Name is the label name. It should be a valid identifier for the
	// destination backend: lower case letters, digits and underscores.
	Name string
	// Value is the label value.
	Value string
}

Tag is used to add dimensions to metrics.

A tag becomes a Prometheus label, a CloudWatch dimension, or part of the aggregation key in InmemSink. Every distinct value combination creates a new series in the backend, so keep the cardinality bounded: never tag with user IDs, request IDs or other unbounded values.

Directories

Path Synopsis
Package cloudwatch provides a metrics.Sink that publishes to AWS CloudWatch.
Package cloudwatch provides a metrics.Sink that publishes to AWS CloudWatch.
Package factory constructs a metrics.Sink from a URL, so the sink can be selected by configuration instead of by import.
Package factory constructs a metrics.Sink from a URL, so the sink can be selected by configuration instead of by import.
Package prometheus provides a metrics.Sink backed by the Prometheus client library.
Package prometheus provides a metrics.Sink backed by the Prometheus client library.

Jump to

Keyboard shortcuts

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