1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use prometheus_client::{
    encoding::text::encode,
    metrics::counter::Counter,
    registry::Registry,
};
use std::{
    ops::Deref,
    sync::{
        Mutex,
        OnceLock,
    },
};

/// The statistic of the service life cycle.
#[derive(Default, Debug)]
pub struct ServiceLifecycle {
    /// The time spent for real actions by the service.
    ///
    /// Time is in nanoseconds.
    // TODO: Use `AtomicU128` when it is stable, otherwise, the field can overflow at some point.
    pub busy: Counter,
    /// The idle time of awaiting sub-tasks or any action from the system/user.
    ///
    /// Time is in nanoseconds.
    // TODO: Use `AtomicU128` when it is stable, otherwise, the field can overflow at some point.
    pub idle: Counter,
}

/// The register of the metrics for each service.
#[derive(Default)]
pub struct ServicesMetrics {
    // It is okay to use std mutex because we register each service only one time.
    pub registry: Mutex<Registry>,
}

impl ServicesMetrics {
    pub fn register_service(&self, service_name: &str) -> ServiceLifecycle {
        let reg =
            regex::Regex::new("^[a-zA-Z_:][a-zA-Z0-9_:]*$").expect("It is a valid Regex");
        if !reg.is_match(service_name) {
            panic!("The service {} has incorrect name.", service_name);
        }
        let lifecycle = ServiceLifecycle::default();
        let mut lock = self
            .registry
            .lock()
            .expect("The lock of the service metric is poisoned");

        // Check that it is a unique service.
        let mut encoded_bytes = String::new();
        encode(&mut encoded_bytes, lock.deref())
            .expect("Unable to decode service metrics");

        let reg = regex::Regex::new(format!("\\b{}\\b", service_name).as_str())
            .expect("It is a valid Regex");
        if reg.is_match(encoded_bytes.as_str()) {
            tracing::warn!("Service with '{}' name is already registered", service_name);
        }

        lock.register(
            format!("{}_idle_ns", service_name),
            format!("The idle time of the {} service", service_name),
            lifecycle.idle.clone(),
        );
        lock.register(
            format!("{}_busy_ns", service_name),
            format!("The busy time of the {} service", service_name),
            lifecycle.busy.clone(),
        );

        lifecycle
    }
}

static SERVICES_METRICS: OnceLock<ServicesMetrics> = OnceLock::new();

pub fn services_metrics() -> &'static ServicesMetrics {
    SERVICES_METRICS.get_or_init(ServicesMetrics::default)
}

#[test]
fn register_success() {
    services_metrics().register_service("Foo");
    services_metrics().register_service("Bar");
}