From 1c74eedf76cefc0e74b6fe3f380d360c896d44bf Mon Sep 17 00:00:00 2001 From: "Matt T. Proud" Date: Fri, 8 Feb 2013 13:05:35 +0100 Subject: [PATCH] Create initial Metric fingerprinter. The old system relies off of super-careful notion that the serialized form of a Protocol Buffer should be used for fingerprint formulation. Of course this is both wrong and inefficient. This commit breaks ground for swapping to a pure attribute-oriented digest. --- model/metric.go | 33 ++++++++++++++++++++++++ model/metric_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 model/metric_test.go diff --git a/model/metric.go b/model/metric.go index de7a7620f..b18a265b0 100644 --- a/model/metric.go +++ b/model/metric.go @@ -14,10 +14,19 @@ package model import ( + "bytes" + "crypto/md5" + "encoding/hex" "fmt" + "sort" "time" ) +const ( + // XXX: Re-evaluate down the road. + reservedDelimiter = '"' +) + // A Fingerprint is a simplified representation of an entity---e.g., a hash of // an entire Metric. type Fingerprint string @@ -40,6 +49,30 @@ type LabelSet map[LabelName]LabelValue // a singleton and refers to one and only one stream of samples. type Metric map[LabelName]LabelValue +// Fingerprint generates a fingerprint for this given Metric. +func (m Metric) Fingerprint() string { + labelLength := len(m) + labelNames := make([]string, 0, labelLength) + + for labelName := range m { + labelNames = append(labelNames, string(labelName)) + } + + sort.Strings(labelNames) + + summer := md5.New() + + buffer := bytes.Buffer{} + for _, labelName := range labelNames { + buffer.WriteString(labelName) + buffer.WriteRune(reservedDelimiter) + buffer.WriteString(string(m[LabelName(labelName)])) + } + summer.Write(buffer.Bytes()) + + return hex.EncodeToString(summer.Sum(nil)) +} + // A SampleValue is a representation of a value for a given sample at a given // time. It is presently float32 due to that being the representation that // Protocol Buffers provide of floats in Go. This is a smell and should be diff --git a/model/metric_test.go b/model/metric_test.go new file mode 100644 index 000000000..9addf7858 --- /dev/null +++ b/model/metric_test.go @@ -0,0 +1,61 @@ +// Copyright 2013 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "github.com/prometheus/prometheus/utility/test" + "testing" +) + +func testMetric(t test.Tester) { + var scenarios = []struct { + input map[string]string + output string + }{ + { + input: map[string]string{}, + output: "d41d8cd98f00b204e9800998ecf8427e", + }, + { + input: map[string]string{ + "first_name": "electro", + "occupation": "robot", + "manufacturer": "westinghouse", + }, + output: "18596f03fce001153495d903b8b577c0", + }, + } + + for i, scenario := range scenarios { + metric := Metric{} + for key, value := range scenario.input { + metric[LabelName(key)] = LabelValue(value) + } + + expected := scenario.output + actual := metric.Fingerprint() + + if expected != actual { + t.Errorf("%d. expected %s, got %s", i, expected, actual) + } + } +} + +func TestMetric(t *testing.T) { + testMetric(t) +} + +func BenchmarkMetric(b *testing.B) { + testMetric(b) +}