Include relevant server model artifacts.

This commit introduces all relevant server-side artifacts such that the
Result streams can be used by external parties for one-off tools and
such.  This will ultimately better enable us to support additional
wireformats with much more ease.
This commit is contained in:
Matt T. Proud 2013-06-11 11:43:39 +02:00
parent d36afbbd8d
commit 10dae5d108
24 changed files with 842 additions and 455 deletions

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
package extraction
import (
"fmt"

View File

@ -11,15 +11,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
package extraction
import (
"fmt"
"net/http"
"testing"
"github.com/prometheus/client_golang/test"
)
func testDiscriminatorHttpHeader(t tester) {
func testDiscriminatorHttpHeader(t test.Tester) {
var scenarios = []struct {
input map[string]string
output Processor

15
extraction/extraction.go Normal file
View File

@ -0,0 +1,15 @@
// 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 extraction decodes Prometheus clients' data streams for consumers.
package extraction

View File

@ -11,23 +11,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
package extraction
import (
"io"
"time"
"github.com/prometheus/client_golang/model"
)
const (
// The label name prefix to prepend if a synthetic label is already present
// in the exported metrics.
ExporterLabelPrefix = LabelName("exporter_")
ExporterLabelPrefix model.LabelName = "exporter_"
// The label name indicating the metric name of a timeseries.
MetricNameLabel = LabelName("name")
MetricNameLabel = "name"
// The label name indicating the job from which a timeseries was scraped.
JobLabel = LabelName("job")
JobLabel = "job"
)
// ProcessOptions dictates how the interpreted stream should be rendered for
@ -37,7 +39,7 @@ type ProcessOptions struct {
Timestamp time.Time
// BaseLabels are labels that are accumulated onto each sample, if any.
BaseLabels LabelSet
BaseLabels model.LabelSet
}
// Processor is responsible for decoding the actual message responses from
@ -53,11 +55,11 @@ type Processor interface {
//
// NOTE: This should be deleted when support for go 1.0.3 is removed; 1.1 is
// smart enough to unmarshal JSON objects into LabelSet directly.
func labelSet(labels map[string]string) LabelSet {
labelset := make(LabelSet, len(labels))
func labelSet(labels map[string]string) model.LabelSet {
labelset := make(model.LabelSet, len(labels))
for k, v := range labels {
labelset[LabelName(k)] = LabelValue(v)
labelset[model.LabelName(k)] = model.LabelValue(v)
}
return labelset
@ -67,12 +69,12 @@ func labelSet(labels map[string]string) LabelSet {
// exported sample. If a label is already defined in the exported sample, we
// assume that we are scraping an intermediate exporter and attach
// "exporter_"-prefixes to Prometheus' own base labels.
func mergeTargetLabels(entityLabels, targetLabels LabelSet) LabelSet {
func mergeTargetLabels(entityLabels, targetLabels model.LabelSet) model.LabelSet {
if targetLabels == nil {
targetLabels = LabelSet{}
targetLabels = model.LabelSet{}
}
result := LabelSet{}
result := model.LabelSet{}
for label, value := range entityLabels {
result[label] = value
@ -91,9 +93,32 @@ func mergeTargetLabels(entityLabels, targetLabels LabelSet) LabelSet {
// Result encapsulates the outcome from processing samples from a source.
type Result struct {
Err error
Samples Samples
Samples model.Samples
}
// A LabelName is a key for a LabelSet or Metric. It has a value associated
// therewith.
type LabelName string
// A basic interface only useful in testing contexts for dispensing the time
// in a controlled manner.
type instantProvider interface {
// The current instant.
Now() time.Time
}
// Clock is a simple means for fluently wrapping around standard Go timekeeping
// mechanisms to enhance testability without compromising code readability.
//
// It is sufficient for use on bare initialization. A provider should be
// set only for test contexts. When not provided, it emits the current
// system time.
type clock struct {
// The underlying means through which time is provided, if supplied.
Provider instantProvider
}
// Emit the current instant.
func (t *clock) Now() time.Time {
if t.Provider == nil {
return time.Now()
}
return t.Provider.Now()
}

View File

@ -11,13 +11,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
package extraction
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"github.com/prometheus/client_golang/model"
)
const (
@ -39,7 +41,7 @@ var Processor001 Processor = &processor001{}
// processor001 is responsible for handling API version 0.0.1.
type processor001 struct {
time Time
time clock
}
// entity001 represents a the JSON structure that 0.0.1 uses.
@ -68,7 +70,7 @@ func (p *processor001) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
}
// TODO(matt): This outer loop is a great basis for parallelization.
pendingSamples := Samples{}
pendingSamples := model.Samples{}
for _, entity := range entities {
for _, value := range entity.Metric.Value {
entityLabels := labelSet(entity.BaseLabels).Merge(labelSet(value.Labels))
@ -83,10 +85,10 @@ func (p *processor001) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
continue
}
pendingSamples = append(pendingSamples, &Sample{
Metric: Metric(labels),
pendingSamples = append(pendingSamples, &model.Sample{
Metric: model.Metric(labels),
Timestamp: o.Timestamp,
Value: SampleValue(sampleValue),
Value: model.SampleValue(sampleValue),
})
break
@ -107,18 +109,18 @@ func (p *processor001) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
continue
}
childMetric := make(map[LabelName]LabelValue, len(labels)+1)
childMetric := make(map[model.LabelName]model.LabelValue, len(labels)+1)
for k, v := range labels {
childMetric[k] = v
}
childMetric[LabelName(percentile001)] = LabelValue(percentile)
childMetric[model.LabelName(percentile001)] = model.LabelValue(percentile)
pendingSamples = append(pendingSamples, &Sample{
Metric: Metric(childMetric),
pendingSamples = append(pendingSamples, &model.Sample{
Metric: model.Metric(childMetric),
Timestamp: o.Timestamp,
Value: SampleValue(individualValue),
Value: model.SampleValue(individualValue),
})
}

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
package extraction
import (
"container/list"
@ -20,13 +20,16 @@ import (
"path"
"testing"
"time"
"github.com/prometheus/client_golang/test"
"github.com/prometheus/client_golang/model"
)
func testProcessor001Process(t tester) {
func testProcessor001Process(t test.Tester) {
var scenarios = []struct {
in string
baseLabels LabelSet
out Samples
baseLabels model.LabelSet
out model.Samples
err error
}{
{
@ -35,85 +38,85 @@ func testProcessor001Process(t tester) {
},
{
in: "test0_0_1-0_0_2.json",
baseLabels: LabelSet{
baseLabels: model.LabelSet{
JobLabel: "batch_exporter",
},
out: Samples{
&Sample{
Metric: Metric{"service": "zed", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
out: model.Samples{
&model.Sample{
Metric: model.Metric{"service": "zed", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
Value: 25,
},
&Sample{
Metric: Metric{"service": "bar", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"service": "bar", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
Value: 25,
},
&Sample{
Metric: Metric{"service": "foo", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"service": "foo", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
Value: 25,
},
&Sample{
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 0.0459814091918713,
},
&Sample{
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 78.48563317257356,
},
&Sample{
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 15.890724674774395,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 0.0459814091918713,
},
&Sample{
Metric: Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 78.48563317257356,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 15.890724674774395,
},
&Sample{
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 0.6120456642749681,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 97.31798360385088,
},
&Sample{
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 84.63044031436561,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 1.355915069887731,
},
&Sample{
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 109.89202084295582,
},
&Sample{
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 160.21100853053224,
},
&Sample{
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 1.772733213161236,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 109.99626121011262,
},
&Sample{
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 172.49828748957728,
},
},
@ -135,12 +138,12 @@ func testProcessor001Process(t tester) {
BaseLabels: scenario.baseLabels,
}
err = Processor001.ProcessSingle(reader, inputChannel, options)
if !errorEqual(scenario.err, err) {
if !test.ErrorEqual(scenario.err, err) {
t.Errorf("%d. expected err of %s, got %s", i, scenario.err, err)
continue
}
delivered := Samples{}
delivered := model.Samples{}
for len(inputChannel) != 0 {
result := <-inputChannel
@ -166,7 +169,7 @@ func testProcessor001Process(t tester) {
found := false
for element := expectedElements.Front(); element != nil && found == false; element = element.Next() {
candidate := element.Value.(*Sample)
candidate := element.Value.(*model.Sample)
if candidate.Value != actual.Value {
continue

View File

@ -11,12 +11,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
package extraction
import (
"encoding/json"
"fmt"
"io"
"github.com/prometheus/client_golang/model"
)
// Processor002 is responsible for decoding payloads from protocol version
@ -24,13 +26,13 @@ import (
var Processor002 = &processor002{}
type histogram002 struct {
Labels map[string]string `json:"labels"`
Values map[string]SampleValue `json:"value"`
Labels map[string]string `json:"labels"`
Values map[string]model.SampleValue `json:"value"`
}
type counter002 struct {
Labels map[string]string `json:"labels"`
Value SampleValue `json:"value"`
Value model.SampleValue `json:"value"`
}
type processor002 struct{}
@ -51,7 +53,7 @@ func (p *processor002) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
return err
}
pendingSamples := Samples{}
pendingSamples := model.Samples{}
for _, entity := range entities {
switch entity.Metric.Type {
case "counter", "gauge":
@ -68,8 +70,8 @@ func (p *processor002) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
entityLabels := labelSet(entity.BaseLabels).Merge(labelSet(counter.Labels))
labels := mergeTargetLabels(entityLabels, o.BaseLabels)
pendingSamples = append(pendingSamples, &Sample{
Metric: Metric(labels),
pendingSamples = append(pendingSamples, &model.Sample{
Metric: model.Metric(labels),
Timestamp: o.Timestamp,
Value: counter.Value,
})
@ -88,11 +90,11 @@ func (p *processor002) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
for _, histogram := range values {
for percentile, value := range histogram.Values {
entityLabels := labelSet(entity.BaseLabels).Merge(labelSet(histogram.Labels))
entityLabels[LabelName("percentile")] = LabelValue(percentile)
entityLabels[model.LabelName("percentile")] = model.LabelValue(percentile)
labels := mergeTargetLabels(entityLabels, o.BaseLabels)
pendingSamples = append(pendingSamples, &Sample{
Metric: Metric(labels),
pendingSamples = append(pendingSamples, &model.Sample{
Metric: model.Metric(labels),
Timestamp: o.Timestamp,
Value: value,
})

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
package extraction
import (
"container/list"
@ -21,13 +21,16 @@ import (
"runtime"
"testing"
"time"
"github.com/prometheus/client_golang/test"
"github.com/prometheus/client_golang/model"
)
func testProcessor002Process(t tester) {
func testProcessor002Process(t test.Tester) {
var scenarios = []struct {
in string
baseLabels LabelSet
out Samples
baseLabels model.LabelSet
out model.Samples
err error
}{
{
@ -36,85 +39,85 @@ func testProcessor002Process(t tester) {
},
{
in: "test0_0_1-0_0_2.json",
baseLabels: LabelSet{
baseLabels: model.LabelSet{
JobLabel: "batch_exporter",
},
out: Samples{
&Sample{
Metric: Metric{"service": "zed", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
out: model.Samples{
&model.Sample{
Metric: model.Metric{"service": "zed", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
Value: 25,
},
&Sample{
Metric: Metric{"service": "bar", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"service": "bar", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
Value: 25,
},
&Sample{
Metric: Metric{"service": "foo", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"service": "foo", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
Value: 25,
},
&Sample{
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 0.0459814091918713,
},
&Sample{
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 78.48563317257356,
},
&Sample{
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 15.890724674774395,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 0.0459814091918713,
},
&Sample{
Metric: Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 78.48563317257356,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 15.890724674774395,
},
&Sample{
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 0.6120456642749681,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 97.31798360385088,
},
&Sample{
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 84.63044031436561,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 1.355915069887731,
},
&Sample{
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 109.89202084295582,
},
&Sample{
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 160.21100853053224,
},
&Sample{
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
Value: 1.772733213161236,
},
&Sample{
&model.Sample{
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
Value: 109.99626121011262,
},
&Sample{
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
&model.Sample{
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
Value: 172.49828748957728,
},
},
@ -136,12 +139,12 @@ func testProcessor002Process(t tester) {
BaseLabels: scenario.baseLabels,
}
err = Processor002.ProcessSingle(reader, inputChannel, options)
if !errorEqual(scenario.err, err) {
if !test.ErrorEqual(scenario.err, err) {
t.Errorf("%d. expected err of %s, got %s", i, scenario.err, err)
continue
}
delivered := Samples{}
delivered := model.Samples{}
for len(inputChannel) != 0 {
result := <-inputChannel
@ -167,7 +170,7 @@ func testProcessor002Process(t tester) {
found := false
for element := expectedElements.Front(); element != nil && found == false; element = element.Next() {
candidate := element.Value.(*Sample)
candidate := element.Value.(*model.Sample)
if candidate.Value != actual.Value {
continue

163
model/fingerprinting.go Normal file
View File

@ -0,0 +1,163 @@
// 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 (
"encoding/binary"
"fmt"
"hash/fnv"
"sort"
"strconv"
"strings"
)
// Fingerprint provides a hash-capable representation of a Metric.
type Fingerprint struct {
// A hashed representation of the underyling entity. For our purposes, FNV-1A
// 64-bit is used.
Hash uint64
FirstCharacterOfFirstLabelName string
LabelMatterLength uint
LastCharacterOfLastLabelValue string
}
func (f *Fingerprint) String() string {
return strings.Join([]string{fmt.Sprintf("%020d", f.Hash), f.FirstCharacterOfFirstLabelName, fmt.Sprint(f.LabelMatterLength), f.LastCharacterOfLastLabelValue}, "-")
}
func (f *Fingerprint) Less(o *Fingerprint) bool {
if f.Hash < o.Hash {
return true
}
if f.Hash > o.Hash {
return false
}
if f.FirstCharacterOfFirstLabelName < o.FirstCharacterOfFirstLabelName {
return true
}
if f.FirstCharacterOfFirstLabelName > o.FirstCharacterOfFirstLabelName {
return false
}
if f.LabelMatterLength < o.LabelMatterLength {
return true
}
if f.LabelMatterLength > o.LabelMatterLength {
return false
}
if f.LastCharacterOfLastLabelValue < o.LastCharacterOfLastLabelValue {
return true
}
if f.LastCharacterOfLastLabelValue > o.LastCharacterOfLastLabelValue {
return false
}
return false
}
func (f *Fingerprint) Equal(o *Fingerprint) bool {
if f.Hash != o.Hash {
return false
}
if f.FirstCharacterOfFirstLabelName != o.FirstCharacterOfFirstLabelName {
return false
}
if f.LabelMatterLength != o.LabelMatterLength {
return false
}
return f.LastCharacterOfLastLabelValue == o.LastCharacterOfLastLabelValue
}
const rowKeyDelimiter = "-"
// LoadFromString transforms a rowKey into a Fingerprint, resetting any
// previous attributes.
func (f *Fingerprint) LoadFromString(s string) {
components := strings.Split(s, rowKeyDelimiter)
hash, err := strconv.ParseUint(components[0], 10, 64)
if err != nil {
panic(err)
}
labelMatterLength, err := strconv.ParseUint(components[2], 10, 0)
if err != nil {
panic(err)
}
f.Hash = hash
f.FirstCharacterOfFirstLabelName = components[1]
f.LabelMatterLength = uint(labelMatterLength)
f.LastCharacterOfLastLabelValue = components[3]
}
const reservedDelimiter = `"`
// LoadFromMetric decomposes a Metric into this Fingerprint
func (f *Fingerprint) LoadFromMetric(m Metric) {
labelLength := len(m)
labelNames := make([]string, 0, labelLength)
for labelName := range m {
labelNames = append(labelNames, string(labelName))
}
sort.Strings(labelNames)
summer := fnv.New64a()
firstCharacterOfFirstLabelName := ""
lastCharacterOfLastLabelValue := ""
labelMatterLength := 0
for i, labelName := range labelNames {
labelValue := m[LabelName(labelName)]
labelNameLength := len(labelName)
labelValueLength := len(labelValue)
labelMatterLength += labelNameLength + labelValueLength
if i == 0 {
firstCharacterOfFirstLabelName = labelName[0:1]
}
if i == labelLength-1 {
lastCharacterOfLastLabelValue = string(labelValue[labelValueLength-1 : labelValueLength])
}
summer.Write([]byte(labelName))
summer.Write([]byte(reservedDelimiter))
summer.Write([]byte(labelValue))
}
f.FirstCharacterOfFirstLabelName = firstCharacterOfFirstLabelName
f.Hash = binary.LittleEndian.Uint64(summer.Sum(nil))
f.LabelMatterLength = uint(labelMatterLength % 10)
f.LastCharacterOfLastLabelValue = lastCharacterOfLastLabelValue
}
// Represents a collection of Fingerprint subject to a given natural sorting
// scheme.
type Fingerprints []*Fingerprint
func (f Fingerprints) Len() int {
return len(f)
}
func (f Fingerprints) Less(i, j int) bool {
return f[i].Less(f[j])
}
func (f Fingerprints) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}

44
model/labelname.go Normal file
View File

@ -0,0 +1,44 @@
// 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 (
"strings"
)
// A LabelName is a key for a LabelSet or Metric. It has a value associated
// therewith.
type LabelName string
type LabelNames []LabelName
func (l LabelNames) Len() int {
return len(l)
}
func (l LabelNames) Less(i, j int) bool {
return l[i] < l[j]
}
func (l LabelNames) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
func (l LabelNames) String() string {
labelStrings := make([]string, 0, len(l))
for _, label := range l {
labelStrings = append(labelStrings, string(label))
}
return strings.Join(labelStrings, ", ")
}

57
model/labelname_test.go Normal file
View File

@ -0,0 +1,57 @@
// 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 (
"sort"
"testing"
"github.com/prometheus/client_golang/test"
)
func testLabelNames(t test.Tester) {
var scenarios = []struct {
in LabelNames
out LabelNames
}{
{
in: LabelNames{"ZZZ", "zzz"},
out: LabelNames{"ZZZ", "zzz"},
},
{
in: LabelNames{"aaa", "AAA"},
out: LabelNames{"AAA", "aaa"},
},
}
for i, scenario := range scenarios {
sort.Sort(scenario.in)
for j, expected := range scenario.out {
if expected != scenario.in[j] {
t.Errorf("%d.%d expected %s, got %s", i, j, expected, scenario.in[j])
}
}
}
}
func TestLabelNames(t *testing.T) {
testLabelNames(t)
}
func BenchmarkLabelNames(b *testing.B) {
for i := 0; i < b.N; i++ {
testLabelNames(b)
}
}

53
model/labelset.go Normal file
View File

@ -0,0 +1,53 @@
// 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 (
"fmt"
"sort"
"strings"
)
// A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet
// may be fully-qualified down to the point where it may resolve to a single
// Metric in the data store or not. All operations that occur within the realm
// of a LabelSet can emit a vector of Metric entities to which the LabelSet may
// match.
type LabelSet map[LabelName]LabelValue
// Helper function to non-destructively merge two label sets.
func (l LabelSet) Merge(other LabelSet) LabelSet {
result := make(LabelSet, len(l))
for k, v := range l {
result[k] = v
}
for k, v := range other {
result[k] = v
}
return result
}
func (l LabelSet) String() string {
labelStrings := make([]string, 0, len(l))
for label, value := range l {
labelStrings = append(labelStrings, fmt.Sprintf("%s='%s'", label, value))
}
sort.Strings(labelStrings)
return fmt.Sprintf("{%s}", strings.Join(labelStrings, ", "))
}

35
model/labelvalue.go Normal file
View File

@ -0,0 +1,35 @@
// 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 (
"sort"
)
// A LabelValue is an associated value for a LabelName.
type LabelValue string
type LabelValues []LabelValue
func (l LabelValues) Len() int {
return len(l)
}
func (l LabelValues) Less(i, j int) bool {
return sort.StringsAreSorted([]string{string(l[i]), string(l[j])})
}
func (l LabelValues) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}

57
model/labelvalue_test.go Normal file
View File

@ -0,0 +1,57 @@
// 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 (
"sort"
"testing"
"github.com/prometheus/client_golang/test"
)
func testLabelValues(t test.Tester) {
var scenarios = []struct {
in LabelValues
out LabelValues
}{
{
in: LabelValues{"ZZZ", "zzz"},
out: LabelValues{"ZZZ", "zzz"},
},
{
in: LabelValues{"aaa", "AAA"},
out: LabelValues{"AAA", "aaa"},
},
}
for i, scenario := range scenarios {
sort.Sort(scenario.in)
for j, expected := range scenario.out {
if expected != scenario.in[j] {
t.Errorf("%d.%d expected %s, got %s", i, j, expected, scenario.in[j])
}
}
}
}
func TestLabelValues(t *testing.T) {
testLabelValues(t)
}
func BenchmarkLabelValues(b *testing.B) {
for i := 0; i < b.N; i++ {
testLabelValues(b)
}
}

38
model/metric.go Normal file
View File

@ -0,0 +1,38 @@
// 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
// A Metric is similar to a LabelSet, but the key difference is that a Metric is
// a singleton and refers to one and only one stream of samples.
type Metric map[LabelName]LabelValue
func (m Metric) Equal(o Metric) bool {
lFingerprint := &Fingerprint{}
rFingerprint := &Fingerprint{}
lFingerprint.LoadFromMetric(m)
rFingerprint.LoadFromMetric(o)
return lFingerprint.Equal(rFingerprint)
}
func (m Metric) Before(o Metric) bool {
lFingerprint := &Fingerprint{}
rFingerprint := &Fingerprint{}
lFingerprint.LoadFromMetric(m)
rFingerprint.LoadFromMetric(o)
return m.Before(o)
}

81
model/metric_test.go Normal file
View File

@ -0,0 +1,81 @@
// 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 (
"testing"
"github.com/prometheus/client_golang/test"
)
func testMetric(t test.Tester) {
var scenarios = []struct {
input map[string]string
hash uint64
rowkey string
}{
{
input: map[string]string{},
rowkey: "02676020557754725067--0-",
hash: 2676020557754725067,
},
{
input: map[string]string{
"first_name": "electro",
"occupation": "robot",
"manufacturer": "westinghouse",
},
rowkey: "04776841610193542734-f-6-t",
hash: 4776841610193542734,
},
{
input: map[string]string{
"x": "y",
},
rowkey: "01306929544689993150-x-2-y",
hash: 1306929544689993150,
},
}
for i, scenario := range scenarios {
metric := Metric{}
for key, value := range scenario.input {
metric[LabelName(key)] = LabelValue(value)
}
expectedRowKey := scenario.rowkey
expectedHash := scenario.hash
fingerprint := &Fingerprint{}
fingerprint.LoadFromMetric(metric)
actualRowKey := fingerprint.String()
actualHash := fingerprint.Hash
if expectedRowKey != actualRowKey {
t.Errorf("%d. expected %s, got %s", i, expectedRowKey, actualRowKey)
}
if actualHash != expectedHash {
t.Errorf("%d. expected %d, got %d", i, expectedHash, actualHash)
}
}
}
func TestMetric(t *testing.T) {
testMetric(t)
}
func BenchmarkMetric(b *testing.B) {
for i := 0; i < b.N; i++ {
testMetric(b)
}
}

View File

@ -11,5 +11,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package decoding decodes Prometheus clients' data streams for consumers.
package decoding
// Package model contains core representation of Prometheus client primitives.
package model

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
package model
import (
"runtime"
@ -21,40 +21,40 @@ import (
func TestFingerprintComparison(t *testing.T) {
fingerprints := []*Fingerprint{
{
hash: 0,
firstCharacterOfFirstLabelName: "b",
labelMatterLength: 1,
lastCharacterOfLastLabelValue: "b",
Hash: 0,
FirstCharacterOfFirstLabelName: "b",
LabelMatterLength: 1,
LastCharacterOfLastLabelValue: "b",
},
{
hash: 1,
firstCharacterOfFirstLabelName: "a",
labelMatterLength: 0,
lastCharacterOfLastLabelValue: "a",
Hash: 1,
FirstCharacterOfFirstLabelName: "a",
LabelMatterLength: 0,
LastCharacterOfLastLabelValue: "a",
},
{
hash: 1,
firstCharacterOfFirstLabelName: "a",
labelMatterLength: 1000,
lastCharacterOfLastLabelValue: "b",
Hash: 1,
FirstCharacterOfFirstLabelName: "a",
LabelMatterLength: 1000,
LastCharacterOfLastLabelValue: "b",
},
{
hash: 1,
firstCharacterOfFirstLabelName: "b",
labelMatterLength: 0,
lastCharacterOfLastLabelValue: "a",
Hash: 1,
FirstCharacterOfFirstLabelName: "b",
LabelMatterLength: 0,
LastCharacterOfLastLabelValue: "a",
},
{
hash: 1,
firstCharacterOfFirstLabelName: "b",
labelMatterLength: 1,
lastCharacterOfLastLabelValue: "a",
Hash: 1,
FirstCharacterOfFirstLabelName: "b",
LabelMatterLength: 1,
LastCharacterOfLastLabelValue: "a",
},
{
hash: 1,
firstCharacterOfFirstLabelName: "b",
labelMatterLength: 1,
lastCharacterOfLastLabelValue: "b",
Hash: 1,
FirstCharacterOfFirstLabelName: "b",
LabelMatterLength: 1,
LastCharacterOfLastLabelValue: "b",
},
}
for i := range fingerprints {
@ -72,16 +72,16 @@ func BenchmarkFingerprinting(b *testing.B) {
b.StopTimer()
fps := []*Fingerprint{
{
hash: 0,
firstCharacterOfFirstLabelName: "a",
labelMatterLength: 2,
lastCharacterOfLastLabelValue: "z",
Hash: 0,
FirstCharacterOfFirstLabelName: "a",
LabelMatterLength: 2,
LastCharacterOfLastLabelValue: "z",
},
{
hash: 0,
firstCharacterOfFirstLabelName: "a",
labelMatterLength: 2,
lastCharacterOfLastLabelValue: "z",
Hash: 0,
FirstCharacterOfFirstLabelName: "a",
LabelMatterLength: 2,
LastCharacterOfLastLabelValue: "z",
},
}
for i := 0; i < 10; i++ {

59
model/sample.go Normal file
View File

@ -0,0 +1,59 @@
// 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 (
"time"
)
type Sample struct {
Metric Metric
Value SampleValue
Timestamp time.Time
}
func (s *Sample) Equal(o *Sample) bool {
if !s.Metric.Equal(o.Metric) {
return false
}
if !s.Timestamp.Equal(o.Timestamp) {
return false
}
if !s.Value.Equal(o.Value) {
return false
}
return true
}
type Samples []*Sample
func (s Samples) Len() int {
return len(s)
}
func (s Samples) Less(i, j int) bool {
switch {
case s[i].Metric.Before(s[j].Metric):
return true
case s[i].Timestamp.Before(s[j].Timestamp):
return true
default:
return false
}
}
func (s Samples) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

34
model/samplevalue.go Normal file
View File

@ -0,0 +1,34 @@
// 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 (
"fmt"
)
// A SampleValue is a representation of a value for a given sample at a given
// time.
type SampleValue float64
func (v SampleValue) Equal(o SampleValue) bool {
return v == o
}
func (v SampleValue) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%f"`, v)), nil
}
func (v SampleValue) String() string {
return fmt.Sprint(float64(v))
}

View File

@ -1,287 +0,0 @@
// 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 decoding
import (
"encoding/binary"
"fmt"
"hash/fnv"
"sort"
"strings"
"time"
)
type Sample struct {
Metric Metric
Value SampleValue
Timestamp time.Time
}
func (s *Sample) Equal(o *Sample) bool {
if !s.Metric.Equal(o.Metric) {
return false
}
if !s.Timestamp.Equal(o.Timestamp) {
return false
}
if !s.Value.Equal(o.Value) {
return false
}
return true
}
type Samples []*Sample
func (s Samples) Len() int {
return len(s)
}
func (s Samples) Less(i, j int) bool {
switch {
case s[i].Metric.Before(s[j].Metric):
return true
case s[i].Timestamp.Before(s[j].Timestamp):
return true
default:
return false
}
}
func (s Samples) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet
// may be fully-qualified down to the point where it may resolve to a single
// Metric in the data store or not. All operations that occur within the realm
// of a LabelSet can emit a vector of Metric entities to which the LabelSet may
// match.
type LabelSet map[LabelName]LabelValue
// Helper function to non-destructively merge two label sets.
func (l LabelSet) Merge(other LabelSet) LabelSet {
result := make(LabelSet, len(l))
for k, v := range l {
result[k] = v
}
for k, v := range other {
result[k] = v
}
return result
}
func (l LabelSet) String() string {
labelStrings := make([]string, 0, len(l))
for label, value := range l {
labelStrings = append(labelStrings, fmt.Sprintf("%s='%s'", label, value))
}
sort.Strings(labelStrings)
return fmt.Sprintf("{%s}", strings.Join(labelStrings, ", "))
}
// A LabelValue is an associated value for a LabelName.
type LabelValue string
// A Metric is similar to a LabelSet, but the key difference is that a Metric is
// a singleton and refers to one and only one stream of samples.
type Metric map[LabelName]LabelValue
func (m Metric) Equal(o Metric) bool {
lFingerprint := &Fingerprint{}
rFingerprint := &Fingerprint{}
m.WriteFingerprint(lFingerprint)
o.WriteFingerprint(rFingerprint)
return lFingerprint.Equal(rFingerprint)
}
func (m Metric) Before(o Metric) bool {
lFingerprint := &Fingerprint{}
rFingerprint := &Fingerprint{}
m.WriteFingerprint(lFingerprint)
o.WriteFingerprint(rFingerprint)
return m.Before(o)
}
func (m Metric) WriteFingerprint(f *Fingerprint) {
labelLength := len(m)
labelNames := make([]string, 0, labelLength)
for labelName := range m {
labelNames = append(labelNames, string(labelName))
}
sort.Strings(labelNames)
summer := fnv.New64a()
firstCharacterOfFirstLabelName := ""
lastCharacterOfLastLabelValue := ""
labelMatterLength := 0
for i, labelName := range labelNames {
labelValue := m[LabelName(labelName)]
labelNameLength := len(labelName)
labelValueLength := len(labelValue)
labelMatterLength += labelNameLength + labelValueLength
if i == 0 {
firstCharacterOfFirstLabelName = labelName[0:1]
}
if i == labelLength-1 {
lastCharacterOfLastLabelValue = string(labelValue[labelValueLength-1 : labelValueLength])
}
fmt.Fprintf(summer, "%s%s%s", labelName, `"`, labelValue)
}
f.firstCharacterOfFirstLabelName = firstCharacterOfFirstLabelName
f.hash = binary.LittleEndian.Uint64(summer.Sum(nil))
f.labelMatterLength = uint(labelMatterLength % 10)
f.lastCharacterOfLastLabelValue = lastCharacterOfLastLabelValue
}
// A SampleValue is a representation of a value for a given sample at a given
// time.
type SampleValue float64
func (v SampleValue) Equal(o SampleValue) bool {
return v == o
}
func (v SampleValue) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%f"`, v)), nil
}
func (v SampleValue) String() string {
return fmt.Sprint(float64(v))
}
// Fingerprint provides a hash-capable representation of a Metric.
type Fingerprint struct {
// A hashed representation of the underyling entity. For our purposes, FNV-1A
// 64-bit is used.
hash uint64
firstCharacterOfFirstLabelName string
labelMatterLength uint
lastCharacterOfLastLabelValue string
}
func (f *Fingerprint) String() string {
return f.ToRowKey()
}
// Transforms the Fingerprint into a database row key.
func (f *Fingerprint) ToRowKey() string {
return strings.Join([]string{fmt.Sprintf("%020d", f.hash), f.firstCharacterOfFirstLabelName, fmt.Sprint(f.labelMatterLength), f.lastCharacterOfLastLabelValue}, "-")
}
func (f *Fingerprint) Hash() uint64 {
return f.hash
}
func (f *Fingerprint) FirstCharacterOfFirstLabelName() string {
return f.firstCharacterOfFirstLabelName
}
func (f *Fingerprint) LabelMatterLength() uint {
return f.labelMatterLength
}
func (f *Fingerprint) LastCharacterOfLastLabelValue() string {
return f.lastCharacterOfLastLabelValue
}
func (f *Fingerprint) Less(o *Fingerprint) bool {
if f.hash < o.hash {
return true
}
if f.hash > o.hash {
return false
}
if f.firstCharacterOfFirstLabelName < o.firstCharacterOfFirstLabelName {
return true
}
if f.firstCharacterOfFirstLabelName > o.firstCharacterOfFirstLabelName {
return false
}
if f.labelMatterLength < o.labelMatterLength {
return true
}
if f.labelMatterLength > o.labelMatterLength {
return false
}
if f.lastCharacterOfLastLabelValue < o.lastCharacterOfLastLabelValue {
return true
}
if f.lastCharacterOfLastLabelValue > o.lastCharacterOfLastLabelValue {
return false
}
return false
}
func (f *Fingerprint) Equal(o *Fingerprint) bool {
if f.Hash() != o.Hash() {
return false
}
if f.FirstCharacterOfFirstLabelName() != o.FirstCharacterOfFirstLabelName() {
return false
}
if f.LabelMatterLength() != o.LabelMatterLength() {
return false
}
return f.LastCharacterOfLastLabelValue() == o.LastCharacterOfLastLabelValue()
}
// A basic interface only useful in testing contexts for dispensing the time
// in a controlled manner.
type instantProvider interface {
// The current instant.
Now() time.Time
}
// Time is a simple means for fluently wrapping around standard Go timekeeping
// mechanisms to enhance testability without compromising code readability.
//
// It is sufficient for use on bare initialization. A provider should be
// set only for test contexts. When not provided, it emits the current
// system time.
type Time struct {
// The underlying means through which time is provided, if supplied.
Provider instantProvider
}
// Emit the current instant.
func (t *Time) Now() time.Time {
if t.Provider == nil {
return time.Now()
}
return t.Provider.Now()
}

View File

@ -11,17 +11,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package decoding
// Package test provides common test helpers to the client library.
package test
type tester interface {
type Tester interface {
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
}
// errorEqual compares Go errors for equality.
func errorEqual(left, right error) bool {
// ErrorEqual compares Go errors for equality.
func ErrorEqual(left, right error) bool {
if left == right {
return true
}