go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/tsmon/store/storetest/testing.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package storetest is imported exclusively by tests for Store implementations. 16 package storetest 17 18 import ( 19 "context" 20 "fmt" 21 "sort" 22 "sync" 23 "testing" 24 "time" 25 26 pb "go.chromium.org/luci/common/tsmon/ts_mon_proto" 27 28 "go.chromium.org/luci/common/clock/testclock" 29 "go.chromium.org/luci/common/tsmon/distribution" 30 "go.chromium.org/luci/common/tsmon/field" 31 "go.chromium.org/luci/common/tsmon/monitor" 32 "go.chromium.org/luci/common/tsmon/target" 33 "go.chromium.org/luci/common/tsmon/types" 34 "google.golang.org/protobuf/proto" 35 36 . "github.com/smartystreets/goconvey/convey" 37 . "go.chromium.org/luci/common/testing/assertions" 38 ) 39 40 // Store is a store under test. 41 // 42 // It is a copy of store.Store interface to break module dependency cycle. 43 type Store interface { 44 DefaultTarget() types.Target 45 SetDefaultTarget(t types.Target) 46 47 Get(ctx context.Context, m types.Metric, resetTime time.Time, fieldVals []any) any 48 Set(ctx context.Context, m types.Metric, resetTime time.Time, fieldVals []any, value any) 49 Del(ctx context.Context, m types.Metric, fieldVals []any) 50 Incr(ctx context.Context, m types.Metric, resetTime time.Time, fieldVals []any, delta any) 51 52 GetAll(ctx context.Context) []types.Cell 53 54 Reset(ctx context.Context, m types.Metric) 55 } 56 57 // TestOptions contains options for RunStoreImplementationTests. 58 type TestOptions struct { 59 // Factory creates and returns a new Store implementation. 60 Factory func() Store 61 62 // RegistrationFinished is called after all metrics have been registered. 63 // Store implementations that need to do expensive initialization (that would 64 // otherwise be done in iface.go) can do that here. 65 RegistrationFinished func(Store) 66 67 // GetNumRegisteredMetrics returns the number of metrics registered in the 68 // store. 69 GetNumRegisteredMetrics func(Store) int 70 } 71 72 // RunStoreImplementationTests runs all the standard tests that all store 73 // implementations are expected to pass. When you write a new Store 74 // implementation you should ensure you run these tests against it. 75 func RunStoreImplementationTests(t *testing.T, ctx context.Context, opts TestOptions) { 76 77 distOne := distribution.New(distribution.DefaultBucketer) 78 distOne.Add(4.2) 79 80 distTwo := distribution.New(distribution.DefaultBucketer) 81 distTwo.Add(5.6) 82 distTwo.Add(1.0) 83 84 tests := []struct { 85 typ types.ValueType 86 bucketer *distribution.Bucketer 87 88 values []any 89 wantSetValidator func(any, any) 90 91 deltas []any 92 wantIncrPanic bool 93 wantIncrValue any 94 wantIncrValidator func(any) 95 96 wantStartTimestamp bool 97 }{ 98 { 99 typ: types.CumulativeIntType, 100 values: makeInterfaceSlice(int64(3), int64(4)), 101 deltas: makeInterfaceSlice(int64(3), int64(4)), 102 wantIncrValue: int64(7), 103 wantStartTimestamp: true, 104 }, 105 { 106 typ: types.CumulativeFloatType, 107 values: makeInterfaceSlice(float64(3.2), float64(4.3)), 108 deltas: makeInterfaceSlice(float64(3.2), float64(4.3)), 109 wantIncrValue: float64(7.5), 110 wantStartTimestamp: true, 111 }, 112 { 113 typ: types.CumulativeDistributionType, 114 bucketer: distribution.DefaultBucketer, 115 values: makeInterfaceSlice(distOne, distTwo), 116 deltas: makeInterfaceSlice(float64(3.2), float64(5.3)), 117 wantIncrValidator: func(v any) { 118 d := v.(*distribution.Distribution) 119 So(d.Buckets(), ShouldResemble, []int64{0, 0, 0, 1, 1}) 120 }, 121 wantStartTimestamp: true, 122 }, 123 { 124 typ: types.NonCumulativeIntType, 125 values: makeInterfaceSlice(int64(3), int64(4)), 126 deltas: makeInterfaceSlice(int64(3), int64(4)), 127 wantIncrPanic: true, 128 }, 129 { 130 typ: types.NonCumulativeFloatType, 131 values: makeInterfaceSlice(float64(3.2), float64(4.3)), 132 deltas: makeInterfaceSlice(float64(3.2), float64(4.3)), 133 wantIncrPanic: true, 134 }, 135 { 136 typ: types.NonCumulativeDistributionType, 137 bucketer: distribution.DefaultBucketer, 138 values: makeInterfaceSlice(distOne, distTwo), 139 deltas: makeInterfaceSlice(float64(3.2), float64(5.3)), 140 wantIncrPanic: true, 141 wantSetValidator: func(got, want any) { 142 // The distribution might be serialized/deserialized and lose sums and 143 // counts. 144 So(got.(*distribution.Distribution).Buckets(), ShouldResemble, 145 want.(*distribution.Distribution).Buckets()) 146 }, 147 }, 148 { 149 typ: types.StringType, 150 values: makeInterfaceSlice("hello", "world"), 151 deltas: makeInterfaceSlice("hello", "world"), 152 wantIncrPanic: true, 153 }, 154 { 155 typ: types.BoolType, 156 values: makeInterfaceSlice(true, false), 157 deltas: makeInterfaceSlice(true, false), 158 wantIncrPanic: true, 159 }, 160 } 161 162 Convey("Set and get", t, func() { 163 Convey("With no fields", func() { 164 for i, test := range tests { 165 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 166 var m types.Metric 167 if test.bucketer != nil { 168 m = &fakeDistributionMetric{FakeMetric{ 169 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 170 types.MetricMetadata{}}, test.bucketer} 171 } else { 172 m = &FakeMetric{ 173 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 174 types.MetricMetadata{}, 175 } 176 } 177 178 s := opts.Factory() 179 180 // Value should be nil initially. 181 v := s.Get(ctx, m, time.Time{}, []any{}) 182 So(v, ShouldBeNil) 183 184 // Set and get the value. 185 s.Set(ctx, m, time.Time{}, []any{}, test.values[0]) 186 v = s.Get(ctx, m, time.Time{}, []any{}) 187 if test.wantSetValidator != nil { 188 test.wantSetValidator(v, test.values[0]) 189 } else { 190 So(v, ShouldEqual, test.values[0]) 191 } 192 }) 193 } 194 }) 195 196 Convey("With fields", func() { 197 for i, test := range tests { 198 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 199 var m types.Metric 200 if test.bucketer != nil { 201 m = &fakeDistributionMetric{FakeMetric{ 202 types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType}, 203 types.MetricMetadata{}}, test.bucketer} 204 } else { 205 m = &FakeMetric{ 206 types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType}, 207 types.MetricMetadata{}} 208 } 209 210 s := opts.Factory() 211 212 // Values should be nil initially. 213 v := s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one")) 214 So(v, ShouldBeNil) 215 v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two")) 216 So(v, ShouldBeNil) 217 218 // Set and get the values. 219 s.Set(ctx, m, time.Time{}, makeInterfaceSlice("one"), test.values[0]) 220 s.Set(ctx, m, time.Time{}, makeInterfaceSlice("two"), test.values[1]) 221 222 v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one")) 223 if test.wantSetValidator != nil { 224 test.wantSetValidator(v, test.values[0]) 225 } else { 226 So(v, ShouldEqual, test.values[0]) 227 } 228 229 v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two")) 230 if test.wantSetValidator != nil { 231 test.wantSetValidator(v, test.values[1]) 232 } else { 233 So(v, ShouldEqual, test.values[1]) 234 } 235 }) 236 } 237 }) 238 239 Convey("With a fixed reset time", func() { 240 for i, test := range tests { 241 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 242 var m types.Metric 243 if test.bucketer != nil { 244 m = &fakeDistributionMetric{FakeMetric{ 245 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 246 types.MetricMetadata{}}, test.bucketer} 247 } else { 248 m = &FakeMetric{ 249 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 250 types.MetricMetadata{}} 251 } 252 253 s := opts.Factory() 254 opts.RegistrationFinished(s) 255 256 // Do the set with a fixed time. 257 t := time.Date(1972, 5, 6, 7, 8, 9, 0, time.UTC) 258 s.Set(ctx, m, t, []any{}, test.values[0]) 259 260 v := s.Get(ctx, m, time.Time{}, []any{}) 261 if test.wantSetValidator != nil { 262 test.wantSetValidator(v, test.values[0]) 263 } else { 264 So(v, ShouldEqual, test.values[0]) 265 } 266 267 // Check the time in the Cell is the same. 268 all := s.GetAll(ctx) 269 So(len(all), ShouldEqual, 1) 270 271 msg := monitor.SerializeValue(all[0], testclock.TestRecentTimeUTC) 272 ts := msg.GetStartTimestamp() 273 if test.wantStartTimestamp { 274 So(time.Unix(ts.GetSeconds(), int64(ts.GetNanos())).UTC().String(), ShouldEqual, t.String()) 275 } else { 276 So(time.Unix(ts.GetSeconds(), int64(ts.GetNanos())).UTC().String(), ShouldEqual, testclock.TestRecentTimeUTC.String()) 277 } 278 }) 279 } 280 }) 281 282 Convey("With a target set in the context", func() { 283 for i, test := range tests { 284 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 285 var m types.Metric 286 if test.bucketer != nil { 287 m = &fakeDistributionMetric{FakeMetric{ 288 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 289 types.MetricMetadata{}}, test.bucketer} 290 } else { 291 m = &FakeMetric{ 292 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 293 types.MetricMetadata{}} 294 } 295 296 s := opts.Factory() 297 opts.RegistrationFinished(s) 298 299 // Create a context with a different target. 300 foo := target.Task{ServiceName: "foo"} 301 ctxWithTarget := target.Set(ctx, &foo) 302 303 // Set the first value on the default target, second value on the 304 // different target. 305 s.Set(ctx, m, time.Time{}, []any{}, test.values[0]) 306 s.Set(ctxWithTarget, m, time.Time{}, []any{}, test.values[1]) 307 308 // Get should return different values for different contexts. 309 v := s.Get(ctx, m, time.Time{}, []any{}) 310 if test.wantSetValidator != nil { 311 test.wantSetValidator(v, test.values[0]) 312 } else { 313 So(v, ShouldEqual, test.values[0]) 314 } 315 316 v = s.Get(ctxWithTarget, m, time.Time{}, []any{}) 317 if test.wantSetValidator != nil { 318 test.wantSetValidator(v, test.values[1]) 319 } else { 320 So(v, ShouldEqual, test.values[1]) 321 } 322 323 // The targets should be set in the Cells. 324 all := s.GetAll(ctx) 325 So(len(all), ShouldEqual, 2) 326 327 coll := monitor.SerializeCells(all, testclock.TestRecentTimeUTC) 328 329 s0 := coll[0].RootLabels[3] 330 s1 := coll[1].RootLabels[3] 331 332 serviceName := &pb.MetricsCollection_RootLabels{ 333 Key: proto.String("service_name"), 334 Value: &pb.MetricsCollection_RootLabels_StringValue{ 335 StringValue: foo.ServiceName, 336 }, 337 } 338 defaultServiceName := &pb.MetricsCollection_RootLabels{ 339 Key: proto.String("service_name"), 340 Value: &pb.MetricsCollection_RootLabels_StringValue{ 341 StringValue: s.DefaultTarget().(*target.Task).ServiceName, 342 }, 343 } 344 345 if proto.Equal(s0, serviceName) { 346 So(s1, ShouldResembleProto, defaultServiceName) 347 } else if proto.Equal(s1, serviceName) { 348 So(s0, ShouldResembleProto, defaultServiceName) 349 } else { 350 t.Fail() 351 } 352 }) 353 } 354 }) 355 356 Convey("With a decreasing value", func() { 357 for i, test := range tests { 358 if !test.typ.IsCumulative() || test.bucketer != nil { 359 continue 360 } 361 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 362 m := &FakeMetric{ 363 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 364 types.MetricMetadata{}} 365 s := opts.Factory() 366 367 // Set the bigger value. 368 s.Set(ctx, m, time.Time{}, []any{}, test.values[1]) 369 So(s.Get(ctx, m, time.Time{}, []any{}), ShouldResemble, test.values[1]) 370 371 // Setting the smaller value should be ignored. 372 s.Set(ctx, m, time.Time{}, []any{}, test.values[0]) 373 So(s.Get(ctx, m, time.Time{}, []any{}), ShouldResemble, test.values[1]) 374 }) 375 } 376 }) 377 }) 378 379 Convey("Increment and get", t, func() { 380 Convey("With no fields", func() { 381 for i, test := range tests { 382 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 383 var m types.Metric 384 if test.bucketer != nil { 385 m = &fakeDistributionMetric{FakeMetric{ 386 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 387 types.MetricMetadata{}}, test.bucketer} 388 389 } else { 390 m = &FakeMetric{ 391 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 392 types.MetricMetadata{}} 393 } 394 395 s := opts.Factory() 396 397 // Value should be nil initially. 398 v := s.Get(ctx, m, time.Time{}, []any{}) 399 So(v, ShouldBeNil) 400 401 // Increment the metric. 402 for _, delta := range test.deltas { 403 call := func() { s.Incr(ctx, m, time.Time{}, []any{}, delta) } 404 if test.wantIncrPanic { 405 So(call, ShouldPanic) 406 } else { 407 call() 408 } 409 } 410 411 // Get the final value. 412 v = s.Get(ctx, m, time.Time{}, []any{}) 413 if test.wantIncrValue != nil { 414 So(v, ShouldEqual, test.wantIncrValue) 415 } else if test.wantIncrValidator != nil { 416 test.wantIncrValidator(v) 417 } 418 }) 419 } 420 }) 421 422 Convey("With fields", func() { 423 for i, test := range tests { 424 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 425 var m types.Metric 426 if test.bucketer != nil { 427 m = &fakeDistributionMetric{FakeMetric{ 428 types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType}, 429 types.MetricMetadata{}}, test.bucketer} 430 } else { 431 m = &FakeMetric{ 432 types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType}, 433 types.MetricMetadata{}} 434 } 435 436 s := opts.Factory() 437 438 // Values should be nil initially. 439 v := s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one")) 440 So(v, ShouldBeNil) 441 v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two")) 442 So(v, ShouldBeNil) 443 444 // Increment one cell. 445 for _, delta := range test.deltas { 446 call := func() { s.Incr(ctx, m, time.Time{}, makeInterfaceSlice("one"), delta) } 447 if test.wantIncrPanic { 448 So(call, ShouldPanic) 449 } else { 450 call() 451 } 452 } 453 454 // Get the final value. 455 v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one")) 456 if test.wantIncrValue != nil { 457 So(v, ShouldEqual, test.wantIncrValue) 458 } else if test.wantIncrValidator != nil { 459 test.wantIncrValidator(v) 460 } 461 462 // Another cell should still be nil. 463 v = s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two")) 464 So(v, ShouldBeNil) 465 }) 466 } 467 }) 468 469 Convey("With a fixed reset time", func() { 470 for i, test := range tests { 471 if test.wantIncrPanic { 472 continue 473 } 474 475 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 476 var m types.Metric 477 if test.bucketer != nil { 478 m = &fakeDistributionMetric{FakeMetric{ 479 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 480 types.MetricMetadata{}}, test.bucketer} 481 } else { 482 m = &FakeMetric{ 483 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 484 types.MetricMetadata{}} 485 } 486 487 s := opts.Factory() 488 opts.RegistrationFinished(s) 489 490 // Do the incr with a fixed time. 491 t := time.Date(1972, 5, 6, 7, 8, 9, 0, time.UTC) 492 s.Incr(ctx, m, t, []any{}, test.deltas[0]) 493 494 // Check the time in the Cell is the same. 495 all := s.GetAll(ctx) 496 So(len(all), ShouldEqual, 1) 497 498 msg := monitor.SerializeValue(all[0], testclock.TestRecentTimeUTC) 499 if test.wantStartTimestamp { 500 ts := msg.GetStartTimestamp() 501 So(time.Unix(ts.GetSeconds(), int64(ts.GetNanos())).UTC().String(), ShouldEqual, t.String()) 502 } else { 503 So(msg.StartTimestamp, ShouldBeNil) 504 } 505 }) 506 } 507 }) 508 509 Convey("With a target set in the context", func() { 510 for i, test := range tests { 511 if test.wantIncrPanic { 512 continue 513 } 514 515 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 516 var m types.Metric 517 if test.bucketer != nil { 518 m = &fakeDistributionMetric{ 519 FakeMetric{types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 520 types.MetricMetadata{}}, 521 test.bucketer} 522 } else { 523 m = &FakeMetric{ 524 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 525 types.MetricMetadata{}} 526 } 527 528 s := opts.Factory() 529 opts.RegistrationFinished(s) 530 531 // Create a context with a different target. 532 foo := target.Task{ServiceName: "foo"} 533 ctxWithTarget := target.Set(ctx, &foo) 534 535 // Incr the first delta on the default target, second delta on the 536 // different target. 537 s.Incr(ctx, m, time.Time{}, []any{}, test.deltas[0]) 538 s.Incr(ctxWithTarget, m, time.Time{}, []any{}, test.deltas[1]) 539 540 // Get should return different values for different contexts. 541 v1 := s.Get(ctx, m, time.Time{}, []any{}) 542 v2 := s.Get(ctxWithTarget, m, time.Time{}, []any{}) 543 So(v1, ShouldNotEqual, v2) 544 545 // The targets should be set in the Cells. 546 all := s.GetAll(ctx) 547 So(len(all), ShouldEqual, 2) 548 549 coll := monitor.SerializeCells(all, testclock.TestRecentTimeUTC) 550 551 s0 := coll[0].RootLabels[3] 552 s1 := coll[1].RootLabels[3] 553 554 serviceName := &pb.MetricsCollection_RootLabels{ 555 Key: proto.String("service_name"), 556 Value: &pb.MetricsCollection_RootLabels_StringValue{ 557 StringValue: foo.ServiceName, 558 }, 559 } 560 defaultServiceName := &pb.MetricsCollection_RootLabels{ 561 Key: proto.String("service_name"), 562 Value: &pb.MetricsCollection_RootLabels_StringValue{ 563 StringValue: s.DefaultTarget().(*target.Task).ServiceName, 564 }, 565 } 566 567 if proto.Equal(s0, serviceName) { 568 So(s1, ShouldResembleProto, defaultServiceName) 569 } else if proto.Equal(s1, serviceName) { 570 So(s0, ShouldResembleProto, defaultServiceName) 571 } else { 572 t.Fail() 573 } 574 }) 575 } 576 }) 577 }) 578 579 Convey("GetAll", t, func() { 580 ctx, tc := testclock.UseTime(ctx, testclock.TestRecentTimeUTC) 581 582 s := opts.Factory() 583 foo := &FakeMetric{ 584 types.MetricInfo{Name: "foo", Description: "", Fields: []field.Field{}, 585 ValueType: types.NonCumulativeIntType, TargetType: target.NilType}, 586 types.MetricMetadata{Units: types.Seconds}} 587 bar := &FakeMetric{ 588 types.MetricInfo{Name: "bar", Description: "", Fields: []field.Field{field.String("f")}, 589 ValueType: types.StringType, TargetType: target.NilType}, 590 types.MetricMetadata{Units: types.Bytes}} 591 baz := &FakeMetric{ 592 types.MetricInfo{Name: "baz", Description: "", Fields: []field.Field{field.String("f")}, 593 ValueType: types.NonCumulativeFloatType, TargetType: target.NilType}, 594 types.MetricMetadata{}} 595 qux := &FakeMetric{ 596 types.MetricInfo{Name: "qux", Description: "", Fields: []field.Field{field.String("f")}, 597 ValueType: types.CumulativeDistributionType, TargetType: target.NilType}, 598 types.MetricMetadata{}} 599 opts.RegistrationFinished(s) 600 601 // Add test records. We increment the test clock each time so that the added 602 // records sort deterministically using sortableCellSlice. 603 for _, m := range []struct { 604 metric types.Metric 605 fieldvals []any 606 value any 607 }{ 608 {foo, []any{}, int64(42)}, 609 {bar, makeInterfaceSlice("one"), "hello"}, 610 {bar, makeInterfaceSlice("two"), "world"}, 611 {baz, makeInterfaceSlice("three"), 1.23}, 612 {baz, makeInterfaceSlice("four"), 4.56}, 613 {qux, makeInterfaceSlice("five"), distribution.New(nil)}, 614 } { 615 s.Set(ctx, m.metric, time.Time{}, m.fieldvals, m.value) 616 tc.Add(time.Second) 617 } 618 619 got := s.GetAll(ctx) 620 621 // Store operations made after GetAll should not be visible in the snapshot. 622 s.Set(ctx, baz, time.Time{}, makeInterfaceSlice("four"), 3.14) 623 s.Incr(ctx, qux, time.Time{}, makeInterfaceSlice("five"), float64(10.0)) 624 625 sort.Sort(sortableCellSlice(got)) 626 want := []types.Cell{ 627 { 628 types.MetricInfo{ 629 Name: "foo", 630 Fields: []field.Field{}, 631 ValueType: types.NonCumulativeIntType, 632 TargetType: target.NilType, 633 }, 634 types.MetricMetadata{Units: types.Seconds}, 635 types.CellData{ 636 FieldVals: []any{}, 637 Value: int64(42), 638 }, 639 }, 640 { 641 types.MetricInfo{ 642 Name: "bar", 643 Fields: []field.Field{field.String("f")}, 644 ValueType: types.StringType, 645 TargetType: target.NilType, 646 }, 647 types.MetricMetadata{Units: types.Bytes}, 648 types.CellData{ 649 FieldVals: makeInterfaceSlice("one"), 650 Value: "hello", 651 }, 652 }, 653 { 654 types.MetricInfo{ 655 Name: "bar", 656 Fields: []field.Field{field.String("f")}, 657 ValueType: types.StringType, 658 TargetType: target.NilType, 659 }, 660 types.MetricMetadata{Units: types.Bytes}, 661 types.CellData{ 662 FieldVals: makeInterfaceSlice("two"), 663 Value: "world", 664 }, 665 }, 666 { 667 types.MetricInfo{ 668 Name: "baz", 669 Fields: []field.Field{field.String("f")}, 670 ValueType: types.NonCumulativeFloatType, 671 TargetType: target.NilType, 672 }, 673 types.MetricMetadata{}, 674 types.CellData{ 675 FieldVals: makeInterfaceSlice("three"), 676 Value: 1.23, 677 }, 678 }, 679 { 680 types.MetricInfo{ 681 Name: "baz", 682 Fields: []field.Field{field.String("f")}, 683 ValueType: types.NonCumulativeFloatType, 684 TargetType: target.NilType, 685 }, 686 types.MetricMetadata{}, 687 types.CellData{ 688 FieldVals: makeInterfaceSlice("four"), 689 Value: 4.56, 690 }, 691 }, 692 { 693 types.MetricInfo{ 694 Name: "qux", 695 Fields: []field.Field{field.String("f")}, 696 ValueType: types.CumulativeDistributionType, 697 TargetType: target.NilType, 698 }, 699 types.MetricMetadata{}, 700 types.CellData{ 701 FieldVals: makeInterfaceSlice("five"), 702 Value: distribution.New(nil), 703 }, 704 }, 705 } 706 So(len(got), ShouldEqual, len(want)) 707 708 for i, g := range got { 709 w := want[i] 710 711 Convey(fmt.Sprintf("%d", i), func() { 712 So(g.Name, ShouldEqual, w.Name) 713 So(len(g.Fields), ShouldEqual, len(w.Fields)) 714 So(g.ValueType, ShouldEqual, w.ValueType) 715 So(g.FieldVals, ShouldResemble, w.FieldVals) 716 So(g.Value, ShouldResemble, w.Value) 717 So(g.Units, ShouldEqual, w.Units) 718 }) 719 } 720 }) 721 722 Convey("Set and del", t, func() { 723 Convey("With no fields", func() { 724 for i, test := range tests { 725 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 726 var m types.Metric 727 if test.bucketer != nil { 728 m = &fakeDistributionMetric{FakeMetric{ 729 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 730 types.MetricMetadata{}}, test.bucketer} 731 732 } else { 733 m = &FakeMetric{ 734 types.MetricInfo{"m", "", []field.Field{}, test.typ, target.NilType}, 735 types.MetricMetadata{}} 736 } 737 738 s := opts.Factory() 739 740 // Value should be nil initially. 741 So(s.Get(ctx, m, time.Time{}, []any{}), ShouldBeNil) 742 743 // Set and get the value. 744 s.Set(ctx, m, time.Time{}, []any{}, test.values[0]) 745 v := s.Get(ctx, m, time.Time{}, []any{}) 746 if test.wantSetValidator != nil { 747 test.wantSetValidator(v, test.values[0]) 748 } else { 749 So(v, ShouldEqual, test.values[0]) 750 } 751 752 // Delete the cell. Then, get should return nil. 753 s.Del(ctx, m, []any{}) 754 So(s.Get(ctx, m, time.Time{}, []any{}), ShouldBeNil) 755 }) 756 } 757 }) 758 759 Convey("With fields", func() { 760 for i, test := range tests { 761 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 762 var m types.Metric 763 if test.bucketer != nil { 764 m = &fakeDistributionMetric{FakeMetric{ 765 types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType}, 766 types.MetricMetadata{}}, test.bucketer} 767 } else { 768 m = &FakeMetric{ 769 types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType}, 770 types.MetricMetadata{}} 771 } 772 773 s := opts.Factory() 774 775 // Values should be nil initially. 776 So(s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one")), ShouldBeNil) 777 So(s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two")), ShouldBeNil) 778 779 // Set both and then delete "one". 780 s.Set(ctx, m, time.Time{}, makeInterfaceSlice("one"), test.values[0]) 781 s.Set(ctx, m, time.Time{}, makeInterfaceSlice("two"), test.values[1]) 782 s.Del(ctx, m, makeInterfaceSlice("one")) 783 784 // Get should return nil for "one", but the value for "two". 785 So(s.Get(ctx, m, time.Time{}, makeInterfaceSlice("one")), ShouldBeNil) 786 v := s.Get(ctx, m, time.Time{}, makeInterfaceSlice("two")) 787 if test.wantSetValidator != nil { 788 test.wantSetValidator(v, test.values[1]) 789 } else { 790 So(v, ShouldEqual, test.values[1]) 791 } 792 }) 793 } 794 }) 795 796 Convey("With a target set in the context", func() { 797 for i, test := range tests { 798 if test.wantIncrPanic { 799 continue 800 } 801 802 Convey(fmt.Sprintf("%d. %s", i, test.typ), func() { 803 var m types.Metric 804 if test.bucketer != nil { 805 m = &fakeDistributionMetric{FakeMetric{ 806 types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType}, 807 types.MetricMetadata{}}, test.bucketer} 808 } else { 809 m = &FakeMetric{ 810 types.MetricInfo{"m", "", []field.Field{field.String("f")}, test.typ, target.NilType}, 811 types.MetricMetadata{}} 812 } 813 814 s := opts.Factory() 815 opts.RegistrationFinished(s) 816 817 // Create a context with a different target. 818 foo := target.Task{ServiceName: "foo"} 819 ctxWithTarget := target.Set(ctx, &foo) 820 821 // Set the first value on the default target, second value on the 822 // different target. Note that both are set with the same field value, 823 // "one". 824 fvs := func() []any { return makeInterfaceSlice("one") } 825 s.Set(ctx, m, time.Time{}, fvs(), test.values[0]) 826 s.Set(ctxWithTarget, m, time.Time{}, fvs(), test.values[1]) 827 828 // Get should return different values for different contexts. 829 v1 := s.Get(ctx, m, time.Time{}, fvs()) 830 v2 := s.Get(ctxWithTarget, m, time.Time{}, fvs()) 831 So(v1, ShouldNotEqual, v2) 832 833 // Delete the cell with the custom target. Then, get should return 834 // the value for the default target, and nil for the custom target. 835 s.Del(ctxWithTarget, m, fvs()) 836 So(s.Get(ctx, m, time.Time{}, fvs()), ShouldEqual, test.values[0]) 837 So(s.Get(ctxWithTarget, m, time.Time{}, fvs()), ShouldBeNil) 838 }) 839 } 840 }) 841 }) 842 843 Convey("Concurrency", t, func() { 844 const numIterations = 100 845 const numGoroutines = 32 846 847 Convey("Incr", func(c C) { 848 s := opts.Factory() 849 m := &FakeMetric{ 850 types.MetricInfo{"m", "", []field.Field{}, types.CumulativeIntType, target.NilType}, 851 types.MetricMetadata{}} 852 853 wg := sync.WaitGroup{} 854 f := func(n int) { 855 defer wg.Done() 856 for i := 0; i < numIterations; i++ { 857 s.Incr(ctx, m, time.Time{}, []any{}, int64(1)) 858 } 859 } 860 861 for n := 0; n < numGoroutines; n++ { 862 wg.Add(1) 863 go f(n) 864 } 865 wg.Wait() 866 867 val := s.Get(ctx, m, time.Time{}, []any{}) 868 So(val, ShouldEqual, numIterations*numGoroutines) 869 }) 870 }) 871 872 Convey("Multiple targets with the same TargetType", t, func() { 873 Convey("Gets from context", func() { 874 s := opts.Factory() 875 m := &FakeMetric{ 876 types.MetricInfo{"m", "", []field.Field{}, types.NonCumulativeIntType, target.NilType}, 877 types.MetricMetadata{}} 878 opts.RegistrationFinished(s) 879 880 t := target.Task{ServiceName: "foo"} 881 ctxWithTarget := target.Set(ctx, &t) 882 883 s.Set(ctx, m, time.Time{}, []any{}, int64(42)) 884 s.Set(ctxWithTarget, m, time.Time{}, []any{}, int64(43)) 885 886 val := s.Get(ctx, m, time.Time{}, []any{}) 887 So(val, ShouldEqual, 42) 888 889 val = s.Get(ctxWithTarget, m, time.Time{}, []any{}) 890 So(val, ShouldEqual, 43) 891 892 all := s.GetAll(ctx) 893 So(len(all), ShouldEqual, 2) 894 895 // The order is undefined. 896 if all[0].Value.(int64) == 42 { 897 So(all[0].Target, ShouldResemble, s.DefaultTarget()) 898 So(all[1].Target, ShouldResemble, &t) 899 } else { 900 So(all[0].Target, ShouldResemble, &t) 901 So(all[1].Target, ShouldResemble, s.DefaultTarget()) 902 } 903 }) 904 }) 905 906 Convey("Multiple targets with multiple TargetTypes", t, func() { 907 Convey("Gets from context", func() { 908 s := opts.Factory() 909 // Two metrics with the same metric name, but different types. 910 mTask := &FakeMetric{ 911 types.MetricInfo{"m", "", []field.Field{}, types.NonCumulativeIntType, target.TaskType}, 912 types.MetricMetadata{}} 913 mDevice := &FakeMetric{ 914 types.MetricInfo{"m", "", []field.Field{}, types.NonCumulativeIntType, target.DeviceType}, 915 types.MetricMetadata{}} 916 opts.RegistrationFinished(s) 917 918 taskTarget := target.Task{ServiceName: "foo"} 919 deviceTarget := target.NetworkDevice{Hostname: "bar"} 920 ctxWithTarget := target.Set(target.Set(ctx, &taskTarget), &deviceTarget) 921 922 s.Set(ctxWithTarget, mTask, time.Time{}, []any{}, int64(42)) 923 s.Set(ctxWithTarget, mDevice, time.Time{}, []any{}, int64(43)) 924 925 val := s.Get(ctxWithTarget, mTask, time.Time{}, []any{}) 926 So(val, ShouldEqual, 42) 927 928 val = s.Get(ctxWithTarget, mDevice, time.Time{}, []any{}) 929 So(val, ShouldEqual, 43) 930 931 all := s.GetAll(ctx) 932 So(len(all), ShouldEqual, 2) 933 934 // The order is undefined. 935 if all[0].Value.(int64) == 42 { 936 So(all[0].Target, ShouldResemble, &taskTarget) 937 So(all[1].Target, ShouldResemble, &deviceTarget) 938 } else { 939 So(all[0].Target, ShouldResemble, &deviceTarget) 940 So(all[1].Target, ShouldResemble, &taskTarget) 941 } 942 }) 943 }) 944 } 945 946 func makeInterfaceSlice(v ...any) []any { 947 return v 948 } 949 950 // FakeMetric is a fake Metric implementation. 951 type FakeMetric struct { 952 types.MetricInfo 953 types.MetricMetadata 954 } 955 956 // Info implements Metric.Info 957 func (m *FakeMetric) Info() types.MetricInfo { return m.MetricInfo } 958 959 // Metadata implements Metric.Metadata 960 func (m *FakeMetric) Metadata() types.MetricMetadata { return m.MetricMetadata } 961 962 // SetFixedResetTime implements Metric.SetFixedResetTime. 963 func (m *FakeMetric) SetFixedResetTime(t time.Time) {} 964 965 type fakeDistributionMetric struct { 966 FakeMetric 967 968 bucketer *distribution.Bucketer 969 } 970 971 func (m *fakeDistributionMetric) Bucketer() *distribution.Bucketer { return m.bucketer } 972 973 type sortableCellSlice []types.Cell 974 975 func (s sortableCellSlice) Len() int { return len(s) } 976 func (s sortableCellSlice) Less(i, j int) bool { 977 return s[i].ResetTime.UnixNano() < s[j].ResetTime.UnixNano() 978 } 979 func (s sortableCellSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }