Add comments and use default quantiles.
Cameron Stitt
7 years ago
3 | 3 | "sync" |
4 | 4 | |
5 | 5 | "time" |
6 | ||
7 | "strconv" | |
8 | 6 | |
9 | 7 | "fmt" |
10 | 8 | |
15 | 13 | "github.com/go-kit/kit/metrics/generic" |
16 | 14 | ) |
17 | 15 | |
18 | // CloudWatch ... | |
16 | // CloudWatch receives metrics observations and forwards them to CloudWatch. | |
17 | // Create a CloudWatch object, use it to create metrics, and pass those metrics as | |
18 | // dependencies to the components that will use them. | |
19 | // | |
20 | // To regularly report metrics to CloudWatch, use the WriteLoop helper method. | |
19 | 21 | type CloudWatch struct { |
20 | 22 | mtx sync.RWMutex |
21 | 23 | namespace string |
26 | 28 | logger log.Logger |
27 | 29 | } |
28 | 30 | |
29 | // New ... | |
31 | // New returns a CloudWatch object that may be used to create metrics. Namespace is | |
32 | // applied to all created metrics and maps to the CloudWatch namespace. | |
33 | // Callers must ensure that regular calls to Send are performed, either manually or with one of the helper methods. | |
30 | 34 | func New(namespace string, logger log.Logger, svc *cloudwatch.CloudWatch) *CloudWatch { |
31 | 35 | return &CloudWatch{ |
32 | 36 | namespace: namespace, |
38 | 42 | } |
39 | 43 | } |
40 | 44 | |
45 | // NewCounter returns a counter. Observations are aggregated and emitted once | |
46 | // per write invocation. | |
41 | 47 | func (cw *CloudWatch) NewCounter(name string) *Counter { |
42 | 48 | c := NewCounter(name) |
43 | 49 | cw.mtx.Lock() |
46 | 52 | return c |
47 | 53 | } |
48 | 54 | |
55 | // NewGauge returns a gauge. Observations are aggregated and emitted once per | |
56 | // write invocation. | |
49 | 57 | func (cw *CloudWatch) NewGauge(name string) *Gauge { |
50 | 58 | g := NewGauge(name) |
51 | 59 | cw.mtx.Lock() |
54 | 62 | return g |
55 | 63 | } |
56 | 64 | |
57 | func (cw *CloudWatch) NewHistogram(name string, quantiles []float64, buckets int) *Histogram { | |
58 | h := NewHistogram(name, quantiles, buckets) | |
65 | // NewHistogram returns a histogram. Observations are aggregated and emitted as | |
66 | // per-quantile gauges, once per write invocation. 50 is a good default value | |
67 | // for buckets. | |
68 | func (cw *CloudWatch) NewHistogram(name string, buckets int) *Histogram { | |
69 | h := NewHistogram(name, buckets) | |
59 | 70 | cw.mtx.Lock() |
60 | 71 | cw.histograms[name] = h |
61 | 72 | cw.mtx.Unlock() |
62 | 73 | return h |
63 | 74 | } |
64 | 75 | |
65 | // WriteLoop is a helper method that invokes WriteTo to the passed writer every | |
76 | // WriteLoop is a helper method that invokes Send every | |
66 | 77 | // time the passed channel fires. This method blocks until the channel is |
67 | 78 | // closed, so clients probably want to run it in its own goroutine. For typical |
68 | 79 | // usage, create a time.Ticker and pass its C channel to this method. |
69 | 80 | func (cw *CloudWatch) WriteLoop(c <-chan time.Time) { |
70 | 81 | for range c { |
71 | cw.mtx.RLock() | |
72 | defer cw.mtx.RUnlock() | |
73 | now := time.Now() | |
74 | ||
75 | datums := []*cloudwatch.MetricDatum{} | |
76 | ||
77 | for name, c := range cw.counters { | |
82 | if err := cw.Send(); err != nil { | |
83 | cw.logger.Log("during", "Send", "err", err) | |
84 | } | |
85 | } | |
86 | } | |
87 | ||
88 | // Send will fire an api request to CloudWatch with the latest stats for | |
89 | // all metrics. | |
90 | func (cw *CloudWatch) Send() error { | |
91 | cw.mtx.RLock() | |
92 | defer cw.mtx.RUnlock() | |
93 | now := time.Now() | |
94 | ||
95 | datums := []*cloudwatch.MetricDatum{} | |
96 | ||
97 | for name, c := range cw.counters { | |
98 | datums = append(datums, &cloudwatch.MetricDatum{ | |
99 | MetricName: aws.String(name), | |
100 | Dimensions: makeDimensions(c.c.LabelValues()...), | |
101 | Value: aws.Float64(c.c.Value()), | |
102 | Timestamp: aws.Time(now), | |
103 | }) | |
104 | } | |
105 | ||
106 | for name, g := range cw.gauges { | |
107 | datums = append(datums, &cloudwatch.MetricDatum{ | |
108 | MetricName: aws.String(name), | |
109 | Dimensions: makeDimensions(g.g.LabelValues()...), | |
110 | Value: aws.Float64(g.g.Value()), | |
111 | Timestamp: aws.Time(now), | |
112 | }) | |
113 | } | |
114 | ||
115 | for name, h := range cw.histograms { | |
116 | for _, p := range []struct { | |
117 | s string | |
118 | f float64 | |
119 | }{ | |
120 | {"50", 0.50}, | |
121 | {"90", 0.90}, | |
122 | {"95", 0.95}, | |
123 | {"99", 0.99}, | |
124 | } { | |
78 | 125 | datums = append(datums, &cloudwatch.MetricDatum{ |
79 | MetricName: aws.String(name), | |
80 | Dimensions: makeDimensions(c.c.LabelValues()...), | |
81 | Value: aws.Float64(c.c.Value()), | |
126 | MetricName: aws.String(fmt.Sprintf("%s_%s", name, p.s)), | |
127 | Dimensions: makeDimensions(h.h.LabelValues()...), | |
128 | Value: aws.Float64(h.h.Quantile(p.f)), | |
82 | 129 | Timestamp: aws.Time(now), |
83 | 130 | }) |
84 | 131 | } |
85 | ||
86 | for name, g := range cw.gauges { | |
87 | datums = append(datums, &cloudwatch.MetricDatum{ | |
88 | MetricName: aws.String(name), | |
89 | Dimensions: makeDimensions(g.g.LabelValues()...), | |
90 | Value: aws.Float64(g.g.Value()), | |
91 | Timestamp: aws.Time(now), | |
92 | }) | |
93 | } | |
94 | ||
95 | for name, h := range cw.histograms { | |
96 | for _, quantile := range h.quantiles { | |
97 | quantileStr := strconv.FormatFloat(quantile, 'f', 2, 64) | |
98 | datums = append(datums, &cloudwatch.MetricDatum{ | |
99 | MetricName: aws.String(fmt.Sprintf("%s_%s", name, quantileStr)), | |
100 | Dimensions: makeDimensions(h.h.LabelValues()...), | |
101 | Value: aws.Float64(h.h.Quantile(quantile)), | |
102 | Timestamp: aws.Time(now), | |
103 | }) | |
104 | } | |
105 | } | |
106 | ||
107 | _, err := cw.svc.PutMetricData(&cloudwatch.PutMetricDataInput{ | |
108 | Namespace: aws.String(cw.namespace), | |
109 | MetricData: datums, | |
110 | }) | |
111 | if err != nil { | |
112 | cw.logger.Log("during", "WriteLoop", "err", err) | |
113 | } | |
114 | } | |
132 | } | |
133 | ||
134 | _, err := cw.svc.PutMetricData(&cloudwatch.PutMetricDataInput{ | |
135 | Namespace: aws.String(cw.namespace), | |
136 | MetricData: datums, | |
137 | }) | |
138 | return err | |
115 | 139 | } |
116 | 140 | |
117 | 141 | // Counter is a CloudWatch counter metric. |
126 | 150 | } |
127 | 151 | } |
128 | 152 | |
129 | // With is a no-op. | |
153 | // With implements counter | |
130 | 154 | func (c *Counter) With(labelValues ...string) metrics.Counter { |
131 | 155 | return c.c.With(labelValues...) |
132 | 156 | } |
148 | 172 | } |
149 | 173 | } |
150 | 174 | |
175 | // With implements gauge | |
151 | 176 | func (g *Gauge) With(labelValues ...string) metrics.Gauge { |
152 | 177 | return &Gauge{ |
153 | 178 | g: g.g.With(labelValues...).(*generic.Gauge), |
154 | 179 | } |
155 | 180 | } |
156 | 181 | |
182 | // Set implements gauge | |
157 | 183 | func (g *Gauge) Set(value float64) { |
158 | 184 | g.g.Set(value) |
159 | 185 | } |
160 | 186 | |
187 | // Add implements gauge | |
161 | 188 | func (g *Gauge) Add(delta float64) { |
162 | 189 | g.g.Add(delta) |
163 | 190 | } |
164 | 191 | |
165 | 192 | // Histogram is a CloudWatch histogram metric |
166 | 193 | type Histogram struct { |
167 | quantiles []float64 | |
168 | h *generic.Histogram | |
194 | h *generic.Histogram | |
169 | 195 | } |
170 | 196 | |
171 | 197 | // NewHistogram returns a new usable histogram metric |
172 | func NewHistogram(name string, quantiles []float64, buckets int) *Histogram { | |
198 | func NewHistogram(name string, buckets int) *Histogram { | |
173 | 199 | return &Histogram{ |
174 | quantiles: quantiles, | |
175 | h: generic.NewHistogram(name, buckets), | |
176 | } | |
177 | } | |
178 | ||
200 | h: generic.NewHistogram(name, buckets), | |
201 | } | |
202 | } | |
203 | ||
204 | // With implements histogram | |
179 | 205 | func (h *Histogram) With(labelValues ...string) metrics.Histogram { |
180 | 206 | return &Histogram{ |
181 | 207 | h: h.h.With(labelValues...).(*generic.Histogram), |
182 | 208 | } |
183 | 209 | } |
184 | 210 | |
211 | // Observe implements histogram | |
185 | 212 | func (h *Histogram) Observe(value float64) { |
186 | 213 | h.h.Observe(value) |
187 | 214 | } |