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:
parent
d36afbbd8d
commit
10dae5d108
|
@ -11,7 +11,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package decoding
|
||||
package extraction
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||
}
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
@ -25,12 +27,12 @@ var Processor002 = &processor002{}
|
|||
|
||||
type histogram002 struct {
|
||||
Labels map[string]string `json:"labels"`
|
||||
Values map[string]SampleValue `json:"value"`
|
||||
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,
|
||||
})
|
|
@ -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
|
|
@ -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]
|
||||
}
|
|
@ -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, ", ")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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, ", "))
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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++ {
|
|
@ -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]
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue