@@ -8,36 +8,81 @@ use tokio::net::ToSocketAddrs;
88use tokio_util:: sync:: CancellationToken ;
99use tracing:: info;
1010
11- #[ cfg( feature = "memory-profiling " ) ]
11+ #[ cfg( feature = "jemalloc " ) ]
1212use crate :: memory_profiler:: MemoryProfiler ;
1313
14- pub fn start_metrics (
15- address : impl ToSocketAddrs + Debug + Send + ' static ,
16- shutdown_signal : CancellationToken ,
17- ) {
18- #[ cfg( feature = "memory-profiling" ) ]
19- let app = {
20- // Try to add memory profiling endpoint
21- match MemoryProfiler :: check_prof_ctl ( ) {
22- Ok ( ( ) ) => {
23- info ! ( "Memory profiling available, enabling /debug/pprof and /debug/flamegraph endpoints" ) ;
24- Router :: new ( )
25- . route ( "/metrics" , get ( serve_metrics) )
26- . route ( "/debug/pprof" , get ( MemoryProfiler :: heap_profile) )
27- . route ( "/debug/flamegraph" , get ( MemoryProfiler :: heap_flamegraph) )
28- }
14+ /// Whether memory profiling endpoints should be enabled on the metrics server.
15+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
16+ pub enum MemoryProfiling {
17+ Enabled ,
18+ Disabled ,
19+ }
20+
21+ impl From < bool > for MemoryProfiling {
22+ fn from ( enabled : bool ) -> Self {
23+ if enabled {
24+ MemoryProfiling :: Enabled
25+ } else {
26+ MemoryProfiling :: Disabled
27+ }
28+ }
29+ }
30+
31+ impl MemoryProfiling {
32+ /// Attempts to activate jemalloc memory profiling if requested.
33+ ///
34+ /// Returns `MemoryProfiling::Enabled` only if activation succeeds. If the flag is
35+ /// false, or jemalloc is not compiled in, or activation fails (e.g. `MALLOC_CONF` not
36+ /// set), returns `MemoryProfiling::Disabled` with an appropriate warning.
37+ #[ cfg( feature = "jemalloc" ) ]
38+ pub async fn try_activate ( requested : bool ) -> Self {
39+ if !requested {
40+ return MemoryProfiling :: Disabled ;
41+ }
42+ match MemoryProfiler :: activate ( ) . await {
43+ Ok ( ( ) ) => MemoryProfiling :: Enabled ,
2944 Err ( e) => {
3045 tracing:: warn!(
31- "Memory profiling not available: {}, serving metrics-only" ,
46+ "--enable-memory-profiling was passed but profiling could not be activated: {}. \
47+ Set MALLOC_CONF=\" prof:true,prof_active:false,lg_prof_sample:19\" before starting the binary.",
3248 e
3349 ) ;
34- Router :: new ( ) . route ( "/metrics" , get ( serve_metrics ) )
50+ MemoryProfiling :: Disabled
3551 }
3652 }
37- } ;
53+ }
3854
39- #[ cfg( not( feature = "memory-profiling" ) ) ]
40- let app = Router :: new ( ) . route ( "/metrics" , get ( serve_metrics) ) ;
55+ /// Non-jemalloc fallback: always returns `Disabled`, warns if profiling was requested.
56+ #[ cfg( not( feature = "jemalloc" ) ) ]
57+ pub async fn try_activate ( requested : bool ) -> Self {
58+ if requested {
59+ tracing:: warn!(
60+ "--enable-memory-profiling was passed but the jemalloc feature is not compiled in"
61+ ) ;
62+ }
63+ MemoryProfiling :: Disabled
64+ }
65+ }
66+
67+ /// Activates memory profiling if requested and starts the metrics server.
68+ ///
69+ /// This is the single entry point that handles both activation and server startup,
70+ /// avoiding duplication across binaries.
71+ pub async fn start_metrics_with_profiling (
72+ address : impl ToSocketAddrs + Debug + Send + ' static ,
73+ shutdown_signal : CancellationToken ,
74+ enable_memory_profiling : bool ,
75+ ) {
76+ let memory_profiling = MemoryProfiling :: try_activate ( enable_memory_profiling) . await ;
77+ start_metrics ( address, shutdown_signal, memory_profiling) ;
78+ }
79+
80+ pub fn start_metrics (
81+ address : impl ToSocketAddrs + Debug + Send + ' static ,
82+ shutdown_signal : CancellationToken ,
83+ memory_profiling : MemoryProfiling ,
84+ ) {
85+ let app = metrics_router ( memory_profiling) ;
4186
4287 tokio:: spawn ( async move {
4388 let listener = tokio:: net:: TcpListener :: bind ( address)
@@ -55,6 +100,36 @@ pub fn start_metrics(
55100 } ) ;
56101}
57102
103+ fn metrics_router ( memory_profiling : MemoryProfiling ) -> Router {
104+ #[ cfg( feature = "jemalloc" ) ]
105+ if memory_profiling == MemoryProfiling :: Enabled {
106+ match MemoryProfiler :: check_prof_ctl ( ) {
107+ Ok ( ( ) ) => {
108+ info ! ( "Memory profiling enabled, registering /debug/pprof and /debug/flamegraph endpoints" ) ;
109+ return Router :: new ( )
110+ . route ( "/metrics" , get ( serve_metrics) )
111+ . route ( "/debug/pprof" , get ( MemoryProfiler :: heap_profile) )
112+ . route ( "/debug/flamegraph" , get ( MemoryProfiler :: heap_flamegraph) ) ;
113+ }
114+ Err ( e) => {
115+ tracing:: warn!(
116+ "Memory profiling requested but not available: {}, serving metrics-only" ,
117+ e
118+ ) ;
119+ }
120+ }
121+ }
122+
123+ #[ cfg( not( feature = "jemalloc" ) ) ]
124+ if memory_profiling == MemoryProfiling :: Enabled {
125+ tracing:: warn!(
126+ "Memory profiling requested but jemalloc feature is not compiled in, serving metrics-only"
127+ ) ;
128+ }
129+
130+ Router :: new ( ) . route ( "/metrics" , get ( serve_metrics) )
131+ }
132+
58133async fn serve_metrics ( ) -> Result < String , AxumError > {
59134 let metric_families = prometheus:: gather ( ) ;
60135 Ok ( prometheus:: TextEncoder :: new ( )
0 commit comments