metricsbp

package
v0.0.0-...-bde19ca Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2020 License: BSD-3-Clause Imports: 11 Imported by: 0

Documentation

Overview

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

There are two 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, but they are usually between 2x and 3x slower compare to using pre-created metrics. 2. Helper function for use cases of pre-create the metrics before using them.

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 labels metrics:

$ go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/fizx/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-labels/histogram-8         	 2624264	       453 ns/op	     192 B/op	       4 allocs/op
BenchmarkStatsd/on-the-fly-with-labels/timing-8            	 2639377	       449 ns/op	     192 B/op	       4 allocs/op
BenchmarkStatsd/on-the-fly-with-labels/counter-8           	 2600418	       457 ns/op	     193 B/op	       4 allocs/op
BenchmarkStatsd/on-the-fly-with-labels/gauge-8             	 3429901	       339 ns/op	     112 B/op	       3 allocs/op
PASS
ok  	github.com/fizx/baseplate.go/metricsbp	18.675s

Index

Examples

Constants

View Source
const DefaultSampleRate = 1

DefaultSampleRate is the default value to be used when *SampleRate in StatsdConfig is nil (zero value).

Variables

M is short for "Metrics".

This is the global Statsd to use. It's pre-initialized with one that does not send metrics anywhere, so it won't cause panic even if you don't initialize it before using it (for example, it's safe to be used in test code).

But in production code you should still properly initialize it to actually send your metrics to your statsd collector, usually early in your main function:

func main() {
  flag.Parse()
  ctx, cancel := context.WithCancel(context.Background())
  defer cancel()
  metricsbp.M = metricsbp.NewStatsd{
    ctx,
    metricsbp.StatsdConfig{
      ...
    },
  }
  metricsbp.M.RunSysStats()
  ...
}

func someOtherFunction() {
  ...
  metricsbp.M.Counter("my-counter").Add(1)
  ...
}
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 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/fizx/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.StatsdConfig{
			Prefix:              prefix,
			Address:             statsdAddr,
			CounterSampleRate:   metricsbp.Float64Ptr(sampleRate),
			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.

Types

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/fizx/baseplate.go/metricsbp"
	"github.com/fizx/baseplate.go/tracing"
)

func main() {
	const prefix = "service.server"

	// 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 MetricsLabels

type MetricsLabels map[string]string

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

func (MetricsLabels) AsStatsdLabels

func (l MetricsLabels) AsStatsdLabels() []string

AsStatsdLabels returns the labels 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 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(labelValues ...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 {
	Statsd *influxstatsd.Influxstatsd
	// 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,

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 safe to use (unless M was explicitly overridden as nil), but accessing the fields will still cause panic. For example:

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

func NewStatsd

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

NewStatsd creates a Statsd object.

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

NewStatsd never returns nil.

func (*Statsd) Counter

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

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

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.

It's a shortcut to st.Statsd.NewGauge(name).

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 StatsdConfig.

func (*Statsd) RunSysStats

func (st *Statsd) RunSysStats(labels MetricsLabels)

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

Canceling the context passed into NewStatsd will stop this goroutine.

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 StatsdConfig.

type StatsdConfig

type StatsdConfig struct {
	// Prefix is the common metrics path prefix shared by all metrics managed by
	// (created from) this Metrics object.
	//
	// If it's not ending with a period ("."), a period will be added.
	Prefix string

	// The reporting sample rate used when creating counters and
	// timings/histograms, respectively.
	//
	// DefaultSampleRate will be used when they are nil (zero value).
	//
	// Use Float64Ptr to convert literals or other values that you can't get the
	// pointer directly.
	CounterSampleRate   *float64
	HistogramSampleRate *float64

	// Address is the UDP address (in "host:port" format) of the statsd service.
	//
	// It could be empty string, in which case we won't start the background
	// reporting goroutine.
	//
	// When Address is the empty string,
	// the Statsd object and the metrics created under it will not be reported
	// anywhere,
	// so it can be used in lieu of discarded metrics in test code.
	// But the metrics are still stored in memory,
	// so it shouldn't be used in lieu of discarded metrics in prod code.
	Address string

	// The log level used by the reporting goroutine.
	LogLevel log.Level

	// Labels are the labels/tags to be attached to every metrics created
	// from this Statsd object. For labels/tags only needed by some metrics,
	// use Counter/Gauge/Timing.With() instead.
	Labels MetricsLabels
}

StatsdConfig is the configs used in NewStatsd.

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/fizx/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()

ObserveDuration 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.

func (*Timer) Start

func (t *Timer) Start()

Start records the start time for the Timer.

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

Jump to

Keyboard shortcuts

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