blob: 4291f57c0e2a96ef2ae098b9fa4c735ee3289c30 [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 metric
import (
"context"
"reflect"
"testing"
"go.chromium.org/luci/common/tsmon"
"go.chromium.org/luci/common/tsmon/distribution"
"go.chromium.org/luci/common/tsmon/registry"
"go.chromium.org/luci/common/tsmon/target"
"go.chromium.org/luci/common/tsmon/types"
. "github.com/smartystreets/goconvey/convey"
)
func makeContext() context.Context {
ret, _ := tsmon.WithDummyInMemory(context.Background())
return ret
}
func TestMetrics(t *testing.T) {
t.Parallel()
tt := target.TaskType
Convey("Int", t, func() {
c := makeContext()
m := NewIntWithTargetType("int", tt, "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewIntWithTargetType("int", tt, "description", nil) },
ShouldPanic,
)
So(m.Get(c), ShouldEqual, 0)
m.Set(c, 42)
So(m.Get(c), ShouldEqual, 42)
So(func() { m.Set(c, 42, "field") }, ShouldPanic)
So(func() { m.Get(c, "field") }, ShouldPanic)
})
Convey("Counter", t, func() {
c := makeContext()
m := NewCounterWithTargetType("counter", tt, "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewCounterWithTargetType("counter", tt, "description", nil) },
ShouldPanic,
)
So(m.Get(c), ShouldEqual, 0)
m.Add(c, 3)
So(m.Get(c), ShouldEqual, 3)
m.Add(c, 2)
So(m.Get(c), ShouldEqual, 5)
})
Convey("Float", t, func() {
c := makeContext()
m := NewFloatWithTargetType("float", tt, "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewFloatWithTargetType("float", tt, "description", nil) },
ShouldPanic,
)
So(m.Get(c), ShouldAlmostEqual, 0.0)
m.Set(c, 42.3)
So(m.Get(c), ShouldAlmostEqual, 42.3)
So(func() { m.Set(c, 42.3, "field") }, ShouldPanic)
So(func() { m.Get(c, "field") }, ShouldPanic)
})
Convey("FloatCounter", t, func() {
c := makeContext()
m := NewFloatCounterWithTargetType("float_counter", tt, "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewFloatCounterWithTargetType("float_counter", tt, "description", nil) },
ShouldPanic,
)
So(m.Get(c), ShouldAlmostEqual, 0.0)
m.Add(c, 3.1)
So(m.Get(c), ShouldAlmostEqual, 3.1)
m.Add(c, 2.2)
So(m.Get(c), ShouldAlmostEqual, 5.3)
})
Convey("String", t, func() {
c := makeContext()
m := NewStringWithTargetType("string", tt, "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(func() { NewStringWithTargetType("string", tt, "description", nil) }, ShouldPanic)
So(m.Get(c), ShouldEqual, "")
m.Set(c, "hello")
So(m.Get(c), ShouldEqual, "hello")
So(func() { m.Set(c, "hello", "field") }, ShouldPanic)
So(func() { m.Get(c, "field") }, ShouldPanic)
})
Convey("Bool", t, func() {
c := makeContext()
m := NewBoolWithTargetType("bool", tt, "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewBoolWithTargetType("bool", tt, "description", nil) },
ShouldPanic,
)
So(m.Get(c), ShouldEqual, false)
m.Set(c, true)
So(m.Get(c), ShouldBeTrue)
So(func() { m.Set(c, true, "field") }, ShouldPanic)
So(func() { m.Get(c, "field") }, ShouldPanic)
})
Convey("CumulativeDistribution", t, func() {
c := makeContext()
m := NewCumulativeDistributionWithTargetType("cumul_dist", tt, "description", nil, distribution.FixedWidthBucketer(10, 20))
So(m.Info().TargetType, ShouldResemble, tt)
So(func() { NewCumulativeDistributionWithTargetType("cumul_dist", tt, "description", nil, m.Bucketer()) }, ShouldPanic)
So(m.Bucketer().GrowthFactor(), ShouldEqual, 0)
So(m.Bucketer().Width(), ShouldEqual, 10)
So(m.Bucketer().NumFiniteBuckets(), ShouldEqual, 20)
So(m.Get(c), ShouldBeNil)
m.Add(c, 5)
v := m.Get(c)
So(v.Bucketer().GrowthFactor(), ShouldEqual, 0)
So(v.Bucketer().Width(), ShouldEqual, 10)
So(v.Bucketer().NumFiniteBuckets(), ShouldEqual, 20)
So(v.Sum(), ShouldEqual, 5)
So(v.Count(), ShouldEqual, 1)
So(func() { m.Add(c, 5, "field") }, ShouldPanic)
So(func() { m.Get(c, "field") }, ShouldPanic)
})
Convey("NonCumulativeDistribution", t, func() {
c := makeContext()
m := NewNonCumulativeDistributionWithTargetType("noncumul_dist", tt, "description", nil, distribution.FixedWidthBucketer(10, 20))
So(m.Info().TargetType, ShouldResemble, tt)
So(func() {
NewNonCumulativeDistributionWithTargetType("noncumul_dist", tt, "description", nil, m.Bucketer())
}, ShouldPanic)
So(m.Bucketer().GrowthFactor(), ShouldEqual, 0)
So(m.Bucketer().Width(), ShouldEqual, 10)
So(m.Bucketer().NumFiniteBuckets(), ShouldEqual, 20)
So(m.Get(c), ShouldBeNil)
d := distribution.New(m.Bucketer())
d.Add(15)
m.Set(c, d)
v := m.Get(c)
So(v.Bucketer().GrowthFactor(), ShouldEqual, 0)
So(v.Bucketer().Width(), ShouldEqual, 10)
So(v.Bucketer().NumFiniteBuckets(), ShouldEqual, 20)
So(v.Sum(), ShouldEqual, 15)
So(v.Count(), ShouldEqual, 1)
So(func() { m.Set(c, d, "field") }, ShouldPanic)
So(func() { m.Get(c, "field") }, ShouldPanic)
})
}
func TestMetricsDefaultTargetType(t *testing.T) {
t.Parallel()
// These tests ensure that metrics are given target.NilType, if created
// without a target type specified.
tt := target.NilType
Convey("Int", t, func() {
m := NewInt("int", "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewIntWithTargetType("int", tt, "description", nil) },
ShouldPanic,
)
})
Convey("Counter", t, func() {
m := NewCounter("counter", "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewCounterWithTargetType("counter", tt, "description", nil) },
ShouldPanic,
)
})
Convey("Float", t, func() {
m := NewFloat("float", "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewFloatWithTargetType("float", tt, "description", nil) },
ShouldPanic,
)
})
Convey("FloatCounter", t, func() {
m := NewFloatCounter("float_counter", "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewFloatCounterWithTargetType("float_counter", tt, "description", nil) },
ShouldPanic,
)
})
Convey("String", t, func() {
m := NewString("string", "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(func() { NewStringWithTargetType("string", tt, "description", nil) }, ShouldPanic)
})
Convey("Bool", t, func() {
m := NewBool("bool", "description", nil)
So(m.Info().TargetType, ShouldResemble, tt)
So(
func() { NewBoolWithTargetType("bool", tt, "description", nil) },
ShouldPanic,
)
})
Convey("CumulativeDistribution", t, func() {
m := NewCumulativeDistribution("cumul_dist", "description", nil, distribution.FixedWidthBucketer(10, 20))
So(m.Info().TargetType, ShouldResemble, tt)
So(func() { NewCumulativeDistributionWithTargetType("cumul_dist", tt, "description", nil, m.Bucketer()) }, ShouldPanic)
})
Convey("NonCumulativeDistribution", t, func() {
m := NewNonCumulativeDistribution("noncumul_dist", "description", nil, distribution.FixedWidthBucketer(10, 20))
So(m.Info().TargetType, ShouldResemble, tt)
So(func() {
NewNonCumulativeDistributionWithTargetType("noncumul_dist", tt, "description", nil, m.Bucketer())
}, ShouldPanic)
})
}
func TestMetricsWithMultipleTargets(t *testing.T) {
t.Parallel()
testTaskTargets := []target.Task{{TaskNum: 0}, {TaskNum: 1}}
testDeviceTargets := []target.NetworkDevice{{Hostname: "a"}}
Convey("with a single TargetType", t, func() {
c := makeContext()
Convey("with a single target in context", func() {
m := NewIntWithTargetType("m_with_s_s", target.TaskType, "desc", nil)
tctx := target.Set(c, &testTaskTargets[0])
So(m.Get(tctx), ShouldEqual, 0)
m.Set(tctx, 42)
So(m.Get(tctx), ShouldEqual, 42)
})
Convey("with multiple targets in context", func() {
m := NewIntWithTargetType("m_with_s_m", target.TaskType, "desc", nil)
tctx0 := target.Set(c, &testTaskTargets[0])
tctx1 := target.Set(tctx0, &testTaskTargets[1])
So(m.Get(tctx0), ShouldEqual, 0)
So(m.Get(tctx1), ShouldEqual, 0)
m.Set(tctx0, 41)
m.Set(tctx1, 42)
So(m.Get(tctx0), ShouldEqual, 41)
So(m.Get(tctx1), ShouldEqual, 42)
})
})
Convey("with multiple TargetTypes", t, func() {
c := makeContext()
Convey("with a single target in context for each type", func() {
tctx := target.Set(
target.Set(c, &testTaskTargets[0]), &testDeviceTargets[0],
)
// two metrics with the same name, but different types.
mDevice := NewIntWithTargetType("m_with_m_s", target.DeviceType, "desc", nil)
mTask := NewIntWithTargetType("m_with_m_s", target.TaskType, "desc", nil)
So(mTask.Get(tctx), ShouldEqual, 0)
So(mDevice.Get(tctx), ShouldEqual, 0)
mTask.Set(tctx, 41)
mDevice.Set(tctx, 42)
So(mTask.Get(tctx), ShouldEqual, 41)
So(mDevice.Get(tctx), ShouldEqual, 42)
})
})
}
// To avoid import cycle, unit tests for Registry with metrics are implemented
// here.
func TestMetricWithRegistry(t *testing.T) {
t.Parallel()
Convey("A single metric", t, func() {
Convey("with TargetType", func() {
metric := NewIntWithTargetType("registry/test/1", target.TaskType, "desc", nil)
var registered types.Metric
registry.Iter(func(m types.Metric) {
if reflect.DeepEqual(m.Info(), metric.Info()) {
registered = m
}
})
So(registered, ShouldNotBeNil)
})
Convey("without TargetType", func() {
metric := NewInt("registry/test/1", "desc", nil)
var registered types.Metric
registry.Iter(func(m types.Metric) {
if reflect.DeepEqual(m.Info(), metric.Info()) {
registered = m
}
})
So(registered, ShouldNotBeNil)
})
})
Convey("Multiple metrics", t, func() {
Convey("with the same metric name and targe type", func() {
NewIntWithTargetType("registry/test/2", target.TaskType, "desc", nil)
So(func() {
NewIntWithTargetType("registry/test/2", target.TaskType, "desc", nil)
}, ShouldPanic)
})
Convey("with the same metric name, but different target type", func() {
mTask := NewIntWithTargetType("registry/test/3", target.TaskType, "desc", nil)
mDevice := NewIntWithTargetType("registry/test/3", target.DeviceType, "desc", nil)
mNil := NewInt("registry/test/3", "desc", nil)
var rTask, rDevice, rNil types.Metric
registry.Iter(func(m types.Metric) {
if reflect.DeepEqual(m.Info(), mTask.Info()) {
rTask = m
} else if reflect.DeepEqual(m.Info(), mDevice.Info()) {
rDevice = m
} else if reflect.DeepEqual(m.Info(), mNil.Info()) {
rNil = m
}
})
So(rTask, ShouldNotBeNil)
So(rDevice, ShouldNotBeNil)
So(rNil, ShouldNotBeNil)
})
})
}
func TestMetricsWithTimeUnit(t *testing.T) {
t.Parallel()
anno := &types.MetricMetadata{Units: types.Seconds}
Convey("check", t, func() {
Convey("with types that time unit can be given to", func() {
NewInt("time/int", "desc", anno)
NewCounter("time/counter", "desc", anno)
NewFloat("time/float", "desc", anno)
NewFloatCounter("time/float_counter", "desc", anno)
bkt := distribution.FixedWidthBucketer(10, 20)
NewCumulativeDistribution("time/cdist", "desc", anno, bkt)
NewNonCumulativeDistribution("time/ncdist", "desc", anno, bkt)
})
Convey("with types that should panic if a time unit is given", func() {
So(func() { NewString("time/string", "desc", anno) }, ShouldPanic)
So(func() { NewBool("time/bool", "desc", anno) }, ShouldPanic)
})
})
}