Move COWMetric into storage/metric package

This commit is contained in:
Fabian Reinartz 2015-08-24 18:04:26 +02:00
parent ad8e8f9f24
commit e061595352
12 changed files with 488 additions and 126 deletions

View File

@ -24,23 +24,23 @@ import (
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_"
ExportedLabelPrefix = "exported_"
// MetricNameLabel is the label name indicating the metric name of a
// timeseries.
MetricNameLabel LabelName = "__name__"
MetricNameLabel = "__name__"
// SchemeLabel is the name of the label that holds the scheme on which to
// scrape a target.
SchemeLabel LabelName = "__scheme__"
SchemeLabel = "__scheme__"
// AddressLabel is the name of the label that holds the address of
// a scrape target.
AddressLabel LabelName = "__address__"
AddressLabel = "__address__"
// MetricsPathLabel is the name of the label that holds the path on which to
// scrape a target.
MetricsPathLabel LabelName = "__metrics_path__"
MetricsPathLabel = "__metrics_path__"
// ReservedLabelPrefix is a prefix which is not legal in user-supplied
// label names.
@ -63,10 +63,10 @@ const (
// JobLabel is the label name indicating the job from which a timeseries
// was scraped.
JobLabel LabelName = "job"
JobLabel = "job"
// InstanceLabel is the label name used for the instance label.
InstanceLabel LabelName = "instance"
InstanceLabel = "instance"
// BucketLabel is used for the label that defines the upper bound of a
// bucket of a histogram ("le" -> "less or equal").

View File

@ -14,7 +14,6 @@
package model
import (
"encoding/json"
"fmt"
"sort"
"strings"
@ -80,41 +79,3 @@ func (m Metric) Fingerprint() Fingerprint {
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

@ -81,52 +81,3 @@ func BenchmarkMetric(b *testing.B) {
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

@ -247,7 +247,7 @@ 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) {
func BenchmarkEmptyLabelSignature(b *testing.B) {
input := []map[string]string{nil, {}}
var ms runtime.MemStats
@ -262,7 +262,7 @@ func TestEmptyLabelSignature(t *testing.T) {
runtime.ReadMemStats(&ms)
if got := ms.Alloc; alloc != got {
t.Fatal("expected LabelsToSignature with empty labels not to perform allocations")
b.Fatal("expected LabelsToSignature with empty labels not to perform allocations")
}
}

View File

@ -112,9 +112,7 @@ 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:]
return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64)
}
// MarshalJSON implements the json.Marshaler interface.

View File

@ -67,18 +67,13 @@ func (s SamplePair) MarshalJSON() ([]byte, error) {
if err != nil {
return nil, err
}
return json.Marshal([...]interface{}{t, v})
return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil
}
// 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})
v := [...]json.Unmarshaler{&s.Timestamp, &s.Value}
return json.Unmarshal(b, &v)
}
// Equal returns true if this SamplePair and o have equal Values and equal
@ -87,15 +82,15 @@ func (s *SamplePair) Equal(o *SamplePair) bool {
return s == o || (s.Value == o.Value && s.Timestamp.Equal(o.Timestamp))
}
func (s *SamplePair) String() string {
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
Metric Metric `json:"metric"`
Value SampleValue `json:"value"`
Timestamp Time `json:"timestamp"`
}
// Equal compares first the metrics, then the timestamp, then the value.
@ -117,13 +112,53 @@ func (s *Sample) Equal(o *Sample) bool {
return true
}
func (s *Sample) String() string {
func (s Sample) String() string {
return fmt.Sprintf("%s => %s", s.Metric, SamplePair{
Timestamp: s.Timestamp,
Value: s.Value,
})
}
// MarshalJSON implements json.Marshaler.
func (s Sample) MarshalJSON() ([]byte, error) {
v := struct {
Metric Metric `json:"metric"`
Value SamplePair `json:"value"`
}{
Metric: s.Metric,
Value: SamplePair{
Timestamp: s.Timestamp,
Value: s.Value,
},
}
return json.Marshal(&v)
}
// UnmarshalJSON implements json.Unmarshaler.
func (s *Sample) UnmarshalJSON(b []byte) error {
v := struct {
Metric Metric `json:"metric"`
Value SamplePair `json:"value"`
}{
Metric: s.Metric,
Value: SamplePair{
Timestamp: s.Timestamp,
Value: s.Value,
},
}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
s.Metric = v.Metric
s.Timestamp = v.Value.Timestamp
s.Value = v.Value.Value
return nil
}
// Samples is a sortable Sample slice. It implements sort.Interface.
type Samples []*Sample
@ -169,7 +204,7 @@ type SampleStream struct {
Values []SamplePair `json:"values"`
}
func (ss *SampleStream) String() string {
func (ss SampleStream) String() string {
vals := make([]string, len(ss.Values))
for i, v := range ss.Values {
vals[i] = v.String()
@ -247,10 +282,33 @@ type Scalar struct {
Timestamp Time
}
func (s *Scalar) String() string {
func (s Scalar) String() string {
return fmt.Sprintf("scalar: %v @[%v]", s.Value, s.Timestamp)
}
// MarshalJSON implements json.Marshaler.
func (s Scalar) MarshalJSON() ([]byte, error) {
v := strconv.FormatFloat(float64(s.Value), 'f', -1, 64)
return json.Marshal([...]interface{}{s.Timestamp, string(v)})
}
// UnmarshalJSON implements json.Unmarshaler.
func (s *Scalar) UnmarshalJSON(b []byte) error {
var f string
v := [...]interface{}{&s.Timestamp, &f}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
value, err := strconv.ParseFloat(f, 64)
if err != nil {
return fmt.Errorf("error parsing sample value: %s", err)
}
s.Value = SampleValue(value)
return nil
}
// String is a string value evaluated at the set timestamp.
type String struct {
Value string
@ -261,6 +319,17 @@ func (s *String) String() string {
return s.Value
}
// MarshalJSON implements json.Marshaler.
func (s String) MarshalJSON() ([]byte, error) {
return json.Marshal([]interface{}{s.Timestamp, s.Value})
}
// UnmarshalJSON implements json.Unmarshaler.
func (s *String) UnmarshalJSON(b []byte) error {
v := [...]interface{}{&s.Timestamp, &s.Value}
return json.Unmarshal(b, &v)
}
// 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

View File

@ -14,10 +14,258 @@
package model
import (
"encoding/json"
"math"
"reflect"
"sort"
"testing"
)
func TestSamplePairJSON(t *testing.T) {
input := []struct {
plain string
value SamplePair
}{
{
plain: `[1234.567,"123.1"]`,
value: SamplePair{
Value: 123.1,
Timestamp: 1234567,
},
},
}
for _, test := range input {
b, err := json.Marshal(test.value)
if err != nil {
t.Error(err)
continue
}
if string(b) != test.plain {
t.Errorf("encoding error: expected %q, got %q", test.plain, b)
continue
}
var sp SamplePair
err = json.Unmarshal(b, &sp)
if err != nil {
t.Error(err)
continue
}
if sp != test.value {
t.Errorf("decoding error: expected %v, got %v", test.value, sp)
}
}
}
func TestSampleJSON(t *testing.T) {
input := []struct {
plain string
value Sample
}{
{
plain: `{"metric":{"__name__":"test_metric"},"value":[1234.567,"123.1"]}`,
value: Sample{
Metric: Metric{
MetricNameLabel: "test_metric",
},
Value: 123.1,
Timestamp: 1234567,
},
},
}
for _, test := range input {
b, err := json.Marshal(test.value)
if err != nil {
t.Error(err)
continue
}
if string(b) != test.plain {
t.Errorf("encoding error: expected %q, got %q", test.plain, b)
continue
}
var sv Sample
err = json.Unmarshal(b, &sv)
if err != nil {
t.Error(err)
continue
}
if !reflect.DeepEqual(sv, test.value) {
t.Errorf("decoding error: expected %v, got %v", test.value, sv)
}
}
}
func TestVectorJSON(t *testing.T) {
input := []struct {
plain string
value Vector
}{
{
plain: `[]`,
value: Vector{},
},
{
plain: `[{"metric":{"__name__":"test_metric"},"value":[1234.567,"123.1"]}]`,
value: Vector{&Sample{
Metric: Metric{
MetricNameLabel: "test_metric",
},
Value: 123.1,
Timestamp: 1234567,
}},
},
{
plain: `[{"metric":{"__name__":"test_metric"},"value":[1234.567,"123.1"]},{"metric":{"foo":"bar"},"value":[1.234,"+Inf"]}]`,
value: Vector{
&Sample{
Metric: Metric{
MetricNameLabel: "test_metric",
},
Value: 123.1,
Timestamp: 1234567,
},
&Sample{
Metric: Metric{
"foo": "bar",
},
Value: SampleValue(math.Inf(1)),
Timestamp: 1234,
},
},
},
}
for _, test := range input {
b, err := json.Marshal(test.value)
if err != nil {
t.Error(err)
continue
}
if string(b) != test.plain {
t.Errorf("encoding error: expected %q, got %q", test.plain, b)
continue
}
var vec Vector
err = json.Unmarshal(b, &vec)
if err != nil {
t.Error(err)
continue
}
if !reflect.DeepEqual(vec, test.value) {
t.Errorf("decoding error: expected %v, got %v", test.value, vec)
}
}
}
func TestScalarJSON(t *testing.T) {
input := []struct {
plain string
value Scalar
}{
{
plain: `[123.456,"456"]`,
value: Scalar{
Timestamp: 123456,
Value: 456,
},
},
{
plain: `[123123.456,"+Inf"]`,
value: Scalar{
Timestamp: 123123456,
Value: SampleValue(math.Inf(1)),
},
},
{
plain: `[123123.456,"-Inf"]`,
value: Scalar{
Timestamp: 123123456,
Value: SampleValue(math.Inf(-1)),
},
},
}
for _, test := range input {
b, err := json.Marshal(test.value)
if err != nil {
t.Error(err)
continue
}
if string(b) != test.plain {
t.Errorf("encoding error: expected %q, got %q", test.plain, b)
continue
}
var sv Scalar
err = json.Unmarshal(b, &sv)
if err != nil {
t.Error(err)
continue
}
if sv != test.value {
t.Errorf("decoding error: expected %v, got %v", test.value, sv)
}
}
}
func TestStringJSON(t *testing.T) {
input := []struct {
plain string
value String
}{
{
plain: `[123.456,"test"]`,
value: String{
Timestamp: 123456,
Value: "test",
},
},
{
plain: `[123123.456,"台北"]`,
value: String{
Timestamp: 123123456,
Value: "台北",
},
},
}
for _, test := range input {
b, err := json.Marshal(test.value)
if err != nil {
t.Error(err)
continue
}
if string(b) != test.plain {
t.Errorf("encoding error: expected %q, got %q", test.plain, b)
continue
}
var sv String
err = json.Unmarshal(b, &sv)
if err != nil {
t.Error(err)
continue
}
if sv != test.value {
t.Errorf("decoding error: expected %v, got %v", test.value, sv)
}
}
}
func TestVectorSort(t *testing.T) {
input := Vector{
&Sample{

View File

@ -40,7 +40,7 @@ type Storage interface {
// MetricsForLabelMatchers returns the metrics from storage that satisfy the given
// label matchers. At least one label matcher must be specified that does not
// match the empty string.
MetricsForLabelMatchers(...*metric.LabelMatcher) map[model.Fingerprint]model.COWMetric
MetricsForLabelMatchers(...*metric.LabelMatcher) map[model.Fingerprint]metric.Metric
// LastSamplePairForFingerprint returns the last sample pair for the
// provided fingerprint. If the respective time series does not exist or
// has an evicted head chunk, nil is returned.
@ -48,7 +48,7 @@ type Storage interface {
// Get all of the label values that are associated with a given label name.
LabelValuesForLabelName(model.LabelName) model.LabelValues
// Get the metric associated with the provided fingerprint.
MetricForFingerprint(model.Fingerprint) model.COWMetric
MetricForFingerprint(model.Fingerprint) metric.Metric
// Construct an iterator for a given fingerprint.
// The iterator will never return samples older than retention time,
// relative to the time NewIterator was called.

View File

@ -410,7 +410,7 @@ func (s *memorySeriesStorage) fingerprintsForLabelPairs(pairs ...model.LabelPair
}
// MetricsForLabelMatchers implements Storage.
func (s *memorySeriesStorage) MetricsForLabelMatchers(matchers ...*metric.LabelMatcher) map[model.Fingerprint]model.COWMetric {
func (s *memorySeriesStorage) MetricsForLabelMatchers(matchers ...*metric.LabelMatcher) map[model.Fingerprint]metric.Metric {
var (
equals []model.LabelPair
filters []*metric.LabelMatcher
@ -462,7 +462,7 @@ func (s *memorySeriesStorage) MetricsForLabelMatchers(matchers ...*metric.LabelM
filters = remaining
}
result := make(map[model.Fingerprint]model.COWMetric, len(resFPs))
result := make(map[model.Fingerprint]metric.Metric, len(resFPs))
for fp := range resFPs {
result[fp] = s.MetricForFingerprint(fp)
}
@ -486,7 +486,7 @@ func (s *memorySeriesStorage) LabelValuesForLabelName(labelName model.LabelName)
}
// MetricForFingerprint implements Storage.
func (s *memorySeriesStorage) MetricForFingerprint(fp model.Fingerprint) model.COWMetric {
func (s *memorySeriesStorage) MetricForFingerprint(fp model.Fingerprint) metric.Metric {
s.fpLocker.Lock(fp)
defer s.fpLocker.Unlock(fp)
@ -494,16 +494,18 @@ func (s *memorySeriesStorage) MetricForFingerprint(fp model.Fingerprint) model.C
if ok {
// Wrap the returned metric in a copy-on-write (COW) metric here because
// the caller might mutate it.
return model.COWMetric{
return metric.Metric{
Metric: series.metric,
}
}
metric, err := s.persistence.archivedMetric(fp)
met, err := s.persistence.archivedMetric(fp)
if err != nil {
log.Errorf("Error retrieving archived metric for fingerprint %v: %v", fp, err)
}
return model.COWMetric{
Metric: metric,
return metric.Metric{
Metric: met,
Copied: false,
}
}

View File

@ -276,7 +276,7 @@ func TestFingerprintsForLabels(t *testing.T) {
}
}
var benchLabelMatchingRes map[model.Fingerprint]model.COWMetric
var benchLabelMatchingRes map[model.Fingerprint]metric.Metric
func BenchmarkLabelMatching(b *testing.B) {
s, closer := NewTestStorage(b, 1)
@ -359,7 +359,7 @@ func BenchmarkLabelMatching(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchLabelMatchingRes = map[model.Fingerprint]model.COWMetric{}
benchLabelMatchingRes = map[model.Fingerprint]metric.Metric{}
for _, mt := range matcherTests {
benchLabelMatchingRes = s.MetricsForLabelMatchers(mt...)
}

63
storage/metric/metric.go Normal file
View File

@ -0,0 +1,63 @@
// 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 metric
import "github.com/prometheus/common/model"
// Metric wraps a model.Metric and copies it upon modification if Copied is false.
type Metric struct {
Copied bool
Metric model.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 *Metric) Set(ln model.LabelName, lv model.LabelValue) {
m.Copy()
m.Metric[ln] = lv
}
// Del deletes a given label name from the wrapped Metric and copies the
// Metric initially, if it is not already a copy.
func (m *Metric) Del(ln model.LabelName) {
m.Copy()
delete(m.Metric, ln)
}
// Get the value for the given label name. An empty value is returned
// if the label does not exist in the metric.
func (m *Metric) Get(ln model.LabelName) model.LabelValue {
return m.Metric[ln]
}
// Gets behaves as Get but the returned boolean is false iff the label
// does not exist.
func (m *Metric) Gets(ln model.LabelName) (model.LabelValue, bool) {
lv, ok := m.Metric[ln]
return lv, ok
}
// Copy the underlying Metric if it is not already a copy.
func (m *Metric) Copy() *Metric {
if !m.Copied {
m.Metric = m.Metric.Clone()
m.Copied = true
}
return m
}
// String implements fmt.Stringer.
func (m Metric) String() string {
return m.Metric.String()
}

View File

@ -0,0 +1,70 @@
// 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 metric
import (
"testing"
"github.com/prometheus/common/model"
)
func TestMetric(t *testing.T) {
testMetric := model.Metric{
"to_delete": "test1",
"to_change": "test2",
}
scenarios := []struct {
fn func(*Metric)
out Metric
}{
{
fn: func(cm *etric) {
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 := &Metric{
Metric: orig,
Copied: false,
}
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)
}
}
}