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 
14 use 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).
21 pub trait Metric {
22     /// Adds `value` to the current counter.
add(&self, value: u64)23     fn add(&self, value: u64);
24     /// Increments by 1 unit the current counter.
inc(&self)25     fn inc(&self) {
26         self.add(1);
27     }
28     /// Returns current value of the counter.
count(&self) -> u6429     fn count(&self) -> u64;
30     /// Resets the metric counter.
reset(&self)31     fn reset(&self);
32     /// Set the metric counter `value`.
set(&self, value: u64)33     fn set(&self, value: u64);
34 }
35 
36 impl 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.
add(&self, value: u64)42     fn add(&self, value: u64) {
43         self.fetch_add(value, Ordering::Relaxed);
44     }
45 
46     /// Returns current value of the counter.
count(&self) -> u6447     fn count(&self) -> u64 {
48         self.load(Ordering::Relaxed)
49     }
50 
51     /// Resets the metric counter to 0.
reset(&self)52     fn reset(&self) {
53         self.store(0, Ordering::Relaxed)
54     }
55 
56     /// Set the metric counter `value`.
set(&self, value: u64)57     fn set(&self, value: u64) {
58         self.store(value, Ordering::Relaxed);
59     }
60 }
61 
62 #[cfg(test)]
63 mod 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.
inc_bark(&self)77         fn inc_bark(&self);
78         // Event to be called when the dog `eat`s.
inc_eat(&self)79         fn inc_eat(&self);
80         // Event to be called when the dog `eat`s a lot.
set_eat(&self, no_times: u64)81         fn set_eat(&self, no_times: u64);
82     }
83 
84     impl<T: DogEvents> Dog<T> {
bark(&self)85         fn bark(&self) {
86             println!("bark! bark!");
87             self.metrics.inc_bark();
88         }
89 
eat(&self)90         fn eat(&self) {
91             println!("nom! nom!");
92             self.metrics.inc_eat();
93         }
94 
eat_more_times(&self, no_times: u64)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> {
new_with_metrics(metrics: T) -> Self101         fn new_with_metrics(metrics: T) -> Self {
102             Self { metrics }
103         }
104     }
105 
106     #[test]
test_main()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 }
179