diff --git a/json.go b/json.go index 081315c..e32058a 100644 --- a/json.go +++ b/json.go @@ -1,6 +1,10 @@ package metrics -import "encoding/json" +import ( + "encoding/json" + "io" + "time" +) // MarshalJSON returns a byte slice containing a JSON representation of all // the metrics in the Registry. @@ -63,3 +67,18 @@ func (r StandardRegistry) MarshalJSON() ([]byte, error) { }) return json.Marshal(data) } + +// WriteJSON writes metrics from the given registry periodically to the +// specified io.Writer as JSON. +func WriteJSON(r Registry, d time.Duration, w io.Writer) { + for { + WriteJSONOnce(r, w) + time.Sleep(d) + } +} + +// WriteJSONOnce writes metrics from the given registry to the specified +// io.Writer as JSON. +func WriteJSONOnce(r Registry, w io.Writer) { + json.NewEncoder(w).Encode(r) +} 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/writer.go b/writer.go index fa37a6f..c1b16b7 100644 --- a/writer.go +++ b/writer.go @@ -3,11 +3,12 @@ package metrics import ( "fmt" "io" + "sort" "time" ) -// Output each metric in the given registry periodically using the given -// io.Writer. +// Write sorts writes each metric in the given registry periodically to the +// given io.Writer. func Write(r Registry, d time.Duration, w io.Writer) { for { WriteOnce(r, w) @@ -15,26 +16,34 @@ func Write(r Registry, d time.Duration, w io.Writer) { } } +// WriteOnce sorts and writes metrics in the given registry to the given +// io.Writer. func WriteOnce(r Registry, w io.Writer) { + var namedMetrics namedMetricSlice r.Each(func(name string, i interface{}) { - switch metric := i.(type) { + namedMetrics = append(namedMetrics, namedMetric{name, i}) + }) + + sort.Sort(namedMetrics) + for _, namedMetric := range namedMetrics { + switch metric := namedMetric.m.(type) { case Counter: - fmt.Fprintf(w, "counter %s\n", name) + fmt.Fprintf(w, "counter %s\n", namedMetric.name) fmt.Fprintf(w, " count: %9d\n", metric.Count()) case Gauge: - fmt.Fprintf(w, "gauge %s\n", name) + fmt.Fprintf(w, "gauge %s\n", namedMetric.name) fmt.Fprintf(w, " value: %9d\n", metric.Value()) case GaugeFloat64: - fmt.Fprintf(w, "gauge %s\n", name) + fmt.Fprintf(w, "gauge %s\n", namedMetric.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", namedMetric.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", namedMetric.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 +56,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", namedMetric.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 +65,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", namedMetric.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 +81,21 @@ 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()) } - }) + } +} + +type namedMetric struct { + name string + m interface{} +} + +// namedMetricSlice is a slice of namedMetrics that implements sort.Interface. +type namedMetricSlice []namedMetric + +func (nms namedMetricSlice) Len() int { return len(nms) } + +func (nms namedMetricSlice) Swap(i, j int) { nms[i], nms[j] = nms[j], nms[i] } + +func (nms namedMetricSlice) Less(i, j int) bool { + return nms[i].name < nms[j].name } diff --git a/writer_test.go b/writer_test.go new file mode 100644 index 0000000..1aacc28 --- /dev/null +++ b/writer_test.go @@ -0,0 +1,22 @@ +package metrics + +import ( + "sort" + "testing" +) + +func TestMetricsSorting(t *testing.T) { + var namedMetrics = namedMetricSlice{ + {name: "zzz"}, + {name: "bbb"}, + {name: "fff"}, + {name: "ggg"}, + } + + sort.Sort(namedMetrics) + for i, name := range []string{"bbb", "fff", "ggg", "zzz"} { + if namedMetrics[i].name != name { + t.Fail() + } + } +}