Merge pull request #442 from go-kit/rm-circonus
metrics: remove circonus, it is too flaky
Peter Bourgon authored 7 years ago
GitHub committed 7 years ago
0 | // Package circonus provides a Circonus backend for metrics. | |
1 | package circonus | |
2 | ||
3 | import ( | |
4 | "sync" | |
5 | ||
6 | "github.com/circonus-labs/circonus-gometrics" | |
7 | ||
8 | "github.com/go-kit/kit/metrics" | |
9 | ) | |
10 | ||
11 | // Circonus wraps a CirconusMetrics object and provides constructors for each of | |
12 | // the Go kit metrics. The CirconusMetrics object manages aggregation of | |
13 | // observations and emission to the Circonus server. | |
14 | type Circonus struct { | |
15 | m *circonusgometrics.CirconusMetrics | |
16 | } | |
17 | ||
18 | // New creates a new Circonus object wrapping the passed CirconusMetrics, which | |
19 | // the caller should create and set in motion. The Circonus object can be used | |
20 | // to construct individual Go kit metrics. | |
21 | func New(m *circonusgometrics.CirconusMetrics) *Circonus { | |
22 | return &Circonus{ | |
23 | m: m, | |
24 | } | |
25 | } | |
26 | ||
27 | // NewCounter returns a counter metric with the given name. | |
28 | func (c *Circonus) NewCounter(name string) *Counter { | |
29 | return &Counter{ | |
30 | name: name, | |
31 | m: c.m, | |
32 | } | |
33 | } | |
34 | ||
35 | // NewGauge returns a gauge metric with the given name. | |
36 | func (c *Circonus) NewGauge(name string) *Gauge { | |
37 | return &Gauge{ | |
38 | name: name, | |
39 | m: c.m, | |
40 | } | |
41 | } | |
42 | ||
43 | // NewHistogram returns a histogram metric with the given name. | |
44 | func (c *Circonus) NewHistogram(name string) *Histogram { | |
45 | return &Histogram{ | |
46 | h: c.m.NewHistogram(name), | |
47 | } | |
48 | } | |
49 | ||
50 | // Counter is a Circonus implementation of a counter metric. | |
51 | type Counter struct { | |
52 | name string | |
53 | m *circonusgometrics.CirconusMetrics | |
54 | } | |
55 | ||
56 | // With implements Counter, but is a no-op, because Circonus metrics have no | |
57 | // concept of per-observation label values. | |
58 | func (c *Counter) With(labelValues ...string) metrics.Counter { return c } | |
59 | ||
60 | // Add implements Counter. Delta is converted to uint64; precision will be lost. | |
61 | func (c *Counter) Add(delta float64) { c.m.Add(c.name, uint64(delta)) } | |
62 | ||
63 | // Gauge is a Circonus implementation of a gauge metric. | |
64 | type Gauge struct { | |
65 | name string | |
66 | m *circonusgometrics.CirconusMetrics | |
67 | val float64 | |
68 | mtx sync.RWMutex | |
69 | } | |
70 | ||
71 | // With implements Gauge, but is a no-op, because Circonus metrics have no | |
72 | // concept of per-observation label values. | |
73 | func (g *Gauge) With(labelValues ...string) metrics.Gauge { return g } | |
74 | ||
75 | // Set implements Gauge. | |
76 | func (g *Gauge) Set(value float64) { | |
77 | g.mtx.Lock() | |
78 | defer g.mtx.Unlock() | |
79 | g.val = value | |
80 | g.m.SetGauge(g.name, value) | |
81 | } | |
82 | ||
83 | // Add implements metrics.Gauge. | |
84 | func (g *Gauge) Add(delta float64) { | |
85 | g.mtx.Lock() | |
86 | defer g.mtx.Unlock() | |
87 | value := g.val + delta | |
88 | g.val = value | |
89 | g.m.SetGauge(g.name, value) | |
90 | } | |
91 | ||
92 | // Histogram is a Circonus implementation of a histogram metric. | |
93 | type Histogram struct { | |
94 | h *circonusgometrics.Histogram | |
95 | } | |
96 | ||
97 | // With implements Histogram, but is a no-op, because Circonus metrics have no | |
98 | // concept of per-observation label values. | |
99 | func (h *Histogram) With(labelValues ...string) metrics.Histogram { return h } | |
100 | ||
101 | // Observe implements Histogram. No precision is lost. | |
102 | func (h *Histogram) Observe(value float64) { h.h.RecordValue(value) } |
0 | package circonus | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "net/http" | |
5 | "net/http/httptest" | |
6 | "regexp" | |
7 | "strconv" | |
8 | "testing" | |
9 | ||
10 | "github.com/circonus-labs/circonus-gometrics" | |
11 | "github.com/circonus-labs/circonus-gometrics/checkmgr" | |
12 | ||
13 | "github.com/go-kit/kit/metrics/generic" | |
14 | "github.com/go-kit/kit/metrics/teststat" | |
15 | ) | |
16 | ||
17 | func TestCounter(t *testing.T) { | |
18 | // The only way to extract values from Circonus is to pose as a Circonus | |
19 | // server and receive real HTTP writes. | |
20 | const name = "abc" | |
21 | var val int64 | |
22 | s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
23 | var res map[string]struct { | |
24 | Value int64 `json:"_value"` // reverse-engineered :\ | |
25 | } | |
26 | json.NewDecoder(r.Body).Decode(&res) | |
27 | val = res[name].Value | |
28 | })) | |
29 | defer s.Close() | |
30 | ||
31 | // Set up a Circonus object, submitting to our HTTP server. | |
32 | m := newCirconusMetrics(s.URL) | |
33 | counter := New(m).NewCounter(name).With("label values", "not supported") | |
34 | value := func() float64 { m.Flush(); return float64(val) } | |
35 | ||
36 | // Engage. | |
37 | if err := teststat.TestCounter(counter, value); err != nil { | |
38 | t.Fatal(err) | |
39 | } | |
40 | } | |
41 | ||
42 | func TestGauge(t *testing.T) { | |
43 | const name = "def" | |
44 | var val float64 | |
45 | s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
46 | var res map[string]struct { | |
47 | Value string `json:"_value"` | |
48 | } | |
49 | json.NewDecoder(r.Body).Decode(&res) | |
50 | val, _ = strconv.ParseFloat(res[name].Value, 64) | |
51 | })) | |
52 | defer s.Close() | |
53 | ||
54 | m := newCirconusMetrics(s.URL) | |
55 | gauge := New(m).NewGauge(name).With("label values", "not supported") | |
56 | value := func() float64 { m.Flush(); return val } | |
57 | ||
58 | if err := teststat.TestGauge(gauge, value); err != nil { | |
59 | t.Fatal(err) | |
60 | } | |
61 | } | |
62 | ||
63 | func TestHistogram(t *testing.T) { | |
64 | const name = "ghi" | |
65 | ||
66 | // Circonus just emits bucketed counts. We'll dump them into a generic | |
67 | // histogram (losing some precision) and take statistics from there. Note | |
68 | // this does assume that the generic histogram computes statistics properly, | |
69 | // but we have another test for that :) | |
70 | re := regexp.MustCompile(`^H\[([0-9\.e\+]+)\]=([0-9]+)$`) // H[1.2e+03]=456 | |
71 | ||
72 | var p50, p90, p95, p99 float64 | |
73 | s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
74 | var res map[string]struct { | |
75 | Values []string `json:"_value"` // reverse-engineered :\ | |
76 | } | |
77 | json.NewDecoder(r.Body).Decode(&res) | |
78 | ||
79 | h := generic.NewHistogram("dummy", len(res[name].Values)) // match tbe bucket counts | |
80 | for _, v := range res[name].Values { | |
81 | match := re.FindStringSubmatch(v) | |
82 | f, _ := strconv.ParseFloat(match[1], 64) | |
83 | n, _ := strconv.ParseInt(match[2], 10, 64) | |
84 | for i := int64(0); i < n; i++ { | |
85 | h.Observe(f) | |
86 | } | |
87 | } | |
88 | ||
89 | p50 = h.Quantile(0.50) | |
90 | p90 = h.Quantile(0.90) | |
91 | p95 = h.Quantile(0.95) | |
92 | p99 = h.Quantile(0.99) | |
93 | })) | |
94 | defer s.Close() | |
95 | ||
96 | m := newCirconusMetrics(s.URL) | |
97 | histogram := New(m).NewHistogram(name).With("label values", "not supported") | |
98 | quantiles := func() (float64, float64, float64, float64) { m.Flush(); return p50, p90, p95, p99 } | |
99 | ||
100 | // Circonus metrics, because they do their own bucketing, are less precise | |
101 | // than other systems. So, we bump the tolerance to 5 percent. | |
102 | if err := teststat.TestHistogram(histogram, quantiles, 0.05); err != nil { | |
103 | t.Fatal(err) | |
104 | } | |
105 | } | |
106 | ||
107 | func newCirconusMetrics(url string) *circonusgometrics.CirconusMetrics { | |
108 | m, err := circonusgometrics.NewCirconusMetrics(&circonusgometrics.Config{ | |
109 | CheckManager: checkmgr.Config{ | |
110 | Check: checkmgr.CheckConfig{ | |
111 | SubmissionURL: url, | |
112 | }, | |
113 | }, | |
114 | }) | |
115 | if err != nil { | |
116 | panic(err) | |
117 | } | |
118 | return m | |
119 | } |
90 | 90 | // expvar 1 atomic atomic synthetic, batch, in-place expose |
91 | 91 | // influx n custom custom custom |
92 | 92 | // prometheus n native native native |
93 | // circonus 1 native native native | |
94 | 93 | // pcp 1 native native native |
95 | 94 | // |
96 | 95 | package metrics |
0 | package provider | |
1 | ||
2 | import ( | |
3 | "github.com/go-kit/kit/metrics" | |
4 | "github.com/go-kit/kit/metrics/circonus" | |
5 | ) | |
6 | ||
7 | type circonusProvider struct { | |
8 | c *circonus.Circonus | |
9 | } | |
10 | ||
11 | // NewCirconusProvider takes the given Circonnus object and returns a Provider | |
12 | // that produces Circonus metrics. | |
13 | func NewCirconusProvider(c *circonus.Circonus) Provider { | |
14 | return &circonusProvider{ | |
15 | c: c, | |
16 | } | |
17 | } | |
18 | ||
19 | // NewCounter implements Provider. | |
20 | func (p *circonusProvider) NewCounter(name string) metrics.Counter { | |
21 | return p.c.NewCounter(name) | |
22 | } | |
23 | ||
24 | // NewGauge implements Provider. | |
25 | func (p *circonusProvider) NewGauge(name string) metrics.Gauge { | |
26 | return p.c.NewGauge(name) | |
27 | } | |
28 | ||
29 | // NewHistogram implements Provider. The buckets parameter is ignored. | |
30 | func (p *circonusProvider) NewHistogram(name string, _ int) metrics.Histogram { | |
31 | return p.c.NewHistogram(name) | |
32 | } | |
33 | ||
34 | // Stop implements Provider, but is a no-op. | |
35 | func (p *circonusProvider) Stop() {} |