metrics/
report.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use 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        // sleep random time on different nodes to reduce competition.
41        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}