Vendor common/expfmt package

This commit is contained in:
Fabian Reinartz 2015-08-20 17:51:40 +02:00
parent 306e8468a0
commit f237b0e2da
14 changed files with 4225 additions and 0 deletions

4
Godeps/Godeps.json generated
View file

@ -58,6 +58,10 @@
"ImportPath": "github.com/prometheus/client_golang/text",
"Rev": "3a499bf7fc46bc58337ce612d0cbb29c550b8118"
},
{
"ImportPath": "github.com/prometheus/common/expfmt",
"Rev": "2502df85be1b9482ed669faa6b7cfe7f850eb08e"
},
{
"ImportPath": "github.com/prometheus/common/model",
"Rev": "2502df85be1b9482ed669faa6b7cfe7f850eb08e"

View file

@ -0,0 +1,171 @@
// 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 (
"bytes"
"compress/gzip"
"io"
"io/ioutil"
"testing"
"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.
//
// Example results on Linux 3.13.0, Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz, go1.4.
//
// BenchmarkParseText 1000 1188535 ns/op 205085 B/op 6135 allocs/op
// BenchmarkParseTextGzip 1000 1376567 ns/op 246224 B/op 6151 allocs/op
// BenchmarkParseProto 10000 172790 ns/op 52258 B/op 1160 allocs/op
// BenchmarkParseProtoGzip 5000 324021 ns/op 94931 B/op 1211 allocs/op
// BenchmarkParseProtoMap 10000 187946 ns/op 58714 B/op 1203 allocs/op
//
// CONCLUSION: The overhead for the map is negligible. Text format needs ~5x more allocations.
// Without compression, it needs ~7x longer, but with compression (the more relevant scenario),
// the difference becomes less relevant, only ~4x.
//
// The test data contains 248 samples.
//
// BenchmarkProcessor002ParseOnly in the extraction package is not quite
// comparable to the benchmarks here, but it gives an idea: JSON parsing is even
// slower than text parsing and needs a comparable amount of allocs.
// BenchmarkParseText benchmarks the parsing of a text-format scrape into metric
// family DTOs.
func BenchmarkParseText(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/text")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
if _, err := parser.TextToMetricFamilies(bytes.NewReader(data)); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkParseTextGzip benchmarks the parsing of a gzipped text-format scrape
// into metric family DTOs.
func BenchmarkParseTextGzip(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/text.gz")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
in, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
b.Fatal(err)
}
if _, err := parser.TextToMetricFamilies(in); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkParseProto benchmarks the parsing of a protobuf-format scrape into
// metric family DTOs. Note that this does not build a map of metric families
// (as the text version does), because it is not required for Prometheus
// ingestion either. (However, it is required for the text-format parsing, as
// the metric family might be sprinkled all over the text, while the
// protobuf-format guarantees bundling at one place.)
func BenchmarkParseProto(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/protobuf")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
family := &dto.MetricFamily{}
in := bytes.NewReader(data)
for {
family.Reset()
if _, err := pbutil.ReadDelimited(in, family); err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
}
}
}
// BenchmarkParseProtoGzip is like BenchmarkParseProto above, but parses gzipped
// protobuf format.
func BenchmarkParseProtoGzip(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/protobuf.gz")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
family := &dto.MetricFamily{}
in, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
b.Fatal(err)
}
for {
family.Reset()
if _, err := pbutil.ReadDelimited(in, family); err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
}
}
}
// BenchmarkParseProtoMap is like BenchmarkParseProto but DOES put the parsed
// metric family DTOs into a map. This is not happening during Prometheus
// ingestion. It is just here to measure the overhead of that map creation and
// separate it from the overhead of the text format parsing.
func BenchmarkParseProtoMap(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/protobuf")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
families := map[string]*dto.MetricFamily{}
in := bytes.NewReader(data)
for {
family := &dto.MetricFamily{}
if _, err := pbutil.ReadDelimited(in, family); err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
families[family.GetName()] = family
}
}
}

View file

@ -0,0 +1,388 @@
// 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"
"math"
"mime"
"net/http"
dto "github.com/prometheus/client_model/go"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
"github.com/prometheus/common/model"
)
// Decoder types decode an input stream into metric families.
type Decoder interface {
Decode(*dto.MetricFamily) error
}
type DecodeOptions struct {
// Timestamp is added to each value from the stream that has no explicit timestamp set.
Timestamp model.Time
}
// NewDecor returns a new decoder based on the HTTP header.
func NewDecoder(r io.Reader, h http.Header) (Decoder, error) {
ct := h.Get(hdrContentType)
mediatype, params, err := mime.ParseMediaType(ct)
if err != nil {
return nil, fmt.Errorf("invalid Content-Type header %q: %s", ct, err)
}
const (
protoType = ProtoType + "/" + ProtoSubType
textType = "text/plain"
)
switch mediatype {
case protoType:
if p := params["proto"]; p != ProtoProtocol {
return nil, fmt.Errorf("unrecognized protocol message %s", p)
}
if e := params["encoding"]; e != "delimited" {
return nil, fmt.Errorf("unsupported encoding %s", e)
}
return &protoDecoder{r: r}, nil
case textType:
if v, ok := params["version"]; ok && v != "0.0.4" {
return nil, fmt.Errorf("unrecognized protocol version %s", v)
}
return &textDecoder{r: r}, nil
default:
return nil, fmt.Errorf("unsupported media type %q, expected %q or %q", mediatype, protoType, textType)
}
}
// protoDecoder implements the Decoder interface for protocol buffers.
type protoDecoder struct {
r io.Reader
}
// Decode implements the Decoder interface.
func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
_, err := pbutil.ReadDelimited(d.r, v)
return err
}
// textDecoder implements the Decoder interface for the text protcol.
type textDecoder struct {
r io.Reader
p TextParser
fams []*dto.MetricFamily
}
// Decode implements the Decoder interface.
func (d *textDecoder) Decode(v *dto.MetricFamily) error {
// TODO(fabxc): Wrap this as a line reader to make streaming safer.
if len(d.fams) == 0 {
// No cached metric families, read everything and parse metrics.
fams, err := d.p.TextToMetricFamilies(d.r)
if err != nil {
return err
}
if len(fams) == 0 {
return io.EOF
}
for _, f := range fams {
d.fams = append(d.fams, f)
}
}
*v = *d.fams[len(d.fams)-1]
d.fams = d.fams[:len(d.fams)-1]
return nil
}
type SampleDecoder struct {
Dec Decoder
Opts *DecodeOptions
f dto.MetricFamily
}
func (sd *SampleDecoder) Decode(s *model.Vector) error {
if err := sd.Dec.Decode(&sd.f); err != nil {
return err
}
*s = extractSamples(&sd.f, sd.Opts)
return nil
}
// Extract samples builds a slice of samples from the provided metric families.
func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) model.Vector {
var all model.Vector
for _, f := range fams {
all = append(all, extractSamples(f, o)...)
}
return all
}
func extractSamples(f *dto.MetricFamily, o *DecodeOptions) model.Vector {
switch *f.Type {
case dto.MetricType_COUNTER:
return extractCounter(o, f)
case dto.MetricType_GAUGE:
return extractGauge(o, f)
case dto.MetricType_SUMMARY:
return extractSummary(o, f)
case dto.MetricType_UNTYPED:
return extractUntyped(o, f)
case dto.MetricType_HISTOGRAM:
return extractHistogram(o, f)
}
panic("expfmt.extractSamples: unknown metric family type")
}
func extractCounter(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
samples := make(model.Vector, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Counter == nil {
continue
}
lset := make(model.LabelSet, len(m.Label)+1)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
smpl := &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(m.Counter.GetValue()),
}
if m.TimestampMs != nil {
smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
} else {
smpl.Timestamp = o.Timestamp
}
samples = append(samples, smpl)
}
return samples
}
func extractGauge(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
samples := make(model.Vector, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Gauge == nil {
continue
}
lset := make(model.LabelSet, len(m.Label)+1)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
smpl := &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(m.Gauge.GetValue()),
}
if m.TimestampMs != nil {
smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
} else {
smpl.Timestamp = o.Timestamp
}
samples = append(samples, smpl)
}
return samples
}
func extractUntyped(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
samples := make(model.Vector, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Untyped == nil {
continue
}
lset := make(model.LabelSet, len(m.Label)+1)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
smpl := &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(m.Untyped.GetValue()),
}
if m.TimestampMs != nil {
smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
} else {
smpl.Timestamp = o.Timestamp
}
samples = append(samples, smpl)
}
return samples
}
func extractSummary(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
samples := make(model.Vector, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Summary == nil {
continue
}
timestamp := o.Timestamp
if m.TimestampMs != nil {
timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
}
for _, q := range m.Summary.Quantile {
lset := make(model.LabelSet, len(m.Label)+2)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
// BUG(matt): Update other names to "quantile".
lset[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile()))
lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
samples = append(samples, &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(q.GetValue()),
Timestamp: timestamp,
})
}
if m.Summary.SampleSum != nil {
lset := make(model.LabelSet, len(m.Label)+1)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
samples = append(samples, &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(m.Summary.GetSampleSum()),
Timestamp: timestamp,
})
}
if m.Summary.SampleCount != nil {
lset := make(model.LabelSet, len(m.Label)+1)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
samples = append(samples, &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(m.Summary.GetSampleCount()),
Timestamp: timestamp,
})
}
}
return samples
}
func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
samples := make(model.Vector, 0, len(f.Metric))
for _, m := range f.Metric {
if m.Histogram == nil {
continue
}
timestamp := o.Timestamp
if m.TimestampMs != nil {
timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
}
infSeen := false
for _, q := range m.Histogram.Bucket {
lset := make(model.LabelSet, len(m.Label)+2)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound()))
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
if math.IsInf(q.GetUpperBound(), +1) {
infSeen = true
}
samples = append(samples, &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(q.GetCumulativeCount()),
Timestamp: timestamp,
})
}
if m.Histogram.SampleSum != nil {
lset := make(model.LabelSet, len(m.Label)+1)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
samples = append(samples, &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(m.Histogram.GetSampleSum()),
Timestamp: timestamp,
})
}
if m.Histogram.SampleCount != nil {
lset := make(model.LabelSet, len(m.Label)+1)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
count := &model.Sample{
Metric: model.Metric(lset),
Value: model.SampleValue(m.Histogram.GetSampleCount()),
Timestamp: timestamp,
}
samples = append(samples, count)
if !infSeen {
// Append a infinity bucket sample.
lset := make(model.LabelSet, len(m.Label)+2)
for _, p := range m.Label {
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
}
lset[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf")
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
samples = append(samples, &model.Sample{
Metric: model.Metric(lset),
Value: count.Value,
Timestamp: timestamp,
})
}
}
}
return samples
}

View file

@ -0,0 +1,327 @@
// 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",
"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.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,
},
},
},
}
for _, 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("output does not match")
}
}
}
func testDiscriminatorHTTPHeader(t testing.TB) {
var scenarios = []struct {
input map[string]string
output Decoder
err error
}{
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
output: &protoDecoder{},
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: &textDecoder{},
err: nil,
},
{
input: map[string]string{"Content-Type": `text/plain`},
output: &textDecoder{},
err: nil,
},
{
input: map[string]string{"Content-Type": `text/plain; version=0.0.3`},
output: nil,
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 := NewDecoder(nil, 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)
}
}

View file

@ -0,0 +1,88 @@
// 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 == ProtoType && ac.SubType == ProtoSubType && 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")
}

View file

@ -0,0 +1,37 @@
// 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`
ProtoSubType = `vnd.google.protobuf`
ProtoProtocol = `io.prometheus.client.MetricFamily`
ProtoFmt = ProtoType + "/" + ProtoSubType + "; 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`
)
const (
hdrContentType = "Content-Type"
hdrAccept = "Accept"
)

View file

@ -0,0 +1,516 @@
fc08 0a22 6874 7470 5f72 6571 7565 7374
5f64 7572 6174 696f 6e5f 6d69 6372 6f73
6563 6f6e 6473 122b 5468 6520 4854 5450
2072 6571 7565 7374 206c 6174 656e 6369
6573 2069 6e20 6d69 6372 6f73 6563 6f6e
6473 2e18 0222 570a 0c0a 0768 616e 646c
6572 1201 2f22 4708 0011 0000 0000 0000
0000 1a12 0900 0000 0000 00e0 3f11 0000
0000 0000 0000 1a12 09cd cccc cccc ccec
3f11 0000 0000 0000 0000 1a12 09ae 47e1
7a14 aeef 3f11 0000 0000 0000 0000 225d
0a12 0a07 6861 6e64 6c65 7212 072f 616c
6572 7473 2247 0800 1100 0000 0000 0000
001a 1209 0000 0000 0000 e03f 1100 0000
0000 0000 001a 1209 cdcc cccc cccc ec3f
1100 0000 0000 0000 001a 1209 ae47 e17a
14ae ef3f 1100 0000 0000 0000 0022 620a
170a 0768 616e 646c 6572 120c 2f61 7069
2f6d 6574 7269 6373 2247 0800 1100 0000
0000 0000 001a 1209 0000 0000 0000 e03f
1100 0000 0000 0000 001a 1209 cdcc cccc
cccc ec3f 1100 0000 0000 0000 001a 1209
ae47 e17a 14ae ef3f 1100 0000 0000 0000
0022 600a 150a 0768 616e 646c 6572 120a
2f61 7069 2f71 7565 7279 2247 0800 1100
0000 0000 0000 001a 1209 0000 0000 0000
e03f 1100 0000 0000 0000 001a 1209 cdcc
cccc cccc ec3f 1100 0000 0000 0000 001a
1209 ae47 e17a 14ae ef3f 1100 0000 0000
0000 0022 660a 1b0a 0768 616e 646c 6572
1210 2f61 7069 2f71 7565 7279 5f72 616e
6765 2247 0800 1100 0000 0000 0000 001a
1209 0000 0000 0000 e03f 1100 0000 0000
0000 001a 1209 cdcc cccc cccc ec3f 1100
0000 0000 0000 001a 1209 ae47 e17a 14ae
ef3f 1100 0000 0000 0000 0022 620a 170a
0768 616e 646c 6572 120c 2f61 7069 2f74
6172 6765 7473 2247 0800 1100 0000 0000
0000 001a 1209 0000 0000 0000 e03f 1100
0000 0000 0000 001a 1209 cdcc cccc cccc
ec3f 1100 0000 0000 0000 001a 1209 ae47
e17a 14ae ef3f 1100 0000 0000 0000 0022
600a 150a 0768 616e 646c 6572 120a 2f63
6f6e 736f 6c65 732f 2247 0800 1100 0000
0000 0000 001a 1209 0000 0000 0000 e03f
1100 0000 0000 0000 001a 1209 cdcc cccc
cccc ec3f 1100 0000 0000 0000 001a 1209
ae47 e17a 14ae ef3f 1100 0000 0000 0000
0022 5c0a 110a 0768 616e 646c 6572 1206
2f67 7261 7068 2247 0800 1100 0000 0000
0000 001a 1209 0000 0000 0000 e03f 1100
0000 0000 0000 001a 1209 cdcc cccc cccc
ec3f 1100 0000 0000 0000 001a 1209 ae47
e17a 14ae ef3f 1100 0000 0000 0000 0022
5b0a 100a 0768 616e 646c 6572 1205 2f68
6561 7022 4708 0011 0000 0000 0000 0000
1a12 0900 0000 0000 00e0 3f11 0000 0000
0000 0000 1a12 09cd cccc cccc ccec 3f11
0000 0000 0000 0000 1a12 09ae 47e1 7a14
aeef 3f11 0000 0000 0000 0000 225e 0a13
0a07 6861 6e64 6c65 7212 082f 7374 6174
6963 2f22 4708 0011 0000 0000 0000 0000
1a12 0900 0000 0000 00e0 3f11 0000 0000
0000 0000 1a12 09cd cccc cccc ccec 3f11
0000 0000 0000 0000 1a12 09ae 47e1 7a14
aeef 3f11 0000 0000 0000 0000 2260 0a15
0a07 6861 6e64 6c65 7212 0a70 726f 6d65
7468 6575 7322 4708 3b11 5b8f c2f5 083f
f440 1a12 0900 0000 0000 00e0 3f11 e17a
14ae c7af 9340 1a12 09cd cccc cccc ccec
3f11 2fdd 2406 81f0 9640 1a12 09ae 47e1
7a14 aeef 3f11 3d0a d7a3 b095 a740 e608
0a17 6874 7470 5f72 6571 7565 7374 5f73
697a 655f 6279 7465 7312 2054 6865 2048
5454 5020 7265 7175 6573 7420 7369 7a65
7320 696e 2062 7974 6573 2e18 0222 570a
0c0a 0768 616e 646c 6572 1201 2f22 4708
0011 0000 0000 0000 0000 1a12 0900 0000
0000 00e0 3f11 0000 0000 0000 0000 1a12
09cd cccc cccc ccec 3f11 0000 0000 0000
0000 1a12 09ae 47e1 7a14 aeef 3f11 0000
0000 0000 0000 225d 0a12 0a07 6861 6e64
6c65 7212 072f 616c 6572 7473 2247 0800
1100 0000 0000 0000 001a 1209 0000 0000
0000 e03f 1100 0000 0000 0000 001a 1209
cdcc cccc cccc ec3f 1100 0000 0000 0000
001a 1209 ae47 e17a 14ae ef3f 1100 0000
0000 0000 0022 620a 170a 0768 616e 646c
6572 120c 2f61 7069 2f6d 6574 7269 6373
2247 0800 1100 0000 0000 0000 001a 1209
0000 0000 0000 e03f 1100 0000 0000 0000
001a 1209 cdcc cccc cccc ec3f 1100 0000
0000 0000 001a 1209 ae47 e17a 14ae ef3f
1100 0000 0000 0000 0022 600a 150a 0768
616e 646c 6572 120a 2f61 7069 2f71 7565
7279 2247 0800 1100 0000 0000 0000 001a
1209 0000 0000 0000 e03f 1100 0000 0000
0000 001a 1209 cdcc cccc cccc ec3f 1100
0000 0000 0000 001a 1209 ae47 e17a 14ae
ef3f 1100 0000 0000 0000 0022 660a 1b0a
0768 616e 646c 6572 1210 2f61 7069 2f71
7565 7279 5f72 616e 6765 2247 0800 1100
0000 0000 0000 001a 1209 0000 0000 0000
e03f 1100 0000 0000 0000 001a 1209 cdcc
cccc cccc ec3f 1100 0000 0000 0000 001a
1209 ae47 e17a 14ae ef3f 1100 0000 0000
0000 0022 620a 170a 0768 616e 646c 6572
120c 2f61 7069 2f74 6172 6765 7473 2247
0800 1100 0000 0000 0000 001a 1209 0000
0000 0000 e03f 1100 0000 0000 0000 001a
1209 cdcc cccc cccc ec3f 1100 0000 0000
0000 001a 1209 ae47 e17a 14ae ef3f 1100
0000 0000 0000 0022 600a 150a 0768 616e
646c 6572 120a 2f63 6f6e 736f 6c65 732f
2247 0800 1100 0000 0000 0000 001a 1209
0000 0000 0000 e03f 1100 0000 0000 0000
001a 1209 cdcc cccc cccc ec3f 1100 0000
0000 0000 001a 1209 ae47 e17a 14ae ef3f
1100 0000 0000 0000 0022 5c0a 110a 0768
616e 646c 6572 1206 2f67 7261 7068 2247
0800 1100 0000 0000 0000 001a 1209 0000
0000 0000 e03f 1100 0000 0000 0000 001a
1209 cdcc cccc cccc ec3f 1100 0000 0000
0000 001a 1209 ae47 e17a 14ae ef3f 1100
0000 0000 0000 0022 5b0a 100a 0768 616e
646c 6572 1205 2f68 6561 7022 4708 0011
0000 0000 0000 0000 1a12 0900 0000 0000
00e0 3f11 0000 0000 0000 0000 1a12 09cd
cccc cccc ccec 3f11 0000 0000 0000 0000
1a12 09ae 47e1 7a14 aeef 3f11 0000 0000
0000 0000 225e 0a13 0a07 6861 6e64 6c65
7212 082f 7374 6174 6963 2f22 4708 0011
0000 0000 0000 0000 1a12 0900 0000 0000
00e0 3f11 0000 0000 0000 0000 1a12 09cd
cccc cccc ccec 3f11 0000 0000 0000 0000
1a12 09ae 47e1 7a14 aeef 3f11 0000 0000
0000 0000 2260 0a15 0a07 6861 6e64 6c65
7212 0a70 726f 6d65 7468 6575 7322 4708
3b11 0000 0000 40c4 d040 1a12 0900 0000
0000 00e0 3f11 0000 0000 0030 7240 1a12
09cd cccc cccc ccec 3f11 0000 0000 0030
7240 1a12 09ae 47e1 7a14 aeef 3f11 0000
0000 0030 7240 7c0a 1368 7474 705f 7265
7175 6573 7473 5f74 6f74 616c 1223 546f
7461 6c20 6e75 6d62 6572 206f 6620 4854
5450 2072 6571 7565 7374 7320 6d61 6465
2e18 0022 3e0a 0b0a 0463 6f64 6512 0332
3030 0a15 0a07 6861 6e64 6c65 7212 0a70
726f 6d65 7468 6575 730a 0d0a 066d 6574
686f 6412 0367 6574 1a09 0900 0000 0000
804d 40e8 080a 1868 7474 705f 7265 7370
6f6e 7365 5f73 697a 655f 6279 7465 7312
2154 6865 2048 5454 5020 7265 7370 6f6e
7365 2073 697a 6573 2069 6e20 6279 7465
732e 1802 2257 0a0c 0a07 6861 6e64 6c65
7212 012f 2247 0800 1100 0000 0000 0000
001a 1209 0000 0000 0000 e03f 1100 0000
0000 0000 001a 1209 cdcc cccc cccc ec3f
1100 0000 0000 0000 001a 1209 ae47 e17a
14ae ef3f 1100 0000 0000 0000 0022 5d0a
120a 0768 616e 646c 6572 1207 2f61 6c65
7274 7322 4708 0011 0000 0000 0000 0000
1a12 0900 0000 0000 00e0 3f11 0000 0000
0000 0000 1a12 09cd cccc cccc ccec 3f11
0000 0000 0000 0000 1a12 09ae 47e1 7a14
aeef 3f11 0000 0000 0000 0000 2262 0a17
0a07 6861 6e64 6c65 7212 0c2f 6170 692f
6d65 7472 6963 7322 4708 0011 0000 0000
0000 0000 1a12 0900 0000 0000 00e0 3f11
0000 0000 0000 0000 1a12 09cd cccc cccc
ccec 3f11 0000 0000 0000 0000 1a12 09ae
47e1 7a14 aeef 3f11 0000 0000 0000 0000
2260 0a15 0a07 6861 6e64 6c65 7212 0a2f
6170 692f 7175 6572 7922 4708 0011 0000
0000 0000 0000 1a12 0900 0000 0000 00e0
3f11 0000 0000 0000 0000 1a12 09cd cccc
cccc ccec 3f11 0000 0000 0000 0000 1a12
09ae 47e1 7a14 aeef 3f11 0000 0000 0000
0000 2266 0a1b 0a07 6861 6e64 6c65 7212
102f 6170 692f 7175 6572 795f 7261 6e67
6522 4708 0011 0000 0000 0000 0000 1a12
0900 0000 0000 00e0 3f11 0000 0000 0000
0000 1a12 09cd cccc cccc ccec 3f11 0000
0000 0000 0000 1a12 09ae 47e1 7a14 aeef
3f11 0000 0000 0000 0000 2262 0a17 0a07
6861 6e64 6c65 7212 0c2f 6170 692f 7461
7267 6574 7322 4708 0011 0000 0000 0000
0000 1a12 0900 0000 0000 00e0 3f11 0000
0000 0000 0000 1a12 09cd cccc cccc ccec
3f11 0000 0000 0000 0000 1a12 09ae 47e1
7a14 aeef 3f11 0000 0000 0000 0000 2260
0a15 0a07 6861 6e64 6c65 7212 0a2f 636f
6e73 6f6c 6573 2f22 4708 0011 0000 0000
0000 0000 1a12 0900 0000 0000 00e0 3f11
0000 0000 0000 0000 1a12 09cd cccc cccc
ccec 3f11 0000 0000 0000 0000 1a12 09ae
47e1 7a14 aeef 3f11 0000 0000 0000 0000
225c 0a11 0a07 6861 6e64 6c65 7212 062f
6772 6170 6822 4708 0011 0000 0000 0000
0000 1a12 0900 0000 0000 00e0 3f11 0000
0000 0000 0000 1a12 09cd cccc cccc ccec
3f11 0000 0000 0000 0000 1a12 09ae 47e1
7a14 aeef 3f11 0000 0000 0000 0000 225b
0a10 0a07 6861 6e64 6c65 7212 052f 6865
6170 2247 0800 1100 0000 0000 0000 001a
1209 0000 0000 0000 e03f 1100 0000 0000
0000 001a 1209 cdcc cccc cccc ec3f 1100
0000 0000 0000 001a 1209 ae47 e17a 14ae
ef3f 1100 0000 0000 0000 0022 5e0a 130a
0768 616e 646c 6572 1208 2f73 7461 7469
632f 2247 0800 1100 0000 0000 0000 001a
1209 0000 0000 0000 e03f 1100 0000 0000
0000 001a 1209 cdcc cccc cccc ec3f 1100
0000 0000 0000 001a 1209 ae47 e17a 14ae
ef3f 1100 0000 0000 0000 0022 600a 150a
0768 616e 646c 6572 120a 7072 6f6d 6574
6865 7573 2247 083b 1100 0000 00e0 b4fc
401a 1209 0000 0000 0000 e03f 1100 0000
0000 349f 401a 1209 cdcc cccc cccc ec3f
1100 0000 0000 08a0 401a 1209 ae47 e17a
14ae ef3f 1100 0000 0000 0aa0 405c 0a19
7072 6f63 6573 735f 6370 755f 7365 636f
6e64 735f 746f 7461 6c12 3054 6f74 616c
2075 7365 7220 616e 6420 7379 7374 656d
2043 5055 2074 696d 6520 7370 656e 7420
696e 2073 6563 6f6e 6473 2e18 0022 0b1a
0909 a470 3d0a d7a3 d03f 4f0a 1270 726f
6365 7373 5f67 6f72 6f75 7469 6e65 7312
2a4e 756d 6265 7220 6f66 2067 6f72 6f75
7469 6e65 7320 7468 6174 2063 7572 7265
6e74 6c79 2065 7869 7374 2e18 0122 0b12
0909 0000 0000 0000 5140 4a0a 0f70 726f
6365 7373 5f6d 6178 5f66 6473 1228 4d61
7869 6d75 6d20 6e75 6d62 6572 206f 6620
6f70 656e 2066 696c 6520 6465 7363 7269
7074 6f72 732e 1801 220b 1209 0900 0000
0000 00c0 4043 0a10 7072 6f63 6573 735f
6f70 656e 5f66 6473 1220 4e75 6d62 6572
206f 6620 6f70 656e 2066 696c 6520 6465
7363 7269 7074 6f72 732e 1801 220b 1209
0900 0000 0000 003d 404e 0a1d 7072 6f63
6573 735f 7265 7369 6465 6e74 5f6d 656d
6f72 795f 6279 7465 7312 1e52 6573 6964
656e 7420 6d65 6d6f 7279 2073 697a 6520
696e 2062 7974 6573 2e18 0122 0b12 0909
0000 0000 004b 8841 630a 1a70 726f 6365
7373 5f73 7461 7274 5f74 696d 655f 7365
636f 6e64 7312 3653 7461 7274 2074 696d
6520 6f66 2074 6865 2070 726f 6365 7373
2073 696e 6365 2075 6e69 7820 6570 6f63
6820 696e 2073 6563 6f6e 6473 2e18 0122
0b12 0909 3d0a 172d e831 d541 4c0a 1c70
726f 6365 7373 5f76 6972 7475 616c 5f6d
656d 6f72 795f 6279 7465 7312 1d56 6972
7475 616c 206d 656d 6f72 7920 7369 7a65
2069 6e20 6279 7465 732e 1801 220b 1209
0900 0000 0020 12c0 415f 0a27 7072 6f6d
6574 6865 7573 5f64 6e73 5f73 645f 6c6f
6f6b 7570 5f66 6169 6c75 7265 735f 746f
7461 6c12 2554 6865 206e 756d 6265 7220
6f66 2044 4e53 2d53 4420 6c6f 6f6b 7570
2066 6169 6c75 7265 732e 1800 220b 1a09
0900 0000 0000 0000 004f 0a1f 7072 6f6d
6574 6865 7573 5f64 6e73 5f73 645f 6c6f
6f6b 7570 735f 746f 7461 6c12 1d54 6865
206e 756d 6265 7220 6f66 2044 4e53 2d53
4420 6c6f 6f6b 7570 732e 1800 220b 1a09
0900 0000 0000 0008 40cf 010a 2a70 726f
6d65 7468 6575 735f 6576 616c 7561 746f
725f 6475 7261 7469 6f6e 5f6d 696c 6c69
7365 636f 6e64 7312 2c54 6865 2064 7572
6174 696f 6e20 666f 7220 616c 6c20 6576
616c 7561 7469 6f6e 7320 746f 2065 7865
6375 7465 2e18 0222 7122 6f08 0b11 0000
0000 0000 2240 1a12 097b 14ae 47e1 7a84
3f11 0000 0000 0000 0000 1a12 099a 9999
9999 99a9 3f11 0000 0000 0000 0000 1a12
0900 0000 0000 00e0 3f11 0000 0000 0000
0000 1a12 09cd cccc cccc ccec 3f11 0000
0000 0000 f03f 1a12 09ae 47e1 7a14 aeef
3f11 0000 0000 0000 f03f a301 0a39 7072
6f6d 6574 6865 7573 5f6c 6f63 616c 5f73
746f 7261 6765 5f63 6865 636b 706f 696e
745f 6475 7261 7469 6f6e 5f6d 696c 6c69
7365 636f 6e64 7312 5754 6865 2064 7572
6174 696f 6e20 2869 6e20 6d69 6c6c 6973
6563 6f6e 6473 2920 6974 2074 6f6f 6b20
746f 2063 6865 636b 706f 696e 7420 696e
2d6d 656d 6f72 7920 6d65 7472 6963 7320
616e 6420 6865 6164 2063 6875 6e6b 732e
1801 220b 1209 0900 0000 0000 0000 00f2
010a 2870 726f 6d65 7468 6575 735f 6c6f
6361 6c5f 7374 6f72 6167 655f 6368 756e
6b5f 6f70 735f 746f 7461 6c12 3354 6865
2074 6f74 616c 206e 756d 6265 7220 6f66
2063 6875 6e6b 206f 7065 7261 7469 6f6e
7320 6279 2074 6865 6972 2074 7970 652e
1800 221b 0a0e 0a04 7479 7065 1206 6372
6561 7465 1a09 0900 0000 0000 b880 4022
1c0a 0f0a 0474 7970 6512 0770 6572 7369
7374 1a09 0900 0000 0000 c05b 4022 180a
0b0a 0474 7970 6512 0370 696e 1a09 0900
0000 0000 807b 4022 1e0a 110a 0474 7970
6512 0974 7261 6e73 636f 6465 1a09 0900
0000 0000 a06b 4022 1a0a 0d0a 0474 7970
6512 0575 6e70 696e 1a09 0900 0000 0000
807b 40c4 010a 3c70 726f 6d65 7468 6575
735f 6c6f 6361 6c5f 7374 6f72 6167 655f
696e 6465 7869 6e67 5f62 6174 6368 5f6c
6174 656e 6379 5f6d 696c 6c69 7365 636f
6e64 7312 3751 7561 6e74 696c 6573 2066
6f72 2062 6174 6368 2069 6e64 6578 696e
6720 6c61 7465 6e63 6965 7320 696e 206d
696c 6c69 7365 636f 6e64 732e 1802 2249
2247 0801 1100 0000 0000 0000 001a 1209
0000 0000 0000 e03f 1100 0000 0000 0000
001a 1209 cdcc cccc cccc ec3f 1100 0000
0000 0000 001a 1209 ae47 e17a 14ae ef3f
1100 0000 0000 0000 00bf 010a 2d70 726f
6d65 7468 6575 735f 6c6f 6361 6c5f 7374
6f72 6167 655f 696e 6465 7869 6e67 5f62
6174 6368 5f73 697a 6573 1241 5175 616e
7469 6c65 7320 666f 7220 696e 6465 7869
6e67 2062 6174 6368 2073 697a 6573 2028
6e75 6d62 6572 206f 6620 6d65 7472 6963
7320 7065 7220 6261 7463 6829 2e18 0222
4922 4708 0111 0000 0000 0000 0040 1a12
0900 0000 0000 00e0 3f11 0000 0000 0000
0040 1a12 09cd cccc cccc ccec 3f11 0000
0000 0000 0040 1a12 09ae 47e1 7a14 aeef
3f11 0000 0000 0000 0040 660a 3070 726f
6d65 7468 6575 735f 6c6f 6361 6c5f 7374
6f72 6167 655f 696e 6465 7869 6e67 5f71
7565 7565 5f63 6170 6163 6974 7912 2354
6865 2063 6170 6163 6974 7920 6f66 2074
6865 2069 6e64 6578 696e 6720 7175 6575
652e 1801 220b 1209 0900 0000 0000 00d0
406d 0a2e 7072 6f6d 6574 6865 7573 5f6c
6f63 616c 5f73 746f 7261 6765 5f69 6e64
6578 696e 675f 7175 6575 655f 6c65 6e67
7468 122c 5468 6520 6e75 6d62 6572 206f
6620 6d65 7472 6963 7320 7761 6974 696e
6720 746f 2062 6520 696e 6465 7865 642e
1801 220b 1209 0900 0000 0000 0000 0067
0a2f 7072 6f6d 6574 6865 7573 5f6c 6f63
616c 5f73 746f 7261 6765 5f69 6e67 6573
7465 645f 7361 6d70 6c65 735f 746f 7461
6c12 2554 6865 2074 6f74 616c 206e 756d
6265 7220 6f66 2073 616d 706c 6573 2069
6e67 6573 7465 642e 1800 220b 1a09 0900
0000 0080 27cd 40c3 010a 3770 726f 6d65
7468 6575 735f 6c6f 6361 6c5f 7374 6f72
6167 655f 696e 7661 6c69 645f 7072 656c
6f61 645f 7265 7175 6573 7473 5f74 6f74
616c 1279 5468 6520 746f 7461 6c20 6e75
6d62 6572 206f 6620 7072 656c 6f61 6420
7265 7175 6573 7473 2072 6566 6572 7269
6e67 2074 6f20 6120 6e6f 6e2d 6578 6973
7465 6e74 2073 6572 6965 732e 2054 6869
7320 6973 2061 6e20 696e 6469 6361 7469
6f6e 206f 6620 6f75 7464 6174 6564 206c
6162 656c 2069 6e64 6578 6573 2e18 0022
0b1a 0909 0000 0000 0000 0000 6f0a 2a70
726f 6d65 7468 6575 735f 6c6f 6361 6c5f
7374 6f72 6167 655f 6d65 6d6f 7279 5f63
6875 6e6b 6465 7363 7312 3254 6865 2063
7572 7265 6e74 206e 756d 6265 7220 6f66
2063 6875 6e6b 2064 6573 6372 6970 746f
7273 2069 6e20 6d65 6d6f 7279 2e18 0122
0b12 0909 0000 0000 0020 8f40 9c01 0a26
7072 6f6d 6574 6865 7573 5f6c 6f63 616c
5f73 746f 7261 6765 5f6d 656d 6f72 795f
6368 756e 6b73 1263 5468 6520 6375 7272
656e 7420 6e75 6d62 6572 206f 6620 6368
756e 6b73 2069 6e20 6d65 6d6f 7279 2c20
6578 636c 7564 696e 6720 636c 6f6e 6564
2063 6875 6e6b 7320 2869 2e65 2e20 6368
756e 6b73 2077 6974 686f 7574 2061 2064
6573 6372 6970 746f 7229 2e18 0122 0b12
0909 0000 0000 00e8 8d40 600a 2670 726f
6d65 7468 6575 735f 6c6f 6361 6c5f 7374
6f72 6167 655f 6d65 6d6f 7279 5f73 6572
6965 7312 2754 6865 2063 7572 7265 6e74
206e 756d 6265 7220 6f66 2073 6572 6965
7320 696e 206d 656d 6f72 792e 1801 220b
1209 0900 0000 0000 807a 40b7 010a 3570
726f 6d65 7468 6575 735f 6c6f 6361 6c5f
7374 6f72 6167 655f 7065 7273 6973 745f
6c61 7465 6e63 795f 6d69 6372 6f73 6563
6f6e 6473 1231 4120 7375 6d6d 6172 7920
6f66 206c 6174 656e 6369 6573 2066 6f72
2070 6572 7369 7374 696e 6720 6561 6368
2063 6875 6e6b 2e18 0222 4922 4708 6f11
1c2f dd24 e68c cc40 1a12 0900 0000 0000
00e0 3f11 8d97 6e12 8360 3e40 1a12 09cd
cccc cccc ccec 3f11 0ad7 a370 3d62 6b40
1a12 09ae 47e1 7a14 aeef 3f11 7b14 ae47
e1b6 7240 6a0a 2f70 726f 6d65 7468 6575
735f 6c6f 6361 6c5f 7374 6f72 6167 655f
7065 7273 6973 745f 7175 6575 655f 6361
7061 6369 7479 1228 5468 6520 746f 7461
6c20 6361 7061 6369 7479 206f 6620 7468
6520 7065 7273 6973 7420 7175 6575 652e
1801 220b 1209 0900 0000 0000 0090 407a
0a2d 7072 6f6d 6574 6865 7573 5f6c 6f63
616c 5f73 746f 7261 6765 5f70 6572 7369
7374 5f71 7565 7565 5f6c 656e 6774 6812
3a54 6865 2063 7572 7265 6e74 206e 756d
6265 7220 6f66 2063 6875 6e6b 7320 7761
6974 696e 6720 696e 2074 6865 2070 6572
7369 7374 2071 7565 7565 2e18 0122 0b12
0909 0000 0000 0000 0000 ac01 0a29 7072
6f6d 6574 6865 7573 5f6c 6f63 616c 5f73
746f 7261 6765 5f73 6572 6965 735f 6f70
735f 746f 7461 6c12 3454 6865 2074 6f74
616c 206e 756d 6265 7220 6f66 2073 6572
6965 7320 6f70 6572 6174 696f 6e73 2062
7920 7468 6569 7220 7479 7065 2e18 0022
1b0a 0e0a 0474 7970 6512 0663 7265 6174
651a 0909 0000 0000 0000 0040 222a 0a1d
0a04 7479 7065 1215 6d61 696e 7465 6e61
6e63 655f 696e 5f6d 656d 6f72 791a 0909
0000 0000 0000 1440 d601 0a2d 7072 6f6d
6574 6865 7573 5f6e 6f74 6966 6963 6174
696f 6e73 5f6c 6174 656e 6379 5f6d 696c
6c69 7365 636f 6e64 7312 584c 6174 656e
6379 2071 7561 6e74 696c 6573 2066 6f72
2073 656e 6469 6e67 2061 6c65 7274 206e
6f74 6966 6963 6174 696f 6e73 2028 6e6f
7420 696e 636c 7564 696e 6720 6472 6f70
7065 6420 6e6f 7469 6669 6361 7469 6f6e
7329 2e18 0222 4922 4708 0011 0000 0000
0000 0000 1a12 0900 0000 0000 00e0 3f11
0000 0000 0000 0000 1a12 09cd cccc cccc
ccec 3f11 0000 0000 0000 0000 1a12 09ae
47e1 7a14 aeef 3f11 0000 0000 0000 0000
680a 2770 726f 6d65 7468 6575 735f 6e6f
7469 6669 6361 7469 6f6e 735f 7175 6575
655f 6361 7061 6369 7479 122e 5468 6520
6361 7061 6369 7479 206f 6620 7468 6520
616c 6572 7420 6e6f 7469 6669 6361 7469
6f6e 7320 7175 6575 652e 1801 220b 1209
0900 0000 0000 0059 4067 0a25 7072 6f6d
6574 6865 7573 5f6e 6f74 6966 6963 6174
696f 6e73 5f71 7565 7565 5f6c 656e 6774
6812 2f54 6865 206e 756d 6265 7220 6f66
2061 6c65 7274 206e 6f74 6966 6963 6174
696f 6e73 2069 6e20 7468 6520 7175 6575
652e 1801 220b 1209 0900 0000 0000 0000
009e 020a 3070 726f 6d65 7468 6575 735f
7275 6c65 5f65 7661 6c75 6174 696f 6e5f
6475 7261 7469 6f6e 5f6d 696c 6c69 7365
636f 6e64 7312 2354 6865 2064 7572 6174
696f 6e20 666f 7220 6120 7275 6c65 2074
6f20 6578 6563 7574 652e 1802 2260 0a15
0a09 7275 6c65 5f74 7970 6512 0861 6c65
7274 696e 6722 4708 3711 0000 0000 0000
2840 1a12 0900 0000 0000 00e0 3f11 0000
0000 0000 0000 1a12 09cd cccc cccc ccec
3f11 0000 0000 0000 0000 1a12 09ae 47e1
7a14 aeef 3f11 0000 0000 0000 0840 2261
0a16 0a09 7275 6c65 5f74 7970 6512 0972
6563 6f72 6469 6e67 2247 0837 1100 0000
0000 002e 401a 1209 0000 0000 0000 e03f
1100 0000 0000 0000 001a 1209 cdcc cccc
cccc ec3f 1100 0000 0000 0000 001a 1209
ae47 e17a 14ae ef3f 1100 0000 0000 0008
4069 0a29 7072 6f6d 6574 6865 7573 5f72
756c 655f 6576 616c 7561 7469 6f6e 5f66
6169 6c75 7265 735f 746f 7461 6c12 2d54
6865 2074 6f74 616c 206e 756d 6265 7220
6f66 2072 756c 6520 6576 616c 7561 7469
6f6e 2066 6169 6c75 7265 732e 1800 220b
1a09 0900 0000 0000 0000 0060 0a21 7072
6f6d 6574 6865 7573 5f73 616d 706c 6573
5f71 7565 7565 5f63 6170 6163 6974 7912
2c43 6170 6163 6974 7920 6f66 2074 6865
2071 7565 7565 2066 6f72 2075 6e77 7269
7474 656e 2073 616d 706c 6573 2e18 0122
0b12 0909 0000 0000 0000 b040 da01 0a1f
7072 6f6d 6574 6865 7573 5f73 616d 706c
6573 5f71 7565 7565 5f6c 656e 6774 6812
a701 4375 7272 656e 7420 6e75 6d62 6572
206f 6620 6974 656d 7320 696e 2074 6865
2071 7565 7565 2066 6f72 2075 6e77 7269
7474 656e 2073 616d 706c 6573 2e20 4561
6368 2069 7465 6d20 636f 6d70 7269 7365
7320 616c 6c20 7361 6d70 6c65 7320 6578
706f 7365 6420 6279 206f 6e65 2074 6172
6765 7420 6173 206f 6e65 206d 6574 7269
6320 6661 6d69 6c79 2028 692e 652e 206d
6574 7269 6373 206f 6620 7468 6520 7361
6d65 206e 616d 6529 2e18 0122 0b12 0909
0000 0000 0000 0000 d902 0a29 7072 6f6d
6574 6865 7573 5f74 6172 6765 745f 696e
7465 7276 616c 5f6c 656e 6774 685f 7365
636f 6e64 7312 2141 6374 7561 6c20 696e
7465 7276 616c 7320 6265 7477 6565 6e20
7363 7261 7065 732e 1802 2282 010a 0f0a
0869 6e74 6572 7661 6c12 0331 3573 226f
0804 1100 0000 0000 804d 401a 1209 7b14
ae47 e17a 843f 1100 0000 0000 002c 401a
1209 9a99 9999 9999 a93f 1100 0000 0000
002c 401a 1209 0000 0000 0000 e03f 1100
0000 0000 002e 401a 1209 cdcc cccc cccc
ec3f 1100 0000 0000 002e 401a 1209 ae47
e17a 14ae ef3f 1100 0000 0000 002e 4022
8101 0a0e 0a08 696e 7465 7276 616c 1202
3173 226f 083a 1100 0000 0000 003c 401a
1209 7b14 ae47 e17a 843f 1100 0000 0000
0000 001a 1209 9a99 9999 9999 a93f 1100
0000 0000 0000 001a 1209 0000 0000 0000
e03f 1100 0000 0000 0000 001a 1209 cdcc
cccc cccc ec3f 1100 0000 0000 00f0 3f1a
1209 ae47 e17a 14ae ef3f 1100 0000 0000
00f0 3f

View file

@ -0,0 +1,129 @@
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

View file

@ -0,0 +1,163 @@
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

View file

@ -0,0 +1,322 @@
# HELP http_request_duration_microseconds The HTTP request latencies in microseconds.
# TYPE http_request_duration_microseconds summary
http_request_duration_microseconds{handler="/",quantile="0.5"} 0
http_request_duration_microseconds{handler="/",quantile="0.9"} 0
http_request_duration_microseconds{handler="/",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/"} 0
http_request_duration_microseconds_count{handler="/"} 0
http_request_duration_microseconds{handler="/alerts",quantile="0.5"} 0
http_request_duration_microseconds{handler="/alerts",quantile="0.9"} 0
http_request_duration_microseconds{handler="/alerts",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/alerts"} 0
http_request_duration_microseconds_count{handler="/alerts"} 0
http_request_duration_microseconds{handler="/api/metrics",quantile="0.5"} 0
http_request_duration_microseconds{handler="/api/metrics",quantile="0.9"} 0
http_request_duration_microseconds{handler="/api/metrics",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/api/metrics"} 0
http_request_duration_microseconds_count{handler="/api/metrics"} 0
http_request_duration_microseconds{handler="/api/query",quantile="0.5"} 0
http_request_duration_microseconds{handler="/api/query",quantile="0.9"} 0
http_request_duration_microseconds{handler="/api/query",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/api/query"} 0
http_request_duration_microseconds_count{handler="/api/query"} 0
http_request_duration_microseconds{handler="/api/query_range",quantile="0.5"} 0
http_request_duration_microseconds{handler="/api/query_range",quantile="0.9"} 0
http_request_duration_microseconds{handler="/api/query_range",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/api/query_range"} 0
http_request_duration_microseconds_count{handler="/api/query_range"} 0
http_request_duration_microseconds{handler="/api/targets",quantile="0.5"} 0
http_request_duration_microseconds{handler="/api/targets",quantile="0.9"} 0
http_request_duration_microseconds{handler="/api/targets",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/api/targets"} 0
http_request_duration_microseconds_count{handler="/api/targets"} 0
http_request_duration_microseconds{handler="/consoles/",quantile="0.5"} 0
http_request_duration_microseconds{handler="/consoles/",quantile="0.9"} 0
http_request_duration_microseconds{handler="/consoles/",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/consoles/"} 0
http_request_duration_microseconds_count{handler="/consoles/"} 0
http_request_duration_microseconds{handler="/graph",quantile="0.5"} 0
http_request_duration_microseconds{handler="/graph",quantile="0.9"} 0
http_request_duration_microseconds{handler="/graph",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/graph"} 0
http_request_duration_microseconds_count{handler="/graph"} 0
http_request_duration_microseconds{handler="/heap",quantile="0.5"} 0
http_request_duration_microseconds{handler="/heap",quantile="0.9"} 0
http_request_duration_microseconds{handler="/heap",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/heap"} 0
http_request_duration_microseconds_count{handler="/heap"} 0
http_request_duration_microseconds{handler="/static/",quantile="0.5"} 0
http_request_duration_microseconds{handler="/static/",quantile="0.9"} 0
http_request_duration_microseconds{handler="/static/",quantile="0.99"} 0
http_request_duration_microseconds_sum{handler="/static/"} 0
http_request_duration_microseconds_count{handler="/static/"} 0
http_request_duration_microseconds{handler="prometheus",quantile="0.5"} 1307.275
http_request_duration_microseconds{handler="prometheus",quantile="0.9"} 1858.632
http_request_duration_microseconds{handler="prometheus",quantile="0.99"} 3087.384
http_request_duration_microseconds_sum{handler="prometheus"} 179886.5000000001
http_request_duration_microseconds_count{handler="prometheus"} 119
# HELP http_request_size_bytes The HTTP request sizes in bytes.
# TYPE http_request_size_bytes summary
http_request_size_bytes{handler="/",quantile="0.5"} 0
http_request_size_bytes{handler="/",quantile="0.9"} 0
http_request_size_bytes{handler="/",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/"} 0
http_request_size_bytes_count{handler="/"} 0
http_request_size_bytes{handler="/alerts",quantile="0.5"} 0
http_request_size_bytes{handler="/alerts",quantile="0.9"} 0
http_request_size_bytes{handler="/alerts",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/alerts"} 0
http_request_size_bytes_count{handler="/alerts"} 0
http_request_size_bytes{handler="/api/metrics",quantile="0.5"} 0
http_request_size_bytes{handler="/api/metrics",quantile="0.9"} 0
http_request_size_bytes{handler="/api/metrics",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/api/metrics"} 0
http_request_size_bytes_count{handler="/api/metrics"} 0
http_request_size_bytes{handler="/api/query",quantile="0.5"} 0
http_request_size_bytes{handler="/api/query",quantile="0.9"} 0
http_request_size_bytes{handler="/api/query",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/api/query"} 0
http_request_size_bytes_count{handler="/api/query"} 0
http_request_size_bytes{handler="/api/query_range",quantile="0.5"} 0
http_request_size_bytes{handler="/api/query_range",quantile="0.9"} 0
http_request_size_bytes{handler="/api/query_range",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/api/query_range"} 0
http_request_size_bytes_count{handler="/api/query_range"} 0
http_request_size_bytes{handler="/api/targets",quantile="0.5"} 0
http_request_size_bytes{handler="/api/targets",quantile="0.9"} 0
http_request_size_bytes{handler="/api/targets",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/api/targets"} 0
http_request_size_bytes_count{handler="/api/targets"} 0
http_request_size_bytes{handler="/consoles/",quantile="0.5"} 0
http_request_size_bytes{handler="/consoles/",quantile="0.9"} 0
http_request_size_bytes{handler="/consoles/",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/consoles/"} 0
http_request_size_bytes_count{handler="/consoles/"} 0
http_request_size_bytes{handler="/graph",quantile="0.5"} 0
http_request_size_bytes{handler="/graph",quantile="0.9"} 0
http_request_size_bytes{handler="/graph",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/graph"} 0
http_request_size_bytes_count{handler="/graph"} 0
http_request_size_bytes{handler="/heap",quantile="0.5"} 0
http_request_size_bytes{handler="/heap",quantile="0.9"} 0
http_request_size_bytes{handler="/heap",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/heap"} 0
http_request_size_bytes_count{handler="/heap"} 0
http_request_size_bytes{handler="/static/",quantile="0.5"} 0
http_request_size_bytes{handler="/static/",quantile="0.9"} 0
http_request_size_bytes{handler="/static/",quantile="0.99"} 0
http_request_size_bytes_sum{handler="/static/"} 0
http_request_size_bytes_count{handler="/static/"} 0
http_request_size_bytes{handler="prometheus",quantile="0.5"} 291
http_request_size_bytes{handler="prometheus",quantile="0.9"} 291
http_request_size_bytes{handler="prometheus",quantile="0.99"} 291
http_request_size_bytes_sum{handler="prometheus"} 34488
http_request_size_bytes_count{handler="prometheus"} 119
# HELP http_requests_total Total number of HTTP requests made.
# TYPE http_requests_total counter
http_requests_total{code="200",handler="prometheus",method="get"} 119
# HELP http_response_size_bytes The HTTP response sizes in bytes.
# TYPE http_response_size_bytes summary
http_response_size_bytes{handler="/",quantile="0.5"} 0
http_response_size_bytes{handler="/",quantile="0.9"} 0
http_response_size_bytes{handler="/",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/"} 0
http_response_size_bytes_count{handler="/"} 0
http_response_size_bytes{handler="/alerts",quantile="0.5"} 0
http_response_size_bytes{handler="/alerts",quantile="0.9"} 0
http_response_size_bytes{handler="/alerts",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/alerts"} 0
http_response_size_bytes_count{handler="/alerts"} 0
http_response_size_bytes{handler="/api/metrics",quantile="0.5"} 0
http_response_size_bytes{handler="/api/metrics",quantile="0.9"} 0
http_response_size_bytes{handler="/api/metrics",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/api/metrics"} 0
http_response_size_bytes_count{handler="/api/metrics"} 0
http_response_size_bytes{handler="/api/query",quantile="0.5"} 0
http_response_size_bytes{handler="/api/query",quantile="0.9"} 0
http_response_size_bytes{handler="/api/query",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/api/query"} 0
http_response_size_bytes_count{handler="/api/query"} 0
http_response_size_bytes{handler="/api/query_range",quantile="0.5"} 0
http_response_size_bytes{handler="/api/query_range",quantile="0.9"} 0
http_response_size_bytes{handler="/api/query_range",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/api/query_range"} 0
http_response_size_bytes_count{handler="/api/query_range"} 0
http_response_size_bytes{handler="/api/targets",quantile="0.5"} 0
http_response_size_bytes{handler="/api/targets",quantile="0.9"} 0
http_response_size_bytes{handler="/api/targets",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/api/targets"} 0
http_response_size_bytes_count{handler="/api/targets"} 0
http_response_size_bytes{handler="/consoles/",quantile="0.5"} 0
http_response_size_bytes{handler="/consoles/",quantile="0.9"} 0
http_response_size_bytes{handler="/consoles/",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/consoles/"} 0
http_response_size_bytes_count{handler="/consoles/"} 0
http_response_size_bytes{handler="/graph",quantile="0.5"} 0
http_response_size_bytes{handler="/graph",quantile="0.9"} 0
http_response_size_bytes{handler="/graph",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/graph"} 0
http_response_size_bytes_count{handler="/graph"} 0
http_response_size_bytes{handler="/heap",quantile="0.5"} 0
http_response_size_bytes{handler="/heap",quantile="0.9"} 0
http_response_size_bytes{handler="/heap",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/heap"} 0
http_response_size_bytes_count{handler="/heap"} 0
http_response_size_bytes{handler="/static/",quantile="0.5"} 0
http_response_size_bytes{handler="/static/",quantile="0.9"} 0
http_response_size_bytes{handler="/static/",quantile="0.99"} 0
http_response_size_bytes_sum{handler="/static/"} 0
http_response_size_bytes_count{handler="/static/"} 0
http_response_size_bytes{handler="prometheus",quantile="0.5"} 2049
http_response_size_bytes{handler="prometheus",quantile="0.9"} 2058
http_response_size_bytes{handler="prometheus",quantile="0.99"} 2064
http_response_size_bytes_sum{handler="prometheus"} 247001
http_response_size_bytes_count{handler="prometheus"} 119
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 0.55
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 70
# HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge
process_max_fds 8192
# HELP process_open_fds Number of open file descriptors.
# TYPE process_open_fds gauge
process_open_fds 29
# HELP process_resident_memory_bytes Resident memory size in bytes.
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 5.3870592e+07
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1.42236894836e+09
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 5.41478912e+08
# HELP prometheus_dns_sd_lookup_failures_total The number of DNS-SD lookup failures.
# TYPE prometheus_dns_sd_lookup_failures_total counter
prometheus_dns_sd_lookup_failures_total 0
# HELP prometheus_dns_sd_lookups_total The number of DNS-SD lookups.
# TYPE prometheus_dns_sd_lookups_total counter
prometheus_dns_sd_lookups_total 7
# HELP prometheus_evaluator_duration_milliseconds The duration for all evaluations to execute.
# TYPE prometheus_evaluator_duration_milliseconds summary
prometheus_evaluator_duration_milliseconds{quantile="0.01"} 0
prometheus_evaluator_duration_milliseconds{quantile="0.05"} 0
prometheus_evaluator_duration_milliseconds{quantile="0.5"} 0
prometheus_evaluator_duration_milliseconds{quantile="0.9"} 1
prometheus_evaluator_duration_milliseconds{quantile="0.99"} 1
prometheus_evaluator_duration_milliseconds_sum 12
prometheus_evaluator_duration_milliseconds_count 23
# HELP prometheus_local_storage_checkpoint_duration_milliseconds The duration (in milliseconds) it took to checkpoint in-memory metrics and head chunks.
# TYPE prometheus_local_storage_checkpoint_duration_milliseconds gauge
prometheus_local_storage_checkpoint_duration_milliseconds 0
# HELP prometheus_local_storage_chunk_ops_total The total number of chunk operations by their type.
# TYPE prometheus_local_storage_chunk_ops_total counter
prometheus_local_storage_chunk_ops_total{type="create"} 598
prometheus_local_storage_chunk_ops_total{type="persist"} 174
prometheus_local_storage_chunk_ops_total{type="pin"} 920
prometheus_local_storage_chunk_ops_total{type="transcode"} 415
prometheus_local_storage_chunk_ops_total{type="unpin"} 920
# HELP prometheus_local_storage_indexing_batch_latency_milliseconds Quantiles for batch indexing latencies in milliseconds.
# TYPE prometheus_local_storage_indexing_batch_latency_milliseconds summary
prometheus_local_storage_indexing_batch_latency_milliseconds{quantile="0.5"} 0
prometheus_local_storage_indexing_batch_latency_milliseconds{quantile="0.9"} 0
prometheus_local_storage_indexing_batch_latency_milliseconds{quantile="0.99"} 0
prometheus_local_storage_indexing_batch_latency_milliseconds_sum 0
prometheus_local_storage_indexing_batch_latency_milliseconds_count 1
# HELP prometheus_local_storage_indexing_batch_sizes Quantiles for indexing batch sizes (number of metrics per batch).
# TYPE prometheus_local_storage_indexing_batch_sizes summary
prometheus_local_storage_indexing_batch_sizes{quantile="0.5"} 2
prometheus_local_storage_indexing_batch_sizes{quantile="0.9"} 2
prometheus_local_storage_indexing_batch_sizes{quantile="0.99"} 2
prometheus_local_storage_indexing_batch_sizes_sum 2
prometheus_local_storage_indexing_batch_sizes_count 1
# HELP prometheus_local_storage_indexing_queue_capacity The capacity of the indexing queue.
# TYPE prometheus_local_storage_indexing_queue_capacity gauge
prometheus_local_storage_indexing_queue_capacity 16384
# HELP prometheus_local_storage_indexing_queue_length The number of metrics waiting to be indexed.
# TYPE prometheus_local_storage_indexing_queue_length gauge
prometheus_local_storage_indexing_queue_length 0
# HELP prometheus_local_storage_ingested_samples_total The total number of samples ingested.
# TYPE prometheus_local_storage_ingested_samples_total counter
prometheus_local_storage_ingested_samples_total 30473
# HELP prometheus_local_storage_invalid_preload_requests_total The total number of preload requests referring to a non-existent series. This is an indication of outdated label indexes.
# TYPE prometheus_local_storage_invalid_preload_requests_total counter
prometheus_local_storage_invalid_preload_requests_total 0
# HELP prometheus_local_storage_memory_chunkdescs The current number of chunk descriptors in memory.
# TYPE prometheus_local_storage_memory_chunkdescs gauge
prometheus_local_storage_memory_chunkdescs 1059
# HELP prometheus_local_storage_memory_chunks The current number of chunks in memory, excluding cloned chunks (i.e. chunks without a descriptor).
# TYPE prometheus_local_storage_memory_chunks gauge
prometheus_local_storage_memory_chunks 1020
# HELP prometheus_local_storage_memory_series The current number of series in memory.
# TYPE prometheus_local_storage_memory_series gauge
prometheus_local_storage_memory_series 424
# HELP prometheus_local_storage_persist_latency_microseconds A summary of latencies for persisting each chunk.
# TYPE prometheus_local_storage_persist_latency_microseconds summary
prometheus_local_storage_persist_latency_microseconds{quantile="0.5"} 30.377
prometheus_local_storage_persist_latency_microseconds{quantile="0.9"} 203.539
prometheus_local_storage_persist_latency_microseconds{quantile="0.99"} 2626.463
prometheus_local_storage_persist_latency_microseconds_sum 20424.415
prometheus_local_storage_persist_latency_microseconds_count 174
# HELP prometheus_local_storage_persist_queue_capacity The total capacity of the persist queue.
# TYPE prometheus_local_storage_persist_queue_capacity gauge
prometheus_local_storage_persist_queue_capacity 1024
# HELP prometheus_local_storage_persist_queue_length The current number of chunks waiting in the persist queue.
# TYPE prometheus_local_storage_persist_queue_length gauge
prometheus_local_storage_persist_queue_length 0
# HELP prometheus_local_storage_series_ops_total The total number of series operations by their type.
# TYPE prometheus_local_storage_series_ops_total counter
prometheus_local_storage_series_ops_total{type="create"} 2
prometheus_local_storage_series_ops_total{type="maintenance_in_memory"} 11
# HELP prometheus_notifications_latency_milliseconds Latency quantiles for sending alert notifications (not including dropped notifications).
# TYPE prometheus_notifications_latency_milliseconds summary
prometheus_notifications_latency_milliseconds{quantile="0.5"} 0
prometheus_notifications_latency_milliseconds{quantile="0.9"} 0
prometheus_notifications_latency_milliseconds{quantile="0.99"} 0
prometheus_notifications_latency_milliseconds_sum 0
prometheus_notifications_latency_milliseconds_count 0
# HELP prometheus_notifications_queue_capacity The capacity of the alert notifications queue.
# TYPE prometheus_notifications_queue_capacity gauge
prometheus_notifications_queue_capacity 100
# HELP prometheus_notifications_queue_length The number of alert notifications in the queue.
# TYPE prometheus_notifications_queue_length gauge
prometheus_notifications_queue_length 0
# HELP prometheus_rule_evaluation_duration_milliseconds The duration for a rule to execute.
# TYPE prometheus_rule_evaluation_duration_milliseconds summary
prometheus_rule_evaluation_duration_milliseconds{rule_type="alerting",quantile="0.5"} 0
prometheus_rule_evaluation_duration_milliseconds{rule_type="alerting",quantile="0.9"} 0
prometheus_rule_evaluation_duration_milliseconds{rule_type="alerting",quantile="0.99"} 2
prometheus_rule_evaluation_duration_milliseconds_sum{rule_type="alerting"} 12
prometheus_rule_evaluation_duration_milliseconds_count{rule_type="alerting"} 115
prometheus_rule_evaluation_duration_milliseconds{rule_type="recording",quantile="0.5"} 0
prometheus_rule_evaluation_duration_milliseconds{rule_type="recording",quantile="0.9"} 0
prometheus_rule_evaluation_duration_milliseconds{rule_type="recording",quantile="0.99"} 3
prometheus_rule_evaluation_duration_milliseconds_sum{rule_type="recording"} 15
prometheus_rule_evaluation_duration_milliseconds_count{rule_type="recording"} 115
# HELP prometheus_rule_evaluation_failures_total The total number of rule evaluation failures.
# TYPE prometheus_rule_evaluation_failures_total counter
prometheus_rule_evaluation_failures_total 0
# HELP prometheus_samples_queue_capacity Capacity of the queue for unwritten samples.
# TYPE prometheus_samples_queue_capacity gauge
prometheus_samples_queue_capacity 4096
# HELP prometheus_samples_queue_length Current number of items in the queue for unwritten samples. Each item comprises all samples exposed by one target as one metric family (i.e. metrics of the same name).
# TYPE prometheus_samples_queue_length gauge
prometheus_samples_queue_length 0
# HELP prometheus_target_interval_length_seconds Actual intervals between scrapes.
# TYPE prometheus_target_interval_length_seconds summary
prometheus_target_interval_length_seconds{interval="15s",quantile="0.01"} 14
prometheus_target_interval_length_seconds{interval="15s",quantile="0.05"} 14
prometheus_target_interval_length_seconds{interval="15s",quantile="0.5"} 15
prometheus_target_interval_length_seconds{interval="15s",quantile="0.9"} 15
prometheus_target_interval_length_seconds{interval="15s",quantile="0.99"} 15
prometheus_target_interval_length_seconds_sum{interval="15s"} 175
prometheus_target_interval_length_seconds_count{interval="15s"} 12
prometheus_target_interval_length_seconds{interval="1s",quantile="0.01"} 0
prometheus_target_interval_length_seconds{interval="1s",quantile="0.05"} 0
prometheus_target_interval_length_seconds{interval="1s",quantile="0.5"} 0
prometheus_target_interval_length_seconds{interval="1s",quantile="0.9"} 1
prometheus_target_interval_length_seconds{interval="1s",quantile="0.99"} 1
prometheus_target_interval_length_seconds_sum{interval="1s"} 55
prometheus_target_interval_length_seconds_count{interval="1s"} 117

View file

@ -0,0 +1,308 @@
// 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 expfmt
import (
"bytes"
"fmt"
"io"
"math"
"strings"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/model"
)
// MetricFamilyToText converts a MetricFamily proto message into text format and
// writes the resulting lines to 'out'. It returns the number of bytes written
// and any error encountered. This function does not perform checks on the
// content of the metric and label names, i.e. invalid metric or label names
// will result in invalid text format output.
// This method fulfills the type 'prometheus.encoder'.
func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
var written int
// Fail-fast checks.
if len(in.Metric) == 0 {
return written, fmt.Errorf("MetricFamily has no metrics: %s", in)
}
name := in.GetName()
if name == "" {
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.
if in.Help != nil {
n, err := fmt.Fprintf(
out, "# HELP %s %s\n",
name, escapeString(*in.Help, false),
)
written += n
if err != nil {
return written, err
}
}
metricType := in.GetType()
n, err := fmt.Fprintf(
out, "# TYPE %s %s\n",
name, strings.ToLower(metricType.String()),
)
written += n
if err != nil {
return written, err
}
// Finally the samples, one line for each.
for _, metric := range in.Metric {
switch metricType {
case dto.MetricType_COUNTER:
if metric.Counter == nil {
return written, fmt.Errorf(
"expected counter in metric %s %s", name, metric,
)
}
n, err = writeSample(
name, metric, "", "",
metric.Counter.GetValue(),
out,
)
case dto.MetricType_GAUGE:
if metric.Gauge == nil {
return written, fmt.Errorf(
"expected gauge in metric %s %s", name, metric,
)
}
n, err = writeSample(
name, metric, "", "",
metric.Gauge.GetValue(),
out,
)
case dto.MetricType_UNTYPED:
if metric.Untyped == nil {
return written, fmt.Errorf(
"expected untyped in metric %s %s", name, metric,
)
}
n, err = writeSample(
name, metric, "", "",
metric.Untyped.GetValue(),
out,
)
case dto.MetricType_SUMMARY:
if metric.Summary == nil {
return written, fmt.Errorf(
"expected summary in metric %s %s", name, metric,
)
}
for _, q := range metric.Summary.Quantile {
n, err = writeSample(
name, metric,
model.QuantileLabel, fmt.Sprint(q.GetQuantile()),
q.GetValue(),
out,
)
written += n
if err != nil {
return written, err
}
}
n, err = writeSample(
name+"_sum", metric, "", "",
metric.Summary.GetSampleSum(),
out,
)
if err != nil {
return written, err
}
written += n
n, err = writeSample(
name+"_count", metric, "", "",
float64(metric.Summary.GetSampleCount()),
out,
)
case dto.MetricType_HISTOGRAM:
if metric.Histogram == nil {
return written, fmt.Errorf(
"expected histogram in metric %s %s", name, metric,
)
}
infSeen := false
for _, q := range metric.Histogram.Bucket {
n, err = writeSample(
name+"_bucket", metric,
model.BucketLabel, fmt.Sprint(q.GetUpperBound()),
float64(q.GetCumulativeCount()),
out,
)
written += n
if err != nil {
return written, err
}
if math.IsInf(q.GetUpperBound(), +1) {
infSeen = true
}
}
if !infSeen {
n, err = writeSample(
name+"_bucket", metric,
model.BucketLabel, "+Inf",
float64(metric.Histogram.GetSampleCount()),
out,
)
if err != nil {
return written, err
}
written += n
}
n, err = writeSample(
name+"_sum", metric, "", "",
metric.Histogram.GetSampleSum(),
out,
)
if err != nil {
return written, err
}
written += n
n, err = writeSample(
name+"_count", metric, "", "",
float64(metric.Histogram.GetSampleCount()),
out,
)
default:
return written, fmt.Errorf(
"unexpected type in metric %s %s", name, metric,
)
}
written += n
if err != nil {
return written, err
}
}
return written, nil
}
// writeSample writes a single sample in text format to out, given the metric
// name, the metric proto message itself, optionally an additional label name
// and value (use empty strings if not required), and the value. The function
// returns the number of bytes written and any error encountered.
func writeSample(
name string,
metric *dto.Metric,
additionalLabelName, additionalLabelValue string,
value float64,
out io.Writer,
) (int, error) {
var written int
n, err := fmt.Fprint(out, name)
written += n
if err != nil {
return written, err
}
n, err = labelPairsToText(
metric.Label,
additionalLabelName, additionalLabelValue,
out,
)
written += n
if err != nil {
return written, err
}
n, err = fmt.Fprintf(out, " %v", value)
written += n
if err != nil {
return written, err
}
if metric.TimestampMs != nil {
n, err = fmt.Fprintf(out, " %v", *metric.TimestampMs)
written += n
if err != nil {
return written, err
}
}
n, err = out.Write([]byte{'\n'})
written += n
if err != nil {
return written, err
}
return written, nil
}
// labelPairsToText converts a slice of LabelPair proto messages plus the
// explicitly given additional label pair into text formatted as required by the
// text format and writes it to 'out'. An empty slice in combination with an
// empty string 'additionalLabelName' results in nothing being
// written. Otherwise, the label pairs are written, escaped as required by the
// text format, and enclosed in '{...}'. The function returns the number of
// bytes written and any error encountered.
func labelPairsToText(
in []*dto.LabelPair,
additionalLabelName, additionalLabelValue string,
out io.Writer,
) (int, error) {
if len(in) == 0 && additionalLabelName == "" {
return 0, nil
}
var written int
separator := '{'
for _, lp := range in {
n, err := fmt.Fprintf(
out, `%c%s="%s"`,
separator, lp.GetName(), escapeString(lp.GetValue(), true),
)
written += n
if err != nil {
return written, err
}
separator = ','
}
if additionalLabelName != "" {
n, err := fmt.Fprintf(
out, `%c%s="%s"`,
separator, additionalLabelName,
escapeString(additionalLabelValue, true),
)
written += n
if err != nil {
return written, err
}
}
n, err := out.Write([]byte{'}'})
written += n
if err != nil {
return written, err
}
return written, nil
}
// escapeString replaces '\' by '\\', new line character by '\n', and - if
// includeDoubleQuote is true - '"' by '\"'.
func escapeString(v string, includeDoubleQuote bool) string {
result := bytes.NewBuffer(make([]byte, 0, len(v)))
for _, c := range v {
switch {
case c == '\\':
result.WriteString(`\\`)
case includeDoubleQuote && c == '"':
result.WriteString(`\"`)
case c == '\n':
result.WriteString(`\n`)
default:
result.WriteRune(c)
}
}
return result.String()
}

View file

@ -0,0 +1,440 @@
// 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 expfmt
import (
"bytes"
"math"
"strings"
"testing"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
)
func testCreate(t testing.TB) {
var scenarios = []struct {
in *dto.MetricFamily
out string
}{
// 0: Counter, NaN as value, timestamp given.
{
in: &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("two-line\n doc str\\ing"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val1"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(math.NaN()),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(.23),
},
TimestampMs: proto.Int64(1234567890),
},
},
},
out: `# HELP name two-line\n doc str\\ing
# TYPE name counter
name{labelname="val1",basename="basevalue"} NaN
name{labelname="val2",basename="basevalue"} 0.23 1234567890
`,
},
// 1: Gauge, some escaping required, +Inf as value, multi-byte characters in label values.
{
in: &dto.MetricFamily{
Name: proto.String("gauge_name"),
Help: proto.String("gauge\ndoc\nstr\"ing"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("name_1"),
Value: proto.String("val with\nnew line"),
},
&dto.LabelPair{
Name: proto.String("name_2"),
Value: proto.String("val with \\backslash and \"quotes\""),
},
},
Gauge: &dto.Gauge{
Value: proto.Float64(math.Inf(+1)),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("name_1"),
Value: proto.String("Björn"),
},
&dto.LabelPair{
Name: proto.String("name_2"),
Value: proto.String("佖佥"),
},
},
Gauge: &dto.Gauge{
Value: proto.Float64(3.14E42),
},
},
},
},
out: `# HELP gauge_name gauge\ndoc\nstr"ing
# TYPE gauge_name gauge
gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes\""} +Inf
gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42
`,
},
// 2: Untyped, no help, one sample with no labels and -Inf as value, another sample with one label.
{
in: &dto.MetricFamily{
Name: proto.String("untyped_name"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(math.Inf(-1)),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("name_1"),
Value: proto.String("value 1"),
},
},
Untyped: &dto.Untyped{
Value: proto.Float64(-1.23e-45),
},
},
},
},
out: `# TYPE untyped_name untyped
untyped_name -Inf
untyped_name{name_1="value 1"} -1.23e-45
`,
},
// 3: Summary.
{
in: &dto.MetricFamily{
Name: proto.String("summary_name"),
Help: proto.String("summary docstring"),
Type: dto.MetricType_SUMMARY.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Summary: &dto.Summary{
SampleCount: proto.Uint64(42),
SampleSum: proto.Float64(-3.4567),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.5),
Value: proto.Float64(-1.23),
},
&dto.Quantile{
Quantile: proto.Float64(0.9),
Value: proto.Float64(.2342354),
},
&dto.Quantile{
Quantile: proto.Float64(0.99),
Value: proto.Float64(0),
},
},
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("name_1"),
Value: proto.String("value 1"),
},
&dto.LabelPair{
Name: proto.String("name_2"),
Value: proto.String("value 2"),
},
},
Summary: &dto.Summary{
SampleCount: proto.Uint64(4711),
SampleSum: proto.Float64(2010.1971),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.5),
Value: proto.Float64(1),
},
&dto.Quantile{
Quantile: proto.Float64(0.9),
Value: proto.Float64(2),
},
&dto.Quantile{
Quantile: proto.Float64(0.99),
Value: proto.Float64(3),
},
},
},
},
},
},
out: `# HELP summary_name summary docstring
# TYPE summary_name summary
summary_name{quantile="0.5"} -1.23
summary_name{quantile="0.9"} 0.2342354
summary_name{quantile="0.99"} 0
summary_name_sum -3.4567
summary_name_count 42
summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1
summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2
summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3
summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
summary_name_count{name_1="value 1",name_2="value 2"} 4711
`,
},
// 4: Histogram
{
in: &dto.MetricFamily{
Name: proto.String("request_duration_microseconds"),
Help: proto.String("The response latency."),
Type: dto.MetricType_HISTOGRAM.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Histogram: &dto.Histogram{
SampleCount: proto.Uint64(2693),
SampleSum: proto.Float64(1756047.3),
Bucket: []*dto.Bucket{
&dto.Bucket{
UpperBound: proto.Float64(100),
CumulativeCount: proto.Uint64(123),
},
&dto.Bucket{
UpperBound: proto.Float64(120),
CumulativeCount: proto.Uint64(412),
},
&dto.Bucket{
UpperBound: proto.Float64(144),
CumulativeCount: proto.Uint64(592),
},
&dto.Bucket{
UpperBound: proto.Float64(172.8),
CumulativeCount: proto.Uint64(1524),
},
&dto.Bucket{
UpperBound: proto.Float64(math.Inf(+1)),
CumulativeCount: proto.Uint64(2693),
},
},
},
},
},
},
out: `# 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
`,
},
// 5: Histogram with missing +Inf bucket.
{
in: &dto.MetricFamily{
Name: proto.String("request_duration_microseconds"),
Help: proto.String("The response latency."),
Type: dto.MetricType_HISTOGRAM.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Histogram: &dto.Histogram{
SampleCount: proto.Uint64(2693),
SampleSum: proto.Float64(1756047.3),
Bucket: []*dto.Bucket{
&dto.Bucket{
UpperBound: proto.Float64(100),
CumulativeCount: proto.Uint64(123),
},
&dto.Bucket{
UpperBound: proto.Float64(120),
CumulativeCount: proto.Uint64(412),
},
&dto.Bucket{
UpperBound: proto.Float64(144),
CumulativeCount: proto.Uint64(592),
},
&dto.Bucket{
UpperBound: proto.Float64(172.8),
CumulativeCount: proto.Uint64(1524),
},
},
},
},
},
},
out: `# 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
`,
},
}
for i, scenario := range scenarios {
out := bytes.NewBuffer(make([]byte, 0, len(scenario.out)))
n, err := MetricFamilyToText(out, scenario.in)
if err != nil {
t.Errorf("%d. error: %s", i, err)
continue
}
if expected, got := len(scenario.out), n; expected != got {
t.Errorf(
"%d. expected %d bytes written, got %d",
i, expected, got,
)
}
if expected, got := scenario.out, out.String(); expected != got {
t.Errorf(
"%d. expected out=%q, got %q",
i, expected, got,
)
}
}
}
func TestCreate(t *testing.T) {
testCreate(t)
}
func BenchmarkCreate(b *testing.B) {
for i := 0; i < b.N; i++ {
testCreate(b)
}
}
func testCreateError(t testing.TB) {
var scenarios = []struct {
in *dto.MetricFamily
err string
}{
// 0: No metric.
{
in: &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("doc string"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{},
},
err: "MetricFamily has no metrics",
},
// 1: No metric name.
{
in: &dto.MetricFamily{
Help: proto.String("doc string"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
err: "MetricFamily has no name",
},
// 2: No metric 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{
Name: proto.String("name"),
Help: proto.String("doc string"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
err: "expected counter in metric",
},
}
for i, scenario := range scenarios {
var out bytes.Buffer
_, err := MetricFamilyToText(&out, scenario.in)
if err == nil {
t.Errorf("%d. expected error, got nil", i)
continue
}
if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 {
t.Errorf(
"%d. expected error starting with %q, got %q",
i, expected, got,
)
}
}
}
func TestCreateError(t *testing.T) {
testCreateError(t)
}
func BenchmarkCreateError(b *testing.B) {
for i := 0; i < b.N; i++ {
testCreateError(b)
}
}

View file

@ -0,0 +1,746 @@
// 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 expfmt
import (
"bufio"
"bytes"
"fmt"
"io"
"math"
"strconv"
"strings"
dto "github.com/prometheus/client_model/go"
"github.com/golang/protobuf/proto"
"github.com/prometheus/common/model"
)
// A stateFn is a function that represents a state in a state machine. By
// executing it, the state is progressed to the next state. The stateFn returns
// another stateFn, which represents the new state. The end state is represented
// by nil.
type stateFn func() stateFn
// ParseError signals errors while parsing the simple and flat text-based
// exchange format.
type ParseError struct {
Line int
Msg string
}
// Error implements the error interface.
func (e ParseError) Error() string {
return fmt.Sprintf("text format parsing error in line %d: %s", e.Line, e.Msg)
}
// TextParser is used to parse the simple and flat text-based exchange format. Its
// nil value is ready to use.
type TextParser struct {
metricFamiliesByName map[string]*dto.MetricFamily
buf *bufio.Reader // Where the parsed input is read through.
err error // Most recent error.
lineCount int // Tracks the line count for error messages.
currentByte byte // The most recent byte read.
currentToken bytes.Buffer // Re-used each time a token has to be gathered from multiple bytes.
currentMF *dto.MetricFamily
currentMetric *dto.Metric
currentLabelPair *dto.LabelPair
// The remaining member variables are only used for summaries/histograms.
currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le'
// Summary specific.
summaries map[uint64]*dto.Metric // Key is created with LabelsToSignature.
currentQuantile float64
// Histogram specific.
histograms map[uint64]*dto.Metric // Key is created with LabelsToSignature.
currentBucket float64
// These tell us if the currently processed line ends on '_count' or
// '_sum' respectively and belong to a summary/histogram, representing the sample
// count and sum of that summary/histogram.
currentIsSummaryCount, currentIsSummarySum bool
currentIsHistogramCount, currentIsHistogramSum bool
}
// TextToMetricFamilies reads 'in' as the simple and flat text-based exchange
// format and creates MetricFamily proto messages. It returns the MetricFamily
// proto messages in a map where the metric names are the keys, along with any
// error encountered.
//
// If the input contains duplicate metrics (i.e. lines with the same metric name
// and exactly the same label set), the resulting MetricFamily will contain
// duplicate Metric proto messages. Similar is true for duplicate label
// names. Checks for duplicates have to be performed separately, if required.
// Also note that neither the metrics within each MetricFamily are sorted nor
// the label pairs within each Metric. Sorting is not required for the most
// frequent use of this method, which is sample ingestion in the Prometheus
// server. However, for presentation purposes, you might want to sort the
// metrics, and in some cases, you must sort the labels, e.g. for consumption by
// the metric family injection hook of the Prometheus registry.
//
// Summaries and histograms are rather special beasts. You would probably not
// use them in the simple text format anyway. This method can deal with
// summaries and histograms if they are presented in exactly the way the
// text.Create function creates them.
//
// This method must not be called concurrently. If you want to parse different
// input concurrently, instantiate a separate Parser for each goroutine.
func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricFamily, error) {
p.reset(in)
for nextState := p.startOfLine; nextState != nil; nextState = nextState() {
// Magic happens here...
}
// Get rid of empty metric families.
for k, mf := range p.metricFamiliesByName {
if len(mf.GetMetric()) == 0 {
delete(p.metricFamiliesByName, k)
}
}
return p.metricFamiliesByName, p.err
}
func (p *TextParser) reset(in io.Reader) {
p.metricFamiliesByName = map[string]*dto.MetricFamily{}
if p.buf == nil {
p.buf = bufio.NewReader(in)
} else {
p.buf.Reset(in)
}
p.err = nil
p.lineCount = 0
if p.summaries == nil || len(p.summaries) > 0 {
p.summaries = map[uint64]*dto.Metric{}
}
if p.histograms == nil || len(p.histograms) > 0 {
p.histograms = map[uint64]*dto.Metric{}
}
p.currentQuantile = math.NaN()
p.currentBucket = math.NaN()
}
// startOfLine represents the state where the next byte read from p.buf is the
// start of a line (or whitespace leading up to it).
func (p *TextParser) startOfLine() stateFn {
p.lineCount++
if p.skipBlankTab(); p.err != nil {
// End of input reached. This is the only case where
// that is not an error but a signal that we are done.
p.err = nil
return nil
}
switch p.currentByte {
case '#':
return p.startComment
case '\n':
return p.startOfLine // Empty line, start the next one.
}
return p.readingMetricName
}
// startComment represents the state where the next byte read from p.buf is the
// start of a comment (or whitespace leading up to it).
func (p *TextParser) startComment() stateFn {
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte == '\n' {
return p.startOfLine
}
if p.readTokenUntilWhitespace(); p.err != nil {
return nil // Unexpected end of input.
}
// If we have hit the end of line already, there is nothing left
// to do. This is not considered a syntax error.
if p.currentByte == '\n' {
return p.startOfLine
}
keyword := p.currentToken.String()
if keyword != "HELP" && keyword != "TYPE" {
// Generic comment, ignore by fast forwarding to end of line.
for p.currentByte != '\n' {
if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil {
return nil // Unexpected end of input.
}
}
return p.startOfLine
}
// There is something. Next has to be a metric name.
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.readTokenAsMetricName(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte == '\n' {
// At the end of the line already.
// Again, this is not considered a syntax error.
return p.startOfLine
}
if !isBlankOrTab(p.currentByte) {
p.parseError("invalid metric name in comment")
return nil
}
p.setOrCreateCurrentMF()
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte == '\n' {
// At the end of the line already.
// Again, this is not considered a syntax error.
return p.startOfLine
}
switch keyword {
case "HELP":
return p.readingHelp
case "TYPE":
return p.readingType
}
panic(fmt.Sprintf("code error: unexpected keyword %q", keyword))
}
// readingMetricName represents the state where the last byte read (now in
// p.currentByte) is the first byte of a metric name.
func (p *TextParser) readingMetricName() stateFn {
if p.readTokenAsMetricName(); p.err != nil {
return nil
}
if p.currentToken.Len() == 0 {
p.parseError("invalid metric name")
return nil
}
p.setOrCreateCurrentMF()
// Now is the time to fix the type if it hasn't happened yet.
if p.currentMF.Type == nil {
p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
}
p.currentMetric = &dto.Metric{}
// Do not append the newly created currentMetric to
// currentMF.Metric right now. First wait if this is a summary,
// and the metric exists already, which we can only know after
// having read all the labels.
if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
return p.readingLabels
}
// readingLabels represents the state where the last byte read (now in
// p.currentByte) is either the first byte of the label set (i.e. a '{'), or the
// first byte of the value (otherwise).
func (p *TextParser) readingLabels() stateFn {
// Summaries/histograms are special. We have to reset the
// currentLabels map, currentQuantile and currentBucket before starting to
// read labels.
if p.currentMF.GetType() == dto.MetricType_SUMMARY || p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
p.currentLabels = map[string]string{}
p.currentLabels[string(model.MetricNameLabel)] = p.currentMF.GetName()
p.currentQuantile = math.NaN()
p.currentBucket = math.NaN()
}
if p.currentByte != '{' {
return p.readingValue
}
return p.startLabelName
}
// startLabelName represents the state where the next byte read from p.buf is
// the start of a label name (or whitespace leading up to it).
func (p *TextParser) startLabelName() stateFn {
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte == '}' {
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
return p.readingValue
}
if p.readTokenAsLabelName(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentToken.Len() == 0 {
p.parseError(fmt.Sprintf("invalid label name for metric %q", p.currentMF.GetName()))
return nil
}
p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())}
if p.currentLabelPair.GetName() == string(model.MetricNameLabel) {
p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel))
return nil
}
// Special summary/histogram treatment. Don't add 'quantile' and 'le'
// labels to 'real' labels.
if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) &&
!(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) {
p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair)
}
if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte != '=' {
p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte))
return nil
}
return p.startLabelValue
}
// startLabelValue represents the state where the next byte read from p.buf is
// the start of a (quoted) label value (or whitespace leading up to it).
func (p *TextParser) startLabelValue() stateFn {
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte != '"' {
p.parseError(fmt.Sprintf("expected '\"' at start of label value, found %q", p.currentByte))
return nil
}
if p.readTokenAsLabelValue(); p.err != nil {
return nil
}
p.currentLabelPair.Value = proto.String(p.currentToken.String())
// Special treatment of summaries:
// - Quantile labels are special, will result in dto.Quantile later.
// - Other labels have to be added to currentLabels for signature calculation.
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
if p.currentLabelPair.GetName() == model.QuantileLabel {
if p.currentQuantile, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
// Create a more helpful error message.
p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue()))
return nil
}
} else {
p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue()
}
}
// Similar special treatment of histograms.
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
if p.currentLabelPair.GetName() == model.BucketLabel {
if p.currentBucket, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
// Create a more helpful error message.
p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue()))
return nil
}
} else {
p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue()
}
}
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
switch p.currentByte {
case ',':
return p.startLabelName
case '}':
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
return p.readingValue
default:
p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.Value))
return nil
}
}
// readingValue represents the state where the last byte read (now in
// p.currentByte) is the first byte of the sample value (i.e. a float).
func (p *TextParser) readingValue() stateFn {
// When we are here, we have read all the labels, so for the
// special case of a summary/histogram, we can finally find out
// if the metric already exists.
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
signature := model.LabelsToSignature(p.currentLabels)
if summary := p.summaries[signature]; summary != nil {
p.currentMetric = summary
} else {
p.summaries[signature] = p.currentMetric
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
}
} else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
signature := model.LabelsToSignature(p.currentLabels)
if histogram := p.histograms[signature]; histogram != nil {
p.currentMetric = histogram
} else {
p.histograms[signature] = p.currentMetric
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
}
} else {
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
}
if p.readTokenUntilWhitespace(); p.err != nil {
return nil // Unexpected end of input.
}
value, err := strconv.ParseFloat(p.currentToken.String(), 64)
if err != nil {
// Create a more helpful error message.
p.parseError(fmt.Sprintf("expected float as value, got %q", p.currentToken.String()))
return nil
}
switch p.currentMF.GetType() {
case dto.MetricType_COUNTER:
p.currentMetric.Counter = &dto.Counter{Value: proto.Float64(value)}
case dto.MetricType_GAUGE:
p.currentMetric.Gauge = &dto.Gauge{Value: proto.Float64(value)}
case dto.MetricType_UNTYPED:
p.currentMetric.Untyped = &dto.Untyped{Value: proto.Float64(value)}
case dto.MetricType_SUMMARY:
// *sigh*
if p.currentMetric.Summary == nil {
p.currentMetric.Summary = &dto.Summary{}
}
switch {
case p.currentIsSummaryCount:
p.currentMetric.Summary.SampleCount = proto.Uint64(uint64(value))
case p.currentIsSummarySum:
p.currentMetric.Summary.SampleSum = proto.Float64(value)
case !math.IsNaN(p.currentQuantile):
p.currentMetric.Summary.Quantile = append(
p.currentMetric.Summary.Quantile,
&dto.Quantile{
Quantile: proto.Float64(p.currentQuantile),
Value: proto.Float64(value),
},
)
}
case dto.MetricType_HISTOGRAM:
// *sigh*
if p.currentMetric.Histogram == nil {
p.currentMetric.Histogram = &dto.Histogram{}
}
switch {
case p.currentIsHistogramCount:
p.currentMetric.Histogram.SampleCount = proto.Uint64(uint64(value))
case p.currentIsHistogramSum:
p.currentMetric.Histogram.SampleSum = proto.Float64(value)
case !math.IsNaN(p.currentBucket):
p.currentMetric.Histogram.Bucket = append(
p.currentMetric.Histogram.Bucket,
&dto.Bucket{
UpperBound: proto.Float64(p.currentBucket),
CumulativeCount: proto.Uint64(uint64(value)),
},
)
}
default:
p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName())
}
if p.currentByte == '\n' {
return p.startOfLine
}
return p.startTimestamp
}
// startTimestamp represents the state where the next byte read from p.buf is
// the start of the timestamp (or whitespace leading up to it).
func (p *TextParser) startTimestamp() stateFn {
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.readTokenUntilWhitespace(); p.err != nil {
return nil // Unexpected end of input.
}
timestamp, err := strconv.ParseInt(p.currentToken.String(), 10, 64)
if err != nil {
// Create a more helpful error message.
p.parseError(fmt.Sprintf("expected integer as timestamp, got %q", p.currentToken.String()))
return nil
}
p.currentMetric.TimestampMs = proto.Int64(timestamp)
if p.readTokenUntilNewline(false); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentToken.Len() > 0 {
p.parseError(fmt.Sprintf("spurious string after timestamp: %q", p.currentToken.String()))
return nil
}
return p.startOfLine
}
// readingHelp represents the state where the last byte read (now in
// p.currentByte) is the first byte of the docstring after 'HELP'.
func (p *TextParser) readingHelp() stateFn {
if p.currentMF.Help != nil {
p.parseError(fmt.Sprintf("second HELP line for metric name %q", p.currentMF.GetName()))
return nil
}
// Rest of line is the docstring.
if p.readTokenUntilNewline(true); p.err != nil {
return nil // Unexpected end of input.
}
p.currentMF.Help = proto.String(p.currentToken.String())
return p.startOfLine
}
// readingType represents the state where the last byte read (now in
// p.currentByte) is the first byte of the type hint after 'HELP'.
func (p *TextParser) readingType() stateFn {
if p.currentMF.Type != nil {
p.parseError(fmt.Sprintf("second TYPE line for metric name %q, or TYPE reported after samples", p.currentMF.GetName()))
return nil
}
// Rest of line is the type.
if p.readTokenUntilNewline(false); p.err != nil {
return nil // Unexpected end of input.
}
metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())]
if !ok {
p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String()))
return nil
}
p.currentMF.Type = dto.MetricType(metricType).Enum()
return p.startOfLine
}
// parseError sets p.err to a ParseError at the current line with the given
// message.
func (p *TextParser) parseError(msg string) {
p.err = ParseError{
Line: p.lineCount,
Msg: msg,
}
}
// skipBlankTab reads (and discards) bytes from p.buf until it encounters a byte
// that is neither ' ' nor '\t'. That byte is left in p.currentByte.
func (p *TextParser) skipBlankTab() {
for {
if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil || !isBlankOrTab(p.currentByte) {
return
}
}
}
// skipBlankTabIfCurrentBlankTab works exactly as skipBlankTab but doesn't do
// anything if p.currentByte is neither ' ' nor '\t'.
func (p *TextParser) skipBlankTabIfCurrentBlankTab() {
if isBlankOrTab(p.currentByte) {
p.skipBlankTab()
}
}
// readTokenUntilWhitespace copies bytes from p.buf into p.currentToken. The
// first byte considered is the byte already read (now in p.currentByte). The
// first whitespace byte encountered is still copied into p.currentByte, but not
// into p.currentToken.
func (p *TextParser) readTokenUntilWhitespace() {
p.currentToken.Reset()
for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' {
p.currentToken.WriteByte(p.currentByte)
p.currentByte, p.err = p.buf.ReadByte()
}
}
// readTokenUntilNewline copies bytes from p.buf into p.currentToken. The first
// byte considered is the byte already read (now in p.currentByte). The first
// newline byte encountered is still copied into p.currentByte, but not into
// p.currentToken. If recognizeEscapeSequence is true, two escape sequences are
// recognized: '\\' tranlates into '\', and '\n' into a line-feed character. All
// other escape sequences are invalid and cause an error.
func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
p.currentToken.Reset()
escaped := false
for p.err == nil {
if recognizeEscapeSequence && escaped {
switch p.currentByte {
case '\\':
p.currentToken.WriteByte(p.currentByte)
case 'n':
p.currentToken.WriteByte('\n')
default:
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
return
}
escaped = false
} else {
switch p.currentByte {
case '\n':
return
case '\\':
escaped = true
default:
p.currentToken.WriteByte(p.currentByte)
}
}
p.currentByte, p.err = p.buf.ReadByte()
}
}
// readTokenAsMetricName copies a metric name from p.buf into p.currentToken.
// The first byte considered is the byte already read (now in p.currentByte).
// The first byte not part of a metric name is still copied into p.currentByte,
// but not into p.currentToken.
func (p *TextParser) readTokenAsMetricName() {
p.currentToken.Reset()
if !isValidMetricNameStart(p.currentByte) {
return
}
for {
p.currentToken.WriteByte(p.currentByte)
p.currentByte, p.err = p.buf.ReadByte()
if p.err != nil || !isValidMetricNameContinuation(p.currentByte) {
return
}
}
}
// readTokenAsLabelName copies a label name from p.buf into p.currentToken.
// The first byte considered is the byte already read (now in p.currentByte).
// The first byte not part of a label name is still copied into p.currentByte,
// but not into p.currentToken.
func (p *TextParser) readTokenAsLabelName() {
p.currentToken.Reset()
if !isValidLabelNameStart(p.currentByte) {
return
}
for {
p.currentToken.WriteByte(p.currentByte)
p.currentByte, p.err = p.buf.ReadByte()
if p.err != nil || !isValidLabelNameContinuation(p.currentByte) {
return
}
}
}
// readTokenAsLabelValue copies a label value from p.buf into p.currentToken.
// In contrast to the other 'readTokenAs...' functions, which start with the
// last read byte in p.currentByte, this method ignores p.currentByte and starts
// with reading a new byte from p.buf. The first byte not part of a label value
// is still copied into p.currentByte, but not into p.currentToken.
func (p *TextParser) readTokenAsLabelValue() {
p.currentToken.Reset()
escaped := false
for {
if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil {
return
}
if escaped {
switch p.currentByte {
case '"', '\\':
p.currentToken.WriteByte(p.currentByte)
case 'n':
p.currentToken.WriteByte('\n')
default:
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
return
}
escaped = false
continue
}
switch p.currentByte {
case '"':
return
case '\n':
p.parseError(fmt.Sprintf("label value %q contains unescaped new-line", p.currentToken.String()))
return
case '\\':
escaped = true
default:
p.currentToken.WriteByte(p.currentByte)
}
}
}
func (p *TextParser) setOrCreateCurrentMF() {
p.currentIsSummaryCount = false
p.currentIsSummarySum = false
p.currentIsHistogramCount = false
p.currentIsHistogramSum = false
name := p.currentToken.String()
if p.currentMF = p.metricFamiliesByName[name]; p.currentMF != nil {
return
}
// Try out if this is a _sum or _count for a summary/histogram.
summaryName := summaryMetricName(name)
if p.currentMF = p.metricFamiliesByName[summaryName]; p.currentMF != nil {
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
if isCount(name) {
p.currentIsSummaryCount = true
}
if isSum(name) {
p.currentIsSummarySum = true
}
return
}
}
histogramName := histogramMetricName(name)
if p.currentMF = p.metricFamiliesByName[histogramName]; p.currentMF != nil {
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
if isCount(name) {
p.currentIsHistogramCount = true
}
if isSum(name) {
p.currentIsHistogramSum = true
}
return
}
}
p.currentMF = &dto.MetricFamily{Name: proto.String(name)}
p.metricFamiliesByName[name] = p.currentMF
}
func isValidLabelNameStart(b byte) bool {
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_'
}
func isValidLabelNameContinuation(b byte) bool {
return isValidLabelNameStart(b) || (b >= '0' && b <= '9')
}
func isValidMetricNameStart(b byte) bool {
return isValidLabelNameStart(b) || b == ':'
}
func isValidMetricNameContinuation(b byte) bool {
return isValidLabelNameContinuation(b) || b == ':'
}
func isBlankOrTab(b byte) bool {
return b == ' ' || b == '\t'
}
func isCount(name string) bool {
return len(name) > 6 && name[len(name)-6:] == "_count"
}
func isSum(name string) bool {
return len(name) > 4 && name[len(name)-4:] == "_sum"
}
func isBucket(name string) bool {
return len(name) > 7 && name[len(name)-7:] == "_bucket"
}
func summaryMetricName(name string) string {
switch {
case isCount(name):
return name[:len(name)-6]
case isSum(name):
return name[:len(name)-4]
default:
return name
}
}
func histogramMetricName(name string) string {
switch {
case isCount(name):
return name[:len(name)-6]
case isSum(name):
return name[:len(name)-4]
case isBucket(name):
return name[:len(name)-7]
default:
return name
}
}

View file

@ -0,0 +1,586 @@
// 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 expfmt
import (
"math"
"strings"
"testing"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
)
func testTextParse(t testing.TB) {
var scenarios = []struct {
in string
out []*dto.MetricFamily
}{
// 0: Empty lines as input.
{
in: `
`,
out: []*dto.MetricFamily{},
},
// 1: Minimal case.
{
in: `
minimal_metric 1.234
another_metric -3e3 103948
# Even that:
no_labels{} 3
# HELP line for non-existing metric will be ignored.
`,
out: []*dto.MetricFamily{
&dto.MetricFamily{
Name: proto.String("minimal_metric"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(1.234),
},
},
},
},
&dto.MetricFamily{
Name: proto.String("another_metric"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(-3e3),
},
TimestampMs: proto.Int64(103948),
},
},
},
&dto.MetricFamily{
Name: proto.String("no_labels"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(3),
},
},
},
},
},
},
// 2: Counters & gauges, docstrings, various whitespace, escape sequences.
{
in: `
# 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
`,
out: []*dto.MetricFamily{
&dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("two-line\n doc str\\ing"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val1"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(math.NaN()),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("base\"v\\al\nue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(.23),
},
TimestampMs: proto.Int64(1234567890),
},
},
},
&dto.MetricFamily{
Name: proto.String("name2"),
Help: proto.String("doc str\"ing 2"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue2"),
},
},
Gauge: &dto.Gauge{
Value: proto.Float64(math.Inf(+1)),
},
TimestampMs: proto.Int64(54321),
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val1"),
},
},
Gauge: &dto.Gauge{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
},
},
// 3: The evil summary, mixed with other types and funny comments.
{
in: `
# 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
`,
out: []*dto.MetricFamily{
&dto.MetricFamily{
Name: proto.String("fake_sum"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val1"),
},
},
Untyped: &dto.Untyped{
Value: proto.Float64(2001),
},
},
},
},
&dto.MetricFamily{
Name: proto.String("decoy"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(-1),
},
TimestampMs: proto.Int64(-2),
},
},
},
&dto.MetricFamily{
Name: proto.String("my_summary"),
Type: dto.MetricType_SUMMARY.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val1"),
},
},
Summary: &dto.Summary{
SampleCount: proto.Uint64(42),
SampleSum: proto.Float64(4711),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.5),
Value: proto.Float64(110),
},
&dto.Quantile{
Quantile: proto.Float64(0.9),
Value: proto.Float64(140),
},
},
},
TimestampMs: proto.Int64(2),
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n2"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val1"),
},
},
Summary: &dto.Summary{
SampleCount: proto.Uint64(5),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(-12.34),
Value: proto.Float64(math.NaN()),
},
},
},
TimestampMs: proto.Int64(5),
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val2"),
},
},
Summary: &dto.Summary{
SampleSum: proto.Float64(8),
},
TimestampMs: proto.Int64(15),
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val3"),
},
},
Summary: &dto.Summary{
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.2),
Value: proto.Float64(4711),
},
},
},
},
},
},
&dto.MetricFamily{
Name: proto.String("another_summary"),
Type: dto.MetricType_SUMMARY.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n2"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val1"),
},
},
Summary: &dto.Summary{
SampleCount: proto.Uint64(20),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.3),
Value: proto.Float64(-1.2),
},
},
},
},
},
},
},
},
// 4: The histogram.
{
in: `
# 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
`,
out: []*dto.MetricFamily{
{
Name: proto.String("request_duration_microseconds"),
Help: proto.String("The response latency."),
Type: dto.MetricType_HISTOGRAM.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Histogram: &dto.Histogram{
SampleCount: proto.Uint64(2693),
SampleSum: proto.Float64(1756047.3),
Bucket: []*dto.Bucket{
&dto.Bucket{
UpperBound: proto.Float64(100),
CumulativeCount: proto.Uint64(123),
},
&dto.Bucket{
UpperBound: proto.Float64(120),
CumulativeCount: proto.Uint64(412),
},
&dto.Bucket{
UpperBound: proto.Float64(144),
CumulativeCount: proto.Uint64(592),
},
&dto.Bucket{
UpperBound: proto.Float64(172.8),
CumulativeCount: proto.Uint64(1524),
},
&dto.Bucket{
UpperBound: proto.Float64(math.Inf(+1)),
CumulativeCount: proto.Uint64(2693),
},
},
},
},
},
},
},
},
}
for i, scenario := range scenarios {
out, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in))
if err != nil {
t.Errorf("%d. error: %s", i, err)
continue
}
if expected, got := len(scenario.out), len(out); expected != got {
t.Errorf(
"%d. expected %d MetricFamilies, got %d",
i, expected, got,
)
}
for _, expected := range scenario.out {
got, ok := out[expected.GetName()]
if !ok {
t.Errorf(
"%d. expected MetricFamily %q, found none",
i, expected.GetName(),
)
continue
}
if expected.String() != got.String() {
t.Errorf(
"%d. expected MetricFamily %s, got %s",
i, expected, got,
)
}
}
}
}
func TestTextParse(t *testing.T) {
testTextParse(t)
}
func BenchmarkTextParse(b *testing.B) {
for i := 0; i < b.N; i++ {
testTextParse(b)
}
}
func testTextParseError(t testing.TB) {
var scenarios = []struct {
in string
err string
}{
// 0: No new-line at end of input.
{
in: `bla 3.14`,
err: "EOF",
},
// 1: Invalid escape sequence in label value.
{
in: `metric{label="\t"} 3.14`,
err: "text format parsing error in line 1: invalid escape sequence",
},
// 2: Newline in label value.
{
in: `
metric{label="new
line"} 3.14
`,
err: `text format parsing error in line 2: label value "new" contains unescaped new-line`,
},
// 3:
{
in: `metric{@="bla"} 3.14`,
err: "text format parsing error in line 1: invalid label name for metric",
},
// 4:
{
in: `metric{__name__="bla"} 3.14`,
err: `text format parsing error in line 1: label name "__name__" is reserved`,
},
// 5:
{
in: `metric{label+="bla"} 3.14`,
err: "text format parsing error in line 1: expected '=' after label name",
},
// 6:
{
in: `metric{label=bla} 3.14`,
err: "text format parsing error in line 1: expected '\"' at start of label value",
},
// 7:
{
in: `
# TYPE metric summary
metric{quantile="bla"} 3.14
`,
err: "text format parsing error in line 3: expected float as value for 'quantile' label",
},
// 8:
{
in: `metric{label="bla"+} 3.14`,
err: "text format parsing error in line 1: unexpected end of label value",
},
// 9:
{
in: `metric{label="bla"} 3.14 2.72
`,
err: "text format parsing error in line 1: expected integer as timestamp",
},
// 10:
{
in: `metric{label="bla"} 3.14 2 3
`,
err: "text format parsing error in line 1: spurious string after timestamp",
},
// 11:
{
in: `metric{label="bla"} blubb
`,
err: "text format parsing error in line 1: expected float as value",
},
// 12:
{
in: `
# HELP metric one
# HELP metric two
`,
err: "text format parsing error in line 3: second HELP line for metric name",
},
// 13:
{
in: `
# TYPE metric counter
# TYPE metric untyped
`,
err: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`,
},
// 14:
{
in: `
metric 4.12
# TYPE metric counter
`,
err: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`,
},
// 14:
{
in: `
# TYPE metric bla
`,
err: "text format parsing error in line 2: unknown metric type",
},
// 15:
{
in: `
# TYPE met-ric
`,
err: "text format parsing error in line 2: invalid metric name in comment",
},
// 16:
{
in: `@invalidmetric{label="bla"} 3.14 2`,
err: "text format parsing error in line 1: invalid metric name",
},
// 17:
{
in: `{label="bla"} 3.14 2`,
err: "text format parsing error in line 1: invalid metric name",
},
// 18:
{
in: `
# TYPE metric histogram
metric_bucket{le="bla"} 3.14
`,
err: "text format parsing error in line 3: expected float as value for 'le' label",
},
}
for i, scenario := range scenarios {
_, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in))
if err == nil {
t.Errorf("%d. expected error, got nil", i)
continue
}
if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 {
t.Errorf(
"%d. expected error starting with %q, got %q",
i, expected, got,
)
}
}
}
func TestTextParseError(t *testing.T) {
testTextParseError(t)
}
func BenchmarkParseError(b *testing.B) {
for i := 0; i < b.N; i++ {
testTextParseError(b)
}
}