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}