use std::{collections::HashMap, fmt::Display};
use anyhow::bail;
use aphomie_db_client::MetadataTrendDataJoinDb;
use chrono::{DateTime, Utc};
use reqwest::{Client, Response};
pub struct PromEntry<T>
where
T: Display,
{
pub name: String,
pub labels: HashMap<String, String>,
pub value: T,
pub stamp: DateTime<Utc>,
}
pub struct VictoriaClient {
client: Client,
host: String,
port: String
}
impl VictoriaClient {
pub fn new(host: String, port: String) -> Self {
let client = Client::new();
VictoriaClient { client, host, port }
}
pub async fn insert<T>(&self, entries: &Vec<PromEntry<T>>) -> Result<Response, reqwest::Error>
where
T: Display,
{
let queries: Vec<String> = entries.iter().map(|e| e.to_query()).collect();
let host = &self.host;
let port = &self.port;
let post_body = queries.join("\n");
let url = format!("http://{host}:{port}/api/v1/import/prometheus");
let response = self.client.post(url).body(post_body).send().await?;
let response = response.error_for_status()?;
Ok(response)
}
}
impl TryFrom<MetadataTrendDataJoinDb> for PromEntry<f64> {
type Error = anyhow::Error;
fn try_from(value: MetadataTrendDataJoinDb) -> std::result::Result<Self, Self::Error> {
if value.value.is_none() {
bail!("Value was null!");
}
let mut s: Vec<&str> = value.source.split('/').collect();
let name = s.pop();
if let None = name {
bail!("source did not contain a name!");
}
let name = name.unwrap().to_string();
let location = s.join("/");
let labels = HashMap::from([("location".to_string(), location)]);
let stamp = value.timestamp;
let value = value.value.unwrap();
Ok(PromEntry {
name,
labels,
stamp,
value,
})
}
}
impl<T> PromEntry<T>
where
T: Display,
{
pub fn to_query(&self) -> String {
let parsed_labels: Vec<String> = self
.labels
.iter()
.map(|(k, v)| format!("{}=\"{}\"", k, v))
.collect();
let labels_string = parsed_labels.join(",");
let name = self.name.clone();
let value = self.value.to_string();
let stamp = (self.stamp.timestamp() * 1000).to_string();
format!("{name}{{{labels_string}}} {value} {stamp}")
}
}