profserve 0.1.0

HTTP-Enabled Profiling for Rust
Documentation
use crate::{
    error::{Error, Result},
    profile::Profiling,
};
use actix_web::{get, middleware::Logger, web, App, HttpResponse, HttpServer};
use serde::Deserialize;
use std::{sync::Mutex, time::Duration};

#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum OutputFormat {
    Text,
    Flamegraph,
}

#[derive(Debug, Deserialize)]
struct ProfileQuery {
    pub seconds: Option<u64>,
    pub frequency: Option<i32>,
    pub output: Option<OutputFormat>,
}

const DEFAULT_DURATION: Duration = Duration::from_secs(10);
const DEFAULT_FREQUENCY: i32 = 100;
const DEFAULT_OUTPUT: OutputFormat = OutputFormat::Flamegraph;

#[get("/prof/cpu")]
pub async fn cpu_profile(
    profiler: web::Data<Mutex<Profiling>>,
    query: web::Query<ProfileQuery>,
) -> Result<HttpResponse> {
    let query = query.into_inner();
    let duration = query
        .seconds
        .and_then(|v| Some(Duration::from_secs(v)))
        .unwrap_or(DEFAULT_DURATION);
    let frequency = query.frequency.unwrap_or(DEFAULT_FREQUENCY);
    let output = query.output.unwrap_or(DEFAULT_OUTPUT);
    let mut profiler = profiler.lock()?;
    *profiler = Profiling::new(duration, frequency);
    let profile = match output {
        OutputFormat::Text => {
            let text = profiler.dump_text().await?;
            Ok(HttpResponse::Ok().body(text))
        }
        OutputFormat::Flamegraph => {
            let flamegraph = profiler.dump_flamegraph().await?;
            Ok(HttpResponse::Ok().body(flamegraph))
        }
    };
    profile
}

pub fn serve(port: u16) -> Result<()> {
    actix_web::rt::System::new().block_on(async {
        let profiler = web::Data::new(Mutex::new(Profiling::new(
            DEFAULT_DURATION,
            DEFAULT_FREQUENCY,
        )));
        HttpServer::new(move || {
            App::new()
                .wrap(Logger::default())
                .app_data(profiler.clone())
                .service(cpu_profile)
        })
        .bind(format!("0.0.0.0:{port}"))
        .map_err(|_| Error::General(format!("Port {port} already in use.")))?
        .run()
        .await
        .map_err(|e| Error::General(e.to_string()))?;
        Ok(())
    })
}