Remove client_golang/model dependency from text package

This commit is contained in:
Fabian Reinartz 2015-08-24 10:54:08 +02:00
parent 8c899cd820
commit 25e1a6571b
3 changed files with 113 additions and 14 deletions

View File

@ -27,7 +27,6 @@ import (
"math"
"strings"
"github.com/prometheus/client_golang/model"
dto "github.com/prometheus/client_model/go"
)
@ -118,7 +117,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
for _, q := range metric.Summary.Quantile {
n, err = writeSample(
name, metric,
model.QuantileLabel, fmt.Sprint(q.GetQuantile()),
quantileLabel, fmt.Sprint(q.GetQuantile()),
q.GetValue(),
out,
)
@ -151,7 +150,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
for _, q := range metric.Histogram.Bucket {
n, err = writeSample(
name+"_bucket", metric,
model.BucketLabel, fmt.Sprint(q.GetUpperBound()),
bucketLabel, fmt.Sprint(q.GetUpperBound()),
float64(q.GetCumulativeCount()),
out,
)
@ -166,7 +165,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
if !infSeen {
n, err = writeSample(
name+"_bucket", metric,
model.BucketLabel, "+Inf",
bucketLabel, "+Inf",
float64(metric.Histogram.GetSampleCount()),
out,
)

View File

@ -17,15 +17,32 @@ import (
"bufio"
"bytes"
"fmt"
"hash"
"hash/fnv"
"io"
"math"
"sort"
"strconv"
"strings"
"sync"
dto "github.com/prometheus/client_model/go"
"github.com/golang/protobuf/proto"
"github.com/prometheus/client_golang/model"
)
const (
// metricNameLabel is the label name indicating the metric name of a
// timeseries.
metricNameLabel = "__name__"
// bucketLabel is used for the label that defines the upper bound of a
// bucket of a histogram ("le" -> "less or equal").
bucketLabel = "le"
// quantileLabel is used for the label that defines the quantile in a
// summary.
quantileLabel = "quantile"
)
// A stateFn is a function that represents a state in a state machine. By
@ -245,7 +262,7 @@ func (p *Parser) readingLabels() stateFn {
// 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.currentLabels[string(metricNameLabel)] = p.currentMF.GetName()
p.currentQuantile = math.NaN()
p.currentBucket = math.NaN()
}
@ -275,14 +292,14 @@ func (p *Parser) startLabelName() stateFn {
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))
if p.currentLabelPair.GetName() == string(metricNameLabel) {
p.parseError(fmt.Sprintf("label name %q is reserved", 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) {
if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == quantileLabel) &&
!(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == bucketLabel) {
p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair)
}
if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
@ -313,7 +330,7 @@ func (p *Parser) startLabelValue() stateFn {
// - 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.currentLabelPair.GetName() == 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()))
@ -325,7 +342,7 @@ func (p *Parser) startLabelValue() stateFn {
}
// Similar special treatment of histograms.
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
if p.currentLabelPair.GetName() == model.BucketLabel {
if p.currentLabelPair.GetName() == 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()))
@ -360,7 +377,7 @@ func (p *Parser) readingValue() stateFn {
// 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)
signature := labelsToSignature(p.currentLabels)
if summary := p.summaries[signature]; summary != nil {
p.currentMetric = summary
} else {
@ -368,7 +385,7 @@ func (p *Parser) readingValue() stateFn {
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
}
} else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
signature := model.LabelsToSignature(p.currentLabels)
signature := labelsToSignature(p.currentLabels)
if histogram := p.histograms[signature]; histogram != nil {
p.currentMetric = histogram
} else {
@ -744,3 +761,62 @@ func histogramMetricName(name string) string {
return name
}
}
// separatorByte is a byte that cannot occur in valid UTF-8 sequences and is
// used to separate label names, label values, and other strings from each other
// when calculating their combined hash value (aka signature aka fingerprint).
const separatorByte byte = 255
var (
// cache the signature of an empty label set.
emptyLabelSignature = fnv.New64a().Sum64()
hashAndBufPool sync.Pool
)
type hashAndBuf struct {
h hash.Hash64
b bytes.Buffer
}
func getHashAndBuf() *hashAndBuf {
hb := hashAndBufPool.Get()
if hb == nil {
return &hashAndBuf{h: fnv.New64a()}
}
return hb.(*hashAndBuf)
}
func putHashAndBuf(hb *hashAndBuf) {
hb.h.Reset()
hb.b.Reset()
hashAndBufPool.Put(hb)
}
// labelsToSignature returns a quasi-unique signature (i.e., fingerprint) for a
// given label set. (Collisions are possible but unlikely if the number of label
// sets the function is applied to is small.)
func labelsToSignature(labels map[string]string) uint64 {
if len(labels) == 0 {
return emptyLabelSignature
}
labelNames := make([]string, 0, len(labels))
for labelName := range labels {
labelNames = append(labelNames, labelName)
}
sort.Strings(labelNames)
hb := getHashAndBuf()
defer putHashAndBuf(hb)
for _, labelName := range labelNames {
hb.b.WriteString(labelName)
hb.b.WriteByte(separatorByte)
hb.b.WriteString(labels[labelName])
hb.b.WriteByte(separatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
}
return hb.h.Sum64()
}

View File

@ -586,3 +586,27 @@ func BenchmarkParseError(b *testing.B) {
testParseError(b)
}
}
func TestLabelsToSignature(t *testing.T) {
var scenarios = []struct {
in map[string]string
out uint64
}{
{
in: map[string]string{},
out: 14695981039346656037,
},
{
in: map[string]string{"name": "garland, briggs", "fear": "love is not enough"},
out: 5799056148416392346,
},
}
for i, scenario := range scenarios {
actual := labelsToSignature(scenario.in)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}