10 changed files with 876 additions and 103 deletions
-
283seaweed-volume/Cargo.lock
-
1seaweed-volume/Cargo.toml
-
111seaweed-volume/src/main.rs
-
151seaweed-volume/src/metrics.rs
-
159seaweed-volume/src/server/debug.rs
-
78seaweed-volume/src/server/heartbeat.rs
-
5seaweed-volume/src/server/mod.rs
-
112seaweed-volume/src/server/volume_server.rs
-
34seaweed-volume/src/server/write_queue.rs
-
45seaweed-volume/tests/http_integration.rs
@ -0,0 +1,159 @@ |
|||||
|
use axum::body::Body;
|
||||
|
use axum::extract::Query;
|
||||
|
use axum::http::{header, StatusCode};
|
||||
|
use axum::response::{IntoResponse, Response};
|
||||
|
use axum::routing::{any, get};
|
||||
|
use axum::Router;
|
||||
|
use pprof::protos::Message;
|
||||
|
use serde::Deserialize;
|
||||
|
|
||||
|
#[derive(Deserialize, Default)]
|
||||
|
struct ProfileQuery {
|
||||
|
seconds: Option<u64>,
|
||||
|
}
|
||||
|
|
||||
|
pub fn build_debug_router() -> Router {
|
||||
|
Router::new()
|
||||
|
.route("/debug/pprof/", get(pprof_index_handler))
|
||||
|
.route("/debug/pprof/cmdline", get(pprof_cmdline_handler))
|
||||
|
.route("/debug/pprof/profile", get(pprof_profile_handler))
|
||||
|
.route("/debug/pprof/symbol", any(pprof_symbol_handler))
|
||||
|
.route("/debug/pprof/trace", get(pprof_trace_handler))
|
||||
|
}
|
||||
|
|
||||
|
async fn pprof_index_handler() -> Response {
|
||||
|
let body = concat!(
|
||||
|
"<html><head><title>/debug/pprof/</title></head><body>",
|
||||
|
"<a href=\"cmdline\">cmdline</a><br>",
|
||||
|
"<a href=\"profile\">profile</a><br>",
|
||||
|
"<a href=\"symbol\">symbol</a><br>",
|
||||
|
"<a href=\"trace\">trace</a><br>",
|
||||
|
"</body></html>",
|
||||
|
);
|
||||
|
(
|
||||
|
StatusCode::OK,
|
||||
|
[(header::CONTENT_TYPE, "text/html; charset=utf-8")],
|
||||
|
body,
|
||||
|
)
|
||||
|
.into_response()
|
||||
|
}
|
||||
|
|
||||
|
async fn pprof_cmdline_handler() -> Response {
|
||||
|
let body = std::env::args().collect::<Vec<_>>().join("\0");
|
||||
|
(
|
||||
|
StatusCode::OK,
|
||||
|
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
|
||||
|
body,
|
||||
|
)
|
||||
|
.into_response()
|
||||
|
}
|
||||
|
|
||||
|
async fn pprof_profile_handler(Query(query): Query<ProfileQuery>) -> Response {
|
||||
|
let seconds = query.seconds.unwrap_or(30).clamp(1, 300);
|
||||
|
let guard = match pprof::ProfilerGuard::new(100) {
|
||||
|
Ok(guard) => guard,
|
||||
|
Err(e) => {
|
||||
|
return (
|
||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
format!("failed to start profiler: {}", e),
|
||||
|
)
|
||||
|
.into_response();
|
||||
|
}
|
||||
|
};
|
||||
|
|
||||
|
tokio::time::sleep(std::time::Duration::from_secs(seconds)).await;
|
||||
|
|
||||
|
let report = match guard.report().build() {
|
||||
|
Ok(report) => report,
|
||||
|
Err(e) => {
|
||||
|
return (
|
||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
format!("failed to build profile report: {}", e),
|
||||
|
)
|
||||
|
.into_response();
|
||||
|
}
|
||||
|
};
|
||||
|
|
||||
|
let profile = match report.pprof() {
|
||||
|
Ok(profile) => profile,
|
||||
|
Err(e) => {
|
||||
|
return (
|
||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
format!("failed to encode profile: {}", e),
|
||||
|
)
|
||||
|
.into_response();
|
||||
|
}
|
||||
|
};
|
||||
|
|
||||
|
let mut bytes = Vec::new();
|
||||
|
if let Err(e) = profile.encode(&mut bytes) {
|
||||
|
return (
|
||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
format!("failed to serialize profile: {}", e),
|
||||
|
)
|
||||
|
.into_response();
|
||||
|
}
|
||||
|
|
||||
|
(
|
||||
|
StatusCode::OK,
|
||||
|
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||
|
bytes,
|
||||
|
)
|
||||
|
.into_response()
|
||||
|
}
|
||||
|
|
||||
|
async fn pprof_symbol_handler() -> Response {
|
||||
|
(
|
||||
|
StatusCode::OK,
|
||||
|
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
|
||||
|
"num_symbols: 0\n",
|
||||
|
)
|
||||
|
.into_response()
|
||||
|
}
|
||||
|
|
||||
|
async fn pprof_trace_handler(Query(query): Query<ProfileQuery>) -> Response {
|
||||
|
let seconds = query.seconds.unwrap_or(1).clamp(1, 30);
|
||||
|
tokio::time::sleep(std::time::Duration::from_secs(seconds)).await;
|
||||
|
Response::builder()
|
||||
|
.status(StatusCode::OK)
|
||||
|
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||
|
.body(Body::from(Vec::<u8>::new()))
|
||||
|
.unwrap()
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use super::*;
|
||||
|
use axum::http::Request;
|
||||
|
use tower::ServiceExt;
|
||||
|
|
||||
|
#[tokio::test]
|
||||
|
async fn test_debug_index_route() {
|
||||
|
let app = build_debug_router();
|
||||
|
let response = app
|
||||
|
.oneshot(
|
||||
|
Request::builder()
|
||||
|
.uri("/debug/pprof/")
|
||||
|
.body(Body::empty())
|
||||
|
.unwrap(),
|
||||
|
)
|
||||
|
.await
|
||||
|
.unwrap();
|
||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
}
|
||||
|
|
||||
|
#[tokio::test]
|
||||
|
async fn test_debug_cmdline_route() {
|
||||
|
let app = build_debug_router();
|
||||
|
let response = app
|
||||
|
.oneshot(
|
||||
|
Request::builder()
|
||||
|
.uri("/debug/pprof/cmdline")
|
||||
|
.body(Body::empty())
|
||||
|
.unwrap(),
|
||||
|
)
|
||||
|
.await
|
||||
|
.unwrap();
|
||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
}
|
||||
|
}
|
||||
@ -1,5 +1,6 @@ |
|||||
pub mod volume_server;
|
|
||||
pub mod handlers;
|
|
||||
|
pub mod debug;
|
||||
pub mod grpc_server;
|
pub mod grpc_server;
|
||||
|
pub mod handlers;
|
||||
pub mod heartbeat;
|
pub mod heartbeat;
|
||||
|
pub mod volume_server;
|
||||
pub mod write_queue;
|
pub mod write_queue;
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue