Categorygithub.com/segmentio/stats/v5
modulepackage
5.4.0
Repository: https://github.com/segmentio/stats.git
Documentation: pkg.go.dev

# README

stats CircleCI Go Report Card GoDoc

A Go package for abstracting stats collection.

Installation

go get github.com/segmentio/stats/v5

Migration to v4/v5

Version 4 of the stats package introduced a new way of producing metrics based on defining struct types with tags on certain fields that define how to interpret the values. This approach allows for much more efficient metric production as it allows the program to do quick assignments and increments of the struct fields to set the values to be reported, and submit them all with one call to the stats engine, resulting in orders of magnitude faster metrics production. Here's an example:

type funcMetrics struct {
    calls struct {
        count int           `metric:"count" type:"counter"`
        time  time.Duration `metric:"time"  type:"histogram"`
    } `metric:"func.calls"`
}
t := time.Now()
f()
callTime := time.Since(t)

m := &funcMetrics{}
m.calls.count = 1
m.calls.time = callTime

// Equivalent to:
//
//   stats.Incr("func.calls.count")
//   stats.Observe("func.calls.time", callTime)
//
stats.Report(m)

To avoid greatly increasing the complexity of the codebase some old APIs were removed in favor of this new approach, other were transformed to provide more flexibility and leverage new features.

The stats package used to only support float values. Metrics can now be of various numeric types (see stats.MakeMeasure for a detailed description), therefore functions like stats.Add now accept an interface{} value instead of float64. stats.ObserveDuration was also removed since this new approach makes it obsolete (durations can be passed to stats.Observe directly).

The stats.Engine type used to be configured through a configuration object passed to its constructor function, and a few methods (like Register) were exposed to mutate engine instances. This required synchronization in order to be safe to modify an engine from multiple goroutines. We haven't had a use case for modifying an engine after creating it so the constraint on being thread-safe were lifted and the fields exposed on the stats.Engine struct type directly to communicate that they are unsafe to modify concurrently. The helper methods remain tho to make migration of existing code smoother.

Histogram buckets (mostly used for the prometheus client) are now defined by default on the stats.Buckets global variable instead of within the engine. This decoupling was made to avoid paying the cost of doing histogram bucket lookups when producing metrics to backends that don't use them (like datadog or influxdb for example).

The data model also changed a little. Handlers for metrics produced by an engine now accept a list of measures instead of single metrics, each measure being made of a name, a set of fields, and tags to apply to each of those fields. This allows a more generic and more efficient approach to metric production, better fits the influxdb data model, while still being compatible with other clients (datadog, prometheus, ...). A single timeseries is usually identified by the combination of the measure name, a field name and value, and the set of tags set on that measure. Refer to each client for a details about how measures are translated to individual metrics.

Note that no changes were made to the end metrics being produced by each sub-package (httpstats, procstats, ...). This was important as we must keep the behavior backward compatible since making changes here would implicitly break dashboards or monitors set on the various metric collection systems that this package supports, potentially causing production issues.

If you find a bug or an API is not available anymore but deserves to be ported feel free to open an issue.

Quick Start

Engine

A core concept of the stats package is the Engine. Every program importing the package gets a default engine where all metrics produced are aggregated. The program then has to instantiate clients that will consume from the engine at regular time intervals and report the state of the engine to metrics collection platforms.

package main

import (
    "github.com/segmentio/stats/v5"
    "github.com/segmentio/stats/v5/datadog"
)

func main() {
    // Creates a new datadog client publishing metrics to localhost:8125
    dd := datadog.NewClient("localhost:8125")

    // Register the client so it receives metrics from the default engine.
    stats.Register(dd)

    // Flush the default stats engine on return to ensure all buffered
    // metrics are sent to the dogstatsd server.
    defer stats.Flush()

    // That's it! Metrics produced by the application will now be reported!
    // ...
}

Metrics

package main

import (
    "github.com/segmentio/stats/v5"
    "github.com/segmentio/stats/v5/datadog"
)

func main() {
    stats.Register(datadog.NewClient("localhost:8125"))
    defer stats.Flush()

    // Increment counters.
    stats.Incr("user.login")
    defer stats.Incr("user.logout")

    // Set a tag on a counter increment.
    stats.Incr("user.login", stats.Tag{"user", "luke"})

    // ...
}

Flushing Metrics

Metrics are stored in a buffer, which will be flushed when it reaches its capacity. For most use-cases, you do not need to explicitly send out metrics.

If you're producing metrics only very infrequently, you may have metrics that stay in the buffer and never get sent out. In that case, you can manually trigger stats flushes like so:

func main() {
    stats.Register(datadog.NewClient("localhost:8125"))
    defer stats.Flush()

    // Force a metrics flush every second
    go func() {
      for range time.Tick(time.Second) {
        stats.Flush()
      }
    }()

    // ...
}

Troubleshooting

Use the debugstats package to print all stats to the console.

handler := debugstats.Client{Dst: os.Stdout}
engine := stats.NewEngine("engine-name", handler)
engine.Incr("server.start")

You can use the Grep property to filter the printed metrics for only ones you care about:

handler := debugstats.Client{Dst: os.Stdout, Grep: regexp.MustCompile("server.start")}

Monitoring

Processes

🚧 Go metrics reported with the procstats package were previously tagged with a version label that reported the Go runtime version. This label was renamed to go_version in v4.6.0.

The github.com/segmentio/stats/procstats package exposes an API for creating a statistics collector on local processes. Statistics are collected for the current process and metrics including Goroutine count and memory usage are reported.

Here's an example of how to use the collector:

package main

import (
    "github.com/segmentio/stats/v5/datadog"
    "github.com/segmentio/stats/v5/procstats"
)


func main() {
     stats.Register(datadog.NewClient("localhost:8125"))
     defer stats.Flush()

    // Start a new collector for the current process, reporting Go metrics.
    c := procstats.StartCollector(procstats.NewGoMetrics())

    // Gracefully stops stats collection.
    defer c.Close()

    // ...
}

One can also collect additional statistics on resource delays, such as CPU delays, block I/O delays, and paging/swapping delays. This capability is currently only available on Linux, and can be optionally enabled as follows:

func main() {
    // As above...

    // Start a new collector for the current process, reporting Go metrics.
    c := procstats.StartCollector(procstats.NewDelayMetrics())
    defer c.Close()
}

HTTP Servers

The github.com/segmentio/stats/httpstats package exposes a decorator of http.Handler that automatically adds metric collection to a HTTP handler, reporting things like request processing time, error counters, header and body sizes...

Here's an example of how to use the decorator:

package main

import (
    "net/http"

    "github.com/segmentio/stats/v5/datadog"
    "github.com/segmentio/stats/v5/httpstats"
)

func main() {
     stats.Register(datadog.NewClient("localhost:8125"))
     defer stats.Flush()

    // ...

    http.ListenAndServe(":8080", httpstats.NewHandler(
        http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
            // This HTTP handler is automatically reporting metrics for all
            // requests it handles.
            // ...
        }),
    ))
}

HTTP Clients

The github.com/segmentio/stats/httpstats package exposes a decorator of http.RoundTripper which collects and reports metrics for client requests the same way it's done on the server side.

Here's an example of how to use the decorator:

package main

import (
    "net/http"

    "github.com/segmentio/stats/v5/datadog"
    "github.com/segmentio/stats/v5/httpstats"
)

func main() {
     stats.Register(datadog.NewClient("localhost:8125"))
     defer stats.Flush()

    // Make a new HTTP client with a transport that will report HTTP metrics,
    // set the engine to nil to use the default.
    httpc := &http.Client{
        Transport: httpstats.NewTransport(
            &http.Transport{},
        ),
    }

    // ...
}

You can also modify the default HTTP client to automatically get metrics for all packages using it, this is very convenient to get insights into dependencies.

package main

import (
    "net/http"

    "github.com/segmentio/stats/v5/datadog"
    "github.com/segmentio/stats/v5/httpstats"
)

func main() {
     stats.Register(datadog.NewClient("localhost:8125"))
     defer stats.Flush()

    // Wraps the default HTTP client's transport.
    http.DefaultClient.Transport = httpstats.NewTransport(http.DefaultClient.Transport)

    // ...
}

Redis

The github.com/segmentio/stats/redisstats package exposes:

Here's an example of how to use the decorator on the client side:

package main

import (
    "github.com/segmentio/redis-go"
    "github.com/segmentio/stats/v5/redisstats"
)

func main() {
    stats.Register(datadog.NewClient("localhost:8125"))
    defer stats.Flush()

    client := redis.Client{
        Addr:      "127.0.0.1:6379",
        Transport: redisstats.NewTransport(&redis.Transport{}),
    }

    // ...
}

And on the server side:

package main

import (
    "github.com/segmentio/redis-go"
    "github.com/segmentio/stats/v5/redisstats"
)

func main() {
    stats.Register(datadog.NewClient("localhost:8125"))
    defer stats.Flush()

    handler := redis.HandlerFunc(func(res redis.ResponseWriter, req *redis.Request) {
      // Implement handler function here
    })

    server := redis.Server{
        Handler: redisstats.NewHandler(&handler),
    }

    server.ListenAndServe()

    // ...
}

Addendum

By default, the stats library will report the running go version when you invoke NewEngine() as a metric:

  • go_version with value 1 and a tag set to the current version.
  • stats_version with value and atag` set to the tag value of segmentio/stats.

Set STATS_DISABLE_GO_VERSION_REPORTING to true in your environment, or set stats.GoVersionReportingEnabled to false before collecting any metrics, to disable this behavior.

# Packages

No description provided by the author
No description provided by the author
Package debugstats simplifies metric troubleshooting by sending metrics to any io.Writer.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Functions

Add increments by value the counter identified by name and tags.
AddAt increments by value the counter identified by name and tags.
ContextAddTags adds the given tags to the given context, if the tags have been set on any of the ancestor contexts.
ContextTags returns a copy of the tags on the context if they exist and nil if they don't exist.
ContextWithTags returns a new child context with the given tags.
FilteredHandler constructs a Handler that processes Measures with `filter` before forwarding to `h`.
Flush flushes the default engine.
Incr increments by one the counter identified by name and tags.
IncrAt increments by one the counter identified by name and tags.
M allows for creating a tag list from a map.
MakeField constructs and returns a new Field from name, value, and ftype.
MakeMeasures takes a struct value or a pointer to a struct value as argument and extracts and returns the list of measures that it represented.
MultiHandler constructs a handler which dispatches measures to all given handlers.
MustValueOf asserts that v's underlying Type is valid, otherwise it panics.
NewEngine creates and returns a new engine configured with prefix, handler, and tags.
Observe reports value for the histogram identified by name and tags.
ObserveAt reports value for the histogram identified by name and tags.
Register adds handler to the default engine.
Report is a helper function that delegates to DefaultEngine.
ReportAt is a helper function that delegates to DefaultEngine.
Set sets to value the gauge identified by name and tags.
SetAt sets to value the gauge identified by name and tags.
SortTags sorts and deduplicates tags in-place, favoring later elements whenever a tag name duplicate occurs.
T is shorthand for `stats.Tag{Name: "blah", Value: "foo"}` It returns the tag for Name k and Value v.
TagsAreSorted returns true if the given list of tags is sorted by tag name, false otherwise.
ValueOf inspects v's underlying type and returns a Value which encapsulates this type.
WithPrefix returns a copy of the engine with prefix appended to default engine's current prefix and tags set to the merge of engine's current tags and those passed as argument.
WithTags returns a copy of the engine with tags set to the merge of the default engine's current tags and those passed as arguments.

# Constants

Underlying Types.
Counter represents incrementing counter metrics.
Underlying Types.
Underlying Types.
Gauge represents metrics that snapshot a value that may increase and decrease.
Histogram represents metrics to observe the distribution of values.
Underlying Types.
Underlying Types.
Underlying Types.
Underlying Types.

# Variables

Buckets is a registry where histogram buckets are placed.
DefaultEngine is the engine used by global helper functions.
Discard is a handler that doesn't do anything with the measures it receives.
No description provided by the author

# Structs

Buffer is the implementation of a measure handler which uses a Serializer to serialize the metric into a memory buffer and write them once the buffer has reached a target size.
The Clock type can be used to report statistics on durations.
An Engine carries the context for producing metrics.
A Field is a key/value type that represents a single metric in a Measure.
Key is a type used to uniquely identify metrics.
Measure is a type that represents a single measure made by the application.
A Tag is a pair of a string key and value set on measures to define the dimensions of the metrics.
Value is a wrapper type which is used to encapsulate underlying types (nil, bool, int, uintptr, float) in a single pseudo-generic type.

# Interfaces

Flusher is an interface implemented by measure handlers in order to flush any buffered data.
The Handler interface is implemented by types that produce measures to various metric collection backends.
The Serializer interface is used to abstract the logic of serializing measures.

# Type aliases

FieldType is an enumeration of the different metric types that may be set on a Field value.
HandlerFunc is a type alias making it possible to use simple functions as measure handlers.
HistogramBuckets is a map type storing histogram buckets.
Type is an int32 type alias used to denote a values underlying type.