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 }