fluvio_system_util/counters/
counters.rs

1//!
2//! # Counters
3//!
4//! Counters object definition and functionality
5//!
6use std::cmp;
7use std::collections::BTreeMap;
8
9#[derive(Debug, PartialEq)]
10pub struct Counters<T> {
11    pub list: BTreeMap<T, Counter>,
12}
13
14#[derive(Debug, PartialEq)]
15pub struct Counter {
16    label: &'static str,
17    internal: bool,
18    value: u32,
19}
20
21impl<T> ::std::default::Default for Counters<T>
22where
23    T: Ord,
24{
25    fn default() -> Self {
26        Self {
27            list: BTreeMap::new(),
28        }
29    }
30}
31
32impl<T> Counters<T>
33where
34    T: Ord,
35{
36    /// build counter from array of tuples
37    pub fn new(items: Vec<(T, &'static str, bool)>) -> Self {
38        let mut counters = Counters::default();
39        for (id, label, internal) in items {
40            counters.list.insert(
41                id,
42                Counter {
43                    label,
44                    internal,
45                    value: 0,
46                },
47            );
48        }
49        counters
50    }
51
52    /// increment counter
53    pub fn inc_counter(&mut self, id: T) {
54        if let Some(counter) = self.list.get_mut(&id) {
55            counter.value += 1;
56        }
57    }
58
59    /// increment counter
60    pub fn set_counter(&mut self, id: T, val: u32) {
61        if let Some(counter) = self.list.get_mut(&id) {
62            counter.value = val;
63        }
64    }
65
66    /// reset counters
67    pub fn reset(&mut self) {
68        for counter in self.list.values_mut() {
69            counter.value = 0;
70        }
71    }
72
73    /// counter heders in string format (center justified, min 10 spaces)
74    pub fn header_fmt(&self) -> String {
75        let mut res = String::new();
76
77        // accumulate labels
78        for counter in self.list.values() {
79            res.push_str(&format!("{:^10}", counter.label));
80            res.push_str("  ");
81        }
82
83        // remove last 2 spaces
84        if res.len() > 2 {
85            res.truncate(res.len() - 2);
86        }
87
88        res
89    }
90
91    /// format values in string format (center justified to column header - min 10 spaces)
92    pub fn values_fmt(&self) -> String {
93        let mut res = String::new();
94
95        // accumulate labels
96        for counter in self.list.values() {
97            let value_str = counter.value.to_string();
98            let space_width = cmp::max(counter.label.len(), 10);
99            let value_width = value_str.len();
100
101            // center justify... need to compute our own adding (as Rust formatter requires literal)
102            let (pad_left, pad_right) = if value_width > space_width {
103                (0, 0)
104            } else {
105                let pad_left = (space_width - value_width) / 2;
106                let pad_right = space_width - pad_left - value_width;
107                (pad_left, pad_right)
108            };
109
110            res.push_str(&(0..pad_left).map(|_| " ").collect::<String>());
111            res.push_str(&value_str);
112            res.push_str(&(0..pad_right).map(|_| " ").collect::<String>());
113            res.push_str("  ");
114        }
115
116        // remove last 2 spaces
117        if res.len() > 2 {
118            res.truncate(res.len() - 2);
119        }
120
121        res
122    }
123}
124
125// -----------------------------------
126// Unit Tests
127// -----------------------------------
128
129#[cfg(test)]
130pub mod test {
131    use super::*;
132
133    #[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
134    enum TestCntr {
135        Ok = 0,
136        Failed = 1,
137        Retry = 2,
138        Shutdown = 3,
139        InternalErr = 4,
140    }
141
142    fn generate_counters() -> Vec<(TestCntr, &'static str, bool)> {
143        vec![
144            (TestCntr::Ok, "CONN-OK", false),
145            (TestCntr::Failed, "CONN-FAILED", false),
146            (TestCntr::Retry, "CONN-RETRY", false),
147            (TestCntr::Shutdown, "CONN-SHUTDOWN", false),
148            (TestCntr::InternalErr, "INTERNAL-ERR", true),
149        ]
150    }
151
152    #[test]
153    fn test_counters_all() {
154        let mut counters = Counters::new(generate_counters());
155
156        // test generation
157        assert_eq!(counters.list.len(), 5);
158
159        // test header formatter
160        let header = counters.header_fmt();
161        let expected_header =
162            " CONN-OK    CONN-FAILED  CONN-RETRY  CONN-SHUTDOWN  INTERNAL-ERR".to_owned();
163        assert_eq!(header, expected_header);
164
165        // test increment & value formatter
166        counters.set_counter(TestCntr::Ok, 4294967290);
167        counters.inc_counter(TestCntr::Failed);
168        counters.set_counter(TestCntr::Retry, 4199999999);
169        counters.inc_counter(TestCntr::Shutdown);
170        counters.inc_counter(TestCntr::Shutdown);
171        let values = counters.values_fmt();
172        let expected_values =
173            "4294967290       1       4199999999        2             0      ".to_owned();
174        assert_eq!(values, expected_values);
175
176        // test equal
177        let mut expected_counters = Counters::new(generate_counters());
178        expected_counters.set_counter(TestCntr::Ok, 4294967290);
179        expected_counters.set_counter(TestCntr::Failed, 1);
180        expected_counters.set_counter(TestCntr::Retry, 4199999999);
181        expected_counters.set_counter(TestCntr::Shutdown, 2);
182        assert_eq!(counters, expected_counters);
183
184        // test reset
185        counters.reset();
186        assert_eq!(counters, Counters::new(generate_counters()));
187        assert_eq!(counters.list.len(), 5);
188    }
189}