github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cmd/services/m3coordinator/downsample/downsampler_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  //nolint: dupl
    22  package downsample
    23  
    24  import (
    25  	"bytes"
    26  	"fmt"
    27  	"os"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/aggregator/client"
    33  	clusterclient "github.com/m3db/m3/src/cluster/client"
    34  	"github.com/m3db/m3/src/cluster/kv"
    35  	"github.com/m3db/m3/src/cluster/kv/mem"
    36  	dbclient "github.com/m3db/m3/src/dbnode/client"
    37  	"github.com/m3db/m3/src/metrics/aggregation"
    38  	"github.com/m3db/m3/src/metrics/generated/proto/metricpb"
    39  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    40  	"github.com/m3db/m3/src/metrics/matcher"
    41  	"github.com/m3db/m3/src/metrics/metadata"
    42  	"github.com/m3db/m3/src/metrics/metric/id"
    43  	"github.com/m3db/m3/src/metrics/metric/unaggregated"
    44  	"github.com/m3db/m3/src/metrics/policy"
    45  	"github.com/m3db/m3/src/metrics/rules"
    46  	ruleskv "github.com/m3db/m3/src/metrics/rules/store/kv"
    47  	"github.com/m3db/m3/src/metrics/rules/view"
    48  	"github.com/m3db/m3/src/metrics/transformation"
    49  	"github.com/m3db/m3/src/query/models"
    50  	"github.com/m3db/m3/src/query/storage"
    51  	"github.com/m3db/m3/src/query/storage/m3"
    52  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    53  	"github.com/m3db/m3/src/query/storage/mock"
    54  	"github.com/m3db/m3/src/query/ts"
    55  	"github.com/m3db/m3/src/x/clock"
    56  	"github.com/m3db/m3/src/x/ident"
    57  	"github.com/m3db/m3/src/x/instrument"
    58  	xio "github.com/m3db/m3/src/x/io"
    59  	"github.com/m3db/m3/src/x/pool"
    60  	"github.com/m3db/m3/src/x/serialize"
    61  	xtest "github.com/m3db/m3/src/x/test"
    62  	xtime "github.com/m3db/m3/src/x/time"
    63  
    64  	"github.com/golang/mock/gomock"
    65  	"github.com/stretchr/testify/assert"
    66  	"github.com/stretchr/testify/require"
    67  	"go.uber.org/zap"
    68  )
    69  
    70  var (
    71  	testAggregationType            = aggregation.Sum
    72  	testAggregationStoragePolicies = []policy.StoragePolicy{
    73  		policy.MustParseStoragePolicy("2s:1d"),
    74  	}
    75  )
    76  
    77  const (
    78  	nameTag = "__name__"
    79  )
    80  
    81  func TestDownsamplerAggregationWithAutoMappingRulesFromNamespacesWatcher(t *testing.T) {
    82  	t.Parallel()
    83  
    84  	ctrl := xtest.NewController(t)
    85  	defer ctrl.Finish()
    86  
    87  	gaugeMetrics, _ := testGaugeMetrics(testGaugeMetricsOptions{})
    88  	require.Equal(t, 1, len(gaugeMetrics))
    89  
    90  	gaugeMetric := gaugeMetrics[0]
    91  	numSamples := len(gaugeMetric.samples)
    92  
    93  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
    94  		ingest: &testDownsamplerOptionsIngest{
    95  			gaugeMetrics: gaugeMetrics,
    96  		},
    97  		expect: &testDownsamplerOptionsExpect{
    98  			writes: []testExpectedWrite{
    99  				{
   100  					tags: gaugeMetric.tags,
   101  					// NB(nate): Automapping rules generated from cluster namespaces currently
   102  					// hardcode 'Last' as the aggregation type. As such, expect value to be the last value
   103  					// in the sample.
   104  					values: []expectedValue{{value: gaugeMetric.samples[numSamples-1]}},
   105  				},
   106  			},
   107  		},
   108  	})
   109  
   110  	require.False(t, testDownsampler.downsampler.Enabled())
   111  
   112  	origStagedMetadata := originalStagedMetadata(t, testDownsampler)
   113  
   114  	session := dbclient.NewMockSession(ctrl)
   115  	setAggregatedNamespaces(t, testDownsampler, session, m3.AggregatedClusterNamespaceDefinition{
   116  		NamespaceID: ident.StringID("2s:1d"),
   117  		Resolution:  2 * time.Second,
   118  		Retention:   24 * time.Hour,
   119  		Session:     session,
   120  	})
   121  
   122  	waitForStagedMetadataUpdate(t, testDownsampler, origStagedMetadata)
   123  
   124  	require.True(t, testDownsampler.downsampler.Enabled())
   125  
   126  	// Test expected output
   127  	testDownsamplerAggregation(t, testDownsampler)
   128  }
   129  
   130  func TestDownsamplerAggregationDownsamplesRawMetricWithRollupRule(t *testing.T) {
   131  	t.Parallel()
   132  
   133  	gaugeMetric := testGaugeMetric{
   134  		tags: map[string]string{
   135  			nameTag:         "http_requests",
   136  			"app":           "nginx_edge",
   137  			"status_code":   "500",
   138  			"endpoint":      "/foo/bar",
   139  			"not_rolled_up": "not_rolled_up_value",
   140  		},
   141  		timedSamples: []testGaugeMetricTimedSample{
   142  			{value: 42},
   143  			{value: 64, offset: 1 * time.Second},
   144  		},
   145  	}
   146  	res := 1 * time.Second
   147  	ret := 30 * 24 * time.Hour
   148  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   149  		rulesConfig: &RulesConfiguration{
   150  			RollupRules: []RollupRuleConfiguration{
   151  				{
   152  					Filter: fmt.Sprintf(
   153  						"%s:http_requests app:* status_code:* endpoint:*",
   154  						nameTag),
   155  					Transforms: []TransformConfiguration{
   156  						{
   157  							Transform: &TransformOperationConfiguration{
   158  								Type: transformation.PerSecond,
   159  							},
   160  						},
   161  						{
   162  							Rollup: &RollupOperationConfiguration{
   163  								MetricName:   "http_requests_by_status_code",
   164  								GroupBy:      []string{"app", "status_code", "endpoint"},
   165  								Aggregations: []aggregation.Type{aggregation.Sum},
   166  							},
   167  						},
   168  					},
   169  					StoragePolicies: []StoragePolicyConfiguration{
   170  						{
   171  							Resolution: res,
   172  							Retention:  ret,
   173  						},
   174  					},
   175  				},
   176  			},
   177  		},
   178  		ingest: &testDownsamplerOptionsIngest{
   179  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   180  		},
   181  		expect: &testDownsamplerOptionsExpect{
   182  			writes: []testExpectedWrite{
   183  				// aggregated rollup metric
   184  				{
   185  					tags: map[string]string{
   186  						nameTag:               "http_requests_by_status_code",
   187  						string(rollupTagName): string(rollupTagValue),
   188  						"app":                 "nginx_edge",
   189  						"status_code":         "500",
   190  						"endpoint":            "/foo/bar",
   191  					},
   192  					values: []expectedValue{{value: 22}},
   193  					attributes: &storagemetadata.Attributes{
   194  						MetricsType: storagemetadata.AggregatedMetricsType,
   195  						Resolution:  res,
   196  						Retention:   ret,
   197  					},
   198  				},
   199  				// raw aggregated metric
   200  				{
   201  					tags:   gaugeMetric.tags,
   202  					values: []expectedValue{{value: 42}, {value: 64}},
   203  				},
   204  			},
   205  		},
   206  	})
   207  
   208  	// Setup auto-mapping rules.
   209  	require.False(t, testDownsampler.downsampler.Enabled())
   210  	origStagedMetadata := originalStagedMetadata(t, testDownsampler)
   211  	ctrl := xtest.NewController(t)
   212  	defer ctrl.Finish()
   213  	session := dbclient.NewMockSession(ctrl)
   214  	setAggregatedNamespaces(t, testDownsampler, session, m3.AggregatedClusterNamespaceDefinition{
   215  		NamespaceID: ident.StringID("1s:30d"),
   216  		Resolution:  res,
   217  		Retention:   ret,
   218  		Session:     session,
   219  	})
   220  	waitForStagedMetadataUpdate(t, testDownsampler, origStagedMetadata)
   221  	require.True(t, testDownsampler.downsampler.Enabled())
   222  
   223  	// Test expected output
   224  	testDownsamplerAggregation(t, testDownsampler)
   225  }
   226  
   227  func TestDownsamplerEmptyGroupBy(t *testing.T) {
   228  	t.Parallel()
   229  
   230  	requestMetric := testGaugeMetric{
   231  		tags: map[string]string{
   232  			nameTag: "http_requests",
   233  		},
   234  		timedSamples: []testGaugeMetricTimedSample{
   235  			{value: 42},
   236  			{value: 64, offset: 1 * time.Second},
   237  		},
   238  	}
   239  	errorMetric := testGaugeMetric{
   240  		tags: map[string]string{
   241  			nameTag: "http_errors",
   242  		},
   243  		timedSamples: []testGaugeMetricTimedSample{
   244  			{value: 43},
   245  			{value: 65, offset: 1 * time.Second},
   246  		},
   247  	}
   248  	res := 1 * time.Second
   249  	ret := 30 * 24 * time.Hour
   250  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   251  		rulesConfig: &RulesConfiguration{
   252  			RollupRules: []RollupRuleConfiguration{
   253  				{
   254  					Filter: fmt.Sprintf("%s:http_*", nameTag),
   255  					Transforms: []TransformConfiguration{
   256  						{
   257  							Transform: &TransformOperationConfiguration{
   258  								Type: transformation.PerSecond,
   259  							},
   260  						},
   261  						{
   262  							Rollup: &RollupOperationConfiguration{
   263  								MetricName:   "http_all",
   264  								GroupBy:      []string{},
   265  								Aggregations: []aggregation.Type{aggregation.Sum},
   266  							},
   267  						},
   268  					},
   269  					StoragePolicies: []StoragePolicyConfiguration{
   270  						{
   271  							Resolution: res,
   272  							Retention:  ret,
   273  						},
   274  					},
   275  				},
   276  			},
   277  		},
   278  		ingest: &testDownsamplerOptionsIngest{
   279  			gaugeMetrics: []testGaugeMetric{requestMetric, errorMetric},
   280  		},
   281  		expect: &testDownsamplerOptionsExpect{
   282  			writes: []testExpectedWrite{
   283  				// aggregated rollup metric
   284  				{
   285  					tags: map[string]string{
   286  						nameTag:               "http_all",
   287  						string(rollupTagName): string(rollupTagValue),
   288  					},
   289  					values: []expectedValue{{value: 22 * 2}},
   290  					attributes: &storagemetadata.Attributes{
   291  						MetricsType: storagemetadata.AggregatedMetricsType,
   292  						Resolution:  res,
   293  						Retention:   ret,
   294  					},
   295  				},
   296  				// raw aggregated metric
   297  				{
   298  					tags:   requestMetric.tags,
   299  					values: []expectedValue{{value: 42}, {value: 64}},
   300  				},
   301  				{
   302  					tags:   errorMetric.tags,
   303  					values: []expectedValue{{value: 43}, {value: 65}},
   304  				},
   305  			},
   306  		},
   307  	})
   308  
   309  	// Setup auto-mapping rules.
   310  	require.False(t, testDownsampler.downsampler.Enabled())
   311  	origStagedMetadata := originalStagedMetadata(t, testDownsampler)
   312  	ctrl := xtest.NewController(t)
   313  	defer ctrl.Finish()
   314  	session := dbclient.NewMockSession(ctrl)
   315  	setAggregatedNamespaces(t, testDownsampler, session, m3.AggregatedClusterNamespaceDefinition{
   316  		NamespaceID: ident.StringID("1s:30d"),
   317  		Resolution:  res,
   318  		Retention:   ret,
   319  		Session:     session,
   320  	})
   321  	waitForStagedMetadataUpdate(t, testDownsampler, origStagedMetadata)
   322  	require.True(t, testDownsampler.downsampler.Enabled())
   323  
   324  	// Test expected output
   325  	testDownsamplerAggregation(t, testDownsampler)
   326  }
   327  
   328  func TestDownsamplerAggregationDoesNotDownsampleRawMetricWithRollupRulesWithoutRollup(t *testing.T) {
   329  	t.Parallel()
   330  
   331  	gaugeMetric := testGaugeMetric{
   332  		tags: map[string]string{
   333  			nameTag:       "http_requests",
   334  			"app":         "nginx_edge",
   335  			"status_code": "500",
   336  			"endpoint":    "/foo/bar",
   337  		},
   338  		timedSamples: []testGaugeMetricTimedSample{
   339  			{value: 42},
   340  			{value: 64, offset: 1 * time.Second},
   341  		},
   342  	}
   343  	res := 1 * time.Second
   344  	ret := 30 * 24 * time.Hour
   345  
   346  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   347  		rulesConfig: &RulesConfiguration{
   348  			RollupRules: []RollupRuleConfiguration{
   349  				{
   350  					Filter: fmt.Sprintf(
   351  						"%s:http_requests app:* status_code:* endpoint:*",
   352  						nameTag),
   353  					Transforms: []TransformConfiguration{
   354  						{
   355  							Aggregate: &AggregateOperationConfiguration{
   356  								Type: aggregation.Sum,
   357  							},
   358  						},
   359  						{
   360  							Transform: &TransformOperationConfiguration{
   361  								Type: transformation.Add,
   362  							},
   363  						},
   364  					},
   365  					StoragePolicies: []StoragePolicyConfiguration{
   366  						{
   367  							Resolution: res,
   368  							Retention:  ret,
   369  						},
   370  					},
   371  				},
   372  			},
   373  		},
   374  		ingest: &testDownsamplerOptionsIngest{
   375  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   376  		},
   377  		expect: &testDownsamplerOptionsExpect{
   378  			writes: []testExpectedWrite{
   379  				// mapped metric
   380  				{
   381  					tags: map[string]string{
   382  						nameTag:       "http_requests",
   383  						"app":         "nginx_edge",
   384  						"status_code": "500",
   385  						"endpoint":    "/foo/bar",
   386  					},
   387  					values: []expectedValue{{value: 42}, {value: 106, offset: 1 * time.Second}},
   388  					attributes: &storagemetadata.Attributes{
   389  						MetricsType: storagemetadata.AggregatedMetricsType,
   390  						Resolution:  res,
   391  						Retention:   ret,
   392  					},
   393  				},
   394  			},
   395  		},
   396  	})
   397  
   398  	// Setup auto-mapping rules.
   399  	require.False(t, testDownsampler.downsampler.Enabled())
   400  	origStagedMetadata := originalStagedMetadata(t, testDownsampler)
   401  	ctrl := xtest.NewController(t)
   402  	defer ctrl.Finish()
   403  	session := dbclient.NewMockSession(ctrl)
   404  	setAggregatedNamespaces(t, testDownsampler, session, m3.AggregatedClusterNamespaceDefinition{
   405  		NamespaceID: ident.StringID("1s:30d"),
   406  		Resolution:  res,
   407  		Retention:   ret,
   408  		Session:     session,
   409  	})
   410  	waitForStagedMetadataUpdate(t, testDownsampler, origStagedMetadata)
   411  	require.True(t, testDownsampler.downsampler.Enabled())
   412  
   413  	// Test expected output
   414  	testDownsamplerAggregation(t, testDownsampler)
   415  }
   416  
   417  func TestDownsamplerAggregationToggleEnabled(t *testing.T) {
   418  	t.Parallel()
   419  
   420  	ctrl := xtest.NewController(t)
   421  	defer ctrl.Finish()
   422  
   423  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{})
   424  
   425  	require.False(t, testDownsampler.downsampler.Enabled())
   426  
   427  	// Add an aggregated namespace and expect downsampler to be enabled.
   428  	session := dbclient.NewMockSession(ctrl)
   429  	setAggregatedNamespaces(t, testDownsampler, session, m3.AggregatedClusterNamespaceDefinition{
   430  		NamespaceID: ident.StringID("2s:1d"),
   431  		Resolution:  2 * time.Second,
   432  		Retention:   24 * time.Hour,
   433  		Session:     session,
   434  	})
   435  	waitForEnabledUpdate(t, &testDownsampler, false)
   436  
   437  	require.True(t, testDownsampler.downsampler.Enabled())
   438  
   439  	// Set just an unaggregated namespace and expect downsampler to be disabled.
   440  	clusters, err := m3.NewClusters(m3.UnaggregatedClusterNamespaceDefinition{
   441  		NamespaceID: ident.StringID("default"),
   442  		Retention:   48 * time.Hour,
   443  		Session:     session,
   444  	})
   445  	require.NoError(t, err)
   446  	require.NoError(t,
   447  		testDownsampler.opts.ClusterNamespacesWatcher.Update(clusters.ClusterNamespaces()))
   448  
   449  	waitForEnabledUpdate(t, &testDownsampler, true)
   450  
   451  	require.False(t, testDownsampler.downsampler.Enabled())
   452  }
   453  
   454  func TestDownsamplerAggregationWithRulesStore(t *testing.T) {
   455  	t.Parallel()
   456  
   457  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{})
   458  	rulesStore := testDownsampler.rulesStore
   459  
   460  	// Create rules
   461  	nss, err := rulesStore.ReadNamespaces()
   462  	require.NoError(t, err)
   463  	_, err = nss.AddNamespace("default", testUpdateMetadata())
   464  	require.NoError(t, err)
   465  
   466  	rule := view.MappingRule{
   467  		ID:              "mappingrule",
   468  		Name:            "mappingrule",
   469  		Filter:          "app:test*",
   470  		AggregationID:   aggregation.MustCompressTypes(testAggregationType),
   471  		StoragePolicies: testAggregationStoragePolicies,
   472  	}
   473  
   474  	rs := rules.NewEmptyRuleSet("default", testUpdateMetadata())
   475  	_, err = rs.AddMappingRule(rule, testUpdateMetadata())
   476  	require.NoError(t, err)
   477  
   478  	err = rulesStore.WriteAll(nss, rs)
   479  	require.NoError(t, err)
   480  
   481  	logger := testDownsampler.instrumentOpts.Logger().
   482  		With(zap.String("test", t.Name()))
   483  
   484  	// Wait for mapping rule to appear
   485  	logger.Info("waiting for mapping rules to propagate")
   486  	appender, err := testDownsampler.downsampler.NewMetricsAppender()
   487  	require.NoError(t, err)
   488  	appenderImpl := appender.(*metricsAppender)
   489  	testMatchID := newTestID(t, map[string]string{
   490  		"__name__": "foo",
   491  		"app":      "test123",
   492  	})
   493  	for {
   494  		now := time.Now().UnixNano()
   495  		res, err := appenderImpl.matcher.ForwardMatch(testMatchID, now, now+1, rules.MatchOptions{
   496  			NameAndTagsFn:       appenderImpl.nameTagFn,
   497  			SortedTagIteratorFn: appenderImpl.tagIterFn,
   498  		})
   499  		require.NoError(t, err)
   500  		results := res.ForExistingIDAt(now)
   501  		if !results.IsDefault() {
   502  			break
   503  		}
   504  		time.Sleep(100 * time.Millisecond)
   505  	}
   506  
   507  	// Test expected output
   508  	testDownsamplerAggregation(t, testDownsampler)
   509  }
   510  
   511  func TestDownsamplerAggregationWithRulesConfigMappingRules(t *testing.T) {
   512  	t.Parallel()
   513  
   514  	gaugeMetric := testGaugeMetric{
   515  		tags: map[string]string{
   516  			nameTag: "foo_metric",
   517  			"app":   "nginx_edge",
   518  		},
   519  		timedSamples: []testGaugeMetricTimedSample{
   520  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
   521  		},
   522  	}
   523  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   524  		rulesConfig: &RulesConfiguration{
   525  			MappingRules: []MappingRuleConfiguration{
   526  				{
   527  					Filter:       "app:nginx*",
   528  					Aggregations: []aggregation.Type{aggregation.Max},
   529  					StoragePolicies: []StoragePolicyConfiguration{
   530  						{
   531  							Resolution: 1 * time.Second,
   532  							Retention:  30 * 24 * time.Hour,
   533  						},
   534  					},
   535  				},
   536  			},
   537  		},
   538  		ingest: &testDownsamplerOptionsIngest{
   539  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   540  		},
   541  		expect: &testDownsamplerOptionsExpect{
   542  			writes: []testExpectedWrite{
   543  				{
   544  					tags:   gaugeMetric.tags,
   545  					values: []expectedValue{{value: 30}},
   546  					attributes: &storagemetadata.Attributes{
   547  						MetricsType: storagemetadata.AggregatedMetricsType,
   548  						Resolution:  1 * time.Second,
   549  						Retention:   30 * 24 * time.Hour,
   550  					},
   551  				},
   552  			},
   553  		},
   554  	})
   555  
   556  	// Test expected output
   557  	testDownsamplerAggregation(t, testDownsampler)
   558  }
   559  
   560  func TestDownsamplerAggregationWithAutoMappingRulesAndRulesConfigMappingRulesAndDropRule(t *testing.T) {
   561  	t.Parallel()
   562  
   563  	gaugeMetric := testGaugeMetric{
   564  		tags: map[string]string{
   565  			nameTag: "foo_metric",
   566  			"app":   "nginx_edge",
   567  			"env":   "staging",
   568  		},
   569  		timedSamples: []testGaugeMetricTimedSample{
   570  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
   571  		},
   572  		expectDropPolicyApplied: true,
   573  	}
   574  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   575  		autoMappingRules: []m3.ClusterNamespaceOptions{
   576  			m3.NewClusterNamespaceOptions(
   577  				storagemetadata.Attributes{
   578  					MetricsType: storagemetadata.AggregatedMetricsType,
   579  					Retention:   2 * time.Hour,
   580  					Resolution:  1 * time.Second,
   581  				},
   582  				nil,
   583  			),
   584  			m3.NewClusterNamespaceOptions(
   585  				storagemetadata.Attributes{
   586  					MetricsType: storagemetadata.AggregatedMetricsType,
   587  					Retention:   12 * time.Hour,
   588  					Resolution:  5 * time.Second,
   589  				},
   590  				nil,
   591  			),
   592  		},
   593  		rulesConfig: &RulesConfiguration{
   594  			MappingRules: []MappingRuleConfiguration{
   595  				{
   596  					Filter: "env:staging",
   597  					Drop:   true,
   598  				},
   599  				{
   600  					Filter:       "app:nginx*",
   601  					Aggregations: []aggregation.Type{aggregation.Max},
   602  					StoragePolicies: []StoragePolicyConfiguration{
   603  						{
   604  							Resolution: 10 * time.Second,
   605  							Retention:  30 * 24 * time.Hour,
   606  						},
   607  					},
   608  				},
   609  			},
   610  		},
   611  		ingest: &testDownsamplerOptionsIngest{
   612  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   613  		},
   614  		expect: &testDownsamplerOptionsExpect{
   615  			allowFilter: &testDownsamplerOptionsExpectAllowFilter{
   616  				attributes: []storagemetadata.Attributes{
   617  					{
   618  						MetricsType: storagemetadata.AggregatedMetricsType,
   619  						Resolution:  10 * time.Second,
   620  						Retention:   30 * 24 * time.Hour,
   621  					},
   622  				},
   623  			},
   624  			writes: []testExpectedWrite{
   625  				{
   626  					tags:   gaugeMetric.tags,
   627  					values: []expectedValue{{value: 30}},
   628  					attributes: &storagemetadata.Attributes{
   629  						MetricsType: storagemetadata.AggregatedMetricsType,
   630  						Resolution:  10 * time.Second,
   631  						Retention:   30 * 24 * time.Hour,
   632  					},
   633  				},
   634  			},
   635  		},
   636  	})
   637  
   638  	// Test expected output
   639  	testDownsamplerAggregation(t, testDownsampler)
   640  }
   641  
   642  func TestDownsamplerAggregationWithRulesConfigMappingRulesPartialReplaceAutoMappingRuleFromNamespacesWatcher(t *testing.T) {
   643  	t.Parallel()
   644  
   645  	ctrl := xtest.NewController(t)
   646  	defer ctrl.Finish()
   647  
   648  	gaugeMetric := testGaugeMetric{
   649  		tags: map[string]string{
   650  			nameTag: "foo_metric",
   651  			"app":   "nginx_edge",
   652  		},
   653  		timedSamples: []testGaugeMetricTimedSample{
   654  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0, offset: 1 * time.Millisecond},
   655  		},
   656  	}
   657  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   658  		rulesConfig: &RulesConfiguration{
   659  			MappingRules: []MappingRuleConfiguration{
   660  				{
   661  					Filter:       "app:nginx*",
   662  					Aggregations: []aggregation.Type{aggregation.Max},
   663  					StoragePolicies: []StoragePolicyConfiguration{
   664  						{
   665  							Resolution: 2 * time.Second,
   666  							Retention:  24 * time.Hour,
   667  						},
   668  					},
   669  				},
   670  			},
   671  		},
   672  		ingest: &testDownsamplerOptionsIngest{
   673  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   674  		},
   675  		expect: &testDownsamplerOptionsExpect{
   676  			writes: []testExpectedWrite{
   677  				// Expect the max to be used and override the default auto
   678  				// mapping rule for the storage policy 2s:24h.
   679  				{
   680  					tags:   gaugeMetric.tags,
   681  					values: []expectedValue{{value: 30}},
   682  					attributes: &storagemetadata.Attributes{
   683  						MetricsType: storagemetadata.AggregatedMetricsType,
   684  						Resolution:  2 * time.Second,
   685  						Retention:   24 * time.Hour,
   686  					},
   687  				},
   688  				// Expect last to still be used for the storage
   689  				// policy 4s:48h.
   690  				{
   691  					tags: gaugeMetric.tags,
   692  					// NB(nate): Automapping rules generated from cluster namespaces currently
   693  					// hardcode 'Last' as the aggregation type. As such, expect value to be the last value
   694  					// in the sample.
   695  					values: []expectedValue{{value: 0}},
   696  					attributes: &storagemetadata.Attributes{
   697  						MetricsType: storagemetadata.AggregatedMetricsType,
   698  						Resolution:  4 * time.Second,
   699  						Retention:   48 * time.Hour,
   700  					},
   701  				},
   702  			},
   703  		},
   704  	})
   705  
   706  	origStagedMetadata := originalStagedMetadata(t, testDownsampler)
   707  
   708  	session := dbclient.NewMockSession(ctrl)
   709  	setAggregatedNamespaces(t, testDownsampler, session, m3.AggregatedClusterNamespaceDefinition{
   710  		NamespaceID: ident.StringID("2s:24h"),
   711  		Resolution:  2 * time.Second,
   712  		Retention:   24 * time.Hour,
   713  		Session:     session,
   714  	}, m3.AggregatedClusterNamespaceDefinition{
   715  		NamespaceID: ident.StringID("4s:48h"),
   716  		Resolution:  4 * time.Second,
   717  		Retention:   48 * time.Hour,
   718  		Session:     session,
   719  	})
   720  
   721  	waitForStagedMetadataUpdate(t, testDownsampler, origStagedMetadata)
   722  
   723  	// Test expected output
   724  	testDownsamplerAggregation(t, testDownsampler)
   725  }
   726  
   727  func TestDownsamplerAggregationWithRulesConfigMappingRulesReplaceAutoMappingRuleFromNamespacesWatcher(t *testing.T) {
   728  	t.Parallel()
   729  
   730  	ctrl := xtest.NewController(t)
   731  	defer ctrl.Finish()
   732  
   733  	gaugeMetric := testGaugeMetric{
   734  		tags: map[string]string{
   735  			nameTag: "foo_metric",
   736  			"app":   "nginx_edge",
   737  		},
   738  		timedSamples: []testGaugeMetricTimedSample{
   739  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
   740  		},
   741  	}
   742  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   743  		rulesConfig: &RulesConfiguration{
   744  			MappingRules: []MappingRuleConfiguration{
   745  				{
   746  					Filter:       "app:nginx*",
   747  					Aggregations: []aggregation.Type{aggregation.Max},
   748  					StoragePolicies: []StoragePolicyConfiguration{
   749  						{
   750  							Resolution: 2 * time.Second,
   751  							Retention:  24 * time.Hour,
   752  						},
   753  					},
   754  				},
   755  			},
   756  		},
   757  		ingest: &testDownsamplerOptionsIngest{
   758  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   759  		},
   760  		expect: &testDownsamplerOptionsExpect{
   761  			writes: []testExpectedWrite{
   762  				// Expect the max to be used and override the default auto
   763  				// mapping rule for the storage policy 2s:24h.
   764  				{
   765  					tags:   gaugeMetric.tags,
   766  					values: []expectedValue{{value: 30}},
   767  					attributes: &storagemetadata.Attributes{
   768  						MetricsType: storagemetadata.AggregatedMetricsType,
   769  						Resolution:  2 * time.Second,
   770  						Retention:   24 * time.Hour,
   771  					},
   772  				},
   773  			},
   774  		},
   775  	})
   776  
   777  	origStagedMetadata := originalStagedMetadata(t, testDownsampler)
   778  
   779  	session := dbclient.NewMockSession(ctrl)
   780  	setAggregatedNamespaces(t, testDownsampler, session, m3.AggregatedClusterNamespaceDefinition{
   781  		NamespaceID: ident.StringID("2s:24h"),
   782  		Resolution:  2 * time.Second,
   783  		Retention:   24 * time.Hour,
   784  		Session:     session,
   785  	})
   786  
   787  	waitForStagedMetadataUpdate(t, testDownsampler, origStagedMetadata)
   788  
   789  	// Test expected output
   790  	testDownsamplerAggregation(t, testDownsampler)
   791  }
   792  
   793  func TestDownsamplerAggregationWithRulesConfigMappingRulesNoNameTag(t *testing.T) {
   794  	t.Parallel()
   795  
   796  	gaugeMetric := testGaugeMetric{
   797  		tags: map[string]string{
   798  			"app":      "nginx_edge",
   799  			"endpoint": "health",
   800  		},
   801  		timedSamples: []testGaugeMetricTimedSample{
   802  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
   803  		},
   804  	}
   805  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   806  		identTag: "endpoint",
   807  		rulesConfig: &RulesConfiguration{
   808  			MappingRules: []MappingRuleConfiguration{
   809  				{
   810  					Filter:       "app:nginx*",
   811  					Aggregations: []aggregation.Type{aggregation.Max},
   812  					StoragePolicies: []StoragePolicyConfiguration{
   813  						{
   814  							Resolution: 1 * time.Second,
   815  							Retention:  30 * 24 * time.Hour,
   816  						},
   817  					},
   818  				},
   819  			},
   820  		},
   821  		ingest: &testDownsamplerOptionsIngest{
   822  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   823  		},
   824  		expect: &testDownsamplerOptionsExpect{
   825  			writes: []testExpectedWrite{
   826  				{
   827  					tags:   gaugeMetric.tags,
   828  					values: []expectedValue{{value: 30}},
   829  					attributes: &storagemetadata.Attributes{
   830  						MetricsType: storagemetadata.AggregatedMetricsType,
   831  						Resolution:  1 * time.Second,
   832  						Retention:   30 * 24 * time.Hour,
   833  					},
   834  				},
   835  			},
   836  		},
   837  	})
   838  
   839  	// Test expected output
   840  	testDownsamplerAggregation(t, testDownsampler)
   841  }
   842  
   843  func TestDownsamplerAggregationWithRulesConfigMappingRulesTypeFilter(t *testing.T) {
   844  	t.Parallel()
   845  
   846  	gaugeMetric := testGaugeMetric{
   847  		tags: map[string]string{
   848  			"app":      "nginx_edge",
   849  			"endpoint": "health",
   850  		},
   851  		timedSamples: []testGaugeMetricTimedSample{
   852  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
   853  		},
   854  	}
   855  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   856  		identTag: "endpoint",
   857  		rulesConfig: &RulesConfiguration{
   858  			MappingRules: []MappingRuleConfiguration{
   859  				{
   860  					Filter:       "__m3_type__:counter",
   861  					Aggregations: []aggregation.Type{aggregation.Max},
   862  					StoragePolicies: []StoragePolicyConfiguration{
   863  						{
   864  							Resolution: 1 * time.Second,
   865  							Retention:  30 * 24 * time.Hour,
   866  						},
   867  					},
   868  				},
   869  			},
   870  		},
   871  		sampleAppenderOpts: &SampleAppenderOptions{
   872  			SeriesAttributes: ts.SeriesAttributes{M3Type: ts.M3MetricTypeCounter},
   873  		},
   874  		ingest: &testDownsamplerOptionsIngest{
   875  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   876  		},
   877  		expect: &testDownsamplerOptionsExpect{
   878  			writes: []testExpectedWrite{
   879  				{
   880  					tags: map[string]string{
   881  						"app":      "nginx_edge",
   882  						"endpoint": "health",
   883  					},
   884  					values: []expectedValue{{value: 30}},
   885  					attributes: &storagemetadata.Attributes{
   886  						MetricsType: storagemetadata.AggregatedMetricsType,
   887  						Resolution:  1 * time.Second,
   888  						Retention:   30 * 24 * time.Hour,
   889  					},
   890  				},
   891  			},
   892  		},
   893  	})
   894  
   895  	// Test expected output
   896  	testDownsamplerAggregation(t, testDownsampler)
   897  }
   898  
   899  //nolint:dupl
   900  func TestDownsamplerAggregationWithRulesConfigMappingRulesTypePromFilter(t *testing.T) {
   901  	t.Parallel()
   902  
   903  	gaugeMetric := testGaugeMetric{
   904  		tags: map[string]string{
   905  			"app":      "nginx_edge",
   906  			"endpoint": "health",
   907  		},
   908  		timedSamples: []testGaugeMetricTimedSample{
   909  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
   910  		},
   911  	}
   912  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   913  		identTag: "endpoint",
   914  		rulesConfig: &RulesConfiguration{
   915  			MappingRules: []MappingRuleConfiguration{
   916  				{
   917  					Filter:       "__m3_prom_type__:counter",
   918  					Aggregations: []aggregation.Type{aggregation.Max},
   919  					StoragePolicies: []StoragePolicyConfiguration{
   920  						{
   921  							Resolution: 1 * time.Second,
   922  							Retention:  30 * 24 * time.Hour,
   923  						},
   924  					},
   925  				},
   926  			},
   927  		},
   928  		sampleAppenderOpts: &SampleAppenderOptions{
   929  			SeriesAttributes: ts.SeriesAttributes{PromType: ts.PromMetricTypeCounter},
   930  		},
   931  		ingest: &testDownsamplerOptionsIngest{
   932  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   933  		},
   934  		expect: &testDownsamplerOptionsExpect{
   935  			writes: []testExpectedWrite{
   936  				{
   937  					tags: map[string]string{
   938  						"app":      "nginx_edge",
   939  						"endpoint": "health",
   940  					},
   941  					values: []expectedValue{{value: 30}},
   942  					attributes: &storagemetadata.Attributes{
   943  						MetricsType: storagemetadata.AggregatedMetricsType,
   944  						Resolution:  1 * time.Second,
   945  						Retention:   30 * 24 * time.Hour,
   946  					},
   947  				},
   948  			},
   949  		},
   950  	})
   951  
   952  	// Test expected output
   953  	testDownsamplerAggregation(t, testDownsampler)
   954  }
   955  
   956  func TestDownsamplerAggregationWithRulesConfigMappingRulesTypeFilterNoMatch(t *testing.T) {
   957  	t.Parallel()
   958  
   959  	gaugeMetric := testGaugeMetric{
   960  		tags: map[string]string{
   961  			"app":      "nginx_edge",
   962  			"endpoint": "health",
   963  		},
   964  		timedSamples: []testGaugeMetricTimedSample{
   965  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
   966  		},
   967  	}
   968  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
   969  		identTag: "endpoint",
   970  		rulesConfig: &RulesConfiguration{
   971  			MappingRules: []MappingRuleConfiguration{
   972  				{
   973  					Filter:       "__m3_type__:counter",
   974  					Aggregations: []aggregation.Type{aggregation.Max},
   975  					StoragePolicies: []StoragePolicyConfiguration{
   976  						{
   977  							Resolution: 1 * time.Second,
   978  							Retention:  30 * 24 * time.Hour,
   979  						},
   980  					},
   981  				},
   982  			},
   983  		},
   984  		sampleAppenderOpts: &SampleAppenderOptions{
   985  			SeriesAttributes: ts.SeriesAttributes{M3Type: ts.M3MetricTypeGauge},
   986  		},
   987  		ingest: &testDownsamplerOptionsIngest{
   988  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
   989  		},
   990  		expect: &testDownsamplerOptionsExpect{
   991  			writes: []testExpectedWrite{},
   992  		},
   993  	})
   994  
   995  	// Test expected output
   996  	testDownsamplerAggregation(t, testDownsampler)
   997  }
   998  
   999  //nolint:dupl
  1000  func TestDownsamplerAggregationWithRulesConfigMappingRulesPromTypeFilterNoMatch(t *testing.T) {
  1001  	t.Parallel()
  1002  
  1003  	gaugeMetric := testGaugeMetric{
  1004  		tags: map[string]string{
  1005  			"app":      "nginx_edge",
  1006  			"endpoint": "health",
  1007  		},
  1008  		timedSamples: []testGaugeMetricTimedSample{
  1009  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1010  		},
  1011  	}
  1012  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1013  		identTag: "endpoint",
  1014  		rulesConfig: &RulesConfiguration{
  1015  			MappingRules: []MappingRuleConfiguration{
  1016  				{
  1017  					Filter:       "__m3_prom_type__:counter",
  1018  					Aggregations: []aggregation.Type{aggregation.Max},
  1019  					StoragePolicies: []StoragePolicyConfiguration{
  1020  						{
  1021  							Resolution: 1 * time.Second,
  1022  							Retention:  30 * 24 * time.Hour,
  1023  						},
  1024  					},
  1025  				},
  1026  			},
  1027  		},
  1028  		sampleAppenderOpts: &SampleAppenderOptions{
  1029  			SeriesAttributes: ts.SeriesAttributes{PromType: ts.PromMetricTypeGauge},
  1030  		},
  1031  		ingest: &testDownsamplerOptionsIngest{
  1032  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1033  		},
  1034  		expect: &testDownsamplerOptionsExpect{
  1035  			writes: []testExpectedWrite{},
  1036  		},
  1037  	})
  1038  
  1039  	// Test expected output
  1040  	testDownsamplerAggregation(t, testDownsampler)
  1041  }
  1042  
  1043  func TestDownsamplerAggregationWithRulesConfigMappingRulesAggregationType(t *testing.T) {
  1044  	t.Parallel()
  1045  
  1046  	gaugeMetric := testGaugeMetric{
  1047  		tags: map[string]string{
  1048  			"__g0__":               "nginx_edge",
  1049  			"__g1__":               "health",
  1050  			"__option_id_scheme__": "graphite",
  1051  		},
  1052  		timedSamples: []testGaugeMetricTimedSample{
  1053  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1054  		},
  1055  	}
  1056  	tags := []Tag{{Name: "__m3_graphite_aggregation__"}}
  1057  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1058  		identTag: "__g2__",
  1059  		rulesConfig: &RulesConfiguration{
  1060  			MappingRules: []MappingRuleConfiguration{
  1061  				{
  1062  					Filter:       "__m3_type__:gauge",
  1063  					Aggregations: []aggregation.Type{aggregation.Max},
  1064  					StoragePolicies: []StoragePolicyConfiguration{
  1065  						{
  1066  							Resolution: 1 * time.Second,
  1067  							Retention:  30 * 24 * time.Hour,
  1068  						},
  1069  					},
  1070  					Tags: tags,
  1071  				},
  1072  			},
  1073  		},
  1074  		ingest: &testDownsamplerOptionsIngest{
  1075  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1076  		},
  1077  		expect: &testDownsamplerOptionsExpect{
  1078  			writes: []testExpectedWrite{
  1079  				{
  1080  					tags: map[string]string{
  1081  						"__g0__": "nginx_edge",
  1082  						"__g1__": "health",
  1083  						"__g2__": "upper",
  1084  					},
  1085  					values: []expectedValue{{value: 30}},
  1086  					attributes: &storagemetadata.Attributes{
  1087  						MetricsType: storagemetadata.AggregatedMetricsType,
  1088  						Resolution:  1 * time.Second,
  1089  						Retention:   30 * 24 * time.Hour,
  1090  					},
  1091  				},
  1092  			},
  1093  		},
  1094  	})
  1095  
  1096  	// Test expected output
  1097  	testDownsamplerAggregation(t, testDownsampler)
  1098  }
  1099  
  1100  func TestDownsamplerAggregationWithRulesConfigMappingRulesMultipleAggregationType(t *testing.T) {
  1101  	t.Parallel()
  1102  
  1103  	gaugeMetric := testGaugeMetric{
  1104  		tags: map[string]string{
  1105  			"__g0__": "nginx_edge",
  1106  			"__g1__": "health",
  1107  		},
  1108  		timedSamples: []testGaugeMetricTimedSample{
  1109  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1110  		},
  1111  	}
  1112  	tags := []Tag{{Name: "__m3_graphite_aggregation__"}}
  1113  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1114  		identTag: "__g2__",
  1115  		rulesConfig: &RulesConfiguration{
  1116  			MappingRules: []MappingRuleConfiguration{
  1117  				{
  1118  					Filter:       "__m3_type__:gauge",
  1119  					Aggregations: []aggregation.Type{aggregation.Max},
  1120  					StoragePolicies: []StoragePolicyConfiguration{
  1121  						{
  1122  							Resolution: 1 * time.Second,
  1123  							Retention:  30 * 24 * time.Hour,
  1124  						},
  1125  					},
  1126  					Tags: tags,
  1127  				},
  1128  				{
  1129  					Filter:       "__m3_type__:gauge",
  1130  					Aggregations: []aggregation.Type{aggregation.Sum},
  1131  					StoragePolicies: []StoragePolicyConfiguration{
  1132  						{
  1133  							Resolution: 1 * time.Second,
  1134  							Retention:  30 * 24 * time.Hour,
  1135  						},
  1136  					},
  1137  					Tags: tags,
  1138  				},
  1139  			},
  1140  		},
  1141  		ingest: &testDownsamplerOptionsIngest{
  1142  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1143  		},
  1144  		expect: &testDownsamplerOptionsExpect{
  1145  			writes: []testExpectedWrite{
  1146  				{
  1147  					tags: map[string]string{
  1148  						"__g0__": "nginx_edge",
  1149  						"__g1__": "health",
  1150  						"__g2__": "upper",
  1151  					},
  1152  					values: []expectedValue{{value: 30}},
  1153  					attributes: &storagemetadata.Attributes{
  1154  						MetricsType: storagemetadata.AggregatedMetricsType,
  1155  						Resolution:  1 * time.Second,
  1156  						Retention:   30 * 24 * time.Hour,
  1157  					},
  1158  				},
  1159  				{
  1160  					tags: map[string]string{
  1161  						"__g0__": "nginx_edge",
  1162  						"__g1__": "health",
  1163  						"__g2__": "sum",
  1164  					},
  1165  					values: []expectedValue{{value: 60}},
  1166  					attributes: &storagemetadata.Attributes{
  1167  						MetricsType: storagemetadata.AggregatedMetricsType,
  1168  						Resolution:  1 * time.Second,
  1169  						Retention:   30 * 24 * time.Hour,
  1170  					},
  1171  				},
  1172  			},
  1173  		},
  1174  	})
  1175  
  1176  	// Test expected output
  1177  	testDownsamplerAggregation(t, testDownsampler)
  1178  }
  1179  
  1180  func TestDownsamplerAggregationWithRulesConfigMappingRulesGraphitePrefixAndAggregationTags(t *testing.T) {
  1181  	t.Parallel()
  1182  
  1183  	gaugeMetric := testGaugeMetric{
  1184  		tags: map[string]string{
  1185  			"__g0__": "nginx_edge",
  1186  			"__g1__": "health",
  1187  		},
  1188  		timedSamples: []testGaugeMetricTimedSample{
  1189  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1190  		},
  1191  	}
  1192  	tags := []Tag{
  1193  		{Name: "__m3_graphite_aggregation__"},
  1194  		{Name: "__m3_graphite_prefix__", Value: "stats.counter"},
  1195  	}
  1196  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1197  		identTag: "__g4__",
  1198  		rulesConfig: &RulesConfiguration{
  1199  			MappingRules: []MappingRuleConfiguration{
  1200  				{
  1201  					Filter:       "__m3_type__:gauge",
  1202  					Aggregations: []aggregation.Type{aggregation.Max},
  1203  					StoragePolicies: []StoragePolicyConfiguration{
  1204  						{
  1205  							Resolution: 1 * time.Second,
  1206  							Retention:  30 * 24 * time.Hour,
  1207  						},
  1208  					},
  1209  					Tags: tags,
  1210  				},
  1211  			},
  1212  		},
  1213  		ingest: &testDownsamplerOptionsIngest{
  1214  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1215  		},
  1216  		expect: &testDownsamplerOptionsExpect{
  1217  			writes: []testExpectedWrite{
  1218  				{
  1219  					tags: map[string]string{
  1220  						"__g0__": "stats",
  1221  						"__g1__": "counter",
  1222  						"__g2__": "nginx_edge",
  1223  						"__g3__": "health",
  1224  						"__g4__": "upper",
  1225  					},
  1226  					values: []expectedValue{{value: 30}},
  1227  					attributes: &storagemetadata.Attributes{
  1228  						MetricsType: storagemetadata.AggregatedMetricsType,
  1229  						Resolution:  1 * time.Second,
  1230  						Retention:   30 * 24 * time.Hour,
  1231  					},
  1232  				},
  1233  			},
  1234  		},
  1235  	})
  1236  
  1237  	// Test expected output
  1238  	testDownsamplerAggregation(t, testDownsampler)
  1239  }
  1240  
  1241  func TestDownsamplerAggregationWithRulesConfigMappingRulesGraphitePrefixTag(t *testing.T) {
  1242  	t.Parallel()
  1243  
  1244  	gaugeMetric := testGaugeMetric{
  1245  		tags: map[string]string{
  1246  			"__g0__": "nginx_edge",
  1247  			"__g1__": "health",
  1248  		},
  1249  		timedSamples: []testGaugeMetricTimedSample{
  1250  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1251  		},
  1252  	}
  1253  	tags := []Tag{
  1254  		{Name: "__m3_graphite_prefix__", Value: "stats.counter"},
  1255  	}
  1256  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1257  		identTag: "__g3__",
  1258  		rulesConfig: &RulesConfiguration{
  1259  			MappingRules: []MappingRuleConfiguration{
  1260  				{
  1261  					Filter:       "__m3_type__:gauge",
  1262  					Aggregations: []aggregation.Type{aggregation.Max},
  1263  					StoragePolicies: []StoragePolicyConfiguration{
  1264  						{
  1265  							Resolution: 1 * time.Second,
  1266  							Retention:  30 * 24 * time.Hour,
  1267  						},
  1268  					},
  1269  					Tags: tags,
  1270  				},
  1271  			},
  1272  		},
  1273  		ingest: &testDownsamplerOptionsIngest{
  1274  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1275  		},
  1276  		expect: &testDownsamplerOptionsExpect{
  1277  			writes: []testExpectedWrite{
  1278  				{
  1279  					tags: map[string]string{
  1280  						"__g0__": "stats",
  1281  						"__g1__": "counter",
  1282  						"__g2__": "nginx_edge",
  1283  						"__g3__": "health",
  1284  					},
  1285  					values: []expectedValue{{value: 30}},
  1286  					attributes: &storagemetadata.Attributes{
  1287  						MetricsType: storagemetadata.AggregatedMetricsType,
  1288  						Resolution:  1 * time.Second,
  1289  						Retention:   30 * 24 * time.Hour,
  1290  					},
  1291  				},
  1292  			},
  1293  		},
  1294  	})
  1295  
  1296  	// Test expected output
  1297  	testDownsamplerAggregation(t, testDownsampler)
  1298  }
  1299  
  1300  func TestDownsamplerAggregationWithRulesConfigMappingRulesPromQuantileTag(t *testing.T) {
  1301  	t.Parallel()
  1302  
  1303  	timerMetric := testTimerMetric{
  1304  		tags: map[string]string{
  1305  			nameTag:    "http_requests",
  1306  			"app":      "nginx_edge",
  1307  			"endpoint": "health",
  1308  		},
  1309  		timedSamples: []testTimerMetricTimedSample{
  1310  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1311  		},
  1312  	}
  1313  	tags := []Tag{
  1314  		{Name: "__m3_prom_summary__"},
  1315  	}
  1316  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1317  		rulesConfig: &RulesConfiguration{
  1318  			MappingRules: []MappingRuleConfiguration{
  1319  				{
  1320  					Filter:       "__m3_type__:timer",
  1321  					Aggregations: []aggregation.Type{aggregation.P50},
  1322  					StoragePolicies: []StoragePolicyConfiguration{
  1323  						{
  1324  							Resolution: 1 * time.Second,
  1325  							Retention:  30 * 24 * time.Hour,
  1326  						},
  1327  					},
  1328  					Tags: tags,
  1329  				},
  1330  			},
  1331  		},
  1332  		sampleAppenderOpts: &SampleAppenderOptions{
  1333  			SeriesAttributes: ts.SeriesAttributes{M3Type: ts.M3MetricTypeTimer},
  1334  		},
  1335  		ingest: &testDownsamplerOptionsIngest{
  1336  			timerMetrics: []testTimerMetric{timerMetric},
  1337  		},
  1338  		expect: &testDownsamplerOptionsExpect{
  1339  			writes: []testExpectedWrite{
  1340  				{
  1341  					tags: map[string]string{
  1342  						nameTag:    "http_requests",
  1343  						"app":      "nginx_edge",
  1344  						"endpoint": "health",
  1345  						"agg":      ".p50",
  1346  						"quantile": "0.5",
  1347  					},
  1348  					values: []expectedValue{{value: 10}},
  1349  					attributes: &storagemetadata.Attributes{
  1350  						MetricsType: storagemetadata.AggregatedMetricsType,
  1351  						Resolution:  1 * time.Second,
  1352  						Retention:   30 * 24 * time.Hour,
  1353  					},
  1354  				},
  1355  			},
  1356  		},
  1357  	})
  1358  
  1359  	// Test expected output
  1360  	testDownsamplerAggregation(t, testDownsampler)
  1361  }
  1362  
  1363  func TestDownsamplerAggregationWithRulesConfigMappingRulesPromQuantileTagIgnored(t *testing.T) {
  1364  	t.Parallel()
  1365  
  1366  	timerMetric := testTimerMetric{
  1367  		tags: map[string]string{
  1368  			nameTag:    "http_requests",
  1369  			"app":      "nginx_edge",
  1370  			"endpoint": "health",
  1371  		},
  1372  		timedSamples: []testTimerMetricTimedSample{
  1373  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1374  		},
  1375  	}
  1376  	tags := []Tag{
  1377  		{Name: "__m3_prom_summary__"},
  1378  	}
  1379  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1380  		rulesConfig: &RulesConfiguration{
  1381  			MappingRules: []MappingRuleConfiguration{
  1382  				{
  1383  					Filter:       "__m3_type__:timer",
  1384  					Aggregations: []aggregation.Type{aggregation.Max},
  1385  					StoragePolicies: []StoragePolicyConfiguration{
  1386  						{
  1387  							Resolution: 1 * time.Second,
  1388  							Retention:  30 * 24 * time.Hour,
  1389  						},
  1390  					},
  1391  					Tags: tags,
  1392  				},
  1393  			},
  1394  		},
  1395  		sampleAppenderOpts: &SampleAppenderOptions{
  1396  			SeriesAttributes: ts.SeriesAttributes{M3Type: ts.M3MetricTypeTimer},
  1397  		},
  1398  		ingest: &testDownsamplerOptionsIngest{
  1399  			timerMetrics: []testTimerMetric{timerMetric},
  1400  		},
  1401  		expect: &testDownsamplerOptionsExpect{
  1402  			writes: []testExpectedWrite{
  1403  				{
  1404  					tags: map[string]string{
  1405  						nameTag:    "http_requests",
  1406  						"app":      "nginx_edge",
  1407  						"endpoint": "health",
  1408  						"agg":      ".upper",
  1409  					},
  1410  					values: []expectedValue{{value: 30}},
  1411  					attributes: &storagemetadata.Attributes{
  1412  						MetricsType: storagemetadata.AggregatedMetricsType,
  1413  						Resolution:  1 * time.Second,
  1414  						Retention:   30 * 24 * time.Hour,
  1415  					},
  1416  				},
  1417  			},
  1418  		},
  1419  	})
  1420  
  1421  	// Test expected output
  1422  	testDownsamplerAggregation(t, testDownsampler)
  1423  }
  1424  
  1425  func TestDownsamplerAggregationWithRulesConfigMappingRulesAugmentTag(t *testing.T) {
  1426  	t.Parallel()
  1427  
  1428  	gaugeMetric := testGaugeMetric{
  1429  		tags: map[string]string{
  1430  			"app":      "nginx_edge",
  1431  			"endpoint": "health",
  1432  		},
  1433  		timedSamples: []testGaugeMetricTimedSample{
  1434  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1435  		},
  1436  	}
  1437  	tags := []Tag{
  1438  		{Name: "datacenter", Value: "abc"},
  1439  	}
  1440  	//nolint:dupl
  1441  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1442  		identTag: "app",
  1443  		rulesConfig: &RulesConfiguration{
  1444  			MappingRules: []MappingRuleConfiguration{
  1445  				{
  1446  					Filter:       "app:nginx*",
  1447  					Aggregations: []aggregation.Type{aggregation.Max},
  1448  					StoragePolicies: []StoragePolicyConfiguration{
  1449  						{
  1450  							Resolution: 1 * time.Second,
  1451  							Retention:  30 * 24 * time.Hour,
  1452  						},
  1453  					},
  1454  					Tags: tags,
  1455  				},
  1456  			},
  1457  		},
  1458  		ingest: &testDownsamplerOptionsIngest{
  1459  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1460  		},
  1461  		expect: &testDownsamplerOptionsExpect{
  1462  			writes: []testExpectedWrite{
  1463  				{
  1464  					tags: map[string]string{
  1465  						"app":        "nginx_edge",
  1466  						"endpoint":   "health",
  1467  						"datacenter": "abc",
  1468  					},
  1469  					values: []expectedValue{{value: 30}},
  1470  					attributes: &storagemetadata.Attributes{
  1471  						MetricsType: storagemetadata.AggregatedMetricsType,
  1472  						Resolution:  1 * time.Second,
  1473  						Retention:   30 * 24 * time.Hour,
  1474  					},
  1475  				},
  1476  			},
  1477  		},
  1478  	})
  1479  
  1480  	// Test expected output
  1481  	testDownsamplerAggregation(t, testDownsampler)
  1482  }
  1483  
  1484  func TestDownsamplerAggregationWithRulesConfigMappingRulesWithDropTSTag(t *testing.T) {
  1485  	t.Parallel()
  1486  
  1487  	gaugeMetric := testGaugeMetric{
  1488  		tags: map[string]string{
  1489  			nameTag: "foo_metric",
  1490  			"app":   "nginx_edge",
  1491  		},
  1492  		timedSamples: []testGaugeMetricTimedSample{
  1493  			{value: 15}, {value: 10}, {value: 30}, {value: 5}, {value: 0},
  1494  		},
  1495  	}
  1496  	counterMetric := testCounterMetric{
  1497  		tags: map[string]string{
  1498  			nameTag: "counter0",
  1499  			"app":   "testapp",
  1500  			"foo":   "bar",
  1501  		},
  1502  		timedSamples: []testCounterMetricTimedSample{
  1503  			{value: 1}, {value: 2}, {value: 3},
  1504  		},
  1505  		expectDropTimestamp: true,
  1506  	}
  1507  	tags := []Tag{
  1508  		{Name: "__m3_drop_timestamp__", Value: ""},
  1509  	}
  1510  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1511  		rulesConfig: &RulesConfiguration{
  1512  			MappingRules: []MappingRuleConfiguration{
  1513  				{
  1514  					Filter:       "app:nginx*",
  1515  					Aggregations: []aggregation.Type{aggregation.Max},
  1516  					StoragePolicies: []StoragePolicyConfiguration{
  1517  						{
  1518  							Resolution: 1 * time.Second,
  1519  							Retention:  30 * 24 * time.Hour,
  1520  						},
  1521  					},
  1522  				},
  1523  				{
  1524  					Filter:       "app:testapp",
  1525  					Aggregations: []aggregation.Type{aggregation.Sum},
  1526  					StoragePolicies: []StoragePolicyConfiguration{
  1527  						{
  1528  							Resolution: 1 * time.Second,
  1529  							Retention:  30 * 24 * time.Hour,
  1530  						},
  1531  					},
  1532  					Tags: tags,
  1533  				},
  1534  			},
  1535  		},
  1536  		ingest: &testDownsamplerOptionsIngest{
  1537  			gaugeMetrics:   []testGaugeMetric{gaugeMetric},
  1538  			counterMetrics: []testCounterMetric{counterMetric},
  1539  		},
  1540  		expect: &testDownsamplerOptionsExpect{
  1541  			writes: []testExpectedWrite{
  1542  				{
  1543  					tags:   gaugeMetric.tags,
  1544  					values: []expectedValue{{value: 30}},
  1545  					attributes: &storagemetadata.Attributes{
  1546  						MetricsType: storagemetadata.AggregatedMetricsType,
  1547  						Resolution:  1 * time.Second,
  1548  						Retention:   30 * 24 * time.Hour,
  1549  					},
  1550  				},
  1551  				{
  1552  					tags:   counterMetric.tags,
  1553  					values: []expectedValue{{value: 6}},
  1554  					attributes: &storagemetadata.Attributes{
  1555  						MetricsType: storagemetadata.AggregatedMetricsType,
  1556  						Resolution:  1 * time.Second,
  1557  						Retention:   30 * 24 * time.Hour,
  1558  					},
  1559  				},
  1560  			},
  1561  		},
  1562  	})
  1563  
  1564  	// Test expected output
  1565  	testDownsamplerAggregation(t, testDownsampler)
  1566  }
  1567  
  1568  func TestDownsamplerAggregationWithRulesConfigRollupRulesNoNameTag(t *testing.T) {
  1569  	t.Parallel()
  1570  
  1571  	gaugeMetric := testGaugeMetric{
  1572  		tags: map[string]string{
  1573  			"app":           "nginx_edge",
  1574  			"status_code":   "500",
  1575  			"endpoint":      "/foo/bar",
  1576  			"not_rolled_up": "not_rolled_up_value",
  1577  		},
  1578  		timedSamples: []testGaugeMetricTimedSample{
  1579  			{value: 42},
  1580  			{value: 64, offset: 1 * time.Second},
  1581  		},
  1582  	}
  1583  	res := 1 * time.Second
  1584  	ret := 30 * 24 * time.Hour
  1585  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1586  		identTag: "endpoint",
  1587  		rulesConfig: &RulesConfiguration{
  1588  			RollupRules: []RollupRuleConfiguration{
  1589  				{
  1590  					Filter: fmt.Sprintf(
  1591  						"%s:http_requests app:* status_code:* endpoint:*",
  1592  						nameTag),
  1593  					Transforms: []TransformConfiguration{
  1594  						{
  1595  							Transform: &TransformOperationConfiguration{
  1596  								Type: transformation.PerSecond,
  1597  							},
  1598  						},
  1599  						{
  1600  							Rollup: &RollupOperationConfiguration{
  1601  								MetricName:   "http_requests_by_status_code",
  1602  								GroupBy:      []string{"app", "status_code", "endpoint"},
  1603  								Aggregations: []aggregation.Type{aggregation.Sum},
  1604  							},
  1605  						},
  1606  					},
  1607  					StoragePolicies: []StoragePolicyConfiguration{
  1608  						{
  1609  							Resolution: res,
  1610  							Retention:  ret,
  1611  						},
  1612  					},
  1613  				},
  1614  			},
  1615  		},
  1616  		ingest: &testDownsamplerOptionsIngest{
  1617  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1618  		},
  1619  		expect: &testDownsamplerOptionsExpect{
  1620  			writes: []testExpectedWrite{},
  1621  		},
  1622  	})
  1623  
  1624  	// Test expected output
  1625  	testDownsamplerAggregation(t, testDownsampler)
  1626  }
  1627  
  1628  func TestDownsamplerAggregationWithRulesConfigRollupRulesPerSecondSum(t *testing.T) {
  1629  	t.Parallel()
  1630  
  1631  	gaugeMetric := testGaugeMetric{
  1632  		tags: map[string]string{
  1633  			nameTag:         "http_requests",
  1634  			"app":           "nginx_edge",
  1635  			"status_code":   "500",
  1636  			"endpoint":      "/foo/bar",
  1637  			"not_rolled_up": "not_rolled_up_value",
  1638  		},
  1639  		timedSamples: []testGaugeMetricTimedSample{
  1640  			{value: 42},
  1641  			{value: 64, offset: 1 * time.Second},
  1642  		},
  1643  	}
  1644  	res := 1 * time.Second
  1645  	ret := 30 * 24 * time.Hour
  1646  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1647  		rulesConfig: &RulesConfiguration{
  1648  			RollupRules: []RollupRuleConfiguration{
  1649  				{
  1650  					Filter: fmt.Sprintf(
  1651  						"%s:http_requests app:* status_code:* endpoint:*",
  1652  						nameTag),
  1653  					Transforms: []TransformConfiguration{
  1654  						{
  1655  							Transform: &TransformOperationConfiguration{
  1656  								Type: transformation.PerSecond,
  1657  							},
  1658  						},
  1659  						{
  1660  							Rollup: &RollupOperationConfiguration{
  1661  								MetricName:   "http_requests_by_status_code",
  1662  								GroupBy:      []string{"app", "status_code", "endpoint"},
  1663  								Aggregations: []aggregation.Type{aggregation.Sum},
  1664  							},
  1665  						},
  1666  					},
  1667  					StoragePolicies: []StoragePolicyConfiguration{
  1668  						{
  1669  							Resolution: res,
  1670  							Retention:  ret,
  1671  						},
  1672  					},
  1673  				},
  1674  			},
  1675  		},
  1676  		ingest: &testDownsamplerOptionsIngest{
  1677  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1678  		},
  1679  		expect: &testDownsamplerOptionsExpect{
  1680  			writes: []testExpectedWrite{
  1681  				{
  1682  					tags: map[string]string{
  1683  						nameTag:               "http_requests_by_status_code",
  1684  						string(rollupTagName): string(rollupTagValue),
  1685  						"app":                 "nginx_edge",
  1686  						"status_code":         "500",
  1687  						"endpoint":            "/foo/bar",
  1688  					},
  1689  					values: []expectedValue{{value: 22}},
  1690  					attributes: &storagemetadata.Attributes{
  1691  						MetricsType: storagemetadata.AggregatedMetricsType,
  1692  						Resolution:  res,
  1693  						Retention:   ret,
  1694  					},
  1695  				},
  1696  			},
  1697  		},
  1698  	})
  1699  
  1700  	// Test expected output
  1701  	testDownsamplerAggregation(t, testDownsampler)
  1702  }
  1703  
  1704  func TestDownsamplerAggregationWithRulesConfigRollupRulesAugmentTags(t *testing.T) {
  1705  	t.Parallel()
  1706  
  1707  	gaugeMetric := testGaugeMetric{
  1708  		tags: map[string]string{
  1709  			nameTag:         "http_requests",
  1710  			"app":           "nginx_edge",
  1711  			"status_code":   "500",
  1712  			"endpoint":      "/foo/bar",
  1713  			"not_rolled_up": "not_rolled_up_value",
  1714  		},
  1715  		timedSamples: []testGaugeMetricTimedSample{
  1716  			{value: 42},
  1717  			{value: 64, offset: 1 * time.Second},
  1718  		},
  1719  	}
  1720  	res := 1 * time.Second
  1721  	ret := 30 * 24 * time.Hour
  1722  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1723  		rulesConfig: &RulesConfiguration{
  1724  			RollupRules: []RollupRuleConfiguration{
  1725  				{
  1726  					Filter: fmt.Sprintf(
  1727  						"%s:http_requests app:* status_code:* endpoint:*",
  1728  						nameTag),
  1729  					Transforms: []TransformConfiguration{
  1730  						{
  1731  							Transform: &TransformOperationConfiguration{
  1732  								Type: transformation.PerSecond,
  1733  							},
  1734  						},
  1735  						{
  1736  							Rollup: &RollupOperationConfiguration{
  1737  								MetricName:   "http_requests_by_status_code",
  1738  								GroupBy:      []string{"app", "status_code", "endpoint"},
  1739  								Aggregations: []aggregation.Type{aggregation.Sum},
  1740  							},
  1741  						},
  1742  					},
  1743  					StoragePolicies: []StoragePolicyConfiguration{
  1744  						{
  1745  							Resolution: res,
  1746  							Retention:  ret,
  1747  						},
  1748  					},
  1749  					Tags: []Tag{
  1750  						{
  1751  							Name:  "__rollup_type__",
  1752  							Value: "counter",
  1753  						},
  1754  					},
  1755  				},
  1756  			},
  1757  		},
  1758  		ingest: &testDownsamplerOptionsIngest{
  1759  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1760  		},
  1761  		expect: &testDownsamplerOptionsExpect{
  1762  			writes: []testExpectedWrite{
  1763  				{
  1764  					tags: map[string]string{
  1765  						nameTag:               "http_requests_by_status_code",
  1766  						string(rollupTagName): string(rollupTagValue),
  1767  						"__rollup_type__":     "counter",
  1768  						"app":                 "nginx_edge",
  1769  						"status_code":         "500",
  1770  						"endpoint":            "/foo/bar",
  1771  					},
  1772  					values: []expectedValue{{value: 22}},
  1773  					attributes: &storagemetadata.Attributes{
  1774  						MetricsType: storagemetadata.AggregatedMetricsType,
  1775  						Resolution:  res,
  1776  						Retention:   ret,
  1777  					},
  1778  				},
  1779  			},
  1780  		},
  1781  	})
  1782  
  1783  	// Test expected output
  1784  	testDownsamplerAggregation(t, testDownsampler)
  1785  }
  1786  
  1787  // TestDownsamplerAggregationWithRulesConfigRollupRulesAggregateTransformNoRollup
  1788  // tests that rollup rules can be used to actually simply transform values
  1789  // without the need for an explicit rollup step.
  1790  func TestDownsamplerAggregationWithRulesConfigRollupRulesAggregateTransformNoRollup(t *testing.T) {
  1791  	t.Parallel()
  1792  
  1793  	gaugeMetric := testGaugeMetric{
  1794  		tags: map[string]string{
  1795  			nameTag:         "http_requests",
  1796  			"app":           "nginx_edge",
  1797  			"status_code":   "500",
  1798  			"endpoint":      "/foo/bar",
  1799  			"not_rolled_up": "not_rolled_up_value",
  1800  		},
  1801  		timedSamples: []testGaugeMetricTimedSample{
  1802  			{value: 42},
  1803  			{value: 64},
  1804  		},
  1805  	}
  1806  	res := 5 * time.Second
  1807  	ret := 30 * 24 * time.Hour
  1808  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1809  		rulesConfig: &RulesConfiguration{
  1810  			RollupRules: []RollupRuleConfiguration{
  1811  				{
  1812  					Filter: fmt.Sprintf(
  1813  						"%s:http_requests app:* status_code:* endpoint:*",
  1814  						nameTag),
  1815  					Transforms: []TransformConfiguration{
  1816  						{
  1817  							Aggregate: &AggregateOperationConfiguration{
  1818  								Type: aggregation.Sum,
  1819  							},
  1820  						},
  1821  						{
  1822  							Transform: &TransformOperationConfiguration{
  1823  								Type: transformation.Add,
  1824  							},
  1825  						},
  1826  					},
  1827  					StoragePolicies: []StoragePolicyConfiguration{
  1828  						{
  1829  							Resolution: res,
  1830  							Retention:  ret,
  1831  						},
  1832  					},
  1833  				},
  1834  			},
  1835  		},
  1836  		ingest: &testDownsamplerOptionsIngest{
  1837  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  1838  		},
  1839  		expect: &testDownsamplerOptionsExpect{
  1840  			writes: []testExpectedWrite{
  1841  				{
  1842  					tags: map[string]string{
  1843  						nameTag:         "http_requests",
  1844  						"app":           "nginx_edge",
  1845  						"status_code":   "500",
  1846  						"endpoint":      "/foo/bar",
  1847  						"not_rolled_up": "not_rolled_up_value",
  1848  					},
  1849  					values: []expectedValue{{value: 106}},
  1850  					attributes: &storagemetadata.Attributes{
  1851  						MetricsType: storagemetadata.AggregatedMetricsType,
  1852  						Resolution:  res,
  1853  						Retention:   ret,
  1854  					},
  1855  				},
  1856  			},
  1857  		},
  1858  	})
  1859  
  1860  	// Test expected output
  1861  	testDownsamplerAggregation(t, testDownsampler)
  1862  }
  1863  
  1864  func TestDownsamplerAggregationWithRulesConfigRollupRulesIncreaseAdd(t *testing.T) {
  1865  	t.Parallel()
  1866  
  1867  	// nolint:dupl
  1868  	gaugeMetrics := []testGaugeMetric{
  1869  		{
  1870  			tags: map[string]string{
  1871  				nameTag:         "http_requests",
  1872  				"app":           "nginx_edge",
  1873  				"status_code":   "500",
  1874  				"endpoint":      "/foo/bar",
  1875  				"not_rolled_up": "not_rolled_up_value_1",
  1876  			},
  1877  			timedSamples: []testGaugeMetricTimedSample{
  1878  				{value: 42, offset: 1 * time.Second}, // +42
  1879  				{value: 12, offset: 2 * time.Second}, // +12 - simulate a reset (should count as a 0)
  1880  				{value: 33, offset: 3 * time.Second}, // +21
  1881  			},
  1882  		},
  1883  		{
  1884  			tags: map[string]string{
  1885  				nameTag:         "http_requests",
  1886  				"app":           "nginx_edge",
  1887  				"status_code":   "500",
  1888  				"endpoint":      "/foo/bar",
  1889  				"not_rolled_up": "not_rolled_up_value_2",
  1890  			},
  1891  			timedSamples: []testGaugeMetricTimedSample{
  1892  				{value: 13, offset: 1 * time.Second}, // +13
  1893  				{value: 27, offset: 2 * time.Second}, // +14
  1894  				{value: 42, offset: 3 * time.Second}, // +15
  1895  			},
  1896  		},
  1897  	}
  1898  	res := 1 * time.Second
  1899  	ret := 30 * 24 * time.Hour
  1900  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1901  		rulesConfig: &RulesConfiguration{
  1902  			RollupRules: []RollupRuleConfiguration{
  1903  				{
  1904  					Filter: fmt.Sprintf(
  1905  						"%s:http_requests app:* status_code:* endpoint:*",
  1906  						nameTag),
  1907  					Transforms: []TransformConfiguration{
  1908  						{
  1909  							Transform: &TransformOperationConfiguration{
  1910  								Type: transformation.Increase,
  1911  							},
  1912  						},
  1913  						{
  1914  							Rollup: &RollupOperationConfiguration{
  1915  								MetricName:   "http_requests_by_status_code",
  1916  								GroupBy:      []string{"app", "status_code", "endpoint"},
  1917  								Aggregations: []aggregation.Type{aggregation.Sum},
  1918  							},
  1919  						},
  1920  						{
  1921  							Transform: &TransformOperationConfiguration{
  1922  								Type: transformation.Add,
  1923  							},
  1924  						},
  1925  					},
  1926  					StoragePolicies: []StoragePolicyConfiguration{
  1927  						{
  1928  							Resolution: res,
  1929  							Retention:  ret,
  1930  						},
  1931  					},
  1932  				},
  1933  			},
  1934  		},
  1935  		ingest: &testDownsamplerOptionsIngest{
  1936  			gaugeMetrics: gaugeMetrics,
  1937  		},
  1938  		expect: &testDownsamplerOptionsExpect{
  1939  			writes: []testExpectedWrite{
  1940  				{
  1941  					tags: map[string]string{
  1942  						nameTag:               "http_requests_by_status_code",
  1943  						string(rollupTagName): string(rollupTagValue),
  1944  						"app":                 "nginx_edge",
  1945  						"status_code":         "500",
  1946  						"endpoint":            "/foo/bar",
  1947  					},
  1948  					values: []expectedValue{
  1949  						{value: 55},
  1950  						{value: 69, offset: 1 * time.Second},
  1951  						{value: 105, offset: 2 * time.Second},
  1952  					},
  1953  					attributes: &storagemetadata.Attributes{
  1954  						MetricsType: storagemetadata.AggregatedMetricsType,
  1955  						Resolution:  res,
  1956  						Retention:   ret,
  1957  					},
  1958  				},
  1959  			},
  1960  		},
  1961  	})
  1962  
  1963  	// Test expected output
  1964  	testDownsamplerAggregation(t, testDownsampler)
  1965  }
  1966  
  1967  func TestDownsamplerAggregationWithRulesConfigRollupRuleAndDropPolicy(t *testing.T) {
  1968  	t.Parallel()
  1969  
  1970  	gaugeMetric := testGaugeMetric{
  1971  		tags: map[string]string{
  1972  			nameTag:         "http_requests",
  1973  			"app":           "nginx_edge",
  1974  			"status_code":   "500",
  1975  			"endpoint":      "/foo/bar",
  1976  			"not_rolled_up": "not_rolled_up_value",
  1977  		},
  1978  		timedSamples: []testGaugeMetricTimedSample{
  1979  			{value: 42},
  1980  			{value: 64, offset: 1 * time.Second},
  1981  		},
  1982  		expectDropPolicyApplied: true,
  1983  	}
  1984  	res := 1 * time.Second
  1985  	ret := 30 * 24 * time.Hour
  1986  	filter := fmt.Sprintf("%s:http_requests app:* status_code:* endpoint:*", nameTag)
  1987  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  1988  		rulesConfig: &RulesConfiguration{
  1989  			MappingRules: []MappingRuleConfiguration{
  1990  				{
  1991  					Filter: filter,
  1992  					Drop:   true,
  1993  				},
  1994  			},
  1995  			RollupRules: []RollupRuleConfiguration{
  1996  				{
  1997  					Filter: filter,
  1998  					Transforms: []TransformConfiguration{
  1999  						{
  2000  							Transform: &TransformOperationConfiguration{
  2001  								Type: transformation.PerSecond,
  2002  							},
  2003  						},
  2004  						{
  2005  							Rollup: &RollupOperationConfiguration{
  2006  								MetricName:   "http_requests_by_status_code",
  2007  								GroupBy:      []string{"app", "status_code", "endpoint"},
  2008  								Aggregations: []aggregation.Type{aggregation.Sum},
  2009  							},
  2010  						},
  2011  					},
  2012  					StoragePolicies: []StoragePolicyConfiguration{
  2013  						{
  2014  							Resolution: res,
  2015  							Retention:  ret,
  2016  						},
  2017  					},
  2018  				},
  2019  			},
  2020  		},
  2021  		ingest: &testDownsamplerOptionsIngest{
  2022  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  2023  		},
  2024  		expect: &testDownsamplerOptionsExpect{
  2025  			writes: []testExpectedWrite{
  2026  				{
  2027  					tags: map[string]string{
  2028  						nameTag:               "http_requests_by_status_code",
  2029  						string(rollupTagName): string(rollupTagValue),
  2030  						"app":                 "nginx_edge",
  2031  						"status_code":         "500",
  2032  						"endpoint":            "/foo/bar",
  2033  					},
  2034  					values: []expectedValue{{value: 22}},
  2035  					attributes: &storagemetadata.Attributes{
  2036  						MetricsType: storagemetadata.AggregatedMetricsType,
  2037  						Resolution:  res,
  2038  						Retention:   ret,
  2039  					},
  2040  				},
  2041  			},
  2042  		},
  2043  	})
  2044  
  2045  	// Test expected output
  2046  	testDownsamplerAggregation(t, testDownsampler)
  2047  }
  2048  
  2049  func TestDownsamplerAggregationWithRulesConfigRollupRuleAndDropPolicyAndDropTimestamp(t *testing.T) {
  2050  	t.Parallel()
  2051  
  2052  	gaugeMetrics := []testGaugeMetric{
  2053  		{
  2054  			tags: map[string]string{
  2055  				nameTag:         "http_requests",
  2056  				"app":           "nginx_edge",
  2057  				"status_code":   "500",
  2058  				"endpoint":      "/foo/bar",
  2059  				"not_rolled_up": "not_rolled_up_value_1",
  2060  			},
  2061  			timedSamples: []testGaugeMetricTimedSample{
  2062  				{value: 42}, // +42 (should not be accounted since is a reset)
  2063  				// Explicit no value.
  2064  				{value: 12, offset: 2 * time.Second}, // +12 - simulate a reset (should not be accounted)
  2065  			},
  2066  			expectDropTimestamp:     true,
  2067  			expectDropPolicyApplied: true,
  2068  		},
  2069  		{
  2070  			tags: map[string]string{
  2071  				nameTag:         "http_requests",
  2072  				"app":           "nginx_edge",
  2073  				"status_code":   "500",
  2074  				"endpoint":      "/foo/bar",
  2075  				"not_rolled_up": "not_rolled_up_value_2",
  2076  			},
  2077  			timedSamples: []testGaugeMetricTimedSample{
  2078  				{value: 13},
  2079  				{value: 27, offset: 2 * time.Second}, // +14
  2080  			},
  2081  			expectDropTimestamp:     true,
  2082  			expectDropPolicyApplied: true,
  2083  		},
  2084  	}
  2085  	tags := []Tag{
  2086  		{Name: "__m3_drop_timestamp__", Value: ""},
  2087  	}
  2088  	res := 1 * time.Second
  2089  	ret := 30 * 24 * time.Hour
  2090  	filter := fmt.Sprintf("%s:http_requests app:* status_code:* endpoint:*", nameTag)
  2091  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2092  		rulesConfig: &RulesConfiguration{
  2093  			MappingRules: []MappingRuleConfiguration{
  2094  				{
  2095  					Filter: filter,
  2096  					Drop:   true,
  2097  					Tags:   tags,
  2098  				},
  2099  			},
  2100  			RollupRules: []RollupRuleConfiguration{
  2101  				{
  2102  					Filter: filter,
  2103  					Transforms: []TransformConfiguration{
  2104  						{
  2105  							Rollup: &RollupOperationConfiguration{
  2106  								MetricName:   "http_requests_by_status_code",
  2107  								GroupBy:      []string{"app", "status_code", "endpoint"},
  2108  								Aggregations: []aggregation.Type{aggregation.Sum},
  2109  							},
  2110  						},
  2111  					},
  2112  					StoragePolicies: []StoragePolicyConfiguration{
  2113  						{
  2114  							Resolution: res,
  2115  							Retention:  ret,
  2116  						},
  2117  					},
  2118  				},
  2119  			},
  2120  		},
  2121  		ingest: &testDownsamplerOptionsIngest{
  2122  			gaugeMetrics: gaugeMetrics,
  2123  		},
  2124  		expect: &testDownsamplerOptionsExpect{
  2125  			writes: []testExpectedWrite{
  2126  				{
  2127  					tags: map[string]string{
  2128  						nameTag:               "http_requests_by_status_code",
  2129  						string(rollupTagName): string(rollupTagValue),
  2130  						"app":                 "nginx_edge",
  2131  						"status_code":         "500",
  2132  						"endpoint":            "/foo/bar",
  2133  					},
  2134  					values: []expectedValue{{value: 94}},
  2135  					attributes: &storagemetadata.Attributes{
  2136  						MetricsType: storagemetadata.AggregatedMetricsType,
  2137  						Resolution:  res,
  2138  						Retention:   ret,
  2139  					},
  2140  				},
  2141  			},
  2142  		},
  2143  	})
  2144  
  2145  	// Test expected output
  2146  	testDownsamplerAggregation(t, testDownsampler)
  2147  }
  2148  
  2149  func TestDownsamplerAggregationWithRulesConfigRollupRuleUntimedRollups(t *testing.T) {
  2150  	t.Parallel()
  2151  
  2152  	gaugeMetrics := []testGaugeMetric{
  2153  		{
  2154  			tags: map[string]string{
  2155  				nameTag:         "http_requests",
  2156  				"app":           "nginx_edge",
  2157  				"status_code":   "500",
  2158  				"endpoint":      "/foo/bar",
  2159  				"not_rolled_up": "not_rolled_up_value_1",
  2160  			},
  2161  			timedSamples: []testGaugeMetricTimedSample{
  2162  				{value: 42},
  2163  				{value: 12, offset: 2 * time.Second},
  2164  			},
  2165  			expectDropTimestamp: true,
  2166  		},
  2167  		{
  2168  			tags: map[string]string{
  2169  				nameTag:         "http_requests",
  2170  				"app":           "nginx_edge",
  2171  				"status_code":   "500",
  2172  				"endpoint":      "/foo/bar",
  2173  				"not_rolled_up": "not_rolled_up_value_2",
  2174  			},
  2175  			timedSamples: []testGaugeMetricTimedSample{
  2176  				{value: 13},
  2177  				{value: 27, offset: 2 * time.Second},
  2178  			},
  2179  			expectDropTimestamp: true,
  2180  		},
  2181  	}
  2182  	res := 1 * time.Second
  2183  	ret := 30 * 24 * time.Hour
  2184  	filter := fmt.Sprintf("%s:http_requests app:* status_code:* endpoint:*", nameTag)
  2185  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2186  		untimedRollups: true,
  2187  		rulesConfig: &RulesConfiguration{
  2188  			MappingRules: []MappingRuleConfiguration{
  2189  				{
  2190  					Filter:       "app:nginx*",
  2191  					Aggregations: []aggregation.Type{aggregation.Max},
  2192  					StoragePolicies: []StoragePolicyConfiguration{
  2193  						{
  2194  							Resolution: 1 * time.Second,
  2195  							Retention:  30 * 24 * time.Hour,
  2196  						},
  2197  					},
  2198  				},
  2199  			},
  2200  			RollupRules: []RollupRuleConfiguration{
  2201  				{
  2202  					Filter: filter,
  2203  					Transforms: []TransformConfiguration{
  2204  						{
  2205  							Rollup: &RollupOperationConfiguration{
  2206  								MetricName:   "http_requests_by_status_code",
  2207  								GroupBy:      []string{"app", "status_code", "endpoint"},
  2208  								Aggregations: []aggregation.Type{aggregation.Sum},
  2209  							},
  2210  						},
  2211  					},
  2212  					StoragePolicies: []StoragePolicyConfiguration{
  2213  						{
  2214  							Resolution: res,
  2215  							Retention:  ret,
  2216  						},
  2217  					},
  2218  				},
  2219  			},
  2220  		},
  2221  		ingest: &testDownsamplerOptionsIngest{
  2222  			gaugeMetrics: gaugeMetrics,
  2223  		},
  2224  		expect: &testDownsamplerOptionsExpect{
  2225  			writes: []testExpectedWrite{
  2226  				{
  2227  					tags: map[string]string{
  2228  						nameTag:               "http_requests_by_status_code",
  2229  						string(rollupTagName): string(rollupTagValue),
  2230  						"app":                 "nginx_edge",
  2231  						"status_code":         "500",
  2232  						"endpoint":            "/foo/bar",
  2233  					},
  2234  					values: []expectedValue{{value: 94}},
  2235  					attributes: &storagemetadata.Attributes{
  2236  						MetricsType: storagemetadata.AggregatedMetricsType,
  2237  						Resolution:  res,
  2238  						Retention:   ret,
  2239  					},
  2240  				},
  2241  			},
  2242  		},
  2243  	})
  2244  
  2245  	// Test expected output
  2246  	testDownsamplerAggregation(t, testDownsampler)
  2247  }
  2248  
  2249  func TestDownsamplerAggregationWithRulesConfigRollupRuleUntimedRollupsWaitForOffset(t *testing.T) {
  2250  	t.Parallel()
  2251  
  2252  	gaugeMetrics := []testGaugeMetric{
  2253  		{
  2254  			tags: map[string]string{
  2255  				nameTag:         "http_requests",
  2256  				"app":           "nginx_edge",
  2257  				"status_code":   "500",
  2258  				"endpoint":      "/foo/bar",
  2259  				"not_rolled_up": "not_rolled_up_value_1",
  2260  			},
  2261  			timedSamples: []testGaugeMetricTimedSample{
  2262  				{value: 42},
  2263  			},
  2264  			expectDropPolicyApplied: true,
  2265  			expectDropTimestamp:     true,
  2266  		},
  2267  		{
  2268  			tags: map[string]string{
  2269  				nameTag:         "http_requests",
  2270  				"app":           "nginx_edge",
  2271  				"status_code":   "500",
  2272  				"endpoint":      "/foo/bar",
  2273  				"not_rolled_up": "not_rolled_up_value_2",
  2274  			},
  2275  			timedSamples: []testGaugeMetricTimedSample{
  2276  				{value: 12, offset: 2 * time.Second},
  2277  			},
  2278  			expectDropPolicyApplied: true,
  2279  			expectDropTimestamp:     true,
  2280  		},
  2281  		{
  2282  			tags: map[string]string{
  2283  				nameTag:         "http_requests",
  2284  				"app":           "nginx_edge",
  2285  				"status_code":   "500",
  2286  				"endpoint":      "/foo/bar",
  2287  				"not_rolled_up": "not_rolled_up_value_3",
  2288  			},
  2289  			timedSamples: []testGaugeMetricTimedSample{
  2290  				{value: 13},
  2291  			},
  2292  			expectDropPolicyApplied: true,
  2293  			expectDropTimestamp:     true,
  2294  		},
  2295  	}
  2296  	res := 1 * time.Second
  2297  	ret := 30 * 24 * time.Hour
  2298  	filter := fmt.Sprintf("%s:http_requests app:* status_code:* endpoint:*", nameTag)
  2299  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2300  		waitForOffset:  true,
  2301  		untimedRollups: true,
  2302  		rulesConfig: &RulesConfiguration{
  2303  			MappingRules: []MappingRuleConfiguration{
  2304  				{
  2305  					Filter: filter,
  2306  					Drop:   true,
  2307  				},
  2308  			},
  2309  			RollupRules: []RollupRuleConfiguration{
  2310  				{
  2311  					Filter: filter,
  2312  					Transforms: []TransformConfiguration{
  2313  						{
  2314  							Rollup: &RollupOperationConfiguration{
  2315  								MetricName:   "http_requests_by_status_code",
  2316  								GroupBy:      []string{"app", "status_code", "endpoint"},
  2317  								Aggregations: []aggregation.Type{aggregation.Sum},
  2318  							},
  2319  						},
  2320  					},
  2321  					StoragePolicies: []StoragePolicyConfiguration{
  2322  						{
  2323  							Resolution: res,
  2324  							Retention:  ret,
  2325  						},
  2326  					},
  2327  				},
  2328  			},
  2329  		},
  2330  		ingest: &testDownsamplerOptionsIngest{
  2331  			gaugeMetrics: gaugeMetrics,
  2332  		},
  2333  		expect: &testDownsamplerOptionsExpect{
  2334  			writes: []testExpectedWrite{
  2335  				{
  2336  					tags: map[string]string{
  2337  						nameTag:               "http_requests_by_status_code",
  2338  						string(rollupTagName): string(rollupTagValue),
  2339  						"app":                 "nginx_edge",
  2340  						"status_code":         "500",
  2341  						"endpoint":            "/foo/bar",
  2342  					},
  2343  					values: []expectedValue{{value: 42}, {value: 25}},
  2344  					attributes: &storagemetadata.Attributes{
  2345  						MetricsType: storagemetadata.AggregatedMetricsType,
  2346  						Resolution:  res,
  2347  						Retention:   ret,
  2348  					},
  2349  				},
  2350  			},
  2351  		},
  2352  	})
  2353  
  2354  	// Test expected output
  2355  	testDownsamplerAggregation(t, testDownsampler)
  2356  }
  2357  
  2358  func TestDownsamplerAggregationWithRulesConfigRollupRuleRollupLaterUntimedRollups(t *testing.T) {
  2359  	t.Parallel()
  2360  
  2361  	gaugeMetrics := []testGaugeMetric{
  2362  		{
  2363  			tags: map[string]string{
  2364  				nameTag:         "http_requests",
  2365  				"app":           "nginx_edge",
  2366  				"status_code":   "500",
  2367  				"endpoint":      "/foo/bar",
  2368  				"not_rolled_up": "not_rolled_up_value_1",
  2369  			},
  2370  			timedSamples: []testGaugeMetricTimedSample{
  2371  				{value: 42},
  2372  				{value: 12, offset: 2 * time.Second},
  2373  			},
  2374  			expectDropTimestamp: true,
  2375  		},
  2376  		{
  2377  			tags: map[string]string{
  2378  				nameTag:         "http_requests",
  2379  				"app":           "nginx_edge",
  2380  				"status_code":   "500",
  2381  				"endpoint":      "/foo/bar",
  2382  				"not_rolled_up": "not_rolled_up_value_2",
  2383  			},
  2384  			timedSamples: []testGaugeMetricTimedSample{
  2385  				{value: 13},
  2386  				{value: 27, offset: 2 * time.Second},
  2387  			},
  2388  			expectDropTimestamp: true,
  2389  		},
  2390  	}
  2391  	res := 1 * time.Second
  2392  	ret := 30 * 24 * time.Hour
  2393  	filter := fmt.Sprintf("%s:http_requests app:* status_code:* endpoint:*", nameTag)
  2394  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2395  		untimedRollups: true,
  2396  		rulesConfig: &RulesConfiguration{
  2397  			MappingRules: []MappingRuleConfiguration{
  2398  				{
  2399  					Filter:       "app:nginx*",
  2400  					Aggregations: []aggregation.Type{aggregation.Max},
  2401  					StoragePolicies: []StoragePolicyConfiguration{
  2402  						{
  2403  							Resolution: 1 * time.Second,
  2404  							Retention:  30 * 24 * time.Hour,
  2405  						},
  2406  					},
  2407  				},
  2408  			},
  2409  			RollupRules: []RollupRuleConfiguration{
  2410  				{
  2411  					Filter: filter,
  2412  					Transforms: []TransformConfiguration{
  2413  						{
  2414  							Transform: &TransformOperationConfiguration{
  2415  								Type: transformation.Add,
  2416  							},
  2417  						},
  2418  						{
  2419  							Rollup: &RollupOperationConfiguration{
  2420  								MetricName:   "http_requests_by_status_code",
  2421  								GroupBy:      []string{"app", "status_code", "endpoint"},
  2422  								Aggregations: []aggregation.Type{aggregation.Sum},
  2423  							},
  2424  						},
  2425  					},
  2426  					StoragePolicies: []StoragePolicyConfiguration{
  2427  						{
  2428  							Resolution: res,
  2429  							Retention:  ret,
  2430  						},
  2431  					},
  2432  				},
  2433  			},
  2434  		},
  2435  		ingest: &testDownsamplerOptionsIngest{
  2436  			gaugeMetrics: gaugeMetrics,
  2437  		},
  2438  		expect: &testDownsamplerOptionsExpect{
  2439  			writes: []testExpectedWrite{
  2440  				{
  2441  					tags: map[string]string{
  2442  						nameTag:               "http_requests_by_status_code",
  2443  						string(rollupTagName): string(rollupTagValue),
  2444  						"app":                 "nginx_edge",
  2445  						"status_code":         "500",
  2446  						"endpoint":            "/foo/bar",
  2447  					},
  2448  					values: []expectedValue{{value: 39}},
  2449  					attributes: &storagemetadata.Attributes{
  2450  						MetricsType: storagemetadata.AggregatedMetricsType,
  2451  						Resolution:  res,
  2452  						Retention:   ret,
  2453  					},
  2454  				},
  2455  			},
  2456  		},
  2457  	})
  2458  
  2459  	// Test expected output
  2460  	testDownsamplerAggregation(t, testDownsampler)
  2461  }
  2462  
  2463  func TestDownsamplerAggregationWithRulesConfigRollupRulesExcludeByLastMean(t *testing.T) {
  2464  	t.Parallel()
  2465  
  2466  	gaugeMetrics := []testGaugeMetric{
  2467  		{
  2468  			tags: map[string]string{
  2469  				nameTag:       "http_request_latency_max_gauge",
  2470  				"app":         "nginx_edge",
  2471  				"status_code": "500",
  2472  				"endpoint":    "/foo/bar",
  2473  				"instance":    "not_rolled_up_instance_1",
  2474  			},
  2475  			timedSamples: []testGaugeMetricTimedSample{
  2476  				{value: 42},
  2477  			},
  2478  		},
  2479  		{
  2480  			tags: map[string]string{
  2481  				nameTag:       "http_request_latency_max_gauge",
  2482  				"app":         "nginx_edge",
  2483  				"status_code": "500",
  2484  				"endpoint":    "/foo/bar",
  2485  				"instance":    "not_rolled_up_instance_2",
  2486  			},
  2487  			timedSamples: []testGaugeMetricTimedSample{
  2488  				{value: 13},
  2489  			},
  2490  		},
  2491  	}
  2492  	res := 1 * time.Second
  2493  	ret := 30 * 24 * time.Hour
  2494  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2495  		rulesConfig: &RulesConfiguration{
  2496  			RollupRules: []RollupRuleConfiguration{
  2497  				{
  2498  					Filter: fmt.Sprintf(
  2499  						"%s:http_request_latency_max_gauge app:* status_code:* endpoint:*",
  2500  						nameTag),
  2501  					Transforms: []TransformConfiguration{
  2502  						{
  2503  							Aggregate: &AggregateOperationConfiguration{
  2504  								Type: aggregation.Last,
  2505  							},
  2506  						},
  2507  						{
  2508  							Rollup: &RollupOperationConfiguration{
  2509  								MetricName:   "{{ .MetricName }}:mean_without_instance",
  2510  								ExcludeBy:    []string{"instance"},
  2511  								Aggregations: []aggregation.Type{aggregation.Mean},
  2512  							},
  2513  						},
  2514  					},
  2515  					StoragePolicies: []StoragePolicyConfiguration{
  2516  						{
  2517  							Resolution: res,
  2518  							Retention:  ret,
  2519  						},
  2520  					},
  2521  				},
  2522  			},
  2523  		},
  2524  		ingest: &testDownsamplerOptionsIngest{
  2525  			gaugeMetrics: gaugeMetrics,
  2526  		},
  2527  		expect: &testDownsamplerOptionsExpect{
  2528  			writes: []testExpectedWrite{
  2529  				{
  2530  					tags: map[string]string{
  2531  						nameTag:               "http_request_latency_max_gauge:mean_without_instance",
  2532  						string(rollupTagName): string(rollupTagValue),
  2533  						"app":                 "nginx_edge",
  2534  						"status_code":         "500",
  2535  						"endpoint":            "/foo/bar",
  2536  					},
  2537  					values: []expectedValue{
  2538  						{value: 27.5},
  2539  					},
  2540  					attributes: &storagemetadata.Attributes{
  2541  						MetricsType: storagemetadata.AggregatedMetricsType,
  2542  						Resolution:  res,
  2543  						Retention:   ret,
  2544  					},
  2545  				},
  2546  			},
  2547  		},
  2548  	})
  2549  
  2550  	// Test expected output
  2551  	testDownsamplerAggregation(t, testDownsampler)
  2552  }
  2553  
  2554  func TestDownsamplerAggregationWithRulesConfigRollupRulesExcludeByIncreaseSumAdd(t *testing.T) {
  2555  	t.Parallel()
  2556  
  2557  	// nolint:dupl
  2558  	gaugeMetrics := []testGaugeMetric{
  2559  		{
  2560  			tags: map[string]string{
  2561  				nameTag:       "http_requests",
  2562  				"app":         "nginx_edge",
  2563  				"status_code": "500",
  2564  				"endpoint":    "/foo/bar",
  2565  				"instance":    "not_rolled_up_instance_1",
  2566  			},
  2567  			timedSamples: []testGaugeMetricTimedSample{
  2568  				{value: 42, offset: 1 * time.Second}, // +42
  2569  				{value: 12, offset: 2 * time.Second}, // +12 - simulate a reset (should count as a 0)
  2570  				{value: 33, offset: 3 * time.Second}, // +21
  2571  			},
  2572  		},
  2573  		{
  2574  			tags: map[string]string{
  2575  				nameTag:       "http_requests",
  2576  				"app":         "nginx_edge",
  2577  				"status_code": "500",
  2578  				"endpoint":    "/foo/bar",
  2579  				"instance":    "not_rolled_up_instance_2",
  2580  			},
  2581  			timedSamples: []testGaugeMetricTimedSample{
  2582  				{value: 13, offset: 1 * time.Second}, // +13
  2583  				{value: 27, offset: 2 * time.Second}, // +14
  2584  				{value: 42, offset: 3 * time.Second}, // +15
  2585  			},
  2586  		},
  2587  	}
  2588  	res := 1 * time.Second
  2589  	ret := 30 * 24 * time.Hour
  2590  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2591  		rulesConfig: &RulesConfiguration{
  2592  			RollupRules: []RollupRuleConfiguration{
  2593  				{
  2594  					Filter: fmt.Sprintf(
  2595  						"%s:http_requests app:* status_code:* endpoint:*",
  2596  						nameTag),
  2597  					Transforms: []TransformConfiguration{
  2598  						{
  2599  							Transform: &TransformOperationConfiguration{
  2600  								Type: transformation.Increase,
  2601  							},
  2602  						},
  2603  						{
  2604  							Rollup: &RollupOperationConfiguration{
  2605  								MetricName:   "{{ .MetricName }}:sum_without_instance",
  2606  								ExcludeBy:    []string{"instance"},
  2607  								Aggregations: []aggregation.Type{aggregation.Sum},
  2608  							},
  2609  						},
  2610  						{
  2611  							Transform: &TransformOperationConfiguration{
  2612  								Type: transformation.Add,
  2613  							},
  2614  						},
  2615  					},
  2616  					StoragePolicies: []StoragePolicyConfiguration{
  2617  						{
  2618  							Resolution: res,
  2619  							Retention:  ret,
  2620  						},
  2621  					},
  2622  				},
  2623  			},
  2624  		},
  2625  		ingest: &testDownsamplerOptionsIngest{
  2626  			gaugeMetrics: gaugeMetrics,
  2627  		},
  2628  		expect: &testDownsamplerOptionsExpect{
  2629  			writes: []testExpectedWrite{
  2630  				{
  2631  					tags: map[string]string{
  2632  						nameTag:               "http_requests:sum_without_instance",
  2633  						string(rollupTagName): string(rollupTagValue),
  2634  						"app":                 "nginx_edge",
  2635  						"status_code":         "500",
  2636  						"endpoint":            "/foo/bar",
  2637  					},
  2638  					values: []expectedValue{
  2639  						{value: 55},
  2640  						{value: 69, offset: 1 * time.Second},
  2641  						{value: 105, offset: 2 * time.Second},
  2642  					},
  2643  					attributes: &storagemetadata.Attributes{
  2644  						MetricsType: storagemetadata.AggregatedMetricsType,
  2645  						Resolution:  res,
  2646  						Retention:   ret,
  2647  					},
  2648  				},
  2649  			},
  2650  		},
  2651  	})
  2652  
  2653  	// Test expected output
  2654  	testDownsamplerAggregation(t, testDownsampler)
  2655  }
  2656  
  2657  func TestDownsamplerAggregationWithTimedSamples(t *testing.T) {
  2658  	counterMetrics, counterMetricsExpect := testCounterMetrics(testCounterMetricsOptions{
  2659  		timedSamples: true,
  2660  	})
  2661  	gaugeMetrics, gaugeMetricsExpect := testGaugeMetrics(testGaugeMetricsOptions{
  2662  		timedSamples: true,
  2663  	})
  2664  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2665  		ingest: &testDownsamplerOptionsIngest{
  2666  			counterMetrics: counterMetrics,
  2667  			gaugeMetrics:   gaugeMetrics,
  2668  		},
  2669  		expect: &testDownsamplerOptionsExpect{
  2670  			writes: append(counterMetricsExpect, gaugeMetricsExpect...),
  2671  		},
  2672  		rulesConfig: &RulesConfiguration{
  2673  			MappingRules: []MappingRuleConfiguration{
  2674  				{
  2675  					Filter:       "__name__:*",
  2676  					Aggregations: []aggregation.Type{testAggregationType},
  2677  					StoragePolicies: []StoragePolicyConfiguration{
  2678  						{
  2679  							Resolution: 2 * time.Second,
  2680  							Retention:  24 * time.Hour,
  2681  						},
  2682  					},
  2683  				},
  2684  			},
  2685  		},
  2686  	})
  2687  
  2688  	// Test expected output
  2689  	testDownsamplerAggregation(t, testDownsampler)
  2690  }
  2691  
  2692  func TestDownsamplerAggregationWithOverrideRules(t *testing.T) {
  2693  	counterMetrics, counterMetricsExpect := testCounterMetrics(testCounterMetricsOptions{})
  2694  	counterMetricsExpect[0].values = []expectedValue{{value: 2}}
  2695  
  2696  	gaugeMetrics, gaugeMetricsExpect := testGaugeMetrics(testGaugeMetricsOptions{})
  2697  	gaugeMetricsExpect[0].values = []expectedValue{{value: 5}}
  2698  
  2699  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2700  		sampleAppenderOpts: &SampleAppenderOptions{
  2701  			Override: true,
  2702  			OverrideRules: SamplesAppenderOverrideRules{
  2703  				MappingRules: []AutoMappingRule{
  2704  					{
  2705  						Aggregations: []aggregation.Type{aggregation.Mean},
  2706  						Policies: []policy.StoragePolicy{
  2707  							policy.MustParseStoragePolicy("4s:1d"),
  2708  						},
  2709  					},
  2710  				},
  2711  			},
  2712  		},
  2713  		rulesConfig: &RulesConfiguration{
  2714  			MappingRules: []MappingRuleConfiguration{
  2715  				{
  2716  					Filter:       "__name__:*",
  2717  					Aggregations: []aggregation.Type{testAggregationType},
  2718  					StoragePolicies: []StoragePolicyConfiguration{
  2719  						{
  2720  							Resolution: 2 * time.Second,
  2721  							Retention:  24 * time.Hour,
  2722  						},
  2723  					},
  2724  				},
  2725  			},
  2726  		},
  2727  		ingest: &testDownsamplerOptionsIngest{
  2728  			counterMetrics: counterMetrics,
  2729  			gaugeMetrics:   gaugeMetrics,
  2730  		},
  2731  		expect: &testDownsamplerOptionsExpect{
  2732  			writes: append(counterMetricsExpect, gaugeMetricsExpect...),
  2733  		},
  2734  	})
  2735  
  2736  	// Test expected output
  2737  	testDownsamplerAggregation(t, testDownsampler)
  2738  }
  2739  
  2740  func TestDownsamplerAggregationWithRemoteAggregatorClient(t *testing.T) {
  2741  	ctrl := xtest.NewController(t)
  2742  	defer ctrl.Finish()
  2743  
  2744  	// Create mock client
  2745  	remoteClientMock := client.NewMockClient(ctrl)
  2746  	remoteClientMock.EXPECT().Init().Return(nil)
  2747  
  2748  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2749  		rulesConfig: &RulesConfiguration{
  2750  			MappingRules: []MappingRuleConfiguration{
  2751  				{
  2752  					Filter:       "__name__:*",
  2753  					Aggregations: []aggregation.Type{testAggregationType},
  2754  					StoragePolicies: []StoragePolicyConfiguration{
  2755  						{
  2756  							Resolution: 2 * time.Second,
  2757  							Retention:  24 * time.Hour,
  2758  						},
  2759  					},
  2760  				},
  2761  			},
  2762  		},
  2763  		remoteClientMock: remoteClientMock,
  2764  	})
  2765  
  2766  	// Test expected output
  2767  	testDownsamplerRemoteAggregation(t, testDownsampler)
  2768  }
  2769  
  2770  func TestDownsamplerWithOverrideNamespace(t *testing.T) {
  2771  	overrideNamespaceTag := "override_namespace_tag"
  2772  
  2773  	gaugeMetric := testGaugeMetric{
  2774  		tags: map[string]string{
  2775  			nameTag:         "http_requests",
  2776  			"app":           "nginx_edge",
  2777  			"status_code":   "500",
  2778  			"endpoint":      "/foo/bar",
  2779  			"not_rolled_up": "not_rolled_up_value",
  2780  			// Set namespace tags on ingested metrics.
  2781  			// The test demonstrates that overrideNamespaceTag is respected, meaning setting
  2782  			// values on defaultNamespaceTag won't affect aggregation.
  2783  			defaultNamespaceTag: "namespace_ignored",
  2784  		},
  2785  		timedSamples: []testGaugeMetricTimedSample{
  2786  			{value: 42},
  2787  			{value: 64, offset: 5 * time.Second},
  2788  		},
  2789  	}
  2790  	res := 5 * time.Second
  2791  	ret := 30 * 24 * time.Hour
  2792  	testDownsampler := newTestDownsampler(t, testDownsamplerOptions{
  2793  		rulesConfig: &RulesConfiguration{
  2794  			RollupRules: []RollupRuleConfiguration{
  2795  				{
  2796  					Filter: fmt.Sprintf(
  2797  						"%s:http_requests app:* status_code:* endpoint:*",
  2798  						nameTag),
  2799  					Transforms: []TransformConfiguration{
  2800  						{
  2801  							Transform: &TransformOperationConfiguration{
  2802  								Type: transformation.PerSecond,
  2803  							},
  2804  						},
  2805  						{
  2806  							Rollup: &RollupOperationConfiguration{
  2807  								MetricName:   "http_requests_by_status_code",
  2808  								GroupBy:      []string{"app", "status_code", "endpoint"},
  2809  								Aggregations: []aggregation.Type{aggregation.Sum},
  2810  							},
  2811  						},
  2812  					},
  2813  					StoragePolicies: []StoragePolicyConfiguration{
  2814  						{
  2815  							Resolution: res,
  2816  							Retention:  ret,
  2817  						},
  2818  					},
  2819  				},
  2820  			},
  2821  		},
  2822  		matcherConfig: MatcherConfiguration{NamespaceTag: overrideNamespaceTag},
  2823  		ingest: &testDownsamplerOptionsIngest{
  2824  			gaugeMetrics: []testGaugeMetric{gaugeMetric},
  2825  		},
  2826  		expect: &testDownsamplerOptionsExpect{
  2827  			writes: []testExpectedWrite{
  2828  				{
  2829  					tags: map[string]string{
  2830  						nameTag:               "http_requests_by_status_code",
  2831  						string(rollupTagName): string(rollupTagValue),
  2832  						"app":                 "nginx_edge",
  2833  						"status_code":         "500",
  2834  						"endpoint":            "/foo/bar",
  2835  					},
  2836  					values: []expectedValue{{value: 4.4}},
  2837  					attributes: &storagemetadata.Attributes{
  2838  						MetricsType: storagemetadata.AggregatedMetricsType,
  2839  						Resolution:  res,
  2840  						Retention:   ret,
  2841  					},
  2842  				},
  2843  			},
  2844  		},
  2845  	})
  2846  
  2847  	// Test expected output
  2848  	testDownsamplerAggregation(t, testDownsampler)
  2849  }
  2850  
  2851  func TestSafeguardInProcessDownsampler(t *testing.T) {
  2852  	ctrl := gomock.NewController(t)
  2853  	defer ctrl.Finish()
  2854  
  2855  	store := kv.NewMockStore(ctrl)
  2856  	store.EXPECT().SetIfNotExists(gomock.Eq(matcher.NewOptions().NamespacesKey()), gomock.Any()).Return(0, nil)
  2857  
  2858  	// explicitly asserting that no more mutations are done for original store.
  2859  	store.EXPECT().Set(gomock.Any(), gomock.Any()).Times(0)
  2860  	store.EXPECT().CheckAndSet(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
  2861  	store.EXPECT().Delete(gomock.Any()).Times(0)
  2862  
  2863  	_ = newTestDownsampler(t, testDownsamplerOptions{
  2864  		remoteClientMock: nil,
  2865  		kvStore:          store,
  2866  	})
  2867  }
  2868  
  2869  func TestDownsamplerNamespacesEtcdInit(t *testing.T) {
  2870  	t.Run("does not reset namespaces key", func(t *testing.T) {
  2871  		store := mem.NewStore()
  2872  		initialNamespaces := rulepb.Namespaces{Namespaces: []*rulepb.Namespace{{Name: "testNamespace"}}}
  2873  		_, err := store.Set(matcher.NewOptions().NamespacesKey(), &initialNamespaces)
  2874  		require.NoError(t, err)
  2875  
  2876  		_ = newTestDownsampler(t, testDownsamplerOptions{kvStore: store})
  2877  
  2878  		assert.Equal(t, initialNamespaces, readNamespacesKey(t, store), 1)
  2879  	})
  2880  
  2881  	t.Run("initializes namespace key", func(t *testing.T) {
  2882  		store := mem.NewStore()
  2883  
  2884  		_ = newTestDownsampler(t, testDownsamplerOptions{kvStore: store})
  2885  
  2886  		ns := readNamespacesKey(t, store)
  2887  		require.NotNil(t, ns)
  2888  		assert.Len(t, ns.Namespaces, 0)
  2889  	})
  2890  
  2891  	t.Run("does not initialize namespaces key when RequireNamespaceWatchOnInit is true", func(t *testing.T) {
  2892  		store := mem.NewStore()
  2893  
  2894  		matcherConfig := MatcherConfiguration{RequireNamespaceWatchOnInit: true}
  2895  		_ = newTestDownsampler(t, testDownsamplerOptions{kvStore: store, matcherConfig: matcherConfig})
  2896  
  2897  		_, err := store.Get(matcher.NewOptions().NamespacesKey())
  2898  		require.Error(t, err)
  2899  	})
  2900  }
  2901  
  2902  func originalStagedMetadata(t *testing.T, testDownsampler testDownsampler) []metricpb.StagedMetadatas {
  2903  	ds, ok := testDownsampler.downsampler.(*downsampler)
  2904  	require.True(t, ok)
  2905  
  2906  	origStagedMetadata := ds.metricsAppenderOpts.defaultStagedMetadatasProtos
  2907  	return origStagedMetadata
  2908  }
  2909  
  2910  func waitForStagedMetadataUpdate(t *testing.T, testDownsampler testDownsampler, origStagedMetadata []metricpb.StagedMetadatas) {
  2911  	ds, ok := testDownsampler.downsampler.(*downsampler)
  2912  	require.True(t, ok)
  2913  
  2914  	require.True(t, clock.WaitUntil(func() bool {
  2915  		ds.RLock()
  2916  		defer ds.RUnlock()
  2917  
  2918  		return !assert.ObjectsAreEqual(origStagedMetadata, ds.metricsAppenderOpts.defaultStagedMetadatasProtos)
  2919  	}, time.Second))
  2920  }
  2921  
  2922  func waitForEnabledUpdate(t *testing.T, testDownsampler *testDownsampler, current bool) {
  2923  	ds, ok := testDownsampler.downsampler.(*downsampler)
  2924  	require.True(t, ok)
  2925  
  2926  	require.True(t, clock.WaitUntil(func() bool {
  2927  		ds.RLock()
  2928  		defer ds.RUnlock()
  2929  
  2930  		return current != ds.enabled
  2931  	}, time.Second))
  2932  }
  2933  
  2934  type testExpectedWrite struct {
  2935  	tags              map[string]string
  2936  	values            []expectedValue // use values for multi expected values
  2937  	valueAllowedError float64         // use for allowing for slightly inexact values due to timing, etc
  2938  	attributes        *storagemetadata.Attributes
  2939  }
  2940  
  2941  type expectedValue struct {
  2942  	offset time.Duration
  2943  	value  float64
  2944  }
  2945  
  2946  type testCounterMetric struct {
  2947  	tags                    map[string]string
  2948  	samples                 []int64
  2949  	timedSamples            []testCounterMetricTimedSample
  2950  	expectDropPolicyApplied bool
  2951  	expectDropTimestamp     bool
  2952  }
  2953  
  2954  type testCounterMetricTimedSample struct {
  2955  	time   xtime.UnixNano
  2956  	offset time.Duration
  2957  	value  int64
  2958  }
  2959  
  2960  type testGaugeMetric struct {
  2961  	tags                    map[string]string
  2962  	samples                 []float64
  2963  	timedSamples            []testGaugeMetricTimedSample
  2964  	expectDropPolicyApplied bool
  2965  	expectDropTimestamp     bool
  2966  }
  2967  
  2968  type testGaugeMetricTimedSample struct {
  2969  	time   xtime.UnixNano
  2970  	offset time.Duration
  2971  	value  float64
  2972  }
  2973  
  2974  type testTimerMetric struct {
  2975  	tags                    map[string]string
  2976  	samples                 []float64
  2977  	timedSamples            []testTimerMetricTimedSample
  2978  	expectDropPolicyApplied bool
  2979  	expectDropTimestamp     bool
  2980  }
  2981  
  2982  type testTimerMetricTimedSample struct {
  2983  	time   xtime.UnixNano
  2984  	offset time.Duration
  2985  	value  float64
  2986  }
  2987  
  2988  type testCounterMetricsOptions struct {
  2989  	timedSamples bool
  2990  }
  2991  
  2992  func testCounterMetrics(opts testCounterMetricsOptions) (
  2993  	[]testCounterMetric,
  2994  	[]testExpectedWrite,
  2995  ) {
  2996  	metric := testCounterMetric{
  2997  		tags:    map[string]string{nameTag: "counter0", "app": "testapp", "foo": "bar"},
  2998  		samples: []int64{1, 2, 3},
  2999  	}
  3000  	if opts.timedSamples {
  3001  		metric.samples = nil
  3002  		metric.timedSamples = []testCounterMetricTimedSample{
  3003  			{value: 1}, {value: 2}, {value: 3},
  3004  		}
  3005  	}
  3006  	write := testExpectedWrite{
  3007  		tags:   metric.tags,
  3008  		values: []expectedValue{{value: 6}},
  3009  	}
  3010  	return []testCounterMetric{metric}, []testExpectedWrite{write}
  3011  }
  3012  
  3013  type testGaugeMetricsOptions struct {
  3014  	timedSamples bool
  3015  }
  3016  
  3017  func testGaugeMetrics(opts testGaugeMetricsOptions) ([]testGaugeMetric, []testExpectedWrite) {
  3018  	metric := testGaugeMetric{
  3019  		tags:    map[string]string{nameTag: "gauge0", "app": "testapp", "qux": "qaz"},
  3020  		samples: []float64{4, 5, 6},
  3021  	}
  3022  	if opts.timedSamples {
  3023  		metric.samples = nil
  3024  		metric.timedSamples = []testGaugeMetricTimedSample{
  3025  			{value: 4},
  3026  			{value: 5},
  3027  			{value: 6, offset: 1 * time.Nanosecond},
  3028  		}
  3029  	}
  3030  	write := testExpectedWrite{
  3031  		tags:   metric.tags,
  3032  		values: []expectedValue{{value: 15}},
  3033  	}
  3034  	return []testGaugeMetric{metric}, []testExpectedWrite{write}
  3035  }
  3036  
  3037  func testDownsamplerAggregation(
  3038  	t *testing.T,
  3039  	testDownsampler testDownsampler,
  3040  ) {
  3041  	testOpts := testDownsampler.testOpts
  3042  
  3043  	logger := testDownsampler.instrumentOpts.Logger().
  3044  		With(zap.String("test", t.Name()))
  3045  
  3046  	counterMetrics, counterMetricsExpect := testCounterMetrics(testCounterMetricsOptions{})
  3047  	gaugeMetrics, gaugeMetricsExpect := testGaugeMetrics(testGaugeMetricsOptions{})
  3048  	expectedWrites := append(counterMetricsExpect, gaugeMetricsExpect...)
  3049  
  3050  	// Allow overrides
  3051  	var (
  3052  		allowFilter  *testDownsamplerOptionsExpectAllowFilter
  3053  		timerMetrics []testTimerMetric
  3054  	)
  3055  	if ingest := testOpts.ingest; ingest != nil {
  3056  		counterMetrics = ingest.counterMetrics
  3057  		gaugeMetrics = ingest.gaugeMetrics
  3058  		timerMetrics = ingest.timerMetrics
  3059  	}
  3060  	if expect := testOpts.expect; expect != nil {
  3061  		expectedWrites = expect.writes
  3062  		allowFilter = expect.allowFilter
  3063  	}
  3064  
  3065  	// Ingest points
  3066  	testDownsamplerAggregationIngest(t, testDownsampler,
  3067  		counterMetrics, gaugeMetrics, timerMetrics)
  3068  
  3069  	// Wait for writes
  3070  	logger.Info("wait for test metrics to appear")
  3071  	logWritesAccumulated := os.Getenv("TEST_LOG_WRITES_ACCUMULATED") == "true"
  3072  	logWritesAccumulatedTicker := time.NewTicker(time.Second)
  3073  
  3074  	logWritesMatch := os.Getenv("TEST_LOG_WRITES_MATCH") == "true"
  3075  	logWritesMatchTicker := time.NewTicker(time.Second)
  3076  
  3077  	identTag := nameTag
  3078  	if len(testDownsampler.testOpts.identTag) > 0 {
  3079  		identTag = testDownsampler.testOpts.identTag
  3080  	}
  3081  
  3082  CheckAllWritesArrivedLoop:
  3083  	for {
  3084  		allWrites := testDownsampler.storage.Writes()
  3085  		if logWritesAccumulated {
  3086  			select {
  3087  			case <-logWritesAccumulatedTicker.C:
  3088  				logger.Info("logging accmulated writes",
  3089  					zap.Int("numAllWrites", len(allWrites)))
  3090  				for _, write := range allWrites {
  3091  					logger.Info("accumulated write",
  3092  						zap.ByteString("tags", write.Tags().ID()),
  3093  						zap.Any("datapoints", write.Datapoints()),
  3094  						zap.Any("attributes", write.Attributes()))
  3095  				}
  3096  			default:
  3097  			}
  3098  		}
  3099  
  3100  		for _, expectedWrite := range expectedWrites {
  3101  			name := expectedWrite.tags[identTag]
  3102  			attrs := expectedWrite.attributes
  3103  			writesForNameAndAttrs, _ := findWrites(allWrites, name, identTag, attrs)
  3104  			if len(writesForNameAndAttrs) != len(expectedWrite.values) {
  3105  				if logWritesMatch {
  3106  					select {
  3107  					case <-logWritesMatchTicker.C:
  3108  						logger.Info("continuing wait for accumulated writes",
  3109  							zap.String("name", name),
  3110  							zap.Any("attributes", attrs),
  3111  							zap.Int("numWritesForNameAndAttrs", len(writesForNameAndAttrs)),
  3112  							zap.Int("numExpectedWriteValues", len(expectedWrite.values)),
  3113  						)
  3114  					default:
  3115  					}
  3116  				}
  3117  
  3118  				time.Sleep(100 * time.Millisecond)
  3119  				continue CheckAllWritesArrivedLoop
  3120  			}
  3121  		}
  3122  		break
  3123  	}
  3124  
  3125  	// Verify writes
  3126  	logger.Info("verify test metrics")
  3127  	allWrites := testDownsampler.storage.Writes()
  3128  	if logWritesAccumulated {
  3129  		logger.Info("logging accmulated writes to verify",
  3130  			zap.Int("numAllWrites", len(allWrites)))
  3131  		for _, write := range allWrites {
  3132  			logger.Info("accumulated write",
  3133  				zap.ByteString("tags", write.Tags().ID()),
  3134  				zap.Any("datapoints", write.Datapoints()))
  3135  		}
  3136  	}
  3137  
  3138  	for _, expectedWrite := range expectedWrites {
  3139  		name := expectedWrite.tags[identTag]
  3140  		expectedValues := expectedWrite.values
  3141  		allowedError := expectedWrite.valueAllowedError
  3142  
  3143  		writesForNameAndAttrs, found := findWrites(allWrites, name, identTag, expectedWrite.attributes)
  3144  		require.True(t, found)
  3145  		require.Equal(t, len(expectedValues), len(writesForNameAndAttrs))
  3146  		for i, expectedValue := range expectedValues {
  3147  			write := writesForNameAndAttrs[i]
  3148  
  3149  			assert.Equal(t, expectedWrite.tags, tagsToStringMap(write.Tags()))
  3150  
  3151  			require.Equal(t, 1, len(write.Datapoints()))
  3152  
  3153  			actualValue := write.Datapoints()[0].Value
  3154  			if allowedError == 0 {
  3155  				// Exact match value.
  3156  				assert.Equal(t, expectedValue.value, actualValue)
  3157  			} else {
  3158  				// Fuzzy match value.
  3159  				lower := expectedValue.value - allowedError
  3160  				upper := expectedValue.value + allowedError
  3161  				withinBounds := (lower <= actualValue) && (actualValue <= upper)
  3162  				msg := fmt.Sprintf("expected within: lower=%f, upper=%f, actual=%f",
  3163  					lower, upper, actualValue)
  3164  				assert.True(t, withinBounds, msg)
  3165  			}
  3166  
  3167  			if expectedOffset := expectedValue.offset; expectedOffset > 0 {
  3168  				// Check if distance between datapoints as expected (use
  3169  				// absolute offset from first write).
  3170  				firstTimestamp := writesForNameAndAttrs[0].Datapoints()[0].Timestamp
  3171  				actualOffset := write.Datapoints()[0].Timestamp.Sub(firstTimestamp)
  3172  				assert.Equal(t, expectedOffset, actualOffset)
  3173  			}
  3174  
  3175  			if attrs := expectedWrite.attributes; attrs != nil {
  3176  				assert.Equal(t, *attrs, write.Attributes())
  3177  			}
  3178  		}
  3179  	}
  3180  
  3181  	if allowFilter == nil {
  3182  		return // No allow filter checking required.
  3183  	}
  3184  
  3185  	for _, write := range testDownsampler.storage.Writes() {
  3186  		attrs := write.Attributes()
  3187  		foundMatchingAttribute := false
  3188  		for _, allowed := range allowFilter.attributes {
  3189  			if allowed == attrs {
  3190  				foundMatchingAttribute = true
  3191  				break
  3192  			}
  3193  		}
  3194  		assert.True(t, foundMatchingAttribute,
  3195  			fmt.Sprintf("attribute not allowed: allowed=%v, actual=%v",
  3196  				allowFilter.attributes, attrs))
  3197  	}
  3198  }
  3199  
  3200  func testDownsamplerRemoteAggregation(
  3201  	t *testing.T,
  3202  	testDownsampler testDownsampler,
  3203  ) {
  3204  	testOpts := testDownsampler.testOpts
  3205  
  3206  	expectTestCounterMetrics, _ := testCounterMetrics(testCounterMetricsOptions{})
  3207  	testCounterMetrics, _ := testCounterMetrics(testCounterMetricsOptions{})
  3208  
  3209  	expectTestGaugeMetrics, _ := testGaugeMetrics(testGaugeMetricsOptions{})
  3210  	testGaugeMetrics, _ := testGaugeMetrics(testGaugeMetricsOptions{})
  3211  
  3212  	remoteClientMock := testOpts.remoteClientMock
  3213  	require.NotNil(t, remoteClientMock)
  3214  
  3215  	// Expect ingestion
  3216  	checkedCounterSamples := 0
  3217  	remoteClientMock.EXPECT().
  3218  		WriteUntimedCounter(gomock.Any(), gomock.Any()).
  3219  		AnyTimes().
  3220  		Do(func(counter unaggregated.Counter,
  3221  			metadatas metadata.StagedMetadatas,
  3222  		) error {
  3223  			for _, c := range expectTestCounterMetrics {
  3224  				if !strings.Contains(counter.ID.String(), c.tags[nameTag]) {
  3225  					continue
  3226  				}
  3227  
  3228  				var remainingSamples []int64
  3229  				found := false
  3230  				for _, s := range c.samples {
  3231  					if !found && s == counter.Value {
  3232  						found = true
  3233  					} else {
  3234  						remainingSamples = append(remainingSamples, s)
  3235  					}
  3236  				}
  3237  				c.samples = remainingSamples
  3238  				if found {
  3239  					checkedCounterSamples++
  3240  				}
  3241  
  3242  				break
  3243  			}
  3244  
  3245  			return nil
  3246  		})
  3247  
  3248  	checkedGaugeSamples := 0
  3249  	remoteClientMock.EXPECT().
  3250  		WriteUntimedGauge(gomock.Any(), gomock.Any()).
  3251  		AnyTimes().
  3252  		Do(func(gauge unaggregated.Gauge,
  3253  			metadatas metadata.StagedMetadatas,
  3254  		) error {
  3255  			for _, g := range expectTestGaugeMetrics {
  3256  				if !strings.Contains(gauge.ID.String(), g.tags[nameTag]) {
  3257  					continue
  3258  				}
  3259  
  3260  				var remainingSamples []float64
  3261  				found := false
  3262  				for _, s := range g.samples {
  3263  					if !found && s == gauge.Value {
  3264  						found = true
  3265  					} else {
  3266  						remainingSamples = append(remainingSamples, s)
  3267  					}
  3268  				}
  3269  				g.samples = remainingSamples
  3270  				if found {
  3271  					checkedGaugeSamples++
  3272  				}
  3273  
  3274  				break
  3275  			}
  3276  
  3277  			return nil
  3278  		})
  3279  
  3280  	// Ingest points
  3281  	testDownsamplerAggregationIngest(t, testDownsampler,
  3282  		testCounterMetrics, testGaugeMetrics, []testTimerMetric{})
  3283  
  3284  	// Ensure we checked counters and gauges
  3285  	samplesCounters := 0
  3286  	for _, c := range testCounterMetrics {
  3287  		samplesCounters += len(c.samples)
  3288  	}
  3289  	samplesGauges := 0
  3290  	for _, c := range testGaugeMetrics {
  3291  		samplesGauges += len(c.samples)
  3292  	}
  3293  	require.Equal(t, samplesCounters, checkedCounterSamples)
  3294  	require.Equal(t, samplesGauges, checkedGaugeSamples)
  3295  }
  3296  
  3297  func testDownsamplerAggregationIngest(
  3298  	t *testing.T,
  3299  	testDownsampler testDownsampler,
  3300  	testCounterMetrics []testCounterMetric,
  3301  	testGaugeMetrics []testGaugeMetric,
  3302  	testTimerMetrics []testTimerMetric,
  3303  ) {
  3304  	downsampler := testDownsampler.downsampler
  3305  
  3306  	testOpts := testDownsampler.testOpts
  3307  
  3308  	logger := testDownsampler.instrumentOpts.Logger().
  3309  		With(zap.String("test", t.Name()))
  3310  
  3311  	logger.Info("write test metrics")
  3312  	appender, err := downsampler.NewMetricsAppender()
  3313  	require.NoError(t, err)
  3314  	defer appender.Finalize()
  3315  
  3316  	var opts SampleAppenderOptions
  3317  	if testOpts.sampleAppenderOpts != nil {
  3318  		opts = *testOpts.sampleAppenderOpts
  3319  	}
  3320  	// make the current timestamp predictable:
  3321  	now := time.Now().Truncate(time.Microsecond)
  3322  	xNow := xtime.ToUnixNano(now)
  3323  	for _, metric := range testCounterMetrics {
  3324  		appender.NextMetric()
  3325  
  3326  		for name, value := range metric.tags {
  3327  			appender.AddTag([]byte(name), []byte(value))
  3328  		}
  3329  
  3330  		samplesAppenderResult, err := appender.SamplesAppender(opts)
  3331  		require.NoError(t, err)
  3332  		require.Equal(t, metric.expectDropPolicyApplied,
  3333  			samplesAppenderResult.IsDropPolicyApplied)
  3334  		require.Equal(t, metric.expectDropTimestamp,
  3335  			samplesAppenderResult.ShouldDropTimestamp)
  3336  
  3337  		samplesAppender := samplesAppenderResult.SamplesAppender
  3338  		for _, sample := range metric.samples {
  3339  			err = samplesAppender.AppendUntimedCounterSample(xtime.Now(), sample, nil)
  3340  			require.NoError(t, err)
  3341  		}
  3342  		for _, sample := range metric.timedSamples {
  3343  			if sample.time.IsZero() {
  3344  				sample.time = xNow // Allow empty time to mean "now"
  3345  			}
  3346  			if sample.offset > 0 {
  3347  				sample.time = sample.time.Add(sample.offset)
  3348  			}
  3349  			if testOpts.waitForOffset {
  3350  				time.Sleep(sample.offset)
  3351  			}
  3352  			if samplesAppenderResult.ShouldDropTimestamp {
  3353  				err = samplesAppender.AppendUntimedCounterSample(sample.time, sample.value, nil)
  3354  			} else {
  3355  				err = samplesAppender.AppendCounterSample(sample.time, sample.value, nil)
  3356  			}
  3357  			require.NoError(t, err)
  3358  		}
  3359  	}
  3360  	for _, metric := range testGaugeMetrics {
  3361  		appender.NextMetric()
  3362  
  3363  		for name, value := range metric.tags {
  3364  			appender.AddTag([]byte(name), []byte(value))
  3365  		}
  3366  
  3367  		samplesAppenderResult, err := appender.SamplesAppender(opts)
  3368  		require.NoError(t, err)
  3369  		require.Equal(t, metric.expectDropPolicyApplied,
  3370  			samplesAppenderResult.IsDropPolicyApplied)
  3371  		require.Equal(t, metric.expectDropTimestamp,
  3372  			samplesAppenderResult.ShouldDropTimestamp)
  3373  
  3374  		samplesAppender := samplesAppenderResult.SamplesAppender
  3375  		for _, sample := range metric.samples {
  3376  			err = samplesAppender.AppendUntimedGaugeSample(xtime.Now(), sample, nil)
  3377  			require.NoError(t, err)
  3378  		}
  3379  		for _, sample := range metric.timedSamples {
  3380  			if sample.time.IsZero() {
  3381  				sample.time = xNow // Allow empty time to mean "now"
  3382  			}
  3383  			if sample.offset > 0 {
  3384  				sample.time = sample.time.Add(sample.offset)
  3385  			}
  3386  			if testOpts.waitForOffset {
  3387  				time.Sleep(sample.offset)
  3388  			}
  3389  			if samplesAppenderResult.ShouldDropTimestamp {
  3390  				err = samplesAppender.AppendUntimedGaugeSample(sample.time, sample.value, nil)
  3391  			} else {
  3392  				err = samplesAppender.AppendGaugeSample(sample.time, sample.value, nil)
  3393  			}
  3394  			require.NoError(t, err)
  3395  		}
  3396  	}
  3397  
  3398  	//nolint:dupl
  3399  	for _, metric := range testTimerMetrics {
  3400  		appender.NextMetric()
  3401  
  3402  		for name, value := range metric.tags {
  3403  			appender.AddTag([]byte(name), []byte(value))
  3404  		}
  3405  
  3406  		samplesAppenderResult, err := appender.SamplesAppender(opts)
  3407  		require.NoError(t, err)
  3408  		require.Equal(t, metric.expectDropPolicyApplied,
  3409  			samplesAppenderResult.IsDropPolicyApplied)
  3410  		require.Equal(t, metric.expectDropTimestamp,
  3411  			samplesAppenderResult.ShouldDropTimestamp)
  3412  
  3413  		samplesAppender := samplesAppenderResult.SamplesAppender
  3414  		for _, sample := range metric.samples {
  3415  			err = samplesAppender.AppendUntimedTimerSample(xtime.Now(), sample, nil)
  3416  			require.NoError(t, err)
  3417  		}
  3418  		for _, sample := range metric.timedSamples {
  3419  			if sample.time.IsZero() {
  3420  				sample.time = xtime.ToUnixNano(now) // Allow empty time to mean "now"
  3421  			}
  3422  			if sample.offset > 0 {
  3423  				sample.time = sample.time.Add(sample.offset)
  3424  			}
  3425  			if testOpts.waitForOffset {
  3426  				time.Sleep(sample.offset)
  3427  			}
  3428  			if samplesAppenderResult.ShouldDropTimestamp {
  3429  				err = samplesAppender.AppendUntimedTimerSample(sample.time, sample.value, nil)
  3430  			} else {
  3431  				err = samplesAppender.AppendTimerSample(sample.time, sample.value, nil)
  3432  			}
  3433  			require.NoError(t, err)
  3434  		}
  3435  	}
  3436  }
  3437  
  3438  func setAggregatedNamespaces(
  3439  	t *testing.T,
  3440  	testDownsampler testDownsampler,
  3441  	session dbclient.Session,
  3442  	namespaces ...m3.AggregatedClusterNamespaceDefinition,
  3443  ) {
  3444  	clusters, err := m3.NewClusters(m3.UnaggregatedClusterNamespaceDefinition{
  3445  		NamespaceID: ident.StringID("default"),
  3446  		Retention:   48 * time.Hour,
  3447  		Session:     session,
  3448  	}, namespaces...)
  3449  	require.NoError(t, err)
  3450  	require.NoError(t, testDownsampler.opts.ClusterNamespacesWatcher.Update(clusters.ClusterNamespaces()))
  3451  }
  3452  
  3453  func tagsToStringMap(tags models.Tags) map[string]string {
  3454  	stringMap := make(map[string]string, tags.Len())
  3455  	for _, t := range tags.Tags {
  3456  		stringMap[string(t.Name)] = string(t.Value)
  3457  	}
  3458  
  3459  	return stringMap
  3460  }
  3461  
  3462  type testDownsampler struct {
  3463  	opts           DownsamplerOptions
  3464  	testOpts       testDownsamplerOptions
  3465  	downsampler    Downsampler
  3466  	storage        mock.Storage
  3467  	rulesStore     rules.Store
  3468  	instrumentOpts instrument.Options
  3469  }
  3470  
  3471  type testDownsamplerOptions struct {
  3472  	clockOpts      clock.Options
  3473  	instrumentOpts instrument.Options
  3474  	identTag       string
  3475  	untimedRollups bool
  3476  	waitForOffset  bool
  3477  
  3478  	// Options for the test
  3479  	autoMappingRules   []m3.ClusterNamespaceOptions
  3480  	sampleAppenderOpts *SampleAppenderOptions
  3481  	remoteClientMock   *client.MockClient
  3482  	rulesConfig        *RulesConfiguration
  3483  	matcherConfig      MatcherConfiguration
  3484  
  3485  	// Test ingest and expectations overrides
  3486  	ingest *testDownsamplerOptionsIngest
  3487  	expect *testDownsamplerOptionsExpect
  3488  
  3489  	kvStore kv.Store
  3490  }
  3491  
  3492  type testDownsamplerOptionsIngest struct {
  3493  	counterMetrics []testCounterMetric
  3494  	gaugeMetrics   []testGaugeMetric
  3495  	timerMetrics   []testTimerMetric
  3496  }
  3497  
  3498  type testDownsamplerOptionsExpect struct {
  3499  	writes      []testExpectedWrite
  3500  	allowFilter *testDownsamplerOptionsExpectAllowFilter
  3501  }
  3502  
  3503  type testDownsamplerOptionsExpectAllowFilter struct {
  3504  	attributes []storagemetadata.Attributes
  3505  }
  3506  
  3507  func newTestDownsampler(t *testing.T, opts testDownsamplerOptions) testDownsampler {
  3508  	if opts.expect == nil {
  3509  		opts.expect = &testDownsamplerOptionsExpect{}
  3510  	}
  3511  	storage := mock.NewMockStorage()
  3512  	rulesKVStore := mem.NewStore()
  3513  
  3514  	clockOpts := clock.NewOptions()
  3515  	if opts.clockOpts != nil {
  3516  		clockOpts = opts.clockOpts
  3517  	}
  3518  
  3519  	// Use a test instrument options by default to get the debug logs on by default.
  3520  	instrumentOpts := instrument.NewTestOptions(t)
  3521  	if opts.instrumentOpts != nil {
  3522  		instrumentOpts = opts.instrumentOpts
  3523  	}
  3524  
  3525  	matcherOpts := matcher.NewOptions()
  3526  
  3527  	// Initialize the namespaces
  3528  	_, err := rulesKVStore.Set(matcherOpts.NamespacesKey(), &rulepb.Namespaces{})
  3529  	require.NoError(t, err)
  3530  
  3531  	rulesetKeyFmt := matcherOpts.RuleSetKeyFn()([]byte("%s"))
  3532  	rulesStoreOpts := ruleskv.NewStoreOptions(matcherOpts.NamespacesKey(),
  3533  		rulesetKeyFmt, nil)
  3534  	rulesStore := ruleskv.NewStore(rulesKVStore, rulesStoreOpts)
  3535  
  3536  	tagEncoderOptions := serialize.NewTagEncoderOptions()
  3537  	tagDecoderOptions := serialize.NewTagDecoderOptions(serialize.TagDecoderOptionsConfig{})
  3538  	tagEncoderPoolOptions := pool.NewObjectPoolOptions().
  3539  		SetSize(2).
  3540  		SetInstrumentOptions(instrumentOpts.
  3541  			SetMetricsScope(instrumentOpts.MetricsScope().
  3542  				SubScope("tag-encoder-pool")))
  3543  	tagDecoderPoolOptions := pool.NewObjectPoolOptions().
  3544  		SetSize(2).
  3545  		SetInstrumentOptions(instrumentOpts.
  3546  			SetMetricsScope(instrumentOpts.MetricsScope().
  3547  				SubScope("tag-decoder-pool")))
  3548  	metricsAppenderPoolOptions := pool.NewObjectPoolOptions().
  3549  		SetSize(2).
  3550  		SetInstrumentOptions(instrumentOpts.
  3551  			SetMetricsScope(instrumentOpts.MetricsScope().
  3552  				SubScope("metrics-appender-pool")))
  3553  
  3554  	cfg := Configuration{
  3555  		BufferPastLimits: []BufferPastLimitConfiguration{
  3556  			{Resolution: 0, BufferPast: 500 * time.Millisecond},
  3557  		},
  3558  	}
  3559  	if opts.remoteClientMock != nil {
  3560  		// Optionally set an override to use remote aggregation
  3561  		// with a mock client
  3562  		cfg.RemoteAggregator = &RemoteAggregatorConfiguration{
  3563  			clientOverride: opts.remoteClientMock,
  3564  		}
  3565  	}
  3566  	if opts.rulesConfig != nil {
  3567  		cfg.Rules = opts.rulesConfig
  3568  	}
  3569  	cfg.Matcher = opts.matcherConfig
  3570  	cfg.UntimedRollups = opts.untimedRollups
  3571  
  3572  	clusterClient := clusterclient.NewMockClient(gomock.NewController(t))
  3573  	kvStore := opts.kvStore
  3574  	if kvStore == nil {
  3575  		kvStore = mem.NewStore()
  3576  	}
  3577  	clusterClient.EXPECT().KV().Return(kvStore, nil).AnyTimes()
  3578  	instance, err := cfg.NewDownsampler(DownsamplerOptions{
  3579  		Storage:                    storage,
  3580  		ClusterClient:              clusterClient,
  3581  		RulesKVStore:               rulesKVStore,
  3582  		ClusterNamespacesWatcher:   m3.NewClusterNamespacesWatcher(),
  3583  		ClockOptions:               clockOpts,
  3584  		InstrumentOptions:          instrumentOpts,
  3585  		TagEncoderOptions:          tagEncoderOptions,
  3586  		TagDecoderOptions:          tagDecoderOptions,
  3587  		TagEncoderPoolOptions:      tagEncoderPoolOptions,
  3588  		TagDecoderPoolOptions:      tagDecoderPoolOptions,
  3589  		MetricsAppenderPoolOptions: metricsAppenderPoolOptions,
  3590  		RWOptions:                  xio.NewOptions(),
  3591  		TagOptions:                 models.NewTagOptions(),
  3592  	})
  3593  	require.NoError(t, err)
  3594  
  3595  	if len(opts.autoMappingRules) > 0 {
  3596  		// Simulate the automapping rules being injected into the downsampler.
  3597  		ctrl := gomock.NewController(t)
  3598  
  3599  		var mockNamespaces m3.ClusterNamespaces
  3600  		for _, r := range opts.autoMappingRules {
  3601  			n := m3.NewMockClusterNamespace(ctrl)
  3602  			n.EXPECT().
  3603  				Options().
  3604  				Return(r).
  3605  				AnyTimes()
  3606  			mockNamespaces = append(mockNamespaces, n)
  3607  		}
  3608  
  3609  		instance.(*downsampler).OnUpdate(mockNamespaces)
  3610  	}
  3611  
  3612  	downcast, ok := instance.(*downsampler)
  3613  	require.True(t, ok)
  3614  
  3615  	return testDownsampler{
  3616  		opts:           downcast.opts,
  3617  		testOpts:       opts,
  3618  		downsampler:    instance,
  3619  		storage:        storage,
  3620  		rulesStore:     rulesStore,
  3621  		instrumentOpts: instrumentOpts,
  3622  	}
  3623  }
  3624  
  3625  func newTestID(t *testing.T, tags map[string]string) id.ID {
  3626  	tagEncoderPool := serialize.NewTagEncoderPool(serialize.NewTagEncoderOptions(),
  3627  		pool.NewObjectPoolOptions().SetSize(1))
  3628  	tagEncoderPool.Init()
  3629  
  3630  	tagsIter := newTags()
  3631  	for name, value := range tags {
  3632  		tagsIter.append([]byte(name), []byte(value))
  3633  	}
  3634  
  3635  	tagEncoder := tagEncoderPool.Get()
  3636  	err := tagEncoder.Encode(tagsIter)
  3637  	require.NoError(t, err)
  3638  
  3639  	data, ok := tagEncoder.Data()
  3640  	require.True(t, ok)
  3641  
  3642  	size := 1
  3643  	tagDecoderPool := serialize.NewTagDecoderPool(
  3644  		serialize.NewTagDecoderOptions(serialize.TagDecoderOptionsConfig{
  3645  			CheckBytesWrapperPoolSize: &size,
  3646  		}),
  3647  		pool.NewObjectPoolOptions().SetSize(size))
  3648  	tagDecoderPool.Init()
  3649  
  3650  	tagDecoder := tagDecoderPool.Get()
  3651  
  3652  	iter := serialize.NewMetricTagsIterator(tagDecoder, nil)
  3653  	iter.Reset(data.Bytes())
  3654  	return iter
  3655  }
  3656  
  3657  func findWrites(
  3658  	writes []*storage.WriteQuery,
  3659  	name, identTag string,
  3660  	optionalMatchAttrs *storagemetadata.Attributes,
  3661  ) ([]*storage.WriteQuery, bool) {
  3662  	var results []*storage.WriteQuery
  3663  	for _, w := range writes {
  3664  		if t, ok := w.Tags().Get([]byte(identTag)); ok {
  3665  			if !bytes.Equal(t, []byte(name)) {
  3666  				// Does not match name.
  3667  				continue
  3668  			}
  3669  			if optionalMatchAttrs != nil && w.Attributes() != *optionalMatchAttrs {
  3670  				// Tried to match attributes and not matched.
  3671  				continue
  3672  			}
  3673  
  3674  			// Matches name and all optional lookups.
  3675  			results = append(results, w)
  3676  		}
  3677  	}
  3678  	return results, len(results) > 0
  3679  }
  3680  
  3681  func testUpdateMetadata() rules.UpdateMetadata {
  3682  	return rules.NewRuleSetUpdateHelper(0).NewUpdateMetadata(time.Now().UnixNano(), "test")
  3683  }
  3684  
  3685  func readNamespacesKey(t *testing.T, store kv.Store) rulepb.Namespaces {
  3686  	v, err := store.Get(matcher.NewOptions().NamespacesKey())
  3687  	require.NoError(t, err)
  3688  	var ns rulepb.Namespaces
  3689  	err = v.Unmarshal(&ns)
  3690  	require.NoError(t, err)
  3691  	require.NotNil(t, ns)
  3692  	return ns
  3693  }