Registry and Metrics implement json.Marshaler
* Drop `AsMarshallable()` from the Metric interface. Use `json.Marshaler` and `MarshalJSON()`, and leverage JSON struct tags where possible. * Add `MarshalJSON()` to Registry and remove `dumpToWriter`, which makes the registry handler much simpler. In addition to simplifying some of the marshalling behavior, this also has the nice side effect of cutting down the number of `map[string]interface{}` instances.
This commit is contained in:
parent
3433b798b3
commit
71dd60e431
|
@ -7,6 +7,7 @@
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -24,8 +25,8 @@ type Counter interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -49,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,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)
|
||||||
}
|
}
|
||||||
|
@ -91,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,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,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -21,8 +22,8 @@ type Gauge interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -56,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,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,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -8,6 +8,7 @@ package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -238,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() {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
Loading…
Reference in New Issue