metricsbp

package
v0.9.16 Latest Latest
Warning

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

Go to latest
Published: Jan 8, 2024 License: BSD-3-Clause Imports: 18 Imported by: 0

Documentation

Overview

Package metricsbp provides metrics related features for baseplate.go, based on go-kit metrics package.

There are three main parts of this package:

1. Wrappers of go-kit metrics to provide easy to use create on-the-fly metrics, similar to what we have in Baseplate.py.

2. Helper function for use cases of pre-create the metrics before using them.

3. Sampled counter/histogram implementations.

This package comes with benchmark test to show the performance difference between pre-created metrics, on-the-fly metrics, and on-the-fly with additional tags metrics:

$ go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/reddit/baseplate.go/metricsbp
BenchmarkStatsd/pre-create/histogram-8         	 8583646	       124 ns/op	      44 B/op	       0 allocs/op
BenchmarkStatsd/pre-create/timing-8            	10221859	       120 ns/op	      47 B/op	       0 allocs/op
BenchmarkStatsd/pre-create/counter-8           	10205341	       120 ns/op	      47 B/op	       0 allocs/op
BenchmarkStatsd/pre-create/gauge-8             	96462238	        12.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkStatsd/on-the-fly/histogram-8         	 4665778	       256 ns/op	      99 B/op	       2 allocs/op
BenchmarkStatsd/on-the-fly/timing-8            	 4784816	       273 ns/op	     126 B/op	       2 allocs/op
BenchmarkStatsd/on-the-fly/counter-8           	 4818908	       259 ns/op	     125 B/op	       2 allocs/op
BenchmarkStatsd/on-the-fly/gauge-8             	28754060	        40.6 ns/op	       0 B/op	       0 allocs/op
BenchmarkStatsd/on-the-fly-with-tags/histogram-8         	 2624264	       453 ns/op	     192 B/op	       4 allocs/op
BenchmarkStatsd/on-the-fly-with-tags/timing-8            	 2639377	       449 ns/op	     192 B/op	       4 allocs/op
BenchmarkStatsd/on-the-fly-with-tags/counter-8           	 2600418	       457 ns/op	     193 B/op	       4 allocs/op
BenchmarkStatsd/on-the-fly-with-tags/gauge-8             	 3429901	       339 ns/op	     112 B/op	       3 allocs/op
PASS
ok  	github.com/reddit/baseplate.go/metricsbp	18.675s

Index

Examples

Constants

View Source
const (
	// DefaultSampleRate is the default value to be used when *SampleRate in
	// Config is nil (zero value).
	DefaultSampleRate = 1

	// DefaultBufferSize is the default value to be used when BufferSize in
	// Config is 0.
	DefaultBufferSize = 4096
)

Default values to be used in the config.

Variables

M is short for "Metrics".

This is the global Statsd to use. It's pre-initialized with one that sends all metrics to a blackhole.

View Source
var ReporterTickerInterval = time.Minute

ReporterTickerInterval is the interval the reporter sends data to statsd server. Default is one minute.

View Source
var SysStatsTickerInterval = time.Second * 10

SysStatsTickerInterval is the interval we pull and report sys stats. Default is 10 seconds.

Functions

func BoolString added in v0.9.5

func BoolString(b bool) string

BoolString returns the string version of a boolean value that should be used in a statsd metric tag.

func CheckNilFields

func CheckNilFields(root interface{}) []string

CheckNilFields returns all the nil value fields inside root.

root should be a value to a struct, or a pointer to a struct, otherwise this function will panic. The return value would be the field names of all the uninitialized fields recursively.

For example, for the following code:

type Bar struct {
  A io.Reader
  B io.Reader
  c io.Reader
  D struct{
    A io.Reader
    B io.Reader
  }
}

func main() {
  fields := CheckNilInterfaceFields(
    &Bar{
      A: strings.NewReader("foo"),
      D: {
        B: bytes.NewReader([]bytes("bar")),
      },
    },
  )
}

fields should contain 3 strings: "Bar.B", "Bar.c", and "Bar.D.A".

Special case: When root itself is nil, or pointer to nil pointer, a single, empty string ("") will be returned.

The main use case of this function is for pre-created metrics. A common pattern is to define a struct contains all the metrics, initialize them in main function, then pass the struct down to the handler functions to use. It has better performance over creating metrics on the fly when using, but comes with a down side that if you added a new metric to the struct but forgot to initialize it in the main function, the code would panic when it's first used.

Use this function to check the metrics struct after initialization could help you panic earlier and in a more predictable way.

Example

This example demonstrate how to use CheckNilFields in your microservice code to pre-create frequently used metrics.

package main

import (
	"context"
	"fmt"

	"github.com/reddit/baseplate.go/metricsbp"

	"github.com/go-kit/kit/metrics"
)

type SubMetrics struct {
	MyHistogram metrics.Histogram
	MyGauge     metrics.Gauge
}

type PreCreatedMetrics struct {
	MyCounter    metrics.Counter
	MySubMetrics SubMetrics
}

// This example demonstrate how to use CheckNilFields in your microservice code
// to pre-create frequently used metrics.
func main() {
	// In reality these should come from flag or other configurations.
	const (
		prefix     = "myservice"
		statsdAddr = "localhost:1234"
		sampleRate = 1
	)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	metricsbp.M = metricsbp.NewStatsd(
		ctx,
		metricsbp.Config{
			Namespace:           prefix,
			Endpoint:            statsdAddr,
			HistogramSampleRate: metricsbp.Float64Ptr(sampleRate),
		},
	)

	// Initialize metrics
	m := PreCreatedMetrics{
		MyCounter: metricsbp.M.Counter("my.counter"),
		MySubMetrics: SubMetrics{
			MyHistogram: metricsbp.M.Histogram("my.histogram"),
			// Forgot to initialize MyGauge here
		},
	}
	missingFields := metricsbp.CheckNilFields(m)
	if len(missingFields) > 0 {
		panic(fmt.Sprintf("Uninitialized metrics: %v", missingFields))
	}

	// Other initializations.

	// Replace with your actual service starter
	startService := func(m PreCreatedMetrics /* and other args */) {}

	startService(
		m,
		// other args
	)
}
Output:

func Float64Ptr

func Float64Ptr(v float64) *float64

Float64Ptr converts float64 value into pointer.

func InitFromConfig

func InitFromConfig(ctx context.Context, cfg Config) io.Closer

InitFromConfig initializes the global metricsbp.M with the given context and Config and returns an io.Closer to use to close out the metrics client when your server exits.

It also registers CreateServerSpanHook and ConcurrencyCreateServerSpanHook with the global tracing hook registry.

func LogWrapper added in v0.6.0

func LogWrapper(args LogWrapperArgs) log.Wrapper

LogWrapper creates a log.Wrapper implementation with metrics emitting.

Types

type Config

type Config struct {
	// Namespace is the standard prefix applied to all of your metrics, it should
	// include the name of your service.
	Namespace string `yaml:"namespace"`

	// Endpoint is the endpoint for your metrics backend.
	Endpoint string `yaml:"endpoint"`

	// Tags are the base tags that will be applied to all metrics.
	Tags Tags `yaml:"tags"`

	// HistogramSampleRate is the fraction of histograms (including timings) that
	// you want to send to your metrics  backend.
	//
	// Optional, defaults to 1 (100%).
	HistogramSampleRate *float64 `yaml:"histogramSampleRate"`

	// When Endpoint is configured,
	// BufferSize can be used to buffer writes to statsd collector together.
	//
	// Set it to an appropriate number will reduce the number of UDP messages sent
	// to the statsd collector. (Recommendation: 4KB)
	//
	// It's guaranteed that every single UDP message will not exceed BufferSize,
	// unless a single metric line exceeds it
	// (usually around 100 bytes depending on the length of the metric path).
	//
	// When it's 0 (default), DefaultBufferSize will be used.
	//
	// To disable buffering, set it to negative value (e.g. -1) or a value smaller
	// than a single statsd metric line (usually around 100, so 1 works),
	// on ReporterTickerInterval we will write one UDP message per metric to the
	// statsd collector.
	BufferSize int `yaml:"bufferSize"`

	// The log level used by the reporting goroutine.
	LogLevel log.Level `yaml:"logLevel"`

	// RunSysStats indicates that you want to publish system stats.
	//
	// Optional, default to false.
	RunSysStats bool `yaml:"runSysStats"`

	// By default, when Endpoint is empty,
	// the Statsd uses a blackhole sink to send statsd metrics to.
	// Set this to true to buffer all statsd metrics in memory until they are read
	// explicitly (via Statsd.WriteTo).
	//
	// This is provided only for tests to verify the statsd metrics.
	// Using it in production code with empty Endpoint will cause the memory used
	// by Statsd to grow unbounded over the time.
	// It also has no effects when Endpoint is non-empty.
	//
	// Optional, default to false.
	BufferInMemoryForTesting bool `yaml:"-"`
}

Config is the confuration struct for the metricsbp package.

Can be deserialized from YAML.

type CreateServerSpanHook

type CreateServerSpanHook struct {
	// Optional, will fallback to M when it's nil.
	Metrics *Statsd
}

CreateServerSpanHook registers each Server Span with a MetricsSpanHook.

Example

This example demonstrates how to use CreateServerSpanHook.

package main

import (
	"github.com/reddit/baseplate.go/metricsbp"
	"github.com/reddit/baseplate.go/tracing"
)

func main() {
	// initialize the CreateServerSpanHook
	hook := metricsbp.CreateServerSpanHook{}

	// register the hook with Baseplate
	tracing.RegisterCreateServerSpanHooks(hook)
}
Output:

func (CreateServerSpanHook) OnCreateServerSpan

func (h CreateServerSpanHook) OnCreateServerSpan(span *tracing.Span) error

OnCreateServerSpan registers MetricSpanHooks on a server Span.

type LogWrapperArgs added in v0.6.0

type LogWrapperArgs struct {
	// The metrics path of the counter.
	//
	// Optional. If it's non-empty,
	// every time the generaged log.Wrapper is called, counter will be added by 1.
	Counter string

	// Additional tags to be applied to the metrics, optional.
	Tags Tags

	// Statsd to use.
	//
	// Optional. If this is nil, metricsbp.M will be used instead.
	Statsd *Statsd

	// The base log.Wrapper implementation.
	//
	// Optional. If this is nil,
	// then LogWrapper implementation will only emit metrics without logging.
	Wrapper log.Wrapper
}

LogWrapperArgs defines the args used by LogWrapper.

type RateArgs added in v0.8.0

type RateArgs struct {
	// Name of the metric, required.
	Name string

	// Sampling rate, required.
	//
	// If AlreadySampledAt is nil,
	// this controls both the sample rate reported to statsd and the rate we use
	// for reporting the metrics.
	//
	// If AlreadySampledAt is non-nil,
	// The sample rate reported to statsd will be Rate*AlreadySampledAt,
	// and Rate controls how we randomly report this metric.
	// It's useful when you are reporting a metric from an already sampled code
	// block, for example:
	//
	//     const rate = 0.01
	//     if randbp.ShouldSampleWithRate(rate) {
	//       if err := myFancyWork(); err != nil {
	//         metricsbp.M.CounterWithRate(metricsbp.RateArgs{
	//           Name: "my.fancy.work.errors",
	//           // 100% report it because we are already sampling it.
	//           Rate: 1,
	//           // but adjust the reporting rate to the actual sample rate.
	//           AlreadySampledAt: metricsbp.Float64Ptr(rate),
	//         }).Add(1)
	//       }
	//     }
	Rate float64

	// Optional. Default to 1 (100%) if it's nil.
	// See the comment on Rate for more details.
	//
	// It will be treated as 1 if >=1, and be trated as 0 if <=0.
	AlreadySampledAt *float64
}

RateArgs defines the args used by -WithRate functions.

func (RateArgs) ReportingRate added in v0.8.0

func (ra RateArgs) ReportingRate() float64

ReportingRate returns the reporting rate according to the args.

type SampledCounter

type SampledCounter struct {
	Counter metrics.Counter

	Rate float64
}

SampledCounter is a metrics.Counter implementation that actually sample the Add calls.

func (SampledCounter) Add

func (c SampledCounter) Add(delta float64)

Add implements metrics.Counter.

func (SampledCounter) With

func (c SampledCounter) With(tagValues ...string) metrics.Counter

With implements metrics.Counter.

type SampledHistogram

type SampledHistogram struct {
	Histogram metrics.Histogram

	Rate float64
}

SampledHistogram is a metrics.Histogram implementation that actually sample the Observe calls.

func (SampledHistogram) Observe

func (h SampledHistogram) Observe(value float64)

Observe implements metrics.Histogram.

func (SampledHistogram) With

func (h SampledHistogram) With(labelValues ...string) metrics.Histogram

With implements metrics.Histogram.

type Statsd

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

Statsd defines a statsd reporter (with influx extension) and the root of the metrics.

It can be used to create metrics, and also maintains the background reporting goroutine,

It supports metrics tags in Influxstatsd format.

Please use NewStatsd to initialize it.

When a *Statsd is nil, any function calls to it will fallback to use M instead, so they are gonna be safe to use (unless M was explicitly overridden as nil). For example:

st := (*metricsbp.Statsd)(nil)
st.Counter("my-counter").Add(1) // does not panic unless metricsbp.M is nil

func NewStatsd

func NewStatsd(ctx context.Context, cfg Config) *Statsd

NewStatsd creates a Statsd object.

It also starts a background reporting goroutine when Endpoint is not empty. The goroutine will be stopped when the passed in context is canceled.

NewStatsd never returns nil.

func (*Statsd) Close

func (st *Statsd) Close() error

Close flushes all metrics not written to collector (if Endpoint was set), and cancel the context, thus stop all background goroutines started by this Statsd.

It blocks until the flushing is completed.

After Close() is called, no more metrics will be send to the remote collector, similar to the situation that this Statsd was initialized without Endpoint set.

After Close() is called, Ctx() will always return an already canceled context.

This function is useful for jobs that exit, to make sure that all metrics are flushed before exiting. For server code, there's usually no need to call Close(), just cancel the context object passed in is sufficient. But server code can also choose to pass in a background context, and use Close() call to do the cleanup instead of canceling the context.

func (*Statsd) Counter

func (st *Statsd) Counter(name string) metrics.Counter

Counter returns a counter metrics to the name, with sample rate inherited from Config.

func (*Statsd) CounterWithRate added in v0.2.0

func (st *Statsd) CounterWithRate(args RateArgs) metrics.Counter

CounterWithRate returns a counter metrics to the name, with sample rate passed in instead of inherited from Config.

func (*Statsd) Ctx

func (st *Statsd) Ctx() context.Context

Ctx provides a read-only access to the context object this Statsd holds.

It's useful when you need to implement your own goroutine to report some metrics (usually gauges) periodically, and be able to stop that goroutine gracefully. For example:

func reportGauges() {
  gauge := metricsbp.M.Gauge("my-gauge")
  go func() {
    ticker := time.NewTicker(time.Minute)
    defer ticker.Stop()

    for {
      select {
      case <- metricsbp.M.Ctx().Done():
        return
      case <- ticker.C:
        gauge.Set(getValue())
      }
    }
  }
}

func (*Statsd) Gauge

func (st *Statsd) Gauge(name string) metrics.Gauge

Gauge returns a gauge metrics to the name.

Please note that gauges are considered "low level". In most cases when you use a Gauge, you want to use RuntimeGauge instead.

func (*Statsd) Histogram

func (st *Statsd) Histogram(name string) metrics.Histogram

Histogram returns a histogram metrics to the name with no specific unit, with sample rate inherited from Config.

func (*Statsd) HistogramWithRate added in v0.2.0

func (st *Statsd) HistogramWithRate(args RateArgs) metrics.Histogram

HistogramWithRate returns a histogram metrics to the name with no specific unit, with sample rate passed in instead of inherited from Config.

func (*Statsd) RunSysStats

func (st *Statsd) RunSysStats()

RunSysStats starts a goroutine to periodically pull and report sys stats.

All the sys stats will be reported as RuntimeGauges.

Canceling the context passed into NewStatsd will stop this goroutine.

func (*Statsd) RuntimeGauge added in v0.4.1

func (st *Statsd) RuntimeGauge(name string) metrics.Gauge

RuntimeGauge returns a Gauge that's suitable to report runtime data.

It will be applied with "runtime." prefix and instance+pid tags automatically.

All gauges reported from RunSysStats are runtime gauges.

func (*Statsd) Timing

func (st *Statsd) Timing(name string) metrics.Histogram

Timing returns a histogram metrics to the name with milliseconds as the unit, with sample rate inherited from Config.

func (*Statsd) TimingWithRate added in v0.2.0

func (st *Statsd) TimingWithRate(args RateArgs) metrics.Histogram

TimingWithRate returns a histogram metrics to the name with milliseconds as the unit, with sample rate passed in instead of inherited from Config.

func (*Statsd) WriteTo added in v0.2.1

func (st *Statsd) WriteTo(w io.Writer) (n int64, err error)

WriteTo calls the underlying statsd implementation's WriteTo function.

Doing this will flush all the buffered metrics to the writer, so in most cases you shouldn't be using it in production code. But it's useful in unit tests to verify that you have the correct metrics you want to report.

When you use this in test code you also want to set BufferInMemoryForTesting to true in the statsd Config, otherwise your test could become flaky.

type Tags added in v0.4.0

type Tags map[string]string

Tags allows you to specify tags as a convenient map and provides helpers to convert them into other formats.

func (Tags) AsStatsdTags added in v0.4.0

func (t Tags) AsStatsdTags() []string

AsStatsdTags returns the tags in the format expected by the statsd metrics client, that is a slice of strings.

This method is nil-safe and will just return nil if the receiver is nil.

type Timer

type Timer struct {
	Histogram metrics.Histogram
	// contains filtered or unexported fields
}

Timer is a timer wraps a histogram.

It's very similar to go-kit's Timer, with a few differences:

1. The reporting unit is millisecond and non-changeable.

2. It's nil-safe (zero values of *Timer or Timer will be safe to call, but they are no-ops)

Example

This example demonstrates how to use Timer.

package main

import (
	"context"

	"github.com/reddit/baseplate.go/metricsbp"
)

// This example demonstrates how to use Timer.
func main() {
	type timerContextKeyType struct{}
	// variables should be properly initialized in production code
	var (
		ctx             context.Context
		timerContextKey timerContextKeyType
	)
	const metricsPath = "dummy.call.timer"

	// initialize and inject a timer into context
	ctx = context.WithValue(
		ctx,
		timerContextKey,
		metricsbp.NewTimer(metricsbp.M.Timing(metricsPath)),
	)
	// do the work
	dummyCall(ctx)
	// get the timer out of context and report
	if t, ok := ctx.Value(timerContextKey).(*metricsbp.Timer); ok {
		t.ObserveDuration()
	}
}

func dummyCall(_ context.Context) {}
Output:

func NewTimer

func NewTimer(h metrics.Histogram) *Timer

NewTimer creates a new Timer and records its start time.

func (*Timer) ObserveDuration

func (t *Timer) ObserveDuration() *Timer

ObserveDuration reports the time elapsed via the wrapped histogram.

This is a shortcut for:

t.ObserveWithEndTime(time.Now())

If either t or *t is zero value, it will be no-op.

The reporting unit is millisecond.

It returns self for chaining.

func (*Timer) ObserveWithEndTime added in v0.7.0

func (t *Timer) ObserveWithEndTime(e time.Time) *Timer

ObserveWithEndTime reports the time elapsed via the wrapped histogram.

If either t or *t is zero value, it will be no-op.

The reporting unit is millisecond.

It returns self for chaining.

func (*Timer) OverrideStartTime added in v0.7.0

func (t *Timer) OverrideStartTime(s time.Time) *Timer

OverrideStartTime overrides the start time for the Timer.

If t is nil, it will be no-op.

It returns self for chaining.

func (*Timer) Start

func (t *Timer) Start() *Timer

Start records the start time for the Timer.

This is a shortcut for:

t.OverrideStartTime(time.Now())

If t is nil, it will be no-op.

It returns self for chaining.

Jump to

Keyboard shortcuts

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