tracing-prof 0.3.0

Experimental library for profiling tracing spans.
Documentation
//! Pyroscope backend for pprof profiles.

use std::time::SystemTime;

use reqwest::blocking::Client;

use super::PProfBackend;

/// A backend configuration for the pprof file backend.
#[derive(Debug, Clone)]
#[must_use]
pub struct PyroscopeBackendConfig {
    /// The ingestion URL for the pyroscope server.
    ingestion_url: String,
    /// The name of teh application.
    app_name: String,
}

impl PyroscopeBackendConfig {
    /// Create a new configuration for the pprof file backend.
    pub fn new(application_name: impl Into<String>, ingestion_url: impl Into<String>) -> Self {
        Self {
            app_name: application_name.into(),
            ingestion_url: ingestion_url.into(),
        }
    }
}

/// A backend that writes pprof profiles to a files in a specified directory.
#[must_use]
pub struct PyroscopeBackend {
    config: PyroscopeBackendConfig,
    client: Client,
}

impl PyroscopeBackend {
    /// Create a new backend.
    ///
    /// # Panics
    ///
    /// Panics only due to bugs.
    pub fn new(config: PyroscopeBackendConfig) -> Self {
        Self {
            client: Client::builder()
                .user_agent(&config.app_name)
                .build()
                .unwrap(),
            config,
        }
    }
}

impl PProfBackend for PyroscopeBackend {
    fn save_profile(
        &self,
        kind: &str,
        start_time: SystemTime,
        end_time: SystemTime,
        profile_data: Vec<u8>,
    ) {
        let url = format!(
            "{}?format=pprof&name={}&from={}&until={}",
            self.config.ingestion_url,
            self.config.app_name,
            start_time
                .duration_since(SystemTime::UNIX_EPOCH)
                .unwrap()
                .as_secs(),
            end_time
                .duration_since(SystemTime::UNIX_EPOCH)
                .unwrap()
                .as_secs()
        );

        match self
            .client
            .post(&url)
            .header("Content-Type", "application/octet-stream")
            .body(profile_data)
            .send()
        {
            Ok(res) => {
                let status = res.status();

                if status.is_success() {
                    tracing::trace!("profile sent to pyroscope");
                } else {
                    let res_text = res.text().unwrap_or_default();
                    tracing::warn!(
                        "failed to send profile `{kind}` to pyroscope: ({}) {}",
                        status,
                        res_text
                    );
                }
            }
            Err(error) => {
                tracing::warn!("failed to send profile `{kind}` to pyroscope: {}", error);
            }
        }
    }
}