Line data Source code
1 : use std::sync::{Arc, OnceLock};
2 :
3 : use lasso::ThreadedRodeo;
4 : use measured::{
5 : label::{FixedCardinalitySet, LabelGroupSet, LabelName, LabelSet, LabelValue, StaticLabelSet},
6 : metric::{histogram::Thresholds, name::MetricName},
7 : Counter, CounterVec, FixedCardinalityLabel, Gauge, Histogram, HistogramVec, LabelGroup,
8 : MetricGroup,
9 : };
10 : use metrics::{CounterPairAssoc, CounterPairVec, HyperLogLog, HyperLogLogVec};
11 :
12 : use tokio::time::{self, Instant};
13 :
14 : use crate::console::messages::ColdStartInfo;
15 :
16 40 : #[derive(MetricGroup)]
17 : #[metric(new(thread_pool: Arc<ThreadPoolMetrics>))]
18 : pub struct Metrics {
19 : #[metric(namespace = "proxy")]
20 : #[metric(init = ProxyMetrics::new(thread_pool))]
21 : pub proxy: ProxyMetrics,
22 :
23 : #[metric(namespace = "wake_compute_lock")]
24 : pub wake_compute_lock: ApiLockMetrics,
25 : }
26 :
27 : static SELF: OnceLock<Metrics> = OnceLock::new();
28 : impl Metrics {
29 0 : pub fn install(thread_pool: Arc<ThreadPoolMetrics>) {
30 0 : SELF.set(Metrics::new(thread_pool))
31 0 : .ok()
32 0 : .expect("proxy metrics must not be installed more than once");
33 0 : }
34 :
35 135 : pub fn get() -> &'static Self {
36 135 : #[cfg(test)]
37 135 : return SELF.get_or_init(|| Metrics::new(Arc::new(ThreadPoolMetrics::new(0))));
38 135 :
39 135 : #[cfg(not(test))]
40 135 : SELF.get()
41 135 : .expect("proxy metrics must be installed by the main() function")
42 135 : }
43 : }
44 :
45 40 : #[derive(MetricGroup)]
46 : #[metric(new(thread_pool: Arc<ThreadPoolMetrics>))]
47 : pub struct ProxyMetrics {
48 : #[metric(flatten)]
49 : pub db_connections: CounterPairVec<NumDbConnectionsGauge>,
50 : #[metric(flatten)]
51 : pub client_connections: CounterPairVec<NumClientConnectionsGauge>,
52 : #[metric(flatten)]
53 : pub connection_requests: CounterPairVec<NumConnectionRequestsGauge>,
54 : #[metric(flatten)]
55 : pub http_endpoint_pools: HttpEndpointPools,
56 :
57 : /// Time it took for proxy to establish a connection to the compute endpoint.
58 : // largest bucket = 2^16 * 0.5ms = 32s
59 : #[metric(metadata = Thresholds::exponential_buckets(0.0005, 2.0))]
60 : pub compute_connection_latency_seconds: HistogramVec<ComputeConnectionLatencySet, 16>,
61 :
62 : /// Time it took for proxy to receive a response from control plane.
63 : #[metric(
64 : // largest bucket = 2^16 * 0.2ms = 13s
65 : metadata = Thresholds::exponential_buckets(0.0002, 2.0),
66 : )]
67 : pub console_request_latency: HistogramVec<ConsoleRequestSet, 16>,
68 :
69 : /// Time it takes to acquire a token to call console plane.
70 : // largest bucket = 3^16 * 0.05ms = 2.15s
71 : #[metric(metadata = Thresholds::exponential_buckets(0.00005, 3.0))]
72 : pub control_plane_token_acquire_seconds: Histogram<16>,
73 :
74 : /// Size of the HTTP request body lengths.
75 : // smallest bucket = 16 bytes
76 : // largest bucket = 4^12 * 16 bytes = 256MB
77 : #[metric(metadata = Thresholds::exponential_buckets(16.0, 4.0))]
78 : pub http_conn_content_length_bytes: HistogramVec<StaticLabelSet<HttpDirection>, 12>,
79 :
80 : /// Time it takes to reclaim unused connection pools.
81 : #[metric(metadata = Thresholds::exponential_buckets(1e-6, 2.0))]
82 : pub http_pool_reclaimation_lag_seconds: Histogram<16>,
83 :
84 : /// Number of opened connections to a database.
85 : pub http_pool_opened_connections: Gauge,
86 :
87 : /// Number of cache hits/misses for allowed ips.
88 : pub allowed_ips_cache_misses: CounterVec<StaticLabelSet<CacheOutcome>>,
89 :
90 : /// Number of allowed ips
91 : #[metric(metadata = Thresholds::with_buckets([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 10.0, 20.0, 50.0, 100.0]))]
92 : pub allowed_ips_number: Histogram<10>,
93 :
94 : /// Number of connections (per sni).
95 : pub accepted_connections_by_sni: CounterVec<StaticLabelSet<SniKind>>,
96 :
97 : /// Number of connection failures (per kind).
98 : pub connection_failures_total: CounterVec<StaticLabelSet<ConnectionFailureKind>>,
99 :
100 : /// Number of wake-up failures (per kind).
101 : pub connection_failures_breakdown: CounterVec<ConnectionFailuresBreakdownSet>,
102 :
103 : /// Number of bytes sent/received between all clients and backends.
104 : pub io_bytes: CounterVec<StaticLabelSet<Direction>>,
105 :
106 : /// Number of errors by a given classification.
107 : pub errors_total: CounterVec<StaticLabelSet<crate::error::ErrorKind>>,
108 :
109 : /// Number of cancellation requests (per found/not_found).
110 : pub cancellation_requests_total: CounterVec<CancellationRequestSet>,
111 :
112 : /// Number of errors by a given classification
113 : pub redis_errors_total: CounterVec<RedisErrorsSet>,
114 :
115 : /// Number of TLS handshake failures
116 : pub tls_handshake_failures: Counter,
117 :
118 : /// Number of connection requests affected by authentication rate limits
119 : pub requests_auth_rate_limits_total: Counter,
120 :
121 : /// HLL approximate cardinality of endpoints that are connecting
122 : pub connecting_endpoints: HyperLogLogVec<StaticLabelSet<Protocol>, 32>,
123 :
124 : /// Number of endpoints affected by errors of a given classification
125 : pub endpoints_affected_by_errors: HyperLogLogVec<StaticLabelSet<crate::error::ErrorKind>, 32>,
126 :
127 : /// Number of endpoints affected by authentication rate limits
128 : pub endpoints_auth_rate_limits: HyperLogLog<32>,
129 :
130 : /// Number of invalid endpoints (per protocol, per rejected).
131 : pub invalid_endpoints_total: CounterVec<InvalidEndpointsSet>,
132 :
133 : /// Number of retries (per outcome, per retry_type).
134 : #[metric(metadata = Thresholds::with_buckets([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]))]
135 : pub retries_metric: HistogramVec<RetriesMetricSet, 9>,
136 :
137 : /// Number of events consumed from redis (per event type).
138 : pub redis_events_count: CounterVec<StaticLabelSet<RedisEventsCount>>,
139 :
140 : #[metric(namespace = "connect_compute_lock")]
141 : pub connect_compute_lock: ApiLockMetrics,
142 :
143 : #[metric(namespace = "scram_pool")]
144 : #[metric(init = thread_pool)]
145 : pub scram_pool: Arc<ThreadPoolMetrics>,
146 : }
147 :
148 80 : #[derive(MetricGroup)]
149 : #[metric(new())]
150 : pub struct ApiLockMetrics {
151 : /// Number of semaphores registered in this api lock
152 : pub semaphores_registered: Counter,
153 : /// Number of semaphores unregistered in this api lock
154 : pub semaphores_unregistered: Counter,
155 : /// Time it takes to reclaim unused semaphores in the api lock
156 : #[metric(metadata = Thresholds::exponential_buckets(1e-6, 2.0))]
157 : pub reclamation_lag_seconds: Histogram<16>,
158 : /// Time it takes to acquire a semaphore lock
159 : #[metric(metadata = Thresholds::exponential_buckets(1e-4, 2.0))]
160 : pub semaphore_acquire_seconds: Histogram<16>,
161 : }
162 :
163 : impl Default for ApiLockMetrics {
164 80 : fn default() -> Self {
165 80 : Self::new()
166 80 : }
167 : }
168 :
169 0 : #[derive(FixedCardinalityLabel, Copy, Clone)]
170 : #[label(singleton = "direction")]
171 : pub enum HttpDirection {
172 : Request,
173 : Response,
174 : }
175 :
176 0 : #[derive(FixedCardinalityLabel, Copy, Clone)]
177 : #[label(singleton = "direction")]
178 : pub enum Direction {
179 : Tx,
180 : Rx,
181 : }
182 :
183 0 : #[derive(FixedCardinalityLabel, Clone, Copy, Debug)]
184 : #[label(singleton = "protocol")]
185 : pub enum Protocol {
186 : Http,
187 : Ws,
188 : Tcp,
189 : SniRouter,
190 : }
191 :
192 : impl Protocol {
193 0 : pub fn as_str(&self) -> &'static str {
194 0 : match self {
195 0 : Protocol::Http => "http",
196 0 : Protocol::Ws => "ws",
197 0 : Protocol::Tcp => "tcp",
198 0 : Protocol::SniRouter => "sni_router",
199 : }
200 0 : }
201 : }
202 :
203 : impl std::fmt::Display for Protocol {
204 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 0 : f.write_str(self.as_str())
206 0 : }
207 : }
208 :
209 : #[derive(FixedCardinalityLabel, Copy, Clone)]
210 : pub enum Bool {
211 : True,
212 : False,
213 : }
214 :
215 0 : #[derive(FixedCardinalityLabel, Copy, Clone)]
216 : #[label(singleton = "outcome")]
217 : pub enum Outcome {
218 : Success,
219 : Failed,
220 : }
221 :
222 0 : #[derive(FixedCardinalityLabel, Copy, Clone)]
223 : #[label(singleton = "outcome")]
224 : pub enum CacheOutcome {
225 : Hit,
226 : Miss,
227 : }
228 :
229 80 : #[derive(LabelGroup)]
230 : #[label(set = ConsoleRequestSet)]
231 : pub struct ConsoleRequest<'a> {
232 : #[label(dynamic_with = ThreadedRodeo, default)]
233 : pub request: &'a str,
234 : }
235 :
236 : #[derive(MetricGroup, Default)]
237 : pub struct HttpEndpointPools {
238 : /// Number of endpoints we have registered pools for
239 : pub http_pool_endpoints_registered_total: Counter,
240 : /// Number of endpoints we have unregistered pools for
241 : pub http_pool_endpoints_unregistered_total: Counter,
242 : }
243 :
244 : pub struct HttpEndpointPoolsGuard<'a> {
245 : dec: &'a Counter,
246 : }
247 :
248 : impl Drop for HttpEndpointPoolsGuard<'_> {
249 2 : fn drop(&mut self) {
250 2 : self.dec.inc();
251 2 : }
252 : }
253 :
254 : impl HttpEndpointPools {
255 2 : pub fn guard(&self) -> HttpEndpointPoolsGuard<'_> {
256 2 : self.http_pool_endpoints_registered_total.inc();
257 2 : HttpEndpointPoolsGuard {
258 2 : dec: &self.http_pool_endpoints_unregistered_total,
259 2 : }
260 2 : }
261 : }
262 : pub struct NumDbConnectionsGauge;
263 : impl CounterPairAssoc for NumDbConnectionsGauge {
264 : const INC_NAME: &'static MetricName = MetricName::from_str("opened_db_connections_total");
265 : const DEC_NAME: &'static MetricName = MetricName::from_str("closed_db_connections_total");
266 : const INC_HELP: &'static str = "Number of opened connections to a database.";
267 : const DEC_HELP: &'static str = "Number of closed connections to a database.";
268 : type LabelGroupSet = StaticLabelSet<Protocol>;
269 : }
270 : pub type NumDbConnectionsGuard<'a> = metrics::MeasuredCounterPairGuard<'a, NumDbConnectionsGauge>;
271 :
272 : pub struct NumClientConnectionsGauge;
273 : impl CounterPairAssoc for NumClientConnectionsGauge {
274 : const INC_NAME: &'static MetricName = MetricName::from_str("opened_client_connections_total");
275 : const DEC_NAME: &'static MetricName = MetricName::from_str("closed_client_connections_total");
276 : const INC_HELP: &'static str = "Number of opened connections from a client.";
277 : const DEC_HELP: &'static str = "Number of closed connections from a client.";
278 : type LabelGroupSet = StaticLabelSet<Protocol>;
279 : }
280 : pub type NumClientConnectionsGuard<'a> =
281 : metrics::MeasuredCounterPairGuard<'a, NumClientConnectionsGauge>;
282 :
283 : pub struct NumConnectionRequestsGauge;
284 : impl CounterPairAssoc for NumConnectionRequestsGauge {
285 : const INC_NAME: &'static MetricName = MetricName::from_str("accepted_connections_total");
286 : const DEC_NAME: &'static MetricName = MetricName::from_str("closed_connections_total");
287 : const INC_HELP: &'static str = "Number of client connections accepted.";
288 : const DEC_HELP: &'static str = "Number of client connections closed.";
289 : type LabelGroupSet = StaticLabelSet<Protocol>;
290 : }
291 : pub type NumConnectionRequestsGuard<'a> =
292 : metrics::MeasuredCounterPairGuard<'a, NumConnectionRequestsGauge>;
293 :
294 240 : #[derive(LabelGroup)]
295 : #[label(set = ComputeConnectionLatencySet)]
296 : pub struct ComputeConnectionLatencyGroup {
297 : protocol: Protocol,
298 : cold_start_info: ColdStartInfo,
299 : outcome: ConnectOutcome,
300 : excluded: LatencyExclusions,
301 : }
302 :
303 : #[derive(FixedCardinalityLabel, Copy, Clone)]
304 : pub enum LatencyExclusions {
305 : Client,
306 : ClientAndCplane,
307 : ClientCplaneCompute,
308 : ClientCplaneComputeRetry,
309 : }
310 :
311 0 : #[derive(FixedCardinalityLabel, Copy, Clone)]
312 : #[label(singleton = "kind")]
313 : pub enum SniKind {
314 : Sni,
315 : NoSni,
316 : PasswordHack,
317 : }
318 :
319 0 : #[derive(FixedCardinalityLabel, Copy, Clone)]
320 : #[label(singleton = "kind")]
321 : pub enum ConnectionFailureKind {
322 : ComputeCached,
323 : ComputeUncached,
324 : }
325 :
326 0 : #[derive(FixedCardinalityLabel, Copy, Clone)]
327 : #[label(singleton = "kind")]
328 : pub enum WakeupFailureKind {
329 : BadComputeAddress,
330 : ApiTransportError,
331 : QuotaExceeded,
332 : ApiConsoleLocked,
333 : ApiConsoleBadRequest,
334 : ApiConsoleOtherServerError,
335 : ApiConsoleOtherError,
336 : TimeoutError,
337 : }
338 :
339 160 : #[derive(LabelGroup)]
340 : #[label(set = ConnectionFailuresBreakdownSet)]
341 : pub struct ConnectionFailuresBreakdownGroup {
342 : pub kind: WakeupFailureKind,
343 : pub retry: Bool,
344 : }
345 :
346 80 : #[derive(LabelGroup, Copy, Clone)]
347 : #[label(set = RedisErrorsSet)]
348 : pub struct RedisErrors<'a> {
349 : #[label(dynamic_with = ThreadedRodeo, default)]
350 : pub channel: &'a str,
351 : }
352 :
353 : #[derive(FixedCardinalityLabel, Copy, Clone)]
354 : pub enum CancellationSource {
355 : FromClient,
356 : FromRedis,
357 : Local,
358 : }
359 :
360 : #[derive(FixedCardinalityLabel, Copy, Clone)]
361 : pub enum CancellationOutcome {
362 : NotFound,
363 : Found,
364 : }
365 :
366 160 : #[derive(LabelGroup)]
367 : #[label(set = CancellationRequestSet)]
368 : pub struct CancellationRequest {
369 : pub source: CancellationSource,
370 : pub kind: CancellationOutcome,
371 : }
372 :
373 : #[derive(Clone, Copy)]
374 : pub enum Waiting {
375 : Cplane,
376 : Client,
377 : Compute,
378 : RetryTimeout,
379 : }
380 :
381 : #[derive(Default)]
382 : struct Accumulated {
383 : cplane: time::Duration,
384 : client: time::Duration,
385 : compute: time::Duration,
386 : retry: time::Duration,
387 : }
388 :
389 : pub struct LatencyTimer {
390 : // time since the stopwatch was started
391 : start: time::Instant,
392 : // time since the stopwatch was stopped
393 : stop: Option<time::Instant>,
394 : // accumulated time on the stopwatch
395 : accumulated: Accumulated,
396 : // label data
397 : protocol: Protocol,
398 : cold_start_info: ColdStartInfo,
399 : outcome: ConnectOutcome,
400 : }
401 :
402 : impl LatencyTimer {
403 61 : pub fn new(protocol: Protocol) -> Self {
404 61 : Self {
405 61 : start: time::Instant::now(),
406 61 : stop: None,
407 61 : accumulated: Accumulated::default(),
408 61 : protocol,
409 61 : cold_start_info: ColdStartInfo::Unknown,
410 61 : // assume failed unless otherwise specified
411 61 : outcome: ConnectOutcome::Failed,
412 61 : }
413 61 : }
414 :
415 22 : pub fn unpause(&mut self, start: Instant, waiting_for: Waiting) {
416 22 : let dur = start.elapsed();
417 22 : match waiting_for {
418 0 : Waiting::Cplane => self.accumulated.cplane += dur,
419 15 : Waiting::Client => self.accumulated.client += dur,
420 1 : Waiting::Compute => self.accumulated.compute += dur,
421 6 : Waiting::RetryTimeout => self.accumulated.retry += dur,
422 : }
423 22 : }
424 :
425 0 : pub fn cold_start_info(&mut self, cold_start_info: ColdStartInfo) {
426 0 : self.cold_start_info = cold_start_info;
427 0 : }
428 :
429 4 : pub fn success(&mut self) {
430 4 : // stop the stopwatch and record the time that we have accumulated
431 4 : self.stop = Some(time::Instant::now());
432 4 :
433 4 : // success
434 4 : self.outcome = ConnectOutcome::Success;
435 4 : }
436 : }
437 :
438 : #[derive(FixedCardinalityLabel, Clone, Copy, Debug)]
439 : pub enum ConnectOutcome {
440 : Success,
441 : Failed,
442 : }
443 :
444 : impl Drop for LatencyTimer {
445 61 : fn drop(&mut self) {
446 61 : let duration = self
447 61 : .stop
448 61 : .unwrap_or_else(time::Instant::now)
449 61 : .duration_since(self.start);
450 61 :
451 61 : let metric = &Metrics::get().proxy.compute_connection_latency_seconds;
452 61 :
453 61 : // Excluding client communication from the accumulated time.
454 61 : metric.observe(
455 61 : ComputeConnectionLatencyGroup {
456 61 : protocol: self.protocol,
457 61 : cold_start_info: self.cold_start_info,
458 61 : outcome: self.outcome,
459 61 : excluded: LatencyExclusions::Client,
460 61 : },
461 61 : duration
462 61 : .saturating_sub(self.accumulated.client)
463 61 : .as_secs_f64(),
464 61 : );
465 61 :
466 61 : // Exclude client and cplane communication from the accumulated time.
467 61 : let accumulated_total = self.accumulated.client + self.accumulated.cplane;
468 61 : metric.observe(
469 61 : ComputeConnectionLatencyGroup {
470 61 : protocol: self.protocol,
471 61 : cold_start_info: self.cold_start_info,
472 61 : outcome: self.outcome,
473 61 : excluded: LatencyExclusions::ClientAndCplane,
474 61 : },
475 61 : duration.saturating_sub(accumulated_total).as_secs_f64(),
476 61 : );
477 61 :
478 61 : // Exclude client cplane, compue communication from the accumulated time.
479 61 : let accumulated_total =
480 61 : self.accumulated.client + self.accumulated.cplane + self.accumulated.compute;
481 61 : metric.observe(
482 61 : ComputeConnectionLatencyGroup {
483 61 : protocol: self.protocol,
484 61 : cold_start_info: self.cold_start_info,
485 61 : outcome: self.outcome,
486 61 : excluded: LatencyExclusions::ClientCplaneCompute,
487 61 : },
488 61 : duration.saturating_sub(accumulated_total).as_secs_f64(),
489 61 : );
490 61 :
491 61 : // Exclude client cplane, compue, retry communication from the accumulated time.
492 61 : let accumulated_total = self.accumulated.client
493 61 : + self.accumulated.cplane
494 61 : + self.accumulated.compute
495 61 : + self.accumulated.retry;
496 61 : metric.observe(
497 61 : ComputeConnectionLatencyGroup {
498 61 : protocol: self.protocol,
499 61 : cold_start_info: self.cold_start_info,
500 61 : outcome: self.outcome,
501 61 : excluded: LatencyExclusions::ClientCplaneComputeRetry,
502 61 : },
503 61 : duration.saturating_sub(accumulated_total).as_secs_f64(),
504 61 : );
505 61 : }
506 : }
507 :
508 : impl From<bool> for Bool {
509 3 : fn from(value: bool) -> Self {
510 3 : if value {
511 2 : Bool::True
512 : } else {
513 1 : Bool::False
514 : }
515 3 : }
516 : }
517 :
518 200 : #[derive(LabelGroup)]
519 : #[label(set = InvalidEndpointsSet)]
520 : pub struct InvalidEndpointsGroup {
521 : pub protocol: Protocol,
522 : pub rejected: Bool,
523 : pub outcome: ConnectOutcome,
524 : }
525 :
526 160 : #[derive(LabelGroup)]
527 : #[label(set = RetriesMetricSet)]
528 : pub struct RetriesMetricGroup {
529 : pub outcome: ConnectOutcome,
530 : pub retry_type: RetryType,
531 : }
532 :
533 : #[derive(FixedCardinalityLabel, Clone, Copy, Debug)]
534 : pub enum RetryType {
535 : WakeCompute,
536 : ConnectToCompute,
537 : }
538 :
539 0 : #[derive(FixedCardinalityLabel, Clone, Copy, Debug)]
540 : #[label(singleton = "event")]
541 : pub enum RedisEventsCount {
542 : EndpointCreated,
543 : BranchCreated,
544 : ProjectCreated,
545 : CancelSession,
546 : PasswordUpdate,
547 : AllowedIpsUpdate,
548 : }
549 :
550 : pub struct ThreadPoolWorkers(usize);
551 : #[derive(Copy, Clone)]
552 : pub struct ThreadPoolWorkerId(pub usize);
553 :
554 : impl LabelValue for ThreadPoolWorkerId {
555 0 : fn visit<V: measured::label::LabelVisitor>(&self, v: V) -> V::Output {
556 0 : v.write_int(self.0 as i64)
557 0 : }
558 : }
559 :
560 : impl LabelGroup for ThreadPoolWorkerId {
561 0 : fn visit_values(&self, v: &mut impl measured::label::LabelGroupVisitor) {
562 0 : v.write_value(LabelName::from_str("worker"), self);
563 0 : }
564 : }
565 :
566 : impl LabelGroupSet for ThreadPoolWorkers {
567 : type Group<'a> = ThreadPoolWorkerId;
568 :
569 92 : fn cardinality(&self) -> Option<usize> {
570 92 : Some(self.0)
571 92 : }
572 :
573 8 : fn encode_dense(&self, value: Self::Unique) -> Option<usize> {
574 8 : Some(value)
575 8 : }
576 :
577 0 : fn decode_dense(&self, value: usize) -> Self::Group<'_> {
578 0 : ThreadPoolWorkerId(value)
579 0 : }
580 :
581 : type Unique = usize;
582 :
583 8 : fn encode(&self, value: Self::Group<'_>) -> Option<Self::Unique> {
584 8 : Some(value.0)
585 8 : }
586 :
587 0 : fn decode(&self, value: &Self::Unique) -> Self::Group<'_> {
588 0 : ThreadPoolWorkerId(*value)
589 0 : }
590 : }
591 :
592 : impl LabelSet for ThreadPoolWorkers {
593 : type Value<'a> = ThreadPoolWorkerId;
594 :
595 0 : fn dynamic_cardinality(&self) -> Option<usize> {
596 0 : Some(self.0)
597 0 : }
598 :
599 0 : fn encode(&self, value: Self::Value<'_>) -> Option<usize> {
600 0 : (value.0 < self.0).then_some(value.0)
601 0 : }
602 :
603 0 : fn decode(&self, value: usize) -> Self::Value<'_> {
604 0 : ThreadPoolWorkerId(value)
605 0 : }
606 : }
607 :
608 : impl FixedCardinalitySet for ThreadPoolWorkers {
609 0 : fn cardinality(&self) -> usize {
610 0 : self.0
611 0 : }
612 : }
613 :
614 46 : #[derive(MetricGroup)]
615 : #[metric(new(workers: usize))]
616 : pub struct ThreadPoolMetrics {
617 : #[metric(init = CounterVec::with_label_set(ThreadPoolWorkers(workers)))]
618 : pub worker_task_turns_total: CounterVec<ThreadPoolWorkers>,
619 : #[metric(init = CounterVec::with_label_set(ThreadPoolWorkers(workers)))]
620 : pub worker_task_skips_total: CounterVec<ThreadPoolWorkers>,
621 : }
|