fluvio_system_util/counters/
counter_tbl.rs1use std::cmp;
7use std::collections::BTreeMap;
8use std::sync::RwLock;
9
10#[derive(Debug)]
11pub struct CounterTable<T, C> {
12 pub columns: BTreeMap<C, Column>,
13 pub rows: RwLock<BTreeMap<T, BTreeMap<C, u32>>>,
14}
15
16#[derive(Debug, PartialEq)]
17pub struct Column {
18 label: &'static str,
19 internal: bool,
20}
21
22impl<T, C> ::std::default::Default for CounterTable<T, C>
23where
24 C: Ord,
25 T: Ord,
26{
27 fn default() -> Self {
28 Self {
29 columns: BTreeMap::new(),
30 rows: RwLock::new(BTreeMap::new()),
31 }
32 }
33}
34
35impl<T, C> ::std::cmp::PartialEq for CounterTable<T, C>
36where
37 C: PartialEq,
38 T: PartialEq,
39{
40 fn eq(&self, other: &CounterTable<T, C>) -> bool {
41 if self.columns != other.columns {
43 return false;
44 }
45
46 let local_rows = self.rows.read().unwrap();
48 let other_rows = other.rows.read().unwrap();
49 if *local_rows != *other_rows {
50 return false;
51 }
52 true
53 }
54}
55
56impl<T, C> CounterTable<T, C>
57where
58 C: Ord + Clone,
59 T: Ord,
60{
61 pub fn with_columns(mut self, columns: Vec<(C, &'static str, bool)>) -> Self {
63 for (column_id, label, internal) in columns {
64 self.columns.insert(column_id, Column { label, internal });
65 }
66 self
67 }
68
69 pub fn add_row(&self, row_id: T) {
71 let mut column_counters = BTreeMap::new();
73 for column_id in self.columns.keys() {
74 column_counters.insert(column_id.clone(), 0);
75 }
76
77 self.rows.write().unwrap().insert(row_id, column_counters);
79 }
80
81 pub fn remove_row(&self, row_id: &T) {
83 self.rows.write().unwrap().remove(row_id);
84 }
85
86 pub fn row_count(&self) -> usize {
88 self.rows.read().unwrap().len()
89 }
90
91 pub fn inc_counter(&self, row_id: &T, column_id: C) {
93 if let Some(row) = self.rows.write().unwrap().get_mut(row_id) {
94 if let Some(counter) = row.get_mut(&column_id) {
95 *counter += 1;
96 }
97 }
98 }
99
100 pub fn set_counter(&self, row_id: &T, column_id: C, val: u32) {
102 if let Some(row) = self.rows.write().unwrap().get_mut(row_id) {
103 if let Some(counter) = row.get_mut(&column_id) {
104 *counter = val;
105 }
106 }
107 }
108
109 pub fn reset_counters(&self) {
111 for row in self.rows.write().unwrap().values_mut() {
112 for counter in row.values_mut() {
113 *counter = 0;
114 }
115 }
116 }
117
118 pub fn header_fmt(&self) -> String {
120 let mut res = String::new();
121
122 for column in self.columns.values() {
124 res.push_str(&format!("{:^10}", column.label));
125 res.push_str(" ");
126 }
127
128 if res.len() > 2 {
130 res.truncate(res.len() - 2);
131 }
132
133 res
134 }
135
136 pub fn values_fmt(&self, row_id: T) -> String {
138 let mut res = String::new();
139 if let Some(row) = self.rows.write().unwrap().get_mut(&row_id) {
140 for (column_id, counter) in row {
142 let value_str = counter.to_string();
143 let column_label = if let Some(column) = self.columns.get(column_id) {
144 &(*column.label)
145 } else {
146 ""
147 };
148 let space_width = cmp::max(column_label.len(), 10);
149 let value_width = value_str.len();
150
151 let (pad_left, pad_right) = if value_width > space_width {
153 (0, 0)
154 } else {
155 let pad_left = (space_width - value_width) / 2;
156 let pad_right = space_width - pad_left - value_width;
157 (pad_left, pad_right)
158 };
159
160 res.push_str(&(0..pad_left).map(|_| " ").collect::<String>());
161 res.push_str(&value_str);
162 res.push_str(&(0..pad_right).map(|_| " ").collect::<String>());
163 res.push_str(" ");
164 }
165
166 if res.len() > 2 {
168 res.truncate(res.len() - 2);
169 }
170 }
171
172 res
173 }
174}
175
176#[cfg(test)]
181pub mod test {
182 use super::*;
183
184 #[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)]
185 enum TestCntr {
186 Ok = 0,
187 Failed = 1,
188 Retry = 2,
189 Shutdown = 3,
190 InternalErr = 4,
191 }
192
193 fn generate_counters() -> Vec<(TestCntr, &'static str, bool)> {
194 vec![
195 (TestCntr::Ok, "CONN-OK", false),
196 (TestCntr::Failed, "CONN-FAILED", false),
197 (TestCntr::Retry, "CONN-RETRY", false),
198 (TestCntr::Shutdown, "CONN-SHUTDOWN", false),
199 (TestCntr::InternalErr, "INTERNAL-ERR", true),
200 ]
201 }
202
203 #[test]
204 fn test_counter_table_all() {
205 let counter_tbl = CounterTable::default().with_columns(generate_counters());
206
207 assert_eq!(counter_tbl.columns.len(), 5);
209
210 let (row0, row1): (i32, i32) = (0, 1);
212 counter_tbl.add_row(row0);
213 counter_tbl.add_row(row1);
214 assert_eq!(counter_tbl.row_count(), 2);
215
216 let header = counter_tbl.header_fmt();
218 let expected_header =
219 " CONN-OK CONN-FAILED CONN-RETRY CONN-SHUTDOWN INTERNAL-ERR".to_owned();
220 assert_eq!(header, expected_header);
221
222 counter_tbl.set_counter(&row0, TestCntr::Ok, 4294967290);
224 counter_tbl.inc_counter(&row0, TestCntr::Failed);
225 counter_tbl.set_counter(&row0, TestCntr::Retry, 4199999999);
226 let values_r1 = counter_tbl.values_fmt(row0);
227 let expected_values_r1 =
228 "4294967290 1 4199999999 0 0 ".to_owned();
229 assert_eq!(values_r1, expected_values_r1);
230
231 counter_tbl.inc_counter(&row1, TestCntr::Shutdown);
232 counter_tbl.inc_counter(&row1, TestCntr::Shutdown);
233 let values_r2 = counter_tbl.values_fmt(row1);
234 let expected_values_r2 =
235 " 0 0 0 2 0 ".to_owned();
236 assert_eq!(values_r2, expected_values_r2);
237
238 let expected_counter_tbl = CounterTable::default().with_columns(generate_counters());
240 expected_counter_tbl.add_row(row0);
241 expected_counter_tbl.add_row(row1);
242 expected_counter_tbl.set_counter(&row0, TestCntr::Ok, 4294967290);
243 expected_counter_tbl.set_counter(&row0, TestCntr::Failed, 1);
244 expected_counter_tbl.set_counter(&row0, TestCntr::Retry, 4199999999);
245 expected_counter_tbl.set_counter(&row1, TestCntr::Shutdown, 2);
246 assert_eq!(counter_tbl, expected_counter_tbl);
247
248 counter_tbl.reset_counters();
250 let expected_reset_tbl = CounterTable::default().with_columns(generate_counters());
251 expected_reset_tbl.add_row(row0);
252 expected_reset_tbl.add_row(row1);
253 assert_eq!(counter_tbl, expected_reset_tbl);
254 assert_eq!(counter_tbl.row_count(), 2);
255
256 counter_tbl.remove_row(&row0);
258 let expected_one_row_tbl = CounterTable::default().with_columns(generate_counters());
259 expected_one_row_tbl.add_row(row1);
260 assert_eq!(counter_tbl, expected_one_row_tbl);
261 assert_eq!(counter_tbl.row_count(), 1);
262 }
263}