google.golang.org/grpc@v1.72.2/experimental/stats/metricregistry_test.go (about)

     1  /*
     2   *
     3   * Copyright 2024 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package stats
    20  
    21  import (
    22  	"fmt"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"google.golang.org/grpc/internal/grpctest"
    28  )
    29  
    30  type s struct {
    31  	grpctest.Tester
    32  }
    33  
    34  func Test(t *testing.T) {
    35  	grpctest.RunSubTests(t, s{})
    36  }
    37  
    38  // TestPanic tests that registering two metrics with the same name across any
    39  // type of metric triggers a panic.
    40  func (s) TestPanic(t *testing.T) {
    41  	cleanup := snapshotMetricsRegistryForTesting()
    42  	defer cleanup()
    43  
    44  	want := "metric simple counter already registered"
    45  	defer func() {
    46  		if r := recover(); !strings.Contains(fmt.Sprint(r), want) {
    47  			t.Errorf("expected panic contains %q, got %q", want, r)
    48  		}
    49  	}()
    50  	desc := MetricDescriptor{
    51  		// Type is not expected to be set from the registerer, but meant to be
    52  		// set by the metric registry.
    53  		Name:        "simple counter",
    54  		Description: "number of times recorded on tests",
    55  		Unit:        "calls",
    56  	}
    57  	RegisterInt64Count(desc)
    58  	RegisterInt64Gauge(desc)
    59  }
    60  
    61  // TestInstrumentRegistry tests the metric registry. It registers testing only
    62  // metrics using the metric registry, and creates a fake metrics recorder which
    63  // uses these metrics. Using the handles returned from the metric registry, this
    64  // test records stats using the fake metrics recorder. Then, the test verifies
    65  // the persisted metrics data in the metrics recorder is what is expected. Thus,
    66  // this tests the interactions between the metrics recorder and the metrics
    67  // registry.
    68  func (s) TestMetricRegistry(t *testing.T) {
    69  	cleanup := snapshotMetricsRegistryForTesting()
    70  	defer cleanup()
    71  
    72  	intCountHandle1 := RegisterInt64Count(MetricDescriptor{
    73  		Name:           "simple counter",
    74  		Description:    "sum of all emissions from tests",
    75  		Unit:           "int",
    76  		Labels:         []string{"int counter label"},
    77  		OptionalLabels: []string{"int counter optional label"},
    78  		Default:        false,
    79  	})
    80  	floatCountHandle1 := RegisterFloat64Count(MetricDescriptor{
    81  		Name:           "float counter",
    82  		Description:    "sum of all emissions from tests",
    83  		Unit:           "float",
    84  		Labels:         []string{"float counter label"},
    85  		OptionalLabels: []string{"float counter optional label"},
    86  		Default:        false,
    87  	})
    88  	intHistoHandle1 := RegisterInt64Histo(MetricDescriptor{
    89  		Name:           "int histo",
    90  		Description:    "sum of all emissions from tests",
    91  		Unit:           "int",
    92  		Labels:         []string{"int histo label"},
    93  		OptionalLabels: []string{"int histo optional label"},
    94  		Default:        false,
    95  	})
    96  	floatHistoHandle1 := RegisterFloat64Histo(MetricDescriptor{
    97  		Name:           "float histo",
    98  		Description:    "sum of all emissions from tests",
    99  		Unit:           "float",
   100  		Labels:         []string{"float histo label"},
   101  		OptionalLabels: []string{"float histo optional label"},
   102  		Default:        false,
   103  	})
   104  	intGaugeHandle1 := RegisterInt64Gauge(MetricDescriptor{
   105  		Name:           "simple gauge",
   106  		Description:    "the most recent int emitted by test",
   107  		Unit:           "int",
   108  		Labels:         []string{"int gauge label"},
   109  		OptionalLabels: []string{"int gauge optional label"},
   110  		Default:        false,
   111  	})
   112  
   113  	fmr := newFakeMetricsRecorder(t)
   114  
   115  	intCountHandle1.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
   116  	// The Metric Descriptor in the handle should be able to identify the metric
   117  	// information. This is the key passed to metrics recorder to identify
   118  	// metric.
   119  	if got := fmr.intValues[intCountHandle1.Descriptor()]; got != 1 {
   120  		t.Fatalf("fmr.intValues[intCountHandle1.MetricDescriptor] got %v, want: %v", got, 1)
   121  	}
   122  
   123  	floatCountHandle1.Record(fmr, 1.2, []string{"some label value", "some optional label value"}...)
   124  	if got := fmr.floatValues[floatCountHandle1.Descriptor()]; got != 1.2 {
   125  		t.Fatalf("fmr.floatValues[floatCountHandle1.MetricDescriptor] got %v, want: %v", got, 1.2)
   126  	}
   127  
   128  	intHistoHandle1.Record(fmr, 3, []string{"some label value", "some optional label value"}...)
   129  	if got := fmr.intValues[intHistoHandle1.Descriptor()]; got != 3 {
   130  		t.Fatalf("fmr.intValues[intHistoHandle1.MetricDescriptor] got %v, want: %v", got, 3)
   131  	}
   132  
   133  	floatHistoHandle1.Record(fmr, 4.3, []string{"some label value", "some optional label value"}...)
   134  	if got := fmr.floatValues[floatHistoHandle1.Descriptor()]; got != 4.3 {
   135  		t.Fatalf("fmr.floatValues[floatHistoHandle1.MetricDescriptor] got %v, want: %v", got, 4.3)
   136  	}
   137  
   138  	intGaugeHandle1.Record(fmr, 7, []string{"some label value", "some optional label value"}...)
   139  	if got := fmr.intValues[intGaugeHandle1.Descriptor()]; got != 7 {
   140  		t.Fatalf("fmr.intValues[intGaugeHandle1.MetricDescriptor] got %v, want: %v", got, 7)
   141  	}
   142  }
   143  
   144  // TestNumerousIntCounts tests numerous int count metrics registered onto the
   145  // metric registry. A component (simulated by test) should be able to record on
   146  // the different registered int count metrics.
   147  func TestNumerousIntCounts(t *testing.T) {
   148  	cleanup := snapshotMetricsRegistryForTesting()
   149  	defer cleanup()
   150  
   151  	intCountHandle1 := RegisterInt64Count(MetricDescriptor{
   152  		Name:           "int counter",
   153  		Description:    "sum of all emissions from tests",
   154  		Unit:           "int",
   155  		Labels:         []string{"int counter label"},
   156  		OptionalLabels: []string{"int counter optional label"},
   157  		Default:        false,
   158  	})
   159  	intCountHandle2 := RegisterInt64Count(MetricDescriptor{
   160  		Name:           "int counter 2",
   161  		Description:    "sum of all emissions from tests",
   162  		Unit:           "int",
   163  		Labels:         []string{"int counter label"},
   164  		OptionalLabels: []string{"int counter optional label"},
   165  		Default:        false,
   166  	})
   167  	intCountHandle3 := RegisterInt64Count(MetricDescriptor{
   168  		Name:           "int counter 3",
   169  		Description:    "sum of all emissions from tests",
   170  		Unit:           "int",
   171  		Labels:         []string{"int counter label"},
   172  		OptionalLabels: []string{"int counter optional label"},
   173  		Default:        false,
   174  	})
   175  
   176  	fmr := newFakeMetricsRecorder(t)
   177  
   178  	intCountHandle1.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
   179  	got := []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
   180  	want := []int64{1, 0, 0}
   181  	if diff := cmp.Diff(got, want); diff != "" {
   182  		t.Fatalf("fmr.intValues (-got, +want): %v", diff)
   183  	}
   184  
   185  	intCountHandle2.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
   186  	got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
   187  	want = []int64{1, 1, 0}
   188  	if diff := cmp.Diff(got, want); diff != "" {
   189  		t.Fatalf("fmr.intValues (-got, +want): %v", diff)
   190  	}
   191  
   192  	intCountHandle3.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
   193  	got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
   194  	want = []int64{1, 1, 1}
   195  	if diff := cmp.Diff(got, want); diff != "" {
   196  		t.Fatalf("fmr.intValues (-got, +want): %v", diff)
   197  	}
   198  
   199  	intCountHandle3.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
   200  	got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
   201  	want = []int64{1, 1, 2}
   202  	if diff := cmp.Diff(got, want); diff != "" {
   203  		t.Fatalf("fmr.intValues (-got, +want): %v", diff)
   204  	}
   205  }
   206  
   207  type fakeMetricsRecorder struct {
   208  	t *testing.T
   209  
   210  	intValues   map[*MetricDescriptor]int64
   211  	floatValues map[*MetricDescriptor]float64
   212  }
   213  
   214  // newFakeMetricsRecorder returns a fake metrics recorder based off the current
   215  // state of global metric registry.
   216  func newFakeMetricsRecorder(t *testing.T) *fakeMetricsRecorder {
   217  	fmr := &fakeMetricsRecorder{
   218  		t:           t,
   219  		intValues:   make(map[*MetricDescriptor]int64),
   220  		floatValues: make(map[*MetricDescriptor]float64),
   221  	}
   222  
   223  	for _, desc := range metricsRegistry {
   224  		switch desc.Type {
   225  		case MetricTypeIntCount:
   226  		case MetricTypeIntHisto:
   227  		case MetricTypeIntGauge:
   228  			fmr.intValues[desc] = 0
   229  		case MetricTypeFloatCount:
   230  		case MetricTypeFloatHisto:
   231  			fmr.floatValues[desc] = 0
   232  		}
   233  	}
   234  	return fmr
   235  }
   236  
   237  // verifyLabels verifies that the labels received are of the expected length.
   238  func verifyLabels(t *testing.T, labelsWant []string, optionalLabelsWant []string, labelsGot []string) {
   239  	if len(labelsWant)+len(optionalLabelsWant) != len(labelsGot) {
   240  		t.Fatalf("length of optional labels expected did not match got %v, want %v", len(labelsGot), len(labelsWant)+len(optionalLabelsWant))
   241  	}
   242  }
   243  
   244  func (r *fakeMetricsRecorder) RecordInt64Count(handle *Int64CountHandle, incr int64, labels ...string) {
   245  	verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
   246  	r.intValues[handle.Descriptor()] += incr
   247  }
   248  
   249  func (r *fakeMetricsRecorder) RecordFloat64Count(handle *Float64CountHandle, incr float64, labels ...string) {
   250  	verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
   251  	r.floatValues[handle.Descriptor()] += incr
   252  }
   253  
   254  func (r *fakeMetricsRecorder) RecordInt64Histo(handle *Int64HistoHandle, incr int64, labels ...string) {
   255  	verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
   256  	r.intValues[handle.Descriptor()] += incr
   257  }
   258  
   259  func (r *fakeMetricsRecorder) RecordFloat64Histo(handle *Float64HistoHandle, incr float64, labels ...string) {
   260  	verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
   261  	r.floatValues[handle.Descriptor()] += incr
   262  }
   263  
   264  func (r *fakeMetricsRecorder) RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string) {
   265  	verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
   266  	r.intValues[handle.Descriptor()] += incr
   267  }