Commit a4ac5ef5 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #14302 from timstclair/prometheus-update

Auto commit by PR queue bot
parents 538cf722 8d49c8e2
...@@ -478,24 +478,9 @@ ...@@ -478,24 +478,9 @@
"Rev": "ca53cad383cad2479bbba7f7a1a05797ec1386e4" "Rev": "ca53cad383cad2479bbba7f7a1a05797ec1386e4"
}, },
{ {
"ImportPath": "github.com/prometheus/client_golang/extraction",
"Comment": "0.4.0-1-g692492e",
"Rev": "692492e54b553a81013254cc1fba4b6dd76fad30"
},
{
"ImportPath": "github.com/prometheus/client_golang/model",
"Comment": "0.4.0-1-g692492e",
"Rev": "692492e54b553a81013254cc1fba4b6dd76fad30"
},
{
"ImportPath": "github.com/prometheus/client_golang/prometheus", "ImportPath": "github.com/prometheus/client_golang/prometheus",
"Comment": "0.4.0-1-g692492e", "Comment": "0.7.0-39-g3b78d7a",
"Rev": "692492e54b553a81013254cc1fba4b6dd76fad30" "Rev": "3b78d7a77f51ccbc364d4bc170920153022cfd08"
},
{
"ImportPath": "github.com/prometheus/client_golang/text",
"Comment": "0.4.0-1-g692492e",
"Rev": "692492e54b553a81013254cc1fba4b6dd76fad30"
}, },
{ {
"ImportPath": "github.com/prometheus/client_model/go", "ImportPath": "github.com/prometheus/client_model/go",
...@@ -503,6 +488,14 @@ ...@@ -503,6 +488,14 @@
"Rev": "fa8ad6fec33561be4280a8f0514318c79d7f6cb6" "Rev": "fa8ad6fec33561be4280a8f0514318c79d7f6cb6"
}, },
{ {
"ImportPath": "github.com/prometheus/common/expfmt",
"Rev": "ef7a9a5fb138aa5d3a19988537606226869a0390"
},
{
"ImportPath": "github.com/prometheus/common/model",
"Rev": "ef7a9a5fb138aa5d3a19988537606226869a0390"
},
{
"ImportPath": "github.com/prometheus/procfs", "ImportPath": "github.com/prometheus/procfs",
"Rev": "490cc6eb5fa45bf8a8b7b73c8bc82a8160e8531d" "Rev": "490cc6eb5fa45bf8a8b7b73c8bc82a8160e8531d"
}, },
......
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"errors"
"fmt"
"mime"
"net/http"
)
// ProcessorForRequestHeader interprets a HTTP request header to determine
// what Processor should be used for the given input. If no acceptable
// Processor can be found, an error is returned.
func ProcessorForRequestHeader(header http.Header) (Processor, error) {
if header == nil {
return nil, errors.New("received illegal and nil header")
}
mediatype, params, err := mime.ParseMediaType(header.Get("Content-Type"))
if err != nil {
return nil, fmt.Errorf("invalid Content-Type header %q: %s", header.Get("Content-Type"), err)
}
switch mediatype {
case "application/vnd.google.protobuf":
if params["proto"] != "io.prometheus.client.MetricFamily" {
return nil, fmt.Errorf("unrecognized protocol message %s", params["proto"])
}
if params["encoding"] != "delimited" {
return nil, fmt.Errorf("unsupported encoding %s", params["encoding"])
}
return MetricFamilyProcessor, nil
case "text/plain":
switch params["version"] {
case "0.0.4":
return Processor004, nil
case "":
// Fallback: most recent version.
return Processor004, nil
default:
return nil, fmt.Errorf("unrecognized API version %s", params["version"])
}
case "application/json":
var prometheusAPIVersion string
if params["schema"] == "prometheus/telemetry" && params["version"] != "" {
prometheusAPIVersion = params["version"]
} else {
prometheusAPIVersion = header.Get("X-Prometheus-API-Version")
}
switch prometheusAPIVersion {
case "0.0.2":
return Processor002, nil
case "0.0.1":
return Processor001, nil
default:
return nil, fmt.Errorf("unrecognized API version %s", prometheusAPIVersion)
}
default:
return nil, fmt.Errorf("unsupported media type %q, expected %q", mediatype, "application/json")
}
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"errors"
"net/http"
"testing"
)
func testDiscriminatorHTTPHeader(t testing.TB) {
var scenarios = []struct {
input map[string]string
output Processor
err error
}{
{
output: nil,
err: errors.New("received illegal and nil header"),
},
{
input: map[string]string{"Content-Type": "application/json", "X-Prometheus-API-Version": "0.0.0"},
output: nil,
err: errors.New("unrecognized API version 0.0.0"),
},
{
input: map[string]string{"Content-Type": "application/json", "X-Prometheus-API-Version": "0.0.1"},
output: Processor001,
err: nil,
},
{
input: map[string]string{"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.0`},
output: nil,
err: errors.New("unrecognized API version 0.0.0"),
},
{
input: map[string]string{"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.1`},
output: Processor001,
err: nil,
},
{
input: map[string]string{"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.2`},
output: Processor002,
err: nil,
},
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
output: MetricFamilyProcessor,
err: nil,
},
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`},
output: nil,
err: errors.New("unrecognized protocol message illegal"),
},
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`},
output: nil,
err: errors.New("unsupported encoding illegal"),
},
{
input: map[string]string{"Content-Type": `text/plain; version=0.0.4`},
output: Processor004,
err: nil,
},
{
input: map[string]string{"Content-Type": `text/plain`},
output: Processor004,
err: nil,
},
{
input: map[string]string{"Content-Type": `text/plain; version=0.0.3`},
output: nil,
err: errors.New("unrecognized API version 0.0.3"),
},
}
for i, scenario := range scenarios {
var header http.Header
if len(scenario.input) > 0 {
header = http.Header{}
}
for key, value := range scenario.input {
header.Add(key, value)
}
actual, err := ProcessorForRequestHeader(header)
if scenario.err != err {
if scenario.err != nil && err != nil {
if scenario.err.Error() != err.Error() {
t.Errorf("%d. expected %s, got %s", i, scenario.err, err)
}
} else if scenario.err != nil || err != nil {
t.Errorf("%d. expected %s, got %s", i, scenario.err, err)
}
}
if scenario.output != actual {
t.Errorf("%d. expected %s, got %s", i, scenario.output, actual)
}
}
}
func TestDiscriminatorHTTPHeader(t *testing.T) {
testDiscriminatorHTTPHeader(t)
}
func BenchmarkDiscriminatorHTTPHeader(b *testing.B) {
for i := 0; i < b.N; i++ {
testDiscriminatorHTTPHeader(b)
}
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"fmt"
"io"
"math"
dto "github.com/prometheus/client_model/go"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
"github.com/prometheus/client_golang/model"
)
type metricFamilyProcessor struct{}
// MetricFamilyProcessor decodes varint encoded record length-delimited streams
// of io.prometheus.client.MetricFamily.
//
// See http://godoc.org/github.com/matttproud/golang_protobuf_extensions/ext for
// more details.
var MetricFamilyProcessor = &metricFamilyProcessor{}
func (m *metricFamilyProcessor) ProcessSingle(i io.Reader, out Ingester, o *ProcessOptions) error {
family := &dto.MetricFamily{}
for {
family.Reset()
if _, err := pbutil.ReadDelimited(i, family); err != nil {
if err == io.EOF {
return nil
}
return err
}
if err := extractMetricFamily(out, o, family); err != nil {
return err
}
}
}
func extractMetricFamily(out Ingester, o *ProcessOptions, family *dto.MetricFamily) error {
switch family.GetType() {
case dto.MetricType_COUNTER:
if err := extractCounter(out, o, family); err != nil {
return err
}
case dto.MetricType_GAUGE:
if err := extractGauge(out, o, family); err != nil {
return err
}
case dto.MetricType_SUMMARY:
if err := extractSummary(out, o, family); err != nil {
return err
}
case dto.MetricType_UNTYPED:
if err := extractUntyped(out, o, family); err != nil {
return err
}
case dto.MetricType_HISTOGRAM:
if err := extractHistogram(out, o, family); err != nil {
return err
}
}
return nil
}
func extractCounter(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error {
samples := make(model.Samples, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Counter == nil {
continue
}
sample := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(m.Counter.GetValue()),
}
samples = append(samples, sample)
if m.TimestampMs != nil {
sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
} else {
sample.Timestamp = o.Timestamp
}
metric := sample.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.MetricNameLabel] = model.LabelValue(f.GetName())
}
return out.Ingest(samples)
}
func extractGauge(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error {
samples := make(model.Samples, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Gauge == nil {
continue
}
sample := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(m.Gauge.GetValue()),
}
samples = append(samples, sample)
if m.TimestampMs != nil {
sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
} else {
sample.Timestamp = o.Timestamp
}
metric := sample.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.MetricNameLabel] = model.LabelValue(f.GetName())
}
return out.Ingest(samples)
}
func extractSummary(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error {
samples := make(model.Samples, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Summary == nil {
continue
}
timestamp := o.Timestamp
if m.TimestampMs != nil {
timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
}
for _, q := range m.Summary.Quantile {
sample := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(q.GetValue()),
Timestamp: timestamp,
}
samples = append(samples, sample)
metric := sample.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
// BUG(matt): Update other names to "quantile".
metric[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile()))
metric[model.MetricNameLabel] = model.LabelValue(f.GetName())
}
if m.Summary.SampleSum != nil {
sum := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(m.Summary.GetSampleSum()),
Timestamp: timestamp,
}
samples = append(samples, sum)
metric := sum.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
}
if m.Summary.SampleCount != nil {
count := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(m.Summary.GetSampleCount()),
Timestamp: timestamp,
}
samples = append(samples, count)
metric := count.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
}
}
return out.Ingest(samples)
}
func extractUntyped(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error {
samples := make(model.Samples, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Untyped == nil {
continue
}
sample := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(m.Untyped.GetValue()),
}
samples = append(samples, sample)
if m.TimestampMs != nil {
sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
} else {
sample.Timestamp = o.Timestamp
}
metric := sample.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.MetricNameLabel] = model.LabelValue(f.GetName())
}
return out.Ingest(samples)
}
func extractHistogram(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error {
samples := make(model.Samples, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Histogram == nil {
continue
}
timestamp := o.Timestamp
if m.TimestampMs != nil {
timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
}
infSeen := false
for _, q := range m.Histogram.Bucket {
sample := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(q.GetCumulativeCount()),
Timestamp: timestamp,
}
samples = append(samples, sample)
metric := sample.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound()))
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
if math.IsInf(q.GetUpperBound(), +1) {
infSeen = true
}
}
if m.Histogram.SampleSum != nil {
sum := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(m.Histogram.GetSampleSum()),
Timestamp: timestamp,
}
samples = append(samples, sum)
metric := sum.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
}
if m.Histogram.SampleCount != nil {
count := &model.Sample{
Metric: model.Metric{},
Value: model.SampleValue(m.Histogram.GetSampleCount()),
Timestamp: timestamp,
}
samples = append(samples, count)
metric := count.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
if !infSeen {
infBucket := &model.Sample{
Metric: model.Metric{},
Value: count.Value,
Timestamp: timestamp,
}
samples = append(samples, infBucket)
metric := infBucket.Metric
for _, p := range m.Label {
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
metric[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf")
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
}
}
}
return out.Ingest(samples)
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"sort"
"strings"
"testing"
"github.com/prometheus/client_golang/model"
)
var testTime = model.Now()
type metricFamilyProcessorScenario struct {
in string
expected, actual []model.Samples
}
func (s *metricFamilyProcessorScenario) Ingest(samples model.Samples) error {
s.actual = append(s.actual, samples)
return nil
}
func (s *metricFamilyProcessorScenario) test(t *testing.T, set int) {
i := strings.NewReader(s.in)
o := &ProcessOptions{
Timestamp: testTime,
}
err := MetricFamilyProcessor.ProcessSingle(i, s, o)
if err != nil {
t.Fatalf("%d. got error: %s", set, err)
}
if len(s.expected) != len(s.actual) {
t.Fatalf("%d. expected length %d, got %d", set, len(s.expected), len(s.actual))
}
for i, expected := range s.expected {
sort.Sort(s.actual[i])
sort.Sort(expected)
if !expected.Equal(s.actual[i]) {
t.Errorf("%d.%d. expected %s, got %s", set, i, expected, s.actual[i])
}
}
}
func TestMetricFamilyProcessor(t *testing.T) {
scenarios := []metricFamilyProcessorScenario{
{
in: "",
},
{
in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_label_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@",
expected: []model.Samples{
model.Samples{
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_count", "some_label_name": "some_label_value"},
Value: -42,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_count", "another_label_name": "another_label_value"},
Value: 84,
Timestamp: testTime,
},
},
},
},
{
in: "\xb9\x01\n\rrequest_count\x12\x12Number of requests\x18\x02\"O\n#\n\x0fsome_label_name\x12\x10some_label_value\"(\x1a\x12\t\xaeG\xe1z\x14\xae\xef?\x11\x00\x00\x00\x00\x00\x00E\xc0\x1a\x12\t+\x87\x16\xd9\xce\xf7\xef?\x11\x00\x00\x00\x00\x00\x00U\xc0\"A\n)\n\x12another_label_name\x12\x13another_label_value\"\x14\x1a\x12\t\x00\x00\x00\x00\x00\x00\xe0?\x11\x00\x00\x00\x00\x00\x00$@",
expected: []model.Samples{
model.Samples{
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_count", "some_label_name": "some_label_value", "quantile": "0.99"},
Value: -42,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_count", "some_label_name": "some_label_value", "quantile": "0.999"},
Value: -84,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_count", "another_label_name": "another_label_value", "quantile": "0.5"},
Value: 10,
Timestamp: testTime,
},
},
},
},
{
in: "\x8d\x01\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"S:Q\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@\x1a\f\b\x85\x15\x11\x00\x00\x00\x00\x00\x00\xf0\u007f",
expected: []model.Samples{
model.Samples{
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "100"},
Value: 123,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "120"},
Value: 412,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "144"},
Value: 592,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "172.8"},
Value: 1524,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "+Inf"},
Value: 2693,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_sum"},
Value: 1756047.3,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_count"},
Value: 2693,
Timestamp: testTime,
},
},
},
},
}
for i, scenario := range scenarios {
scenario.test(t, i)
}
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"io"
"time"
"github.com/prometheus/client_golang/model"
)
// ProcessOptions dictates how the interpreted stream should be rendered for
// consumption.
type ProcessOptions struct {
// Timestamp is added to each value from the stream that has no explicit
// timestamp set.
Timestamp model.Timestamp
}
// Ingester consumes result streams in whatever way is desired by the user.
type Ingester interface {
Ingest(model.Samples) error
}
// Processor is responsible for decoding the actual message responses from
// stream into a format that can be consumed with the end result written
// to the results channel.
type Processor interface {
// ProcessSingle treats the input as a single self-contained message body and
// transforms it accordingly. It has no support for streaming.
ProcessSingle(in io.Reader, out Ingester, o *ProcessOptions) error
}
// Helper function to convert map[string]string into LabelSet.
//
// NOTE: This should be deleted when support for go 1.0.3 is removed; 1.1 is
// smart enough to unmarshal JSON objects into LabelSet directly.
func labelSet(labels map[string]string) model.LabelSet {
labelset := make(model.LabelSet, len(labels))
for k, v := range labels {
labelset[model.LabelName(k)] = model.LabelValue(v)
}
return labelset
}
// A basic interface only useful in testing contexts for dispensing the time
// in a controlled manner.
type instantProvider interface {
// The current instant.
Now() time.Time
}
// Clock is a simple means for fluently wrapping around standard Go timekeeping
// mechanisms to enhance testability without compromising code readability.
//
// It is sufficient for use on bare initialization. A provider should be
// set only for test contexts. When not provided, it emits the current
// system time.
type clock struct {
// The underlying means through which time is provided, if supplied.
Provider instantProvider
}
// Emit the current instant.
func (t *clock) Now() time.Time {
if t.Provider == nil {
return time.Now()
}
return t.Provider.Now()
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"github.com/prometheus/client_golang/model"
)
const (
baseLabels001 = "baseLabels"
counter001 = "counter"
docstring001 = "docstring"
gauge001 = "gauge"
histogram001 = "histogram"
labels001 = "labels"
metric001 = "metric"
type001 = "type"
value001 = "value"
percentile001 = "percentile"
)
// Processor001 is responsible for decoding payloads from protocol version
// 0.0.1.
var Processor001 = &processor001{}
// processor001 is responsible for handling API version 0.0.1.
type processor001 struct{}
// entity001 represents a the JSON structure that 0.0.1 uses.
type entity001 []struct {
BaseLabels map[string]string `json:"baseLabels"`
Docstring string `json:"docstring"`
Metric struct {
MetricType string `json:"type"`
Value []struct {
Labels map[string]string `json:"labels"`
Value interface{} `json:"value"`
} `json:"value"`
} `json:"metric"`
}
func (p *processor001) ProcessSingle(in io.Reader, out Ingester, o *ProcessOptions) error {
// TODO(matt): Replace with plain-jane JSON unmarshalling.
buffer, err := ioutil.ReadAll(in)
if err != nil {
return err
}
entities := entity001{}
if err = json.Unmarshal(buffer, &entities); err != nil {
return err
}
// TODO(matt): This outer loop is a great basis for parallelization.
pendingSamples := model.Samples{}
for _, entity := range entities {
for _, value := range entity.Metric.Value {
labels := labelSet(entity.BaseLabels).Merge(labelSet(value.Labels))
switch entity.Metric.MetricType {
case gauge001, counter001:
sampleValue, ok := value.Value.(float64)
if !ok {
return fmt.Errorf("could not convert value from %s %s to float64", entity, value)
}
pendingSamples = append(pendingSamples, &model.Sample{
Metric: model.Metric(labels),
Timestamp: o.Timestamp,
Value: model.SampleValue(sampleValue),
})
break
case histogram001:
sampleValue, ok := value.Value.(map[string]interface{})
if !ok {
return fmt.Errorf("could not convert value from %q to a map[string]interface{}", value.Value)
}
for percentile, percentileValue := range sampleValue {
individualValue, ok := percentileValue.(float64)
if !ok {
return fmt.Errorf("could not convert value from %q to a float64", percentileValue)
}
childMetric := make(map[model.LabelName]model.LabelValue, len(labels)+1)
for k, v := range labels {
childMetric[k] = v
}
childMetric[model.LabelName(percentile001)] = model.LabelValue(percentile)
pendingSamples = append(pendingSamples, &model.Sample{
Metric: model.Metric(childMetric),
Timestamp: o.Timestamp,
Value: model.SampleValue(individualValue),
})
}
break
}
}
}
if len(pendingSamples) > 0 {
return out.Ingest(pendingSamples)
}
return nil
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"errors"
"os"
"path"
"sort"
"testing"
"github.com/prometheus/client_golang/model"
)
var test001Time = model.Now()
type testProcessor001ProcessScenario struct {
in string
expected, actual []model.Samples
err error
}
func (s *testProcessor001ProcessScenario) Ingest(samples model.Samples) error {
s.actual = append(s.actual, samples)
return nil
}
func (s *testProcessor001ProcessScenario) test(t testing.TB, set int) {
reader, err := os.Open(path.Join("fixtures", s.in))
if err != nil {
t.Fatalf("%d. couldn't open scenario input file %s: %s", set, s.in, err)
}
options := &ProcessOptions{
Timestamp: test001Time,
}
err = Processor001.ProcessSingle(reader, s, options)
if s.err != err && (s.err == nil || err == nil || err.Error() != s.err.Error()) {
t.Fatalf("%d. expected err of %s, got %s", set, s.err, err)
}
if len(s.actual) != len(s.expected) {
t.Fatalf("%d. expected output length of %d, got %d", set, len(s.expected), len(s.actual))
}
for i, expected := range s.expected {
sort.Sort(s.actual[i])
sort.Sort(expected)
if !expected.Equal(s.actual[i]) {
t.Errorf("%d.%d. expected %s, got %s", set, i, expected, s.actual[i])
}
}
}
func testProcessor001Process(t testing.TB) {
var scenarios = []testProcessor001ProcessScenario{
{
in: "empty.json",
err: errors.New("unexpected end of JSON input"),
},
{
in: "test0_0_1-0_0_2.json",
expected: []model.Samples{
model.Samples{
&model.Sample{
Metric: model.Metric{"service": "zed", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"},
Value: 25,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"service": "bar", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"},
Value: 25,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"service": "foo", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"},
Value: 25,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 0.0459814091918713,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 78.48563317257356,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 15.890724674774395,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 0.0459814091918713,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 78.48563317257356,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 15.890724674774395,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 0.6120456642749681,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 97.31798360385088,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 84.63044031436561,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 1.355915069887731,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 109.89202084295582,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 160.21100853053224,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 1.772733213161236,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 109.99626121011262,
Timestamp: test001Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 172.49828748957728,
Timestamp: test001Time,
},
},
},
},
}
for i, scenario := range scenarios {
scenario.test(t, i)
}
}
func TestProcessor001Process(t *testing.T) {
testProcessor001Process(t)
}
func BenchmarkProcessor001Process(b *testing.B) {
for i := 0; i < b.N; i++ {
testProcessor001Process(b)
}
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"encoding/json"
"fmt"
"io"
"github.com/prometheus/client_golang/model"
)
// Processor002 is responsible for decoding payloads from protocol version
// 0.0.2.
var Processor002 = &processor002{}
type histogram002 struct {
Labels map[string]string `json:"labels"`
Values map[string]model.SampleValue `json:"value"`
}
type counter002 struct {
Labels map[string]string `json:"labels"`
Value model.SampleValue `json:"value"`
}
type processor002 struct{}
func (p *processor002) ProcessSingle(in io.Reader, out Ingester, o *ProcessOptions) error {
// Processor for telemetry schema version 0.0.2.
// container for telemetry data
var entities []struct {
BaseLabels map[string]string `json:"baseLabels"`
Docstring string `json:"docstring"`
Metric struct {
Type string `json:"type"`
Values json.RawMessage `json:"value"`
} `json:"metric"`
}
if err := json.NewDecoder(in).Decode(&entities); err != nil {
return err
}
pendingSamples := model.Samples{}
for _, entity := range entities {
switch entity.Metric.Type {
case "counter", "gauge":
var values []counter002
if err := json.Unmarshal(entity.Metric.Values, &values); err != nil {
return fmt.Errorf("could not extract %s value: %s", entity.Metric.Type, err)
}
for _, counter := range values {
labels := labelSet(entity.BaseLabels).Merge(labelSet(counter.Labels))
pendingSamples = append(pendingSamples, &model.Sample{
Metric: model.Metric(labels),
Timestamp: o.Timestamp,
Value: counter.Value,
})
}
case "histogram":
var values []histogram002
if err := json.Unmarshal(entity.Metric.Values, &values); err != nil {
return fmt.Errorf("could not extract %s value: %s", entity.Metric.Type, err)
}
for _, histogram := range values {
for percentile, value := range histogram.Values {
labels := labelSet(entity.BaseLabels).Merge(labelSet(histogram.Labels))
labels[model.LabelName("percentile")] = model.LabelValue(percentile)
pendingSamples = append(pendingSamples, &model.Sample{
Metric: model.Metric(labels),
Timestamp: o.Timestamp,
Value: value,
})
}
}
default:
return fmt.Errorf("unknown metric type %q", entity.Metric.Type)
}
}
if len(pendingSamples) > 0 {
return out.Ingest(pendingSamples)
}
return nil
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"bytes"
"errors"
"io/ioutil"
"os"
"path"
"runtime"
"sort"
"testing"
"github.com/prometheus/client_golang/model"
)
var test002Time = model.Now()
type testProcessor002ProcessScenario struct {
in string
expected, actual []model.Samples
err error
}
func (s *testProcessor002ProcessScenario) Ingest(samples model.Samples) error {
s.actual = append(s.actual, samples)
return nil
}
func (s *testProcessor002ProcessScenario) test(t testing.TB, set int) {
reader, err := os.Open(path.Join("fixtures", s.in))
if err != nil {
t.Fatalf("%d. couldn't open scenario input file %s: %s", set, s.in, err)
}
options := &ProcessOptions{
Timestamp: test002Time,
}
err = Processor002.ProcessSingle(reader, s, options)
if s.err != err && (s.err == nil || err == nil || err.Error() != s.err.Error()) {
t.Fatalf("%d. expected err of %s, got %s", set, s.err, err)
}
if len(s.actual) != len(s.expected) {
t.Fatalf("%d. expected output length of %d, got %d", set, len(s.expected), len(s.actual))
}
for i, expected := range s.expected {
sort.Sort(s.actual[i])
sort.Sort(expected)
if !expected.Equal(s.actual[i]) {
t.Fatalf("%d.%d. expected %s, got %s", set, i, expected, s.actual[i])
}
}
}
func testProcessor002Process(t testing.TB) {
var scenarios = []testProcessor002ProcessScenario{
{
in: "empty.json",
err: errors.New("EOF"),
},
{
in: "test0_0_1-0_0_2.json",
expected: []model.Samples{
model.Samples{
&model.Sample{
Metric: model.Metric{"service": "zed", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"},
Value: 25,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"service": "bar", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"},
Value: 25,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"service": "foo", model.MetricNameLabel: "rpc_calls_total", "job": "batch_job"},
Value: 25,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 0.0459814091918713,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 78.48563317257356,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 15.890724674774395,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 0.0459814091918713,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 78.48563317257356,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.050000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 15.890724674774395,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 0.6120456642749681,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 97.31798360385088,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 84.63044031436561,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 1.355915069887731,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 109.89202084295582,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 160.21100853053224,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "zed"},
Value: 1.772733213161236,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "bar"},
Value: 109.99626121011262,
Timestamp: test002Time,
},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", model.MetricNameLabel: "rpc_latency_microseconds", "service": "foo"},
Value: 172.49828748957728,
Timestamp: test002Time,
},
},
},
},
}
for i, scenario := range scenarios {
scenario.test(t, i)
}
}
func TestProcessor002Process(t *testing.T) {
testProcessor002Process(t)
}
func BenchmarkProcessor002Process(b *testing.B) {
b.StopTimer()
pre := runtime.MemStats{}
runtime.ReadMemStats(&pre)
b.StartTimer()
for i := 0; i < b.N; i++ {
testProcessor002Process(b)
}
post := runtime.MemStats{}
runtime.ReadMemStats(&post)
allocated := post.TotalAlloc - pre.TotalAlloc
b.Logf("Allocated %d at %f per cycle with %d cycles.", allocated, float64(allocated)/float64(b.N), b.N)
}
func BenchmarkProcessor002ParseOnly(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("fixtures/test0_0_1-0_0_2-large.json")
if err != nil {
b.Fatal(err)
}
ing := fakeIngester{}
b.StartTimer()
for i := 0; i < b.N; i++ {
if err := Processor002.ProcessSingle(bytes.NewReader(data), ing, &ProcessOptions{}); err != nil {
b.Fatal(err)
}
}
}
type fakeIngester struct{}
func (i fakeIngester) Ingest(model.Samples) error {
return nil
}
// Copyright 2014 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"io"
"github.com/prometheus/client_golang/text"
)
type processor004 struct{}
// Processor004 s responsible for decoding payloads from the text based variety
// of protocol version 0.0.4.
var Processor004 = &processor004{}
func (t *processor004) ProcessSingle(i io.Reader, out Ingester, o *ProcessOptions) error {
var parser text.Parser
metricFamilies, err := parser.TextToMetricFamilies(i)
if err != nil {
return err
}
for _, metricFamily := range metricFamilies {
if err := extractMetricFamily(out, o, metricFamily); err != nil {
return err
}
}
return nil
}
// Copyright 2014 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package extraction
import (
"sort"
"strings"
"testing"
"github.com/prometheus/client_golang/model"
)
var (
ts = model.Now()
in = `
# Only a quite simple scenario with two metric families.
# More complicated tests of the parser itself can be found in the text package.
# TYPE mf2 counter
mf2 3
mf1{label="value1"} -3.14 123456
mf1{label="value2"} 42
mf2 4
`
out = map[model.LabelValue]model.Samples{
"mf1": model.Samples{
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "mf1", "label": "value1"},
Value: -3.14,
Timestamp: 123456,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "mf1", "label": "value2"},
Value: 42,
Timestamp: ts,
},
},
"mf2": model.Samples{
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "mf2"},
Value: 3,
Timestamp: ts,
},
&model.Sample{
Metric: model.Metric{model.MetricNameLabel: "mf2"},
Value: 4,
Timestamp: ts,
},
},
}
)
type testIngester struct {
results []model.Samples
}
func (i *testIngester) Ingest(s model.Samples) error {
i.results = append(i.results, s)
return nil
}
func TestTextProcessor(t *testing.T) {
var ingester testIngester
i := strings.NewReader(in)
o := &ProcessOptions{
Timestamp: ts,
}
err := Processor004.ProcessSingle(i, &ingester, o)
if err != nil {
t.Fatal(err)
}
if expected, got := len(out), len(ingester.results); expected != got {
t.Fatalf("Expected length %d, got %d", expected, got)
}
for _, r := range ingester.results {
expected, ok := out[r[0].Metric[model.MetricNameLabel]]
if !ok {
t.Fatalf(
"Unexpected metric name %q",
r[0].Metric[model.MetricNameLabel],
)
}
sort.Sort(expected)
sort.Sort(r)
if !expected.Equal(r) {
t.Errorf("expected %s, got %s", expected, r)
}
}
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"sort"
"strings"
)
// A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet
// may be fully-qualified down to the point where it may resolve to a single
// Metric in the data store or not. All operations that occur within the realm
// of a LabelSet can emit a vector of Metric entities to which the LabelSet may
// match.
type LabelSet map[LabelName]LabelValue
// Merge is a helper function to non-destructively merge two label sets.
func (l LabelSet) Merge(other LabelSet) LabelSet {
result := make(LabelSet, len(l))
for k, v := range l {
result[k] = v
}
for k, v := range other {
result[k] = v
}
return result
}
func (l LabelSet) String() string {
labelStrings := make([]string, 0, len(l))
for label, value := range l {
labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value))
}
switch len(labelStrings) {
case 0:
return ""
default:
sort.Strings(labelStrings)
return fmt.Sprintf("{%s}", strings.Join(labelStrings, ", "))
}
}
// MergeFromMetric merges Metric into this LabelSet.
func (l LabelSet) MergeFromMetric(m Metric) {
for k, v := range m {
l[k] = v
}
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"sort"
)
// A LabelValue is an associated value for a LabelName.
type LabelValue string
// LabelValues is a sortable LabelValue slice. It implements sort.Interface.
type LabelValues []LabelValue
func (l LabelValues) Len() int {
return len(l)
}
func (l LabelValues) Less(i, j int) bool {
return sort.StringsAreSorted([]string{string(l[i]), string(l[j])})
}
func (l LabelValues) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"sort"
"testing"
)
func testLabelValues(t testing.TB) {
var scenarios = []struct {
in LabelValues
out LabelValues
}{
{
in: LabelValues{"ZZZ", "zzz"},
out: LabelValues{"ZZZ", "zzz"},
},
{
in: LabelValues{"aaa", "AAA"},
out: LabelValues{"AAA", "aaa"},
},
}
for i, scenario := range scenarios {
sort.Sort(scenario.in)
for j, expected := range scenario.out {
if expected != scenario.in[j] {
t.Errorf("%d.%d expected %s, got %s", i, j, expected, scenario.in[j])
}
}
}
}
func TestLabelValues(t *testing.T) {
testLabelValues(t)
}
func BenchmarkLabelValues(b *testing.B) {
for i := 0; i < b.N; i++ {
testLabelValues(b)
}
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
// Sample is a sample value with a timestamp and a metric.
type Sample struct {
Metric Metric
Value SampleValue
Timestamp Timestamp
}
// Equal compares first the metrics, then the timestamp, then the value.
func (s *Sample) Equal(o *Sample) bool {
if s == o {
return true
}
if !s.Metric.Equal(o.Metric) {
return false
}
if !s.Timestamp.Equal(o.Timestamp) {
return false
}
if !s.Value.Equal(o.Value) {
return false
}
return true
}
// Samples is a sortable Sample slice. It implements sort.Interface.
type Samples []*Sample
func (s Samples) Len() int {
return len(s)
}
// Less compares first the metrics, then the timestamp.
func (s Samples) Less(i, j int) bool {
switch {
case s[i].Metric.Before(s[j].Metric):
return true
case s[j].Metric.Before(s[i].Metric):
return false
case s[i].Timestamp.Before(s[j].Timestamp):
return true
default:
return false
}
}
func (s Samples) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Equal compares two sets of samples and returns true if they are equal.
func (s Samples) Equal(o Samples) bool {
if len(s) != len(o) {
return false
}
for i, sample := range s {
if !sample.Equal(o[i]) {
return false
}
}
return true
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"sort"
"testing"
)
func TestSamplesSort(t *testing.T) {
input := Samples{
&Sample{
// Fingerprint: 81f9c9ed24563f8f.
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 1,
},
&Sample{
// Fingerprint: 81f9c9ed24563f8f.
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 2,
},
&Sample{
// Fingerprint: 1bf6c9ed24543f8f.
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 1,
},
&Sample{
// Fingerprint: 1bf6c9ed24543f8f.
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 2,
},
&Sample{
// Fingerprint: 68f4c9ed24533f8f.
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 1,
},
&Sample{
// Fingerprint: 68f4c9ed24533f8f.
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 2,
},
}
expected := Samples{
&Sample{
// Fingerprint: 1bf6c9ed24543f8f.
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 1,
},
&Sample{
// Fingerprint: 1bf6c9ed24543f8f.
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 2,
},
&Sample{
// Fingerprint: 68f4c9ed24533f8f.
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 1,
},
&Sample{
// Fingerprint: 68f4c9ed24533f8f.
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 2,
},
&Sample{
// Fingerprint: 81f9c9ed24563f8f.
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 1,
},
&Sample{
// Fingerprint: 81f9c9ed24563f8f.
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 2,
},
}
sort.Sort(input)
for i, actual := range input {
actualFp := actual.Metric.Fingerprint()
expectedFp := expected[i].Metric.Fingerprint()
if !actualFp.Equal(expectedFp) {
t.Fatalf("%d. Incorrect fingerprint. Got %s; want %s", i, actualFp.String(), expectedFp.String())
}
if actual.Timestamp != expected[i].Timestamp {
t.Fatalf("%d. Incorrect timestamp. Got %s; want %s", i, actual.Timestamp, expected[i].Timestamp)
}
}
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"strconv"
)
// A SampleValue is a representation of a value for a given sample at a given
// time.
type SampleValue float64
// Equal does a straight v==o.
func (v SampleValue) Equal(o SampleValue) bool {
return v == o
}
// MarshalJSON implements json.Marshaler.
func (v SampleValue) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, v)), nil
}
func (v SampleValue) String() string {
return strconv.FormatFloat(float64(v), 'f', -1, 64)
}
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"math"
"strconv"
native_time "time"
)
// Timestamp is the number of milliseconds since the epoch
// (1970-01-01 00:00 UTC) excluding leap seconds.
type Timestamp int64
const (
// MinimumTick is the minimum supported time resolution. This has to be
// at least native_time.Second in order for the code below to work.
MinimumTick = native_time.Millisecond
// second is the timestamp duration equivalent to one second.
second = int64(native_time.Second / MinimumTick)
// The number of nanoseconds per minimum tick.
nanosPerTick = int64(MinimumTick / native_time.Nanosecond)
// Earliest is the earliest timestamp representable. Handy for
// initializing a high watermark.
Earliest = Timestamp(math.MinInt64)
// Latest is the latest timestamp representable. Handy for initializing
// a low watermark.
Latest = Timestamp(math.MaxInt64)
)
// Equal reports whether two timestamps represent the same instant.
func (t Timestamp) Equal(o Timestamp) bool {
return t == o
}
// Before reports whether the timestamp t is before o.
func (t Timestamp) Before(o Timestamp) bool {
return t < o
}
// After reports whether the timestamp t is after o.
func (t Timestamp) After(o Timestamp) bool {
return t > o
}
// Add returns the Timestamp t + d.
func (t Timestamp) Add(d native_time.Duration) Timestamp {
return t + Timestamp(d/MinimumTick)
}
// Sub returns the Duration t - o.
func (t Timestamp) Sub(o Timestamp) native_time.Duration {
return native_time.Duration(t-o) * MinimumTick
}
// Time returns the time.Time representation of t.
func (t Timestamp) Time() native_time.Time {
return native_time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick)
}
// Unix returns t as a Unix time, the number of seconds elapsed
// since January 1, 1970 UTC.
func (t Timestamp) Unix() int64 {
return int64(t) / second
}
// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
// since January 1, 1970 UTC.
func (t Timestamp) UnixNano() int64 {
return int64(t) * nanosPerTick
}
// String returns a string representation of the timestamp.
func (t Timestamp) String() string {
return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64)
}
// MarshalJSON implements the json.Marshaler interface.
func (t Timestamp) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
}
// Now returns the current time as a Timestamp.
func Now() Timestamp {
return TimestampFromTime(native_time.Now())
}
// TimestampFromTime returns the Timestamp equivalent to the time.Time t.
func TimestampFromTime(t native_time.Time) Timestamp {
return TimestampFromUnixNano(t.UnixNano())
}
// TimestampFromUnix returns the Timestamp equivalent to the Unix timestamp t
// provided in seconds.
func TimestampFromUnix(t int64) Timestamp {
return Timestamp(t * second)
}
// TimestampFromUnixNano returns the Timestamp equivalent to the Unix timestamp
// t provided in nanoseconds.
func TimestampFromUnixNano(t int64) Timestamp {
return Timestamp(t / nanosPerTick)
}
...@@ -33,7 +33,7 @@ type Counter interface { ...@@ -33,7 +33,7 @@ type Counter interface {
// Set is used to set the Counter to an arbitrary value. It is only used // Set is used to set the Counter to an arbitrary value. It is only used
// if you have to transfer a value from an external counter into this // if you have to transfer a value from an external counter into this
// Prometheus metrics. Do not use it for regular handling of a // Prometheus metric. Do not use it for regular handling of a
// Prometheus counter (as it can be used to break the contract of // Prometheus counter (as it can be used to break the contract of
// monotonically increasing values). // monotonically increasing values).
Set(float64) Set(float64)
......
...@@ -9,18 +9,20 @@ import ( ...@@ -9,18 +9,20 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/prometheus/client_golang/model" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/golang/protobuf/proto"
) )
var ( var (
metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`) metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`)
labelNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`) labelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
) )
// reservedLabelPrefix is a prefix which is not legal in user-supplied
// label names.
const reservedLabelPrefix = "__"
// Labels represents a collection of label name -> value mappings. This type is // Labels represents a collection of label name -> value mappings. This type is
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of // commonly used with the With(Labels) and GetMetricWith(Labels) methods of
// metric vector Collectors, e.g.: // metric vector Collectors, e.g.:
...@@ -134,7 +136,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) * ...@@ -134,7 +136,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
for _, val := range labelValues { for _, val := range labelValues {
b.Reset() b.Reset()
b.WriteString(val) b.WriteString(val)
b.WriteByte(model.SeparatorByte) b.WriteByte(separatorByte)
h.Write(b.Bytes()) h.Write(b.Bytes())
} }
d.id = h.Sum64() d.id = h.Sum64()
...@@ -145,12 +147,12 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) * ...@@ -145,12 +147,12 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
h.Reset() h.Reset()
b.Reset() b.Reset()
b.WriteString(help) b.WriteString(help)
b.WriteByte(model.SeparatorByte) b.WriteByte(separatorByte)
h.Write(b.Bytes()) h.Write(b.Bytes())
for _, labelName := range labelNames { for _, labelName := range labelNames {
b.Reset() b.Reset()
b.WriteString(labelName) b.WriteString(labelName)
b.WriteByte(model.SeparatorByte) b.WriteByte(separatorByte)
h.Write(b.Bytes()) h.Write(b.Bytes())
} }
d.dimHash = h.Sum64() d.dimHash = h.Sum64()
...@@ -195,5 +197,5 @@ func (d *Desc) String() string { ...@@ -195,5 +197,5 @@ func (d *Desc) String() string {
func checkLabelName(l string) bool { func checkLabelName(l string) bool {
return labelNameRE.MatchString(l) && return labelNameRE.MatchString(l) &&
!strings.HasPrefix(l, model.ReservedLabelPrefix) !strings.HasPrefix(l, reservedLabelPrefix)
} }
...@@ -61,12 +61,13 @@ ...@@ -61,12 +61,13 @@
// It also exports some stats about the HTTP usage of the /metrics // It also exports some stats about the HTTP usage of the /metrics
// endpoint. (See the Handler function for more detail.) // endpoint. (See the Handler function for more detail.)
// //
// A more advanced metric type is the Summary. // Two more advanced metric types are the Summary and Histogram.
// //
// In addition to the fundamental metric types Gauge, Counter, and Summary, a // In addition to the fundamental metric types Gauge, Counter, Summary, and
// very important part of the Prometheus data model is the partitioning of // Histogram, a very important part of the Prometheus data model is the
// samples along dimensions called labels, which results in metric vectors. The // partitioning of samples along dimensions called labels, which results in
// fundamental types are GaugeVec, CounterVec, and SummaryVec. // metric vectors. The fundamental types are GaugeVec, CounterVec, SummaryVec,
// and HistogramVec.
// //
// Those are all the parts needed for basic usage. Detailed documentation and // Those are all the parts needed for basic usage. Detailed documentation and
// examples are provided below. // examples are provided below.
......
...@@ -392,6 +392,9 @@ func ExampleSummaryVec() { ...@@ -392,6 +392,9 @@ func ExampleSummaryVec() {
temps.WithLabelValues("lithobates-catesbeianus").Observe(32 + math.Floor(100*math.Cos(float64(i)*0.11))/10) temps.WithLabelValues("lithobates-catesbeianus").Observe(32 + math.Floor(100*math.Cos(float64(i)*0.11))/10)
} }
// Create a Summary without any observations.
temps.WithLabelValues("leiopelma-hochstetteri")
// Just for demonstration, let's check the state of the summary vector // Just for demonstration, let's check the state of the summary vector
// by (ab)using its Collect method and the Write method of its elements // by (ab)using its Collect method and the Write method of its elements
// (which is usually only used by Prometheus internally - code like the // (which is usually only used by Prometheus internally - code like the
...@@ -414,6 +417,26 @@ func ExampleSummaryVec() { ...@@ -414,6 +417,26 @@ func ExampleSummaryVec() {
// Output: // Output:
// [label: < // [label: <
// name: "species" // name: "species"
// value: "leiopelma-hochstetteri"
// >
// summary: <
// sample_count: 0
// sample_sum: 0
// quantile: <
// quantile: 0.5
// value: nan
// >
// quantile: <
// quantile: 0.9
// value: nan
// >
// quantile: <
// quantile: 0.99
// value: nan
// >
// >
// label: <
// name: "species"
// value: "lithobates-catesbeianus" // value: "lithobates-catesbeianus"
// > // >
// summary: < // summary: <
...@@ -455,6 +478,56 @@ func ExampleSummaryVec() { ...@@ -455,6 +478,56 @@ func ExampleSummaryVec() {
// ] // ]
} }
func ExampleConstSummary() {
desc := prometheus.NewDesc(
"http_request_duration_seconds",
"A summary of the HTTP request durations.",
[]string{"code", "method"},
prometheus.Labels{"owner": "example"},
)
// Create a constant summary from values we got from a 3rd party telemetry system.
s := prometheus.MustNewConstSummary(
desc,
4711, 403.34,
map[float64]float64{0.5: 42.3, 0.9: 323.3},
"200", "get",
)
// Just for demonstration, let's check the state of the summary by
// (ab)using its Write method (which is usually only used by Prometheus
// internally).
metric := &dto.Metric{}
s.Write(metric)
fmt.Println(proto.MarshalTextString(metric))
// Output:
// label: <
// name: "code"
// value: "200"
// >
// label: <
// name: "method"
// value: "get"
// >
// label: <
// name: "owner"
// value: "example"
// >
// summary: <
// sample_count: 4711
// sample_sum: 403.34
// quantile: <
// quantile: 0.5
// value: 42.3
// >
// quantile: <
// quantile: 0.9
// value: 323.3
// >
// >
}
func ExampleHistogram() { func ExampleHistogram() {
temps := prometheus.NewHistogram(prometheus.HistogramOpts{ temps := prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "pond_temperature_celsius", Name: "pond_temperature_celsius",
...@@ -501,6 +574,64 @@ func ExampleHistogram() { ...@@ -501,6 +574,64 @@ func ExampleHistogram() {
// > // >
} }
func ExampleConstHistogram() {
desc := prometheus.NewDesc(
"http_request_duration_seconds",
"A histogram of the HTTP request durations.",
[]string{"code", "method"},
prometheus.Labels{"owner": "example"},
)
// Create a constant histogram from values we got from a 3rd party telemetry system.
h := prometheus.MustNewConstHistogram(
desc,
4711, 403.34,
map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
"200", "get",
)
// Just for demonstration, let's check the state of the histogram by
// (ab)using its Write method (which is usually only used by Prometheus
// internally).
metric := &dto.Metric{}
h.Write(metric)
fmt.Println(proto.MarshalTextString(metric))
// Output:
// label: <
// name: "code"
// value: "200"
// >
// label: <
// name: "method"
// value: "get"
// >
// label: <
// name: "owner"
// value: "example"
// >
// histogram: <
// sample_count: 4711
// sample_sum: 403.34
// bucket: <
// cumulative_count: 121
// upper_bound: 25
// >
// bucket: <
// cumulative_count: 2403
// upper_bound: 50
// >
// bucket: <
// cumulative_count: 3221
// upper_bound: 100
// >
// bucket: <
// cumulative_count: 4233
// upper_bound: 200
// >
// >
}
func ExamplePushCollectors() { func ExamplePushCollectors() {
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
completionTime := prometheus.NewGauge(prometheus.GaugeOpts{ completionTime := prometheus.NewGauge(prometheus.GaugeOpts{
......
...@@ -2,10 +2,13 @@ package prometheus ...@@ -2,10 +2,13 @@ package prometheus
import ( import (
"runtime" "runtime"
"runtime/debug"
"time"
) )
type goCollector struct { type goCollector struct {
goroutines Gauge goroutines Gauge
gcDesc *Desc
} }
// NewGoCollector returns a collector which exports metrics about the current // NewGoCollector returns a collector which exports metrics about the current
...@@ -13,19 +16,35 @@ type goCollector struct { ...@@ -13,19 +16,35 @@ type goCollector struct {
func NewGoCollector() *goCollector { func NewGoCollector() *goCollector {
return &goCollector{ return &goCollector{
goroutines: NewGauge(GaugeOpts{ goroutines: NewGauge(GaugeOpts{
Name: "process_goroutines", Name: "go_goroutines",
Help: "Number of goroutines that currently exist.", Help: "Number of goroutines that currently exist.",
}), }),
gcDesc: NewDesc(
"go_gc_duration_seconds",
"A summary of the GC invocation durations.",
nil, nil),
} }
} }
// Describe returns all descriptions of the collector. // Describe returns all descriptions of the collector.
func (c *goCollector) Describe(ch chan<- *Desc) { func (c *goCollector) Describe(ch chan<- *Desc) {
ch <- c.goroutines.Desc() ch <- c.goroutines.Desc()
ch <- c.gcDesc
} }
// Collect returns the current state of all metrics of the collector. // Collect returns the current state of all metrics of the collector.
func (c *goCollector) Collect(ch chan<- Metric) { func (c *goCollector) Collect(ch chan<- Metric) {
c.goroutines.Set(float64(runtime.NumGoroutine())) c.goroutines.Set(float64(runtime.NumGoroutine()))
ch <- c.goroutines ch <- c.goroutines
var stats debug.GCStats
stats.PauseQuantiles = make([]time.Duration, 5)
debug.ReadGCStats(&stats)
quantiles := make(map[float64]float64)
for idx, pq := range stats.PauseQuantiles[1:] {
quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds()
}
quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles)
} }
package prometheus package prometheus
import ( import (
"reflect" "runtime"
"testing" "testing"
"time" "time"
...@@ -35,6 +35,9 @@ func TestGoCollector(t *testing.T) { ...@@ -35,6 +35,9 @@ func TestGoCollector(t *testing.T) {
case Gauge: case Gauge:
pb := &dto.Metric{} pb := &dto.Metric{}
m.Write(pb) m.Write(pb)
if pb.GetGauge() == nil {
continue
}
if old == -1 { if old == -1 {
old = int(pb.GetGauge().GetValue()) old = int(pb.GetGauge().GetValue())
...@@ -47,9 +50,71 @@ func TestGoCollector(t *testing.T) { ...@@ -47,9 +50,71 @@ func TestGoCollector(t *testing.T) {
t.Errorf("want 1 new goroutine, got %d", diff) t.Errorf("want 1 new goroutine, got %d", diff)
} }
// GoCollector performs two sends per call.
// On line 27 we need to receive the second send
// to shut down cleanly.
<-ch
return
}
case <-time.After(1 * time.Second):
t.Fatalf("expected collect timed out")
}
}
}
func TestGCCollector(t *testing.T) {
var (
c = NewGoCollector()
ch = make(chan Metric)
waitc = make(chan struct{})
closec = make(chan struct{})
oldGC uint64
oldPause float64
)
defer close(closec)
go func() {
c.Collect(ch)
// force GC
runtime.GC()
<-waitc
c.Collect(ch)
}()
first := true
for {
select {
case metric := <-ch:
switch m := metric.(type) {
case *constSummary, *value:
pb := &dto.Metric{}
m.Write(pb)
if pb.GetSummary() == nil {
continue
}
if len(pb.GetSummary().Quantile) != 5 {
t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile))
}
for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} {
if *pb.GetSummary().Quantile[idx].Quantile != want {
t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want)
}
}
if first {
first = false
oldGC = *pb.GetSummary().SampleCount
oldPause = *pb.GetSummary().SampleSum
close(waitc)
continue
}
if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 {
t.Errorf("want 1 new garbage collection run, got %d", diff)
}
if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 {
t.Errorf("want moar pause, got %f", diff)
}
return return
default:
t.Errorf("want type Gauge, got %s", reflect.TypeOf(metric))
} }
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
t.Fatalf("expected collect timed out") t.Fatalf("expected collect timed out")
......
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/client_golang/model"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
...@@ -49,6 +48,10 @@ type Histogram interface { ...@@ -49,6 +48,10 @@ type Histogram interface {
Observe(float64) Observe(float64)
} }
// bucketLabel is used for the label that defines the upper bound of a
// bucket of a histogram ("le" -> "less or equal").
const bucketLabel = "le"
var ( var (
// DefBuckets are the default Histogram buckets. The default buckets are // DefBuckets are the default Histogram buckets. The default buckets are
// tailored to broadly measure the response time (in seconds) of a // tailored to broadly measure the response time (in seconds) of a
...@@ -57,7 +60,7 @@ var ( ...@@ -57,7 +60,7 @@ var (
DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
errBucketLabelNotAllowed = fmt.Errorf( errBucketLabelNotAllowed = fmt.Errorf(
"%q is not allowed as label name in histograms", model.BucketLabel, "%q is not allowed as label name in histograms", bucketLabel,
) )
) )
...@@ -147,7 +150,7 @@ type HistogramOpts struct { ...@@ -147,7 +150,7 @@ type HistogramOpts struct {
// element in the slice is the upper inclusive bound of a bucket. The // element in the slice is the upper inclusive bound of a bucket. The
// values must be sorted in strictly increasing order. There is no need // values must be sorted in strictly increasing order. There is no need
// to add a highest bucket with +Inf bound, it will be added // to add a highest bucket with +Inf bound, it will be added
// implicitly. The default value is DefObjectives. // implicitly. The default value is DefBuckets.
Buckets []float64 Buckets []float64
} }
...@@ -171,12 +174,12 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr ...@@ -171,12 +174,12 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
} }
for _, n := range desc.variableLabels { for _, n := range desc.variableLabels {
if n == model.BucketLabel { if n == bucketLabel {
panic(errBucketLabelNotAllowed) panic(errBucketLabelNotAllowed)
} }
} }
for _, lp := range desc.constLabelPairs { for _, lp := range desc.constLabelPairs {
if lp.GetName() == model.BucketLabel { if lp.GetName() == bucketLabel {
panic(errBucketLabelNotAllowed) panic(errBucketLabelNotAllowed)
} }
} }
...@@ -213,6 +216,13 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr ...@@ -213,6 +216,13 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
} }
type histogram struct { type histogram struct {
// sumBits contains the bits of the float64 representing the sum of all
// observations. sumBits and count have to go first in the struct to
// guarantee alignment for atomic operations.
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
sumBits uint64
count uint64
SelfCollector SelfCollector
// Note that there is no mutex required. // Note that there is no mutex required.
...@@ -222,9 +232,6 @@ type histogram struct { ...@@ -222,9 +232,6 @@ type histogram struct {
counts []uint64 counts []uint64
labelPairs []*dto.LabelPair labelPairs []*dto.LabelPair
sumBits uint64 // The bits of the float64 representing the sum of all observations.
count uint64
} }
func (h *histogram) Desc() *Desc { func (h *histogram) Desc() *Desc {
...@@ -342,3 +349,102 @@ func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram { ...@@ -342,3 +349,102 @@ func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
func (m *HistogramVec) With(labels Labels) Histogram { func (m *HistogramVec) With(labels Labels) Histogram {
return m.MetricVec.With(labels).(Histogram) return m.MetricVec.With(labels).(Histogram)
} }
type constHistogram struct {
desc *Desc
count uint64
sum float64
buckets map[float64]uint64
labelPairs []*dto.LabelPair
}
func (h *constHistogram) Desc() *Desc {
return h.desc
}
func (h *constHistogram) Write(out *dto.Metric) error {
his := &dto.Histogram{}
buckets := make([]*dto.Bucket, 0, len(h.buckets))
his.SampleCount = proto.Uint64(h.count)
his.SampleSum = proto.Float64(h.sum)
for upperBound, count := range h.buckets {
buckets = append(buckets, &dto.Bucket{
CumulativeCount: proto.Uint64(count),
UpperBound: proto.Float64(upperBound),
})
}
if len(buckets) > 0 {
sort.Sort(buckSort(buckets))
}
his.Bucket = buckets
out.Histogram = his
out.Label = h.labelPairs
return nil
}
// NewConstHistogram returns a metric representing a Prometheus histogram with
// fixed values for the count, sum, and bucket counts. As those parameters
// cannot be changed, the returned value does not implement the Histogram
// interface (but only the Metric interface). Users of this package will not
// have much use for it in regular operations. However, when implementing custom
// Collectors, it is useful as a throw-away metric that is generated on the fly
// to send it to Prometheus in the Collect method.
//
// buckets is a map of upper bounds to cumulative counts, excluding the +Inf
// bucket.
//
// NewConstHistogram returns an error if the length of labelValues is not
// consistent with the variable labels in Desc.
func NewConstHistogram(
desc *Desc,
count uint64,
sum float64,
buckets map[float64]uint64,
labelValues ...string,
) (Metric, error) {
if len(desc.variableLabels) != len(labelValues) {
return nil, errInconsistentCardinality
}
return &constHistogram{
desc: desc,
count: count,
sum: sum,
buckets: buckets,
labelPairs: makeLabelPairs(desc, labelValues),
}, nil
}
// MustNewConstHistogram is a version of NewConstHistogram that panics where
// NewConstMetric would have returned an error.
func MustNewConstHistogram(
desc *Desc,
count uint64,
sum float64,
buckets map[float64]uint64,
labelValues ...string,
) Metric {
m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
if err != nil {
panic(err)
}
return m
}
type buckSort []*dto.Bucket
func (s buckSort) Len() int {
return len(s)
}
func (s buckSort) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s buckSort) Less(i, j int) bool {
return s[i].GetUpperBound() < s[j].GetUpperBound()
}
...@@ -124,6 +124,10 @@ func BenchmarkHistogramWrite8(b *testing.B) { ...@@ -124,6 +124,10 @@ func BenchmarkHistogramWrite8(b *testing.B) {
var testBuckets = []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)} var testBuckets = []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)}
func TestHistogramConcurrency(t *testing.T) { func TestHistogramConcurrency(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode.")
}
rand.Seed(42) rand.Seed(42)
it := func(n uint32) bool { it := func(n uint32) bool {
...@@ -198,6 +202,10 @@ func TestHistogramConcurrency(t *testing.T) { ...@@ -198,6 +202,10 @@ func TestHistogramConcurrency(t *testing.T) {
} }
func TestHistogramVecConcurrency(t *testing.T) { func TestHistogramVecConcurrency(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode.")
}
rand.Seed(42) rand.Seed(42)
objectives := make([]float64, 0, len(DefObjectives)) objectives := make([]float64, 0, len(DefObjectives))
......
...@@ -14,6 +14,9 @@ ...@@ -14,6 +14,9 @@
package prometheus package prometheus
import ( import (
"bufio"
"io"
"net"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
...@@ -141,7 +144,18 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo ...@@ -141,7 +144,18 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
urlLen = len(r.URL.String()) urlLen = len(r.URL.String())
} }
go computeApproximateRequestSize(r, out, urlLen) go computeApproximateRequestSize(r, out, urlLen)
handlerFunc(delegate, r)
_, cn := w.(http.CloseNotifier)
_, fl := w.(http.Flusher)
_, hj := w.(http.Hijacker)
_, rf := w.(io.ReaderFrom)
var rw http.ResponseWriter
if cn && fl && hj && rf {
rw = &fancyResponseWriterDelegator{delegate}
} else {
rw = delegate
}
handlerFunc(rw, r)
elapsed := float64(time.Since(now)) / float64(time.Microsecond) elapsed := float64(time.Since(now)) / float64(time.Microsecond)
...@@ -178,7 +192,7 @@ type responseWriterDelegator struct { ...@@ -178,7 +192,7 @@ type responseWriterDelegator struct {
handler, method string handler, method string
status int status int
written int written int64
wroteHeader bool wroteHeader bool
} }
...@@ -193,7 +207,32 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) { ...@@ -193,7 +207,32 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) {
r.WriteHeader(http.StatusOK) r.WriteHeader(http.StatusOK)
} }
n, err := r.ResponseWriter.Write(b) n, err := r.ResponseWriter.Write(b)
r.written += n r.written += int64(n)
return n, err
}
type fancyResponseWriterDelegator struct {
*responseWriterDelegator
}
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
func (f *fancyResponseWriterDelegator) Flush() {
f.ResponseWriter.(http.Flusher).Flush()
}
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return f.ResponseWriter.(http.Hijacker).Hijack()
}
func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) {
if !f.wroteHeader {
f.WriteHeader(http.StatusOK)
}
n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r)
f.written += n
return n, err return n, err
} }
......
...@@ -19,6 +19,8 @@ import ( ...@@ -19,6 +19,8 @@ import (
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
const separatorByte byte = 255
// A Metric models a single sample value with its meta data being exported to // A Metric models a single sample value with its meta data being exported to
// Prometheus. Implementers of Metric in this package inclued Gauge, Counter, // Prometheus. Implementers of Metric in this package inclued Gauge, Counter,
// Untyped, and Summary. Users can implement their own Metric types, but that // Untyped, and Summary. Users can implement their own Metric types, but that
......
...@@ -33,13 +33,10 @@ import ( ...@@ -33,13 +33,10 @@ import (
"strings" "strings"
"sync" "sync"
"bitbucket.org/ww/goautoneg"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/expfmt"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/prometheus/client_golang/model"
"github.com/prometheus/client_golang/text"
) )
var ( var (
...@@ -170,6 +167,11 @@ func Unregister(c Collector) bool { ...@@ -170,6 +167,11 @@ func Unregister(c Collector) bool {
// checks are performed, but no further consistency checks (which would require // checks are performed, but no further consistency checks (which would require
// knowledge of a metric descriptor). // knowledge of a metric descriptor).
// //
// Sorting concerns: The caller is responsible for sorting the label pairs in
// each metric. However, the order of metrics will be sorted by the registry as
// it is required anyway after merging with the metric families collected
// conventionally.
//
// The function must be callable at any time and concurrently. // The function must be callable at any time and concurrently.
func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) { func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
defRegistry.metricFamilyInjectionHook = hook defRegistry.metricFamilyInjectionHook = hook
...@@ -341,7 +343,7 @@ func (r *registry) Push(job, instance, pushURL, method string) error { ...@@ -341,7 +343,7 @@ func (r *registry) Push(job, instance, pushURL, method string) error {
} }
buf := r.getBuf() buf := r.getBuf()
defer r.giveBuf(buf) defer r.giveBuf(buf)
if _, err := r.writePB(buf, text.WriteProtoDelimited); err != nil { if err := r.writePB(expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)); err != nil {
if r.panicOnCollectError { if r.panicOnCollectError {
panic(err) panic(err)
} }
...@@ -364,11 +366,11 @@ func (r *registry) Push(job, instance, pushURL, method string) error { ...@@ -364,11 +366,11 @@ func (r *registry) Push(job, instance, pushURL, method string) error {
} }
func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) {
enc, contentType := chooseEncoder(req) contentType := expfmt.Negotiate(req.Header)
buf := r.getBuf() buf := r.getBuf()
defer r.giveBuf(buf) defer r.giveBuf(buf)
writer, encoding := decorateWriter(req, buf) writer, encoding := decorateWriter(req, buf)
if _, err := r.writePB(writer, enc); err != nil { if err := r.writePB(expfmt.NewEncoder(writer, contentType)); err != nil {
if r.panicOnCollectError { if r.panicOnCollectError {
panic(err) panic(err)
} }
...@@ -379,7 +381,7 @@ func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -379,7 +381,7 @@ func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) {
closer.Close() closer.Close()
} }
header := w.Header() header := w.Header()
header.Set(contentTypeHeader, contentType) header.Set(contentTypeHeader, string(contentType))
header.Set(contentLengthHeader, fmt.Sprint(buf.Len())) header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
if encoding != "" { if encoding != "" {
header.Set(contentEncodingHeader, encoding) header.Set(contentEncodingHeader, encoding)
...@@ -387,7 +389,7 @@ func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -387,7 +389,7 @@ func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Write(buf.Bytes()) w.Write(buf.Bytes())
} }
func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) { func (r *registry) writePB(encoder expfmt.Encoder) error {
var metricHashes map[uint64]struct{} var metricHashes map[uint64]struct{}
if r.collectChecksEnabled { if r.collectChecksEnabled {
metricHashes = make(map[uint64]struct{}) metricHashes = make(map[uint64]struct{})
...@@ -439,7 +441,7 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) { ...@@ -439,7 +441,7 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) {
// TODO: Consider different means of error reporting so // TODO: Consider different means of error reporting so
// that a single erroneous metric could be skipped // that a single erroneous metric could be skipped
// instead of blowing up the whole collection. // instead of blowing up the whole collection.
return 0, fmt.Errorf("error collecting metric %v: %s", desc, err) return fmt.Errorf("error collecting metric %v: %s", desc, err)
} }
switch { switch {
case metricFamily.Type != nil: case metricFamily.Type != nil:
...@@ -455,11 +457,11 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) { ...@@ -455,11 +457,11 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) {
case dtoMetric.Histogram != nil: case dtoMetric.Histogram != nil:
metricFamily.Type = dto.MetricType_HISTOGRAM.Enum() metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
default: default:
return 0, fmt.Errorf("empty metric collected: %s", dtoMetric) return fmt.Errorf("empty metric collected: %s", dtoMetric)
} }
if r.collectChecksEnabled { if r.collectChecksEnabled {
if err := r.checkConsistency(metricFamily, dtoMetric, desc, metricHashes); err != nil { if err := r.checkConsistency(metricFamily, dtoMetric, desc, metricHashes); err != nil {
return 0, err return err
} }
} }
metricFamily.Metric = append(metricFamily.Metric, dtoMetric) metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
...@@ -473,7 +475,7 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) { ...@@ -473,7 +475,7 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) {
if r.collectChecksEnabled { if r.collectChecksEnabled {
for _, m := range mf.Metric { for _, m := range mf.Metric {
if err := r.checkConsistency(mf, m, nil, metricHashes); err != nil { if err := r.checkConsistency(mf, m, nil, metricHashes); err != nil {
return 0, err return err
} }
} }
} }
...@@ -482,7 +484,7 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) { ...@@ -482,7 +484,7 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) {
for _, m := range mf.Metric { for _, m := range mf.Metric {
if r.collectChecksEnabled { if r.collectChecksEnabled {
if err := r.checkConsistency(existingMF, m, nil, metricHashes); err != nil { if err := r.checkConsistency(existingMF, m, nil, metricHashes); err != nil {
return 0, err return err
} }
} }
existingMF.Metric = append(existingMF.Metric, m) existingMF.Metric = append(existingMF.Metric, m)
...@@ -503,15 +505,12 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) { ...@@ -503,15 +505,12 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) {
} }
sort.Strings(names) sort.Strings(names)
var written int
for _, name := range names { for _, name := range names {
w, err := writeEncoded(w, metricFamiliesByName[name]) if err := encoder.Encode(metricFamiliesByName[name]); err != nil {
written += w return err
if err != nil {
return written, err
} }
} }
return written, nil return nil
} }
func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *dto.Metric, desc *Desc, metricHashes map[uint64]struct{}) error { func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *dto.Metric, desc *Desc, metricHashes map[uint64]struct{}) error {
...@@ -520,10 +519,11 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d ...@@ -520,10 +519,11 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil || if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil || metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
metricFamily.GetType() == dto.MetricType_SUMMARY && dtoMetric.Summary == nil || metricFamily.GetType() == dto.MetricType_SUMMARY && dtoMetric.Summary == nil ||
metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil { metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
return fmt.Errorf( return fmt.Errorf(
"collected metric %q is not a %s", "collected metric %s %s is not a %s",
dtoMetric, metricFamily.Type, metricFamily.GetName(), dtoMetric, metricFamily.GetType(),
) )
} }
...@@ -531,19 +531,24 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d ...@@ -531,19 +531,24 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
h := fnv.New64a() h := fnv.New64a()
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(metricFamily.GetName()) buf.WriteString(metricFamily.GetName())
buf.WriteByte(model.SeparatorByte) buf.WriteByte(separatorByte)
h.Write(buf.Bytes()) h.Write(buf.Bytes())
// Make sure label pairs are sorted. We depend on it for the consistency
// check. Label pairs must be sorted by contract. But the point of this
// method is to check for contract violations. So we better do the sort
// now.
sort.Sort(LabelPairSorter(dtoMetric.Label))
for _, lp := range dtoMetric.Label { for _, lp := range dtoMetric.Label {
buf.Reset() buf.Reset()
buf.WriteString(lp.GetValue()) buf.WriteString(lp.GetValue())
buf.WriteByte(model.SeparatorByte) buf.WriteByte(separatorByte)
h.Write(buf.Bytes()) h.Write(buf.Bytes())
} }
metricHash := h.Sum64() metricHash := h.Sum64()
if _, exists := metricHashes[metricHash]; exists { if _, exists := metricHashes[metricHash]; exists {
return fmt.Errorf( return fmt.Errorf(
"collected metric %q was collected before with the same name and label values", "collected metric %s %s was collected before with the same name and label values",
dtoMetric, metricFamily.GetName(), dtoMetric,
) )
} }
metricHashes[metricHash] = struct{}{} metricHashes[metricHash] = struct{}{}
...@@ -555,14 +560,14 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d ...@@ -555,14 +560,14 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
// Desc consistency with metric family. // Desc consistency with metric family.
if metricFamily.GetName() != desc.fqName { if metricFamily.GetName() != desc.fqName {
return fmt.Errorf( return fmt.Errorf(
"collected metric %q has name %q but should have %q", "collected metric %s %s has name %q but should have %q",
dtoMetric, metricFamily.GetName(), desc.fqName, metricFamily.GetName(), dtoMetric, metricFamily.GetName(), desc.fqName,
) )
} }
if metricFamily.GetHelp() != desc.help { if metricFamily.GetHelp() != desc.help {
return fmt.Errorf( return fmt.Errorf(
"collected metric %q has help %q but should have %q", "collected metric %s %s has help %q but should have %q",
dtoMetric, metricFamily.GetHelp(), desc.help, metricFamily.GetName(), dtoMetric, metricFamily.GetHelp(), desc.help,
) )
} }
...@@ -576,8 +581,8 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d ...@@ -576,8 +581,8 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
} }
if len(lpsFromDesc) != len(dtoMetric.Label) { if len(lpsFromDesc) != len(dtoMetric.Label) {
return fmt.Errorf( return fmt.Errorf(
"labels in collected metric %q are inconsistent with descriptor %s", "labels in collected metric %s %s are inconsistent with descriptor %s",
dtoMetric, desc, metricFamily.GetName(), dtoMetric, desc,
) )
} }
sort.Sort(LabelPairSorter(lpsFromDesc)) sort.Sort(LabelPairSorter(lpsFromDesc))
...@@ -586,8 +591,8 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d ...@@ -586,8 +591,8 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
if lpFromDesc.GetName() != lpFromMetric.GetName() || if lpFromDesc.GetName() != lpFromMetric.GetName() ||
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() { lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
return fmt.Errorf( return fmt.Errorf(
"labels in collected metric %q are inconsistent with descriptor %s", "labels in collected metric %s %s are inconsistent with descriptor %s",
dtoMetric, desc, metricFamily.GetName(), dtoMetric, desc,
) )
} }
} }
...@@ -597,7 +602,10 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d ...@@ -597,7 +602,10 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
// Is the desc registered? // Is the desc registered?
if _, exist := r.descIDs[desc.id]; !exist { if _, exist := r.descIDs[desc.id]; !exist {
return fmt.Errorf("collected metric %q with unregistered descriptor %s", dtoMetric, desc) return fmt.Errorf(
"collected metric %s %s with unregistered descriptor %s",
metricFamily.GetName(), dtoMetric, desc,
)
} }
return nil return nil
...@@ -672,34 +680,6 @@ func newDefaultRegistry() *registry { ...@@ -672,34 +680,6 @@ func newDefaultRegistry() *registry {
return r return r
} }
func chooseEncoder(req *http.Request) (encoder, string) {
accepts := goautoneg.ParseAccept(req.Header.Get(acceptHeader))
for _, accept := range accepts {
switch {
case accept.Type == "application" &&
accept.SubType == "vnd.google.protobuf" &&
accept.Params["proto"] == "io.prometheus.client.MetricFamily":
switch accept.Params["encoding"] {
case "delimited":
return text.WriteProtoDelimited, DelimitedTelemetryContentType
case "text":
return text.WriteProtoText, ProtoTextTelemetryContentType
case "compact-text":
return text.WriteProtoCompactText, ProtoCompactTextTelemetryContentType
default:
continue
}
case accept.Type == "text" &&
accept.SubType == "plain" &&
(accept.Params["version"] == "0.0.4" || accept.Params["version"] == ""):
return text.MetricFamilyToText, TextTelemetryContentType
default:
continue
}
}
return text.MetricFamilyToText, TextTelemetryContentType
}
// decorateWriter wraps a writer to handle gzip compression if requested. It // decorateWriter wraps a writer to handle gzip compression if requested. It
// returns the decorated writer and the appropriate "Content-Encoding" header // returns the decorated writer and the appropriate "Content-Encoding" header
// (which is empty if no compression is enabled). // (which is empty if no compression is enabled).
......
...@@ -69,13 +69,13 @@ func testHandler(t testing.TB) { ...@@ -69,13 +69,13 @@ func testHandler(t testing.TB) {
{ {
Label: []*dto.LabelPair{ Label: []*dto.LabelPair{
{ {
Name: proto.String("externallabelname"),
Value: proto.String("externalval1"),
},
{
Name: proto.String("externalconstname"), Name: proto.String("externalconstname"),
Value: proto.String("externalconstvalue"), Value: proto.String("externalconstvalue"),
}, },
{
Name: proto.String("externallabelname"),
Value: proto.String("externalval1"),
},
}, },
Counter: &dto.Counter{ Counter: &dto.Counter{
Value: proto.Float64(1), Value: proto.Float64(1),
...@@ -100,27 +100,27 @@ func testHandler(t testing.TB) { ...@@ -100,27 +100,27 @@ func testHandler(t testing.TB) {
externalMetricFamilyAsBytes := externalBuf.Bytes() externalMetricFamilyAsBytes := externalBuf.Bytes()
externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring
# TYPE externalname counter # TYPE externalname counter
externalname{externallabelname="externalval1",externalconstname="externalconstvalue"} 1 externalname{externalconstname="externalconstvalue",externallabelname="externalval1"} 1
`) `)
externalMetricFamilyAsProtoText := []byte(`name: "externalname" externalMetricFamilyAsProtoText := []byte(`name: "externalname"
help: "externaldocstring" help: "externaldocstring"
type: COUNTER type: COUNTER
metric: < metric: <
label: < label: <
name: "externallabelname"
value: "externalval1"
>
label: <
name: "externalconstname" name: "externalconstname"
value: "externalconstvalue" value: "externalconstvalue"
> >
label: <
name: "externallabelname"
value: "externalval1"
>
counter: < counter: <
value: 1 value: 1
> >
> >
`) `)
externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric:<label:<name:"externallabelname" value:"externalval1" > label:<name:"externalconstname" value:"externalconstvalue" > counter:<value:1 > > externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric:<label:<name:"externalconstname" value:"externalconstvalue" > label:<name:"externallabelname" value:"externalval1" > counter:<value:1 > >
`) `)
expectedMetricFamily := &dto.MetricFamily{ expectedMetricFamily := &dto.MetricFamily{
......
...@@ -25,10 +25,12 @@ import ( ...@@ -25,10 +25,12 @@ import (
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/prometheus/client_golang/model"
) )
// quantileLabel is used for the label that defines the quantile in a
// summary.
const quantileLabel = "quantile"
// A Summary captures individual observations from an event or sample stream and // A Summary captures individual observations from an event or sample stream and
// summarizes them in a manner similar to traditional summary statistics: 1. sum // summarizes them in a manner similar to traditional summary statistics: 1. sum
// of observations, 2. observation count, 3. rank estimations. // of observations, 2. observation count, 3. rank estimations.
...@@ -57,7 +59,7 @@ var ( ...@@ -57,7 +59,7 @@ var (
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001} DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
errQuantileLabelNotAllowed = fmt.Errorf( errQuantileLabelNotAllowed = fmt.Errorf(
"%q is not allowed as label name in summaries", model.QuantileLabel, "%q is not allowed as label name in summaries", quantileLabel,
) )
) )
...@@ -112,7 +114,9 @@ type SummaryOpts struct { ...@@ -112,7 +114,9 @@ type SummaryOpts struct {
ConstLabels Labels ConstLabels Labels
// Objectives defines the quantile rank estimates with their respective // Objectives defines the quantile rank estimates with their respective
// absolute error. The default value is DefObjectives. // absolute error. If Objectives[q] = e, then the value reported
// for q will be the φ-quantile value for some φ between q-e and q+e.
// The default value is DefObjectives.
Objectives map[float64]float64 Objectives map[float64]float64
// MaxAge defines the duration for which an observation stays relevant // MaxAge defines the duration for which an observation stays relevant
...@@ -170,12 +174,12 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { ...@@ -170,12 +174,12 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
} }
for _, n := range desc.variableLabels { for _, n := range desc.variableLabels {
if n == model.QuantileLabel { if n == quantileLabel {
panic(errQuantileLabelNotAllowed) panic(errQuantileLabelNotAllowed)
} }
} }
for _, lp := range desc.constLabelPairs { for _, lp := range desc.constLabelPairs {
if lp.GetName() == model.QuantileLabel { if lp.GetName() == quantileLabel {
panic(errQuantileLabelNotAllowed) panic(errQuantileLabelNotAllowed)
} }
} }
...@@ -448,3 +452,89 @@ func (m *SummaryVec) WithLabelValues(lvs ...string) Summary { ...@@ -448,3 +452,89 @@ func (m *SummaryVec) WithLabelValues(lvs ...string) Summary {
func (m *SummaryVec) With(labels Labels) Summary { func (m *SummaryVec) With(labels Labels) Summary {
return m.MetricVec.With(labels).(Summary) return m.MetricVec.With(labels).(Summary)
} }
type constSummary struct {
desc *Desc
count uint64
sum float64
quantiles map[float64]float64
labelPairs []*dto.LabelPair
}
func (s *constSummary) Desc() *Desc {
return s.desc
}
func (s *constSummary) Write(out *dto.Metric) error {
sum := &dto.Summary{}
qs := make([]*dto.Quantile, 0, len(s.quantiles))
sum.SampleCount = proto.Uint64(s.count)
sum.SampleSum = proto.Float64(s.sum)
for rank, q := range s.quantiles {
qs = append(qs, &dto.Quantile{
Quantile: proto.Float64(rank),
Value: proto.Float64(q),
})
}
if len(qs) > 0 {
sort.Sort(quantSort(qs))
}
sum.Quantile = qs
out.Summary = sum
out.Label = s.labelPairs
return nil
}
// NewConstSummary returns a metric representing a Prometheus summary with fixed
// values for the count, sum, and quantiles. As those parameters cannot be
// changed, the returned value does not implement the Summary interface (but
// only the Metric interface). Users of this package will not have much use for
// it in regular operations. However, when implementing custom Collectors, it is
// useful as a throw-away metric that is generated on the fly to send it to
// Prometheus in the Collect method.
//
// quantiles maps ranks to quantile values. For example, a median latency of
// 0.23s and a 99th percentile latency of 0.56s would be expressed as:
// map[float64]float64{0.5: 0.23, 0.99: 0.56}
//
// NewConstSummary returns an error if the length of labelValues is not
// consistent with the variable labels in Desc.
func NewConstSummary(
desc *Desc,
count uint64,
sum float64,
quantiles map[float64]float64,
labelValues ...string,
) (Metric, error) {
if len(desc.variableLabels) != len(labelValues) {
return nil, errInconsistentCardinality
}
return &constSummary{
desc: desc,
count: count,
sum: sum,
quantiles: quantiles,
labelPairs: makeLabelPairs(desc, labelValues),
}, nil
}
// MustNewConstSummary is a version of NewConstSummary that panics where
// NewConstMetric would have returned an error.
func MustNewConstSummary(
desc *Desc,
count uint64,
sum float64,
quantiles map[float64]float64,
labelValues ...string,
) Metric {
m, err := NewConstSummary(desc, count, sum, quantiles, labelValues...)
if err != nil {
panic(err)
}
return m
}
...@@ -21,7 +21,7 @@ import "hash/fnv" ...@@ -21,7 +21,7 @@ import "hash/fnv"
// An Untyped metric works the same as a Gauge. The only difference is that to // An Untyped metric works the same as a Gauge. The only difference is that to
// no type information is implied. // no type information is implied.
// //
// To create Gauge instances, use NewUntyped. // To create Untyped instances, use NewUntyped.
type Untyped interface { type Untyped interface {
Metric Metric
Collector Collector
......
...@@ -43,11 +43,15 @@ var errInconsistentCardinality = errors.New("inconsistent label cardinality") ...@@ -43,11 +43,15 @@ var errInconsistentCardinality = errors.New("inconsistent label cardinality")
// ValueType. This is a low-level building block used by the library to back the // ValueType. This is a low-level building block used by the library to back the
// implementations of Counter, Gauge, and Untyped. // implementations of Counter, Gauge, and Untyped.
type value struct { type value struct {
// valBits containst the bits of the represented float64 value. It has
// to go first in the struct to guarantee alignment for atomic
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
valBits uint64
SelfCollector SelfCollector
desc *Desc desc *Desc
valType ValueType valType ValueType
valBits uint64 // These are the bits of the represented float64 value.
labelPairs []*dto.LabelPair labelPairs []*dto.LabelPair
} }
......
...@@ -58,6 +58,11 @@ func (m *MetricVec) Collect(ch chan<- Metric) { ...@@ -58,6 +58,11 @@ func (m *MetricVec) Collect(ch chan<- Metric) {
// GetMetricWithLabelValues returns the Metric for the given slice of label // GetMetricWithLabelValues returns the Metric for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of // values (same order as the VariableLabels in Desc). If that combination of
// label values is accessed for the first time, a new Metric is created. // label values is accessed for the first time, a new Metric is created.
//
// It is possible to call this method without using the returned Metric to only
// create the new Metric but leave it at its start value (e.g. a Summary or
// Histogram without any observations). See also the SummaryVec example.
//
// Keeping the Metric for later use is possible (and should be considered if // Keeping the Metric for later use is possible (and should be considered if
// performance is critical), but keep in mind that Reset, DeleteLabelValues and // performance is critical), but keep in mind that Reset, DeleteLabelValues and
// Delete can be used to delete the Metric from the MetricVec. In that case, the // Delete can be used to delete the Metric from the MetricVec. In that case, the
...@@ -87,8 +92,9 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) { ...@@ -87,8 +92,9 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
// GetMetricWith returns the Metric for the given Labels map (the label names // GetMetricWith returns the Metric for the given Labels map (the label names
// must match those of the VariableLabels in Desc). If that label map is // must match those of the VariableLabels in Desc). If that label map is
// accessed for the first time, a new Metric is created. Implications of keeping // accessed for the first time, a new Metric is created. Implications of
// the Metric are the same as for GetMetricWithLabelValues. // creating a Metric without using it and keeping the Metric for later use are
// the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc. // with those of the VariableLabels in Desc.
......
// Copyright 2014 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package text
import (
"fmt"
"io"
"github.com/golang/protobuf/proto"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
dto "github.com/prometheus/client_model/go"
)
// WriteProtoDelimited writes the MetricFamily to the writer in delimited
// protobuf format and returns the number of bytes written and any error
// encountered.
func WriteProtoDelimited(w io.Writer, p *dto.MetricFamily) (int, error) {
return pbutil.WriteDelimited(w, p)
}
// WriteProtoText writes the MetricFamily to the writer in text format and
// returns the number of bytes written and any error encountered.
func WriteProtoText(w io.Writer, p *dto.MetricFamily) (int, error) {
return fmt.Fprintf(w, "%s\n", proto.MarshalTextString(p))
}
// WriteProtoCompactText writes the MetricFamily to the writer in compact text
// format and returns the number of bytes written and any error encountered.
func WriteProtoCompactText(w io.Writer, p *dto.MetricFamily) (int, error) {
return fmt.Fprintf(w, "%s\n", p)
}
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package text package expfmt
import ( import (
"bytes" "bytes"
...@@ -19,11 +19,14 @@ import ( ...@@ -19,11 +19,14 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"testing" "testing"
dto "github.com/prometheus/client_model/go"
"github.com/matttproud/golang_protobuf_extensions/pbutil" "github.com/matttproud/golang_protobuf_extensions/pbutil"
dto "github.com/prometheus/client_model/go"
) )
var parser TextParser
// Benchmarks to show how much penalty text format parsing actually inflicts. // Benchmarks to show how much penalty text format parsing actually inflicts.
// //
// Example results on Linux 3.13.0, Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz, go1.4. // Example results on Linux 3.13.0, Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz, go1.4.
......
// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expfmt
import (
"errors"
"io"
"net/http"
"reflect"
"sort"
"strings"
"testing"
"github.com/prometheus/common/model"
)
func TestTextDecoder(t *testing.T) {
var (
ts = model.Now()
in = `
# Only a quite simple scenario with two metric families.
# More complicated tests of the parser itself can be found in the text package.
# TYPE mf2 counter
mf2 3
mf1{label="value1"} -3.14 123456
mf1{label="value2"} 42
mf2 4
`
out = model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "mf1",
"label": "value1",
},
Value: -3.14,
Timestamp: 123456,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "mf1",
"label": "value2",
},
Value: 42,
Timestamp: ts,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "mf2",
},
Value: 3,
Timestamp: ts,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "mf2",
},
Value: 4,
Timestamp: ts,
},
}
)
dec := &SampleDecoder{
Dec: &textDecoder{r: strings.NewReader(in)},
Opts: &DecodeOptions{
Timestamp: ts,
},
}
var all model.Vector
for {
var smpls model.Vector
err := dec.Decode(&smpls)
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
all = append(all, smpls...)
}
sort.Sort(all)
sort.Sort(out)
if !reflect.DeepEqual(all, out) {
t.Fatalf("output does not match")
}
}
func TestProtoDecoder(t *testing.T) {
var testTime = model.Now()
scenarios := []struct {
in string
expected model.Vector
}{
{
in: "",
},
{
in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_label_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@",
expected: model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"some_label_name": "some_label_value",
},
Value: -42,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"another_label_name": "another_label_value",
},
Value: 84,
Timestamp: testTime,
},
},
},
{
in: "\xb9\x01\n\rrequest_count\x12\x12Number of requests\x18\x02\"O\n#\n\x0fsome_label_name\x12\x10some_label_value\"(\x1a\x12\t\xaeG\xe1z\x14\xae\xef?\x11\x00\x00\x00\x00\x00\x00E\xc0\x1a\x12\t+\x87\x16\xd9\xce\xf7\xef?\x11\x00\x00\x00\x00\x00\x00U\xc0\"A\n)\n\x12another_label_name\x12\x13another_label_value\"\x14\x1a\x12\t\x00\x00\x00\x00\x00\x00\xe0?\x11\x00\x00\x00\x00\x00\x00$@",
expected: model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count_count",
"some_label_name": "some_label_value",
},
Value: 0,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count_sum",
"some_label_name": "some_label_value",
},
Value: 0,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"some_label_name": "some_label_value",
"quantile": "0.99",
},
Value: -42,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"some_label_name": "some_label_value",
"quantile": "0.999",
},
Value: -84,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count_count",
"another_label_name": "another_label_value",
},
Value: 0,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count_sum",
"another_label_name": "another_label_value",
},
Value: 0,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"another_label_name": "another_label_value",
"quantile": "0.5",
},
Value: 10,
Timestamp: testTime,
},
},
},
{
in: "\x8d\x01\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"S:Q\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@\x1a\f\b\x85\x15\x11\x00\x00\x00\x00\x00\x00\xf0\u007f",
expected: model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "100",
},
Value: 123,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "120",
},
Value: 412,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "144",
},
Value: 592,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "172.8",
},
Value: 1524,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "+Inf",
},
Value: 2693,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_sum",
},
Value: 1756047.3,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_count",
},
Value: 2693,
Timestamp: testTime,
},
},
},
{
// The metric type is unset in this protobuf, which needs to be handled
// correctly by the decoder.
in: "\x1c\n\rrequest_count\"\v\x1a\t\t\x00\x00\x00\x00\x00\x00\xf0?",
expected: model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
},
Value: 1,
Timestamp: testTime,
},
},
},
}
for i, scenario := range scenarios {
dec := &SampleDecoder{
Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
Opts: &DecodeOptions{
Timestamp: testTime,
},
}
var all model.Vector
for {
var smpls model.Vector
err := dec.Decode(&smpls)
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
all = append(all, smpls...)
}
sort.Sort(all)
sort.Sort(scenario.expected)
if !reflect.DeepEqual(all, scenario.expected) {
t.Fatalf("%d. output does not match, want: %#v, got %#v", i, scenario.expected, all)
}
}
}
func testDiscriminatorHTTPHeader(t testing.TB) {
var scenarios = []struct {
input map[string]string
output Format
err error
}{
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
output: FmtProtoDelim,
err: nil,
},
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`},
output: "",
err: errors.New("unrecognized protocol message illegal"),
},
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`},
output: "",
err: errors.New("unsupported encoding illegal"),
},
{
input: map[string]string{"Content-Type": `text/plain; version=0.0.4`},
output: FmtText,
err: nil,
},
{
input: map[string]string{"Content-Type": `text/plain`},
output: FmtText,
err: nil,
},
{
input: map[string]string{"Content-Type": `text/plain; version=0.0.3`},
output: "",
err: errors.New("unrecognized protocol version 0.0.3"),
},
}
for i, scenario := range scenarios {
var header http.Header
if len(scenario.input) > 0 {
header = http.Header{}
}
for key, value := range scenario.input {
header.Add(key, value)
}
actual, err := ResponseFormat(header)
if scenario.err != err {
if scenario.err != nil && err != nil {
if scenario.err.Error() != err.Error() {
t.Errorf("%d. expected %s, got %s", i, scenario.err, err)
}
} else if scenario.err != nil || err != nil {
t.Errorf("%d. expected %s, got %s", i, scenario.err, err)
}
}
if !reflect.DeepEqual(scenario.output, actual) {
t.Errorf("%d. expected %s, got %s", i, scenario.output, actual)
}
}
}
func TestDiscriminatorHTTPHeader(t *testing.T) {
testDiscriminatorHTTPHeader(t)
}
func BenchmarkDiscriminatorHTTPHeader(b *testing.B) {
for i := 0; i < b.N; i++ {
testDiscriminatorHTTPHeader(b)
}
}
// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expfmt
import (
"fmt"
"io"
"net/http"
"bitbucket.org/ww/goautoneg"
"github.com/golang/protobuf/proto"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
dto "github.com/prometheus/client_model/go"
)
// Encoder types encode metric families into an underlying wire protocol.
type Encoder interface {
Encode(*dto.MetricFamily) error
}
type encoder func(*dto.MetricFamily) error
func (e encoder) Encode(v *dto.MetricFamily) error {
return e(v)
}
// Negotiate returns the Content-Type based on the given Accept header.
// If no appropriate accepted type is found, FmtText is returned.
func Negotiate(h http.Header) Format {
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
// Check for protocol buffer
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
switch ac.Params["encoding"] {
case "delimited":
return FmtProtoDelim
case "text":
return FmtProtoText
case "compact-text":
return FmtProtoCompact
}
}
// Check for text format.
ver := ac.Params["version"]
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
return FmtText
}
}
return FmtText
}
// NewEncoder returns a new encoder based on content type negotiation.
func NewEncoder(w io.Writer, format Format) Encoder {
switch format {
case FmtProtoDelim:
return encoder(func(v *dto.MetricFamily) error {
_, err := pbutil.WriteDelimited(w, v)
return err
})
case FmtProtoCompact:
return encoder(func(v *dto.MetricFamily) error {
_, err := fmt.Fprintln(w, v.String())
return err
})
case FmtProtoText:
return encoder(func(v *dto.MetricFamily) error {
_, err := fmt.Fprintln(w, proto.MarshalTextString(v))
return err
})
case FmtText:
return encoder(func(v *dto.MetricFamily) error {
_, err := MetricFamilyToText(w, v)
return err
})
}
panic("expfmt.NewEncoder: unknown format")
}
// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// A package for reading and writing Prometheus metrics.
package expfmt
type Format string
const (
TextVersion = "0.0.4"
ProtoType = `application/vnd.google.protobuf`
ProtoProtocol = `io.prometheus.client.MetricFamily`
ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";"
// The Content-Type values for the different wire protocols.
FmtText Format = `text/plain; version=` + TextVersion
FmtProtoDelim Format = ProtoFmt + ` encoding=delimited`
FmtProtoText Format = ProtoFmt + ` encoding=text`
FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
FmtJSON2 Format = `application/json; version=0.0.2`
)
const (
hdrContentType = "Content-Type"
hdrAccept = "Accept"
)
// Copyright 2013 The Prometheus Authors // Copyright 2014 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
...@@ -11,5 +11,26 @@ ...@@ -11,5 +11,26 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package extraction decodes Prometheus clients' data streams for consumers. // Build only when actually fuzzing
package extraction // +build gofuzz
package expfmt
import "bytes"
// Fuzz text metric parser with with github.com/dvyukov/go-fuzz:
//
// go-fuzz-build github.com/prometheus/client_golang/text
// go-fuzz -bin text-fuzz.zip -workdir fuzz
//
// Further input samples should go in the folder fuzz/corpus.
func Fuzz(in []byte) int {
parser := TextParser{}
_, err := parser.TextToMetricFamilies(bytes.NewReader(in))
if err != nil {
return 0
}
return 1
}
minimal_metric 1.234
another_metric -3e3 103948
# Even that:
no_labels{} 3
# HELP line for non-existing metric will be ignored.
# A normal comment.
#
# TYPE name counter
name{labelname="val1",basename="basevalue"} NaN
name {labelname="val2",basename="base\"v\\al\nue"} 0.23 1234567890
# HELP name two-line\n doc str\\ing
# HELP name2 doc str"ing 2
# TYPE name2 gauge
name2{labelname="val2" ,basename = "basevalue2" } +Inf 54321
name2{ labelname = "val1" , }-Inf
# TYPE my_summary summary
my_summary{n1="val1",quantile="0.5"} 110
decoy -1 -2
my_summary{n1="val1",quantile="0.9"} 140 1
my_summary_count{n1="val1"} 42
# Latest timestamp wins in case of a summary.
my_summary_sum{n1="val1"} 4711 2
fake_sum{n1="val1"} 2001
# TYPE another_summary summary
another_summary_count{n2="val2",n1="val1"} 20
my_summary_count{n2="val2",n1="val1"} 5 5
another_summary{n1="val1",n2="val2",quantile=".3"} -1.2
my_summary_sum{n1="val2"} 08 15
my_summary{n1="val3", quantile="0.2"} 4711
my_summary{n1="val1",n2="val2",quantile="-12.34",} NaN
# some
# funny comments
# HELP
# HELP
# HELP my_summary
# HELP my_summary
# HELP request_duration_microseconds The response latency.
# TYPE request_duration_microseconds histogram
request_duration_microseconds_bucket{le="100"} 123
request_duration_microseconds_bucket{le="120"} 412
request_duration_microseconds_bucket{le="144"} 592
request_duration_microseconds_bucket{le="172.8"} 1524
request_duration_microseconds_bucket{le="+Inf"} 2693
request_duration_microseconds_sum 1.7560473e+06
request_duration_microseconds_count 2693
// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expfmt
import (
"encoding/json"
"fmt"
"io"
"sort"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/model"
)
type json2Decoder struct {
dec *json.Decoder
fams []*dto.MetricFamily
}
func newJSON2Decoder(r io.Reader) Decoder {
return &json2Decoder{
dec: json.NewDecoder(r),
}
}
type histogram002 struct {
Labels model.LabelSet `json:"labels"`
Values map[string]float64 `json:"value"`
}
type counter002 struct {
Labels model.LabelSet `json:"labels"`
Value float64 `json:"value"`
}
func protoLabelSet(base, ext model.LabelSet) []*dto.LabelPair {
labels := base.Clone().Merge(ext)
delete(labels, model.MetricNameLabel)
names := make([]string, 0, len(labels))
for ln := range labels {
names = append(names, string(ln))
}
sort.Strings(names)
pairs := make([]*dto.LabelPair, 0, len(labels))
for _, ln := range names {
lv := labels[model.LabelName(ln)]
pairs = append(pairs, &dto.LabelPair{
Name: proto.String(ln),
Value: proto.String(string(lv)),
})
}
return pairs
}
func (d *json2Decoder) more() error {
var entities []struct {
BaseLabels model.LabelSet `json:"baseLabels"`
Docstring string `json:"docstring"`
Metric struct {
Type string `json:"type"`
Values json.RawMessage `json:"value"`
} `json:"metric"`
}
if err := d.dec.Decode(&entities); err != nil {
return err
}
for _, e := range entities {
f := &dto.MetricFamily{
Name: proto.String(string(e.BaseLabels[model.MetricNameLabel])),
Help: proto.String(e.Docstring),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{},
}
d.fams = append(d.fams, f)
switch e.Metric.Type {
case "counter", "gauge":
var values []counter002
if err := json.Unmarshal(e.Metric.Values, &values); err != nil {
return fmt.Errorf("could not extract %s value: %s", e.Metric.Type, err)
}
for _, ctr := range values {
f.Metric = append(f.Metric, &dto.Metric{
Label: protoLabelSet(e.BaseLabels, ctr.Labels),
Untyped: &dto.Untyped{
Value: proto.Float64(ctr.Value),
},
})
}
case "histogram":
var values []histogram002
if err := json.Unmarshal(e.Metric.Values, &values); err != nil {
return fmt.Errorf("could not extract %s value: %s", e.Metric.Type, err)
}
for _, hist := range values {
quants := make([]string, 0, len(values))
for q := range hist.Values {
quants = append(quants, q)
}
sort.Strings(quants)
for _, q := range quants {
value := hist.Values[q]
// The correct label is "quantile" but to not break old expressions
// this remains "percentile"
hist.Labels["percentile"] = model.LabelValue(q)
f.Metric = append(f.Metric, &dto.Metric{
Label: protoLabelSet(e.BaseLabels, hist.Labels),
Untyped: &dto.Untyped{
Value: proto.Float64(value),
},
})
}
}
default:
return fmt.Errorf("unknown metric type %q", e.Metric.Type)
}
}
return nil
}
// Decode implements the Decoder interface.
func (d *json2Decoder) Decode(v *dto.MetricFamily) error {
if len(d.fams) == 0 {
if err := d.more(); err != nil {
return err
}
}
*v = *d.fams[0]
d.fams = d.fams[1:]
return nil
}
// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expfmt
import (
"os"
"reflect"
"testing"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
)
func TestJSON2Decode(t *testing.T) {
f, err := os.Open("testdata/json2")
if err != nil {
t.Fatal(err)
}
defer f.Close()
dec := newJSON2Decoder(f)
var v1 dto.MetricFamily
if err := dec.Decode(&v1); err != nil {
t.Fatal(err)
}
exp1 := dto.MetricFamily{
Type: dto.MetricType_UNTYPED.Enum(),
Help: proto.String("RPC calls."),
Name: proto.String("rpc_calls_total"),
Metric: []*dto.Metric{
{
Label: []*dto.LabelPair{
{
Name: proto.String("job"),
Value: proto.String("batch_job"),
}, {
Name: proto.String("service"),
Value: proto.String("zed"),
},
},
Untyped: &dto.Untyped{
Value: proto.Float64(25),
},
},
{
Label: []*dto.LabelPair{
{
Name: proto.String("job"),
Value: proto.String("batch_job"),
}, {
Name: proto.String("service"),
Value: proto.String("bar"),
},
},
Untyped: &dto.Untyped{
Value: proto.Float64(24),
},
},
},
}
if !reflect.DeepEqual(v1, exp1) {
t.Fatalf("Expected %v, got %v", exp1, v1)
}
var v2 dto.MetricFamily
if err := dec.Decode(&v2); err != nil {
t.Fatal(err)
}
exp2 := dto.MetricFamily{
Type: dto.MetricType_UNTYPED.Enum(),
Help: proto.String("RPC latency."),
Name: proto.String("rpc_latency_microseconds"),
Metric: []*dto.Metric{
{
Label: []*dto.LabelPair{
{
Name: proto.String("percentile"),
Value: proto.String("0.010000"),
}, {
Name: proto.String("service"),
Value: proto.String("foo"),
},
},
Untyped: &dto.Untyped{
Value: proto.Float64(15),
},
},
{
Label: []*dto.LabelPair{
{
Name: proto.String("percentile"),
Value: proto.String("0.990000"),
}, {
Name: proto.String("service"),
Value: proto.String("foo"),
},
},
Untyped: &dto.Untyped{
Value: proto.Float64(17),
},
},
},
}
if !reflect.DeepEqual(v2, exp2) {
t.Fatalf("Expected %v, got %v", exp2, v2)
}
}
...@@ -18,13 +18,7 @@ ...@@ -18,13 +18,7 @@
"labels": { "labels": {
"service": "bar" "service": "bar"
}, },
"value": 25 "value": 24
},
{
"labels": {
"service": "foo"
},
"value": 25
} }
] ]
} }
...@@ -33,47 +27,20 @@ ...@@ -33,47 +27,20 @@
"baseLabels": { "baseLabels": {
"__name__": "rpc_latency_microseconds" "__name__": "rpc_latency_microseconds"
}, },
"docstring": "RPC latency.", "docstring": "RPC latency.",
"metric": { "metric": {
"type": "histogram", "type": "histogram",
"value": [ "value": [
{ {
"labels": { "labels": {
"service": "foo" "service": "foo"
},
"value": {
"0.010000": 15.890724674774395,
"0.050000": 15.890724674774395,
"0.500000": 84.63044031436561,
"0.900000": 160.21100853053224,
"0.990000": 172.49828748957728
}
},
{
"labels": {
"service": "zed"
},
"value": {
"0.010000": 0.0459814091918713,
"0.050000": 0.0459814091918713,
"0.500000": 0.6120456642749681,
"0.900000": 1.355915069887731,
"0.990000": 1.772733213161236
}
}, },
{ "value": {
"labels": { "0.010000": 15,
"service": "bar" "0.990000": 17
},
"value": {
"0.010000": 78.48563317257356,
"0.050000": 78.48563317257356,
"0.500000": 97.31798360385088,
"0.900000": 109.89202084295582,
"0.990000": 109.99626121011262
}
} }
] }
} ]
}
} }
] ]
1f8b 0808 efa0 c754 0003 7072 6f74 6f62
7566 00ed 594d 8c1c c515 9eb1 8d3d 5b86
6037 265e 8c4d ca03 c4bb ceee cc9a 9f58
01cc f6ca 4424 041b 8837 21c8 24ed daee
9a99 cef6 1f55 d578 c7e4 b004 0e39 8088
8448 048a 124b 4442 9110 e110 25b9 c54a
9072 01c5 9724 4a24 2472 413e 448a 8592
1b87 bcea aeda eeea 99d9 3530 49a4 68e7
b0bb 5355 fdde abf7 bef7 bdf7 7a3f 6ca0
664f 88c4 61f4 8994 72e1 7829 23c2 8f23
27f4 5d16 73ea c691 c7ad cf2d f628 fed2
e2e2 c358 9dc3 0111 3472 7dca b11f e1f2
d9d6 e496 e6a3 e86a b4a3 4722 2fa0 ccaa
b79b f737 6abb 6bea b3cf 9ac8 ff78 6fbe
bcf6 cedb f2f3 7763 ed8d fbff 766e cf1b
ff28 d69a df44 5621 7847 9bc0 2fc1 c727
7e09 ed2d c45f dd26 89df 0ea9 60be 3b46
1d67 d0f5 850e 94e9 008f b2fe f834 74d0
8d85 865d 8506 8791 a84b ffa3 de12 8475
e938 2352 f116 208c c701 e563 84d4 e368
77a1 617b bbcb 48d2 1b9f f4d3 6857 21fd
aa76 8f92 647c c2bf 85ae 2b84 37da 5c40
e6ba 6374 8de9 fc84 c590 0c3d 9aca f0de
bdfb f40b bffd 5763 fe9f 7659 8314 f0fb
9fbf 6897 35b4 dfbd 65fb d397 7f60 9735
1c43 7f7e f5cd 975e b3df 6fa0 bd06 fb70
ff1c 7596 fa82 720b 0f50 8edc cce8 263b
b0c9 339b 3cb3 c933 5afa ff2f cfc8 13f6
5b17 ed01 0d73 cc1e d090 af99 1a60 ed3b
e8ba 32cd 7047 c482 04d6 cd8b f217 8ed2
7089 321c 770c bae1 3824 1e6d 4dd6 9af7
a29d 689b 1b7b d4da 7adb dcdc 085b d135
68bb fc33 f6ac ad00 cd7d 13b9 b5ab 27ec
4b0d 34a9 b4f3 0470 45cb 2c77 b0c4 72f9
ee26 cd7d 02ec 6cd2 dc26 cd7d 6ce1 ff73
9a7b ef17 1f0e d2dc 1d3f 19a4 b9c6 f941
9a43 e7ed c7d1 0d20 d5a5 9c3b 6e92 3a6a
2053 6437 9793 5dca 81ea c006 ccfb 5cd0
101f 7ff8 6b58 f821 d04e 4223 2169 676d
8eab 3577 028d fd34 91dd dac5 f987 90a5
8577 6316 a7c2 8f80 bf0e 9f5c 23cf 6215
8b1e 11d8 4d19 0391 411f d315 9f8b d664
bdb9 d352 b458 7bc4 7e00 5dab e585 64c5
e9c0 9439 7582 acf8 611a 9618 3906 ab70
c70f 28f6 2877 999f 8898 7153 d405 fb38
daa5 45c9 f399 2c7c f2a3 c838 669f 4407
b40c 6062 df03 cb9d 9086 31e4 79ce d437
7d55 2de3 7c39 e3e9 124d 97c4 7de5 7b0b
2eda a7c5 018e 9870 a48f 7544 accf 9f92
6bb9 dfc1 4040 0156 a741 6ae4 529c 46fe
0aa6 49ec f68c 88e4 3a8e a1bd b397 8efc
71e1 41b4 5feb 78d2 6722 2581 69f1 81af
e7ab 1b1a 8cad 0b0b 0e3a 5420 d2f1 22b0
db73 8238 5e4e 13a7 43fc 2005 af28 24dd
2a6b 5611 a2fb 4e9e 9a3d 751f cecf 627d
56c3 47a3 ff21 f499 51f2 b5dc 03eb c8ad
c86b d87f a8a3 c325 81f4 4912 a404 025b
7e81 1104 bef6 f88c 94ad b770 2786 1c08
02ac 9e82 25c0 6c0c 38a5 6e2a a82c b94f
34e3 c64e 95ba 4d99 6c4f ed91 e9f6 ac91
e2af bc2c 3f3f 9bff 88f4 7079 7e90 1e2e
cfbf 5a47 5f28 5d28 885d 8827 871b 912e
75dc 1e75 9793 d88f c488 fb3d 6adc 6f2a
7b27 536c 4f63 1fd0 068e 94b7 2c64 0118
6615 3654 5dce 9801 58d5 8353 69b4 5cc9
925a ed83 3a9a 5ac7 4878 0432 50c7 f376
6993 a8b4 58d9 2199 924c f97d a92f f1ef
332c fa49 d66e dd88 3e85 b6c9 2fd6 7697
5122 a88e faaf 57ed e67e 74ad dadc 0122
38f0 8ade bd70 da6e 4eca 4e2d dbdd 9af8
d15a 0ff6 94dd bc09 ca52 be33 21a0 6e73
d9ce e9fd f3cb 7673 1ff4 6ff9 fe55 6964
3efb 561d dd33 f2ce 7ee4 01bb 455d 6789
08b7 e7e4 6fc5 fa66 6c8e 3e92 9248 00ff
f00c 78d9 49ac 1fac be48 2b9e 9330 fc32
d486 fa58 aacf 6fea 68f6 4a6f 9175 a0d6
8269 f69a c1b9 fd79 973a 5504 5623 08c2
921f 991e b8c0 6071 cbd7 aa17 182c 6eb0
d641 731b db0f 8d59 0a40 2409 717d d187
061f 10a8 bf69 a65d bb48 76d8 44f8 453b
44ad 2b55 13d0 a82b 7a39 b50c fae1 2cf1
85d4 0219 b7a4 9452 af9a 4f5d d45e 475b
17c6 10ea 399c 8449 60b2 6f35 abd4 11ac
9f29 b3e5 eaa1 77ec dfd5 d1d1 7514 010d
fa9e 9330 1ac4 c4ab 4e49 fd61 0ad5 d962
5862 b443 1953 1726 388a a3d9 acec cb82
092d 07e0 bb85 177b 3e98 2849 46fa c377
73b2 9215 3a15 1ea4 8107 c9b0 4403 e5ac
8112 121b 8c6f de41 15be 8c5d 6495 e7d6
6d59 ecf3 1e64 807f 4a8d 4096 76d9 d346
70f0 0bf6 8fea e8b3 57a4 905b ee3a ca4a
1a66 a0c4 b841 ea49 37b9 411c 51cd b3c0
d82d dad2 5fce fa30 47a6 02dc 58d8 396d
5877 e979 fbcc c6c6 e57e b70e 0d37 2edf
1d71 fdd5 73f6 afea e8ce 911a 14f9 9608
aff4 df82 230b 98a7 6148 5896 7305 c149
1a51 0f4a 0f50 023c 925d 5933 45bc 7b7f
fbdd 5bde 7fee 6d83 299e ff61 643d 73e6
5e83 29a0 254d 8e2d 2d1b 4c91 95e8 5f32
fbdb eb24 95b6 bb42 1453 05c6 ab74 a19e
18c6 16df b7cf ad43 aaa6 2a45 1677 ad0b
14cd 1910 930d 54d7 6aaf d7d1 f448 dd79
6c4b b5f8 8ea1 ac91 23e0 6315 6360 e4e6
6174 406d 5e1f 12e8 2768 44a0 7905 3e51
005c 3bbb c7fe 9359 7ea2 58f8 1d45 007c
78d5 fcc6 83f9 2adc be5c 8638 8db2 f4c9
de55 6043 0e54 a358 f634 3ac3 3c16 2709
a498 7168 ad2a 8d67 a8eb 196d b379 ad0a
c65a c38a d1b0 6b0c 09f7 6376 17dd ba81
2285 b0b6 598e 8629 50f0 1a0a ab1f 6f31
ea2c 4b03 ea14 6df2 88ee f3e6 c1ee 1acb
272b 4db5 1c80 2732 8919 681a 996d 1029
88c6 51e5 d1a9 613d c215 46a3 6137 09fa
7459 c304 0303 9967 aa68 7d22 15be 9175
55f7 5426 a5d9 6159 9739 a678 66e4 c474
061d 2c69 d24d 4005 5433 c72b 80ca f6b3
10a4 d159 e60b c821 dd1d 98a1 7ed3 fe6b
dd98 c94c 0d0a 4daf d58f 0f90 952f 6868
8268 843e fc45 c9f0 f238 76e3 3061 8017
9ecd 5dba 5da1 2b09 140d 4fd2 0e14 439c
bfee c284 67df f246 0adc 0350 ebab 02a9
9b2b 7559 9003 5887 1fd3 5518 ff65 8b11
a75c b223 398a 81e7 d5ed d6e6 f183 0b6e
3628 eb7d 2042 2ace 5279 1597 9124 7f0b
fbdd 3acc 1e0d 7dc4 da7a e44e 0e43 e2b6
1c19 ab27 860c 8933 f6e0 9038 3304 7dad
214d 706b 4813 dcb2 9b4f d781 900b 23b6
1c91 36dc a5f6 eff9 af0c aaff 06f1 48e5
4433 2000 00
\ No newline at end of file
1f8b 0808 2aa1 c754 0003 7465 7874 00b5
5b5d 939b 3816 7def 5fa1 ea79 99a9 4d3c
601b db3c f4c3 5426 55f3 309b ca6e 7ab7
6a9e 281a d436 150c 04c4 a4bd 5df3 dff7
4a88 361f 025d 094f 1e92 34e8 1cae 8ea4
ab7b 04fd 03f9 ede3 ef9f c989 b122 28e9
b79a 562c 88eb 3264 499e 05e7 242a f38a
4679 1657 e4f1 44c9 6f8f 8f9f 896c 46d2
90d1 2c4a 6845 928c 749b aeee 7e20 8f7f
7cfe 8861 adea f339 2c2f 77fa a6af a730
8b53 5a3e dcff 7cff ee5b 1d66 2c49 e9c3
bdb3 f2ee ff22 ce12 027f 3101 9621 80ee
7659 90a8 28af 3366 8eeb 2042 f887 558b
7553 d158 a8a7 a4b1 d450 7259 2a69 84ee
e28a e4e7 3365 6512 dd40 d429 2e1b 6527
b96c e5ed 10da 6a6c 4c31 0043 cbf2 7213
9915 4c96 22ab 9816 48dc d02d 10d8 8440
050d ca30 3bd2 db89 ace2 5b22 b592 6fa9
e092 74a9 ec46 3403 0216 9647 7a8b cc3c
c565 29ba 9a6b 81e0 2de1 02b1 cd28 3a60
f8b9 ca53 5a2d 2f1c 2698 2c44 9e62 b294
f84a 6729 b029 4107 7a2c c3e2 b458 5a05
8b85 ac2a 164b 491b 2a4b 394d c01d d889
86c5 6225 c724 1642 2a48 2c75 144c 9632
1a60 3ba8 8ac1 ed68 f96a 57f2 5868 a9e6
b194 b325 b354 d40c 7e05 1665 0e45 dc89
d68a bdca dd38 fbd5 7aef dd84 90cb e21e
bcc3 6ab7 59df 8690 336e 9cc3 7eb5 396c
8df5 eeb0 425c 7bff 70d8 ad3c 47fe 712d
46a0 4fe8 fa60 96c7 16bc 4afe 4783 a70b
a30a dfcd ef09 cf2d eeab cd76 07af 74d8
d7fb 26b6 1a81 524c 6a0c 6a16 a675 cd9d
a67a abac 0c07 e98f d158 ac0c 5827 3c29
c694 819d 9144 0fb1 34ba 6604 6889 4c2c
edb4 4e73 2674 4e2c 1cce cab1 9ac0 4dd4
427a d359 ad26 fca4 4629 2d6a 81f5 3427
31d6 0c6b 32f5 ca4d 5942 8c7e 7aac a587
3423 3051 0fed 1667 959b f477 1ad5 1038
2b33 6802 c7aa 6560 fb26 b59a b16a 334a
a150 c6ae 0e0b c5ea 83f4 6f93 da4c f8ae
195d b408 537b 8644 6215 c119 b149 41d4
0e6a 460f 1dc0 c267 e1c1 5851 d08e 6a52
9749 1f34 230d 0283 334c 6bdf b527 f017
1368 1866 0cd0 66bb 3d1c b07a 619c 4e15
b09c 8529 7914 7f67 f5f9 8996 247f ee39
9e8a 9cc3 982a 8d4e 0b17 4fa6 e59d e2de
6b94 c7d0 edb5 e3dc bf53 4ac3 ff93 c70f
f7b0 8728 e3ac 0ac8 9c74 c292 3537 359e
6ccc 3030 65a3 0638 5786 87f9 96b0 79dc
8c31 1bb7 9d73 6673 1169 ad99 2918 ad85
de9c e914 195b 2dbd 2e08 8cb1 3fb3 62c0
eb84 7368 5ab1 d456 0ba1 1812 6868 d22c
f046 9269 6d1a 46b0 91e3 c2c9 a587 5939
356b 1673 e1f4 5e0d 2ddf d870 1988 8800
1bdb 352b 0623 0911 860d 239f c279 e1a4
c300 0d3d 9b05 1e2d 19ca b5e9 0453 1a30
bd5c 3898 8171 33c4 a245 d25a 379d 4023
27a6 1747 0fc1 bb37 3328 5a16 9d7f d3a9
32f4 637a 51b4 0823 0b67 8c46 2b83 3071
3a71 148e 4caf 0f06 84f4 71ce d65f 4021
7c98 e31d 9650 341c bb2d 52b1 9e27 5b6f
f79d 7758 5ae1 a6fc 1c5c 8f68 05cd 8b3a
685f 7a75 5d5d 5d81 a703 1252 5d2a 46cf
e4c3 e7ff 1096 9cc1 3515 3463 dc35 0d3f
1c9d 666c 8dde 740b 1819 6f18 d931 2ff3
9a25 1938 af4f 6f16 b373 919d 4246 a2ba
2c21 9ef4 42e8 4b52 b151 309d f6c7 b03e
d23b c58d bd33 7cf4 397c 099e e38a fc33
7c49 cef5 b963 7173 e83d 7986 7124 31ad
a232 2958 5e8e 2568 f1fd 47b6 570f aebf
1e3e 91f3 8a9b 9f0c 1ff5 06ec 3feb edf2
7a34 e230 6992 1834 0bce f49c 432d d498
db7f cbab a4b9 2acc f1d8 1bcf 73f4 4350
b7f1 569b c3de f1fc 35fd 87b3 1f86 068b
bc64 019f 66ed fc20 5ff8 a566 e681 2630
91db c610 6116 5152 67c9 0ba1 451e 9de6
e6a4 82b8 1fac a281 bbda aed7 9bdd c1df
1e36 3b88 7624 e49f 49c9 ea30 edf7 efbf
cd45 9c8c 4a86 7e60 ca26 de6a eb6e f707
dfe5 2a1e 3a71 c9a5 1ec4 1974 290e d23c
ff5a 17c1 7398 a435 0c47 bbc0 41c4 eb8c
fef5 d397 f75f 7e25 4d53 d236 ed86 8a22
edac 7154 7b47 1735 225a 7d94 d8e8 da76
7b45 54f4 cf30 ad43 587c dd4f 05d2 34e9
7e63 dfde 21cf 3964 cd34 2512 0497 2051
e590 9c68 5433 aa8a 5747 df9e 3ae1 21af
ddbd c671 c596 698b f696 a017 81c5 2725
d660 5334 df70 89bb 3641 8839 45d6 1bc5
9449 f308 966c 05d8 f048 83e8 44a3 af45
9e64 0c33 837e 14bf 9871 bdfb 1349 20ff
c12c e5f3 e84a 0549 e5bd cc31 f218 45ec
d650 46c6 d0aa cebe 2a17 8761 606f a9c8
12af 5ae4 430a 0815 76ab ee6a 6783 6365
d186 6f87 a55c 504f 17be 1124 2561 9742
b9a6 e69f a148 06b3 8057 fe98 87fb a8a4
21e3 8706 9e7f 30c5 42ec 1594 27e2 6ba4
ad31 38c9 00e8 af1d 5320 2bc3 ace2 27e9
00df ba9e 29bc ceae 4fd6 8d63 92c5 5080
65c7 e029 64d1 2968 7ecd e8d2 9f0d ff92
0bb4 1259 5234 242d 6ef8 8b49 5798 7e7c
31cf 5664 5163 92f9 dcb6 8cce bf31 dd72
3e91 1117 5234 29d2 359d 3dcd 8b99 fe74
799b 28cd bc69 9afc 784d 126d 1284 95d6
34f9 c978 e234 9ca6 3345 a046 5363 bd00
ef2f c55b 1088 d136 c518 0fef b79a d690
6dc2 228c 1276 11c9 feed 0759 ddbf 8db3
686b 3086 036e cdd6 3505 7377 fc7b 53c3
0ea5 343b b2d3 a052 6d27 e4f7 3061 bc3f
b07b 3fc9 eed1 d8b8 5ff2 1166 bd92 204c
f63e 5270 f971 5085 e722 a573 9bb1 6c41
5a08 a627 4a72 ed2e 3c81 db38 dbbd bee6
4a32 a8de 9238 284a 9ae6 613c 7a73 ade8
996c 7a7d 815d d267 5a96 72ec 4292 e5d9
7b71 c8c0 5d72 454b d8ab 5640 9480 16bc
f6e2 439b 444d 0dc7 dd7b cd62 4889 316c
6c4f 3495 e38e dacc 6603 47a8 368b d7cf
0569 3445 49c0 0f1e 9af2 549e b38c aab2
ced1 84d8 b805 58df cbf1 4334 337b 0c70
1dcf 37ea cc6c 473a d1bf 03b7 16a5 75cc
073e 4af3 8cb6 0535 94e6 2bba 6a7f f89e
b013 0c32 4c8c ab06 883d a71f 9141 af79
8f11 8598 8434 f373 a2c7 f2a6 f978 4920
2e6a d978 bbd6 e753 591e 778a 88ce 6f9b
ffd2 6ec9 3cf4 6b99 c88b 0289 e323 4543
a80a 8450 fade cc3e 4ebb ffcf a147 75c0
c659 6df6 fb1b 9035 47c6 9b95 b7f1 6fc1
26e8 76eb dd6a bbdb d8f1 3515 8303 c3bb
9af5 16b3 1cb2 82d8 e3a7 88a2 8490 9971
5048 4800 b68e 98e0 d74c f509 14ac 54d3
1e75 6a88 c914 d596 12b0 7017 f710 5750
2831 fa24 d42c 7d8d ad97 f9c1 ded7 8f9e
a2dd 1c87 88a1 b39f 2980 27a0 e730 8147
6661 16f1 ad57 a63e f1a6 4521 5296 b3e4
59d6 0895 daa7 fede 5c24 df7a e6a7 a299
d88e c467 46a4 4703 1e28 e787 41ed 8e15
9779 51c0 96d5 6ba4 dc97 10d1 2872 a11e
356f 930d f123 1f6b 8ab7 2018 3b5f 04a6
c964 aaa5 d107 232c 906a 9427 d7f8 2cfb
6875 cfb6 761d 6cf8 4ac3 a30a 5b66 2aa3
e8a7 32d3 4c5b 55dc 659d d2e0 7a0c 8f3e
bc27 1ca8 39b3 c771 2b56 0f0a f82a 5a35
f945 880a eb5a f5ae fff6 bca3 c572 2bde
d189 048a 58bc 0557 91ff 3538 aac7 b135
6fc6 27f8 fa25 8c71 bf4b b854 c67f c340
4d10 2f1f a929 62f1 8bb7 8b87 eaca 0eda
9a4b 3b1e ab1e a1eb 2116 bce2 ade7 b004
114b fd0a 997d fba9 a157 d41e 1a84 2a69
b547 1d83 ccfc 61b0 4388 db22 5dd5 d9f7
3261 b01f b507 33aa d027 5847 1976 a2dd
d6f1 77da 5865 26fe 30aa 5d13 46cf fd8d
6022 70f2 915b 38de 1cc4 3c17 25cc 854a
bc4b 6d8f 9ce8 4b01 c621 e665 22b8 72d2
7c8e 48c2 4afc d41c b7c1 08c2 34ba 48a7
de1e c149 d580 07f6 2bf8 4b59 0e29 bba3
9168 66fb 69a2 0b78 7558 c214 904d df3e
2ef8 2512 5f09 b4b7 a1f6 a5ec 3be5 6a44
6558 a887 5143 a9d8 6ee6 11af edf5 877b
d71b 7ca2 245e 1bbb db1b 9179 3724 f346
19c5 9ecb bf25 9729 9948 997d 42fe 7ad0
84a1 c992 238e b55d 8f54 53c0 b90d d568
1fb4 a6ba 1dd3 e813 017b 2643 aae1 c8f3
41f3 168d 7bf3 71df feee ff2d f9e8 431a
5200 00
\ No newline at end of file
...@@ -175,9 +175,9 @@ http_response_size_bytes_count{handler="prometheus"} 119 ...@@ -175,9 +175,9 @@ http_response_size_bytes_count{handler="prometheus"} 119
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter # TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 0.55 process_cpu_seconds_total 0.55
# HELP process_goroutines Number of goroutines that currently exist. # HELP go_goroutines Number of goroutines that currently exist.
# TYPE process_goroutines gauge # TYPE go_goroutines gauge
process_goroutines 70 go_goroutines 70
# HELP process_max_fds Maximum number of open file descriptors. # HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge # TYPE process_max_fds gauge
process_max_fds 8192 process_max_fds 8192
......
...@@ -11,14 +11,7 @@ ...@@ -11,14 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package text contains helper functions to parse and create text-based package expfmt
// exchange formats. The package currently supports (only) version 0.0.4 of the
// exchange format. Should other versions be supported in the future, some
// versioning scheme has to be applied. Possibilities include separate packages
// or separate functions. The best way depends on the nature of future changes,
// which is the reason why no versioning scheme has been applied prematurely
// here.
package text
import ( import (
"bytes" "bytes"
...@@ -27,8 +20,8 @@ import ( ...@@ -27,8 +20,8 @@ import (
"math" "math"
"strings" "strings"
"github.com/prometheus/client_golang/model"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/model"
) )
// MetricFamilyToText converts a MetricFamily proto message into text format and // MetricFamilyToText converts a MetricFamily proto message into text format and
...@@ -48,9 +41,6 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { ...@@ -48,9 +41,6 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
if name == "" { if name == "" {
return written, fmt.Errorf("MetricFamily has no name: %s", in) return written, fmt.Errorf("MetricFamily has no name: %s", in)
} }
if in.Type == nil {
return written, fmt.Errorf("MetricFamily has no type: %s", in)
}
// Comments, first HELP, then TYPE. // Comments, first HELP, then TYPE.
if in.Help != nil { if in.Help != nil {
...@@ -79,7 +69,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { ...@@ -79,7 +69,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
case dto.MetricType_COUNTER: case dto.MetricType_COUNTER:
if metric.Counter == nil { if metric.Counter == nil {
return written, fmt.Errorf( return written, fmt.Errorf(
"expected counter in metric %s", metric, "expected counter in metric %s %s", name, metric,
) )
} }
n, err = writeSample( n, err = writeSample(
...@@ -90,7 +80,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { ...@@ -90,7 +80,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
case dto.MetricType_GAUGE: case dto.MetricType_GAUGE:
if metric.Gauge == nil { if metric.Gauge == nil {
return written, fmt.Errorf( return written, fmt.Errorf(
"expected gauge in metric %s", metric, "expected gauge in metric %s %s", name, metric,
) )
} }
n, err = writeSample( n, err = writeSample(
...@@ -101,7 +91,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { ...@@ -101,7 +91,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
case dto.MetricType_UNTYPED: case dto.MetricType_UNTYPED:
if metric.Untyped == nil { if metric.Untyped == nil {
return written, fmt.Errorf( return written, fmt.Errorf(
"expected untyped in metric %s", metric, "expected untyped in metric %s %s", name, metric,
) )
} }
n, err = writeSample( n, err = writeSample(
...@@ -112,7 +102,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { ...@@ -112,7 +102,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
case dto.MetricType_SUMMARY: case dto.MetricType_SUMMARY:
if metric.Summary == nil { if metric.Summary == nil {
return written, fmt.Errorf( return written, fmt.Errorf(
"expected summary in metric %s", metric, "expected summary in metric %s %s", name, metric,
) )
} }
for _, q := range metric.Summary.Quantile { for _, q := range metric.Summary.Quantile {
...@@ -144,7 +134,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { ...@@ -144,7 +134,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
case dto.MetricType_HISTOGRAM: case dto.MetricType_HISTOGRAM:
if metric.Histogram == nil { if metric.Histogram == nil {
return written, fmt.Errorf( return written, fmt.Errorf(
"expected summary in metric %s", metric, "expected histogram in metric %s %s", name, metric,
) )
} }
infSeen := false infSeen := false
...@@ -191,7 +181,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { ...@@ -191,7 +181,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
) )
default: default:
return written, fmt.Errorf( return written, fmt.Errorf(
"unexpected type in metric %s", metric, "unexpected type in metric %s %s", name, metric,
) )
} }
written += n written += n
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package text package expfmt
import ( import (
"bytes" "bytes"
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"testing" "testing"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
...@@ -313,6 +314,24 @@ request_duration_microseconds_sum 1.7560473e+06 ...@@ -313,6 +314,24 @@ request_duration_microseconds_sum 1.7560473e+06
request_duration_microseconds_count 2693 request_duration_microseconds_count 2693
`, `,
}, },
// 6: No metric type, should result in default type Counter.
{
in: &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("doc string"),
Metric: []*dto.Metric{
&dto.Metric{
Counter: &dto.Counter{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
out: `# HELP name doc string
# TYPE name counter
name -Inf
`,
},
} }
for i, scenario := range scenarios { for i, scenario := range scenarios {
...@@ -378,22 +397,7 @@ func testCreateError(t testing.TB) { ...@@ -378,22 +397,7 @@ func testCreateError(t testing.TB) {
}, },
err: "MetricFamily has no name", err: "MetricFamily has no name",
}, },
// 2: No metric type. // 2: Wrong type.
{
in: &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("doc string"),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
err: "MetricFamily has no type",
},
// 3: Wrong type.
{ {
in: &dto.MetricFamily{ in: &dto.MetricFamily{
Name: proto.String("name"), Name: proto.String("name"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment