github.com/thanos-io/thanos@v0.32.5/pkg/receive/request_limiter.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package receive
     5  
     6  import (
     7  	"github.com/prometheus/client_golang/prometheus"
     8  	"github.com/prometheus/client_golang/prometheus/promauto"
     9  )
    10  
    11  const (
    12  	seriesLimitName    = "series"
    13  	samplesLimitName   = "samples"
    14  	sizeBytesLimitName = "body_size"
    15  )
    16  
    17  var unlimitedRequestLimitsConfig = NewEmptyRequestLimitsConfig().
    18  	SetSizeBytesLimit(0).
    19  	SetSeriesLimit(0).
    20  	SetSamplesLimit(0)
    21  
    22  // configRequestLimiter implements requestLimiter interface.
    23  type configRequestLimiter struct {
    24  	tenantLimits        map[string]*requestLimitsConfig
    25  	cachedDefaultLimits *requestLimitsConfig
    26  	limitsHit           *prometheus.SummaryVec
    27  	configuredLimits    *prometheus.GaugeVec
    28  }
    29  
    30  func newConfigRequestLimiter(reg prometheus.Registerer, writeLimits *WriteLimitsConfig) *configRequestLimiter {
    31  	// Merge the default limits configuration with an unlimited configuration
    32  	// to ensure the nils are overwritten with zeroes.
    33  	defaultRequestLimits := writeLimits.DefaultLimits.RequestLimits.OverlayWith(unlimitedRequestLimitsConfig)
    34  
    35  	// Load up the request limits into a map with the tenant name as key and
    36  	// merge with the defaults to provide easy and fast access when checking
    37  	// limits.
    38  	// The merge with the default happen because a tenant limit that isn't
    39  	// present means the value is inherited from the default configuration.
    40  	tenantsLimits := writeLimits.TenantsLimits
    41  	tenantRequestLimits := make(map[string]*requestLimitsConfig)
    42  	for tenant, limitConfig := range tenantsLimits {
    43  		if limitConfig.RequestLimits != nil {
    44  			tenantRequestLimits[tenant] = limitConfig.RequestLimits.OverlayWith(defaultRequestLimits)
    45  		}
    46  	}
    47  
    48  	limiter := configRequestLimiter{
    49  		tenantLimits:        tenantRequestLimits,
    50  		cachedDefaultLimits: defaultRequestLimits,
    51  	}
    52  	limiter.registerMetrics(reg)
    53  	return &limiter
    54  }
    55  
    56  func (l *configRequestLimiter) registerMetrics(reg prometheus.Registerer) {
    57  	l.limitsHit = promauto.With(reg).NewSummaryVec(
    58  		prometheus.SummaryOpts{
    59  			Namespace:  "thanos",
    60  			Subsystem:  "receive",
    61  			Name:       "write_limits_hit",
    62  			Help:       "Summary of how much beyond the limit a refused remote write request was.",
    63  			Objectives: map[float64]float64{0.50: 0.1, 0.95: 0.1, 0.99: 0.001},
    64  		}, []string{"tenant", "limit"},
    65  	)
    66  	l.configuredLimits = promauto.With(reg).NewGaugeVec(
    67  		prometheus.GaugeOpts{
    68  			Namespace: "thanos",
    69  			Subsystem: "receive",
    70  			Name:      "write_limits",
    71  			Help:      "The configured write limits.",
    72  		}, []string{"tenant", "limit"},
    73  	)
    74  	for tenant, limits := range l.tenantLimits {
    75  		l.configuredLimits.WithLabelValues(tenant, sizeBytesLimitName).Set(float64(*limits.SizeBytesLimit))
    76  		l.configuredLimits.WithLabelValues(tenant, seriesLimitName).Set(float64(*limits.SeriesLimit))
    77  		l.configuredLimits.WithLabelValues(tenant, samplesLimitName).Set(float64(*limits.SamplesLimit))
    78  	}
    79  	l.configuredLimits.WithLabelValues("", sizeBytesLimitName).Set(float64(*l.cachedDefaultLimits.SizeBytesLimit))
    80  	l.configuredLimits.WithLabelValues("", seriesLimitName).Set(float64(*l.cachedDefaultLimits.SeriesLimit))
    81  	l.configuredLimits.WithLabelValues("", samplesLimitName).Set(float64(*l.cachedDefaultLimits.SamplesLimit))
    82  }
    83  
    84  func (l *configRequestLimiter) AllowSizeBytes(tenant string, contentLengthBytes int64) bool {
    85  	limit := l.limitsFor(tenant).SizeBytesLimit
    86  	if *limit <= 0 {
    87  		return true
    88  	}
    89  
    90  	allowed := *limit >= contentLengthBytes
    91  	if !allowed {
    92  		l.limitsHit.
    93  			WithLabelValues(tenant, sizeBytesLimitName).
    94  			Observe(float64(contentLengthBytes - *limit))
    95  	}
    96  	return allowed
    97  }
    98  
    99  func (l *configRequestLimiter) AllowSeries(tenant string, amount int64) bool {
   100  	limit := l.limitsFor(tenant).SeriesLimit
   101  	if *limit <= 0 {
   102  		return true
   103  	}
   104  
   105  	allowed := *limit >= amount
   106  	if !allowed && l.limitsHit != nil {
   107  		l.limitsHit.
   108  			WithLabelValues(tenant, seriesLimitName).
   109  			Observe(float64(amount - *limit))
   110  	}
   111  	return allowed
   112  }
   113  
   114  func (l *configRequestLimiter) AllowSamples(tenant string, amount int64) bool {
   115  	limit := l.limitsFor(tenant).SamplesLimit
   116  	if *limit <= 0 {
   117  		return true
   118  	}
   119  	allowed := *limit >= amount
   120  	if !allowed && l.limitsHit != nil {
   121  		l.limitsHit.
   122  			WithLabelValues(tenant, samplesLimitName).
   123  			Observe(float64(amount - *limit))
   124  	}
   125  	return allowed
   126  }
   127  
   128  func (l *configRequestLimiter) limitsFor(tenant string) *requestLimitsConfig {
   129  	limits, ok := l.tenantLimits[tenant]
   130  	if !ok {
   131  		limits = l.cachedDefaultLimits
   132  	}
   133  	return limits
   134  }
   135  
   136  type noopRequestLimiter struct{}
   137  
   138  func (l *noopRequestLimiter) AllowSizeBytes(tenant string, contentLengthBytes int64) bool {
   139  	return true
   140  }
   141  
   142  func (l *noopRequestLimiter) AllowSeries(tenant string, amount int64) bool {
   143  	return true
   144  }
   145  
   146  func (l *noopRequestLimiter) AllowSamples(tenant string, amount int64) bool {
   147  	return true
   148  }