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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package decoding
|
package extraction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -11,15 +11,17 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package decoding
|
package extraction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testDiscriminatorHttpHeader(t tester) {
|
func testDiscriminatorHttpHeader(t test.Tester) {
|
||||||
var scenarios = []struct {
|
var scenarios = []struct {
|
||||||
input map[string]string
|
input map[string]string
|
||||||
output Processor
|
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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package decoding
|
package extraction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The label name prefix to prepend if a synthetic label is already present
|
// The label name prefix to prepend if a synthetic label is already present
|
||||||
// in the exported metrics.
|
// in the exported metrics.
|
||||||
ExporterLabelPrefix = LabelName("exporter_")
|
ExporterLabelPrefix model.LabelName = "exporter_"
|
||||||
|
|
||||||
// The label name indicating the metric name of a timeseries.
|
// 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.
|
// 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
|
// ProcessOptions dictates how the interpreted stream should be rendered for
|
||||||
|
@ -37,7 +39,7 @@ type ProcessOptions struct {
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
|
|
||||||
// BaseLabels are labels that are accumulated onto each sample, if any.
|
// 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
|
// 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
|
// 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.
|
// smart enough to unmarshal JSON objects into LabelSet directly.
|
||||||
func labelSet(labels map[string]string) LabelSet {
|
func labelSet(labels map[string]string) model.LabelSet {
|
||||||
labelset := make(LabelSet, len(labels))
|
labelset := make(model.LabelSet, len(labels))
|
||||||
|
|
||||||
for k, v := range labels {
|
for k, v := range labels {
|
||||||
labelset[LabelName(k)] = LabelValue(v)
|
labelset[model.LabelName(k)] = model.LabelValue(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return labelset
|
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
|
// exported sample. If a label is already defined in the exported sample, we
|
||||||
// assume that we are scraping an intermediate exporter and attach
|
// assume that we are scraping an intermediate exporter and attach
|
||||||
// "exporter_"-prefixes to Prometheus' own base labels.
|
// "exporter_"-prefixes to Prometheus' own base labels.
|
||||||
func mergeTargetLabels(entityLabels, targetLabels LabelSet) LabelSet {
|
func mergeTargetLabels(entityLabels, targetLabels model.LabelSet) model.LabelSet {
|
||||||
if targetLabels == nil {
|
if targetLabels == nil {
|
||||||
targetLabels = LabelSet{}
|
targetLabels = model.LabelSet{}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := LabelSet{}
|
result := model.LabelSet{}
|
||||||
|
|
||||||
for label, value := range entityLabels {
|
for label, value := range entityLabels {
|
||||||
result[label] = value
|
result[label] = value
|
||||||
|
@ -91,9 +93,32 @@ func mergeTargetLabels(entityLabels, targetLabels LabelSet) LabelSet {
|
||||||
// Result encapsulates the outcome from processing samples from a source.
|
// Result encapsulates the outcome from processing samples from a source.
|
||||||
type Result struct {
|
type Result struct {
|
||||||
Err error
|
Err error
|
||||||
Samples Samples
|
Samples model.Samples
|
||||||
}
|
}
|
||||||
|
|
||||||
// A LabelName is a key for a LabelSet or Metric. It has a value associated
|
// A basic interface only useful in testing contexts for dispensing the time
|
||||||
// therewith.
|
// in a controlled manner.
|
||||||
type LabelName string
|
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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package decoding
|
package extraction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -39,7 +41,7 @@ var Processor001 Processor = &processor001{}
|
||||||
|
|
||||||
// processor001 is responsible for handling API version 0.0.1.
|
// processor001 is responsible for handling API version 0.0.1.
|
||||||
type processor001 struct {
|
type processor001 struct {
|
||||||
time Time
|
time clock
|
||||||
}
|
}
|
||||||
|
|
||||||
// entity001 represents a the JSON structure that 0.0.1 uses.
|
// 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.
|
// TODO(matt): This outer loop is a great basis for parallelization.
|
||||||
pendingSamples := Samples{}
|
pendingSamples := model.Samples{}
|
||||||
for _, entity := range entities {
|
for _, entity := range entities {
|
||||||
for _, value := range entity.Metric.Value {
|
for _, value := range entity.Metric.Value {
|
||||||
entityLabels := labelSet(entity.BaseLabels).Merge(labelSet(value.Labels))
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingSamples = append(pendingSamples, &Sample{
|
pendingSamples = append(pendingSamples, &model.Sample{
|
||||||
Metric: Metric(labels),
|
Metric: model.Metric(labels),
|
||||||
Timestamp: o.Timestamp,
|
Timestamp: o.Timestamp,
|
||||||
Value: SampleValue(sampleValue),
|
Value: model.SampleValue(sampleValue),
|
||||||
})
|
})
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -107,18 +109,18 @@ func (p *processor001) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
childMetric := make(map[LabelName]LabelValue, len(labels)+1)
|
childMetric := make(map[model.LabelName]model.LabelValue, len(labels)+1)
|
||||||
|
|
||||||
for k, v := range labels {
|
for k, v := range labels {
|
||||||
childMetric[k] = v
|
childMetric[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
childMetric[LabelName(percentile001)] = LabelValue(percentile)
|
childMetric[model.LabelName(percentile001)] = model.LabelValue(percentile)
|
||||||
|
|
||||||
pendingSamples = append(pendingSamples, &Sample{
|
pendingSamples = append(pendingSamples, &model.Sample{
|
||||||
Metric: Metric(childMetric),
|
Metric: model.Metric(childMetric),
|
||||||
Timestamp: o.Timestamp,
|
Timestamp: o.Timestamp,
|
||||||
Value: SampleValue(individualValue),
|
Value: model.SampleValue(individualValue),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package decoding
|
package extraction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
@ -20,13 +20,16 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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 {
|
var scenarios = []struct {
|
||||||
in string
|
in string
|
||||||
baseLabels LabelSet
|
baseLabels model.LabelSet
|
||||||
out Samples
|
out model.Samples
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -35,85 +38,85 @@ func testProcessor001Process(t tester) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "test0_0_1-0_0_2.json",
|
in: "test0_0_1-0_0_2.json",
|
||||||
baseLabels: LabelSet{
|
baseLabels: model.LabelSet{
|
||||||
JobLabel: "batch_exporter",
|
JobLabel: "batch_exporter",
|
||||||
},
|
},
|
||||||
out: Samples{
|
out: model.Samples{
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"service": "zed", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
Metric: model.Metric{"service": "zed", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
||||||
Value: 25,
|
Value: 25,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"service": "bar", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
Metric: model.Metric{"service": "bar", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
||||||
Value: 25,
|
Value: 25,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"service": "foo", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
Metric: model.Metric{"service": "foo", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
||||||
Value: 25,
|
Value: 25,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
||||||
Value: 0.0459814091918713,
|
Value: 0.0459814091918713,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
||||||
Value: 78.48563317257356,
|
Value: 78.48563317257356,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
||||||
Value: 15.890724674774395,
|
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,
|
Value: 0.0459814091918713,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
||||||
Value: 78.48563317257356,
|
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,
|
Value: 15.890724674774395,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
||||||
Value: 0.6120456642749681,
|
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,
|
Value: 97.31798360385088,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
||||||
Value: 84.63044031436561,
|
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,
|
Value: 1.355915069887731,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
||||||
Value: 109.89202084295582,
|
Value: 109.89202084295582,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
||||||
Value: 160.21100853053224,
|
Value: 160.21100853053224,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
||||||
Value: 1.772733213161236,
|
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,
|
Value: 109.99626121011262,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
||||||
Value: 172.49828748957728,
|
Value: 172.49828748957728,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -135,12 +138,12 @@ func testProcessor001Process(t tester) {
|
||||||
BaseLabels: scenario.baseLabels,
|
BaseLabels: scenario.baseLabels,
|
||||||
}
|
}
|
||||||
err = Processor001.ProcessSingle(reader, inputChannel, options)
|
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)
|
t.Errorf("%d. expected err of %s, got %s", i, scenario.err, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
delivered := Samples{}
|
delivered := model.Samples{}
|
||||||
|
|
||||||
for len(inputChannel) != 0 {
|
for len(inputChannel) != 0 {
|
||||||
result := <-inputChannel
|
result := <-inputChannel
|
||||||
|
@ -166,7 +169,7 @@ func testProcessor001Process(t tester) {
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for element := expectedElements.Front(); element != nil && found == false; element = element.Next() {
|
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 {
|
if candidate.Value != actual.Value {
|
||||||
continue
|
continue
|
|
@ -11,12 +11,14 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package decoding
|
package extraction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Processor002 is responsible for decoding payloads from protocol version
|
// Processor002 is responsible for decoding payloads from protocol version
|
||||||
|
@ -25,12 +27,12 @@ var Processor002 = &processor002{}
|
||||||
|
|
||||||
type histogram002 struct {
|
type histogram002 struct {
|
||||||
Labels map[string]string `json:"labels"`
|
Labels map[string]string `json:"labels"`
|
||||||
Values map[string]SampleValue `json:"value"`
|
Values map[string]model.SampleValue `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type counter002 struct {
|
type counter002 struct {
|
||||||
Labels map[string]string `json:"labels"`
|
Labels map[string]string `json:"labels"`
|
||||||
Value SampleValue `json:"value"`
|
Value model.SampleValue `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type processor002 struct{}
|
type processor002 struct{}
|
||||||
|
@ -51,7 +53,7 @@ func (p *processor002) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingSamples := Samples{}
|
pendingSamples := model.Samples{}
|
||||||
for _, entity := range entities {
|
for _, entity := range entities {
|
||||||
switch entity.Metric.Type {
|
switch entity.Metric.Type {
|
||||||
case "counter", "gauge":
|
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))
|
entityLabels := labelSet(entity.BaseLabels).Merge(labelSet(counter.Labels))
|
||||||
labels := mergeTargetLabels(entityLabels, o.BaseLabels)
|
labels := mergeTargetLabels(entityLabels, o.BaseLabels)
|
||||||
|
|
||||||
pendingSamples = append(pendingSamples, &Sample{
|
pendingSamples = append(pendingSamples, &model.Sample{
|
||||||
Metric: Metric(labels),
|
Metric: model.Metric(labels),
|
||||||
Timestamp: o.Timestamp,
|
Timestamp: o.Timestamp,
|
||||||
Value: counter.Value,
|
Value: counter.Value,
|
||||||
})
|
})
|
||||||
|
@ -88,11 +90,11 @@ func (p *processor002) ProcessSingle(in io.Reader, out chan<- *Result, o *Proces
|
||||||
for _, histogram := range values {
|
for _, histogram := range values {
|
||||||
for percentile, value := range histogram.Values {
|
for percentile, value := range histogram.Values {
|
||||||
entityLabels := labelSet(entity.BaseLabels).Merge(labelSet(histogram.Labels))
|
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)
|
labels := mergeTargetLabels(entityLabels, o.BaseLabels)
|
||||||
|
|
||||||
pendingSamples = append(pendingSamples, &Sample{
|
pendingSamples = append(pendingSamples, &model.Sample{
|
||||||
Metric: Metric(labels),
|
Metric: model.Metric(labels),
|
||||||
Timestamp: o.Timestamp,
|
Timestamp: o.Timestamp,
|
||||||
Value: value,
|
Value: value,
|
||||||
})
|
})
|
|
@ -11,7 +11,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package decoding
|
package extraction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
@ -21,13 +21,16 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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 {
|
var scenarios = []struct {
|
||||||
in string
|
in string
|
||||||
baseLabels LabelSet
|
baseLabels model.LabelSet
|
||||||
out Samples
|
out model.Samples
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -36,85 +39,85 @@ func testProcessor002Process(t tester) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: "test0_0_1-0_0_2.json",
|
in: "test0_0_1-0_0_2.json",
|
||||||
baseLabels: LabelSet{
|
baseLabels: model.LabelSet{
|
||||||
JobLabel: "batch_exporter",
|
JobLabel: "batch_exporter",
|
||||||
},
|
},
|
||||||
out: Samples{
|
out: model.Samples{
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"service": "zed", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
Metric: model.Metric{"service": "zed", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
||||||
Value: 25,
|
Value: 25,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"service": "bar", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
Metric: model.Metric{"service": "bar", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
||||||
Value: 25,
|
Value: 25,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"service": "foo", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
Metric: model.Metric{"service": "foo", MetricNameLabel: "rpc_calls_total", "job": "batch_job", "exporter_job": "batch_exporter"},
|
||||||
Value: 25,
|
Value: 25,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
||||||
Value: 0.0459814091918713,
|
Value: 0.0459814091918713,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
||||||
Value: 78.48563317257356,
|
Value: 78.48563317257356,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.010000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
||||||
Value: 15.890724674774395,
|
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,
|
Value: 0.0459814091918713,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.050000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
||||||
Value: 78.48563317257356,
|
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,
|
Value: 15.890724674774395,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
||||||
Value: 0.6120456642749681,
|
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,
|
Value: 97.31798360385088,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.500000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
||||||
Value: 84.63044031436561,
|
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,
|
Value: 1.355915069887731,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "bar", "job": "batch_exporter"},
|
||||||
Value: 109.89202084295582,
|
Value: 109.89202084295582,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.900000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
||||||
Value: 160.21100853053224,
|
Value: 160.21100853053224,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "zed", "job": "batch_exporter"},
|
||||||
Value: 1.772733213161236,
|
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,
|
Value: 109.99626121011262,
|
||||||
},
|
},
|
||||||
&Sample{
|
&model.Sample{
|
||||||
Metric: Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
Metric: model.Metric{"percentile": "0.990000", MetricNameLabel: "rpc_latency_microseconds", "service": "foo", "job": "batch_exporter"},
|
||||||
Value: 172.49828748957728,
|
Value: 172.49828748957728,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -136,12 +139,12 @@ func testProcessor002Process(t tester) {
|
||||||
BaseLabels: scenario.baseLabels,
|
BaseLabels: scenario.baseLabels,
|
||||||
}
|
}
|
||||||
err = Processor002.ProcessSingle(reader, inputChannel, options)
|
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)
|
t.Errorf("%d. expected err of %s, got %s", i, scenario.err, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
delivered := Samples{}
|
delivered := model.Samples{}
|
||||||
|
|
||||||
for len(inputChannel) != 0 {
|
for len(inputChannel) != 0 {
|
||||||
result := <-inputChannel
|
result := <-inputChannel
|
||||||
|
@ -167,7 +170,7 @@ func testProcessor002Process(t tester) {
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for element := expectedElements.Front(); element != nil && found == false; element = element.Next() {
|
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 {
|
if candidate.Value != actual.Value {
|
||||||
continue
|
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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package decoding decodes Prometheus clients' data streams for consumers.
|
// Package model contains core representation of Prometheus client primitives.
|
||||||
package decoding
|
package model
|
|
@ -11,7 +11,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package decoding
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -21,40 +21,40 @@ import (
|
||||||
func TestFingerprintComparison(t *testing.T) {
|
func TestFingerprintComparison(t *testing.T) {
|
||||||
fingerprints := []*Fingerprint{
|
fingerprints := []*Fingerprint{
|
||||||
{
|
{
|
||||||
hash: 0,
|
Hash: 0,
|
||||||
firstCharacterOfFirstLabelName: "b",
|
FirstCharacterOfFirstLabelName: "b",
|
||||||
labelMatterLength: 1,
|
LabelMatterLength: 1,
|
||||||
lastCharacterOfLastLabelValue: "b",
|
LastCharacterOfLastLabelValue: "b",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hash: 1,
|
Hash: 1,
|
||||||
firstCharacterOfFirstLabelName: "a",
|
FirstCharacterOfFirstLabelName: "a",
|
||||||
labelMatterLength: 0,
|
LabelMatterLength: 0,
|
||||||
lastCharacterOfLastLabelValue: "a",
|
LastCharacterOfLastLabelValue: "a",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hash: 1,
|
Hash: 1,
|
||||||
firstCharacterOfFirstLabelName: "a",
|
FirstCharacterOfFirstLabelName: "a",
|
||||||
labelMatterLength: 1000,
|
LabelMatterLength: 1000,
|
||||||
lastCharacterOfLastLabelValue: "b",
|
LastCharacterOfLastLabelValue: "b",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hash: 1,
|
Hash: 1,
|
||||||
firstCharacterOfFirstLabelName: "b",
|
FirstCharacterOfFirstLabelName: "b",
|
||||||
labelMatterLength: 0,
|
LabelMatterLength: 0,
|
||||||
lastCharacterOfLastLabelValue: "a",
|
LastCharacterOfLastLabelValue: "a",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hash: 1,
|
Hash: 1,
|
||||||
firstCharacterOfFirstLabelName: "b",
|
FirstCharacterOfFirstLabelName: "b",
|
||||||
labelMatterLength: 1,
|
LabelMatterLength: 1,
|
||||||
lastCharacterOfLastLabelValue: "a",
|
LastCharacterOfLastLabelValue: "a",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hash: 1,
|
Hash: 1,
|
||||||
firstCharacterOfFirstLabelName: "b",
|
FirstCharacterOfFirstLabelName: "b",
|
||||||
labelMatterLength: 1,
|
LabelMatterLength: 1,
|
||||||
lastCharacterOfLastLabelValue: "b",
|
LastCharacterOfLastLabelValue: "b",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i := range fingerprints {
|
for i := range fingerprints {
|
||||||
|
@ -72,16 +72,16 @@ func BenchmarkFingerprinting(b *testing.B) {
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
fps := []*Fingerprint{
|
fps := []*Fingerprint{
|
||||||
{
|
{
|
||||||
hash: 0,
|
Hash: 0,
|
||||||
firstCharacterOfFirstLabelName: "a",
|
FirstCharacterOfFirstLabelName: "a",
|
||||||
labelMatterLength: 2,
|
LabelMatterLength: 2,
|
||||||
lastCharacterOfLastLabelValue: "z",
|
LastCharacterOfLastLabelValue: "z",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hash: 0,
|
Hash: 0,
|
||||||
firstCharacterOfFirstLabelName: "a",
|
FirstCharacterOfFirstLabelName: "a",
|
||||||
labelMatterLength: 2,
|
LabelMatterLength: 2,
|
||||||
lastCharacterOfLastLabelValue: "z",
|
LastCharacterOfLastLabelValue: "z",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i := 0; i < 10; i++ {
|
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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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{})
|
Error(args ...interface{})
|
||||||
Errorf(format string, args ...interface{})
|
Errorf(format string, args ...interface{})
|
||||||
Fatal(args ...interface{})
|
Fatal(args ...interface{})
|
||||||
Fatalf(format string, args ...interface{})
|
Fatalf(format string, args ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// errorEqual compares Go errors for equality.
|
// ErrorEqual compares Go errors for equality.
|
||||||
func errorEqual(left, right error) bool {
|
func ErrorEqual(left, right error) bool {
|
||||||
if left == right {
|
if left == right {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
Loading…
Reference in New Issue