Merge pull request #12 from prometheus/refactor/reduce-marshaling-complexity

Refactor/reduce marshaling complexity
This commit is contained in:
Bernerd Schaefer 2013-04-19 06:12:13 -07:00
commit 04c7f7bc7f
9 changed files with 107 additions and 156 deletions

View File

@ -7,6 +7,7 @@
package prometheus package prometheus
import ( import (
"encoding/json"
"fmt" "fmt"
"sync" "sync"
) )
@ -14,19 +15,18 @@ import (
// TODO(matt): Refactor to de-duplicate behaviors. // TODO(matt): Refactor to de-duplicate behaviors.
type Counter interface { type Counter interface {
AsMarshallable() map[string]interface{} Metric
Decrement(labels map[string]string) float64 Decrement(labels map[string]string) float64
DecrementBy(labels map[string]string, value float64) float64 DecrementBy(labels map[string]string, value float64) float64
Increment(labels map[string]string) float64 Increment(labels map[string]string) float64
IncrementBy(labels map[string]string, value float64) float64 IncrementBy(labels map[string]string, value float64) float64
ResetAll()
Set(labels map[string]string, value float64) float64 Set(labels map[string]string, value float64) float64
String() string
} }
type counterVector struct { type counterVector struct {
labels map[string]string Labels map[string]string `json:"labels"`
value float64 Value float64 `json:"value"`
} }
func NewCounter() Counter { func NewCounter() Counter {
@ -50,11 +50,11 @@ func (metric *counter) Set(labels map[string]string, value float64) float64 {
signature := labelsToSignature(labels) signature := labelsToSignature(labels)
if original, ok := metric.values[signature]; ok { if original, ok := metric.values[signature]; ok {
original.value = value original.Value = value
} else { } else {
metric.values[signature] = &counterVector{ metric.values[signature] = &counterVector{
labels: labels, Labels: labels,
value: value, Value: value,
} }
} }
@ -66,8 +66,8 @@ func (metric *counter) ResetAll() {
defer metric.mutex.Unlock() defer metric.mutex.Unlock()
for key, value := range metric.values { for key, value := range metric.values {
for label := range value.labels { for label := range value.Labels {
delete(value.labels, label) delete(value.Labels, label)
} }
delete(metric.values, key) delete(metric.values, key)
} }
@ -92,11 +92,11 @@ func (metric *counter) IncrementBy(labels map[string]string, value float64) floa
signature := labelsToSignature(labels) signature := labelsToSignature(labels)
if original, ok := metric.values[signature]; ok { if original, ok := metric.values[signature]; ok {
original.value += value original.Value += value
} else { } else {
metric.values[signature] = &counterVector{ metric.values[signature] = &counterVector{
labels: labels, Labels: labels,
value: value, Value: value,
} }
} }
@ -117,11 +117,11 @@ func (metric *counter) DecrementBy(labels map[string]string, value float64) floa
signature := labelsToSignature(labels) signature := labelsToSignature(labels)
if original, ok := metric.values[signature]; ok { if original, ok := metric.values[signature]; ok {
original.value -= value original.Value -= value
} else { } else {
metric.values[signature] = &counterVector{ metric.values[signature] = &counterVector{
labels: labels, Labels: labels,
value: -1 * value, Value: -1 * value,
} }
} }
@ -132,20 +132,18 @@ func (metric *counter) Decrement(labels map[string]string) float64 {
return metric.DecrementBy(labels, 1) return metric.DecrementBy(labels, 1)
} }
func (metric counter) AsMarshallable() map[string]interface{} { func (metric counter) MarshalJSON() ([]byte, error) {
metric.mutex.RLock() metric.mutex.RLock()
defer metric.mutex.RUnlock() defer metric.mutex.RUnlock()
values := make([]map[string]interface{}, 0, len(metric.values)) values := make([]*counterVector, 0, len(metric.values))
for _, value := range metric.values { for _, value := range metric.values {
values = append(values, map[string]interface{}{ values = append(values, value)
labelsKey: value.labels,
valueKey: value.value,
})
} }
return map[string]interface{}{ return json.Marshal(map[string]interface{}{
valueKey: values, valueKey: values,
typeKey: counterTypeValue, typeKey: counterTypeValue,
} })
} }

View File

@ -28,7 +28,7 @@ func testCounter(t tester) {
steps: []func(g Counter){}, steps: []func(g Counter){},
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[]}", value: `{"type":"counter","value":[]}`,
}, },
}, },
{ {
@ -40,7 +40,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{},\"value\":1}]}", value: `{"type":"counter","value":[{"labels":{},"value":1}]}`,
}, },
}, },
{ {
@ -52,7 +52,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{},\"value\":2}]}", value: `{"type":"counter","value":[{"labels":{},"value":2}]}`,
}, },
}, },
{ {
@ -67,7 +67,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{},\"value\":5}]}", value: `{"type":"counter","value":[{"labels":{},"value":5}]}`,
}, },
}, },
{ {
@ -85,7 +85,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[]}", value: `{"type":"counter","value":[]}`,
}, },
}, },
{ {
@ -97,7 +97,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":19}]}", value: `{"type":"counter","value":[{"labels":{"handler":"/foo"},"value":19}]}`,
}, },
}, },
{ {
@ -112,7 +112,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":24}]}", value: `{"type":"counter","value":[{"labels":{"handler":"/foo"},"value":24}]}`,
}, },
}, },
{ {
@ -124,7 +124,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":1}]}", value: `{"type":"counter","value":[{"labels":{"handler":"/foo"},"value":1}]}`,
}, },
}, },
{ {
@ -136,7 +136,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":-1}]}", value: `{"type":"counter","value":[{"labels":{"handler":"/foo"},"value":-1}]}`,
}, },
}, },
{ {
@ -151,7 +151,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":28}]}", value: `{"type":"counter","value":[{"labels":{"handler":"/foo"},"value":28}]}`,
}, },
}, },
{ {
@ -166,7 +166,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":36}]}", value: `{"type":"counter","value":[{"labels":{"handler":"/foo"},"value":36}]}`,
}, },
}, },
{ {
@ -181,7 +181,7 @@ func testCounter(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":27}]}", value: `{"type":"counter","value":[{"labels":{"handler":"/foo"},"value":27}]}`,
}, },
}, },
} }
@ -193,9 +193,7 @@ func testCounter(t tester) {
step(counter) step(counter)
} }
marshallable := counter.AsMarshallable() bytes, err := json.Marshal(counter)
bytes, err := json.Marshal(marshallable)
if err != nil { if err != nil {
t.Errorf("%d. could not marshal into JSON %s", i, err) t.Errorf("%d. could not marshal into JSON %s", i, err)
continue continue

View File

@ -7,6 +7,7 @@
package prometheus package prometheus
import ( import (
"encoding/json"
"fmt" "fmt"
"sync" "sync"
) )
@ -16,15 +17,13 @@ import (
// temperature or the hitherto bandwidth used, this would be the metric for such // temperature or the hitherto bandwidth used, this would be the metric for such
// circumstances. // circumstances.
type Gauge interface { type Gauge interface {
AsMarshallable() map[string]interface{} Metric
ResetAll()
Set(labels map[string]string, value float64) float64 Set(labels map[string]string, value float64) float64
String() string
} }
type gaugeVector struct { type gaugeVector struct {
labels map[string]string Labels map[string]string `json:"labels"`
value float64 Value float64 `json:"value"`
} }
func NewGauge() Gauge { func NewGauge() Gauge {
@ -58,11 +57,11 @@ func (metric *gauge) Set(labels map[string]string, value float64) float64 {
signature := labelsToSignature(labels) signature := labelsToSignature(labels)
if original, ok := metric.values[signature]; ok { if original, ok := metric.values[signature]; ok {
original.value = value original.Value = value
} else { } else {
metric.values[signature] = &gaugeVector{ metric.values[signature] = &gaugeVector{
labels: labels, Labels: labels,
value: value, Value: value,
} }
} }
@ -74,27 +73,24 @@ func (metric *gauge) ResetAll() {
defer metric.mutex.Unlock() defer metric.mutex.Unlock()
for key, value := range metric.values { for key, value := range metric.values {
for label := range value.labels { for label := range value.Labels {
delete(value.labels, label) delete(value.Labels, label)
} }
delete(metric.values, key) delete(metric.values, key)
} }
} }
func (metric gauge) AsMarshallable() map[string]interface{} { func (metric gauge) MarshalJSON() ([]byte, error) {
metric.mutex.RLock() metric.mutex.RLock()
defer metric.mutex.RUnlock() defer metric.mutex.RUnlock()
values := make([]map[string]interface{}, 0, len(metric.values)) values := make([]*gaugeVector, 0, len(metric.values))
for _, value := range metric.values { for _, value := range metric.values {
values = append(values, map[string]interface{}{ values = append(values, value)
labelsKey: value.labels,
valueKey: value.value,
})
} }
return map[string]interface{}{ return json.Marshal(map[string]interface{}{
typeKey: gaugeTypeValue, typeKey: gaugeTypeValue,
valueKey: values, valueKey: values,
} })
} }

View File

@ -28,7 +28,7 @@ func testGauge(t tester) {
steps: []func(g Gauge){}, steps: []func(g Gauge){},
}, },
out: output{ out: output{
value: "{\"type\":\"gauge\",\"value\":[]}", value: `{"type":"gauge","value":[]}`,
}, },
}, },
{ {
@ -40,7 +40,7 @@ func testGauge(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"gauge\",\"value\":[{\"labels\":{},\"value\":1}]}", value: `{"type":"gauge","value":[{"labels":{},"value":1}]}`,
}, },
}, },
{ {
@ -52,7 +52,7 @@ func testGauge(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"gauge\",\"value\":[{\"labels\":{},\"value\":2}]}", value: `{"type":"gauge","value":[{"labels":{},"value":2}]}`,
}, },
}, },
{ {
@ -67,7 +67,7 @@ func testGauge(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"gauge\",\"value\":[{\"labels\":{},\"value\":5}]}", value: `{"type":"gauge","value":[{"labels":{},"value":5}]}`,
}, },
}, },
{ {
@ -85,7 +85,7 @@ func testGauge(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"gauge\",\"value\":[]}", value: `{"type":"gauge","value":[]}`,
}, },
}, },
{ {
@ -97,7 +97,7 @@ func testGauge(t tester) {
}, },
}, },
out: output{ out: output{
value: "{\"type\":\"gauge\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":19}]}", value: `{"type":"gauge","value":[{"labels":{"handler":"/foo"},"value":19}]}`,
}, },
}, },
} }
@ -109,9 +109,7 @@ func testGauge(t tester) {
step(gauge) step(gauge)
} }
marshallable := gauge.AsMarshallable() bytes, err := json.Marshal(gauge)
bytes, err := json.Marshal(marshallable)
if err != nil { if err != nil {
t.Errorf("%d. could not marshal into JSON %s", i, err) t.Errorf("%d. could not marshal into JSON %s", i, err)
continue continue

View File

@ -8,6 +8,7 @@ package prometheus
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"math" "math"
"strconv" "strconv"
@ -52,10 +53,8 @@ type HistogramSpecification struct {
} }
type Histogram interface { type Histogram interface {
Metric
Add(labels map[string]string, value float64) Add(labels map[string]string, value float64)
AsMarshallable() map[string]interface{}
ResetAll()
String() string
} }
// The histogram is an accumulator for samples. It merely routes into which // The histogram is an accumulator for samples. It merely routes into which
@ -240,29 +239,30 @@ func formatFloat(value float64) string {
return strconv.FormatFloat(value, floatFormat, floatPrecision, floatBitCount) return strconv.FormatFloat(value, floatFormat, floatPrecision, floatBitCount)
} }
func (h histogram) AsMarshallable() map[string]interface{} { func (h histogram) MarshalJSON() ([]byte, error) {
h.mutex.RLock() h.mutex.RLock()
defer h.mutex.RUnlock() defer h.mutex.RUnlock()
result := make(map[string]interface{}, 2)
result[typeKey] = histogramTypeValue
values := make([]map[string]interface{}, 0, len(h.values)) values := make([]map[string]interface{}, 0, len(h.values))
for signature, value := range h.values { for signature, value := range h.values {
metricContainer := map[string]interface{}{} percentiles := make(map[string]float64, len(h.reportablePercentiles))
metricContainer[labelsKey] = value.labels
intermediate := map[string]interface{}{}
for _, percentile := range h.reportablePercentiles { for _, percentile := range h.reportablePercentiles {
formatted := formatFloat(percentile) formatted := formatFloat(percentile)
intermediate[formatted] = h.percentile(signature, percentile) percentiles[formatted] = h.percentile(signature, percentile)
}
metricContainer[valueKey] = intermediate
values = append(values, metricContainer)
} }
result[valueKey] = values values = append(values, map[string]interface{}{
labelsKey: value.labels,
valueKey: percentiles,
})
}
return result return json.Marshal(map[string]interface{}{
typeKey: histogramTypeValue,
valueKey: values,
})
} }
func (h *histogram) ResetAll() { func (h *histogram) ResetAll() {

View File

@ -6,10 +6,12 @@
package prometheus package prometheus
import "encoding/json"
// A Metric is something that can be exposed via the registry framework. // A Metric is something that can be exposed via the registry framework.
type Metric interface { type Metric interface {
// Produce a JSON-consumable representation of the metric. // Produce a JSON representation of the metric.
AsMarshallable() map[string]interface{} json.Marshaler
// Reset the parent metrics and delete all child metrics. // Reset the parent metrics and delete all child metrics.
ResetAll() ResetAll()
// Produce a human-consumable representation of the metric. // Produce a human-consumable representation of the metric.

View File

@ -43,9 +43,9 @@ var (
// container represents a top-level registered metric that encompasses its // container represents a top-level registered metric that encompasses its
// static metadata. // static metadata.
type container struct { type container struct {
baseLabels map[string]string BaseLabels map[string]string `json:"baseLabels"`
docstring string Docstring string `json:"docstring"`
metric Metric Metric Metric `json:"metric"`
name string name string
} }
@ -82,6 +82,24 @@ func Register(name, docstring string, baseLabels map[string]string, metric Metri
return DefaultRegistry.Register(name, docstring, baseLabels, metric) return DefaultRegistry.Register(name, docstring, baseLabels, metric)
} }
// Implements json.Marshaler
func (r registry) MarshalJSON() (_ []byte, err error) {
metrics := make([]interface{}, 0, len(r.signatureContainers))
keys := make([]string, 0, len(metrics))
for key := range r.signatureContainers {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
metrics = append(metrics, r.signatureContainers[key])
}
return json.Marshal(metrics)
}
// isValidCandidate returns true if the candidate is acceptable for use. In the // isValidCandidate returns true if the candidate is acceptable for use. In the
// event of any apparent incorrect use it will report the problem, invalidate // event of any apparent incorrect use it will report the problem, invalidate
// the candidate, or outright abort. // the candidate, or outright abort.
@ -125,7 +143,7 @@ func (r registry) isValidCandidate(name string, baseLabels map[string]string) (s
if useAggressiveSanityChecks { if useAggressiveSanityChecks {
for _, container := range r.signatureContainers { for _, container := range r.signatureContainers {
if container.name == name { if container.name == name {
err = fmt.Errorf("metric named %s with baseLabels %s is already registered as %s and risks causing confusion", name, baseLabels, container.baseLabels) err = fmt.Errorf("metric named %s with baseLabels %s is already registered as %s and risks causing confusion", name, baseLabels, container.BaseLabels)
if abortOnMisuse { if abortOnMisuse {
panic(err) panic(err)
} else if debugRegistration { } else if debugRegistration {
@ -154,9 +172,9 @@ func (r registry) Register(name, docstring string, baseLabels map[string]string,
} }
r.signatureContainers[signature] = container{ r.signatureContainers[signature] = container{
baseLabels: baseLabels, BaseLabels: baseLabels,
docstring: docstring, Docstring: docstring,
metric: metric, Metric: metric,
name: name, name: name,
} }
@ -194,61 +212,6 @@ func (register registry) YieldBasicAuthExporter(username, password string) http.
}) })
} }
func (registry registry) dumpToWriter(writer io.Writer) (err error) {
defer func() {
if err != nil {
dumpErrorCount.Increment(nil)
}
}()
numberOfMetrics := len(registry.signatureContainers)
keys := make([]string, 0, numberOfMetrics)
for key := range registry.signatureContainers {
keys = append(keys, key)
}
sort.Strings(keys)
_, err = writer.Write([]byte("["))
if err != nil {
return
}
index := 0
for _, key := range keys {
container := registry.signatureContainers[key]
intermediate := map[string]interface{}{
baseLabelsKey: container.baseLabels,
docstringKey: container.docstring,
metricKey: container.metric.AsMarshallable(),
}
marshaled, err := json.Marshal(intermediate)
if err != nil {
marshalErrorCount.Increment(nil)
index++
continue
}
if index > 0 && index < numberOfMetrics {
_, err = writer.Write([]byte(","))
if err != nil {
return err
}
}
_, err = writer.Write(marshaled)
if err != nil {
return err
}
index++
}
_, err = writer.Write([]byte("]"))
return
}
// decorateWriter annotates the response writer to handle any other behaviors // decorateWriter annotates the response writer to handle any other behaviors
// that might be beneficial to the client---e.g., GZIP encoding. // that might be beneficial to the client---e.g., GZIP encoding.
func decorateWriter(request *http.Request, writer http.ResponseWriter) io.Writer { func decorateWriter(request *http.Request, writer http.ResponseWriter) io.Writer {
@ -286,8 +249,7 @@ func (registry registry) Handler() http.HandlerFunc {
defer closer.Close() defer closer.Close()
} }
registry.dumpToWriter(writer) json.NewEncoder(writer).Encode(registry)
} else { } else {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
} }

View File

@ -8,6 +8,7 @@ package prometheus
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -282,7 +283,7 @@ func testDumpToWriter(t tester) {
"foo": NewCounter(), "foo": NewCounter(),
}, },
}, },
out: []byte("[{\"baseLabels\":{\"label_foo\":\"foo\",\"name\":\"foo\"},\"docstring\":\"metric foo\",\"metric\":{\"type\":\"counter\",\"value\":[]}}]"), out: []byte(`[{"baseLabels":{"label_foo":"foo","name":"foo"},"docstring":"metric foo","metric":{"type":"counter","value":[]}}]`),
}, },
{ {
in: input{ in: input{
@ -291,7 +292,7 @@ func testDumpToWriter(t tester) {
"bar": NewCounter(), "bar": NewCounter(),
}, },
}, },
out: []byte("[{\"baseLabels\":{\"label_bar\":\"bar\",\"name\":\"bar\"},\"docstring\":\"metric bar\",\"metric\":{\"type\":\"counter\",\"value\":[]}},{\"baseLabels\":{\"label_foo\":\"foo\",\"name\":\"foo\"},\"docstring\":\"metric foo\",\"metric\":{\"type\":\"counter\",\"value\":[]}}]"), out: []byte(`[{"baseLabels":{"label_bar":"bar","name":"bar"},"docstring":"metric bar","metric":{"type":"counter","value":[]}},{"baseLabels":{"label_foo":"foo","name":"foo"},"docstring":"metric foo","metric":{"type":"counter","value":[]}}]`),
}, },
} }
@ -305,15 +306,14 @@ func testDumpToWriter(t tester) {
} }
} }
actual := &bytes.Buffer{} actual, err := json.Marshal(registry)
err := registry.dumpToWriter(actual)
if err != nil { if err != nil {
t.Errorf("%d. encountered error while dumping %s", i, err) t.Errorf("%d. encountered error while dumping %s", i, err)
} }
if !bytes.Equal(scenario.out, actual.Bytes()) { if !bytes.Equal(scenario.out, actual) {
t.Errorf("%d. expected %q for dumping, got %q", i, scenario.out, actual.Bytes()) t.Errorf("%d. expected %q for dumping, got %q", i, scenario.out, actual)
} }
} }
} }

View File

@ -15,9 +15,6 @@ import (
// exposed if the DefaultRegistry's exporter is hooked into the HTTP request // exposed if the DefaultRegistry's exporter is hooked into the HTTP request
// handler. // handler.
var ( var (
marshalErrorCount = NewCounter()
dumpErrorCount = NewCounter()
requestCount = NewCounter() requestCount = NewCounter()
requestLatencyBuckets = LogarithmicSizedBucketsFor(0, 1000) requestLatencyBuckets = LogarithmicSizedBucketsFor(0, 1000)
requestLatency = NewHistogram(&HistogramSpecification{ requestLatency = NewHistogram(&HistogramSpecification{