krabka_telemetry/profiling/
server.rs1use {
9 super::{
10 config::{ProfilingConfig, ProfilingError},
11 routes::{pprof_router, pprof_router_with_config},
12 },
13 axum::Router,
14 std::{
15 env,
16 io::{self, Error},
17 net::SocketAddr,
18 },
19 tokio::{net::TcpListener, task::JoinHandle},
20};
21
22pub async fn serve_admin(addr: SocketAddr, extra: Router) -> io::Result<()> {
30 serve_router(addr, pprof_router().merge(extra)).await
31}
32
33async fn serve_router(addr: SocketAddr, app: Router) -> io::Result<()> {
34 let task = spawn_router(addr, app).await?;
35 tokio::spawn(async move {
36 match task.await {
37 Ok(Ok(())) => tracing::warn!("admin server stopped unexpectedly"),
38 Ok(Err(error)) => tracing::warn!(%error, "admin server error"),
39 Err(error) => tracing::warn!(%error, "admin server task failed"),
40 }
41 });
42 Ok(())
43}
44
45async fn spawn_router(addr: SocketAddr, app: Router) -> io::Result<JoinHandle<io::Result<()>>> {
46 let listener = TcpListener::bind(addr).await?;
47 let bound = listener.local_addr()?;
48 tracing::info!(%bound, "profiling admin server listening");
49 Ok(tokio::spawn(
50 async move { axum::serve(listener, app).await },
51 ))
52}
53
54pub async fn serve_admin_with_config(
59 addr: SocketAddr,
60 extra: Router,
61 config: ProfilingConfig,
62) -> Result<(), ProfilingError> {
63 let app = pprof_router_with_config(config)?.merge(extra);
64 serve_router(addr, app).await?;
65 Ok(())
66}
67
68pub async fn spawn_admin_with_config(
73 addr: SocketAddr,
74 extra: Router,
75 config: ProfilingConfig,
76) -> Result<JoinHandle<io::Result<()>>, ProfilingError> {
77 let app = pprof_router_with_config(config)?.merge(extra);
78 Ok(spawn_router(addr, app).await?)
79}
80
81pub async fn await_admin_exit(task: JoinHandle<io::Result<()>>) -> io::Result<()> {
86 match task.await {
87 Ok(Ok(())) => Err(Error::other("admin server stopped unexpectedly")),
88 Ok(Err(error)) => Err(error),
89 Err(error) => Err(Error::other(format!("admin server task failed: {error}"))),
90 }
91}
92
93fn admin_address(default_addr: &str) -> SocketAddr {
94 let raw = env::var("KRABKA_ADMIN_LISTEN_ADDR").unwrap_or_else(|_| default_addr.to_owned());
95 raw.parse()
96 .unwrap_or_else(|e| panic!("invalid KRABKA_ADMIN_LISTEN_ADDR `{raw}`: {e}"))
97}
98
99pub async fn serve_admin_from_env(default_addr: &str) -> io::Result<()> {
111 serve_admin_from_env_with(default_addr, Router::new()).await
112}
113
114pub async fn serve_admin_from_env_with(default_addr: &str, extra: Router) -> io::Result<()> {
126 let addr = admin_address(default_addr);
127 serve_admin(addr, extra).await
128}
129
130pub async fn serve_admin_from_env_with_config(
139 default_addr: &str,
140 extra: Router,
141 config: ProfilingConfig,
142) -> Result<(), ProfilingError> {
143 let addr = admin_address(default_addr);
144 serve_admin_with_config(addr, extra, config).await
145}
146
147pub async fn spawn_admin_from_env_with_config(
155 default_addr: &str,
156 extra: Router,
157 config: ProfilingConfig,
158) -> Result<JoinHandle<io::Result<()>>, ProfilingError> {
159 let addr = admin_address(default_addr);
160 spawn_admin_with_config(addr, extra, config).await
161}
162
163#[cfg(test)]
164mod tests {
165 use {super::*, assert2::assert, std::io::Error};
166
167 #[tokio::test]
168 async fn supervised_admin_exit_classifies_clean_error_and_join_outcomes() {
169 let clean = tokio::spawn(async { Ok(()) });
170 assert!(
171 await_admin_exit(clean).await.unwrap_err().to_string()
172 == "admin server stopped unexpectedly"
173 );
174
175 let io_error = tokio::spawn(async { Err(Error::other("socket failed")) });
176 assert!(await_admin_exit(io_error).await.unwrap_err().to_string() == "socket failed");
177
178 let panic = tokio::spawn(async {
179 panic!("admin panic");
180 #[allow(unreachable_code)]
181 Ok(())
182 });
183 assert!(
184 await_admin_exit(panic)
185 .await
186 .unwrap_err()
187 .to_string()
188 .starts_with("admin server task failed:")
189 );
190 }
191}