@@ -5,19 +5,55 @@ use std::sync::Arc;
55
66use hyper:: service:: { make_service_fn, service_fn} ;
77use hyper:: { Body , Request , Response } ;
8+ use tokio:: sync:: oneshot;
9+ use tracing:: info;
810
911use crate :: exporters:: Exporter ;
1012use crate :: { Error , Report } ;
1113
14+ /// A handle to the metrics server.
15+ #[ derive( Debug ) ]
16+ pub struct MetricsServerHandle {
17+ /// The actual address that the server is bound to.
18+ addr : SocketAddr ,
19+ /// The shutdown sender to stop the server.
20+ shutdown_tx : Option < oneshot:: Sender < ( ) > > ,
21+ /// The task handle to wait for server completion.
22+ task_handle : tokio:: task:: JoinHandle < Result < ( ) , Error > > ,
23+ }
24+
25+ impl MetricsServerHandle {
26+ /// Tell the server to stop without waiting for the server to stop.
27+ pub fn stop ( & mut self ) -> Result < ( ) , Error > {
28+ if let Some ( tx) = self . shutdown_tx . take ( ) {
29+ // Ignore error if receiver already dropped
30+ let _ = tx. send ( ( ) ) ;
31+ Ok ( ( ) )
32+ } else {
33+ Err ( Error :: AlreadyStopped )
34+ }
35+ }
36+
37+ /// Wait until the server has stopped.
38+ pub async fn stopped ( self ) -> Result < ( ) , Error > {
39+ self . task_handle . await . map_err ( |e| Error :: JoinError ( e. to_string ( ) ) ) ?
40+ }
41+
42+ /// Returns the socket address the server is listening on.
43+ pub fn addr ( & self ) -> & SocketAddr {
44+ & self . addr
45+ }
46+ }
47+
1248/// A helper trait for defining the type for hooks that are called when the metrics are being
1349/// collected by the server.
1450trait Hook : Fn ( ) + Send + Sync { }
1551impl < T : Fn ( ) + Send + Sync > Hook for T { }
1652
17- /// A boxed [`Hook`] .
18- type BoxedHook = Box < dyn Hook < Output = ( ) > > ;
19- /// A list of [BoxedHook] .
20- type Hooks = Vec < BoxedHook > ;
53+ /// A shared hook that can be cloned .
54+ type SharedHook = Arc < dyn Hook < Output = ( ) > > ;
55+ /// A list of shared hooks .
56+ type Hooks = Vec < SharedHook > ;
2157
2258/// Server for serving metrics.
2359// TODO: allow configuring the server executor to allow cancelling on invidiual connection tasks.
4682 where
4783 I : IntoIterator < Item = Box < dyn Report > > ,
4884 {
49- // convert the report types into callable hooks
50- let hooks = reports. into_iter ( ) . map ( |r| Box :: new ( move || r. report ( ) ) as BoxedHook ) ;
85+ // convert the report types into callable hooks, wrapping in Arc for sharing
86+ let hooks = reports. into_iter ( ) . map ( |r| Arc :: new ( move || r. report ( ) ) as SharedHook ) ;
5187 self . hooks . extend ( hooks) ;
5288 self
5389 }
@@ -60,42 +96,67 @@ where
6096 describe_memory_stats ( ) ;
6197
6298 let hooks: Hooks =
63- vec ! [ Box :: new( collect_memory_stats) , Box :: new( move || process. collect( ) ) ] ;
99+ vec ! [ Arc :: new( collect_memory_stats) , Arc :: new( move || process. collect( ) ) ] ;
64100
65101 self . hooks . extend ( hooks) ;
66102 self
67103 }
68104
69105 /// Starts an endpoint at the given address to serve Prometheus metrics.
70- pub async fn start ( self , addr : SocketAddr ) -> Result < ( ) , Error > {
71- let hooks = Arc :: new ( move || self . hooks . iter ( ) . for_each ( |hook| hook ( ) ) ) ;
106+ ///
107+ /// Returns a handle that can be used to stop the server and wait for it to finish.
108+ pub async fn start ( & self , addr : SocketAddr ) -> Result < MetricsServerHandle , Error > {
109+ // Clone the hooks (clones the Arc references, not the closures themselves)
110+ let hooks = self . hooks . clone ( ) ;
111+ let exporter = self . exporter . clone ( ) ;
72112
73- hyper:: Server :: try_bind ( & addr)
113+ let ( shutdown_tx, shutdown_rx) = oneshot:: channel :: < ( ) > ( ) ;
114+
115+ let server = hyper:: Server :: try_bind ( & addr)
74116 . map_err ( |_| Error :: FailedToBindAddress { addr } ) ?
75117 . serve ( make_service_fn ( move |_| {
76- let hook = Arc :: clone ( & hooks ) ;
77- let exporter = self . exporter . clone ( ) ;
118+ let hooks = hooks . clone ( ) ;
119+ let exporter = exporter. clone ( ) ;
78120 async move {
79121 Ok :: < _ , Infallible > ( service_fn ( move |_: Request < Body > | {
80- // call the hooks to collect metrics before exporting them
81- ( hook) ( ) ;
82- // export the metrics from the installed exporter and send as response
83- let metrics = Body :: from ( exporter. export ( ) ) ;
84- async move { Ok :: < _ , Infallible > ( Response :: new ( metrics) ) }
122+ let hooks = hooks. clone ( ) ;
123+ let exporter = exporter. clone ( ) ;
124+ async move {
125+ // need to call the hooks to collect metrics before exporting them
126+ for hook in & hooks {
127+ hook ( ) ;
128+ }
129+ // export the metrics from the installed exporter and send as response
130+ let metrics = Body :: from ( exporter. export ( ) ) ;
131+ Ok :: < _ , Infallible > ( Response :: new ( metrics) )
132+ }
85133 } ) )
86134 }
87- } ) )
88- . await ?;
135+ } ) ) ;
136+
137+ let actual_addr = server. local_addr ( ) ;
89138
90- Ok ( ( ) )
139+ // Spawn the server with graceful shutdown
140+ let task_handle = tokio:: spawn ( async move {
141+ server
142+ . with_graceful_shutdown ( async {
143+ shutdown_rx. await . ok ( ) ;
144+ } )
145+ . await
146+ . map_err ( Error :: Server )
147+ } ) ;
148+
149+ info ! ( target: "metrics" , addr = %actual_addr, "Metrics server started." ) ;
150+
151+ Ok ( MetricsServerHandle { addr : actual_addr, shutdown_tx : Some ( shutdown_tx) , task_handle } )
91152 }
92153}
93154
94- impl < MetricsExporter > fmt:: Debug for Server < MetricsExporter >
95- where
96- MetricsExporter : fmt:: Debug ,
97- {
155+ impl < MetricsExporter > fmt:: Debug for Server < MetricsExporter > {
98156 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
99- f. debug_struct ( "Server" ) . field ( "hooks" , & "..." ) . field ( "exporter" , & self . exporter ) . finish ( )
157+ f. debug_struct ( "Server" )
158+ . field ( "hooks" , & format_args ! ( "{} hook(s)" , self . hooks. len( ) ) )
159+ . field ( "exporter" , & "<exporter>" )
160+ . finish ( )
100161 }
101162}
0 commit comments