blob: c728161eb4709d9f14a9b5834f96498f6d784653 [file] [log] [blame] [edit]
// 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<T: DogEvents> {
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<T: DogEvents> Dog<T> {
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<T: DogEvents> Dog<T> {
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<DogEventMetrics> {
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<DogEventMetrics>,
}
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);
}
}