Skip to content

Commit 61cb33a

Browse files
committed
chore: more consistent pprof endpoints
1 parent d195f35 commit 61cb33a

File tree

1 file changed

+63
-39
lines changed

1 file changed

+63
-39
lines changed

pulsebeam/src/node.rs

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,13 @@ mod internal {
160160
use axum::{
161161
Router,
162162
extract::Query,
163-
response::{Html, IntoResponse},
163+
response::{Html, IntoResponse, Response},
164164
routing::get,
165165
};
166-
use hyper::StatusCode;
166+
use hyper::{
167+
StatusCode,
168+
header::{CONTENT_DISPOSITION, CONTENT_TYPE},
169+
};
167170
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
168171
use pprof::{ProfilerGuard, protos::Message};
169172
use pulsebeam_runtime::actor::Actor;
@@ -173,7 +176,7 @@ mod internal {
173176
use crate::{controller, gateway, participant, room, shard};
174177

175178
#[derive(Deserialize)]
176-
struct ProfileParams {
179+
pub struct ProfileParams {
177180
#[serde(default = "default_seconds")]
178181
seconds: u64,
179182
#[serde(default)]
@@ -195,7 +198,7 @@ mod internal {
195198
<li><a href="/debug/pprof/profile?seconds=30">CPU Profile (pprof)</a></li>
196199
<li><a href="/debug/pprof/profile?seconds=30&flamegraph=true">CPU Flamegraph</a></li>
197200
<li><a href="/debug/pprof/allocs?seconds=30">Memory Profile (pprof)</a></li>
198-
<li><a href="/debug/pprof/allocs/flamegraph?seconds=30">Memory Flamegraph</a></li>
201+
<li><a href="/debug/pprof/allocs?seconds=30&flamegraph=true">Memory Flamegraph</a></li>
199202
</ul>
200203
"#;
201204

@@ -209,11 +212,7 @@ mod internal {
209212
}),
210213
)
211214
.route("/debug/pprof/profile", get(pprof_profile))
212-
.route("/debug/pprof/allocs", axum::routing::get(handle_get_heap))
213-
.route(
214-
"/debug/pprof/allocs/flamegraph",
215-
axum::routing::get(handle_get_heap_flamegraph),
216-
)
215+
.route("/debug/pprof/allocs", axum::routing::get(heap_profile))
217216
.route("/", get(|| async { Html(INDEX_HTML) }))
218217
.with_state(());
219218

@@ -301,29 +300,44 @@ mod internal {
301300
}
302301
}
303302

304-
pub async fn handle_get_heap() -> Result<impl IntoResponse, (StatusCode, String)> {
303+
pub async fn heap_profile(
304+
Query(params): Query<ProfileParams>,
305+
) -> Result<Response, (StatusCode, String)> {
305306
let mut prof_ctl = jemalloc_pprof::PROF_CTL.as_ref().unwrap().lock().await;
306307
require_profiling_activated(&prof_ctl)?;
307-
let pprof = prof_ctl
308-
.dump_pprof()
309-
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
310-
Ok(pprof)
311-
}
312308

313-
pub async fn handle_get_heap_flamegraph() -> Result<impl IntoResponse, (StatusCode, String)> {
314-
use axum::body::Body;
315-
use axum::http::header::CONTENT_TYPE;
316-
use axum::response::Response;
309+
let resp = if params.flamegraph {
310+
use axum::http::header::CONTENT_TYPE;
317311

318-
let mut prof_ctl = jemalloc_pprof::PROF_CTL.as_ref().unwrap().lock().await;
319-
require_profiling_activated(&prof_ctl)?;
320-
let svg = prof_ctl
321-
.dump_flamegraph()
322-
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
323-
Response::builder()
324-
.header(CONTENT_TYPE, "image/svg+xml")
325-
.body(Body::from(svg))
326-
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))
312+
let svg = prof_ctl
313+
.dump_flamegraph()
314+
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
315+
316+
(
317+
axum::http::StatusCode::OK,
318+
[
319+
(CONTENT_TYPE, "image/svg+xml"),
320+
(CONTENT_DISPOSITION, "attachment; filename=allocs.svg"),
321+
],
322+
svg,
323+
)
324+
.into_response()
325+
} else {
326+
let pprof = prof_ctl
327+
.dump_pprof()
328+
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
329+
330+
(
331+
axum::http::StatusCode::OK,
332+
[
333+
(CONTENT_TYPE, "application/octet-stream"),
334+
(CONTENT_DISPOSITION, "attachment; filename=allocs.pprof"),
335+
],
336+
pprof,
337+
)
338+
.into_response()
339+
};
340+
Ok(resp)
327341
}
328342

329343
/// Checks whether jemalloc profiling is activated an returns an error response if not.
@@ -341,32 +355,40 @@ mod internal {
341355
}
342356

343357
/// Handler: /debug/pprof/profile?seconds=30&flamegraph=true
344-
async fn pprof_profile(Query(params): Query<ProfileParams>) -> impl IntoResponse {
358+
async fn pprof_profile(
359+
Query(params): Query<ProfileParams>,
360+
) -> Result<impl IntoResponse, (StatusCode, String)> {
345361
let guard = ProfilerGuard::new(100).unwrap(); // 100 Hz sampling
346362
tokio::time::sleep(Duration::from_secs(params.seconds)).await;
347363

348-
match guard.report().build() {
364+
let resp = match guard.report().build() {
349365
Ok(report) => {
350366
if params.flamegraph {
351367
let mut body = Vec::new();
352-
if let Err(e) = report.flamegraph(&mut body) {
353-
return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
354-
.into_response();
355-
}
368+
report
369+
.flamegraph(&mut body)
370+
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
371+
356372
(
357373
axum::http::StatusCode::OK,
358-
[("Content-Type", "image/svg+xml")],
374+
[
375+
(CONTENT_TYPE, "image/svg+xml"),
376+
(CONTENT_DISPOSITION, "attachment; filename=cpu.svg"),
377+
],
359378
body,
360379
)
361380
.into_response()
362381
} else {
363-
let profile = report.pprof().unwrap();
382+
let profile = report
383+
.pprof()
384+
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
385+
364386
let body = profile.encode_to_vec();
365387
(
366388
axum::http::StatusCode::OK,
367389
[
368-
("Content-Type", "application/octet-stream"),
369-
("Content-Disposition", "attachment; filename=cpu.pprof"),
390+
(CONTENT_TYPE, "application/octet-stream"),
391+
(CONTENT_DISPOSITION, "attachment; filename=cpu.pprof"),
370392
],
371393
body,
372394
)
@@ -378,7 +400,9 @@ mod internal {
378400
format!("Failed to build pprof report: {e}"),
379401
)
380402
.into_response(),
381-
}
403+
};
404+
405+
Ok(resp)
382406
}
383407
}
384408

0 commit comments

Comments
 (0)