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 }