victoria_client 0.1.1

Client library for Victoria Metrics DB
Documentation
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();
        // They use ms since epoch, not seconds
        let stamp = (self.stamp.timestamp() * 1000).to_string();

        format!("{name}{{{labels_string}}} {value} {stamp}")
    }
}