blob: 09286c69fc45c9ec69bcaf579b3c1c675587de28 [file] [log] [blame]
// Copyright 2015 The LUCI 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 storetest is imported exclusively by tests for Store implementations.
package storetest
import (
"context"
"fmt"
"sort"
"sync"
"testing"
"time"
"go.chromium.org/luci/common/clock/testclock"
"go.chromium.org/luci/common/tsmon/distribution"
"go.chromium.org/luci/common/tsmon/field"
"go.chromium.org/luci/common/tsmon/monitor"
"go.chromium.org/luci/common/tsmon/target"
"go.chromium.org/luci/common/tsmon/types"
. "github.com/smartystreets/goconvey/convey"
pb "go.chromium.org/luci/common/tsmon/ts_mon_proto"
)
// Store is a store under test.
//
// It is a copy of store.Store interface to break module dependency cycle.
type Store interface {
DefaultTarget() types.Target
SetDefaultTarget(t types.Target)
Get(c context.Context, m types.Metric, resetTime time.Time, fieldVals []interface{}) interface{}
Set(c context.Context, m types.Metric, resetTime time.Time, fieldVals []interface{}, value interface{})
Del(c context.Context, m types.Metric, fieldVals []interface{})
Incr(c context.Context, m types.Metric, resetTime time.Time, fieldVals []interface{}, delta interface{})
GetAll(c context.Context) []types.Cell
Reset(c context.Context, m types.Metric)
}
// TestOptions contains options for RunStoreImplementationTests.
type TestOptions struct {
// Factory creates and returns a new Store implementation.
Factory func() Store
// RegistrationFinished is called after all metrics have been registered.
// Store implementations that need to do expensive initialization (that would
// otherwise be done in iface.go) can do that here.
RegistrationFinished func(Store)
// GetNumRegisteredMetrics returns the number of metrics registered in the
// store.
GetNumRegisteredMetrics func(Store) int
}
// RunStoreImplementationTests runs all the standard tests that all store
// implementations are expected to pass. When you write a new Store
// implementation you should ensure you run these tests against it.
func RunStoreImplementationTests(t *testing.T, ctx context.Context, opts TestOptions) {
distOne := distribution.New(distribution.DefaultBucketer)
distOne.Add(4.2)
distTwo := distribution.New(distribution.DefaultBucketer)
distTwo.Add(5.6)
distTwo.Add(1.0)
tests := []struct {
typ types.ValueType
bucketer *distribution.Bucketer
values []interface{}
wantSetValidator func(interface{}, interface{})
deltas []interface{}
wantIncrPanic bool
wantIncrValue interface{}
wantIncrValidator func(interface{})
wantStartTimestamp bool
}{
{
typ: types.CumulativeIntType,
values: makeInterfaceSlice(int64(3), int64(4)),
deltas: makeInterfaceSlice(int64(3), int64(4)),
wantIncrValue: int64(7),
wantStartTimestamp: true,
},
{
typ: types.CumulativeFloatType,
values: makeInterfaceSlice(float64(3.2), float64(4.3)),
deltas: makeInterfaceSlice(float64(3.2), float64(4.3)),
wantIncrValue: float64(7.5),
wantStartTimestamp: true,
},
{
typ: types.CumulativeDistributionType,
bucketer: distribution.DefaultBucketer,
values: makeInterfaceSlice(distOne, distTwo),
deltas: makeInterfaceSlice(float64(3.2), float64(5.3)),
wantIncrValidator: func(v interface{}) {
d := v.(*distribution.Distribution)
So(d.Buckets(), ShouldResemble, []int64{0, 0, 0, 1, 1})
},
wantStartTimestamp: true,
},
{
typ: types.NonCumulativeIntType,
values: makeInterfaceSlice(int64(3), int64(4)),
deltas: makeInterfaceSlice(int64(3), int64(4)),
wantIncrPanic: true,
},
{
typ: types.NonCumulativeFloatType,
values: makeInterfaceSlice(float64(3.2), float64(4.3)),
deltas: makeInterfaceSlice(float64(3.2), float64(4.3)),
wantIncrPanic: true,
},
{
typ: types.NonCumulativeDistributionType,
bucketer: distribution.DefaultBucketer,
values: makeInterfaceSlice(distOne, distTwo),
deltas: makeInterfaceSlice(float64(3.2), float64(5.3)),
wantIncrPanic: true,
wantSetValidator: func(got, want interface{}) {
// The distribution might be serialized/deserialized and lose sums and
// counts.
So(got.(*distribution.Distribution).Buckets(), ShouldResemble,
want.(*distribution.Distribution).Buckets())
},
},
{
typ: types.StringType,
values: makeInterfaceSlice("hello", "world"),
deltas: makeInterfaceSlice("hello", "world"),
wantIncrPanic: true,
},
{
typ: types.BoolType,
values: makeInterfaceSlice(true, false),
deltas: makeInterfaceSlice(true, false),
wantIncrPanic: true,
},
}
Convey("Set and get", t, func() {
Convey("With no fields", func() {
for i, test := range tests {
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{},
}
}
s := opts.Factory()
// Value should be nil initially.
v := s.Get(ctx, m, time.Time{}, []interface{}{})
So(v, ShouldBeNil)
// Set and get the value.
s.Set(ctx, m, time.Time{}, []interface{}{}, test.values[0])
v = s.Get(ctx, m, time.Time{}, []interface{}{})
if test.wantSetValidator != nil {
test.wantSetValidator(v, test.values[0])
} else {
So(v, ShouldEqual, test.values[0])
}
})
}
})
Convey("With fields", func() {
for i, test := range tests {
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
// Values should be nil initially.
v := s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one"))
So(v, ShouldBeNil)
v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two"))
So(v, ShouldBeNil)
// Set and get the values.
s.Set(ctx, m, time.Time{}, makeInterfaceSlice("one"), test.values[0])
s.Set(ctx, m, time.Time{}, makeInterfaceSlice("two"), test.values[1])
v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one"))
if test.wantSetValidator != nil {
test.wantSetValidator(v, test.values[0])
} else {
So(v, ShouldEqual, test.values[0])
}
v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two"))
if test.wantSetValidator != nil {
test.wantSetValidator(v, test.values[1])
} else {
So(v, ShouldEqual, test.values[1])
}
})
}
})
Convey("With a fixed reset time", func() {
for i, test := range tests {
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
opts.RegistrationFinished(s)
// Do the set with a fixed time.
t := time.Date(1972, 5, 6, 7, 8, 9, 0, time.UTC)
s.Set(ctx, m, t, []interface{}{}, test.values[0])
v := s.Get(ctx, m, time.Time{}, []interface{}{})
if test.wantSetValidator != nil {
test.wantSetValidator(v, test.values[0])
} else {
So(v, ShouldEqual, test.values[0])
}
// Check the time in the Cell is the same.
all := s.GetAll(ctx)
So(len(all), ShouldEqual, 1)
msg := monitor.SerializeValue(all[0], testclock.TestRecentTimeUTC)
ts := msg.GetStartTimestamp()
if test.wantStartTimestamp {
So(time.Unix(ts.GetSeconds(), int64(ts.GetNanos())).UTC().String(), ShouldEqual, t.String())
} else {
So(time.Unix(ts.GetSeconds(), int64(ts.GetNanos())).UTC().String(), ShouldEqual, testclock.TestRecentTimeUTC.String())
}
})
}
})
Convey("With a target set in the context", func() {
for i, test := range tests {
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
opts.RegistrationFinished(s)
// Create a context with a different target.
foo := target.Task{ServiceName: "foo"}
ctxWithTarget := target.Set(ctx, &foo)
// Set the first value on the default target, second value on the
// different target.
s.Set(ctx, m, time.Time{}, []interface{}{}, test.values[0])
s.Set(ctxWithTarget, m, time.Time{}, []interface{}{}, test.values[1])
// Get should return different values for different contexts.
v := s.Get(ctx, m, time.Time{}, []interface{}{})
if test.wantSetValidator != nil {
test.wantSetValidator(v, test.values[0])
} else {
So(v, ShouldEqual, test.values[0])
}
v = s.Get(ctxWithTarget, m, time.Time{}, []interface{}{})
if test.wantSetValidator != nil {
test.wantSetValidator(v, test.values[1])
} else {
So(v, ShouldEqual, test.values[1])
}
// The targets should be set in the Cells.
all := s.GetAll(ctx)
So(len(all), ShouldEqual, 2)
coll := monitor.SerializeCells(all, testclock.TestRecentTimeUTC)
s0 := coll[0].TargetSchema.(*pb.MetricsCollection_Task).Task.GetServiceName()
s1 := coll[1].TargetSchema.(*pb.MetricsCollection_Task).Task.GetServiceName()
switch {
case s0 == foo.ServiceName:
So(s1, ShouldEqual, s.DefaultTarget().(*target.Task).ServiceName)
case s1 == foo.ServiceName:
So(s0, ShouldEqual, s.DefaultTarget().(*target.Task).ServiceName)
default:
t.Fail()
}
})
}
})
Convey("With a decreasing value", func() {
for i, test := range tests {
if !test.typ.IsCumulative() || test.bucketer != nil {
continue
}
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
m := &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}
s := opts.Factory()
// Set the bigger value.
s.Set(ctx, m, time.Time{}, []interface{}{}, test.values[1])
So(s.Get(ctx, m, time.Time{}, []interface{}{}), ShouldResemble, test.values[1])
// Setting the smaller value should be ignored.
s.Set(ctx, m, time.Time{}, []interface{}{}, test.values[0])
So(s.Get(ctx, m, time.Time{}, []interface{}{}), ShouldResemble, test.values[1])
})
}
})
})
Convey("Increment and get", t, func() {
Convey("With no fields", func() {
for i, test := range tests {
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
// Value should be nil initially.
v := s.Get(ctx, m, time.Time{}, []interface{}{})
So(v, ShouldBeNil)
// Increment the metric.
for _, delta := range test.deltas {
call := func() { s.Incr(ctx, m, time.Time{}, []interface{}{}, delta) }
if test.wantIncrPanic {
So(call, ShouldPanic)
} else {
call()
}
}
// Get the final value.
v = s.Get(ctx, m, time.Time{}, []interface{}{})
if test.wantIncrValue != nil {
So(v, ShouldEqual, test.wantIncrValue)
} else if test.wantIncrValidator != nil {
test.wantIncrValidator(v)
}
})
}
})
Convey("With fields", func() {
for i, test := range tests {
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
// Values should be nil initially.
v := s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one"))
So(v, ShouldBeNil)
v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two"))
So(v, ShouldBeNil)
// Increment one cell.
for _, delta := range test.deltas {
call := func() { s.Incr(ctx, m, time.Time{}, makeInterfaceSlice("one"), delta) }
if test.wantIncrPanic {
So(call, ShouldPanic)
} else {
call()
}
}
// Get the final value.
v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one"))
if test.wantIncrValue != nil {
So(v, ShouldEqual, test.wantIncrValue)
} else if test.wantIncrValidator != nil {
test.wantIncrValidator(v)
}
// Another cell should still be nil.
v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two"))
So(v, ShouldBeNil)
})
}
})
Convey("With a fixed reset time", func() {
for i, test := range tests {
if test.wantIncrPanic {
continue
}
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
opts.RegistrationFinished(s)
// Do the incr with a fixed time.
t := time.Date(1972, 5, 6, 7, 8, 9, 0, time.UTC)
s.Incr(ctx, m, t, []interface{}{}, test.deltas[0])
// Check the time in the Cell is the same.
all := s.GetAll(ctx)
So(len(all), ShouldEqual, 1)
msg := monitor.SerializeValue(all[0], testclock.TestRecentTimeUTC)
if test.wantStartTimestamp {
ts := msg.GetStartTimestamp()
So(time.Unix(ts.GetSeconds(), int64(ts.GetNanos())).UTC().String(), ShouldEqual, t.String())
} else {
So(msg.StartTimestamp, ShouldBeNil)
}
})
}
})
Convey("With a target set in the context", func() {
for i, test := range tests {
if test.wantIncrPanic {
continue
}
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{
FakeMetric{types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}},
test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
opts.RegistrationFinished(s)
// Create a context with a different target.
foo := target.Task{ServiceName: "foo"}
ctxWithTarget := target.Set(ctx, &foo)
// Incr the first delta on the default target, second delta on the
// different target.
s.Incr(ctx, m, time.Time{}, []interface{}{}, test.deltas[0])
s.Incr(ctxWithTarget, m, time.Time{}, []interface{}{}, test.deltas[1])
// Get should return different values for different contexts.
v1 := s.Get(ctx, m, time.Time{}, []interface{}{})
v2 := s.Get(ctxWithTarget, m, time.Time{}, []interface{}{})
So(v1, ShouldNotEqual, v2)
// The targets should be set in the Cells.
all := s.GetAll(ctx)
So(len(all), ShouldEqual, 2)
coll := monitor.SerializeCells(all, testclock.TestRecentTimeUTC)
s0 := coll[0].TargetSchema.(*pb.MetricsCollection_Task).Task.GetServiceName()
s1 := coll[1].TargetSchema.(*pb.MetricsCollection_Task).Task.GetServiceName()
switch {
case s0 == foo.ServiceName:
So(s1, ShouldEqual, s.DefaultTarget().(*target.Task).ServiceName)
case s1 == foo.ServiceName:
So(s0, ShouldEqual, s.DefaultTarget().(*target.Task).ServiceName)
default:
t.Fail()
}
})
}
})
})
Convey("GetAll", t, func() {
ctx, tc := testclock.UseTime(ctx, testclock.TestRecentTimeUTC)
s := opts.Factory()
foo := &FakeMetric{
types.MetricInfo{"foo", "", []field.Field{}, types.NonCumulativeIntType, target.NilType},
types.MetricMetadata{}}
bar := &FakeMetric{
types.MetricInfo{"bar", "", []field.Field{field.String("f")}, types.StringType, target.NilType},
types.MetricMetadata{}}
baz := &FakeMetric{
types.MetricInfo{"baz", "", []field.Field{field.String("f")}, types.NonCumulativeFloatType, target.NilType},
types.MetricMetadata{}}
opts.RegistrationFinished(s)
// Add test records. We increment the test clock each time so that the added
// records sort deterministically using sortableCellSlice.
for _, m := range []struct {
metric types.Metric
fieldvals []interface{}
value interface{}
}{
{foo, []interface{}{}, int64(42)},
{bar, makeInterfaceSlice("one"), "hello"},
{bar, makeInterfaceSlice("two"), "world"},
{baz, makeInterfaceSlice("three"), 1.23},
{baz, makeInterfaceSlice("four"), 4.56},
} {
s.Set(ctx, m.metric, time.Time{}, m.fieldvals, m.value)
tc.Add(time.Second)
}
got := s.GetAll(ctx)
sort.Sort(sortableCellSlice(got))
want := []types.Cell{
{
types.MetricInfo{
Name: "foo",
Fields: []field.Field{},
ValueType: types.NonCumulativeIntType,
TargetType: target.NilType,
},
types.MetricMetadata{},
types.CellData{
FieldVals: []interface{}{},
Value: int64(42),
},
},
{
types.MetricInfo{
Name: "bar",
Fields: []field.Field{field.String("f")},
ValueType: types.StringType,
TargetType: target.NilType,
},
types.MetricMetadata{},
types.CellData{
FieldVals: makeInterfaceSlice("one"),
Value: "hello",
},
},
{
types.MetricInfo{
Name: "bar",
Fields: []field.Field{field.String("f")},
ValueType: types.StringType,
TargetType: target.NilType,
},
types.MetricMetadata{},
types.CellData{
FieldVals: makeInterfaceSlice("two"),
Value: "world",
},
},
{
types.MetricInfo{
Name: "baz",
Fields: []field.Field{field.String("f")},
ValueType: types.NonCumulativeFloatType,
TargetType: target.NilType,
},
types.MetricMetadata{},
types.CellData{
FieldVals: makeInterfaceSlice("three"),
Value: 1.23,
},
},
{
types.MetricInfo{
Name: "baz",
Fields: []field.Field{field.String("f")},
ValueType: types.NonCumulativeFloatType,
TargetType: target.NilType,
},
types.MetricMetadata{},
types.CellData{
FieldVals: makeInterfaceSlice("four"),
Value: 4.56,
},
},
}
So(len(got), ShouldEqual, len(want))
for i, g := range got {
w := want[i]
Convey(fmt.Sprintf("%d", i), func() {
So(g.Name, ShouldEqual, w.Name)
So(len(g.Fields), ShouldEqual, len(w.Fields))
So(g.ValueType, ShouldEqual, w.ValueType)
So(g.FieldVals, ShouldResemble, w.FieldVals)
So(g.Value, ShouldEqual, w.Value)
})
}
})
Convey("Set and del", t, func() {
Convey("With no fields", func() {
for i, test := range tests {
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
// Value should be nil initially.
So(s.Get(ctx, m, time.Time{}, []interface{}{}), ShouldBeNil)
// Set and get the value.
s.Set(ctx, m, time.Time{}, []interface{}{}, test.values[0])
v := s.Get(ctx, m, time.Time{}, []interface{}{})
if test.wantSetValidator != nil {
test.wantSetValidator(v, test.values[0])
} else {
So(v, ShouldEqual, test.values[0])
}
// Delete the cell. Then, get should return nil.
s.Del(ctx, m, []interface{}{})
So(s.Get(ctx, m, time.Time{}, []interface{}{}), ShouldBeNil)
})
}
})
Convey("With fields", func() {
for i, test := range tests {
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
// Values should be nil initially.
So(s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one")), ShouldBeNil)
So(s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two")), ShouldBeNil)
// Set both and then delete "one".
s.Set(ctx, m, time.Time{}, makeInterfaceSlice("one"), test.values[0])
s.Set(ctx, m, time.Time{}, makeInterfaceSlice("two"), test.values[1])
s.Del(ctx, m, makeInterfaceSlice("one"))
// Get should return nil for "one", but the value for "two".
So(s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one")), ShouldBeNil)
v := s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two"))
if test.wantSetValidator != nil {
test.wantSetValidator(v, test.values[1])
} else {
So(v, ShouldEqual, test.values[1])
}
})
}
})
Convey("With a target set in the context", func() {
for i, test := range tests {
if test.wantIncrPanic {
continue
}
Convey(fmt.Sprintf("%d. %s", i, test.typ), func() {
var m types.Metric
if test.bucketer != nil {
m = &fakeDistributionMetric{FakeMetric{
types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType},
types.MetricMetadata{}}, test.bucketer}
} else {
m = &FakeMetric{
types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType},
types.MetricMetadata{}}
}
s := opts.Factory()
opts.RegistrationFinished(s)
// Create a context with a different target.
foo := target.Task{ServiceName: "foo"}
ctxWithTarget := target.Set(ctx, &foo)
// Set the first value on the default target, second value on the
// different target. Note that both are set with the same field value,
// "one".
fvs := func() []interface{} { return makeInterfaceSlice("one") }
s.Set(ctx, m, time.Time{}, fvs(), test.values[0])
s.Set(ctxWithTarget, m, time.Time{}, fvs(), test.values[1])
// Get should return different values for different contexts.
v1 := s.Get(ctx, m, time.Time{}, fvs())
v2 := s.Get(ctxWithTarget, m, time.Time{}, fvs())
So(v1, ShouldNotEqual, v2)
// Delete the cell with the custom target. Then, get should return
// the value for the default target, and nil for the custom target.
s.Del(ctxWithTarget, m, fvs())
So(s.Get(ctx, m, time.Time{}, fvs()), ShouldEqual, test.values[0])
So(s.Get(ctxWithTarget, m, time.Time{}, fvs()), ShouldBeNil)
})
}
})
})
Convey("Concurrency", t, func() {
const numIterations = 100
const numGoroutines = 32
Convey("Incr", func(c C) {
s := opts.Factory()
m := &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, types.CumulativeIntType, target.NilType},
types.MetricMetadata{}}
wg := sync.WaitGroup{}
f := func(n int) {
defer wg.Done()
for i := 0; i < numIterations; i++ {
s.Incr(ctx, m, time.Time{}, []interface{}{}, int64(1))
}
}
for n := 0; n < numGoroutines; n++ {
wg.Add(1)
go f(n)
}
wg.Wait()
val := s.Get(ctx, m, time.Time{}, []interface{}{})
So(val, ShouldEqual, numIterations*numGoroutines)
})
})
Convey("Multiple targets with the same TargetType", t, func() {
Convey("Gets from context", func() {
s := opts.Factory()
m := &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, types.NonCumulativeIntType, target.NilType},
types.MetricMetadata{}}
opts.RegistrationFinished(s)
t := target.Task{ServiceName: "foo"}
ctxWithTarget := target.Set(ctx, &t)
s.Set(ctx, m, time.Time{}, []interface{}{}, int64(42))
s.Set(ctxWithTarget, m, time.Time{}, []interface{}{}, int64(43))
val := s.Get(ctx, m, time.Time{}, []interface{}{})
So(val, ShouldEqual, 42)
val = s.Get(ctxWithTarget, m, time.Time{}, []interface{}{})
So(val, ShouldEqual, 43)
all := s.GetAll(ctx)
So(len(all), ShouldEqual, 2)
// The order is undefined.
if all[0].Value.(int64) == 42 {
So(all[0].Target, ShouldResemble, s.DefaultTarget())
So(all[1].Target, ShouldResemble, &t)
} else {
So(all[0].Target, ShouldResemble, &t)
So(all[1].Target, ShouldResemble, s.DefaultTarget())
}
})
})
Convey("Multiple targets with multiple TargetTypes", t, func() {
Convey("Gets from context", func() {
s := opts.Factory()
// Two metrics with the same metric name, but different types.
mTask := &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, types.NonCumulativeIntType, target.TaskType},
types.MetricMetadata{}}
mDevice := &FakeMetric{
types.MetricInfo{"m", "", []field.Field{}, types.NonCumulativeIntType, target.DeviceType},
types.MetricMetadata{}}
opts.RegistrationFinished(s)
taskTarget := target.Task{ServiceName: "foo"}
deviceTarget := target.NetworkDevice{Hostname: "bar"}
ctxWithTarget := target.Set(target.Set(ctx, &taskTarget), &deviceTarget)
s.Set(ctxWithTarget, mTask, time.Time{}, []interface{}{}, int64(42))
s.Set(ctxWithTarget, mDevice, time.Time{}, []interface{}{}, int64(43))
val := s.Get(ctxWithTarget, mTask, time.Time{}, []interface{}{})
So(val, ShouldEqual, 42)
val = s.Get(ctxWithTarget, mDevice, time.Time{}, []interface{}{})
So(val, ShouldEqual, 43)
all := s.GetAll(ctx)
So(len(all), ShouldEqual, 2)
// The order is undefined.
if all[0].Value.(int64) == 42 {
So(all[0].Target, ShouldResemble, &taskTarget)
So(all[1].Target, ShouldResemble, &deviceTarget)
} else {
So(all[0].Target, ShouldResemble, &deviceTarget)
So(all[1].Target, ShouldResemble, &taskTarget)
}
})
})
}
func makeInterfaceSlice(v ...interface{}) []interface{} {
return v
}
// FakeMetric is a fake Metric implementation.
type FakeMetric struct {
types.MetricInfo
types.MetricMetadata
}
// Info implements Metric.Info
func (m *FakeMetric) Info() types.MetricInfo { return m.MetricInfo }
// Metadata implements Metric.Metadata
func (m *FakeMetric) Metadata() types.MetricMetadata { return m.MetricMetadata }
// SetFixedResetTime implements Metric.SetFixedResetTime.
func (m *FakeMetric) SetFixedResetTime(t time.Time) {}
type fakeDistributionMetric struct {
FakeMetric
bucketer *distribution.Bucketer
}
func (m *fakeDistributionMetric) Bucketer() *distribution.Bucketer { return m.bucketer }
type sortableCellSlice []types.Cell
func (s sortableCellSlice) Len() int { return len(s) }
func (s sortableCellSlice) Less(i, j int) bool {
return s[i].ResetTime.UnixNano() < s[j].ResetTime.UnixNano()
}
func (s sortableCellSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }