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

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package gate
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"github.com/prometheus/client_golang/prometheus/promauto"
    13  	promgate "github.com/prometheus/prometheus/util/gate"
    14  )
    15  
    16  // Gate controls the maximum number of concurrently running and waiting queries.
    17  //
    18  // Example of use:
    19  //
    20  //	g := gate.New(r, 5)
    21  //
    22  //	if err := g.Start(ctx); err != nil {
    23  //	   return
    24  //	}
    25  //	defer g.Done()
    26  type Gate interface {
    27  	// Start initiates a new request and waits until it's our turn to fulfill a request.
    28  	Start(ctx context.Context) error
    29  	// Done finishes a query.
    30  	Done()
    31  }
    32  
    33  // Keeper is used to create multiple gates sharing the same metrics.
    34  //
    35  // Deprecated: when Keeper is used to create several gates, the metric tracking
    36  // the number of in-flight metric isn't meaningful because it is hard to say
    37  // whether requests are being blocked or not. For clients that call
    38  // gate.(*Keeper).NewGate only once, it is recommended to use gate.New()
    39  // instead. Otherwise it is recommended to use the
    40  // github.com/prometheus/prometheus/util/gate package directly and wrap the
    41  // returned gate with gate.InstrumentGateDuration().
    42  type Keeper struct {
    43  	reg prometheus.Registerer
    44  }
    45  
    46  // NewKeeper creates a new Keeper.
    47  //
    48  // Deprecated: see Keeper.
    49  func NewKeeper(reg prometheus.Registerer) *Keeper {
    50  	return &Keeper{
    51  		reg: reg,
    52  	}
    53  }
    54  
    55  // NewGate returns a new Gate ready for use.
    56  //
    57  // Deprecated: see Keeper.
    58  func (k *Keeper) NewGate(maxConcurrent int) Gate {
    59  	return New(k.reg, maxConcurrent, Queries)
    60  }
    61  
    62  type OperationName string
    63  
    64  const (
    65  	Queries       OperationName = "queries"
    66  	Selects       OperationName = "selects"
    67  	Gets          OperationName = "gets"
    68  	Sets          OperationName = "sets"
    69  	WriteRequests OperationName = "write_requests"
    70  )
    71  
    72  type GateFactory interface {
    73  	New() Gate
    74  }
    75  
    76  type gateProducer struct {
    77  	reg           prometheus.Registerer
    78  	opName        OperationName
    79  	maxConcurrent int
    80  
    81  	durationHist prometheus.Histogram
    82  	total        prometheus.Counter
    83  	inflight     prometheus.Gauge
    84  }
    85  
    86  func (g *gateProducer) New() Gate {
    87  	var gate Gate
    88  	if g.maxConcurrent <= 0 {
    89  		gate = NewNoop()
    90  	} else {
    91  		gate = promgate.New(g.maxConcurrent)
    92  	}
    93  
    94  	return InstrumentGateDuration(
    95  		g.durationHist,
    96  		InstrumentGateTotal(
    97  			g.total,
    98  			InstrumentGateInFlight(
    99  				g.inflight,
   100  				gate,
   101  			),
   102  		),
   103  	)
   104  }
   105  
   106  var (
   107  	maxGaugeOpts = func(opName OperationName) prometheus.GaugeOpts {
   108  		return prometheus.GaugeOpts{
   109  			Name: fmt.Sprintf("gate_%s_max", opName),
   110  			Help: fmt.Sprintf("Maximum number of concurrent %s.", opName),
   111  		}
   112  	}
   113  	inFlightGaugeOpts = func(opName OperationName) prometheus.GaugeOpts {
   114  		return prometheus.GaugeOpts{
   115  			Name: fmt.Sprintf("gate_%s_in_flight", opName),
   116  			Help: fmt.Sprintf("Number of %s that are currently in flight.", opName),
   117  		}
   118  	}
   119  	totalCounterOpts = func(opName OperationName) prometheus.CounterOpts {
   120  		return prometheus.CounterOpts{
   121  			Name: fmt.Sprintf("gate_%s_total", opName),
   122  			Help: fmt.Sprintf("Total number of %s.", opName),
   123  		}
   124  	}
   125  	durationHistogramOpts = func(opName OperationName) prometheus.HistogramOpts {
   126  		return prometheus.HistogramOpts{
   127  			Name:    fmt.Sprintf("gate_%s_duration_seconds", opName),
   128  			Help:    fmt.Sprintf("How many seconds it took for %s to wait at the gate.", opName),
   129  			Buckets: []float64{0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120, 240, 360, 720},
   130  		}
   131  	}
   132  )
   133  
   134  // NewGateFactory creates a Gate factory. They act like Gate but each produced Gate
   135  // acts individually in terms of the limit and they have unified metrics.
   136  func NewGateFactory(reg prometheus.Registerer, maxConcurrent int, opName OperationName) GateFactory {
   137  	promauto.With(reg).NewGauge(maxGaugeOpts(opName)).Set(float64(maxConcurrent))
   138  
   139  	return &gateProducer{
   140  		reg:           reg,
   141  		opName:        opName,
   142  		maxConcurrent: maxConcurrent,
   143  		durationHist:  promauto.With(reg).NewHistogram(durationHistogramOpts(opName)),
   144  		total:         promauto.With(reg).NewCounter(totalCounterOpts(opName)),
   145  		inflight:      promauto.With(reg).NewGauge(inFlightGaugeOpts(opName)),
   146  	}
   147  }
   148  
   149  // New returns an instrumented gate limiting the number of requests being
   150  // executed concurrently.
   151  //
   152  // The gate implementation is based on the
   153  // github.com/prometheus/prometheus/util/gate package.
   154  //
   155  // It can be called several times but not with the same registerer otherwise it
   156  // will panic when trying to register the same metric multiple times.
   157  func New(reg prometheus.Registerer, maxConcurrent int, opName OperationName) Gate {
   158  	promauto.With(reg).NewGauge(maxGaugeOpts(opName)).Set(float64(maxConcurrent))
   159  
   160  	var gate Gate
   161  	if maxConcurrent <= 0 {
   162  		gate = NewNoop()
   163  	} else {
   164  		gate = promgate.New(maxConcurrent)
   165  	}
   166  
   167  	return InstrumentGateDuration(
   168  		promauto.With(reg).NewHistogram(durationHistogramOpts(opName)),
   169  		InstrumentGateTotal(
   170  			promauto.With(reg).NewCounter(totalCounterOpts(opName)),
   171  			InstrumentGateInFlight(
   172  				promauto.With(reg).NewGauge(inFlightGaugeOpts(opName)),
   173  				gate,
   174  			),
   175  		),
   176  	)
   177  }
   178  
   179  type noopGate struct{}
   180  
   181  func (noopGate) Start(context.Context) error { return nil }
   182  func (noopGate) Done()                       {}
   183  
   184  func NewNoop() Gate { return noopGate{} }
   185  
   186  type instrumentedDurationGate struct {
   187  	g        Gate
   188  	duration prometheus.Observer
   189  }
   190  
   191  // InstrumentGateDuration instruments the provided Gate to track how much time
   192  // the request has been waiting in the gate.
   193  func InstrumentGateDuration(duration prometheus.Observer, g Gate) Gate {
   194  	return &instrumentedDurationGate{
   195  		g:        g,
   196  		duration: duration,
   197  	}
   198  }
   199  
   200  // Start implements the Gate interface.
   201  func (g *instrumentedDurationGate) Start(ctx context.Context) error {
   202  	start := time.Now()
   203  	defer func() {
   204  		g.duration.Observe(time.Since(start).Seconds())
   205  	}()
   206  
   207  	return g.g.Start(ctx)
   208  }
   209  
   210  // Done implements the Gate interface.
   211  func (g *instrumentedDurationGate) Done() {
   212  	g.g.Done()
   213  }
   214  
   215  type instrumentedInFlightGate struct {
   216  	g        Gate
   217  	inflight prometheus.Gauge
   218  }
   219  
   220  // InstrumentGateInFlight instruments the provided Gate to track how many
   221  // requests are currently in flight.
   222  func InstrumentGateInFlight(inflight prometheus.Gauge, g Gate) Gate {
   223  	return &instrumentedInFlightGate{
   224  		g:        g,
   225  		inflight: inflight,
   226  	}
   227  }
   228  
   229  // Start implements the Gate interface.
   230  func (g *instrumentedInFlightGate) Start(ctx context.Context) error {
   231  	if err := g.g.Start(ctx); err != nil {
   232  		return err
   233  	}
   234  
   235  	g.inflight.Inc()
   236  	return nil
   237  }
   238  
   239  // Done implements the Gate interface.
   240  func (g *instrumentedInFlightGate) Done() {
   241  	g.inflight.Dec()
   242  	g.g.Done()
   243  }
   244  
   245  type instrumentedTotalGate struct {
   246  	g     Gate
   247  	total prometheus.Counter
   248  }
   249  
   250  // InstrumentGateTotal instruments the provided Gate to track total requests.
   251  func InstrumentGateTotal(total prometheus.Counter, g Gate) Gate {
   252  	return &instrumentedTotalGate{
   253  		g:     g,
   254  		total: total,
   255  	}
   256  }
   257  
   258  // Start implements the Gate interface.
   259  func (g *instrumentedTotalGate) Start(ctx context.Context) error {
   260  	g.total.Inc()
   261  	if err := g.g.Start(ctx); err != nil {
   262  		return err
   263  	}
   264  
   265  	return nil
   266  }
   267  
   268  // Done implements the Gate interface.
   269  func (g *instrumentedTotalGate) Done() {
   270  	g.g.Done()
   271  }