...

Source file src/github.com/go-kit/kit/metrics/graphite/graphite.go

Documentation: github.com/go-kit/kit/metrics/graphite

     1  // Package graphite provides a Graphite backend for metrics. Metrics are batched
     2  // and emitted in the plaintext protocol. For more information, see
     3  // http://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol
     4  //
     5  // Graphite does not have a native understanding of metric parameterization, so
     6  // label values not supported. Use distinct metrics for each unique combination
     7  // of label values.
     8  package graphite
     9  
    10  import (
    11  	"context"
    12  	"fmt"
    13  	"io"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/go-kit/kit/metrics"
    18  	"github.com/go-kit/kit/metrics/generic"
    19  	"github.com/go-kit/kit/util/conn"
    20  	"github.com/go-kit/log"
    21  )
    22  
    23  // Graphite receives metrics observations and forwards them to a Graphite server.
    24  // Create a Graphite object, use it to create metrics, and pass those metrics as
    25  // dependencies to the components that will use them.
    26  //
    27  // All metrics are buffered until WriteTo is called. Counters and gauges are
    28  // aggregated into a single observation per timeseries per write. Histograms are
    29  // exploded into per-quantile gauges and reported once per write.
    30  //
    31  // To regularly report metrics to an io.Writer, use the WriteLoop helper method.
    32  // To send to a Graphite server, use the SendLoop helper method.
    33  type Graphite struct {
    34  	mtx        sync.RWMutex
    35  	prefix     string
    36  	counters   map[string]*Counter
    37  	gauges     map[string]*Gauge
    38  	histograms map[string]*Histogram
    39  	logger     log.Logger
    40  }
    41  
    42  // New returns a Graphite object that may be used to create metrics. Prefix is
    43  // applied to all created metrics. Callers must ensure that regular calls to
    44  // WriteTo are performed, either manually or with one of the helper methods.
    45  func New(prefix string, logger log.Logger) *Graphite {
    46  	return &Graphite{
    47  		prefix:     prefix,
    48  		counters:   map[string]*Counter{},
    49  		gauges:     map[string]*Gauge{},
    50  		histograms: map[string]*Histogram{},
    51  		logger:     logger,
    52  	}
    53  }
    54  
    55  // NewCounter returns a counter. Observations are aggregated and emitted once
    56  // per write invocation.
    57  func (g *Graphite) NewCounter(name string) *Counter {
    58  	c := NewCounter(g.prefix + name)
    59  	g.mtx.Lock()
    60  	g.counters[g.prefix+name] = c
    61  	g.mtx.Unlock()
    62  	return c
    63  }
    64  
    65  // NewGauge returns a gauge. Observations are aggregated and emitted once per
    66  // write invocation.
    67  func (g *Graphite) NewGauge(name string) *Gauge {
    68  	ga := NewGauge(g.prefix + name)
    69  	g.mtx.Lock()
    70  	g.gauges[g.prefix+name] = ga
    71  	g.mtx.Unlock()
    72  	return ga
    73  }
    74  
    75  // NewHistogram returns a histogram. Observations are aggregated and emitted as
    76  // per-quantile gauges, once per write invocation. 50 is a good default value
    77  // for buckets.
    78  func (g *Graphite) NewHistogram(name string, buckets int) *Histogram {
    79  	h := NewHistogram(g.prefix+name, buckets)
    80  	g.mtx.Lock()
    81  	g.histograms[g.prefix+name] = h
    82  	g.mtx.Unlock()
    83  	return h
    84  }
    85  
    86  // WriteLoop is a helper method that invokes WriteTo to the passed writer every
    87  // time the passed channel fires. This method blocks until ctx is canceled,
    88  // so clients probably want to run it in its own goroutine. For typical
    89  // usage, create a time.Ticker and pass its C channel to this method.
    90  func (g *Graphite) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) {
    91  	for {
    92  		select {
    93  		case <-c:
    94  			if _, err := g.WriteTo(w); err != nil {
    95  				g.logger.Log("during", "WriteTo", "err", err)
    96  			}
    97  		case <-ctx.Done():
    98  			return
    99  		}
   100  	}
   101  }
   102  
   103  // SendLoop is a helper method that wraps WriteLoop, passing a managed
   104  // connection to the network and address. Like WriteLoop, this method blocks
   105  // until ctx is canceled, so clients probably want to start it in its own
   106  // goroutine. For typical usage, create a time.Ticker and pass its C channel to
   107  // this method.
   108  func (g *Graphite) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) {
   109  	g.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, g.logger))
   110  }
   111  
   112  // WriteTo flushes the buffered content of the metrics to the writer, in
   113  // Graphite plaintext format. WriteTo abides best-effort semantics, so
   114  // observations are lost if there is a problem with the write. Clients should be
   115  // sure to call WriteTo regularly, ideally through the WriteLoop or SendLoop
   116  // helper methods.
   117  func (g *Graphite) WriteTo(w io.Writer) (count int64, err error) {
   118  	g.mtx.RLock()
   119  	defer g.mtx.RUnlock()
   120  	now := time.Now().Unix()
   121  
   122  	for name, c := range g.counters {
   123  		n, err := fmt.Fprintf(w, "%s %f %d\n", name, c.c.ValueReset(), now)
   124  		if err != nil {
   125  			return count, err
   126  		}
   127  		count += int64(n)
   128  	}
   129  
   130  	for name, ga := range g.gauges {
   131  		n, err := fmt.Fprintf(w, "%s %f %d\n", name, ga.g.Value(), now)
   132  		if err != nil {
   133  			return count, err
   134  		}
   135  		count += int64(n)
   136  	}
   137  
   138  	for name, h := range g.histograms {
   139  		for _, p := range []struct {
   140  			s string
   141  			f float64
   142  		}{
   143  			{"50", 0.50},
   144  			{"90", 0.90},
   145  			{"95", 0.95},
   146  			{"99", 0.99},
   147  		} {
   148  			n, err := fmt.Fprintf(w, "%s.p%s %f %d\n", name, p.s, h.h.Quantile(p.f), now)
   149  			if err != nil {
   150  				return count, err
   151  			}
   152  			count += int64(n)
   153  		}
   154  	}
   155  
   156  	return count, err
   157  }
   158  
   159  // Counter is a Graphite counter metric.
   160  type Counter struct {
   161  	c *generic.Counter
   162  }
   163  
   164  // NewCounter returns a new usable counter metric.
   165  func NewCounter(name string) *Counter {
   166  	return &Counter{generic.NewCounter(name)}
   167  }
   168  
   169  // With is a no-op.
   170  func (c *Counter) With(...string) metrics.Counter { return c }
   171  
   172  // Add implements counter.
   173  func (c *Counter) Add(delta float64) { c.c.Add(delta) }
   174  
   175  // Gauge is a Graphite gauge metric.
   176  type Gauge struct {
   177  	g *generic.Gauge
   178  }
   179  
   180  // NewGauge returns a new usable Gauge metric.
   181  func NewGauge(name string) *Gauge {
   182  	return &Gauge{generic.NewGauge(name)}
   183  }
   184  
   185  // With is a no-op.
   186  func (g *Gauge) With(...string) metrics.Gauge { return g }
   187  
   188  // Set implements gauge.
   189  func (g *Gauge) Set(value float64) { g.g.Set(value) }
   190  
   191  // Add implements metrics.Gauge.
   192  func (g *Gauge) Add(delta float64) { g.g.Add(delta) }
   193  
   194  // Histogram is a Graphite histogram metric. Observations are bucketed into
   195  // per-quantile gauges.
   196  type Histogram struct {
   197  	h *generic.Histogram
   198  }
   199  
   200  // NewHistogram returns a new usable Histogram metric.
   201  func NewHistogram(name string, buckets int) *Histogram {
   202  	return &Histogram{generic.NewHistogram(name, buckets)}
   203  }
   204  
   205  // With is a no-op.
   206  func (h *Histogram) With(...string) metrics.Histogram { return h }
   207  
   208  // Observe implements histogram.
   209  func (h *Histogram) Observe(value float64) { h.h.Observe(value) }
   210  

View as plain text