1#[macro_use]
46extern crate log;
47extern crate hotmic;
48
49use hotmic::{
50 snapshot::{Snapshot, SummarizedHistogram, TypedMeasurement},
51 Controller,
52};
53use log::Level;
54use std::collections::{HashMap, VecDeque};
55use std::fmt::Display;
56use std::thread;
57use std::time::Duration;
58
59pub struct StdoutExporter {
61 controller: Controller,
62 level: Level,
63 interval: Duration,
64}
65
66impl StdoutExporter {
67 pub fn new(controller: Controller, level: Level, interval: Duration) -> Self {
72 StdoutExporter {
73 controller,
74 level,
75 interval,
76 }
77 }
78
79 fn turn(&mut self) {
80 match self.controller.get_snapshot() {
81 Ok(snapshot) => self.process_snapshot(snapshot),
82 Err(e) => error!("caught error getting metrics snapshot: {}", e),
83 }
84 }
85
86 pub fn run(&mut self) {
95 loop {
96 self.turn();
97 thread::sleep(self.interval);
98 }
99 }
100
101 fn process_snapshot(&self, snapshot: Snapshot) {
102 let mut nested = Nested::default();
103
104 for measurement in snapshot.into_vec() {
105 let (name_parts, mut values) = match measurement {
106 TypedMeasurement::Counter(key, value) => {
107 let (layers, name) = name_to_parts(key);
108 let values = single_value_to_values(name, value);
109
110 (layers, values)
111 }
112 TypedMeasurement::Gauge(key, value) => {
113 let (layers, name) = name_to_parts(key);
114 let values = single_value_to_values(name, value);
115
116 (layers, values)
117 }
118 TypedMeasurement::TimingHistogram(key, summary) => {
119 let (layers, name) = name_to_parts(key);
120 let values = summary_to_values(name, summary, "ns");
121
122 (layers, values)
123 }
124 TypedMeasurement::ValueHistogram(key, summary) => {
125 let (layers, name) = name_to_parts(key);
126 let values = summary_to_values(name, summary, "");
127
128 (layers, values)
129 }
130 };
131
132 nested.insert(name_parts, &mut values);
133 }
134
135 let output = nested.into_output();
136 log!(self.level, "metrics:\n{}", output);
137 }
138}
139
140fn name_to_parts(name: String) -> (VecDeque<String>, String) {
141 let mut parts = name
142 .split('.')
143 .map(ToOwned::to_owned)
144 .collect::<VecDeque<_>>();
145 let name = parts.pop_back().expect("name didn't have a single part");
146
147 (parts, name)
148}
149
150fn single_value_to_values<T>(name: String, value: T) -> Vec<String>
151where
152 T: Display,
153{
154 let fvalue = format!("{}: {}", name, value);
155 vec![fvalue]
156}
157
158fn summary_to_values(name: String, summary: SummarizedHistogram, suffix: &str) -> Vec<String> {
159 let mut values = Vec::new();
160
161 values.push(format!("{} count: {}", name, summary.count()));
162 for (percentile, value) in summary.measurements() {
163 values.push(format!(
164 "{} {}: {}{}",
165 name,
166 percentile.label(),
167 value,
168 suffix
169 ));
170 }
171
172 values
173}
174
175struct Nested {
176 level: usize,
177 current: Vec<String>,
178 next: HashMap<String, Nested>,
179}
180
181impl Nested {
182 pub fn with_level(level: usize) -> Self {
183 Nested {
184 level,
185 ..Default::default()
186 }
187 }
188
189 pub fn insert(&mut self, mut name_parts: VecDeque<String>, values: &mut Vec<String>) {
190 match name_parts.len() {
191 0 => {
192 let indent = " ".repeat(self.level + 1);
193 let mut indented = values
194 .iter()
195 .map(move |x| format!("{}{}", indent, x))
196 .collect::<Vec<_>>();
197 self.current.append(&mut indented);
198 }
199 _ => {
200 let name = name_parts
201 .pop_front()
202 .expect("failed to get next name component");
203 let current_level = self.level;
204 let inner = self
205 .next
206 .entry(name)
207 .or_insert_with(move || Nested::with_level(current_level + 1));
208 inner.insert(name_parts, values);
209 }
210 }
211 }
212
213 pub fn into_output(self) -> String {
214 let indent = " ".repeat(self.level + 1);
215 let mut output = String::new();
216 if self.level == 0 {
217 output.push_str("\nroot:\n");
218 }
219
220 let mut sorted = self
221 .current
222 .into_iter()
223 .map(SortEntry::Inline)
224 .chain(self.next.into_iter().map(|(k, v)| SortEntry::Nested(k, v)))
225 .collect::<Vec<_>>();
226 sorted.sort();
227
228 for entry in sorted {
229 match entry {
230 SortEntry::Inline(s) => {
231 output.push_str(s.as_str());
232 output.push_str("\n");
233 }
234 SortEntry::Nested(s, inner) => {
235 output.push_str(indent.as_str());
236 output.push_str(s.as_str());
237 output.push_str(":\n");
238
239 let layer_output = inner.into_output();
240 output.push_str(layer_output.as_str());
241 }
242 }
243 }
244
245 output
246 }
247}
248
249impl Default for Nested {
250 fn default() -> Self {
251 Nested {
252 level: 0,
253 current: Vec::new(),
254 next: HashMap::new(),
255 }
256 }
257}
258
259enum SortEntry {
260 Inline(String),
261 Nested(String, Nested),
262}
263
264impl SortEntry {
265 fn name(&self) -> &String {
266 match self {
267 SortEntry::Inline(s) => s,
268 SortEntry::Nested(s, _) => s,
269 }
270 }
271}
272
273impl PartialEq for SortEntry {
274 fn eq(&self, other: &SortEntry) -> bool {
275 self.name() == other.name()
276 }
277}
278
279impl Eq for SortEntry {}
280
281impl std::cmp::PartialOrd for SortEntry {
282 fn partial_cmp(&self, other: &SortEntry) -> Option<std::cmp::Ordering> {
283 Some(self.cmp(other))
284 }
285}
286
287impl std::cmp::Ord for SortEntry {
288 fn cmp(&self, other: &SortEntry) -> std::cmp::Ordering {
289 self.name().cmp(other.name())
290 }
291}