Vendor common/model package

This commit is contained in:
Fabian Reinartz 2015-08-20 17:18:26 +02:00
parent a7c248e3b1
commit 7a6d12a44c
14 changed files with 2058 additions and 0 deletions

4
Godeps/Godeps.json generated
View file

@ -58,6 +58,10 @@
"ImportPath": "github.com/prometheus/client_golang/text",
"Rev": "3a499bf7fc46bc58337ce612d0cbb29c550b8118"
},
{
"ImportPath": "github.com/prometheus/common/model",
"Rev": "2502df85be1b9482ed669faa6b7cfe7f850eb08e"
},
{
"ImportPath": "github.com/prometheus/client_model/go",
"Comment": "model-0.0.2-12-gfa8ad6f",

View file

@ -0,0 +1,105 @@
// Copyright 2013 The Prometheus Authors
// 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"
"strconv"
)
// Fingerprint provides a hash-capable representation of a Metric.
// For our purposes, FNV-1A 64-bit is used.
type Fingerprint uint64
// FingerprintFromString transforms a string representation into a Fingerprint.
func FingerprintFromString(s string) (Fingerprint, error) {
num, err := strconv.ParseUint(s, 16, 64)
return Fingerprint(num), err
}
// ParseFingerprint parses the input string into a fingerprint.
func ParseFingerprint(s string) (Fingerprint, error) {
num, err := strconv.ParseUint(s, 16, 64)
if err != nil {
return 0, err
}
return Fingerprint(num), nil
}
func (f Fingerprint) String() string {
return fmt.Sprintf("%016x", uint64(f))
}
// Fingerprints represents a collection of Fingerprint subject to a given
// natural sorting scheme. It implements sort.Interface.
type Fingerprints []Fingerprint
// Len implements sort.Interface.
func (f Fingerprints) Len() int {
return len(f)
}
// Less implements sort.Interface.
func (f Fingerprints) Less(i, j int) bool {
return f[i] < f[j]
}
// Swap implements sort.Interface.
func (f Fingerprints) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
// FingerprintSet is a set of Fingerprints.
type FingerprintSet map[Fingerprint]struct{}
// Equal returns true if both sets contain the same elements (and not more).
func (s FingerprintSet) Equal(o FingerprintSet) bool {
if len(s) != len(o) {
return false
}
for k := range s {
if _, ok := o[k]; !ok {
return false
}
}
return true
}
// Intersection returns the elements contained in both sets.
func (s FingerprintSet) Intersection(o FingerprintSet) FingerprintSet {
myLength, otherLength := len(s), len(o)
if myLength == 0 || otherLength == 0 {
return FingerprintSet{}
}
subSet := s
superSet := o
if otherLength < myLength {
subSet = o
superSet = s
}
out := FingerprintSet{}
for k := range subSet {
if _, ok := superSet[k]; ok {
out[k] = struct{}{}
}
}
return out
}

View file

@ -0,0 +1,185 @@
// Copyright 2013 The Prometheus Authors
// 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/json"
"fmt"
"regexp"
"sort"
"strings"
)
const (
// ExportedLabelPrefix is the prefix to prepend to the label names present in
// exported metrics if a label of the same name is added by the server.
ExportedLabelPrefix LabelName = "exported_"
// MetricNameLabel is the label name indicating the metric name of a
// timeseries.
MetricNameLabel LabelName = "__name__"
// SchemeLabel is the name of the label that holds the scheme on which to
// scrape a target.
SchemeLabel LabelName = "__scheme__"
// AddressLabel is the name of the label that holds the address of
// a scrape target.
AddressLabel LabelName = "__address__"
// MetricsPathLabel is the name of the label that holds the path on which to
// scrape a target.
MetricsPathLabel LabelName = "__metrics_path__"
// ReservedLabelPrefix is a prefix which is not legal in user-supplied
// label names.
ReservedLabelPrefix = "__"
// MetaLabelPrefix is a prefix for labels that provide meta information.
// Labels with this prefix are used for intermediate label processing and
// will not be attached to time series.
MetaLabelPrefix = "__meta_"
// TmpLabelPrefix is a prefix for temporary labels as part of relabelling.
// Labels with this prefix are used for intermediate label processing and
// will not be attached to time series. This is reserved for use in
// Prometheus configuration files by users.
TmpLabelPrefix = "__tmp_"
// ParamLabelPrefix is a prefix for labels that provide URL parameters
// used to scrape a target.
ParamLabelPrefix = "__param_"
// JobLabel is the label name indicating the job from which a timeseries
// was scraped.
JobLabel LabelName = "job"
// InstanceLabel is the label name used for the instance label.
InstanceLabel LabelName = "instance"
// BucketLabel is used for the label that defines the upper bound of a
// bucket of a histogram ("le" -> "less or equal").
BucketLabel = "le"
// QuantileLabel is used for the label that defines the quantile in a
// summary.
QuantileLabel = "quantile"
)
// LabelNameRE is a regular expression matching valid label names.
var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
// A LabelName is a key for a LabelSet or Metric. It has a value associated
// therewith.
type LabelName string
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (ln *LabelName) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
if !LabelNameRE.MatchString(s) {
return fmt.Errorf("%q is not a valid label name", s)
}
*ln = LabelName(s)
return nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (ln *LabelName) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if !LabelNameRE.MatchString(s) {
return fmt.Errorf("%q is not a valid label name", s)
}
*ln = LabelName(s)
return nil
}
// LabelNames is a sortable LabelName slice. In implements sort.Interface.
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, ", ")
}
// A LabelValue is an associated value for a LabelName.
type LabelValue string
// LabelValues is a sortable LabelValue slice. It implements sort.Interface.
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]
}
// LabelPair pairs a name with a value.
type LabelPair struct {
Name LabelName
Value LabelValue
}
// LabelPairs is a sortable slice of LabelPair pointers. It implements
// sort.Interface.
type LabelPairs []*LabelPair
func (l LabelPairs) Len() int {
return len(l)
}
func (l LabelPairs) Less(i, j int) bool {
switch {
case l[i].Name > l[j].Name:
return false
case l[i].Name < l[j].Name:
return true
case l[i].Value > l[j].Value:
return false
case l[i].Value < l[j].Value:
return true
default:
return false
}
}
func (l LabelPairs) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}

View file

@ -0,0 +1,91 @@
// Copyright 2013 The Prometheus Authors
// 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"
)
func testLabelNames(t testing.TB) {
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)
}
}
func testLabelValues(t testing.TB) {
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)
}
}

View file

@ -0,0 +1,153 @@
// Copyright 2013 The Prometheus Authors
// 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/json"
"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
func (ls LabelSet) Equal(o LabelSet) bool {
if len(ls) != len(o) {
return false
}
for ln, lv := range ls {
olv, ok := o[ln]
if !ok {
return false
}
if olv != lv {
return false
}
}
return true
}
// Before compares the metrics, using the following criteria:
//
// If m has fewer labels than o, it is before o. If it has more, it is not.
//
// If the number of labels is the same, the superset of all label names is
// sorted alphanumerically. The first differing label pair found in that order
// determines the outcome: If the label does not exist at all in m, then m is
// before o, and vice versa. Otherwise the label value is compared
// alphanumerically.
//
// If m and o are equal, the method returns false.
func (ls LabelSet) Before(o LabelSet) bool {
if len(ls) < len(o) {
return true
}
if len(ls) > len(o) {
return false
}
lns := make(LabelNames, 0, len(ls)+len(o))
for ln := range ls {
lns = append(lns, ln)
}
for ln := range o {
lns = append(lns, ln)
}
// It's probably not worth it to de-dup lns.
sort.Sort(lns)
for _, ln := range lns {
mlv, ok := ls[ln]
if !ok {
return true
}
olv, ok := o[ln]
if !ok {
return false
}
if mlv < olv {
return true
}
if mlv > olv {
return false
}
}
return false
}
func (ls LabelSet) Clone() LabelSet {
lsn := make(LabelSet, len(ls))
for ln, lv := range ls {
lsn[ln] = lv
}
return lsn
}
// Merge is a 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 {
lstrs := make([]string, 0, len(l))
for l, v := range l {
lstrs = append(lstrs, fmt.Sprintf("%s=%q", l, v))
}
sort.Strings(lstrs)
return fmt.Sprintf("{%s}", strings.Join(lstrs, ", "))
}
// Fingerprint returns the LabelSet's fingerprint.
func (ls LabelSet) Fingerprint() Fingerprint {
return labelSetToFingerprint(ls)
}
// FastFingerprint returns the LabelSet's Fingerprint calculated by a faster hashing
// algorithm, which is, however, more susceptible to hash collisions.
func (ls LabelSet) FastFingerprint() Fingerprint {
return labelSetToFastFingerprint(ls)
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (l *LabelSet) UnmarshalJSON(b []byte) error {
var m map[LabelName]LabelValue
if err := json.Unmarshal(b, &m); err != nil {
return err
}
// encoding/json only unmarshals maps of the form map[string]T. It treats
// LabelName as a string and does not call its UnmarshalJSON method.
// Thus, we have to replicate the behavior here.
for ln := range m {
if !LabelNameRE.MatchString(string(ln)) {
return fmt.Errorf("%q is not a valid label name", ln)
}
}
*l = LabelSet(m)
return nil
}

View file

@ -0,0 +1,120 @@
// Copyright 2013 The Prometheus Authors
// 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/json"
"fmt"
"sort"
"strings"
)
var separator = []byte{0}
// 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 LabelSet
// Equal compares the metrics.
func (m Metric) Equal(o Metric) bool {
return LabelSet(m).Equal(LabelSet(o))
}
// Before compares the metrics' underlying label sets.
func (m Metric) Before(o Metric) bool {
return LabelSet(m).Before(LabelSet(o))
}
// Clone returns a copy of the Metric.
func (m Metric) Clone() Metric {
clone := Metric{}
for k, v := range m {
clone[k] = v
}
return clone
}
func (m Metric) String() string {
metricName, hasName := m[MetricNameLabel]
numLabels := len(m) - 1
if !hasName {
numLabels = len(m)
}
labelStrings := make([]string, 0, numLabels)
for label, value := range m {
if label != MetricNameLabel {
labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value))
}
}
switch numLabels {
case 0:
if hasName {
return string(metricName)
}
return "{}"
default:
sort.Strings(labelStrings)
return fmt.Sprintf("%s{%s}", metricName, strings.Join(labelStrings, ", "))
}
}
// Fingerprint returns a Metric's Fingerprint.
func (m Metric) Fingerprint() Fingerprint {
return LabelSet(m).Fingerprint()
}
// FastFingerprint returns a Metric's Fingerprint calculated by a faster hashing
// algorithm, which is, however, more susceptible to hash collisions.
func (m Metric) FastFingerprint() Fingerprint {
return LabelSet(m).FastFingerprint()
}
// COWMetric wraps a Metric to enable copy-on-write access patterns.
type COWMetric struct {
Copied bool
Metric Metric
}
// Set sets a label name in the wrapped Metric to a given value and copies the
// Metric initially, if it is not already a copy.
func (m *COWMetric) Set(ln LabelName, lv LabelValue) {
m.doCOW()
m.Metric[ln] = lv
}
// Delete deletes a given label name from the wrapped Metric and copies the
// Metric initially, if it is not already a copy.
func (m *COWMetric) Del(ln LabelName) {
m.doCOW()
delete(m.Metric, ln)
}
// doCOW copies the underlying Metric if it is not already a copy.
func (m *COWMetric) doCOW() {
if !m.Copied {
m.Metric = m.Metric.Clone()
m.Copied = true
}
}
// String implements fmt.Stringer.
func (m COWMetric) String() string {
return m.Metric.String()
}
// MarshalJSON implements json.Marshaler.
func (m COWMetric) MarshalJSON() ([]byte, error) {
return json.Marshal(m.Metric)
}

View file

@ -0,0 +1,132 @@
// Copyright 2013 The Prometheus Authors
// 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"
func testMetric(t testing.TB) {
var scenarios = []struct {
input LabelSet
fingerprint Fingerprint
fastFingerprint Fingerprint
}{
{
input: LabelSet{},
fingerprint: 14695981039346656037,
fastFingerprint: 14695981039346656037,
},
{
input: LabelSet{
"first_name": "electro",
"occupation": "robot",
"manufacturer": "westinghouse",
},
fingerprint: 5911716720268894962,
fastFingerprint: 11310079640881077873,
},
{
input: LabelSet{
"x": "y",
},
fingerprint: 8241431561484471700,
fastFingerprint: 13948396922932177635,
},
{
input: LabelSet{
"a": "bb",
"b": "c",
},
fingerprint: 3016285359649981711,
fastFingerprint: 3198632812309449502,
},
{
input: LabelSet{
"a": "b",
"bb": "c",
},
fingerprint: 7122421792099404749,
fastFingerprint: 5774953389407657638,
},
}
for i, scenario := range scenarios {
input := Metric(scenario.input)
if scenario.fingerprint != input.Fingerprint() {
t.Errorf("%d. expected %d, got %d", i, scenario.fingerprint, input.Fingerprint())
}
if scenario.fastFingerprint != input.FastFingerprint() {
t.Errorf("%d. expected %d, got %d", i, scenario.fastFingerprint, input.FastFingerprint())
}
}
}
func TestMetric(t *testing.T) {
testMetric(t)
}
func BenchmarkMetric(b *testing.B) {
for i := 0; i < b.N; i++ {
testMetric(b)
}
}
func TestCOWMetric(t *testing.T) {
testMetric := Metric{
"to_delete": "test1",
"to_change": "test2",
}
scenarios := []struct {
fn func(*COWMetric)
out Metric
}{
{
fn: func(cm *COWMetric) {
cm.Del("to_delete")
},
out: Metric{
"to_change": "test2",
},
},
{
fn: func(cm *COWMetric) {
cm.Set("to_change", "changed")
},
out: Metric{
"to_delete": "test1",
"to_change": "changed",
},
},
}
for i, s := range scenarios {
orig := testMetric.Clone()
cm := &COWMetric{
Metric: orig,
}
s.fn(cm)
// Test that the original metric was not modified.
if !orig.Equal(testMetric) {
t.Fatalf("%d. original metric changed; expected %v, got %v", i, testMetric, orig)
}
// Test that the new metric has the right changes.
if !cm.Metric.Equal(s.out) {
t.Fatalf("%d. copied metric doesn't contain expected changes; expected %v, got %v", i, s.out, cm.Metric)
}
}
}

View file

@ -0,0 +1,16 @@
// Copyright 2013 The Prometheus Authors
// 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 contains common data structures that are shared across
// Prometheus componenets and libraries.
package model

View file

@ -0,0 +1,190 @@
// Copyright 2014 The Prometheus Authors
// 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 (
"bytes"
"hash"
"hash/fnv"
"sort"
"sync"
)
// SeparatorByte is a byte that cannot occur in valid UTF-8 sequences and is
// used to separate label names, label values, and other strings from each other
// when calculating their combined hash value (aka signature aka fingerprint).
const SeparatorByte byte = 255
var (
// cache the signature of an empty label set.
emptyLabelSignature = fnv.New64a().Sum64()
hashAndBufPool sync.Pool
)
type hashAndBuf struct {
h hash.Hash64
b bytes.Buffer
}
func getHashAndBuf() *hashAndBuf {
hb := hashAndBufPool.Get()
if hb == nil {
return &hashAndBuf{h: fnv.New64a()}
}
return hb.(*hashAndBuf)
}
func putHashAndBuf(hb *hashAndBuf) {
hb.h.Reset()
hb.b.Reset()
hashAndBufPool.Put(hb)
}
// LabelsToSignature returns a quasi-unique signature (i.e., fingerprint) for a
// given label set. (Collisions are possible but unlikely if the number of label
// sets the function is applied to is small.)
func LabelsToSignature(labels map[string]string) uint64 {
if len(labels) == 0 {
return emptyLabelSignature
}
labelNames := make([]string, 0, len(labels))
for labelName := range labels {
labelNames = append(labelNames, labelName)
}
sort.Strings(labelNames)
hb := getHashAndBuf()
defer putHashAndBuf(hb)
for _, labelName := range labelNames {
hb.b.WriteString(labelName)
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(labels[labelName])
hb.b.WriteByte(SeparatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
}
return hb.h.Sum64()
}
// labelSetToFingerprint works exactly as LabelsToSignature but takes a LabelSet as
// parameter (rather than a label map) and returns a Fingerprint.
func labelSetToFingerprint(ls LabelSet) Fingerprint {
if len(ls) == 0 {
return Fingerprint(emptyLabelSignature)
}
labelNames := make(LabelNames, 0, len(ls))
for labelName := range ls {
labelNames = append(labelNames, labelName)
}
sort.Sort(labelNames)
hb := getHashAndBuf()
defer putHashAndBuf(hb)
for _, labelName := range labelNames {
hb.b.WriteString(string(labelName))
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(string(ls[labelName]))
hb.b.WriteByte(SeparatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
}
return Fingerprint(hb.h.Sum64())
}
// labelSetToFastFingerprint works similar to labelSetToFingerprint but uses a
// faster and less allocation-heavy hash function, which is more susceptible to
// create hash collisions. Therefore, collision detection should be applied.
func labelSetToFastFingerprint(ls LabelSet) Fingerprint {
if len(ls) == 0 {
return Fingerprint(emptyLabelSignature)
}
var result uint64
hb := getHashAndBuf()
defer putHashAndBuf(hb)
for labelName, labelValue := range ls {
hb.b.WriteString(string(labelName))
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(string(labelValue))
hb.h.Write(hb.b.Bytes())
result ^= hb.h.Sum64()
hb.h.Reset()
hb.b.Reset()
}
return Fingerprint(result)
}
// SignatureForLabels works like LabelsToSignature but takes a Metric as
// parameter (rather than a label map) and only includes the labels with the
// specified LabelNames into the signature calculation. The labels passed in
// will be sorted by this function.
func SignatureForLabels(m Metric, labels ...LabelName) uint64 {
if len(m) == 0 || len(labels) == 0 {
return emptyLabelSignature
}
sort.Sort(LabelNames(labels))
hb := getHashAndBuf()
defer putHashAndBuf(hb)
for _, label := range labels {
hb.b.WriteString(string(label))
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(string(m[label]))
hb.b.WriteByte(SeparatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
}
return hb.h.Sum64()
}
// SignatureWithoutLabels works like LabelsToSignature but takes a Metric as
// parameter (rather than a label map) and excludes the labels with any of the
// specified LabelNames from the signature calculation.
func SignatureWithoutLabels(m Metric, labels map[LabelName]struct{}) uint64 {
if len(m) == 0 {
return emptyLabelSignature
}
labelNames := make(LabelNames, 0, len(m))
for labelName := range m {
if _, exclude := labels[labelName]; !exclude {
labelNames = append(labelNames, labelName)
}
}
if len(labelNames) == 0 {
return emptyLabelSignature
}
sort.Sort(labelNames)
hb := getHashAndBuf()
defer putHashAndBuf(hb)
for _, labelName := range labelNames {
hb.b.WriteString(string(labelName))
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(string(m[labelName]))
hb.b.WriteByte(SeparatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
}
return hb.h.Sum64()
}

View file

@ -0,0 +1,304 @@
// Copyright 2014 The Prometheus Authors
// 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 (
"runtime"
"sync"
"testing"
)
func TestLabelsToSignature(t *testing.T) {
var scenarios = []struct {
in map[string]string
out uint64
}{
{
in: map[string]string{},
out: 14695981039346656037,
},
{
in: map[string]string{"name": "garland, briggs", "fear": "love is not enough"},
out: 5799056148416392346,
},
}
for i, scenario := range scenarios {
actual := LabelsToSignature(scenario.in)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func TestMetricToFingerprint(t *testing.T) {
var scenarios = []struct {
in LabelSet
out Fingerprint
}{
{
in: LabelSet{},
out: 14695981039346656037,
},
{
in: LabelSet{"name": "garland, briggs", "fear": "love is not enough"},
out: 5799056148416392346,
},
}
for i, scenario := range scenarios {
actual := labelSetToFingerprint(scenario.in)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func TestMetricToFastFingerprint(t *testing.T) {
var scenarios = []struct {
in LabelSet
out Fingerprint
}{
{
in: LabelSet{},
out: 14695981039346656037,
},
{
in: LabelSet{"name": "garland, briggs", "fear": "love is not enough"},
out: 12952432476264840823,
},
}
for i, scenario := range scenarios {
actual := labelSetToFastFingerprint(scenario.in)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func TestSignatureForLabels(t *testing.T) {
var scenarios = []struct {
in Metric
labels LabelNames
out uint64
}{
{
in: Metric{},
labels: nil,
out: 14695981039346656037,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: LabelNames{"fear", "name"},
out: 5799056148416392346,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough", "foo": "bar"},
labels: LabelNames{"fear", "name"},
out: 5799056148416392346,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: LabelNames{},
out: 14695981039346656037,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: nil,
out: 14695981039346656037,
},
}
for i, scenario := range scenarios {
actual := SignatureForLabels(scenario.in, scenario.labels...)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func TestSignatureWithoutLabels(t *testing.T) {
var scenarios = []struct {
in Metric
labels map[LabelName]struct{}
out uint64
}{
{
in: Metric{},
labels: nil,
out: 14695981039346656037,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: map[LabelName]struct{}{"fear": struct{}{}, "name": struct{}{}},
out: 14695981039346656037,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough", "foo": "bar"},
labels: map[LabelName]struct{}{"foo": struct{}{}},
out: 5799056148416392346,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: map[LabelName]struct{}{},
out: 5799056148416392346,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: nil,
out: 5799056148416392346,
},
}
for i, scenario := range scenarios {
actual := SignatureWithoutLabels(scenario.in, scenario.labels)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func benchmarkLabelToSignature(b *testing.B, l map[string]string, e uint64) {
for i := 0; i < b.N; i++ {
if a := LabelsToSignature(l); a != e {
b.Fatalf("expected signature of %d for %s, got %d", e, l, a)
}
}
}
func BenchmarkLabelToSignatureScalar(b *testing.B) {
benchmarkLabelToSignature(b, nil, 14695981039346656037)
}
func BenchmarkLabelToSignatureSingle(b *testing.B) {
benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value"}, 5146282821936882169)
}
func BenchmarkLabelToSignatureDouble(b *testing.B) {
benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value"}, 3195800080984914717)
}
func BenchmarkLabelToSignatureTriple(b *testing.B) {
benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 13843036195897128121)
}
func benchmarkMetricToFingerprint(b *testing.B, ls LabelSet, e Fingerprint) {
for i := 0; i < b.N; i++ {
if a := labelSetToFingerprint(ls); a != e {
b.Fatalf("expected signature of %d for %s, got %d", e, ls, a)
}
}
}
func BenchmarkMetricToFingerprintScalar(b *testing.B) {
benchmarkMetricToFingerprint(b, nil, 14695981039346656037)
}
func BenchmarkMetricToFingerprintSingle(b *testing.B) {
benchmarkMetricToFingerprint(b, LabelSet{"first-label": "first-label-value"}, 5146282821936882169)
}
func BenchmarkMetricToFingerprintDouble(b *testing.B) {
benchmarkMetricToFingerprint(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value"}, 3195800080984914717)
}
func BenchmarkMetricToFingerprintTriple(b *testing.B) {
benchmarkMetricToFingerprint(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 13843036195897128121)
}
func benchmarkMetricToFastFingerprint(b *testing.B, ls LabelSet, e Fingerprint) {
for i := 0; i < b.N; i++ {
if a := labelSetToFastFingerprint(ls); a != e {
b.Fatalf("expected signature of %d for %s, got %d", e, ls, a)
}
}
}
func BenchmarkMetricToFastFingerprintScalar(b *testing.B) {
benchmarkMetricToFastFingerprint(b, nil, 14695981039346656037)
}
func BenchmarkMetricToFastFingerprintSingle(b *testing.B) {
benchmarkMetricToFastFingerprint(b, LabelSet{"first-label": "first-label-value"}, 5147259542624943964)
}
func BenchmarkMetricToFastFingerprintDouble(b *testing.B) {
benchmarkMetricToFastFingerprint(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value"}, 18269973311206963528)
}
func BenchmarkMetricToFastFingerprintTriple(b *testing.B) {
benchmarkMetricToFastFingerprint(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676)
}
func TestEmptyLabelSignature(t *testing.T) {
input := []map[string]string{nil, {}}
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
alloc := ms.Alloc
for _, labels := range input {
LabelsToSignature(labels)
}
runtime.ReadMemStats(&ms)
if got := ms.Alloc; alloc != got {
t.Fatal("expected LabelsToSignature with empty labels not to perform allocations")
}
}
func benchmarkMetricToFastFingerprintConc(b *testing.B, ls LabelSet, e Fingerprint, concLevel int) {
var start, end sync.WaitGroup
start.Add(1)
end.Add(concLevel)
for i := 0; i < concLevel; i++ {
go func() {
start.Wait()
for j := b.N / concLevel; j >= 0; j-- {
if a := labelSetToFastFingerprint(ls); a != e {
b.Fatalf("expected signature of %d for %s, got %d", e, ls, a)
}
}
end.Done()
}()
}
b.ResetTimer()
start.Done()
end.Wait()
}
func BenchmarkMetricToFastFingerprintTripleConc1(b *testing.B) {
benchmarkMetricToFastFingerprintConc(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 1)
}
func BenchmarkMetricToFastFingerprintTripleConc2(b *testing.B) {
benchmarkMetricToFastFingerprintConc(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 2)
}
func BenchmarkMetricToFastFingerprintTripleConc4(b *testing.B) {
benchmarkMetricToFastFingerprintConc(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 4)
}
func BenchmarkMetricToFastFingerprintTripleConc8(b *testing.B) {
benchmarkMetricToFastFingerprintConc(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 8)
}

View file

@ -0,0 +1,232 @@
// Copyright 2013 The Prometheus Authors
// 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"
"math"
"regexp"
"strconv"
"strings"
"time"
)
const (
// MinimumTick is the minimum supported time resolution. This has to be
// at least time.Second in order for the code below to work.
minimumTick = time.Millisecond
// second is the Time duration equivalent to one second.
second = int64(time.Second / minimumTick)
// The number of nanoseconds per minimum tick.
nanosPerTick = int64(minimumTick / time.Nanosecond)
// Earliest is the earliest Time representable. Handy for
// initializing a high watermark.
Earliest = Time(math.MinInt64)
// Latest is the latest Time representable. Handy for initializing
// a low watermark.
Latest = Time(math.MaxInt64)
)
// Time is the number of milliseconds since the epoch
// (1970-01-01 00:00 UTC) excluding leap seconds.
type Time int64
// Interval describes and interval between two timestamps.
type Interval struct {
Start, End Time
}
// Now returns the current time as a Time.
func Now() Time {
return TimeFromUnixNano(time.Now().UnixNano())
}
// TimeFromUnix returns the Time equivalent to the Unix Time t
// provided in seconds.
func TimeFromUnix(t int64) Time {
return Time(t * second)
}
// TimeFromUnixNano returns the Time equivalent to the Unix Time
// t provided in nanoseconds.
func TimeFromUnixNano(t int64) Time {
return Time(t / nanosPerTick)
}
// Equal reports whether two Times represent the same instant.
func (t Time) Equal(o Time) bool {
return t == o
}
// Before reports whether the Time t is before o.
func (t Time) Before(o Time) bool {
return t < o
}
// After reports whether the Time t is after o.
func (t Time) After(o Time) bool {
return t > o
}
// Add returns the Time t + d.
func (t Time) Add(d time.Duration) Time {
return t + Time(d/minimumTick)
}
// Sub returns the Duration t - o.
func (t Time) Sub(o Time) time.Duration {
return time.Duration(t-o) * minimumTick
}
// Time returns the time.Time representation of t.
func (t Time) Time() time.Time {
return time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick)
}
// Unix returns t as a Unix time, the number of seconds elapsed
// since January 1, 1970 UTC.
func (t Time) Unix() int64 {
return int64(t) / second
}
// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
// since January 1, 1970 UTC.
func (t Time) UnixNano() int64 {
return int64(t) * nanosPerTick
}
// The number of digits after the dot.
var dotPrecision = int(math.Log10(float64(second)))
// String returns a string representation of the Time.
func (t Time) String() string {
s := strconv.FormatInt(int64(t), 10)
i := len(s) - dotPrecision
return s[:i] + "." + s[i:]
}
// MarshalJSON implements the json.Marshaler interface.
func (t Time) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *Time) UnmarshalJSON(b []byte) error {
p := strings.Split(string(b), ".")
switch len(p) {
case 1:
v, err := strconv.ParseInt(string(p[0]), 10, 64)
if err != nil {
return err
}
*t = Time(v * second)
case 2:
v, err := strconv.ParseInt(string(p[0]), 10, 64)
if err != nil {
return err
}
v *= second
prec := dotPrecision - len(p[1])
if prec < 0 {
p[1] = p[1][:dotPrecision]
} else if prec > 0 {
p[1] = p[1] + strings.Repeat("0", prec)
}
va, err := strconv.ParseInt(p[1], 10, 32)
if err != nil {
return err
}
*t = Time(v + va)
default:
return fmt.Errorf("invalid time %q", string(b))
}
return nil
}
// Duration wraps time.Duration. It is used to parse the custom duration format
// from YAML.
// This type should not propagate beyond the scope of input/output processing.
type Duration time.Duration
// StringToDuration parses a string into a time.Duration, assuming that a year
// a day always has 24h.
func ParseDuration(durationStr string) (Duration, error) {
matches := durationRE.FindStringSubmatch(durationStr)
if len(matches) != 3 {
return 0, fmt.Errorf("not a valid duration string: %q", durationStr)
}
durSeconds, _ := strconv.Atoi(matches[1])
dur := time.Duration(durSeconds) * time.Second
unit := matches[2]
switch unit {
case "d":
dur *= 60 * 60 * 24
case "h":
dur *= 60 * 60
case "m":
dur *= 60
case "s":
dur *= 1
default:
return 0, fmt.Errorf("invalid time unit in duration string: %q", unit)
}
return Duration(dur), nil
}
var durationRE = regexp.MustCompile("^([0-9]+)([ywdhms]+)$")
func (d Duration) String() string {
seconds := int64(time.Duration(d) / time.Second)
factors := map[string]int64{
"d": 60 * 60 * 24,
"h": 60 * 60,
"m": 60,
"s": 1,
}
unit := "s"
switch int64(0) {
case seconds % factors["d"]:
unit = "d"
case seconds % factors["h"]:
unit = "h"
case seconds % factors["m"]:
unit = "m"
}
return fmt.Sprintf("%v%v", seconds/factors[unit], unit)
}
// MarshalYAML implements the yaml.Marshaler interface.
func (d Duration) MarshalYAML() (interface{}, error) {
return d.String(), nil
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
dur, err := ParseDuration(s)
if err != nil {
return err
}
*d = dur
return nil
}

View file

@ -0,0 +1,86 @@
// Copyright 2013 The Prometheus Authors
// 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"
"time"
)
func TestComparators(t *testing.T) {
t1a := TimeFromUnix(0)
t1b := TimeFromUnix(0)
t2 := TimeFromUnix(2*second - 1)
if !t1a.Equal(t1b) {
t.Fatalf("Expected %s to be equal to %s", t1a, t1b)
}
if t1a.Equal(t2) {
t.Fatalf("Expected %s to not be equal to %s", t1a, t2)
}
if !t1a.Before(t2) {
t.Fatalf("Expected %s to be before %s", t1a, t2)
}
if t1a.Before(t1b) {
t.Fatalf("Expected %s to not be before %s", t1a, t1b)
}
if !t2.After(t1a) {
t.Fatalf("Expected %s to be after %s", t2, t1a)
}
if t1b.After(t1a) {
t.Fatalf("Expected %s to not be after %s", t1b, t1a)
}
}
func TestTimeConversions(t *testing.T) {
unixSecs := int64(1136239445)
unixNsecs := int64(123456789)
unixNano := unixSecs*1e9 + unixNsecs
t1 := time.Unix(unixSecs, unixNsecs-unixNsecs%nanosPerTick)
t2 := time.Unix(unixSecs, unixNsecs)
ts := TimeFromUnixNano(unixNano)
if !ts.Time().Equal(t1) {
t.Fatalf("Expected %s, got %s", t1, ts.Time())
}
// Test available precision.
ts = TimeFromUnixNano(t2.UnixNano())
if !ts.Time().Equal(t1) {
t.Fatalf("Expected %s, got %s", t1, ts.Time())
}
if ts.UnixNano() != unixNano-unixNano%nanosPerTick {
t.Fatalf("Expected %d, got %d", unixNano, ts.UnixNano())
}
}
func TestDuration(t *testing.T) {
duration := time.Second + time.Minute + time.Hour
goTime := time.Unix(1136239445, 0)
ts := TimeFromUnix(goTime.Unix())
if !goTime.Add(duration).Equal(ts.Add(duration).Time()) {
t.Fatalf("Expected %s to be equal to %s", goTime.Add(duration), ts.Add(duration))
}
earlier := ts.Add(-duration)
delta := ts.Sub(earlier)
if delta != duration {
t.Fatalf("Expected %s to be equal to %s", delta, duration)
}
}

View file

@ -0,0 +1,326 @@
// Copyright 2013 The Prometheus Authors
// 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/json"
"fmt"
"sort"
"strconv"
"strings"
)
// A SampleValue is a representation of a value for a given sample at a given
// time.
type SampleValue float64
// MarshalJSON implements json.Marshaler.
func (v SampleValue) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
// UnmarshalJSON implements json.Unmarshaler.
func (v *SampleValue) UnmarshalJSON(b []byte) error {
if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
return fmt.Errorf("sample value must be a quoted string")
}
f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64)
if err != nil {
return err
}
*v = SampleValue(f)
return nil
}
func (v SampleValue) Equal(o SampleValue) bool {
return v == o
}
func (v SampleValue) String() string {
return strconv.FormatFloat(float64(v), 'f', -1, 64)
}
// SamplePair pairs a SampleValue with a Timestamp.
type SamplePair struct {
Timestamp Time
Value SampleValue
}
// MarshalJSON implements json.Marshaler.
func (s SamplePair) MarshalJSON() ([]byte, error) {
t, err := json.Marshal(s.Timestamp)
if err != nil {
return nil, err
}
v, err := json.Marshal(s.Value)
if err != nil {
return nil, err
}
return json.Marshal([...]interface{}{t, v})
}
// UnmarshalJSON implements json.Unmarshaler.
func (s *SamplePair) UnmarshalJSON(b []byte) error {
if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
return fmt.Errorf("sample pair must be array")
}
b = b[1 : len(b)-1]
return json.Unmarshal(b, [...]json.Unmarshaler{&s.Timestamp, &s.Value})
}
// Equal returns true if this SamplePair and o have equal Values and equal
// Timestamps.
func (s *SamplePair) Equal(o *SamplePair) bool {
return s == o || (s.Value == o.Value && s.Timestamp.Equal(o.Timestamp))
}
func (s *SamplePair) String() string {
return fmt.Sprintf("%s @[%s]", s.Value, s.Timestamp)
}
// Sample is a sample pair associated with a metric.
type Sample struct {
Metric Metric
Value SampleValue
Timestamp Time
}
// Equal compares first the metrics, then the timestamp, then the value.
func (s *Sample) Equal(o *Sample) bool {
if s == o {
return true
}
if !s.Metric.Equal(o.Metric) {
return false
}
if !s.Timestamp.Equal(o.Timestamp) {
return false
}
if s.Value != o.Value {
return false
}
return true
}
func (s *Sample) String() string {
return fmt.Sprintf("%s => %s", s.Metric, SamplePair{
Timestamp: s.Timestamp,
Value: s.Value,
})
}
// Samples is a sortable Sample slice. It implements sort.Interface.
type Samples []*Sample
func (s Samples) Len() int {
return len(s)
}
// Less compares first the metrics, then the timestamp.
func (s Samples) Less(i, j int) bool {
switch {
case s[i].Metric.Before(s[j].Metric):
return true
case s[j].Metric.Before(s[i].Metric):
return false
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]
}
// Equal compares two sets of samples and returns true if they are equal.
func (s Samples) Equal(o Samples) bool {
if len(s) != len(o) {
return false
}
for i, sample := range s {
if !sample.Equal(o[i]) {
return false
}
}
return true
}
// SampleStream is a stream of Values belonging to an attached COWMetric.
type SampleStream struct {
Metric Metric `json:"metric"`
Values []SamplePair `json:"values"`
}
func (ss *SampleStream) String() string {
vals := make([]string, len(ss.Values))
for i, v := range ss.Values {
vals[i] = v.String()
}
return fmt.Sprintf("%s =>\n%s", ss.Metric, strings.Join(vals, "\n"))
}
// Value is a generic interface for values resulting from a query evaluation.
type Value interface {
Type() ValueType
String() string
}
func (Matrix) Type() ValueType { return ValMatrix }
func (Vector) Type() ValueType { return ValVector }
func (*Scalar) Type() ValueType { return ValScalar }
func (*String) Type() ValueType { return ValString }
type ValueType int
const (
ValNone ValueType = iota
ValScalar
ValVector
ValMatrix
ValString
)
// MarshalJSON implements json.Marshaler.
func (et ValueType) MarshalJSON() ([]byte, error) {
return json.Marshal(et.String())
}
func (et *ValueType) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
switch s {
case "<ValNone>":
*et = ValNone
case "scalar":
*et = ValScalar
case "vector":
*et = ValVector
case "matrix":
*et = ValMatrix
case "string":
*et = ValString
default:
return fmt.Errorf("unknown value type %q", s)
}
return nil
}
func (e ValueType) String() string {
switch e {
case ValNone:
return "<ValNone>"
case ValScalar:
return "scalar"
case ValVector:
return "vector"
case ValMatrix:
return "matrix"
case ValString:
return "string"
}
panic("ValueType.String: unhandled value type")
}
// Scalar is a scalar value evaluated at the set timestamp.
type Scalar struct {
Value SampleValue `json:"value"`
Timestamp Time `json:"timestamp"`
}
func (s *Scalar) String() string {
return fmt.Sprintf("scalar: %v @[%v]", s.Value, s.Timestamp)
}
// String is a string value evaluated at the set timestamp.
type String struct {
Value string `json:"value"`
Timestamp Time `json:"timestamp"`
}
func (s *String) String() string {
return s.Value
}
// Vector is basically only an alias for Samples, but the
// contract is that in a Vector, all Samples have the same timestamp.
type Vector []*Sample
func (vec Vector) String() string {
entries := make([]string, len(vec))
for i, s := range vec {
entries[i] = s.String()
}
return strings.Join(entries, "\n")
}
func (vec Vector) Len() int { return len(vec) }
func (vec Vector) Swap(i, j int) { vec[i], vec[j] = vec[j], vec[i] }
// Less compares first the metrics, then the timestamp.
func (vec Vector) Less(i, j int) bool {
switch {
case vec[i].Metric.Before(vec[j].Metric):
return true
case vec[j].Metric.Before(vec[i].Metric):
return false
case vec[i].Timestamp.Before(vec[j].Timestamp):
return true
default:
return false
}
}
// Equal compares two sets of samples and returns true if they are equal.
func (vec Vector) Equal(o Vector) bool {
if len(vec) != len(o) {
return false
}
for i, sample := range vec {
if !sample.Equal(o[i]) {
return false
}
}
return true
}
// Matrix is a list of time series.
type Matrix []*SampleStream
func (m Matrix) Len() int { return len(m) }
func (m Matrix) Less(i, j int) bool { return m[i].Metric.Before(m[j].Metric) }
func (m Matrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (mat Matrix) String() string {
matCp := make(Matrix, len(mat))
copy(matCp, mat)
sort.Sort(matCp)
strs := make([]string, len(matCp))
for i, ss := range matCp {
strs[i] = ss.String()
}
return strings.Join(strs, "\n")
}

View file

@ -0,0 +1,114 @@
// Copyright 2013 The Prometheus Authors
// 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"
)
func TestVectorSort(t *testing.T) {
input := Vector{
&Sample{
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 2,
},
&Sample{
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 2,
},
&Sample{
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 2,
},
}
expected := Vector{
&Sample{
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 2,
},
&Sample{
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 2,
},
&Sample{
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 2,
},
}
sort.Sort(input)
for i, actual := range input {
actualFp := actual.Metric.Fingerprint()
expectedFp := expected[i].Metric.Fingerprint()
if actualFp != expectedFp {
t.Fatalf("%d. Incorrect fingerprint. Got %s; want %s", i, actualFp.String(), expectedFp.String())
}
if actual.Timestamp != expected[i].Timestamp {
t.Fatalf("%d. Incorrect timestamp. Got %s; want %s", i, actual.Timestamp, expected[i].Timestamp)
}
}
}