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