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] }