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 }