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 }