@@ -85,7 +85,7 @@ impl Drop for UnsentEventCount {
8585#[ derive( Clone ) ]
8686pub ( super ) struct Output {
8787 sender : LimitedSender < SourceSenderItem > ,
88- lag_time : Option < Histogram > ,
88+ metrics : OutputMetrics ,
8989 events_sent : Registered < EventsSent > ,
9090 /// The schema definition that will be attached to Log events sent through here
9191 log_definition : Option < Arc < Definition > > ,
@@ -95,6 +95,27 @@ pub(super) struct Output {
9595 timeout : Option < Duration > ,
9696}
9797
98+ #[ derive( Clone , Default ) ]
99+ pub ( super ) struct OutputMetrics {
100+ lag_time : Option < Histogram > ,
101+ send_latency : Option < Histogram > ,
102+ send_batch_latency : Option < Histogram > ,
103+ }
104+
105+ impl OutputMetrics {
106+ pub ( super ) fn new (
107+ lag_time : Option < Histogram > ,
108+ send_latency : Option < Histogram > ,
109+ send_batch_latency : Option < Histogram > ,
110+ ) -> Self {
111+ Self {
112+ lag_time,
113+ send_latency,
114+ send_batch_latency,
115+ }
116+ }
117+ }
118+
98119#[ expect( clippy:: missing_fields_in_debug) ]
99120impl fmt:: Debug for Output {
100121 fn fmt ( & self , fmt : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
@@ -111,19 +132,20 @@ impl Output {
111132 pub ( super ) fn new_with_buffer (
112133 n : usize ,
113134 output : String ,
114- lag_time : Option < Histogram > ,
135+ metrics : OutputMetrics ,
115136 log_definition : Option < Arc < Definition > > ,
116137 output_id : OutputId ,
117138 timeout : Option < Duration > ,
118139 ewma_half_life_seconds : Option < f64 > ,
119140 ) -> ( Self , LimitedReceiver < SourceSenderItem > ) {
120141 let limit = MemoryBufferSize :: MaxEvents ( NonZeroUsize :: new ( n) . unwrap ( ) ) ;
121- let metrics = ChannelMetricMetadata :: new ( UTILIZATION_METRIC_PREFIX , Some ( output. clone ( ) ) ) ;
122- let ( tx, rx) = channel:: limited ( limit, Some ( metrics) , ewma_half_life_seconds) ;
142+ let channel_metrics =
143+ ChannelMetricMetadata :: new ( UTILIZATION_METRIC_PREFIX , Some ( output. clone ( ) ) ) ;
144+ let ( tx, rx) = channel:: limited ( limit, Some ( channel_metrics) , ewma_half_life_seconds) ;
123145 (
124146 Self {
125147 sender : tx,
126- lag_time ,
148+ metrics ,
127149 events_sent : internal_event:: register ( EventsSent :: from ( internal_event:: Output (
128150 Some ( output. into ( ) ) ,
129151 ) ) ) ,
@@ -136,12 +158,21 @@ impl Output {
136158 }
137159
138160 pub ( super ) async fn send (
161+ & mut self ,
162+ events : EventArray ,
163+ unsent_event_count : & mut UnsentEventCount ,
164+ ) -> Result < ( ) , SendError > {
165+ let reference = Utc :: now ( ) . timestamp_millis ( ) ;
166+ self . send_inner ( events, unsent_event_count, reference) . await
167+ }
168+
169+ async fn send_inner (
139170 & mut self ,
140171 mut events : EventArray ,
141172 unsent_event_count : & mut UnsentEventCount ,
173+ reference : i64 ,
142174 ) -> Result < ( ) , SendError > {
143175 let send_reference = Instant :: now ( ) ;
144- let reference = Utc :: now ( ) . timestamp_millis ( ) ;
145176 events
146177 . iter_events ( )
147178 . for_each ( |event| self . emit_lag_time ( event, reference) ) ;
@@ -156,7 +187,17 @@ impl Output {
156187
157188 let byte_size = events. estimated_json_encoded_size_of ( ) ;
158189 let count = events. len ( ) ;
159- self . send_with_timeout ( events, send_reference) . await ?;
190+
191+ let send_start = Instant :: now ( ) ;
192+
193+ let send_result = self . send_with_timeout ( events, send_reference) . await ;
194+
195+ if let Some ( send_latency) = & self . metrics . send_latency {
196+ send_latency. record ( send_start. elapsed ( ) . as_secs_f64 ( ) ) ;
197+ }
198+
199+ send_result?;
200+
160201 self . events_sent . emit ( CountByteSize ( count, byte_size) ) ;
161202 unsent_event_count. decr ( count) ;
162203 Ok ( ( ) )
@@ -218,33 +259,47 @@ impl Output {
218259 I : IntoIterator < Item = E > ,
219260 <I as IntoIterator >:: IntoIter : ExactSizeIterator ,
220261 {
262+ // Capture a single reference timestamp for the entire batch so that lag time
263+ // measurements are not inflated by channel-send latency for later chunks.
264+ let reference = Utc :: now ( ) . timestamp_millis ( ) ;
265+
221266 // It's possible that the caller stops polling this future while it is blocked waiting
222267 // on `self.send()`. When that happens, we use `UnsentEventCount` to correctly emit
223268 // `ComponentEventsDropped` events.
224269 let events = events. into_iter ( ) . map ( Into :: into) ;
225270 let mut unsent_event_count = UnsentEventCount :: new ( events. len ( ) ) ;
271+ let send_batch_start = Instant :: now ( ) ;
272+
226273 for events in array:: events_into_arrays ( events, Some ( CHUNK_SIZE ) ) {
227- self . send ( events, & mut unsent_event_count)
274+ self . send_inner ( events, & mut unsent_event_count, reference )
228275 . await
229- . inspect_err ( |error| match error {
230- SendError :: Timeout => {
231- unsent_event_count. timed_out ( ) ;
276+ . inspect_err ( |error| {
277+ match error {
278+ SendError :: Timeout => {
279+ unsent_event_count. timed_out ( ) ;
280+ }
281+ SendError :: Closed => {
282+ // The unsent event count is discarded here because the callee emits the
283+ // `StreamClosedError`.
284+ unsent_event_count. discard ( ) ;
285+ }
232286 }
233- SendError :: Closed => {
234- // The unsent event count is discarded here because the callee emits the
235- // `StreamClosedError`.
236- unsent_event_count. discard ( ) ;
287+ if let Some ( send_batch_latency) = & self . metrics . send_batch_latency {
288+ send_batch_latency. record ( send_batch_start. elapsed ( ) . as_secs_f64 ( ) ) ;
237289 }
238290 } ) ?;
239291 }
292+ if let Some ( send_batch_latency) = & self . metrics . send_batch_latency {
293+ send_batch_latency. record ( send_batch_start. elapsed ( ) . as_secs_f64 ( ) ) ;
294+ }
240295 Ok ( ( ) )
241296 }
242297
243298 /// Calculate the difference between the reference time and the
244299 /// timestamp stored in the given event reference, and emit the
245300 /// different, as expressed in milliseconds, as a histogram.
246301 pub ( super ) fn emit_lag_time ( & self , event : EventRef < ' _ > , reference : i64 ) {
247- if let Some ( lag_time_metric) = & self . lag_time {
302+ if let Some ( lag_time_metric) = & self . metrics . lag_time {
248303 let timestamp = match event {
249304 EventRef :: Log ( log) => {
250305 log_schema ( )
0 commit comments