github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cmd/services/m3coordinator/downsample/options.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  package downsample
    22  
    23  import (
    24  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"runtime"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/aggregator/aggregator"
    31  	"github.com/m3db/m3/src/aggregator/aggregator/handler"
    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  	"github.com/m3db/m3/src/cluster/placement"
    37  	placementservice "github.com/m3db/m3/src/cluster/placement/service"
    38  	placementstorage "github.com/m3db/m3/src/cluster/placement/storage"
    39  	"github.com/m3db/m3/src/cluster/services"
    40  	"github.com/m3db/m3/src/metrics/aggregation"
    41  	"github.com/m3db/m3/src/metrics/filters"
    42  	"github.com/m3db/m3/src/metrics/generated/proto/aggregationpb"
    43  	"github.com/m3db/m3/src/metrics/generated/proto/pipelinepb"
    44  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    45  	"github.com/m3db/m3/src/metrics/generated/proto/transformationpb"
    46  	"github.com/m3db/m3/src/metrics/matcher"
    47  	"github.com/m3db/m3/src/metrics/matcher/cache"
    48  	"github.com/m3db/m3/src/metrics/matcher/namespace"
    49  	"github.com/m3db/m3/src/metrics/metadata"
    50  	"github.com/m3db/m3/src/metrics/metric"
    51  	"github.com/m3db/m3/src/metrics/metric/aggregated"
    52  	"github.com/m3db/m3/src/metrics/metric/id"
    53  	"github.com/m3db/m3/src/metrics/metric/unaggregated"
    54  	"github.com/m3db/m3/src/metrics/pipeline"
    55  	"github.com/m3db/m3/src/metrics/policy"
    56  	"github.com/m3db/m3/src/metrics/rules"
    57  	ruleskv "github.com/m3db/m3/src/metrics/rules/store/kv"
    58  	"github.com/m3db/m3/src/metrics/rules/view"
    59  	"github.com/m3db/m3/src/metrics/transformation"
    60  	"github.com/m3db/m3/src/query/models"
    61  	"github.com/m3db/m3/src/query/storage"
    62  	"github.com/m3db/m3/src/query/storage/m3"
    63  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    64  	"github.com/m3db/m3/src/x/clock"
    65  	"github.com/m3db/m3/src/x/ident"
    66  	"github.com/m3db/m3/src/x/instrument"
    67  	xio "github.com/m3db/m3/src/x/io"
    68  	"github.com/m3db/m3/src/x/pool"
    69  	"github.com/m3db/m3/src/x/serialize"
    70  	xsync "github.com/m3db/m3/src/x/sync"
    71  	xtime "github.com/m3db/m3/src/x/time"
    72  
    73  	"github.com/pborman/uuid"
    74  	"github.com/prometheus/common/model"
    75  )
    76  
    77  const (
    78  	instanceID                     = "downsampler_local"
    79  	placementKVKey                 = "/placement"
    80  	defaultConfigInMemoryNamespace = "default"
    81  	replicationFactor              = 1
    82  	defaultStorageFlushConcurrency = 20000
    83  	defaultOpenTimeout             = 10 * time.Second
    84  	defaultBufferFutureTimedMetric = time.Minute
    85  	defaultVerboseErrors           = true
    86  	// defaultMatcherCacheCapacity sets the default matcher cache
    87  	// capacity to zero so that the cache is turned off.
    88  	// This is due to discovering that there is a lot of contention
    89  	// used by the cache and the fact that most coordinators are used
    90  	// in a stateless manner with a central deployment which in turn
    91  	// leads to an extremely low cache hit ratio anyway.
    92  	defaultMatcherCacheCapacity = 0
    93  )
    94  
    95  var (
    96  	defaultMetricNameTagName    = []byte(model.MetricNameLabel)
    97  	numShards                   = runtime.GOMAXPROCS(0)
    98  	defaultNamespaceTag         = metric.M3MetricsPrefixString + "_namespace__"
    99  	defaultFilterOutTagPrefixes = [][]byte{
   100  		metric.M3MetricsPrefix,
   101  	}
   102  
   103  	errNoStorage                    = errors.New("downsampling enabled with storage not set")
   104  	errNoClusterClient              = errors.New("downsampling enabled with cluster client not set")
   105  	errNoRulesStore                 = errors.New("downsampling enabled with rules store not set")
   106  	errNoClockOptions               = errors.New("downsampling enabled with clock options not set")
   107  	errNoInstrumentOptions          = errors.New("downsampling enabled with instrument options not set")
   108  	errNoTagEncoderOptions          = errors.New("downsampling enabled with tag encoder options not set")
   109  	errNoTagDecoderOptions          = errors.New("downsampling enabled with tag decoder options not set")
   110  	errNoTagEncoderPoolOptions      = errors.New("downsampling enabled with tag encoder pool options not set")
   111  	errNoTagDecoderPoolOptions      = errors.New("downsampling enabled with tag decoder pool options not set")
   112  	errNoMetricsAppenderPoolOptions = errors.New("downsampling enabled with metrics appender pool options not set")
   113  	errRollupRuleNoTransforms       = errors.New("rollup rule has no transforms set")
   114  )
   115  
   116  // CustomRuleStoreFn is a function to swap the backend used for the rule stores.
   117  type CustomRuleStoreFn func(clusterclient.Client, instrument.Options) (kv.TxnStore, error)
   118  
   119  // DownsamplerOptions is a set of required downsampler options.
   120  type DownsamplerOptions struct {
   121  	Storage                    storage.Appender
   122  	StorageFlushConcurrency    int
   123  	ClusterClient              clusterclient.Client
   124  	RulesKVStore               kv.Store
   125  	ClusterNamespacesWatcher   m3.ClusterNamespacesWatcher
   126  	NameTag                    string
   127  	ClockOptions               clock.Options
   128  	InstrumentOptions          instrument.Options
   129  	TagEncoderOptions          serialize.TagEncoderOptions
   130  	TagDecoderOptions          serialize.TagDecoderOptions
   131  	TagEncoderPoolOptions      pool.ObjectPoolOptions
   132  	TagDecoderPoolOptions      pool.ObjectPoolOptions
   133  	OpenTimeout                time.Duration
   134  	TagOptions                 models.TagOptions
   135  	MetricsAppenderPoolOptions pool.ObjectPoolOptions
   136  	RWOptions                  xio.Options
   137  	InterruptedCh              <-chan struct{}
   138  }
   139  
   140  // NameTagOrDefault returns the configured name tag or the default if one is not set.
   141  func (o DownsamplerOptions) NameTagOrDefault() []byte {
   142  	if o.NameTag == "" {
   143  		return defaultMetricNameTagName
   144  	}
   145  	return []byte(o.NameTag)
   146  }
   147  
   148  // AutoMappingRule is a mapping rule to apply to metrics.
   149  type AutoMappingRule struct {
   150  	Aggregations []aggregation.Type
   151  	Policies     policy.StoragePolicies
   152  }
   153  
   154  // NewAutoMappingRules generates mapping rules from cluster namespaces.
   155  func NewAutoMappingRules(namespaces []m3.ClusterNamespace) ([]AutoMappingRule, error) {
   156  	autoMappingRules := make([]AutoMappingRule, 0, len(namespaces))
   157  	for _, namespace := range namespaces {
   158  		opts := namespace.Options()
   159  		attrs := opts.Attributes()
   160  		if attrs.MetricsType != storagemetadata.AggregatedMetricsType {
   161  			continue
   162  		}
   163  
   164  		if opts.ReadOnly() {
   165  			continue
   166  		}
   167  
   168  		downsampleOpts, err := opts.DownsampleOptions()
   169  		if err != nil {
   170  			errFmt := "unable to resolve downsample options for namespace: %v"
   171  			return nil, fmt.Errorf(errFmt, namespace.NamespaceID().String())
   172  		}
   173  		if downsampleOpts.All {
   174  			storagePolicy := policy.NewStoragePolicy(attrs.Resolution,
   175  				xtime.Second, attrs.Retention)
   176  			autoMappingRules = append(autoMappingRules, AutoMappingRule{
   177  				// NB(r): By default we will apply just keep all last values
   178  				// since coordinator only uses downsampling with Prometheus
   179  				// remote write endpoint.
   180  				// More rich static configuration mapping rules can be added
   181  				// in the future but they are currently not required.
   182  				Aggregations: []aggregation.Type{aggregation.Last},
   183  				Policies:     policy.StoragePolicies{storagePolicy},
   184  			})
   185  		}
   186  	}
   187  	return autoMappingRules, nil
   188  }
   189  
   190  // StagedMetadatas returns the corresponding staged metadatas for this mapping rule.
   191  func (r AutoMappingRule) StagedMetadatas() (metadata.StagedMetadatas, error) {
   192  	aggID, err := aggregation.CompressTypes(r.Aggregations...)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	return metadata.StagedMetadatas{
   198  		metadata.StagedMetadata{
   199  			Metadata: metadata.Metadata{
   200  				Pipelines: metadata.PipelineMetadatas{
   201  					metadata.PipelineMetadata{
   202  						AggregationID:   aggID,
   203  						StoragePolicies: r.Policies,
   204  					},
   205  				},
   206  			},
   207  		},
   208  	}, nil
   209  }
   210  
   211  // Validate validates the dynamic downsampling options.
   212  func (o DownsamplerOptions) validate() error {
   213  	if o.Storage == nil {
   214  		return errNoStorage
   215  	}
   216  	if o.ClusterClient == nil {
   217  		return errNoClusterClient
   218  	}
   219  	if o.RulesKVStore == nil {
   220  		return errNoRulesStore
   221  	}
   222  	if o.ClockOptions == nil {
   223  		return errNoClockOptions
   224  	}
   225  	if o.InstrumentOptions == nil {
   226  		return errNoInstrumentOptions
   227  	}
   228  	if o.TagEncoderOptions == nil {
   229  		return errNoTagEncoderOptions
   230  	}
   231  	if o.TagDecoderOptions == nil {
   232  		return errNoTagDecoderOptions
   233  	}
   234  	if o.TagEncoderPoolOptions == nil {
   235  		return errNoTagEncoderPoolOptions
   236  	}
   237  	if o.TagDecoderPoolOptions == nil {
   238  		return errNoTagDecoderPoolOptions
   239  	}
   240  	if o.MetricsAppenderPoolOptions == nil {
   241  		return errNoMetricsAppenderPoolOptions
   242  	}
   243  	return nil
   244  }
   245  
   246  // agg will have one of aggregator or clientRemote set, the
   247  // rest of the fields must not be nil.
   248  type agg struct {
   249  	aggregator   aggregator.Aggregator
   250  	clientRemote client.Client
   251  
   252  	clockOpts      clock.Options
   253  	matcher        matcher.Matcher
   254  	pools          aggPools
   255  	untimedRollups bool
   256  }
   257  
   258  // Configuration configurates a downsampler.
   259  type Configuration struct {
   260  	// Matcher is the configuration for the downsampler matcher.
   261  	Matcher MatcherConfiguration `yaml:"matcher"`
   262  
   263  	// Rules is a set of downsample rules. If set, this overrides any rules set
   264  	// in the KV store (and the rules in KV store are not evaluated at all).
   265  	Rules *RulesConfiguration `yaml:"rules"`
   266  
   267  	// RemoteAggregator specifies that downsampling should be done remotely
   268  	// by sending values to a remote m3aggregator cluster which then
   269  	// can forward the aggregated values to stateless m3coordinator backends.
   270  	RemoteAggregator *RemoteAggregatorConfiguration `yaml:"remoteAggregator"`
   271  
   272  	// AggregationTypes configs the aggregation types.
   273  	AggregationTypes *aggregation.TypesConfiguration `yaml:"aggregationTypes"`
   274  
   275  	// Pool of counter elements.
   276  	CounterElemPool pool.ObjectPoolConfiguration `yaml:"counterElemPool"`
   277  
   278  	// Pool of timer elements.
   279  	TimerElemPool pool.ObjectPoolConfiguration `yaml:"timerElemPool"`
   280  
   281  	// Pool of gauge elements.
   282  	GaugeElemPool pool.ObjectPoolConfiguration `yaml:"gaugeElemPool"`
   283  
   284  	// BufferPastLimits specifies the buffer past limits.
   285  	BufferPastLimits []BufferPastLimitConfiguration `yaml:"bufferPastLimits"`
   286  
   287  	// EntryTTL determines how long an entry remains alive before it may be
   288  	// expired due to inactivity.
   289  	EntryTTL time.Duration `yaml:"entryTTL"`
   290  
   291  	// UntimedRollups indicates rollup rules should be untimed.
   292  	UntimedRollups bool `yaml:"untimedRollups"`
   293  }
   294  
   295  // MatcherConfiguration is the configuration for the rule matcher.
   296  type MatcherConfiguration struct {
   297  	// Cache if non-zero will set the capacity of the rules matching cache.
   298  	Cache MatcherCacheConfiguration `yaml:"cache"`
   299  	// NamespaceTag defines the namespace tag to use to select rules
   300  	// namespace to evaluate against. Default is "__m3_namespace__".
   301  	NamespaceTag string `yaml:"namespaceTag"`
   302  	// RequireNamespaceWatchOnInit returns the flag to ensure matcher is initialized with a loaded namespace watch.
   303  	// This only makes sense to use if the corresponding namespace / ruleset values are properly seeded.
   304  	RequireNamespaceWatchOnInit bool `yaml:"requireNamespaceWatchOnInit"`
   305  }
   306  
   307  // MatcherCacheConfiguration is the configuration for the rule matcher cache.
   308  type MatcherCacheConfiguration struct {
   309  	// Capacity if set the capacity of the rules matching cache.
   310  	Capacity *int `yaml:"capacity"`
   311  }
   312  
   313  // RulesConfiguration is a set of rules configuration to use for downsampling.
   314  type RulesConfiguration struct {
   315  	// MappingRules are mapping rules that set retention and resolution
   316  	// for metrics given a filter to match metrics against.
   317  	MappingRules []MappingRuleConfiguration `yaml:"mappingRules"`
   318  
   319  	// RollupRules are rollup rules that sets specific aggregations for sets
   320  	// of metrics given a filter to match metrics against.
   321  	RollupRules []RollupRuleConfiguration `yaml:"rollupRules"`
   322  }
   323  
   324  // MappingRuleConfiguration is a mapping rule configuration.
   325  type MappingRuleConfiguration struct {
   326  	// Filter is a string separated filter of label name to label value
   327  	// glob patterns to filter the mapping rule to.
   328  	// e.g. "app:*nginx* foo:bar baz:qux*qaz*"
   329  	Filter string `yaml:"filter"`
   330  
   331  	// Aggregations is the aggregations to apply to the set of metrics.
   332  	// One of:
   333  	// - "Last"
   334  	// - "Min"
   335  	// - "Max"
   336  	// - "Mean"
   337  	// - "Median"
   338  	// - "Count"
   339  	// - "Sum"
   340  	// - "SumSq"
   341  	// - "Stdev"
   342  	// - "P10"
   343  	// - "P20"
   344  	// - "P30"
   345  	// - "P40"
   346  	// - "P50"
   347  	// - "P60"
   348  	// - "P70"
   349  	// - "P80"
   350  	// - "P90"
   351  	// - "P95"
   352  	// - "P99"
   353  	// - "P999"
   354  	// - "P9999"
   355  	Aggregations []aggregation.Type `yaml:"aggregations"`
   356  
   357  	// StoragePolicies are retention/resolution storage policies at which to
   358  	// keep matched metrics.
   359  	StoragePolicies []StoragePolicyConfiguration `yaml:"storagePolicies"`
   360  
   361  	// Drop specifies to drop any metrics that match the filter rather than
   362  	// keeping them with a storage policy.
   363  	Drop bool `yaml:"drop"`
   364  
   365  	// Tags are the tags to be added to the metric while applying the mapping
   366  	// rule. Users are free to add name/value combinations to the metric. The
   367  	// coordinator also supports certain first class tags which will augment
   368  	// the metric with coordinator generated tag values.
   369  	// __m3_graphite_aggregation__ as a tag will augment the metric with an
   370  	// aggregation tag which is required for graphite. If a metric is of the
   371  	// form {__g0__:stats __g1__:metric __g2__:timer} and we have configured
   372  	// a P95 aggregation, this option will add __g3__:P95 to the metric.
   373  	// __m3_graphite_prefix__ as a tag will add the provided value as a prefix
   374  	// to graphite metrics.
   375  	// __m3_drop_timestamp__ as a tag will drop the timestamp from while
   376  	// writing the metric out. So effectively treat it as an untimed metric.
   377  	Tags []Tag `yaml:"tags"`
   378  
   379  	// Optional fields follow.
   380  
   381  	// Name is optional.
   382  	Name string `yaml:"name"`
   383  }
   384  
   385  // Tag is structure describing tags as used by mapping rule configuration.
   386  type Tag struct {
   387  	// Name is the tag name.
   388  	Name string `yaml:"name"`
   389  	// Value is the tag value.
   390  	Value string `yaml:"value"`
   391  }
   392  
   393  // Rule returns the mapping rule for the mapping rule configuration.
   394  func (r MappingRuleConfiguration) Rule() (view.MappingRule, error) {
   395  	id := uuid.New()
   396  	name := r.Name
   397  	if name == "" {
   398  		name = id
   399  	}
   400  	filter := r.Filter
   401  
   402  	aggID, err := aggregation.CompressTypes(r.Aggregations...)
   403  	if err != nil {
   404  		return view.MappingRule{}, err
   405  	}
   406  
   407  	storagePolicies, err := StoragePolicyConfigurations(r.StoragePolicies).StoragePolicies()
   408  	if err != nil {
   409  		return view.MappingRule{}, err
   410  	}
   411  
   412  	var drop policy.DropPolicy
   413  	if r.Drop {
   414  		drop = policy.DropIfOnlyMatch
   415  	}
   416  
   417  	tags := make([]models.Tag, 0, len(r.Tags))
   418  	for _, tag := range r.Tags {
   419  		tags = append(tags, models.Tag{
   420  			Name:  []byte(tag.Name),
   421  			Value: []byte(tag.Value),
   422  		})
   423  	}
   424  
   425  	return view.MappingRule{
   426  		ID:              id,
   427  		Name:            name,
   428  		Filter:          filter,
   429  		AggregationID:   aggID,
   430  		StoragePolicies: storagePolicies,
   431  		DropPolicy:      drop,
   432  		Tags:            tags,
   433  	}, nil
   434  }
   435  
   436  // StoragePolicyConfiguration is the storage policy to apply to a set of metrics.
   437  type StoragePolicyConfiguration struct {
   438  	Resolution time.Duration `yaml:"resolution"`
   439  	Retention  time.Duration `yaml:"retention"`
   440  }
   441  
   442  // StoragePolicy returns the corresponding storage policy.
   443  func (p StoragePolicyConfiguration) StoragePolicy() (policy.StoragePolicy, error) {
   444  	return policy.ParseStoragePolicy(p.String())
   445  }
   446  
   447  func (p StoragePolicyConfiguration) String() string {
   448  	return fmt.Sprintf("%s:%s", p.Resolution.String(), p.Retention.String())
   449  }
   450  
   451  // StoragePolicyConfigurations are a set of storage policy configurations.
   452  type StoragePolicyConfigurations []StoragePolicyConfiguration
   453  
   454  // StoragePolicies returns storage policies.
   455  func (p StoragePolicyConfigurations) StoragePolicies() (policy.StoragePolicies, error) {
   456  	storagePolicies := make(policy.StoragePolicies, 0, len(p))
   457  	for _, policy := range p {
   458  		value, err := policy.StoragePolicy()
   459  		if err != nil {
   460  			return nil, err
   461  		}
   462  		storagePolicies = append(storagePolicies, value)
   463  	}
   464  	return storagePolicies, nil
   465  }
   466  
   467  // RollupRuleConfiguration is a rollup rule configuration.
   468  type RollupRuleConfiguration struct {
   469  	// Filter is a space separated filter of label name to label value glob
   470  	// patterns to which to filter the mapping rule.
   471  	// e.g. "app:*nginx* foo:bar baz:qux*qaz*"
   472  	Filter string `yaml:"filter"`
   473  
   474  	// Transforms are a set of of rollup rule transforms.
   475  	Transforms []TransformConfiguration `yaml:"transforms"`
   476  
   477  	// StoragePolicies are retention/resolution storage policies at which to keep
   478  	// the matched metrics.
   479  	StoragePolicies []StoragePolicyConfiguration `yaml:"storagePolicies"`
   480  
   481  	// Optional fields follow.
   482  
   483  	// Name is optional.
   484  	Name string `yaml:"name"`
   485  
   486  	// Tags are the tags to be added to the metric while applying the rollup
   487  	// rule. Users are free to add name/value combinations to the metric.
   488  	Tags []Tag `yaml:"tags"`
   489  }
   490  
   491  // Rule returns the rollup rule for the rollup rule configuration.
   492  func (r RollupRuleConfiguration) Rule() (view.RollupRule, error) {
   493  	id := uuid.New()
   494  	name := r.Name
   495  	if name == "" {
   496  		name = id
   497  	}
   498  	filter := r.Filter
   499  
   500  	storagePolicies, err := StoragePolicyConfigurations(r.StoragePolicies).
   501  		StoragePolicies()
   502  	if err != nil {
   503  		return view.RollupRule{}, err
   504  	}
   505  
   506  	ops := make([]pipeline.OpUnion, 0, len(r.Transforms))
   507  	for _, elem := range r.Transforms {
   508  		// TODO: make sure only one of "Rollup" or "Aggregate" or "Transform" is not nil
   509  		switch {
   510  		case elem.Rollup != nil:
   511  			cfg := elem.Rollup
   512  			if len(cfg.GroupBy) > 0 && len(cfg.ExcludeBy) > 0 {
   513  				return view.RollupRule{}, fmt.Errorf(
   514  					"must specify group by or exclude by tags for rollup operation not both: "+
   515  						"groupBy=%d, excludeBy=%d", len(cfg.GroupBy), len(cfg.ExcludeBy))
   516  			}
   517  
   518  			rollupType := pipelinepb.RollupOp_GROUP_BY
   519  			tags := cfg.GroupBy
   520  			if len(cfg.ExcludeBy) > 0 {
   521  				rollupType = pipelinepb.RollupOp_EXCLUDE_BY
   522  				tags = cfg.ExcludeBy
   523  			}
   524  
   525  			aggregationTypes, err := AggregationTypes(cfg.Aggregations).Proto()
   526  			if err != nil {
   527  				return view.RollupRule{}, err
   528  			}
   529  
   530  			op, err := pipeline.NewOpUnionFromProto(pipelinepb.PipelineOp{
   531  				Type: pipelinepb.PipelineOp_ROLLUP,
   532  				Rollup: &pipelinepb.RollupOp{
   533  					Type:             rollupType,
   534  					NewName:          cfg.MetricName,
   535  					Tags:             tags,
   536  					AggregationTypes: aggregationTypes,
   537  				},
   538  			})
   539  			if err != nil {
   540  				return view.RollupRule{}, err
   541  			}
   542  			ops = append(ops, op)
   543  		case elem.Aggregate != nil:
   544  			cfg := elem.Aggregate
   545  			aggregationType, err := cfg.Type.Proto()
   546  			if err != nil {
   547  				return view.RollupRule{}, err
   548  			}
   549  			op, err := pipeline.NewOpUnionFromProto(pipelinepb.PipelineOp{
   550  				Type: pipelinepb.PipelineOp_AGGREGATION,
   551  				Aggregation: &pipelinepb.AggregationOp{
   552  					Type: aggregationType,
   553  				},
   554  			})
   555  			if err != nil {
   556  				return view.RollupRule{}, err
   557  			}
   558  			ops = append(ops, op)
   559  		case elem.Transform != nil:
   560  			cfg := elem.Transform
   561  			var transformType transformationpb.TransformationType
   562  			err := cfg.Type.ToProto(&transformType)
   563  			if err != nil {
   564  				return view.RollupRule{}, err
   565  			}
   566  			op, err := pipeline.NewOpUnionFromProto(pipelinepb.PipelineOp{
   567  				Type: pipelinepb.PipelineOp_TRANSFORMATION,
   568  				Transformation: &pipelinepb.TransformationOp{
   569  					Type: transformType,
   570  				},
   571  			})
   572  			if err != nil {
   573  				return view.RollupRule{}, err
   574  			}
   575  			ops = append(ops, op)
   576  		}
   577  	}
   578  
   579  	if len(ops) == 0 {
   580  		return view.RollupRule{}, errRollupRuleNoTransforms
   581  	}
   582  
   583  	targetPipeline := pipeline.NewPipeline(ops)
   584  
   585  	targets := []view.RollupTarget{
   586  		{
   587  			Pipeline:        targetPipeline,
   588  			StoragePolicies: storagePolicies,
   589  		},
   590  	}
   591  
   592  	tags := make([]models.Tag, 0, len(r.Tags))
   593  	for _, tag := range r.Tags {
   594  		tags = append(tags, models.Tag{
   595  			Name:  []byte(tag.Name),
   596  			Value: []byte(tag.Value),
   597  		})
   598  	}
   599  
   600  	return view.RollupRule{
   601  		ID:      id,
   602  		Name:    name,
   603  		Filter:  filter,
   604  		Targets: targets,
   605  		Tags:    tags,
   606  	}, nil
   607  }
   608  
   609  // TransformConfiguration is a rollup rule transform operation, only one
   610  // single operation is allowed to be specified on any one transform configuration.
   611  type TransformConfiguration struct {
   612  	Rollup    *RollupOperationConfiguration    `yaml:"rollup"`
   613  	Aggregate *AggregateOperationConfiguration `yaml:"aggregate"`
   614  	Transform *TransformOperationConfiguration `yaml:"transform"`
   615  }
   616  
   617  // RollupOperationConfiguration is a rollup operation.
   618  type RollupOperationConfiguration struct {
   619  	// MetricName is the name of the new metric that is emitted after
   620  	// the rollup is applied with its aggregations and group by's.
   621  	MetricName string `yaml:"metricName"`
   622  
   623  	// GroupBy is a set of labels to group by, only these remain on the
   624  	// new metric name produced by the rollup operation.
   625  	// Note: Can only use either groupBy or excludeBy, not both, use the
   626  	// rollup operation "type" to specify which is used.
   627  	GroupBy []string `yaml:"groupBy"`
   628  
   629  	// ExcludeBy is a set of labels to exclude by, only these tags are removed
   630  	// from the resulting rolled up metric.
   631  	// Note: Can only use either groupBy or excludeBy, not both, use the
   632  	// rollup operation "type" to specify which is used.
   633  	ExcludeBy []string `yaml:"excludeBy"`
   634  
   635  	// Aggregations is a set of aggregate operations to perform.
   636  	Aggregations []aggregation.Type `yaml:"aggregations"`
   637  }
   638  
   639  // AggregateOperationConfiguration is an aggregate operation.
   640  type AggregateOperationConfiguration struct {
   641  	// Type is an aggregation operation type.
   642  	Type aggregation.Type `yaml:"type"`
   643  }
   644  
   645  // TransformOperationConfiguration is a transform operation.
   646  type TransformOperationConfiguration struct {
   647  	// Type is a transformation operation type.
   648  	Type transformation.Type `yaml:"type"`
   649  }
   650  
   651  // AggregationTypes is a set of aggregation types.
   652  type AggregationTypes []aggregation.Type
   653  
   654  // Proto returns a set of aggregation types as their protobuf value.
   655  func (t AggregationTypes) Proto() ([]aggregationpb.AggregationType, error) {
   656  	result := make([]aggregationpb.AggregationType, 0, len(t))
   657  	for _, elem := range t {
   658  		value, err := elem.Proto()
   659  		if err != nil {
   660  			return nil, err
   661  		}
   662  		result = append(result, value)
   663  	}
   664  	return result, nil
   665  }
   666  
   667  // RemoteAggregatorConfiguration specifies a remote aggregator
   668  // to use for downsampling.
   669  type RemoteAggregatorConfiguration struct {
   670  	// Client is the remote aggregator client.
   671  	Client client.Configuration `yaml:"client"`
   672  	// clientOverride can be used in tests to test initializing a mock client.
   673  	clientOverride client.Client
   674  }
   675  
   676  func (c RemoteAggregatorConfiguration) newClient(
   677  	kvClient clusterclient.Client,
   678  	clockOpts clock.Options,
   679  	instrumentOpts instrument.Options,
   680  	rwOpts xio.Options,
   681  ) (client.Client, error) {
   682  	if c.clientOverride != nil {
   683  		return c.clientOverride, nil
   684  	}
   685  
   686  	return c.Client.NewClient(kvClient, clockOpts, instrumentOpts, rwOpts)
   687  }
   688  
   689  // BufferPastLimitConfiguration specifies a custom buffer past limit
   690  // for aggregation tiles.
   691  type BufferPastLimitConfiguration struct {
   692  	Resolution time.Duration `yaml:"resolution"`
   693  	BufferPast time.Duration `yaml:"bufferPast"`
   694  }
   695  
   696  // NewDownsampler returns a new downsampler.
   697  func (cfg Configuration) NewDownsampler(
   698  	opts DownsamplerOptions,
   699  ) (Downsampler, error) {
   700  	agg, err := cfg.newAggregator(opts)
   701  	if err != nil {
   702  		return nil, err
   703  	}
   704  
   705  	return newDownsampler(downsamplerOptions{
   706  		opts: opts,
   707  		agg:  agg,
   708  	})
   709  }
   710  
   711  func (cfg Configuration) newAggregator(o DownsamplerOptions) (agg, error) {
   712  	// Validate options first.
   713  	if err := o.validate(); err != nil {
   714  		return agg{}, err
   715  	}
   716  
   717  	var (
   718  		storageFlushConcurrency = defaultStorageFlushConcurrency
   719  		clockOpts               = o.ClockOptions
   720  		instrumentOpts          = o.InstrumentOptions
   721  		scope                   = instrumentOpts.MetricsScope()
   722  		logger                  = instrumentOpts.Logger()
   723  		openTimeout             = defaultOpenTimeout
   724  		namespaceTag            = defaultNamespaceTag
   725  	)
   726  	if o.StorageFlushConcurrency > 0 {
   727  		storageFlushConcurrency = o.StorageFlushConcurrency
   728  	}
   729  	if o.OpenTimeout > 0 {
   730  		openTimeout = o.OpenTimeout
   731  	}
   732  	if cfg.Matcher.NamespaceTag != "" {
   733  		namespaceTag = cfg.Matcher.NamespaceTag
   734  	}
   735  
   736  	pools := o.newAggregatorPools()
   737  	ruleSetOpts := o.newAggregatorRulesOptions(pools)
   738  
   739  	matcherOpts := matcher.NewOptions().
   740  		SetClockOptions(clockOpts).
   741  		SetInstrumentOptions(instrumentOpts).
   742  		SetRuleSetOptions(ruleSetOpts).
   743  		SetKVStore(o.RulesKVStore).
   744  		SetNamespaceResolver(namespace.NewResolver([]byte(namespaceTag), nil)).
   745  		SetRequireNamespaceWatchOnInit(cfg.Matcher.RequireNamespaceWatchOnInit).
   746  		SetInterruptedCh(o.InterruptedCh)
   747  
   748  	// NB(r): If rules are being explicitly set in config then we are
   749  	// going to use an in memory KV store for rules and explicitly set them up.
   750  	if cfg.Rules != nil {
   751  		logger.Debug("registering downsample rules from config, not using KV")
   752  		kvTxnMemStore := mem.NewStore()
   753  
   754  		// Initialize the namespaces
   755  		if err := initStoreNamespaces(kvTxnMemStore, matcherOpts.NamespacesKey()); err != nil {
   756  			return agg{}, err
   757  		}
   758  
   759  		rulesetKeyFmt := matcherOpts.RuleSetKeyFn()([]byte("%s"))
   760  		rulesStoreOpts := ruleskv.NewStoreOptions(matcherOpts.NamespacesKey(),
   761  			rulesetKeyFmt, nil)
   762  		rulesStore := ruleskv.NewStore(kvTxnMemStore, rulesStoreOpts)
   763  
   764  		ruleNamespaces, err := rulesStore.ReadNamespaces()
   765  		if err != nil {
   766  			return agg{}, err
   767  		}
   768  
   769  		updateMetadata := rules.NewRuleSetUpdateHelper(0).
   770  			NewUpdateMetadata(time.Now().UnixNano(), "config")
   771  
   772  		// Create the default namespace, always not present since in-memory.
   773  		_, err = ruleNamespaces.AddNamespace(defaultConfigInMemoryNamespace,
   774  			updateMetadata)
   775  		if err != nil {
   776  			return agg{}, err
   777  		}
   778  
   779  		// Create the ruleset in the default namespace.
   780  		rs := rules.NewEmptyRuleSet(defaultConfigInMemoryNamespace,
   781  			updateMetadata)
   782  		for _, mappingRule := range cfg.Rules.MappingRules {
   783  			rule, err := mappingRule.Rule()
   784  			if err != nil {
   785  				return agg{}, err
   786  			}
   787  
   788  			_, err = rs.AddMappingRule(rule, updateMetadata)
   789  			if err != nil {
   790  				return agg{}, err
   791  			}
   792  		}
   793  
   794  		for _, rollupRule := range cfg.Rules.RollupRules {
   795  			rule, err := rollupRule.Rule()
   796  			if err != nil {
   797  				return agg{}, err
   798  			}
   799  
   800  			_, err = rs.AddRollupRule(rule, updateMetadata)
   801  			if err != nil {
   802  				return agg{}, err
   803  			}
   804  		}
   805  
   806  		if err := rulesStore.WriteAll(ruleNamespaces, rs); err != nil {
   807  			return agg{}, err
   808  		}
   809  
   810  		// Set the rules KV store to the in-memory one we created to
   811  		// store the rules we created from config.
   812  		// This makes sure that other components using rules KV store points to
   813  		// the in-memory store that has the rules created from config.
   814  		matcherOpts = matcherOpts.SetKVStore(kvTxnMemStore)
   815  	}
   816  
   817  	matcherCacheCapacity := defaultMatcherCacheCapacity
   818  	if v := cfg.Matcher.Cache.Capacity; v != nil {
   819  		matcherCacheCapacity = *v
   820  	}
   821  
   822  	kvStore, err := o.ClusterClient.KV()
   823  	if err != nil {
   824  		return agg{}, err
   825  	}
   826  
   827  	// NB(antanas): matcher registers watcher on namespaces key. Making sure it is set, otherwise watcher times out.
   828  	// With RequireNamespaceWatchOnInit being true we expect namespaces to be set upfront
   829  	// so we do not initialize them here at all because it might potentially hide human error.
   830  	if !matcherOpts.RequireNamespaceWatchOnInit() {
   831  		if err := initStoreNamespaces(kvStore, matcherOpts.NamespacesKey()); err != nil {
   832  			return agg{}, err
   833  		}
   834  	}
   835  
   836  	matcher, err := o.newAggregatorMatcher(matcherOpts, matcherCacheCapacity)
   837  	if err != nil {
   838  		return agg{}, err
   839  	}
   840  
   841  	if remoteAgg := cfg.RemoteAggregator; remoteAgg != nil {
   842  		// If downsampling setup to use a remote aggregator instead of local
   843  		// aggregator, set that up instead.
   844  		scope := instrumentOpts.MetricsScope().SubScope("remote-aggregator-client")
   845  		iOpts := instrumentOpts.SetMetricsScope(scope)
   846  		rwOpts := o.RWOptions
   847  		if rwOpts == nil {
   848  			logger.Info("no rw options set, using default")
   849  			rwOpts = xio.NewOptions()
   850  		}
   851  
   852  		client, err := remoteAgg.newClient(o.ClusterClient, clockOpts, iOpts, rwOpts)
   853  		if err != nil {
   854  			err = fmt.Errorf("could not create remote aggregator client: %v", err)
   855  			return agg{}, err
   856  		}
   857  		if err := client.Init(); err != nil {
   858  			return agg{}, fmt.Errorf("could not initialize remote aggregator client: %v", err)
   859  		}
   860  
   861  		return agg{
   862  			clientRemote:   client,
   863  			matcher:        matcher,
   864  			pools:          pools,
   865  			untimedRollups: cfg.UntimedRollups,
   866  		}, nil
   867  	}
   868  
   869  	serviceID := services.NewServiceID().
   870  		SetEnvironment("production").
   871  		SetName("downsampler").
   872  		SetZone("embedded")
   873  
   874  	localKVStore := kvStore
   875  	// NB(antanas): to protect against running with real Etcd and overriding existing placements.
   876  	if !mem.IsMem(localKVStore) {
   877  		localKVStore = mem.NewStore()
   878  	}
   879  
   880  	placementManager, err := o.newAggregatorPlacementManager(serviceID, localKVStore)
   881  	if err != nil {
   882  		return agg{}, err
   883  	}
   884  
   885  	flushTimesManager := aggregator.NewFlushTimesManager(
   886  		aggregator.NewFlushTimesManagerOptions().
   887  			SetFlushTimesStore(localKVStore))
   888  
   889  	electionManager, err := o.newAggregatorElectionManager(serviceID,
   890  		placementManager, flushTimesManager, clockOpts)
   891  	if err != nil {
   892  		return agg{}, err
   893  	}
   894  
   895  	flushManager, flushHandler := o.newAggregatorFlushManagerAndHandler(
   896  		placementManager, flushTimesManager, electionManager, o.ClockOptions, instrumentOpts,
   897  		storageFlushConcurrency, pools)
   898  
   899  	bufferPastLimits := defaultBufferPastLimits
   900  	if numLimitsCfg := len(cfg.BufferPastLimits); numLimitsCfg > 0 {
   901  		// Allow overrides from config.
   902  		bufferPastLimits = make([]bufferPastLimit, 0, numLimitsCfg)
   903  		for _, limit := range cfg.BufferPastLimits {
   904  			bufferPastLimits = append(bufferPastLimits, bufferPastLimit{
   905  				upperBound: limit.Resolution,
   906  				bufferPast: limit.BufferPast,
   907  			})
   908  		}
   909  	}
   910  
   911  	bufferForPastTimedMetricFn := func(tile time.Duration) time.Duration {
   912  		return bufferForPastTimedMetric(bufferPastLimits, tile)
   913  	}
   914  
   915  	maxAllowedForwardingDelayFn := func(tile time.Duration, numForwardedTimes int) time.Duration {
   916  		return maxAllowedForwardingDelay(bufferPastLimits, tile, numForwardedTimes)
   917  	}
   918  
   919  	// Finally construct all options.
   920  	aggregatorOpts := aggregator.NewOptions(clockOpts).
   921  		SetInstrumentOptions(instrumentOpts).
   922  		SetDefaultStoragePolicies(nil).
   923  		SetMetricPrefix(nil).
   924  		SetCounterPrefix(nil).
   925  		SetGaugePrefix(nil).
   926  		SetTimerPrefix(nil).
   927  		SetPlacementManager(placementManager).
   928  		SetFlushTimesManager(flushTimesManager).
   929  		SetElectionManager(electionManager).
   930  		SetFlushManager(flushManager).
   931  		SetFlushHandler(flushHandler).
   932  		SetBufferForPastTimedMetricFn(bufferForPastTimedMetricFn).
   933  		SetBufferForFutureTimedMetric(defaultBufferFutureTimedMetric).
   934  		SetMaxAllowedForwardingDelayFn(maxAllowedForwardingDelayFn).
   935  		SetVerboseErrors(defaultVerboseErrors)
   936  
   937  	if cfg.EntryTTL != 0 {
   938  		aggregatorOpts = aggregatorOpts.SetEntryTTL(cfg.EntryTTL)
   939  	}
   940  
   941  	if cfg.AggregationTypes != nil {
   942  		aggTypeOpts, err := cfg.AggregationTypes.NewOptions(instrumentOpts)
   943  		if err != nil {
   944  			return agg{}, err
   945  		}
   946  		aggregatorOpts = aggregatorOpts.SetAggregationTypesOptions(aggTypeOpts)
   947  	}
   948  
   949  	// Set counter elem pool.
   950  	counterElemPoolOpts := cfg.CounterElemPool.NewObjectPoolOptions(
   951  		instrumentOpts.SetMetricsScope(scope.SubScope("counter-elem-pool")),
   952  	)
   953  	counterElemPool := aggregator.NewCounterElemPool(counterElemPoolOpts)
   954  	aggregatorOpts = aggregatorOpts.SetCounterElemPool(counterElemPool)
   955  	// use a singleton ElemOptions to avoid allocs per elem.
   956  	elemOpts := aggregator.NewElemOptions(aggregatorOpts)
   957  	counterElemPool.Init(func() *aggregator.CounterElem {
   958  		return aggregator.MustNewCounterElem(aggregator.ElemData{}, elemOpts)
   959  	})
   960  
   961  	// Set timer elem pool.
   962  	timerElemPoolOpts := cfg.TimerElemPool.NewObjectPoolOptions(
   963  		instrumentOpts.SetMetricsScope(scope.SubScope("timer-elem-pool")),
   964  	)
   965  	timerElemPool := aggregator.NewTimerElemPool(timerElemPoolOpts)
   966  	aggregatorOpts = aggregatorOpts.SetTimerElemPool(timerElemPool)
   967  	timerElemPool.Init(func() *aggregator.TimerElem {
   968  		return aggregator.MustNewTimerElem(aggregator.ElemData{}, elemOpts)
   969  	})
   970  
   971  	// Set gauge elem pool.
   972  	gaugeElemPoolOpts := cfg.GaugeElemPool.NewObjectPoolOptions(
   973  		instrumentOpts.SetMetricsScope(scope.SubScope("gauge-elem-pool")),
   974  	)
   975  	gaugeElemPool := aggregator.NewGaugeElemPool(gaugeElemPoolOpts)
   976  	aggregatorOpts = aggregatorOpts.SetGaugeElemPool(gaugeElemPool)
   977  	gaugeElemPool.Init(func() *aggregator.GaugeElem {
   978  		return aggregator.MustNewGaugeElem(aggregator.ElemData{}, elemOpts)
   979  	})
   980  
   981  	adminAggClient := newAggregatorLocalAdminClient()
   982  	aggregatorOpts = aggregatorOpts.SetAdminClient(adminAggClient)
   983  
   984  	aggregatorInstance := aggregator.NewAggregator(aggregatorOpts)
   985  	if err := aggregatorInstance.Open(); err != nil {
   986  		return agg{}, err
   987  	}
   988  
   989  	// Update the local aggregator client with the active aggregator instance.
   990  	// NB: Can't do this at construction time since needs to be passed as an
   991  	// option to the aggregator constructor.
   992  	adminAggClient.setAggregator(aggregatorInstance)
   993  
   994  	// Wait until the aggregator becomes leader so we don't miss datapoints
   995  	deadline := time.Now().Add(openTimeout)
   996  	for {
   997  		if !time.Now().Before(deadline) {
   998  			return agg{}, fmt.Errorf("aggregator not promoted to leader after: %s",
   999  				openTimeout.String())
  1000  		}
  1001  		if electionManager.ElectionState() == aggregator.LeaderState {
  1002  			break
  1003  		}
  1004  		time.Sleep(10 * time.Millisecond)
  1005  	}
  1006  
  1007  	return agg{
  1008  		aggregator:     aggregatorInstance,
  1009  		matcher:        matcher,
  1010  		pools:          pools,
  1011  		untimedRollups: cfg.UntimedRollups,
  1012  	}, nil
  1013  }
  1014  
  1015  func initStoreNamespaces(store kv.Store, nsKey string) error {
  1016  	_, err := store.SetIfNotExists(nsKey, &rulepb.Namespaces{})
  1017  	if errors.Is(err, kv.ErrAlreadyExists) {
  1018  		return nil
  1019  	}
  1020  	return err
  1021  }
  1022  
  1023  type aggPools struct {
  1024  	tagEncoderPool         serialize.TagEncoderPool
  1025  	tagDecoderPool         serialize.TagDecoderPool
  1026  	metricTagsIteratorPool serialize.MetricTagsIteratorPool
  1027  	metricsAppenderPool    *metricsAppenderPool
  1028  }
  1029  
  1030  func (o DownsamplerOptions) newAggregatorPools() aggPools {
  1031  	tagEncoderPool := serialize.NewTagEncoderPool(o.TagEncoderOptions,
  1032  		o.TagEncoderPoolOptions)
  1033  	tagEncoderPool.Init()
  1034  
  1035  	tagDecoderPool := serialize.NewTagDecoderPool(o.TagDecoderOptions,
  1036  		o.TagDecoderPoolOptions)
  1037  	tagDecoderPool.Init()
  1038  
  1039  	metricTagsIteratorPool := serialize.NewMetricTagsIteratorPool(tagDecoderPool,
  1040  		o.TagDecoderPoolOptions)
  1041  	metricTagsIteratorPool.Init()
  1042  
  1043  	metricsAppenderPool := newMetricsAppenderPool(
  1044  		o.MetricsAppenderPoolOptions,
  1045  		o.TagDecoderOptions.TagSerializationLimits(),
  1046  		o.NameTagOrDefault())
  1047  
  1048  	return aggPools{
  1049  		tagEncoderPool:         tagEncoderPool,
  1050  		tagDecoderPool:         tagDecoderPool,
  1051  		metricTagsIteratorPool: metricTagsIteratorPool,
  1052  		metricsAppenderPool:    metricsAppenderPool,
  1053  	}
  1054  }
  1055  
  1056  func (o DownsamplerOptions) newAggregatorRulesOptions(pools aggPools) rules.Options {
  1057  	nameTag := o.NameTagOrDefault()
  1058  	tagsFilterOpts := filters.TagsFilterOptions{
  1059  		NameTagKey: nameTag,
  1060  	}
  1061  
  1062  	isRollupIDFn := func(name []byte, tags []byte) bool {
  1063  		return isRollupID(tags, pools.metricTagsIteratorPool)
  1064  	}
  1065  
  1066  	newRollupIDProviderPool := newRollupIDProviderPool(pools.tagEncoderPool,
  1067  		o.TagEncoderPoolOptions, ident.BytesID(nameTag))
  1068  	newRollupIDProviderPool.Init()
  1069  
  1070  	newRollupIDFn := func(newName []byte, tagPairs []id.TagPair) []byte {
  1071  		// First filter out any tags that have a prefix that
  1072  		// are not included in output metric IDs (such as metric
  1073  		// type tags that are just used for filtering like __m3_type__).
  1074  		filtered := tagPairs[:0]
  1075  	TagPairsFilterLoop:
  1076  		for i := range tagPairs {
  1077  			for _, filter := range defaultFilterOutTagPrefixes {
  1078  				if bytes.HasPrefix(tagPairs[i].Name, filter) {
  1079  					continue TagPairsFilterLoop
  1080  				}
  1081  			}
  1082  			filtered = append(filtered, tagPairs[i])
  1083  		}
  1084  
  1085  		// Create the rollup using filtered tag pairs.
  1086  		rollupIDProvider := newRollupIDProviderPool.Get()
  1087  		id, err := rollupIDProvider.provide(newName, filtered)
  1088  		if err != nil {
  1089  			panic(err) // Encoding should never fail
  1090  		}
  1091  		rollupIDProvider.finalize()
  1092  		return id
  1093  	}
  1094  
  1095  	return rules.NewOptions().
  1096  		SetTagsFilterOptions(tagsFilterOpts).
  1097  		SetNewRollupIDFn(newRollupIDFn).
  1098  		SetIsRollupIDFn(isRollupIDFn)
  1099  }
  1100  
  1101  func (o DownsamplerOptions) newAggregatorMatcher(
  1102  	opts matcher.Options,
  1103  	capacity int,
  1104  ) (matcher.Matcher, error) {
  1105  	var matcherCache cache.Cache
  1106  	if capacity > 0 {
  1107  		scope := opts.InstrumentOptions().MetricsScope().SubScope("matcher-cache")
  1108  		instrumentOpts := opts.InstrumentOptions().
  1109  			SetMetricsScope(scope)
  1110  		cacheOpts := cache.NewOptions().
  1111  			SetCapacity(capacity).
  1112  			SetNamespaceResolver(opts.NamespaceResolver()).
  1113  			SetClockOptions(opts.ClockOptions()).
  1114  			SetInstrumentOptions(instrumentOpts)
  1115  		matcherCache = cache.NewCache(cacheOpts)
  1116  	}
  1117  
  1118  	return matcher.NewMatcher(matcherCache, opts)
  1119  }
  1120  
  1121  func (o DownsamplerOptions) newAggregatorPlacementManager(
  1122  	serviceID services.ServiceID,
  1123  	localKVStore kv.Store,
  1124  ) (aggregator.PlacementManager, error) {
  1125  	instance := placement.NewInstance().
  1126  		SetID(instanceID).
  1127  		SetWeight(1).
  1128  		SetEndpoint(instanceID)
  1129  
  1130  	placementOpts := placement.NewOptions().
  1131  		SetIsStaged(true).
  1132  		SetShardStateMode(placement.StableShardStateOnly)
  1133  
  1134  	placementSvc := placementservice.NewPlacementService(
  1135  		placementstorage.NewPlacementStorage(localKVStore, placementKVKey, placementOpts),
  1136  		placementservice.WithPlacementOptions(placementOpts))
  1137  
  1138  	_, err := placementSvc.BuildInitialPlacement([]placement.Instance{instance}, numShards,
  1139  		replicationFactor)
  1140  	if err != nil {
  1141  		return nil, err
  1142  	}
  1143  
  1144  	placementWatcherOpts := placement.NewWatcherOptions().
  1145  		SetStagedPlacementKey(placementKVKey).
  1146  		SetStagedPlacementStore(localKVStore)
  1147  	placementManagerOpts := aggregator.NewPlacementManagerOptions().
  1148  		SetInstanceID(instanceID).
  1149  		SetWatcherOptions(placementWatcherOpts)
  1150  
  1151  	return aggregator.NewPlacementManager(placementManagerOpts), nil
  1152  }
  1153  
  1154  func (o DownsamplerOptions) newAggregatorElectionManager(
  1155  	serviceID services.ServiceID,
  1156  	placementManager aggregator.PlacementManager,
  1157  	flushTimesManager aggregator.FlushTimesManager,
  1158  	clockOpts clock.Options,
  1159  ) (aggregator.ElectionManager, error) {
  1160  	leaderValue := instanceID
  1161  	campaignOpts, err := services.NewCampaignOptions()
  1162  	if err != nil {
  1163  		return nil, err
  1164  	}
  1165  
  1166  	campaignOpts = campaignOpts.SetLeaderValue(leaderValue)
  1167  
  1168  	leaderService := newLocalLeaderService(serviceID)
  1169  
  1170  	electionManagerOpts := aggregator.NewElectionManagerOptions().
  1171  		SetClockOptions(clockOpts).
  1172  		SetCampaignOptions(campaignOpts).
  1173  		SetLeaderService(leaderService).
  1174  		SetPlacementManager(placementManager).
  1175  		SetFlushTimesManager(flushTimesManager)
  1176  
  1177  	return aggregator.NewElectionManager(electionManagerOpts), nil
  1178  }
  1179  
  1180  func (o DownsamplerOptions) newAggregatorFlushManagerAndHandler(
  1181  	placementManager aggregator.PlacementManager,
  1182  	flushTimesManager aggregator.FlushTimesManager,
  1183  	electionManager aggregator.ElectionManager,
  1184  	clockOpts clock.Options,
  1185  	instrumentOpts instrument.Options,
  1186  	storageFlushConcurrency int,
  1187  	pools aggPools,
  1188  ) (aggregator.FlushManager, handler.Handler) {
  1189  	flushManagerOpts := aggregator.NewFlushManagerOptions().
  1190  		SetClockOptions(clockOpts).
  1191  		SetPlacementManager(placementManager).
  1192  		SetFlushTimesManager(flushTimesManager).
  1193  		SetElectionManager(electionManager).
  1194  		SetJitterEnabled(false)
  1195  	flushManager := aggregator.NewFlushManager(flushManagerOpts)
  1196  
  1197  	flushWorkers := xsync.NewWorkerPool(storageFlushConcurrency)
  1198  	flushWorkers.Init()
  1199  	handler := newDownsamplerFlushHandler(o.Storage, pools.metricTagsIteratorPool,
  1200  		flushWorkers, o.TagOptions, instrumentOpts)
  1201  
  1202  	return flushManager, handler
  1203  }
  1204  
  1205  // Force the local aggregator client to implement client.Client.
  1206  var _ client.AdminClient = (*aggregatorLocalAdminClient)(nil)
  1207  
  1208  type aggregatorLocalAdminClient struct {
  1209  	agg aggregator.Aggregator
  1210  }
  1211  
  1212  func newAggregatorLocalAdminClient() *aggregatorLocalAdminClient {
  1213  	return &aggregatorLocalAdminClient{}
  1214  }
  1215  
  1216  func (c *aggregatorLocalAdminClient) setAggregator(agg aggregator.Aggregator) {
  1217  	c.agg = agg
  1218  }
  1219  
  1220  // Init initializes the client.
  1221  func (c *aggregatorLocalAdminClient) Init() error {
  1222  	return fmt.Errorf("always initialized")
  1223  }
  1224  
  1225  // WriteUntimedCounter writes untimed counter metrics.
  1226  func (c *aggregatorLocalAdminClient) WriteUntimedCounter(
  1227  	counter unaggregated.Counter,
  1228  	metadatas metadata.StagedMetadatas,
  1229  ) error {
  1230  	return c.agg.AddUntimed(counter.ToUnion(), metadatas)
  1231  }
  1232  
  1233  // WriteUntimedBatchTimer writes untimed batch timer metrics.
  1234  func (c *aggregatorLocalAdminClient) WriteUntimedBatchTimer(
  1235  	batchTimer unaggregated.BatchTimer,
  1236  	metadatas metadata.StagedMetadatas,
  1237  ) error {
  1238  	return c.agg.AddUntimed(batchTimer.ToUnion(), metadatas)
  1239  }
  1240  
  1241  // WriteUntimedGauge writes untimed gauge metrics.
  1242  func (c *aggregatorLocalAdminClient) WriteUntimedGauge(
  1243  	gauge unaggregated.Gauge,
  1244  	metadatas metadata.StagedMetadatas,
  1245  ) error {
  1246  	return c.agg.AddUntimed(gauge.ToUnion(), metadatas)
  1247  }
  1248  
  1249  // WriteTimed writes timed metrics.
  1250  func (c *aggregatorLocalAdminClient) WriteTimed(
  1251  	metric aggregated.Metric,
  1252  	metadata metadata.TimedMetadata,
  1253  ) error {
  1254  	return c.agg.AddTimed(metric, metadata)
  1255  }
  1256  
  1257  // WriteTimedWithStagedMetadatas writes timed metrics with staged metadatas.
  1258  func (c *aggregatorLocalAdminClient) WriteTimedWithStagedMetadatas(
  1259  	metric aggregated.Metric,
  1260  	metadatas metadata.StagedMetadatas,
  1261  ) error {
  1262  	return c.agg.AddTimedWithStagedMetadatas(metric, metadatas)
  1263  }
  1264  
  1265  // WriteForwarded writes forwarded metrics.
  1266  func (c *aggregatorLocalAdminClient) WriteForwarded(
  1267  	metric aggregated.ForwardedMetric,
  1268  	metadata metadata.ForwardMetadata,
  1269  ) error {
  1270  	return c.agg.AddForwarded(metric, metadata)
  1271  }
  1272  
  1273  // WritePassthrough writes passthrough metrics.
  1274  func (c *aggregatorLocalAdminClient) WritePassthrough(
  1275  	metric aggregated.Metric,
  1276  	storagePolicy policy.StoragePolicy,
  1277  ) error {
  1278  	return c.agg.AddPassthrough(metric, storagePolicy)
  1279  }
  1280  
  1281  // Flush flushes any remaining data buffered by the client.
  1282  func (c *aggregatorLocalAdminClient) Flush() error {
  1283  	return nil
  1284  }
  1285  
  1286  // Close closes the client.
  1287  func (c *aggregatorLocalAdminClient) Close() error {
  1288  	return nil
  1289  }
  1290  
  1291  type bufferPastLimit struct {
  1292  	upperBound time.Duration
  1293  	bufferPast time.Duration
  1294  }
  1295  
  1296  var defaultBufferPastLimits = []bufferPastLimit{
  1297  	{upperBound: 0, bufferPast: 15 * time.Second},
  1298  	{upperBound: 30 * time.Second, bufferPast: 30 * time.Second},
  1299  	{upperBound: time.Minute, bufferPast: time.Minute},
  1300  	{upperBound: 2 * time.Minute, bufferPast: 2 * time.Minute},
  1301  }
  1302  
  1303  func bufferForPastTimedMetric(
  1304  	limits []bufferPastLimit,
  1305  	tile time.Duration,
  1306  ) time.Duration {
  1307  	bufferPast := limits[0].bufferPast
  1308  	for _, limit := range limits {
  1309  		if tile < limit.upperBound {
  1310  			return bufferPast
  1311  		}
  1312  		bufferPast = limit.bufferPast
  1313  	}
  1314  	return bufferPast
  1315  }
  1316  
  1317  func maxAllowedForwardingDelay(
  1318  	limits []bufferPastLimit,
  1319  	tile time.Duration,
  1320  	numForwardedTimes int,
  1321  ) time.Duration {
  1322  	resolutionForwardDelay := tile * time.Duration(numForwardedTimes)
  1323  	bufferPast := limits[0].bufferPast
  1324  	for _, limit := range limits {
  1325  		if tile < limit.upperBound {
  1326  			return bufferPast + resolutionForwardDelay
  1327  		}
  1328  		bufferPast = limit.bufferPast
  1329  	}
  1330  	return bufferPast + resolutionForwardDelay
  1331  }