datadog_client/
metrics.rs

1use crate::client::{Client, Error};
2use serde::ser::SerializeSeq;
3use serde::{Serialize, Serializer};
4
5#[derive(Clone, Debug, Serialize)]
6#[serde(rename_all = "snake_case")]
7pub enum Type {
8    Count,
9    Gauge,
10    Rate,
11}
12
13#[derive(Clone, Debug)]
14pub struct Point {
15    timestamp: u64,
16    value: f64,
17}
18
19impl Point {
20    pub fn new(timestamp: u64, value: f64) -> Self {
21        Self { timestamp, value }
22    }
23}
24
25impl Serialize for Point {
26    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27    where
28        S: Serializer,
29    {
30        let mut seq = serializer.serialize_seq(Some(2))?;
31        seq.serialize_element(&self.timestamp)?;
32        seq.serialize_element(&self.value)?;
33        seq.end()
34    }
35}
36
37/// # Examples
38///
39/// ```
40/// use datadog_client::metrics::{Point, Serie, Type};
41///
42/// let serie = Serie::new("cpu.usage".to_string(), Type::Gauge)
43///     .set_host("raspberrypi".to_string())
44///     .set_interval(42)
45///     .set_points(vec![])
46///     .add_point(Point::new(123456, 12.34))
47///     .set_tags(vec![])
48///     .add_tag("whatever:tag".to_string());
49/// ```
50#[derive(Debug, Clone, Serialize)]
51pub struct Serie {
52    // The name of the host that produced the metric.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    host: Option<String>,
55    // If the type of the metric is rate or count, define the corresponding interval.
56    #[serde(skip_serializing_if = "Option::is_none")]
57    interval: Option<i64>,
58    // The name of the timeseries.
59    metric: String,
60    // Points relating to a metric. All points must be tuples with timestamp and a scalar value (cannot be a string).
61    // Timestamps should be in POSIX time in seconds, and cannot be more than ten minutes in the future or more than one hour in the past.
62    points: Vec<Point>,
63    // A list of tags associated with the metric.
64    tags: Vec<String>,
65    // The type of the metric either count, gauge, or rate.
66    #[serde(rename = "type")]
67    dtype: Type,
68}
69
70impl Serie {
71    pub fn new(metric: String, dtype: Type) -> Self {
72        Self {
73            host: None,
74            interval: None,
75            metric,
76            points: Vec::new(),
77            tags: Vec::new(),
78            dtype,
79        }
80    }
81}
82
83impl Serie {
84    pub fn set_host(mut self, host: String) -> Self {
85        self.host = Some(host);
86        self
87    }
88
89    pub fn set_interval(mut self, interval: i64) -> Self {
90        self.interval = Some(interval);
91        self
92    }
93
94    pub fn set_points(mut self, points: Vec<Point>) -> Self {
95        self.points = points;
96        self
97    }
98
99    pub fn add_point(mut self, point: Point) -> Self {
100        self.points.push(point);
101        self
102    }
103
104    pub fn set_tags(mut self, tags: Vec<String>) -> Self {
105        self.tags = tags;
106        self
107    }
108
109    pub fn add_tag(mut self, tag: String) -> Self {
110        self.tags.push(tag);
111        self
112    }
113}
114
115impl Client {
116    /// Submit metrics
117    ///
118    /// https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
119    ///
120    pub async fn post_metrics(&self, series: &[Serie]) -> Result<(), Error> {
121        let payload = serde_json::json!({ "series": series });
122        self.post("/api/v1/series", &payload).await
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use mockito::mock;
130
131    #[test]
132    fn serialize_point() {
133        let point = Point::new(1234, 12.34);
134        assert_eq!(serde_json::to_string(&point).unwrap(), "[1234,12.34]");
135    }
136
137    #[test]
138    fn serialize_serie() {
139        let serie = Serie::new("metric".to_string(), Type::Count)
140            .add_point(Point::new(1234, 1.234))
141            .add_tag("tag".to_string())
142            .set_host("host".to_string());
143        assert_eq!(
144            serde_json::to_string(&serie).unwrap(),
145            "{\"host\":\"host\",\"metric\":\"metric\",\"points\":[[1234,1.234]],\"tags\":[\"tag\"],\"type\":\"count\"}"
146        );
147    }
148
149    #[tokio::test]
150    async fn post_metrics_success() {
151        let call = mock("POST", "/api/v1/series").with_status(202).create();
152        let client = Client::new(mockito::server_url(), "fake-api-key".to_string());
153        let series = vec![
154            Serie::new("something".to_string(), Type::Gauge).add_point(Point::new(1234, 12.34))
155        ];
156        let result = client.post_metrics(&series).await;
157        assert!(result.is_ok());
158        call.expect(1);
159    }
160
161    #[tokio::test]
162    async fn post_metrics_unauthorized() {
163        let call = mock("POST", "/api/v1/series")
164            .with_status(403)
165            .with_body("{\"errors\":[\"Authentication error\"]}")
166            .create();
167        let client = Client::new(mockito::server_url(), "fake-api-key".to_string());
168        let series = vec![
169            Serie::new("something".to_string(), Type::Gauge).add_point(Point::new(1234, 12.34))
170        ];
171        let result = client.post_metrics(&series).await;
172        assert!(result.is_err());
173        call.expect(1);
174    }
175}