vmm_sys_util/
metric.rs

1// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: BSD-3-Clause
3//! The purpose of this module is to provide abstractions for working with
4//! metrics in the context of rust-vmm components where there is a strong need
5//! to have metrics as an optional feature.
6//!
7//! As multiple stakeholders are using these components, there are also
8//! questions regarding the serialization format, as metrics are expected to be
9//! flexible enough to allow different formatting, serialization and writers.
10//! When using the rust-vmm metrics, the expectation is that VMMs built on top
11//! of these components can choose what metrics they’re interested in and also
12//! can add their own custom metrics without the need to maintain forks.
13
14use std::sync::atomic::{AtomicU64, Ordering};
15
16/// Abstraction over the common metric operations.
17///
18/// An object implementing `Metric` is expected to have an inner counter that
19/// can be incremented and reset. The `Metric` trait can be used for
20/// implementing a metric system backend (or an aggregator).
21pub trait Metric {
22    /// Adds `value` to the current counter.
23    fn add(&self, value: u64);
24    /// Increments by 1 unit the current counter.
25    fn inc(&self) {
26        self.add(1);
27    }
28    /// Returns current value of the counter.
29    fn count(&self) -> u64;
30    /// Resets the metric counter.
31    fn reset(&self);
32    /// Set the metric counter `value`.
33    fn set(&self, value: u64);
34}
35
36impl Metric for AtomicU64 {
37    /// Adds `value` to the current counter.
38    ///
39    /// According to
40    /// [`fetch_add` documentation](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU64.html#method.fetch_add),
41    /// in case of an integer overflow, the counter starts over from 0.
42    fn add(&self, value: u64) {
43        self.fetch_add(value, Ordering::Relaxed);
44    }
45
46    /// Returns current value of the counter.
47    fn count(&self) -> u64 {
48        self.load(Ordering::Relaxed)
49    }
50
51    /// Resets the metric counter to 0.
52    fn reset(&self) {
53        self.store(0, Ordering::Relaxed)
54    }
55
56    /// Set the metric counter `value`.
57    fn set(&self, value: u64) {
58        self.store(value, Ordering::Relaxed);
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use crate::metric::Metric;
65
66    use std::sync::atomic::AtomicU64;
67    use std::sync::Arc;
68
69    struct Dog<T: DogEvents> {
70        metrics: T,
71    }
72
73    // Trait that declares events that can happen during the lifetime of the
74    // `Dog` which should also have associated events (such as metrics).
75    trait DogEvents {
76        // Event to be called when the dog `bark`s.
77        fn inc_bark(&self);
78        // Event to be called when the dog `eat`s.
79        fn inc_eat(&self);
80        // Event to be called when the dog `eat`s a lot.
81        fn set_eat(&self, no_times: u64);
82    }
83
84    impl<T: DogEvents> Dog<T> {
85        fn bark(&self) {
86            println!("bark! bark!");
87            self.metrics.inc_bark();
88        }
89
90        fn eat(&self) {
91            println!("nom! nom!");
92            self.metrics.inc_eat();
93        }
94
95        fn eat_more_times(&self, no_times: u64) {
96            self.metrics.set_eat(no_times);
97        }
98    }
99
100    impl<T: DogEvents> Dog<T> {
101        fn new_with_metrics(metrics: T) -> Self {
102            Self { metrics }
103        }
104    }
105
106    #[test]
107    fn test_main() {
108        // The `Metric` trait is implemented for `AtomicUsize` so we can easily use it as the
109        // counter for the dog events.
110        #[derive(Default, Debug)]
111        struct DogEventMetrics {
112            bark: AtomicU64,
113            eat: AtomicU64,
114        }
115
116        impl DogEvents for Arc<DogEventMetrics> {
117            fn inc_bark(&self) {
118                self.bark.inc();
119            }
120
121            fn inc_eat(&self) {
122                self.eat.inc();
123            }
124
125            fn set_eat(&self, no_times: u64) {
126                self.eat.set(no_times);
127            }
128        }
129
130        impl DogEventMetrics {
131            fn reset(&self) {
132                self.bark.reset();
133                self.eat.reset();
134            }
135        }
136
137        // This is the central object of mini-app built in this example.
138        // All the metrics that might be needed by the app are referenced through the
139        // `SystemMetrics` object. The `SystemMetric` also decides how to format the metrics.
140        // In this simple example, the metrics are formatted with the dummy Debug formatter.
141        #[derive(Default)]
142        struct SystemMetrics {
143            pub(crate) dog_metrics: Arc<DogEventMetrics>,
144        }
145
146        impl SystemMetrics {
147            fn serialize(&self) -> String {
148                let mut serialized_metrics = format!("{:#?}", &self.dog_metrics);
149                // We can choose to reset the metrics right after we format them for serialization.
150                self.dog_metrics.reset();
151
152                serialized_metrics.retain(|c| !c.is_whitespace());
153                serialized_metrics
154            }
155        }
156
157        let system_metrics = SystemMetrics::default();
158        let dog = Dog::new_with_metrics(system_metrics.dog_metrics.clone());
159        dog.bark();
160        dog.bark();
161        dog.eat();
162
163        let expected_metrics = String::from("DogEventMetrics{bark:2,eat:1,}");
164        let actual_metrics = system_metrics.serialize();
165        assert_eq!(expected_metrics, actual_metrics);
166
167        assert_eq!(system_metrics.dog_metrics.eat.count(), 0);
168        assert_eq!(system_metrics.dog_metrics.bark.count(), 0);
169
170        // Set `std::u64::MAX` value to `eat` metric.
171        dog.eat_more_times(std::u64::MAX);
172        assert_eq!(system_metrics.dog_metrics.eat.count(), std::u64::MAX);
173        // Check that `add()` wraps around on overflow.
174        dog.eat();
175        dog.eat();
176        assert_eq!(system_metrics.dog_metrics.eat.count(), 1);
177    }
178}