1use crate::{
6 counter::{Counter, CounterUsize},
7 gauge::{Gauge, GaugeUsize},
8 histogram::Histogram,
9 meter::{Meter, StandardMeter},
10 metrics::is_enabled,
11 registry::{DEFAULT_GROUPING_REGISTRY, DEFAULT_REGISTRY},
12};
13use lazy_static::lazy_static;
14use rand::Rng;
15use std::{
16 fs::OpenOptions,
17 io::Write,
18 sync::Arc,
19 thread,
20 time::{Duration, Instant, SystemTime, UNIX_EPOCH},
21};
22
23lazy_static! {
24 static ref REPORT_TIME: Arc<dyn Gauge<usize>> =
25 GaugeUsize::register("metrics_report_time");
26 static ref REPORT_FAILURE_COUNTER: Arc<dyn Counter<usize>> =
27 CounterUsize::register("metrics_report_failures");
28}
29
30pub trait Reporter: Send {
31 fn report(&self) -> Result<bool, String>;
32}
33
34pub fn report_async<R: 'static + Reporter>(reporter: R, interval: Duration) {
35 if !is_enabled() {
36 return;
37 }
38
39 thread::spawn(move || loop {
40 thread::sleep(
42 interval.mul_f64(0.5 + rand::rng().random_range(0.0..1.0)),
43 );
44
45 let start = Instant::now();
46
47 match reporter.report() {
48 Ok(true) => REPORT_TIME.update(start.elapsed().as_nanos() as usize),
49 Ok(false) => REPORT_FAILURE_COUNTER.inc(1),
50 Err(e) => {
51 eprintln!("Exit metrics reporting due to error: {}", e);
52 return;
53 }
54 }
55 });
56}
57
58pub struct FileReporter {
59 file_path: String,
60}
61
62impl FileReporter {
63 pub fn new(file_path: String) -> Self { FileReporter { file_path } }
64}
65
66impl Reporter for FileReporter {
67 fn report(&self) -> Result<bool, String> {
68 let now = SystemTime::now()
69 .duration_since(UNIX_EPOCH)
70 .map_err(|e| format!("invalid system time {:?}", e))?;
71
72 let mut file = OpenOptions::new()
73 .create(true)
74 .append(true)
75 .open(self.file_path.as_str())
76 .map_err(|e| format!("failed to open file, {:?}", e))?;
77
78 for (name, metric) in DEFAULT_REGISTRY.read().get_all() {
79 file.write(
80 format!(
81 "{}, {}, {}, {}\n",
82 now.as_millis(),
83 name,
84 metric.get_type(),
85 metric.get_value()
86 )
87 .as_bytes(),
88 )
89 .map_err(|e| format!("failed to write file, {:?}", e))?;
90 }
91
92 for (group_name, metrics) in DEFAULT_GROUPING_REGISTRY.read().get_all()
93 {
94 let agg_metric: Vec<String> = metrics
95 .iter()
96 .map(|(name, metric)| metric.get_value_with_group(name))
97 .collect();
98 file.write(
99 format!(
100 "{}, {}, Group, {{{}}}\n",
101 now.as_millis(),
102 group_name,
103 agg_metric.join(", ")
104 )
105 .as_bytes(),
106 )
107 .map_err(|e| format!("failed to write file, {:?}", e))?;
108 }
109
110 Ok(true)
111 }
112}
113
114pub trait Reportable {
115 fn get_value(&self) -> String;
116 fn get_value_with_group(&self, name: &String) -> String;
117}
118
119impl Reportable for CounterUsize {
120 fn get_value(&self) -> String { format!("{}", self.count()) }
121
122 fn get_value_with_group(&self, name: &String) -> String {
123 format!("{}: {}", name, self.count())
124 }
125}
126
127impl Reportable for GaugeUsize {
128 fn get_value(&self) -> String { format!("{}", self.value()) }
129
130 fn get_value_with_group(&self, name: &String) -> String {
131 format!("{}: {}", name, self.value())
132 }
133}
134
135impl Reportable for StandardMeter {
136 fn get_value(&self) -> String {
137 let snapshot = self.snapshot();
138 format!(
139 "{{count: {}, m1: {:.2}, m5: {:.2}, m15: {:.2}, mean: {:.2}, m0: {:.2}}}",
140 snapshot.count(),
141 snapshot.rate1(),
142 snapshot.rate5(),
143 snapshot.rate15(),
144 snapshot.rate_mean(),
145 snapshot.rate_m0()
146 )
147 }
148
149 fn get_value_with_group(&self, name: &String) -> String {
150 let snapshot = self.snapshot();
151 format!(
152 "{0}.count: {1}, {0}.m1: {2:.2}, {0}.m5: {3:.2}, {0}.m15: {4:.2}, {0}.mean: {5:.2}, {0}.m0: {6:.2}",
153 name,
154 snapshot.count(),
155 snapshot.rate1(),
156 snapshot.rate5(),
157 snapshot.rate15(),
158 snapshot.rate_mean(),
159 snapshot.rate_m0()
160 )
161 }
162}
163
164impl<T: Histogram> Reportable for T {
165 fn get_value(&self) -> String {
166 let snapshot = self.snapshot();
167 format!(
168 "{{count: {}, min: {}, mean: {:.2}, max: {}, stddev: {:.2}, variance: {:.2}, p50: {}, p75: {}, p90: {}, p95: {}, p99: {}, p999: {}}}",
169 snapshot.count(),
170 snapshot.min(),
171 snapshot.mean(),
172 snapshot.max(),
173 snapshot.stddev(),
174 snapshot.variance(),
175 snapshot.percentile(0.5),
176 snapshot.percentile(0.75),
177 snapshot.percentile(0.9),
178 snapshot.percentile(0.95),
179 snapshot.percentile(0.99),
180 snapshot.percentile(0.999),
181 )
182 }
183
184 fn get_value_with_group(&self, name: &String) -> String {
185 let snapshot = self.snapshot();
186 format!(
187 "{0}.count: {1}, {0}.min: {2}, {0}.mean: {3:.2}, {0}.max: {4}, {0}.stddev: {5:.2}, {0}.variance: {6:.2}, {0}.p50: {7}, {0}.p75: {8}, {0}.p90: {9}, {0}.p95: {10}, {0}.p99: {11}, {0}.p999: {12}",
188 name,
189 snapshot.count(),
190 snapshot.min(),
191 snapshot.mean(),
192 snapshot.max(),
193 snapshot.stddev(),
194 snapshot.variance(),
195 snapshot.percentile(0.5),
196 snapshot.percentile(0.75),
197 snapshot.percentile(0.9),
198 snapshot.percentile(0.95),
199 snapshot.percentile(0.99),
200 snapshot.percentile(0.999),
201 )
202 }
203}