solana_metrics/
datapoint.rs

1//! This file defines a set of macros for reporting metrics.
2//!
3//! To report a metric, simply calling one of the following datapoint macros
4//! with a suitable message level:
5//!
6//! - datapoint_error!
7//! - datapoint_warn!
8//! - datapoint_trace!
9//! - datapoint_info!
10//! - datapoint_debug!
11//!
12//! The matric macro consists of the following three main parts:
13//!  - name: the name of the metric.
14//!
15//!  - tags (optional): when a metric sample is reported with tags, you can use
16//!    group-by when querying the reported samples.  Each metric sample can be
17//!    attached with zero to many tags.  Each tag is of the format:
18//!
19//!    - "tag-name" => "tag-value"
20//!
21//!  - fields (optional): fields are the main content of a metric sample. The
22//!    macro supports four different types of fields: bool, i64, f64, and String.
23//!    Here're their syntax:
24//!
25//!    - ("field-name", "field-value", bool)
26//!    - ("field-name", "field-value", i64)
27//!    - ("field-name", "field-value", f64)
28//!    - ("field-name", "field-value", String)
29//!
30//! Example:
31//!
32//! datapoint_debug!(
33//!     "name-of-the-metric",
34//!     "tag" => "tag-value",
35//!     "tag2" => "tag-value2",
36//!     ("some-bool", false, bool),
37//!     ("some-int", 100, i64),
38//!     ("some-float", 1.05, f64),
39//!     ("some-string", "field-value", String),
40//! );
41//!
42use std::{fmt, time::SystemTime};
43
44#[derive(Clone, Debug)]
45pub struct DataPoint {
46    pub name: &'static str,
47    pub timestamp: SystemTime,
48    /// tags are eligible for group-by operations.
49    pub tags: Vec<(&'static str, String)>,
50    pub fields: Vec<(&'static str, String)>,
51}
52
53impl DataPoint {
54    pub fn new(name: &'static str) -> Self {
55        DataPoint {
56            name,
57            timestamp: SystemTime::now(),
58            tags: vec![],
59            fields: vec![],
60        }
61    }
62
63    pub fn add_tag(&mut self, name: &'static str, value: &str) -> &mut Self {
64        self.tags.push((name, value.to_string()));
65        self
66    }
67
68    pub fn add_field_str(&mut self, name: &'static str, value: &str) -> &mut Self {
69        self.fields
70            .push((name, format!("\"{}\"", value.replace('\"', "\\\""))));
71        self
72    }
73
74    pub fn add_field_bool(&mut self, name: &'static str, value: bool) -> &mut Self {
75        self.fields.push((name, value.to_string()));
76        self
77    }
78
79    pub fn add_field_i64(&mut self, name: &'static str, value: i64) -> &mut Self {
80        self.fields.push((name, value.to_string() + "i"));
81        self
82    }
83
84    pub fn add_field_f64(&mut self, name: &'static str, value: f64) -> &mut Self {
85        self.fields.push((name, value.to_string()));
86        self
87    }
88}
89
90impl fmt::Display for DataPoint {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        write!(f, "datapoint: {}", self.name)?;
93        for tag in &self.tags {
94            write!(f, ",{}={}", tag.0, tag.1)?;
95        }
96        for field in &self.fields {
97            write!(f, " {}={}", field.0, field.1)?;
98        }
99        Ok(())
100    }
101}
102
103#[macro_export]
104macro_rules! create_datapoint {
105    (@field $point:ident $name:expr, $string:expr, String) => {
106        $point.add_field_str($name, &$string);
107    };
108    (@field $point:ident $name:expr, $value:expr, i64) => {
109        $point.add_field_i64($name, $value as i64);
110    };
111    (@field $point:ident $name:expr, $value:expr, f64) => {
112        $point.add_field_f64($name, $value as f64);
113    };
114    (@field $point:ident $name:expr, $value:expr, bool) => {
115        $point.add_field_bool($name, $value as bool);
116    };
117    (@tag $point:ident $tag_name:expr, $tag_value:expr) => {
118        $point.add_tag($tag_name, &$tag_value);
119    };
120
121    (@fields $point:ident) => {};
122
123    // process tags
124    (@fields $point:ident $tag_name:expr => $tag_value:expr, $($rest:tt)*) => {
125        $crate::create_datapoint!(@tag $point $tag_name, $tag_value);
126        $crate::create_datapoint!(@fields $point $($rest)*);
127    };
128    (@fields $point:ident $tag_name:expr => $tag_value:expr) => {
129        $crate::create_datapoint!(@tag $point $tag_name, $tag_value);
130    };
131
132    // process fields
133    (@fields $point:ident ($name:expr, $value:expr, $type:ident) , $($rest:tt)*) => {
134        $crate::create_datapoint!(@field $point $name, $value, $type);
135        $crate::create_datapoint!(@fields $point $($rest)*);
136    };
137    (@fields $point:ident ($name:expr, $value:expr, $type:ident)) => {
138        $crate::create_datapoint!(@field $point $name, $value, $type);
139    };
140
141    (@point $name:expr, $($fields:tt)+) => {
142        {
143            let mut point = $crate::datapoint::DataPoint::new(&$name);
144            $crate::create_datapoint!(@fields point $($fields)+);
145            point
146        }
147    };
148    (@point $name:expr) => {
149        $crate::datapoint::DataPoint::new(&$name)
150    };
151}
152
153#[macro_export]
154macro_rules! datapoint {
155    ($level:expr, $name:expr) => {
156        if log::log_enabled!($level) {
157            $crate::submit($crate::create_datapoint!(@point $name), $level);
158        }
159    };
160    ($level:expr, $name:expr, $($fields:tt)+) => {
161        if log::log_enabled!($level) {
162            $crate::submit($crate::create_datapoint!(@point $name, $($fields)+), $level);
163        }
164    };
165}
166#[macro_export]
167macro_rules! datapoint_error {
168    ($name:expr) => {
169        $crate::datapoint!(log::Level::Error, $name);
170    };
171    ($name:expr, $($fields:tt)+) => {
172        $crate::datapoint!(log::Level::Error, $name, $($fields)+);
173    };
174}
175
176#[macro_export]
177macro_rules! datapoint_warn {
178    ($name:expr) => {
179        $crate::datapoint!(log::Level::Warn, $name);
180    };
181    ($name:expr, $($fields:tt)+) => {
182        $crate::datapoint!(log::Level::Warn, $name, $($fields)+);
183    };
184}
185
186#[macro_export]
187macro_rules! datapoint_info {
188    ($name:expr) => {
189        $crate::datapoint!(log::Level::Info, $name);
190    };
191    ($name:expr, $($fields:tt)+) => {
192        $crate::datapoint!(log::Level::Info, $name, $($fields)+);
193    };
194}
195
196#[macro_export]
197macro_rules! datapoint_debug {
198    ($name:expr) => {
199        $crate::datapoint!(log::Level::Debug, $name);
200    };
201    ($name:expr, $($fields:tt)+) => {
202        $crate::datapoint!(log::Level::Debug, $name, $($fields)+);
203    };
204}
205
206#[macro_export]
207macro_rules! datapoint_trace {
208    ($name:expr) => {
209        $crate::datapoint!(log::Level::Trace, $name);
210    };
211    ($name:expr, $($fields:tt)+) => {
212        $crate::datapoint!(log::Level::Trace, $name, $($fields)+);
213    };
214}
215
216#[cfg(test)]
217mod test {
218    #[test]
219    fn test_datapoint() {
220        datapoint_debug!("name", ("field name", "test", String));
221        datapoint_info!("name", ("field name", 12.34_f64, f64));
222        datapoint_trace!("name", ("field name", true, bool));
223        datapoint_warn!("name", ("field name", 1, i64));
224        datapoint_error!("name", ("field name", 1, i64),);
225        datapoint!(
226            log::Level::Warn,
227            "name",
228            ("field1 name", 2, i64),
229            ("field2 name", 2, i64)
230        );
231        datapoint_info!("name", ("field1 name", 2, i64), ("field2 name", 2, i64),);
232        datapoint_trace!(
233            "name",
234            ("field1 name", 2, i64),
235            ("field2 name", 2, i64),
236            ("field3 name", 3, i64)
237        );
238        datapoint!(
239            log::Level::Error,
240            "name",
241            ("field1 name", 2, i64),
242            ("field2 name", 2, i64),
243            ("field3 name", 3, i64),
244        );
245
246        let point = create_datapoint!(
247            @point "name",
248            ("i64", 1, i64),
249            ("String", "string space string", String),
250            ("f64", 12.34_f64, f64),
251            ("bool", true, bool)
252        );
253        assert_eq!(point.name, "name");
254        assert_eq!(point.tags.len(), 0);
255        assert_eq!(point.fields[0], ("i64", "1i".to_string()));
256        assert_eq!(
257            point.fields[1],
258            ("String", "\"string space string\"".to_string())
259        );
260        assert_eq!(point.fields[2], ("f64", "12.34".to_string()));
261        assert_eq!(point.fields[3], ("bool", "true".to_string()));
262    }
263
264    #[test]
265    fn test_datapoint_with_tags() {
266        datapoint_debug!("name", "tag" => "tag-value", ("field name", "test", String));
267        datapoint_info!(
268            "name",
269            "tag" => "tag-value",
270            "tag2" => "tag-value-2",
271            ("field name", 12.34_f64, f64)
272        );
273        datapoint_trace!(
274            "name",
275            "tag" => "tag-value",
276            "tag2" => "tag-value-2",
277            "tag3" => "tag-value-3",
278            ("field name", true, bool)
279        );
280        datapoint_warn!("name", "tag" => "tag-value");
281        datapoint_error!("name", "tag" => "tag-value", ("field name", 1, i64),);
282        datapoint!(
283            log::Level::Warn,
284            "name",
285            "tag" => "tag-value",
286            ("field1 name", 2, i64),
287            ("field2 name", 2, i64)
288        );
289        datapoint_info!("name", ("field1 name", 2, i64), ("field2 name", 2, i64),);
290        datapoint_trace!(
291            "name",
292            "tag" => "tag-value",
293            ("field1 name", 2, i64),
294            ("field2 name", 2, i64),
295            ("field3 name", 3, i64)
296        );
297        datapoint!(
298            log::Level::Error,
299            "name",
300            "tag" => "tag-value",
301            ("field1 name", 2, i64),
302            ("field2 name", 2, i64),
303            ("field3 name", 3, i64),
304        );
305
306        let point = create_datapoint!(
307            @point "name",
308            "tag1" => "tag-value-1",
309            "tag2" => "tag-value-2",
310            "tag3" => "tag-value-3",
311            ("i64", 1, i64),
312            ("String", "string space string", String),
313            ("f64", 12.34_f64, f64),
314            ("bool", true, bool)
315        );
316        assert_eq!(point.name, "name");
317        assert_eq!(point.fields[0], ("i64", "1i".to_string()));
318        assert_eq!(
319            point.fields[1],
320            ("String", "\"string space string\"".to_string())
321        );
322        assert_eq!(point.fields[2], ("f64", "12.34".to_string()));
323        assert_eq!(point.fields[3], ("bool", "true".to_string()));
324        assert_eq!(point.tags[0], ("tag1", "tag-value-1".to_string()));
325        assert_eq!(point.tags[1], ("tag2", "tag-value-2".to_string()));
326        assert_eq!(point.tags[2], ("tag3", "tag-value-3".to_string()));
327    }
328}