// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: BSD-3-Clause //! The purpose of this module is to provide abstractions for working with //! metrics in the context of rust-vmm components where there is a strong need //! to have metrics as an optional feature. //! //! As multiple stakeholders are using these components, there are also //! questions regarding the serialization format, as metrics are expected to be //! flexible enough to allow different formatting, serialization and writers. //! When using the rust-vmm metrics, the expectation is that VMMs built on top //! of these components can choose what metrics they’re interested in and also //! can add their own custom metrics without the need to maintain forks. use std::sync::atomic::{AtomicU64, Ordering}; /// Abstraction over the common metric operations. /// /// An object implementing `Metric` is expected to have an inner counter that /// can be incremented and reset. The `Metric` trait can be used for /// implementing a metric system backend (or an aggregator). pub trait Metric { /// Adds `value` to the current counter. fn add(&self, value: u64); /// Increments by 1 unit the current counter. fn inc(&self) { self.add(1); } /// Returns current value of the counter. fn count(&self) -> u64; /// Resets the metric counter. fn reset(&self); /// Set the metric counter `value`. fn set(&self, value: u64); } impl Metric for AtomicU64 { /// Adds `value` to the current counter. /// /// According to /// [`fetch_add` documentation](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU64.html#method.fetch_add), /// in case of an integer overflow, the counter starts over from 0. fn add(&self, value: u64) { self.fetch_add(value, Ordering::Relaxed); } /// Returns current value of the counter. fn count(&self) -> u64 { self.load(Ordering::Relaxed) } /// Resets the metric counter to 0. fn reset(&self) { self.store(0, Ordering::Relaxed) } /// Set the metric counter `value`. fn set(&self, value: u64) { self.store(value, Ordering::Relaxed); } } #[cfg(test)] mod tests { use crate::metric::Metric; use std::sync::atomic::AtomicU64; use std::sync::Arc; struct Dog { metrics: T, } // Trait that declares events that can happen during the lifetime of the // `Dog` which should also have associated events (such as metrics). trait DogEvents { // Event to be called when the dog `bark`s. fn inc_bark(&self); // Event to be called when the dog `eat`s. fn inc_eat(&self); // Event to be called when the dog `eat`s a lot. fn set_eat(&self, no_times: u64); } impl Dog { fn bark(&self) { println!("bark! bark!"); self.metrics.inc_bark(); } fn eat(&self) { println!("nom! nom!"); self.metrics.inc_eat(); } fn eat_more_times(&self, no_times: u64) { self.metrics.set_eat(no_times); } } impl Dog { fn new_with_metrics(metrics: T) -> Self { Self { metrics } } } #[test] fn test_main() { // The `Metric` trait is implemented for `AtomicUsize` so we can easily use it as the // counter for the dog events. #[derive(Default, Debug)] struct DogEventMetrics { bark: AtomicU64, eat: AtomicU64, } impl DogEvents for Arc { fn inc_bark(&self) { self.bark.inc(); } fn inc_eat(&self) { self.eat.inc(); } fn set_eat(&self, no_times: u64) { self.eat.set(no_times); } } impl DogEventMetrics { fn reset(&self) { self.bark.reset(); self.eat.reset(); } } // This is the central object of mini-app built in this example. // All the metrics that might be needed by the app are referenced through the // `SystemMetrics` object. The `SystemMetric` also decides how to format the metrics. // In this simple example, the metrics are formatted with the dummy Debug formatter. #[derive(Default)] struct SystemMetrics { pub(crate) dog_metrics: Arc, } impl SystemMetrics { fn serialize(&self) -> String { let mut serialized_metrics = format!("{:#?}", &self.dog_metrics); // We can choose to reset the metrics right after we format them for serialization. self.dog_metrics.reset(); serialized_metrics.retain(|c| !c.is_whitespace()); serialized_metrics } } let system_metrics = SystemMetrics::default(); let dog = Dog::new_with_metrics(system_metrics.dog_metrics.clone()); dog.bark(); dog.bark(); dog.eat(); let expected_metrics = String::from("DogEventMetrics{bark:2,eat:1,}"); let actual_metrics = system_metrics.serialize(); assert_eq!(expected_metrics, actual_metrics); assert_eq!(system_metrics.dog_metrics.eat.count(), 0); assert_eq!(system_metrics.dog_metrics.bark.count(), 0); // Set `std::u64::MAX` value to `eat` metric. dog.eat_more_times(std::u64::MAX); assert_eq!(system_metrics.dog_metrics.eat.count(), std::u64::MAX); // Check that `add()` wraps around on overflow. dog.eat(); dog.eat(); assert_eq!(system_metrics.dog_metrics.eat.count(), 1); } }