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