Codebase list golang-github-go-kit-kit / 94d041d
Merge pull request #599 from feliksik/feliksik/easy-percentiles Cleaner/easier way for user to specify Cloudwatch metric percentiles Peter Bourgon authored 6 years ago GitHub committed 6 years ago
2 changed file(s) with 74 addition(s) and 19 deletion(s). Raw diff Collapse all Expand all
1313 "github.com/go-kit/kit/metrics"
1414 "github.com/go-kit/kit/metrics/generic"
1515 "github.com/go-kit/kit/metrics/internal/lv"
16 "strconv"
1617 )
1718
1819 const (
3738 counters *lv.Space
3839 gauges *lv.Space
3940 histograms *lv.Space
40 percentiles Percentiles
41 percentiles []float64 // percentiles to track
4142 logger log.Logger
4243 numConcurrentRequests int
4344 }
5657 }
5758 }
5859
59 func WithPercentiles(p Percentiles) option {
60 // WithPercentiles registers the percentiles to track, overriding the
61 // existing/default values.
62 // Reason is that Cloudwatch makes you pay per metric, so you can save half the money
63 // by only using 2 metrics instead of the default 4.
64 func WithPercentiles(percentiles ...float64) option {
6065 return func(c *CloudWatch) {
61 validated := Percentiles{}
62 for _, entry := range p {
63 if entry.f < 0 || entry.f > 1 {
64 continue // illegal entry
66 c.percentiles = make([]float64, 0, len(percentiles))
67 for _, p := range percentiles {
68 if p < 0 || p > 1 {
69 continue // illegal entry; ignore
6570 }
66 validated = append(validated, entry)
67 }
68 c.percentiles = validated
71 c.percentiles = append(c.percentiles, p)
72 }
6973 }
7074 }
7175
9296 histograms: lv.NewSpace(),
9397 numConcurrentRequests: 10,
9498 logger: log.NewLogfmtLogger(os.Stderr),
95 percentiles: Percentiles{
96 {"50", 0.50},
97 {"90", 0.90},
98 {"95", 0.95},
99 {"99", 0.99},
100 },
99 percentiles: []float64{0.50, 0.90, 0.95, 0.99},
101100 }
102101
103102 for _, optFunc := range options {
178177 return true
179178 })
180179
180 // format a [0,1]-float value to a percentile value, with minimum nr of decimals
181 // 0.90 -> "90"
182 // 0.95 -> "95"
183 // 0.999 -> "99.9"
184 formatPerc := func(p float64) string {
185 return strconv.FormatFloat(p*100, 'f', -1, 64)
186 }
187
181188 cw.histograms.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
182189 histogram := generic.NewHistogram(name, 50)
183190
185192 histogram.Observe(v)
186193 }
187194
188 for _, p := range cw.percentiles {
189 value := histogram.Quantile(p.f)
195 for _, perc := range cw.percentiles {
196 value := histogram.Quantile(perc)
190197 datums = append(datums, &cloudwatch.MetricDatum{
191 MetricName: aws.String(fmt.Sprintf("%s_%s", name, p.s)),
198 MetricName: aws.String(fmt.Sprintf("%s_%s", name, formatPerc(perc))),
192199 Dimensions: makeDimensions(lvs...),
193200 Value: aws.Float64(value),
194201 Timestamp: aws.Time(now),
192192 if err := svc.testDimensions(n99, label, value); err != nil {
193193 t.Fatal(err)
194194 }
195 }
195
196 // now test with only 2 custom percentiles
197 //
198 svc = newMockCloudWatch()
199 cw = New(namespace, svc, WithLogger(log.NewNopLogger()), WithPercentiles(0.50, 0.90))
200 histogram = cw.NewHistogram(name).With(label, value)
201
202 customQuantiles := func() (p50, p90, p95, p99 float64) {
203 err := cw.Send()
204 if err != nil {
205 t.Fatal(err)
206 }
207 svc.mtx.RLock()
208 defer svc.mtx.RUnlock()
209 p50 = svc.valuesReceived[n50]
210 p90 = svc.valuesReceived[n90]
211
212 // our teststat.TestHistogram wants us to give p95 and p99,
213 // but with custom percentiles we don't have those.
214 // So fake them. Maybe we should make teststat.nvq() public and use that?
215 p95 = 541.121341
216 p99 = 558.158697
217
218 // but fail if they are actually set (because that would mean the
219 // WithPercentiles() is not respected)
220 if _, isSet := svc.valuesReceived[n95]; isSet {
221 t.Fatal("p95 should not be set")
222 }
223 if _, isSet := svc.valuesReceived[n99]; isSet {
224 t.Fatal("p99 should not be set")
225 }
226 return
227 }
228 if err := teststat.TestHistogram(histogram, customQuantiles, 0.01); err != nil {
229 t.Fatal(err)
230 }
231 if err := svc.testDimensions(n50, label, value); err != nil {
232 t.Fatal(err)
233 }
234 if err := svc.testDimensions(n90, label, value); err != nil {
235 t.Fatal(err)
236 }
237 if err := svc.testDimensions(n95, label, value); err != nil {
238 t.Fatal(err)
239 }
240 if err := svc.testDimensions(n99, label, value); err != nil {
241 t.Fatal(err)
242 }
243 }