algolia_monitoring_rs/
lib.rs

1use reqwest::Client;
2use serde::de::DeserializeOwned;
3use serde::Deserialize;
4use std::collections::HashMap;
5
6#[derive(Debug, Deserialize)]
7pub struct DataPoint {
8    t: u64,
9    v: u32,
10}
11
12#[derive(Debug, Deserialize)]
13pub struct Error {
14    reason: String,
15}
16
17#[derive(Debug, Deserialize)]
18pub struct Status {
19    status: HashMap<String, String>
20}
21
22#[derive(Debug, Deserialize)]
23pub struct Incident {
24    t: i64,
25    v: IncidentDetails,
26}
27
28#[derive(Debug, Deserialize)]
29pub struct IncidentDetails {
30    title: String,
31    body: String,
32    status: String,
33}
34
35#[derive(Debug, Deserialize)]
36pub struct Incidents {
37    incidents: HashMap<String, Vec<Incident>>,
38}
39
40#[derive(Debug, Deserialize)]
41pub struct InventoryItem {
42    name: String,
43    region: String,
44    is_replica: bool,
45    cluster: String,
46}
47
48#[derive(Debug, Deserialize)]
49pub struct Inventory {
50    inventory: Vec<InventoryItem>,
51}
52
53#[derive(Debug, Deserialize)]
54pub struct MetricsGroup {
55    #[serde(default)]
56    latency: Option<HashMap<String, Vec<DataPoint>>>,
57    #[serde(default)]
58    indexing: Option<HashMap<String, Vec<DataPoint>>>,
59    #[serde(default)]
60    avg_build_time: Option<HashMap<String, Vec<DataPoint>>>,
61    #[serde(default)]
62    ssd_usage: Option<HashMap<String, Vec<DataPoint>>>,
63    #[serde(default)]
64    ram_search_usage: Option<HashMap<String, Vec<DataPoint>>>,
65    #[serde(default)]
66    ram_indexing_usage: Option<HashMap<String, Vec<DataPoint>>>,
67    #[serde(default)]
68    cpu_usage: Option<HashMap<String, Vec<DataPoint>>>,
69}
70
71#[derive(Debug, Deserialize)]
72pub struct Metrics {
73    metrics: MetricsGroup,
74}
75
76pub struct AlgoliaMonitoring {
77    api_key: Option<String>,
78    app_id: Option<String>,
79}
80
81impl AlgoliaMonitoring {
82    pub fn new(api_key: Option<String>, app_id: Option<String>) -> Self {
83        AlgoliaMonitoring { api_key, app_id }
84    }
85
86    /// Get the status of the Algolia servers
87    /// `servers` is an optional list of servers to get the status of, if None, all servers are returned
88    pub async fn get_status(&self, servers: Option<Vec<String>>) -> Result<Status, Error> {
89        let servers = match servers.map(|s| s.join(",")) {
90            Some(s) => format!("/{}", s),
91            None => "".to_owned(),
92        };
93
94        let path = format!("status{}", servers);
95        self.fetch_data::<Status>(path.as_str()).await
96    }
97
98    /// Get the incidents of the Algolia servers
99    /// `servers` is an optional list of servers to get the incidents of, if None, all servers are returned
100    pub async fn get_incidents(&self, servers: Option<Vec<String>>) -> Result<Incidents, Error> {
101        let servers = match servers.map(|s| s.join(",")) {
102            Some(s) => format!("/{}", s),
103            None => "".to_owned(),
104        };
105        let path = format!("incidents/{}", servers);
106        self.fetch_data::<Incidents>(path.as_str()).await
107    }
108
109    /// Get the inventory of the Algolia servers
110    pub async fn get_inventory(&self) -> Result<Inventory, Error> {
111        self.fetch_data::<Inventory>("inventory").await
112    }
113
114    /// Get the latency of the Algolia servers
115    /// `servers` is a list of servers to get the latency of
116    pub async fn get_latency(&self, servers: Vec<String>) -> Result<Metrics, Error> {
117        let servers = servers.join(",");
118        let path = format!("latency/{}", servers);
119        self.fetch_data::<Metrics>(path.as_str()).await
120    }
121
122    /// This method gets the reachability for the servers passed in the URL
123    /// `servers` is a list of servers to get the reachability of
124    pub async fn get_reachability(&self, servers: Vec<String>) -> Result<Metrics, Error> {
125        let servers = servers.join(",");
126        let path = format!("reachability/{}/probes", servers);
127        self.fetch_data::<Metrics>(path.as_str()).await
128    }
129
130    /// This method gets a metric over a period of time
131    /// `metric` is the metric to get
132    /// - `avg_build_time`: Average build time of the indices in seconds
133    /// - `ssd_usage`: proportion of SSD vs RAM usage in % (0% means no SSD utilization, 32 GB storage used on 64 GB RAM system is 50%)
134    /// - `ram_search_usage`: RAM usage for the search in MB
135    /// - `ram_indexing_usage`: RAM usage for the indexing in MB
136    /// - `cpu_usage`: proportion of CPU idleness in % (0% means the CPU isn’t idle)
137    /// - `*`: All of the above
138    /// `period` is the period of time to get the metric over
139    /// - `minute`: 1 minute ago, 1 point per 10 seconds (10 points)
140    /// - `hour`: 1 hour ago, 1 point per 1 minute (60 points)
141    /// - `day`: 1 day ago, 1 point per 10 minutes (144 points)
142    /// - `week`: 1 week ago, 1 point per 1 hour (168 points)
143    /// - `month`: 1 month ago, 1 point per 1 day (30 points)
144    pub async fn get_infrastructure_metrics(&self, metric: String, period: String) -> Result<Metrics, Error> {
145        let path = format!("infrastructure/{}/period/{}", metric, period);
146        self.fetch_data::<Metrics>(path.as_str()).await
147    }
148
149    fn get_http_client(&self) -> Result<Client, reqwest::Error> {
150        let mut headers = reqwest::header::HeaderMap::new();
151        if !self.api_key.is_some() && !self.app_id.is_some() {
152            headers.insert("X-Algolia-API-Key", self.api_key.as_ref().unwrap().parse().unwrap());
153            headers.insert("X-Algolia-Application-Id", self.app_id.as_ref().unwrap().parse().unwrap());
154        }
155        Client::builder().default_headers(headers).build()
156    }
157
158    fn get_endpoint(&self) -> String {
159        "https://status.algolia.com/1".to_owned()
160    }
161
162    async fn fetch_data<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
163        let client = self.get_http_client().map_err(|e| Error {
164            reason: e.to_string(),
165        })?;
166
167        let url = format!("{}/{}", self.get_endpoint(), path);
168        let response = client.get(&url).send().await.map_err(|e| Error {
169            reason: e.to_string(),
170        })?;
171
172        response.json::<T>().await.map_err(|e| Error {
173            reason: e.to_string(),
174        })
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    fn create_algolia_monitoring() -> AlgoliaMonitoring {
183        AlgoliaMonitoring::new(Some("your_api_key".to_string()), Some("your_app_id".to_string()))
184    }
185
186    #[test]
187    fn test_new() {
188        let algolia_monitoring = create_algolia_monitoring();
189        assert_eq!(algolia_monitoring.api_key, Some("your_api_key".to_string()));
190        assert_eq!(algolia_monitoring.app_id, Some("your_app_id".to_string()));
191    }
192}