@@ -19,6 +19,7 @@ import (
1919 "errors"
2020 "fmt"
2121 "math"
22+ "slices"
2223 "strings"
2324 "sync"
2425 "time"
@@ -149,6 +150,13 @@ type Exporter struct {
149150 // Used to construct a new metric client when options change, or at initialization. It
150151 // is exposed as a variable so that unit tests may change the constructor.
151152 newMetricClient func (ctx context.Context , opts ExporterOpts ) (metricServiceClient , error )
153+
154+ // Currently applied matchers in seriesCache.matchers. Avoids locking for reads.
155+ matchers Matchers
156+
157+ // We allow runtime config path. For startup consistency make exporter noop until export
158+ // will see the first ApplyConfig.
159+ configured bool
152160}
153161
154162const (
@@ -206,7 +214,8 @@ type ExporterOpts struct {
206214 // A list of metric matchers. Only Prometheus time series satisfying at
207215 // least one of the matchers are exported.
208216 // This option matches the semantics of the Prometheus federation match[]
209- // parameter.
217+ // parameter. This is a setting from flags only, see ApplyConfig on
218+ // how it's applied dynamically in conjunction with Export.Match runtime option.
210219 Matchers Matchers
211220
212221 // Prefix under which metrics are written to GCM.
@@ -367,6 +376,7 @@ func defaultNewMetricClient(ctx context.Context, opts ExporterOpts) (metricServi
367376}
368377
369378// New returns a new Cloud Monitoring Exporter.
379+ // IMPORTANT: Created exporter is noop until ApplyConfig is called at least once. This is for configuration startup consistency.
370380func New (ctx context.Context , logger log.Logger , reg prometheus.Registerer , opts ExporterOpts , lease Lease ) (* Exporter , error ) {
371381 grpc_prometheus .EnableClientHandlingTimeHistogram (
372382 grpc_prometheus .WithHistogramBuckets ([]float64 {0.005 , 0.01 , 0.025 , 0.05 , 0.1 , 0.25 , 0.5 , 1 , 2.5 , 5 , 10 , 15 , 20 , 30 , 40 , 50 , 60 }),
@@ -409,7 +419,7 @@ func New(ctx context.Context, logger log.Logger, reg prometheus.Registerer, opts
409419 ctx : ctx ,
410420 opts : opts ,
411421 metricClient : metricClient ,
412- seriesCache : newSeriesCache (logger , reg , opts .MetricTypePrefix , opts . Matchers ),
422+ seriesCache : newSeriesCache (logger , reg , opts .MetricTypePrefix ),
413423 externalLabels : createLabelSet (& config.Config {}, & opts ),
414424 newMetricClient : defaultNewMetricClient ,
415425 nextc : make (chan struct {}, 1 ),
@@ -418,14 +428,17 @@ func New(ctx context.Context, logger log.Logger, reg prometheus.Registerer, opts
418428 lease : lease ,
419429 }
420430
431+ // Set initial matchers.
432+ e .matchers = opts .Matchers
433+ e .seriesCache .setMatchers (e .matchers )
434+
421435 // Whenever the lease is lost, clear the series cache so we don't start off of out-of-range
422436 // reset timestamps when we gain the lease again.
423437 lease .OnLeaderChange (e .seriesCache .clear )
424438
425439 for i := range e .shards {
426440 e .shards [i ] = newShard (opts .Efficiency .ShardBufferSize )
427441 }
428-
429442 return e , nil
430443}
431444
@@ -442,10 +455,12 @@ const (
442455// ApplyConfig updates the exporter state to the given configuration. For the
443456// Prometheus fork config's google_cloud entry, ExporterOpts might be
444457// changed and applied to the exporter, potentially recreating the metric client.
458+ // NOTE: Runtime configuration will not override disable/disableAuth options from flags.
445459func (e * Exporter ) ApplyConfig (cfg * config.Config ) (err error ) {
446460 // Note: We don't expect the NopExporter to call this. Only the config reloader calls it.
447461 e .mtx .Lock ()
448462 defer e .mtx .Unlock ()
463+ e .configured = true
449464
450465 // Don't recreate the metric client each time. If the metric client is recreated, it has to
451466 // potentially redo the TCP handshake. With HTTP/2, TCP connections are kept alive for a small
@@ -463,7 +478,6 @@ func (e *Exporter) ApplyConfig(cfg *config.Config) (err error) {
463478 e .opts .CredentialsFile = cfg .GoogleCloud .Export .CredentialsFile
464479 recreateClient = true
465480 }
466- // NOTE(bwplotka): Matchers are deprecated, see https://github.com/GoogleCloudPlatform/prometheus-engine/pull/1688
467481
468482 lset := createLabelSet (cfg , & e .opts )
469483 labelsChanged := ! labels .Equal (e .externalLabels , lset )
@@ -477,20 +491,41 @@ func (e *Exporter) ApplyConfig(cfg *config.Config) (err error) {
477491 }
478492 }
479493
480- // If changed, or we're calling this for the first time, we need to recreate the client.
494+ var toApply Matchers
495+ switch {
496+ case cfg .GoogleCloud .Export .EnableMatch == nil :
497+ // Ignore Export.Match, whatever was in flag or default wins.
498+ toApply = e .opts .Matchers
499+ case ! * cfg .GoogleCloud .Export .EnableMatch :
500+ // Explicit apply all matching (disabling the filtering).
501+ toApply = nil
502+ case * cfg .GoogleCloud .Export .EnableMatch :
503+ // Runtime Export.Match opted-in; override:
504+ for _ , match := range cfg .GoogleCloud .Export .Match {
505+ if err := toApply .Set (match ); err != nil {
506+ return err
507+ }
508+ }
509+ }
510+
481511 if recreateClient {
512+ // If changed, or we're calling this for the first time, we need to recreate the client.
482513 e .metricClient , err = e .newMetricClient (e .ctx , e .opts )
483514 if err != nil {
484515 return fmt .Errorf ("create metric client: %w" , err )
485516 }
486517 }
487518
519+ // Updates series cache settings; those require extra care on cache entries.
520+ if ! e .matchers .Equals (toApply ) {
521+ e .matchers = toApply
522+ e .seriesCache .setMatchers (e .matchers )
523+ }
488524 if labelsChanged {
489525 e .externalLabels = lset
490526 // New external labels possibly invalidate the cached series conversions.
491527 e .seriesCache .forceRefresh ()
492528 }
493-
494529 return nil
495530}
496531
@@ -557,13 +592,18 @@ func (e *Exporter) Export(metadata MetadataFunc, batch []record.RefSample, exemp
557592 return
558593 }
559594
560- metadata = e .wrapMetadata (metadata )
561-
562595 e .mtx .Lock ()
596+ if ! e .configured {
597+ // ApplyConfig must be called at least once, otherwise we risk a race between flag and runtime config change on start.
598+ e .mtx .Unlock ()
599+ return
600+ }
563601 externalLabels := e .externalLabels
564602 start , end , ok := e .lease .Range ()
565603 e .mtx .Unlock ()
566604
605+ metadata = e .wrapMetadata (metadata )
606+
567607 if ! ok {
568608 exemplarsDropped .WithLabelValues ("not-in-ha-range" ).Add (float64 (len (exemplarMap )))
569609 samplesDropped .WithLabelValues ("not-in-ha-range" ).Add (float64 (batchSize ))
@@ -1030,6 +1070,21 @@ func (m *Matchers) IsCumulative() bool {
10301070 return true
10311071}
10321072
1073+ // Equals returns true if m is the same as other matchers. Order matters.
1074+ //
1075+ // NOTE: In practice order semi-matter, as the match result should be similar, but
1076+ // things are faster or slower depending on the order. For efficiency of this function
1077+ // and deterministic efficiency of matching, we assume order matter.
1078+ func (m * Matchers ) Equals (other Matchers ) bool {
1079+ return slices .EqualFunc (* m , other , func (msel , osel labels.Selector ) bool {
1080+ return slices .EqualFunc (msel , osel , func (matcher , otherMatcher * labels.Matcher ) bool {
1081+ return matcher .Type == otherMatcher .Type &&
1082+ matcher .Name == otherMatcher .Name &&
1083+ matcher .Value == otherMatcher .Value
1084+ })
1085+ })
1086+ }
1087+
10331088func (m * Matchers ) Matches (lset labels.Labels ) bool {
10341089 if len (* m ) == 0 {
10351090 return true
0 commit comments