// Package teststat provides helpers for testing metrics backends. package teststat import ( "errors" "fmt" "math" "math/rand" "strings" "github.com/go-kit/kit/metrics" ) // TestCounter puts some deltas through the counter, and then calls the value // func to check that the counter has the correct final value. func TestCounter(counter metrics.Counter, value func() float64) error { want := FillCounter(counter) if have := value(); want != have { return fmt.Errorf("want %f, have %f", want, have) } return nil } // FillCounter puts some deltas through the counter and returns the total value. func FillCounter(counter metrics.Counter) float64 { a := rand.Perm(100) n := rand.Intn(len(a)) var want float64 for i := 0; i < n; i++ { f := float64(a[i]) counter.Add(f) want += f } return want } // TestGauge puts some values through the gauge, and then calls the value func // to check that the gauge has the correct final value. func TestGauge(gauge metrics.Gauge, value func() float64) error { a := rand.Perm(100) n := rand.Intn(len(a)) var want float64 for i := 0; i < n; i++ { f := float64(a[i]) gauge.Set(f) want = f } for i := 0; i < n; i++ { f := float64(a[i]) gauge.Add(f) want += f } if have := value(); want != have { return fmt.Errorf("want %f, have %f", want, have) } return nil } // TestHistogram puts some observations through the histogram, and then calls // the quantiles func to checks that the histogram has computed the correct // quantiles within some tolerance func TestHistogram(histogram metrics.Histogram, quantiles func() (p50, p90, p95, p99 float64), tolerance float64) error { PopulateNormalHistogram(histogram, rand.Int()) want50, want90, want95, want99 := normalQuantiles() have50, have90, have95, have99 := quantiles() var errs []string if want, have := want50, have50; !cmp(want, have, tolerance) { errs = append(errs, fmt.Sprintf("p50: want %f, have %f", want, have)) } if want, have := want90, have90; !cmp(want, have, tolerance) { errs = append(errs, fmt.Sprintf("p90: want %f, have %f", want, have)) } if want, have := want95, have95; !cmp(want, have, tolerance) { errs = append(errs, fmt.Sprintf("p95: want %f, have %f", want, have)) } if want, have := want99, have99; !cmp(want, have, tolerance) { errs = append(errs, fmt.Sprintf("p99: want %f, have %f", want, have)) } if len(errs) > 0 { return errors.New(strings.Join(errs, "; ")) } return nil } var ( // Count is the number of observations. Count = 12345 // Mean is the center of the normal distribution of observations. Mean = 500 // Stdev of the normal distribution of observations. Stdev = 25 ) // ExpectedObservationsLessThan returns the number of observations that should // have a value less than or equal to the given value, given a normal // distribution of observations described by Count, Mean, and Stdev. func ExpectedObservationsLessThan(bucket int64) int64 { // https://code.google.com/p/gostat/source/browse/stat/normal.go cdf := ((1.0 / 2.0) * (1 + math.Erf((float64(bucket)-float64(Mean))/(float64(Stdev)*math.Sqrt2)))) return int64(cdf * float64(Count)) } func cmp(want, have, tol float64) bool { if (math.Abs(want-have) / want) > tol { return false } return true }