1use crate::observe::Event;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum MetricKind {
16 Request,
18 Miss,
20 Disabled,
22 Error,
24 Invalidation,
26 Coalesced,
28 ShadowValidation,
30 ShadowValueAge,
32 ShadowMismatchLogging,
34 FutureTimestampOffset,
37 StaleRecovery,
39 StaleRecoveryValueAge,
42 Compression,
44 Get,
46 Fallback,
48 Serialization,
50 Size,
52 StoredSize,
54 CompressionRatio,
56 CompressionDuration,
58}
59
60const LABEL_CACHE_NAMESPACE: &str = "cache_namespace";
62const LABEL_USE_CASE: &str = "use_case";
63const LABEL_KEY_TYPE: &str = "key_type";
64
65impl MetricKind {
66 pub const ALL: [MetricKind; 20] = [
68 MetricKind::Request,
69 MetricKind::Miss,
70 MetricKind::Disabled,
71 MetricKind::Error,
72 MetricKind::Invalidation,
73 MetricKind::Coalesced,
74 MetricKind::ShadowValidation,
75 MetricKind::ShadowValueAge,
76 MetricKind::ShadowMismatchLogging,
77 MetricKind::FutureTimestampOffset,
78 MetricKind::StaleRecovery,
79 MetricKind::StaleRecoveryValueAge,
80 MetricKind::Compression,
81 MetricKind::Get,
82 MetricKind::Fallback,
83 MetricKind::Serialization,
84 MetricKind::Size,
85 MetricKind::StoredSize,
86 MetricKind::CompressionRatio,
87 MetricKind::CompressionDuration,
88 ];
89
90 pub fn of(event: &Event) -> MetricKind {
92 match event {
93 Event::Request { .. } => MetricKind::Request,
94 Event::Miss { .. } => MetricKind::Miss,
95 Event::Disabled { .. } => MetricKind::Disabled,
96 Event::Error { .. } => MetricKind::Error,
97 Event::Invalidation { .. } => MetricKind::Invalidation,
98 Event::Coalesced { .. } => MetricKind::Coalesced,
99 Event::ShadowValidation { .. } => MetricKind::ShadowValidation,
100 Event::ShadowValueAge { .. } => MetricKind::ShadowValueAge,
101 Event::ShadowMismatchLogging { .. } => MetricKind::ShadowMismatchLogging,
102 Event::FutureTimestampOffset { .. } => MetricKind::FutureTimestampOffset,
103 Event::StaleRecovery { .. } => MetricKind::StaleRecovery,
104 Event::StaleRecoveryValueAge { .. } => MetricKind::StaleRecoveryValueAge,
105 Event::Compression { .. } => MetricKind::Compression,
106 Event::Get { .. } => MetricKind::Get,
107 Event::Fallback { .. } => MetricKind::Fallback,
108 Event::Serialization { .. } => MetricKind::Serialization,
109 Event::Size { .. } => MetricKind::Size,
110 Event::StoredSize { .. } => MetricKind::StoredSize,
111 Event::CompressionRatio { .. } => MetricKind::CompressionRatio,
112 Event::CompressionDuration { .. } => MetricKind::CompressionDuration,
113 }
114 }
115
116 pub fn as_str(self) -> &'static str {
118 match self {
119 MetricKind::Request => "request",
120 MetricKind::Miss => "miss",
121 MetricKind::Disabled => "disabled",
122 MetricKind::Error => "error",
123 MetricKind::Invalidation => "invalidation",
124 MetricKind::Coalesced => "coalesced",
125 MetricKind::ShadowValidation => "shadowValidation",
126 MetricKind::ShadowValueAge => "shadowValueAge",
127 MetricKind::ShadowMismatchLogging => "shadowMismatchLogging",
128 MetricKind::FutureTimestampOffset => "futureTimestampOffset",
129 MetricKind::StaleRecovery => "staleRecovery",
130 MetricKind::StaleRecoveryValueAge => "staleRecoveryValueAge",
131 MetricKind::Compression => "compression",
132 MetricKind::Get => "get",
133 MetricKind::Fallback => "fallback",
134 MetricKind::Serialization => "serialization",
135 MetricKind::Size => "size",
136 MetricKind::StoredSize => "storedSize",
137 MetricKind::CompressionRatio => "compressionRatio",
138 MetricKind::CompressionDuration => "compressionDuration",
139 }
140 }
141
142 pub fn is_counter(self) -> bool {
145 matches!(
146 self,
147 MetricKind::Request
148 | MetricKind::Miss
149 | MetricKind::Disabled
150 | MetricKind::Error
151 | MetricKind::Invalidation
152 | MetricKind::Coalesced
153 | MetricKind::ShadowValidation
154 | MetricKind::ShadowMismatchLogging
155 | MetricKind::StaleRecovery
156 | MetricKind::Compression
157 )
158 }
159
160 pub fn index(self) -> usize {
162 self as usize
163 }
164
165 pub fn label_names(self) -> &'static [&'static str] {
168 const BASE: &[&str] = &[
169 LABEL_CACHE_NAMESPACE,
170 LABEL_USE_CASE,
171 LABEL_KEY_TYPE,
172 "layer",
173 ];
174 const OUTCOME: &[&str] = &[
175 LABEL_CACHE_NAMESPACE,
176 LABEL_USE_CASE,
177 LABEL_KEY_TYPE,
178 "outcome",
179 ];
180 match self {
181 MetricKind::Request
182 | MetricKind::FutureTimestampOffset
183 | MetricKind::Get
184 | MetricKind::Fallback
185 | MetricKind::Size
186 | MetricKind::StoredSize
187 | MetricKind::CompressionRatio => BASE,
188 MetricKind::Miss | MetricKind::Disabled => &[
189 LABEL_CACHE_NAMESPACE,
190 LABEL_USE_CASE,
191 LABEL_KEY_TYPE,
192 "layer",
193 "reason",
194 ],
195 MetricKind::Error => &[
196 LABEL_CACHE_NAMESPACE,
197 LABEL_USE_CASE,
198 LABEL_KEY_TYPE,
199 "layer",
200 "error",
201 "in_fallback",
202 ],
203 MetricKind::Invalidation => &[LABEL_CACHE_NAMESPACE, LABEL_KEY_TYPE, "layer"],
204 MetricKind::Coalesced => &[
205 LABEL_CACHE_NAMESPACE,
206 LABEL_USE_CASE,
207 LABEL_KEY_TYPE,
208 "scope",
209 ],
210 MetricKind::ShadowValidation
211 | MetricKind::ShadowValueAge
212 | MetricKind::ShadowMismatchLogging
213 | MetricKind::StaleRecovery
214 | MetricKind::StaleRecoveryValueAge => OUTCOME,
215 MetricKind::Compression => &[
216 LABEL_CACHE_NAMESPACE,
217 LABEL_USE_CASE,
218 LABEL_KEY_TYPE,
219 "layer",
220 "outcome",
221 ],
222 MetricKind::Serialization | MetricKind::CompressionDuration => &[
223 LABEL_CACHE_NAMESPACE,
224 LABEL_USE_CASE,
225 LABEL_KEY_TYPE,
226 "layer",
227 "operation",
228 ],
229 }
230 }
231
232 pub fn labels(event: &Event) -> Vec<(&'static str, String)> {
236 let mut labels: Vec<(&'static str, String)> = Vec::with_capacity(6);
237 match event {
238 Event::Invalidation {
239 namespace,
240 key_type,
241 layer,
242 } => {
243 labels.push((LABEL_CACHE_NAMESPACE, namespace.to_string()));
244 labels.push((LABEL_KEY_TYPE, key_type.to_string()));
245 labels.push(("layer", layer.as_str().to_string()));
246 }
247 Event::Coalesced {
248 labels: outcome,
249 scope,
250 } => {
251 push_outcome_base(&mut labels, outcome);
252 labels.push(("scope", scope.as_str().to_string()));
253 }
254 Event::ShadowValidation {
255 labels: outcome,
256 outcome: verdict,
257 }
258 | Event::ShadowValueAge {
259 labels: outcome,
260 outcome: verdict,
261 ..
262 } => {
263 push_outcome_base(&mut labels, outcome);
264 labels.push(("outcome", verdict.as_str().to_string()));
265 }
266 Event::ShadowMismatchLogging {
267 labels: outcome,
268 outcome: logging,
269 } => {
270 push_outcome_base(&mut labels, outcome);
271 labels.push(("outcome", logging.as_str().to_string()));
272 }
273 Event::StaleRecovery {
274 labels: outcome,
275 outcome: verdict,
276 }
277 | Event::StaleRecoveryValueAge {
278 labels: outcome,
279 outcome: verdict,
280 ..
281 } => {
282 push_outcome_base(&mut labels, outcome);
283 labels.push(("outcome", verdict.as_str().to_string()));
284 }
285 Event::Request { labels: base }
286 | Event::FutureTimestampOffset { labels: base, .. }
287 | Event::Get { labels: base, .. }
288 | Event::Fallback { labels: base, .. }
289 | Event::Size { labels: base, .. }
290 | Event::StoredSize { labels: base, .. }
291 | Event::CompressionRatio { labels: base, .. } => push_layer_base(&mut labels, base),
292 Event::Miss {
293 labels: base,
294 reason,
295 } => {
296 push_layer_base(&mut labels, base);
297 labels.push(("reason", reason.as_str().to_string()));
298 }
299 Event::Disabled {
300 labels: base,
301 reason,
302 } => {
303 push_layer_base(&mut labels, base);
304 labels.push(("reason", reason.as_str().to_string()));
305 }
306 Event::Error {
307 labels: base,
308 error,
309 in_fallback,
310 } => {
311 push_layer_base(&mut labels, base);
312 labels.push(("error", error.as_str().to_string()));
313 labels.push(("in_fallback", in_fallback.to_string()));
314 }
315 Event::Compression {
316 labels: base,
317 outcome,
318 } => {
319 push_layer_base(&mut labels, base);
320 labels.push(("outcome", outcome.as_str().to_string()));
321 }
322 Event::Serialization {
323 labels: base,
324 operation,
325 ..
326 } => {
327 push_layer_base(&mut labels, base);
328 labels.push(("operation", operation.as_str().to_string()));
329 }
330 Event::CompressionDuration {
331 labels: base,
332 operation,
333 ..
334 } => {
335 push_layer_base(&mut labels, base);
336 labels.push(("operation", operation.as_str().to_string()));
337 }
338 }
339 labels
340 }
341
342 pub fn value(event: &Event) -> f64 {
347 match event {
348 Event::ShadowValueAge { seconds, .. }
349 | Event::FutureTimestampOffset { seconds, .. }
350 | Event::StaleRecoveryValueAge { seconds, .. }
351 | Event::Get { seconds, .. }
352 | Event::Fallback { seconds, .. }
353 | Event::Serialization { seconds, .. }
354 | Event::CompressionDuration { seconds, .. } => *seconds,
355 Event::Size { bytes, .. } | Event::StoredSize { bytes, .. } => *bytes as f64,
356 Event::CompressionRatio { ratio, .. } => *ratio,
357 Event::Request { .. }
358 | Event::Miss { .. }
359 | Event::Disabled { .. }
360 | Event::Error { .. }
361 | Event::Invalidation { .. }
362 | Event::Coalesced { .. }
363 | Event::ShadowValidation { .. }
364 | Event::ShadowMismatchLogging { .. }
365 | Event::StaleRecovery { .. }
366 | Event::Compression { .. } => 1.0,
367 }
368 }
369}
370
371fn push_layer_base(labels: &mut Vec<(&'static str, String)>, base: &crate::observe::Labels) {
372 labels.push((LABEL_CACHE_NAMESPACE, base.namespace.to_string()));
373 labels.push((LABEL_USE_CASE, base.use_case.to_string()));
374 labels.push((LABEL_KEY_TYPE, base.key_type.to_string()));
375 labels.push(("layer", base.layer.as_str().to_string()));
376}
377
378fn push_outcome_base(
379 labels: &mut Vec<(&'static str, String)>,
380 base: &crate::observe::OutcomeLabels,
381) {
382 labels.push((LABEL_CACHE_NAMESPACE, base.namespace.to_string()));
383 labels.push((LABEL_USE_CASE, base.use_case.to_string()));
384 labels.push((LABEL_KEY_TYPE, base.key_type.to_string()));
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390 use crate::observe::{
391 CoalescingScope, CompressionOperation, CompressionOutcome, DisabledReason, ErrorKind,
392 Labels, Layer, MissReason, OutcomeLabels, RecoveryOutcome, SerializationOperation,
393 ShadowMismatchLoggingOutcome, ShadowOutcome,
394 };
395 use std::sync::Arc;
396
397 fn base() -> Labels {
398 Labels {
399 namespace: Arc::from("logical"),
400 use_case: Arc::from("lookup"),
401 key_type: Arc::from("item"),
402 layer: Layer::Remote,
403 }
404 }
405
406 fn outcome() -> OutcomeLabels {
407 OutcomeLabels {
408 namespace: Arc::from("logical"),
409 use_case: Arc::from("lookup"),
410 key_type: Arc::from("item"),
411 }
412 }
413
414 pub(crate) fn event_of(kind: MetricKind) -> Event {
416 match kind {
417 MetricKind::Request => Event::Request { labels: base() },
418 MetricKind::Miss => Event::Miss {
419 labels: base(),
420 reason: MissReason::Expired,
421 },
422 MetricKind::Disabled => Event::Disabled {
423 labels: base(),
424 reason: DisabledReason::RampedDown,
425 },
426 MetricKind::Error => Event::Error {
427 labels: base(),
428 error: ErrorKind::Fallback,
429 in_fallback: true,
430 },
431 MetricKind::Invalidation => Event::Invalidation {
432 namespace: Arc::from("logical"),
433 key_type: Arc::from("item"),
434 layer: Layer::Remote,
435 },
436 MetricKind::Coalesced => Event::Coalesced {
437 labels: outcome(),
438 scope: CoalescingScope::Process,
439 },
440 MetricKind::ShadowValidation => Event::ShadowValidation {
441 labels: outcome(),
442 outcome: ShadowOutcome::Mismatch,
443 },
444 MetricKind::ShadowValueAge => Event::ShadowValueAge {
445 labels: outcome(),
446 outcome: ShadowOutcome::Mismatch,
447 seconds: 0.25,
448 },
449 MetricKind::ShadowMismatchLogging => Event::ShadowMismatchLogging {
450 labels: outcome(),
451 outcome: ShadowMismatchLoggingOutcome::HookError,
452 },
453 MetricKind::FutureTimestampOffset => Event::FutureTimestampOffset {
454 labels: base(),
455 seconds: 0.25,
456 },
457 MetricKind::StaleRecovery => Event::StaleRecovery {
458 labels: outcome(),
459 outcome: RecoveryOutcome::Served,
460 },
461 MetricKind::StaleRecoveryValueAge => Event::StaleRecoveryValueAge {
462 labels: outcome(),
463 outcome: RecoveryOutcome::Served,
464 seconds: 0.25,
465 },
466 MetricKind::Compression => Event::Compression {
467 labels: base(),
468 outcome: CompressionOutcome::Compressed,
469 },
470 MetricKind::Get => Event::Get {
471 labels: base(),
472 seconds: 0.25,
473 },
474 MetricKind::Fallback => Event::Fallback {
475 labels: base(),
476 seconds: 0.25,
477 },
478 MetricKind::Serialization => Event::Serialization {
479 labels: base(),
480 operation: SerializationOperation::Dump,
481 seconds: 0.25,
482 },
483 MetricKind::Size => Event::Size {
484 labels: base(),
485 bytes: 123,
486 },
487 MetricKind::StoredSize => Event::StoredSize {
488 labels: base(),
489 bytes: 123,
490 },
491 MetricKind::CompressionRatio => Event::CompressionRatio {
492 labels: base(),
493 ratio: 0.25,
494 },
495 MetricKind::CompressionDuration => Event::CompressionDuration {
496 labels: base(),
497 operation: CompressionOperation::Compress,
498 seconds: 0.25,
499 },
500 }
501 }
502
503 #[test]
504 fn every_kind_round_trips_through_of_and_index() {
505 for (index, kind) in MetricKind::ALL.iter().enumerate() {
506 assert_eq!(MetricKind::of(&event_of(*kind)), *kind);
507 assert_eq!(kind.index(), index);
508 }
509 let names: std::collections::HashSet<&str> =
510 MetricKind::ALL.iter().map(|k| k.as_str()).collect();
511 assert_eq!(names.len(), 20);
512 }
513
514 #[test]
515 fn label_values_follow_the_declared_label_names() {
516 for kind in MetricKind::ALL {
517 let labels = MetricKind::labels(&event_of(kind));
518 let names: Vec<&str> = labels.iter().map(|(name, _)| *name).collect();
519 assert_eq!(names, kind.label_names(), "{}", kind.as_str());
520 }
521 }
522
523 #[test]
524 fn labels_derive_as_the_go_port_does() {
525 let pairs = |kind: MetricKind| -> Vec<(String, String)> {
526 MetricKind::labels(&event_of(kind))
527 .into_iter()
528 .map(|(k, v)| (k.to_string(), v))
529 .collect()
530 };
531 let owned = |items: &[(&str, &str)]| -> Vec<(String, String)> {
532 items
533 .iter()
534 .map(|(k, v)| (k.to_string(), v.to_string()))
535 .collect()
536 };
537 assert_eq!(
538 pairs(MetricKind::Error),
539 owned(&[
540 ("cache_namespace", "logical"),
541 ("use_case", "lookup"),
542 ("key_type", "item"),
543 ("layer", "remote"),
544 ("error", "fallback"),
545 ("in_fallback", "true"),
546 ])
547 );
548 assert_eq!(
549 pairs(MetricKind::Invalidation),
550 owned(&[
551 ("cache_namespace", "logical"),
552 ("key_type", "item"),
553 ("layer", "remote"),
554 ])
555 );
556 assert_eq!(
557 pairs(MetricKind::Coalesced),
558 owned(&[
559 ("cache_namespace", "logical"),
560 ("use_case", "lookup"),
561 ("key_type", "item"),
562 ("scope", "process"),
563 ])
564 );
565 assert_eq!(
566 pairs(MetricKind::ShadowValidation),
567 owned(&[
568 ("cache_namespace", "logical"),
569 ("use_case", "lookup"),
570 ("key_type", "item"),
571 ("outcome", "mismatch"),
572 ])
573 );
574 assert_eq!(
575 pairs(MetricKind::ShadowMismatchLogging),
576 owned(&[
577 ("cache_namespace", "logical"),
578 ("use_case", "lookup"),
579 ("key_type", "item"),
580 ("outcome", "hook_error"),
581 ])
582 );
583 assert_eq!(
584 pairs(MetricKind::Miss),
585 owned(&[
586 ("cache_namespace", "logical"),
587 ("use_case", "lookup"),
588 ("key_type", "item"),
589 ("layer", "remote"),
590 ("reason", "expired"),
591 ])
592 );
593 assert_eq!(
594 pairs(MetricKind::Disabled).last().unwrap(),
595 &("reason".to_string(), "ramped_down".to_string())
596 );
597 assert_eq!(
598 pairs(MetricKind::Compression).last().unwrap(),
599 &("outcome".to_string(), "compressed".to_string())
600 );
601 assert_eq!(
602 pairs(MetricKind::Serialization).last().unwrap(),
603 &("operation".to_string(), "dump".to_string())
604 );
605 assert_eq!(
606 pairs(MetricKind::CompressionDuration).last().unwrap(),
607 &("operation".to_string(), "compress".to_string())
608 );
609 let in_fallback_false = Event::Error {
610 labels: base(),
611 error: ErrorKind::CacheRead,
612 in_fallback: false,
613 };
614 assert_eq!(
615 MetricKind::labels(&in_fallback_false).last().unwrap().1,
616 "false"
617 );
618 }
619
620 #[test]
621 fn values_carry_the_documented_units() {
622 for kind in MetricKind::ALL {
623 let value = MetricKind::value(&event_of(kind));
624 let expected = match kind {
625 MetricKind::Size | MetricKind::StoredSize => 123.0,
626 _ if kind.is_counter() => 1.0,
627 _ => 0.25,
628 };
629 assert_eq!(value, expected, "{}", kind.as_str());
630 }
631 let counters: Vec<&str> = MetricKind::ALL
632 .iter()
633 .filter(|k| k.is_counter())
634 .map(|k| k.as_str())
635 .collect();
636 assert_eq!(
637 counters,
638 [
639 "request",
640 "miss",
641 "disabled",
642 "error",
643 "invalidation",
644 "coalesced",
645 "shadowValidation",
646 "shadowMismatchLogging",
647 "staleRecovery",
648 "compression",
649 ]
650 );
651 }
652}