项目作者: segmentio

项目描述 :
Go package for abstracting stats collection
高级语言: Go
项目地址: git://github.com/segmentio/stats.git
创建时间: 2016-08-01T23:45:21Z
项目社区:https://github.com/segmentio/stats

开源协议:MIT License

下载


stats CircleCI Go Report Card GoDoc

A Go package for abstracting stats collection.

Installation

  1. 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:

  1. type funcMetrics struct {
  2. calls struct {
  3. count int `metric:"count" type:"counter"`
  4. time time.Duration `metric:"time" type:"histogram"`
  5. } `metric:"func.calls"`
  6. }
  1. t := time.Now()
  2. f()
  3. callTime := time.Since(t)
  4. m := &funcMetrics{}
  5. m.calls.count = 1
  6. m.calls.time = callTime
  7. // Equivalent to:
  8. //
  9. // stats.Incr("func.calls.count")
  10. // stats.Observe("func.calls.time", callTime)
  11. //
  12. 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.

  1. package main
  2. import (
  3. "github.com/segmentio/stats/v5"
  4. "github.com/segmentio/stats/v5/datadog"
  5. )
  6. func main() {
  7. // Creates a new datadog client publishing metrics to localhost:8125
  8. dd := datadog.NewClient("localhost:8125")
  9. // Register the client so it receives metrics from the default engine.
  10. stats.Register(dd)
  11. // Flush the default stats engine on return to ensure all buffered
  12. // metrics are sent to the dogstatsd server.
  13. defer stats.Flush()
  14. // That's it! Metrics produced by the application will now be reported!
  15. // ...
  16. }

Metrics

  1. package main
  2. import (
  3. "github.com/segmentio/stats/v5"
  4. "github.com/segmentio/stats/v5/datadog"
  5. )
  6. func main() {
  7. stats.Register(datadog.NewClient("localhost:8125"))
  8. defer stats.Flush()
  9. // Increment counters.
  10. stats.Incr("user.login")
  11. defer stats.Incr("user.logout")
  12. // Set a tag on a counter increment.
  13. stats.Incr("user.login", stats.Tag{"user", "luke"})
  14. // ...
  15. }

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:

  1. func main() {
  2. stats.Register(datadog.NewClient("localhost:8125"))
  3. defer stats.Flush()
  4. // Force a metrics flush every second
  5. go func() {
  6. for range time.Tick(time.Second) {
  7. stats.Flush()
  8. }
  9. }()
  10. // ...
  11. }

Troubleshooting

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

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

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

  1. 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:

  1. package main
  2. import (
  3. "github.com/segmentio/stats/v5/datadog"
  4. "github.com/segmentio/stats/v5/procstats"
  5. )
  6. func main() {
  7. stats.Register(datadog.NewClient("localhost:8125"))
  8. defer stats.Flush()
  9. // Start a new collector for the current process, reporting Go metrics.
  10. c := procstats.StartCollector(procstats.NewGoMetrics())
  11. // Gracefully stops stats collection.
  12. defer c.Close()
  13. // ...
  14. }

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:

  1. func main() {
  2. // As above...
  3. // Start a new collector for the current process, reporting Go metrics.
  4. c := procstats.StartCollector(procstats.NewDelayMetrics())
  5. defer c.Close()
  6. }

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:

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/segmentio/stats/v5/datadog"
  5. "github.com/segmentio/stats/v5/httpstats"
  6. )
  7. func main() {
  8. stats.Register(datadog.NewClient("localhost:8125"))
  9. defer stats.Flush()
  10. // ...
  11. http.ListenAndServe(":8080", httpstats.NewHandler(
  12. http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
  13. // This HTTP handler is automatically reporting metrics for all
  14. // requests it handles.
  15. // ...
  16. }),
  17. ))
  18. }

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:

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/segmentio/stats/v5/datadog"
  5. "github.com/segmentio/stats/v5/httpstats"
  6. )
  7. func main() {
  8. stats.Register(datadog.NewClient("localhost:8125"))
  9. defer stats.Flush()
  10. // Make a new HTTP client with a transport that will report HTTP metrics,
  11. // set the engine to nil to use the default.
  12. httpc := &http.Client{
  13. Transport: httpstats.NewTransport(
  14. &http.Transport{},
  15. ),
  16. }
  17. // ...
  18. }

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.

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/segmentio/stats/v5/datadog"
  5. "github.com/segmentio/stats/v5/httpstats"
  6. )
  7. func main() {
  8. stats.Register(datadog.NewClient("localhost:8125"))
  9. defer stats.Flush()
  10. // Wraps the default HTTP client's transport.
  11. http.DefaultClient.Transport = httpstats.NewTransport(http.DefaultClient.Transport)
  12. // ...
  13. }

Redis

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

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

  1. package main
  2. import (
  3. "github.com/segmentio/redis-go"
  4. "github.com/segmentio/stats/v5/redisstats"
  5. )
  6. func main() {
  7. stats.Register(datadog.NewClient("localhost:8125"))
  8. defer stats.Flush()
  9. client := redis.Client{
  10. Addr: "127.0.0.1:6379",
  11. Transport: redisstats.NewTransport(&redis.Transport{}),
  12. }
  13. // ...
  14. }

And on the server side:

  1. package main
  2. import (
  3. "github.com/segmentio/redis-go"
  4. "github.com/segmentio/stats/v5/redisstats"
  5. )
  6. func main() {
  7. stats.Register(datadog.NewClient("localhost:8125"))
  8. defer stats.Flush()
  9. handler := redis.HandlerFunc(func(res redis.ResponseWriter, req *redis.Request) {
  10. // Implement handler function here
  11. })
  12. server := redis.Server{
  13. Handler: redisstats.NewHandler(&handler),
  14. }
  15. server.ListenAndServe()
  16. // ...
  17. }

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.