use crate::NodeDelegate;
#[cfg(not(target_env = "msvc"))]
mod jemalloc_profiling {
use axum::body::Body;
use axum::extract::Query;
use axum::response::{IntoResponse, Response};
use axum::Json;
use http::header::CONTENT_TYPE;
use http::StatusCode;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct HeapQuery {
format: Option<String>,
}
async fn handle_get_heap(Query(params): Query<HeapQuery>) -> Result<impl IntoResponse, (StatusCode, String)> {
let Some(ctl) = jemalloc_pprof::PROF_CTL.as_ref() else {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"jemalloc profiling is disabled and cannot be activated".into(),
));
};
let mut prof_ctl = ctl.lock().await;
require_profiling_activated(&prof_ctl)?;
match params.format.as_deref() {
Some("flame") => {
let svg = prof_ctl
.dump_flamegraph()
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
Response::builder()
.header(CONTENT_TYPE, "image/svg+xml")
.body(Body::from(svg))
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))
}
_ => {
let pprof = prof_ctl
.dump_pprof()
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
Response::builder()
.header(CONTENT_TYPE, "application/octet-stream")
.body(Body::from(pprof))
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))
}
}
}
fn require_profiling_activated(prof_ctl: &jemalloc_pprof::JemallocProfCtl) -> Result<(), (StatusCode, String)> {
if prof_ctl.activated() {
Ok(())
} else {
Err((
axum::http::StatusCode::FORBIDDEN,
"heap profiling is not activate. Activate by POSTing to /heap/settings?enabled=true".into(),
))
}
}
#[derive(Deserialize)]
struct ToggleQuery {
enabled: bool,
}
#[derive(Serialize)]
struct CurrentState {
enabled: bool,
}
async fn handle_post_heap_enabled(
Query(params): Query<ToggleQuery>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let Some(ctl) = jemalloc_pprof::PROF_CTL.as_ref() else {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"jemalloc profiling is disabled and cannot be activated".into(),
));
};
let mut prof_ctl = ctl.lock().await;
if params.enabled {
prof_ctl.activate().map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to activate heap profiling: {e}"),
)
})?;
Ok(("Heap profiling activated").into_response())
} else {
prof_ctl.deactivate().map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to deactivate heap profiling: {e}"),
)
})?;
Ok(("Heap profiling deactivated").into_response())
}
}
async fn handle_get_heap_enabled() -> Result<impl IntoResponse, (StatusCode, String)> {
let Some(ctl) = jemalloc_pprof::PROF_CTL.as_ref() else {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"jemalloc profiling is disabled and cannot be activated.".into(),
));
};
let prof_ctl = ctl.lock().await;
let current_state = CurrentState {
enabled: prof_ctl.activated(),
};
Ok(Json(current_state))
}
pub fn jemalloc_router<S: Clone + Send + Sync + 'static>() -> axum::Router<S> {
use axum::routing::get;
axum::Router::new()
.route("/", get(handle_get_heap))
.route("/settings", get(handle_get_heap_enabled).post(handle_post_heap_enabled))
}
}
#[cfg(target_env = "msvc")]
mod jemalloc_profiling {
use axum::response::IntoResponse;
use http::StatusCode;
async fn jemalloc_unsupported() -> impl IntoResponse {
(
StatusCode::INTERNAL_SERVER_ERROR,
"jemalloc heap profiling is not supported on this platform.",
)
}
pub fn jemalloc_router<S: Clone + Send + Sync + 'static>() -> axum::Router<S> {
use axum::routing::get;
axum::Router::new()
.route("/", get(jemalloc_unsupported))
.route("/settings", get(jemalloc_unsupported).post(jemalloc_unsupported))
}
}
pub fn router<S>() -> axum::Router<S>
where
S: NodeDelegate + Clone + 'static,
{
axum::Router::new().nest("/heap", jemalloc_profiling::jemalloc_router())
}