blob: 5173967d641f23a2212c51973a80640cb35615bf [file] [log] [blame] [edit]
/*
*
* Copyright 2024 gRPC 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 stats
import (
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc/internal/grpctest"
)
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
// TestPanic tests that registering two metrics with the same name across any
// type of metric triggers a panic.
func (s) TestPanic(t *testing.T) {
cleanup := snapshotMetricsRegistryForTesting()
defer cleanup()
want := "metric simple counter already registered"
defer func() {
if r := recover(); !strings.Contains(fmt.Sprint(r), want) {
t.Errorf("expected panic contains %q, got %q", want, r)
}
}()
desc := MetricDescriptor{
// Type is not expected to be set from the registerer, but meant to be
// set by the metric registry.
Name: "simple counter",
Description: "number of times recorded on tests",
Unit: "{call}",
}
RegisterInt64Count(desc)
RegisterInt64Gauge(desc)
}
// TestInstrumentRegistry tests the metric registry. It registers testing only
// metrics using the metric registry, and creates a fake metrics recorder which
// uses these metrics. Using the handles returned from the metric registry, this
// test records stats using the fake metrics recorder. Then, the test verifies
// the persisted metrics data in the metrics recorder is what is expected. Thus,
// this tests the interactions between the metrics recorder and the metrics
// registry.
func (s) TestMetricRegistry(t *testing.T) {
cleanup := snapshotMetricsRegistryForTesting()
defer cleanup()
intCountHandle1 := RegisterInt64Count(MetricDescriptor{
Name: "simple counter",
Description: "sum of all emissions from tests",
Unit: "int",
Labels: []string{"int counter label"},
OptionalLabels: []string{"int counter optional label"},
Default: false,
})
floatCountHandle1 := RegisterFloat64Count(MetricDescriptor{
Name: "float counter",
Description: "sum of all emissions from tests",
Unit: "float",
Labels: []string{"float counter label"},
OptionalLabels: []string{"float counter optional label"},
Default: false,
})
intHistoHandle1 := RegisterInt64Histo(MetricDescriptor{
Name: "int histo",
Description: "sum of all emissions from tests",
Unit: "int",
Labels: []string{"int histo label"},
OptionalLabels: []string{"int histo optional label"},
Default: false,
})
floatHistoHandle1 := RegisterFloat64Histo(MetricDescriptor{
Name: "float histo",
Description: "sum of all emissions from tests",
Unit: "float",
Labels: []string{"float histo label"},
OptionalLabels: []string{"float histo optional label"},
Default: false,
})
intGaugeHandle1 := RegisterInt64Gauge(MetricDescriptor{
Name: "simple gauge",
Description: "the most recent int emitted by test",
Unit: "int",
Labels: []string{"int gauge label"},
OptionalLabels: []string{"int gauge optional label"},
Default: false,
})
intUpDownCountHandle1 := RegisterInt64UpDownCount(MetricDescriptor{
Name: "simple up down counter",
Description: "current number of emissions from tests",
Unit: "int",
Labels: []string{"int up down counter label"},
OptionalLabels: []string{"int up down counter optional label"},
Default: false,
})
fmr := newFakeMetricsRecorder(t)
intCountHandle1.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
// The Metric Descriptor in the handle should be able to identify the metric
// information. This is the key passed to metrics recorder to identify
// metric.
if got := fmr.intValues[intCountHandle1.Descriptor()]; got != 1 {
t.Fatalf("fmr.intValues[intCountHandle1.MetricDescriptor] got %v, want: %v", got, 1)
}
intUpDownCountHandle1.Record(fmr, 2, []string{"some label value", "some optional label value"}...)
// The Metric Descriptor in the handle should be able to identify the metric
// information. This is the key passed to metrics recorder to identify
// metric.
if got := fmr.intValues[intUpDownCountHandle1.Descriptor()]; got != 2 {
t.Fatalf("fmr.intValues[intUpDownCountHandle1.MetricDescriptor] got %v, want: %v", got, 2)
}
floatCountHandle1.Record(fmr, 1.2, []string{"some label value", "some optional label value"}...)
if got := fmr.floatValues[floatCountHandle1.Descriptor()]; got != 1.2 {
t.Fatalf("fmr.floatValues[floatCountHandle1.MetricDescriptor] got %v, want: %v", got, 1.2)
}
intHistoHandle1.Record(fmr, 3, []string{"some label value", "some optional label value"}...)
if got := fmr.intValues[intHistoHandle1.Descriptor()]; got != 3 {
t.Fatalf("fmr.intValues[intHistoHandle1.MetricDescriptor] got %v, want: %v", got, 3)
}
floatHistoHandle1.Record(fmr, 4.3, []string{"some label value", "some optional label value"}...)
if got := fmr.floatValues[floatHistoHandle1.Descriptor()]; got != 4.3 {
t.Fatalf("fmr.floatValues[floatHistoHandle1.MetricDescriptor] got %v, want: %v", got, 4.3)
}
intGaugeHandle1.Record(fmr, 7, []string{"some label value", "some optional label value"}...)
if got := fmr.intValues[intGaugeHandle1.Descriptor()]; got != 7 {
t.Fatalf("fmr.intValues[intGaugeHandle1.MetricDescriptor] got %v, want: %v", got, 7)
}
}
func TestUpDownCounts(t *testing.T) {
cleanup := snapshotMetricsRegistryForTesting()
defer cleanup()
intUpDownCountHandle1 := RegisterInt64UpDownCount(MetricDescriptor{
Name: "simple up down counter",
Description: "current number of emissions from tests",
Unit: "int",
Labels: []string{"int up down counter label"},
OptionalLabels: []string{"int up down counter optional label"},
Default: false,
})
fmr := newFakeMetricsRecorder(t)
intUpDownCountHandle1.Record(fmr, 2, []string{"up down value", "some optional label value"}...)
intUpDownCountHandle1.Record(fmr, -1, []string{"up down value", "some optional label value"}...)
if got := fmr.intValues[intUpDownCountHandle1.Descriptor()]; got != 1 {
t.Fatalf("fmr.intValues[intUpDownCountHandle1.MetricDescriptor] got %v, want: %v", got, 1)
}
}
// TestNumerousIntCounts tests numerous int count metrics registered onto the
// metric registry. A component (simulated by test) should be able to record on
// the different registered int count metrics.
func TestNumerousIntCounts(t *testing.T) {
cleanup := snapshotMetricsRegistryForTesting()
defer cleanup()
intCountHandle1 := RegisterInt64Count(MetricDescriptor{
Name: "int counter",
Description: "sum of all emissions from tests",
Unit: "int",
Labels: []string{"int counter label"},
OptionalLabels: []string{"int counter optional label"},
Default: false,
})
intCountHandle2 := RegisterInt64Count(MetricDescriptor{
Name: "int counter 2",
Description: "sum of all emissions from tests",
Unit: "int",
Labels: []string{"int counter label"},
OptionalLabels: []string{"int counter optional label"},
Default: false,
})
intCountHandle3 := RegisterInt64Count(MetricDescriptor{
Name: "int counter 3",
Description: "sum of all emissions from tests",
Unit: "int",
Labels: []string{"int counter label"},
OptionalLabels: []string{"int counter optional label"},
Default: false,
})
fmr := newFakeMetricsRecorder(t)
intCountHandle1.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
got := []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
want := []int64{1, 0, 0}
if diff := cmp.Diff(got, want); diff != "" {
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
}
intCountHandle2.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
want = []int64{1, 1, 0}
if diff := cmp.Diff(got, want); diff != "" {
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
}
intCountHandle3.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
want = []int64{1, 1, 1}
if diff := cmp.Diff(got, want); diff != "" {
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
}
intCountHandle3.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
want = []int64{1, 1, 2}
if diff := cmp.Diff(got, want); diff != "" {
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
}
}
type fakeMetricsRecorder struct {
t *testing.T
intValues map[*MetricDescriptor]int64
floatValues map[*MetricDescriptor]float64
}
// newFakeMetricsRecorder returns a fake metrics recorder based off the current
// state of global metric registry.
func newFakeMetricsRecorder(t *testing.T) *fakeMetricsRecorder {
fmr := &fakeMetricsRecorder{
t: t,
intValues: make(map[*MetricDescriptor]int64),
floatValues: make(map[*MetricDescriptor]float64),
}
for _, desc := range metricsRegistry {
switch desc.Type {
case MetricTypeIntCount:
case MetricTypeIntHisto:
case MetricTypeIntGauge:
fmr.intValues[desc] = 0
case MetricTypeFloatCount:
case MetricTypeFloatHisto:
fmr.floatValues[desc] = 0
}
}
return fmr
}
// verifyLabels verifies that the labels received are of the expected length.
func verifyLabels(t *testing.T, labelsWant []string, optionalLabelsWant []string, labelsGot []string) {
if len(labelsWant)+len(optionalLabelsWant) != len(labelsGot) {
t.Fatalf("length of optional labels expected did not match got %v, want %v", len(labelsGot), len(labelsWant)+len(optionalLabelsWant))
}
}
func (r *fakeMetricsRecorder) RecordInt64Count(handle *Int64CountHandle, incr int64, labels ...string) {
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
r.intValues[handle.Descriptor()] += incr
}
func (r *fakeMetricsRecorder) RecordFloat64Count(handle *Float64CountHandle, incr float64, labels ...string) {
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
r.floatValues[handle.Descriptor()] += incr
}
func (r *fakeMetricsRecorder) RecordInt64Histo(handle *Int64HistoHandle, incr int64, labels ...string) {
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
r.intValues[handle.Descriptor()] += incr
}
func (r *fakeMetricsRecorder) RecordFloat64Histo(handle *Float64HistoHandle, incr float64, labels ...string) {
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
r.floatValues[handle.Descriptor()] += incr
}
func (r *fakeMetricsRecorder) RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string) {
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
r.intValues[handle.Descriptor()] += incr
}
func (r *fakeMetricsRecorder) RecordInt64UpDownCount(handle *Int64UpDownCountHandle, incr int64, labels ...string) {
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
r.intValues[handle.Descriptor()] += incr
}