diff --git a/json.go b/json.go index 081315c..1cefef7 100644 --- a/json.go +++ b/json.go @@ -1,6 +1,9 @@ package metrics -import "encoding/json" +import ( + "encoding/json" + "io" +) // MarshalJSON returns a byte slice containing a JSON representation of all // the metrics in the Registry. @@ -63,3 +66,66 @@ func (r StandardRegistry) MarshalJSON() ([]byte, error) { }) return json.Marshal(data) } + +// WriteJSONOnce outputs metrics from the given registry to the specified writer in JSON format +func WriteJSONOnce(r Registry, w io.Writer) { + data := make(map[string]map[string]interface{}) + r.Each(func(name string, i interface{}) { + values := make(map[string]interface{}) + switch metric := i.(type) { + case Counter: + values["count"] = metric.Count() + case Gauge: + values["value"] = metric.Value() + case GaugeFloat64: + values["value"] = metric.Value() + case Healthcheck: + values["error"] = nil + metric.Check() + if err := metric.Error(); nil != err { + values["error"] = metric.Error().Error() + } + case Histogram: + h := metric.Snapshot() + ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + values["count"] = h.Count() + values["min"] = h.Min() + values["max"] = h.Max() + values["mean"] = h.Mean() + values["stddev"] = h.StdDev() + values["median"] = ps[0] + values["75%"] = ps[1] + values["95%"] = ps[2] + values["99%"] = ps[3] + values["99.9%"] = ps[4] + case Meter: + m := metric.Snapshot() + values["count"] = m.Count() + values["1m.rate"] = m.Rate1() + values["5m.rate"] = m.Rate5() + values["15m.rate"] = m.Rate15() + values["mean.rate"] = m.RateMean() + case Timer: + t := metric.Snapshot() + ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + values["count"] = t.Count() + values["min"] = t.Min() + values["max"] = t.Max() + values["mean"] = t.Mean() + values["stddev"] = t.StdDev() + values["median"] = ps[0] + values["75%"] = ps[1] + values["95%"] = ps[2] + values["99%"] = ps[3] + values["99.9%"] = ps[4] + values["1m.rate"] = t.Rate1() + values["5m.rate"] = t.Rate5() + values["15m.rate"] = t.Rate15() + values["mean.rate"] = t.RateMean() + } + data[name] = values + }) + + enc := json.NewEncoder(w) + enc.Encode(data) +} diff --git a/json_test.go b/json_test.go index 241aeab..cf70051 100644 --- a/json_test.go +++ b/json_test.go @@ -16,3 +16,13 @@ func TestRegistryMarshallJSON(t *testing.T) { t.Fatalf(s) } } + +func TestRegistryWriteJSONOnce(t *testing.T) { + r := NewRegistry() + r.Register("counter", NewCounter()) + b := &bytes.Buffer{} + WriteJSONOnce(r, b) + if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" { + t.Fail() + } +} diff --git a/metrics.go b/metrics.go index b97a49e..e2f0d68 100644 --- a/metrics.go +++ b/metrics.go @@ -11,3 +11,19 @@ package metrics // This global kill-switch helps quantify the observer effect and makes // for less cluttered pprof profiles. var UseNilMetrics bool = false + +type metric struct { + name string + m interface{} +} + +// metrics is a slice of metrics that implements sort.Interface +type metrics []metric + +func (m metrics) Len() int { return len(m) } + +func (m metrics) Swap(i, j int) { m[i], m[j] = m[j], m[i] } + +func (m metrics) Less(i, j int) bool { + return m[i].name < m[j].name +} diff --git a/metrics_test.go b/metrics_test.go index 083f967..e2d88f3 100644 --- a/metrics_test.go +++ b/metrics_test.go @@ -5,6 +5,7 @@ import ( "log" "sync" "testing" + "sort" ) const FANOUT = 128 @@ -105,3 +106,19 @@ func BenchmarkMetrics(b *testing.B) { wgR.Wait() wgW.Wait() } + +func TestMetricsSorting(t *testing.T) { + var data = metrics{ + {name:"zzz"}, + {name:"bbb"}, + {name:"fff"}, + {name:"ggg"}, + } + + sort.Sort(data) + for i, d := range []string{"bbb","fff","ggg","zzz"} { + if data[i].name != d { + t.Fail() + } + } +} diff --git a/writer.go b/writer.go index fa37a6f..32149fd 100644 --- a/writer.go +++ b/writer.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "time" + "sort" ) // Output each metric in the given registry periodically using the given @@ -15,26 +16,35 @@ func Write(r Registry, d time.Duration, w io.Writer) { } } +// WriteOnce outputs metrics for the given registry to the specified io.Writer +// +// WriteOnce sorts by metric name to ensure the output remains consistent between runs func WriteOnce(r Registry, w io.Writer) { + var data metrics r.Each(func(name string, i interface{}) { - switch metric := i.(type) { + data = append(data, metric{name, i}) + }) + + sort.Sort(data) + for _, i := range data { + switch metric := i.m.(type) { case Counter: - fmt.Fprintf(w, "counter %s\n", name) + fmt.Fprintf(w, "counter %s\n", i.name) fmt.Fprintf(w, " count: %9d\n", metric.Count()) case Gauge: - fmt.Fprintf(w, "gauge %s\n", name) + fmt.Fprintf(w, "gauge %s\n", i.name) fmt.Fprintf(w, " value: %9d\n", metric.Value()) case GaugeFloat64: - fmt.Fprintf(w, "gauge %s\n", name) + fmt.Fprintf(w, "gauge %s\n", i.name) fmt.Fprintf(w, " value: %f\n", metric.Value()) case Healthcheck: metric.Check() - fmt.Fprintf(w, "healthcheck %s\n", name) + fmt.Fprintf(w, "healthcheck %s\n", i.name) fmt.Fprintf(w, " error: %v\n", metric.Error()) case Histogram: h := metric.Snapshot() ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) - fmt.Fprintf(w, "histogram %s\n", name) + fmt.Fprintf(w, "histogram %s\n", i.name) fmt.Fprintf(w, " count: %9d\n", h.Count()) fmt.Fprintf(w, " min: %9d\n", h.Min()) fmt.Fprintf(w, " max: %9d\n", h.Max()) @@ -47,7 +57,7 @@ func WriteOnce(r Registry, w io.Writer) { fmt.Fprintf(w, " 99.9%%: %12.2f\n", ps[4]) case Meter: m := metric.Snapshot() - fmt.Fprintf(w, "meter %s\n", name) + fmt.Fprintf(w, "meter %s\n", i.name) fmt.Fprintf(w, " count: %9d\n", m.Count()) fmt.Fprintf(w, " 1-min rate: %12.2f\n", m.Rate1()) fmt.Fprintf(w, " 5-min rate: %12.2f\n", m.Rate5()) @@ -56,7 +66,7 @@ func WriteOnce(r Registry, w io.Writer) { case Timer: t := metric.Snapshot() ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) - fmt.Fprintf(w, "timer %s\n", name) + fmt.Fprintf(w, "timer %s\n", i.name) fmt.Fprintf(w, " count: %9d\n", t.Count()) fmt.Fprintf(w, " min: %9d\n", t.Min()) fmt.Fprintf(w, " max: %9d\n", t.Max()) @@ -72,5 +82,5 @@ func WriteOnce(r Registry, w io.Writer) { fmt.Fprintf(w, " 15-min rate: %12.2f\n", t.Rate15()) fmt.Fprintf(w, " mean rate: %12.2f\n", t.RateMean()) } - }) + } }