33
44use async_trait:: async_trait;
55use std:: { sync:: Arc , time} ;
6- use tokio:: sync:: { Mutex , mpsc:: Receiver } ;
6+ use tokio:: sync:: mpsc:: Receiver ;
7+ use tokio:: sync:: oneshot;
78use tracing:: { debug, error} ;
89
910use libdd_trace_protobuf:: pb;
@@ -46,22 +47,28 @@ async fn send_stats_payload(config: &Arc<Config>, payload: pb::StatsPayload) {
4647#[ async_trait]
4748pub trait StatsFlusher {
4849 /// Starts a stats flusher that listens for stats payloads sent to the tokio mpsc Receiver,
49- /// implementing flushing logic that calls flush_stats.
50+ /// implementing flushing logic that calls flush_stats. Runs until the shutdown signal fires,
51+ /// at which point it performs a final force flush and returns.
5052 async fn start_stats_flusher (
5153 & self ,
5254 config : Arc < Config > ,
53- mut rx : Receiver < pb:: ClientStatsPayload > ,
55+ rx : Receiver < pb:: ClientStatsPayload > ,
56+ shutdown_rx : oneshot:: Receiver < ( ) > ,
5457 ) ;
5558 /// Flushes stats to the Datadog trace stats intake.
56- async fn flush_stats ( & self , config : Arc < Config > , client_stats : Vec < pb:: ClientStatsPayload > ) ;
59+ /// `force_flush` controls whether in-progress concentrator buckets are flushed (true on
60+ /// shutdown, false on normal interval flushes).
61+ async fn flush_stats (
62+ & self ,
63+ config : Arc < Config > ,
64+ client_stats : Vec < pb:: ClientStatsPayload > ,
65+ force_flush : bool ,
66+ ) ;
5767}
5868
5969#[ derive( Clone ) ]
6070pub struct ServerlessStatsFlusher {
6171 pub stats_concentrator : Option < StatsConcentrator > ,
62- /// When false, flushes are done on completed buckets
63- /// When true, flushes are done on any in progress buckets, useful for integration tests
64- pub force_flush_concentrator : bool ,
6572}
6673
6774#[ async_trait]
@@ -70,44 +77,51 @@ impl StatsFlusher for ServerlessStatsFlusher {
7077 & self ,
7178 config : Arc < Config > ,
7279 mut rx : Receiver < pb:: ClientStatsPayload > ,
80+ mut shutdown_rx : oneshot:: Receiver < ( ) > ,
7381 ) {
74- let buffer: Arc < Mutex < Vec < pb:: ClientStatsPayload > > > = Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ;
75-
76- let buffer_producer = buffer. clone ( ) ;
77- let buffer_consumer = buffer. clone ( ) ;
78-
79- // Drain the stats channel continuously into the buffer
80- tokio:: spawn ( async move {
81- while let Some ( stats_payload) = rx. recv ( ) . await {
82- let mut buffer = buffer_producer. lock ( ) . await ;
83- buffer. push ( stats_payload) ;
84- }
85- } ) ;
82+ let mut interval =
83+ tokio:: time:: interval ( time:: Duration :: from_secs ( config. stats_flush_interval_secs ) ) ;
84+ let mut buffer: Vec < pb:: ClientStatsPayload > = Vec :: new ( ) ;
8685
87- // Flush stats from the buffer on a fixed interval
8886 loop {
89- tokio:: time:: sleep ( time:: Duration :: from_secs ( config. stats_flush_interval_secs ) ) . await ;
90-
91- let mut buffer = buffer_consumer. lock ( ) . await ;
92- // Copy the batch for this flush
93- let channel_stats = buffer. to_vec ( ) ;
94- // Reset the buffer so the next tick only sees new stats
95- buffer. clear ( ) ;
96- // Release the mutex before flushing stats
97- drop ( buffer) ;
98-
99- let should_flush = should_flush_stats_buffer (
100- !channel_stats. is_empty ( ) ,
101- self . stats_concentrator . is_some ( ) ,
102- ) ;
103- if should_flush {
104- self . flush_stats ( config. clone ( ) , channel_stats) . await ;
87+ tokio:: select! {
88+ // Receive client stats and add them to the buffer
89+ Some ( stats) = rx. recv( ) => {
90+ buffer. push( stats) ;
91+ }
92+
93+ // Drain client stats in buffer and stats from concentrator on interval
94+ _ = interval. tick( ) => {
95+ let client_stats = std:: mem:: take( & mut buffer) ;
96+ let should_flush = should_flush_stats_buffer(
97+ !client_stats. is_empty( ) ,
98+ self . stats_concentrator. is_some( ) ,
99+ ) ;
100+ if should_flush {
101+ self . flush_stats( config. clone( ) , client_stats, false ) . await ;
102+ }
103+ }
104+
105+ _ = & mut shutdown_rx => {
106+ // Drain any client stats that arrived before the shutdown signal
107+ while let Ok ( stats) = rx. try_recv( ) {
108+ buffer. push( stats) ;
109+ }
110+ // Force flush all in progress concentrator buckets on shutdown signal
111+ self . flush_stats( config. clone( ) , std:: mem:: take( & mut buffer) , true ) . await ;
112+ return ;
113+ }
105114 }
106115 }
107116 }
108117
109118 /// Flushes client computed stats from the tracer and serverless computed stats as separate payloads
110- async fn flush_stats ( & self , config : Arc < Config > , client_stats : Vec < pb:: ClientStatsPayload > ) {
119+ async fn flush_stats (
120+ & self ,
121+ config : Arc < Config > ,
122+ client_stats : Vec < pb:: ClientStatsPayload > ,
123+ force_flush : bool ,
124+ ) {
111125 // Flush client computed stats from the tracer
112126 if !client_stats. is_empty ( ) {
113127 let payload = stats_utils:: construct_stats_payload ( client_stats) ;
@@ -116,7 +130,7 @@ impl StatsFlusher for ServerlessStatsFlusher {
116130
117131 // Flush serverless computed stats from the concentrator
118132 if let Some ( ref concentrator) = self . stats_concentrator
119- && let Some ( agent_stats) = concentrator. flush ( self . force_flush_concentrator )
133+ && let Some ( agent_stats) = concentrator. flush ( force_flush )
120134 {
121135 let mut payload = stats_utils:: construct_stats_payload ( vec ! [ agent_stats] ) ;
122136 payload. client_computed = false ;
0 commit comments