github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ingester/user_metrics_metadata.go (about)

     1  package ingester
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/prometheus/prometheus/pkg/labels"
     8  
     9  	"github.com/cortexproject/cortex/pkg/cortexpb"
    10  	"github.com/cortexproject/cortex/pkg/util/validation"
    11  )
    12  
    13  // userMetricsMetadata allows metric metadata of a tenant to be held by the ingester.
    14  // Metadata is kept as a set as it can come from multiple targets that Prometheus scrapes
    15  // with the same metric name.
    16  type userMetricsMetadata struct {
    17  	limiter *Limiter
    18  	metrics *ingesterMetrics
    19  	userID  string
    20  
    21  	mtx              sync.RWMutex
    22  	metricToMetadata map[string]metricMetadataSet
    23  }
    24  
    25  func newMetadataMap(l *Limiter, m *ingesterMetrics, userID string) *userMetricsMetadata {
    26  	return &userMetricsMetadata{
    27  		metricToMetadata: map[string]metricMetadataSet{},
    28  		limiter:          l,
    29  		metrics:          m,
    30  		userID:           userID,
    31  	}
    32  }
    33  
    34  func (mm *userMetricsMetadata) add(metric string, metadata *cortexpb.MetricMetadata) error {
    35  	mm.mtx.Lock()
    36  	defer mm.mtx.Unlock()
    37  
    38  	// As we get the set, we also validate two things:
    39  	// 1. The user is allowed to create new metrics to add metadata to.
    40  	// 2. If the metadata set is already present, it hasn't reached the limit of metadata we can append.
    41  	set, ok := mm.metricToMetadata[metric]
    42  	if !ok {
    43  		// Verify that the user can create more metric metadata given we don't have a set for that metric name.
    44  		if err := mm.limiter.AssertMaxMetricsWithMetadataPerUser(mm.userID, len(mm.metricToMetadata)); err != nil {
    45  			validation.DiscardedMetadata.WithLabelValues(mm.userID, perUserMetadataLimit).Inc()
    46  			return makeLimitError(perUserMetadataLimit, mm.limiter.FormatError(mm.userID, err))
    47  		}
    48  		set = metricMetadataSet{}
    49  		mm.metricToMetadata[metric] = set
    50  	}
    51  
    52  	if err := mm.limiter.AssertMaxMetadataPerMetric(mm.userID, len(set)); err != nil {
    53  		validation.DiscardedMetadata.WithLabelValues(mm.userID, perMetricMetadataLimit).Inc()
    54  		return makeMetricLimitError(perMetricMetadataLimit, labels.FromStrings(labels.MetricName, metric), mm.limiter.FormatError(mm.userID, err))
    55  	}
    56  
    57  	// if we have seen this metadata before, it is a no-op and we don't need to change our metrics.
    58  	_, ok = set[*metadata]
    59  	if !ok {
    60  		mm.metrics.memMetadata.Inc()
    61  		mm.metrics.memMetadataCreatedTotal.WithLabelValues(mm.userID).Inc()
    62  	}
    63  
    64  	mm.metricToMetadata[metric][*metadata] = time.Now()
    65  	return nil
    66  }
    67  
    68  // If deadline is zero, all metadata is purged.
    69  func (mm *userMetricsMetadata) purge(deadline time.Time) {
    70  	mm.mtx.Lock()
    71  	defer mm.mtx.Unlock()
    72  	var deleted int
    73  	for m, s := range mm.metricToMetadata {
    74  		deleted += s.purge(deadline)
    75  
    76  		if len(s) <= 0 {
    77  			delete(mm.metricToMetadata, m)
    78  		}
    79  	}
    80  
    81  	mm.metrics.memMetadata.Sub(float64(deleted))
    82  	mm.metrics.memMetadataRemovedTotal.WithLabelValues(mm.userID).Add(float64(deleted))
    83  }
    84  
    85  func (mm *userMetricsMetadata) toClientMetadata() []*cortexpb.MetricMetadata {
    86  	mm.mtx.RLock()
    87  	defer mm.mtx.RUnlock()
    88  	r := make([]*cortexpb.MetricMetadata, 0, len(mm.metricToMetadata))
    89  	for _, set := range mm.metricToMetadata {
    90  		for m := range set {
    91  			r = append(r, &m)
    92  		}
    93  	}
    94  	return r
    95  }
    96  
    97  type metricMetadataSet map[cortexpb.MetricMetadata]time.Time
    98  
    99  // If deadline is zero time, all metrics are purged.
   100  func (mms metricMetadataSet) purge(deadline time.Time) int {
   101  	var deleted int
   102  	for metadata, t := range mms {
   103  		if deadline.IsZero() || deadline.After(t) {
   104  			delete(mms, metadata)
   105  			deleted++
   106  		}
   107  	}
   108  
   109  	return deleted
   110  }