@@ -12,7 +12,10 @@ import (
1212 "strings"
1313
1414 "github.com/gogo/protobuf/types"
15+ cache "github.com/hashicorp/golang-lru/v2"
1516 "github.com/pkg/errors"
17+ "github.com/prometheus/client_golang/prometheus"
18+ "github.com/prometheus/client_golang/prometheus/promauto"
1619 "github.com/prometheus/prometheus/model/labels"
1720 "google.golang.org/grpc/codes"
1821
@@ -381,34 +384,120 @@ func PromMatchersToMatchers(ms ...*labels.Matcher) ([]LabelMatcher, error) {
381384 return res , nil
382385}
383386
387+ func matcherToPromMatcher (m LabelMatcher ) (* labels.Matcher , error ) {
388+ var t labels.MatchType
389+
390+ switch m .Type {
391+ case LabelMatcher_EQ :
392+ t = labels .MatchEqual
393+ case LabelMatcher_NEQ :
394+ t = labels .MatchNotEqual
395+ case LabelMatcher_RE :
396+ t = labels .MatchRegexp
397+ case LabelMatcher_NRE :
398+ t = labels .MatchNotRegexp
399+ default :
400+ return nil , errors .Errorf ("unrecognized label matcher type %d" , m .Type )
401+ }
402+ pm , err := labels .NewMatcher (t , m .Name , m .Value )
403+ if err != nil {
404+ return nil , err
405+ }
406+ return pm , nil
407+ }
408+
384409// MatchersToPromMatchers returns Prometheus matchers from proto matchers.
385410// NOTE: It allocates memory.
386411func MatchersToPromMatchers (ms ... LabelMatcher ) ([]* labels.Matcher , error ) {
387412 res := make ([]* labels.Matcher , 0 , len (ms ))
388413 for _ , m := range ms {
389- var t labels.MatchType
414+ m , err := matcherToPromMatcher (m )
415+ if err != nil {
416+ return nil , err
417+ }
418+ res = append (res , m )
419+ }
420+ return res , nil
421+ }
390422
391- switch m .Type {
392- case LabelMatcher_EQ :
393- t = labels .MatchEqual
394- case LabelMatcher_NEQ :
395- t = labels .MatchNotEqual
396- case LabelMatcher_RE :
397- t = labels .MatchRegexp
398- case LabelMatcher_NRE :
399- t = labels .MatchNotRegexp
400- default :
401- return nil , errors .Errorf ("unrecognized label matcher type %d" , m .Type )
423+ type MatcherConverter struct {
424+ cache * cache.TwoQueueCache [LabelMatcher , * labels.Matcher ]
425+ cacheCapacity int
426+ metrics * matcherConverterMetrics
427+ }
428+
429+ type matcherConverterMetrics struct {
430+ cacheTotalCount prometheus.Counter
431+ cacheHitCount prometheus.Counter
432+ cacheSizeGauge prometheus.Gauge
433+ }
434+
435+ func newMatcherConverterMetrics (reg prometheus.Registerer ) * matcherConverterMetrics {
436+ var m matcherConverterMetrics
437+
438+ m .cacheTotalCount = promauto .With (reg ).NewCounter (prometheus.CounterOpts {
439+ Name : "thanos_store_matcher_converter_cache_total" ,
440+ Help : "Total number of cache access." ,
441+ })
442+ m .cacheHitCount = promauto .With (reg ).NewCounter (prometheus.CounterOpts {
443+ Name : "thanos_store_matcher_converter_cache_hit_total" ,
444+ Help : "Total number of cache hits." ,
445+ })
446+ m .cacheSizeGauge = promauto .With (reg ).NewGauge (prometheus.GaugeOpts {
447+ Name : "thanos_store_matcher_converter_cache_size" ,
448+ Help : "Current size of the cache." ,
449+ })
450+
451+ return & m
452+ }
453+
454+ // NewMatcherConverter creates a new MatcherConverter with given capacity.
455+ func NewMatcherConverter (cacheCapacity int , reg prometheus.Registerer ) (* MatcherConverter , error ) {
456+ c , err := cache.New2Q [LabelMatcher , * labels.Matcher ](cacheCapacity )
457+ if err != nil {
458+ return nil , err
459+ }
460+ metrics := newMatcherConverterMetrics (reg )
461+ return & MatcherConverter {cache : c , cacheCapacity : cacheCapacity , metrics : metrics }, nil
462+ }
463+
464+ // MatchersToPromMatchers converts proto label matchers to Prometheus label matchers. It caches regex conversions.
465+ func (c * MatcherConverter ) MatchersToPromMatchers (ms ... LabelMatcher ) ([]* labels.Matcher , error ) {
466+ res := make ([]* labels.Matcher , 0 , len (ms ))
467+ for _ , m := range ms {
468+ if m .Type != LabelMatcher_RE && m .Type != LabelMatcher_NRE {
469+ // EQ and NEQ are very cheap, so we don't cache them.
470+ pm , err := matcherToPromMatcher (m )
471+ if err != nil {
472+ return nil , err
473+ }
474+ res = append (res , pm )
475+ continue
402476 }
403- m , err := labels .NewMatcher (t , m .Name , m .Value )
477+ c .metrics .cacheTotalCount .Inc ()
478+ if pm , ok := c .cache .Get (m ); ok {
479+ // cache hit
480+ c .metrics .cacheHitCount .Inc ()
481+ res = append (res , pm )
482+ continue
483+ }
484+ // cache miss
485+ pm , err := matcherToPromMatcher (m )
404486 if err != nil {
405487 return nil , err
406488 }
407- res = append (res , m )
489+ c .cache .Add (m , pm )
490+ res = append (res , pm )
408491 }
492+ c .metrics .cacheSizeGauge .Set (float64 (c .cache .Len ()))
409493 return res , nil
410494}
411495
496+ // Get all keys from the cache for debugging.
497+ func (c * MatcherConverter ) Keys () []LabelMatcher {
498+ return c .cache .Keys ()
499+ }
500+
412501// MatchersToString converts label matchers to string format.
413502// String should be parsable as a valid PromQL query metric selector.
414503func MatchersToString (ms ... LabelMatcher ) string {
0 commit comments