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 optional fields
124    (@fields $point:ident ($name:expr, $value:expr, Option<$type:ident>) , $($rest:tt)*) => {
125        if let Some(value) = $value {
126            $crate::create_datapoint!(@field $point $name, value, $type);
127        }
128        $crate::create_datapoint!(@fields $point $($rest)*);
129    };
130    (@fields $point:ident ($name:expr, $value:expr, Option<$type:ident>) $(,)?) => {
131        if let Some(value) = $value {
132            $crate::create_datapoint!(@field $point $name, value, $type);
133        }
134    };
135
136    // process tags
137    (@fields $point:ident $tag_name:expr => $tag_value:expr, $($rest:tt)*) => {
138        $crate::create_datapoint!(@tag $point $tag_name, $tag_value);
139        $crate::create_datapoint!(@fields $point $($rest)*);
140    };
141    (@fields $point:ident $tag_name:expr => $tag_value:expr $(,)?) => {
142        $crate::create_datapoint!(@tag $point $tag_name, $tag_value);
143    };
144
145    // process fields
146    (@fields $point:ident ($name:expr, $value:expr, $type:ident) , $($rest:tt)*) => {
147        $crate::create_datapoint!(@field $point $name, $value, $type);
148        $crate::create_datapoint!(@fields $point $($rest)*);
149    };
150    (@fields $point:ident ($name:expr, $value:expr, $type:ident) $(,)?) => {
151        $crate::create_datapoint!(@field $point $name, $value, $type);
152    };
153
154    (@point $name:expr, $($fields:tt)+) => {
155        {
156            let mut point = $crate::datapoint::DataPoint::new(&$name);
157            $crate::create_datapoint!(@fields point $($fields)+);
158            point
159        }
160    };
161}
162
163#[macro_export]
164macro_rules! datapoint {
165    ($level:expr, $name:expr, $($fields:tt)+) => {
166        if log::log_enabled!($level) {
167            $crate::submit($crate::create_datapoint!(@point $name, $($fields)+), $level);
168        }
169    };
170}
171#[macro_export]
172macro_rules! datapoint_error {
173    ($name:expr, $($fields:tt)+) => {
174        $crate::datapoint!(log::Level::Error, $name, $($fields)+);
175    };
176}
177
178#[macro_export]
179macro_rules! datapoint_warn {
180    ($name:expr, $($fields:tt)+) => {
181        $crate::datapoint!(log::Level::Warn, $name, $($fields)+);
182    };
183}
184
185#[macro_export]
186macro_rules! datapoint_info {
187    ($name:expr, $($fields:tt)+) => {
188        $crate::datapoint!(log::Level::Info, $name, $($fields)+);
189    };
190}
191
192#[macro_export]
193macro_rules! datapoint_debug {
194    ($name:expr, $($fields:tt)+) => {
195        $crate::datapoint!(log::Level::Debug, $name, $($fields)+);
196    };
197}
198
199#[macro_export]
200macro_rules! datapoint_trace {
201    ($name:expr, $($fields:tt)+) => {
202        $crate::datapoint!(log::Level::Trace, $name, $($fields)+);
203    };
204}
205
206#[cfg(test)]
207mod test {
208    #[test]
209    fn test_datapoint() {
210        datapoint_debug!("name", ("field name", "test", String));
211        datapoint_info!("name", ("field name", 12.34_f64, f64));
212        datapoint_trace!("name", ("field name", true, bool));
213        datapoint_warn!("name", ("field name", 1, i64));
214        datapoint_error!("name", ("field name", 1, i64),);
215        datapoint!(
216            log::Level::Warn,
217            "name",
218            ("field1 name", 2, i64),
219            ("field2 name", 2, i64)
220        );
221        datapoint_info!("name", ("field1 name", 2, i64), ("field2 name", 2, i64),);
222        datapoint_trace!(
223            "name",
224            ("field1 name", 2, i64),
225            ("field2 name", 2, i64),
226            ("field3 name", 3, i64)
227        );
228        datapoint!(
229            log::Level::Error,
230            "name",
231            ("field1 name", 2, i64),
232            ("field2 name", 2, i64),
233            ("field3 name", 3, i64),
234        );
235
236        let point = create_datapoint!(
237            @point "name",
238            ("i64", 1, i64),
239            ("String", "string space string", String),
240            ("f64", 12.34_f64, f64),
241            ("bool", true, bool)
242        );
243        assert_eq!(point.name, "name");
244        assert_eq!(point.tags.len(), 0);
245        assert_eq!(point.fields[0], ("i64", "1i".to_string()));
246        assert_eq!(
247            point.fields[1],
248            ("String", "\"string space string\"".to_string())
249        );
250        assert_eq!(point.fields[2], ("f64", "12.34".to_string()));
251        assert_eq!(point.fields[3], ("bool", "true".to_string()));
252    }
253
254    #[test]
255    fn test_optional_datapoint() {
256        datapoint_debug!("name", ("field name", Some("test"), Option<String>));
257        datapoint_info!("name", ("field name", Some(12.34_f64), Option<f64>));
258        datapoint_trace!("name", ("field name", Some(true), Option<bool>));
259        datapoint_warn!("name", ("field name", Some(1), Option<i64>));
260        datapoint_error!("name", ("field name", Some(1), Option<i64>),);
261        datapoint_debug!("name", ("field name", None::<String>, Option<String>));
262        datapoint_info!("name", ("field name", None::<f64>, Option<f64>));
263        datapoint_trace!("name", ("field name", None::<bool>, Option<bool>));
264        datapoint_warn!("name", ("field name", None::<i64>, Option<i64>));
265        datapoint_error!("name", ("field name", None::<i64>, Option<i64>),);
266
267        let point = create_datapoint!(
268            @point "name",
269            ("some_i64", Some(1), Option<i64>),
270            ("no_i64", None::<i64>, Option<i64>),
271            ("some_String", Some("string space string"), Option<String>),
272            ("no_String", None::<String>, Option<String>),
273            ("some_f64", Some(12.34_f64), Option<f64>),
274            ("no_f64", None::<f64>, Option<f64>),
275            ("some_bool", Some(true), Option<bool>),
276            ("no_bool", None::<bool>, Option<bool>),
277        );
278        assert_eq!(point.name, "name");
279        assert_eq!(point.tags.len(), 0);
280        assert_eq!(point.fields[0], ("some_i64", "1i".to_string()));
281        assert_eq!(
282            point.fields[1],
283            ("some_String", "\"string space string\"".to_string())
284        );
285        assert_eq!(point.fields[2], ("some_f64", "12.34".to_string()));
286        assert_eq!(point.fields[3], ("some_bool", "true".to_string()));
287        assert_eq!(point.fields.len(), 4);
288    }
289
290    #[test]
291    fn test_datapoint_with_tags() {
292        datapoint_debug!("name", "tag" => "tag-value", ("field name", "test", String));
293        datapoint_info!(
294            "name",
295            "tag" => "tag-value",
296            "tag2" => "tag-value-2",
297            ("field name", 12.34_f64, f64)
298        );
299        datapoint_trace!(
300            "name",
301            "tag" => "tag-value",
302            "tag2" => "tag-value-2",
303            "tag3" => "tag-value-3",
304            ("field name", true, bool)
305        );
306        datapoint_warn!("name", "tag" => "tag-value");
307        datapoint_error!("name", "tag" => "tag-value", ("field name", 1, i64),);
308        datapoint!(
309            log::Level::Warn,
310            "name",
311            "tag" => "tag-value",
312            ("field1 name", 2, i64),
313            ("field2 name", 2, i64)
314        );
315        datapoint_info!("name", ("field1 name", 2, i64), ("field2 name", 2, i64),);
316        datapoint_trace!(
317            "name",
318            "tag" => "tag-value",
319            ("field1 name", 2, i64),
320            ("field2 name", 2, i64),
321            ("field3 name", 3, i64)
322        );
323        datapoint!(
324            log::Level::Error,
325            "name",
326            "tag" => "tag-value",
327            ("field1 name", 2, i64),
328            ("field2 name", 2, i64),
329            ("field3 name", 3, i64),
330        );
331
332        let point = create_datapoint!(
333            @point "name",
334            "tag1" => "tag-value-1",
335            "tag2" => "tag-value-2",
336            "tag3" => "tag-value-3",
337            ("i64", 1, i64),
338            ("String", "string space string", String),
339            ("f64", 12.34_f64, f64),
340            ("bool", true, bool)
341        );
342        assert_eq!(point.name, "name");
343        assert_eq!(point.fields[0], ("i64", "1i".to_string()));
344        assert_eq!(
345            point.fields[1],
346            ("String", "\"string space string\"".to_string())
347        );
348        assert_eq!(point.fields[2], ("f64", "12.34".to_string()));
349        assert_eq!(point.fields[3], ("bool", "true".to_string()));
350        assert_eq!(point.tags[0], ("tag1", "tag-value-1".to_string()));
351        assert_eq!(point.tags[1], ("tag2", "tag-value-2".to_string()));
352        assert_eq!(point.tags[2], ("tag3", "tag-value-3".to_string()));
353    }
354}