@@ -11,12 +11,14 @@ use reqwest::{blocking::Client, StatusCode};
1111use serde:: Serialize ;
1212use std:: {
1313 fmt:: Display ,
14+ sync:: atomic:: { AtomicBool , Ordering } ,
1415 time:: { Duration , Instant } ,
1516} ;
1617
1718const BASE_URL : & str = "https://speed.cloudflare.com" ;
1819const DOWNLOAD_URL : & str = "__down?bytes=" ;
1920const UPLOAD_URL : & str = "__up" ;
21+ static WARNED_NEGATIVE_LATENCY : AtomicBool = AtomicBool :: new ( false ) ;
2022
2123#[ derive( Clone , Copy , Debug , Hash , Serialize , Eq , PartialEq ) ]
2224pub enum TestType {
@@ -180,29 +182,37 @@ pub fn test_latency(client: &Client) -> f64 {
180182 let req_builder = client. get ( url) ;
181183
182184 let start = Instant :: now ( ) ;
183- let response = req_builder. send ( ) . expect ( "failed to get response" ) ;
185+ let mut response = req_builder. send ( ) . expect ( "failed to get response" ) ;
184186 let _status_code = response. status ( ) ;
185- let duration = start. elapsed ( ) . as_secs_f64 ( ) * 1_000.0 ;
187+ // Drain body to complete the request; ignore errors.
188+ let _ = std:: io:: copy ( & mut response, & mut std:: io:: sink ( ) ) ;
189+ let total_ms = start. elapsed ( ) . as_secs_f64 ( ) * 1_000.0 ;
186190
187191 let re = Regex :: new ( r"cfRequestDuration;dur=([\d.]+)" ) . unwrap ( ) ;
192+ let server_timing = response
193+ . headers ( )
194+ . get ( "Server-Timing" )
195+ . expect ( "No Server-Timing in response header" )
196+ . to_str ( )
197+ . unwrap ( ) ;
188198 let cf_req_duration: f64 = re
189- . captures (
190- response
191- . headers ( )
192- . get ( "Server-Timing" )
193- . expect ( "No Server-Timing in response header" )
194- . to_str ( )
195- . unwrap ( ) ,
196- )
199+ . captures ( server_timing)
197200 . unwrap ( )
198201 . get ( 1 )
199202 . unwrap ( )
200203 . as_str ( )
201204 . parse ( )
202205 . unwrap ( ) ;
203- let mut req_latency = duration - cf_req_duration;
206+ let mut req_latency = total_ms - cf_req_duration;
207+ log:: debug!(
208+ "latency debug: total_ms={total_ms:.3} cf_req_duration_ms={cf_req_duration:.3} req_latency_total={req_latency:.3} server_timing={server_timing}"
209+ ) ;
204210 if req_latency < 0.0 {
205- // TODO investigate negative latency values
211+ if !WARNED_NEGATIVE_LATENCY . swap ( true , Ordering :: Relaxed ) {
212+ log:: warn!(
213+ "negative latency after server timing subtraction; clamping to 0.0 (total_ms={total_ms:.3} cf_req_duration_ms={cf_req_duration:.3})"
214+ ) ;
215+ }
206216 req_latency = 0.0
207217 }
208218 req_latency
@@ -261,14 +271,16 @@ pub fn test_upload(client: &Client, payload_size_bytes: usize, output_format: Ou
261271 let url = & format ! ( "{BASE_URL}/{UPLOAD_URL}" ) ;
262272 let payload: Vec < u8 > = vec ! [ 1 ; payload_size_bytes] ;
263273 let req_builder = client. post ( url) . body ( payload) ;
264- let ( status_code, mbits, duration) = {
274+ let ( mut response , status_code, mbits, duration) = {
265275 let start = Instant :: now ( ) ;
266276 let response = req_builder. send ( ) . expect ( "failed to get response" ) ;
267277 let status_code = response. status ( ) ;
268278 let duration = start. elapsed ( ) ;
269279 let mbits = ( payload_size_bytes as f64 * 8.0 / 1_000_000.0 ) / duration. as_secs_f64 ( ) ;
270- ( status_code, mbits, duration)
280+ ( response , status_code, mbits, duration)
271281 } ;
282+ // Drain response after timing so we don't skew upload measurement.
283+ let _ = std:: io:: copy ( & mut response, & mut std:: io:: sink ( ) ) ;
272284 if output_format == OutputFormat :: StdOut {
273285 print_current_speed ( mbits, duration, status_code, payload_size_bytes) ;
274286 }
@@ -284,9 +296,10 @@ pub fn test_download(
284296 let req_builder = client. get ( url) ;
285297 let ( status_code, mbits, duration) = {
286298 let start = Instant :: now ( ) ;
287- let response = req_builder. send ( ) . expect ( "failed to get response" ) ;
299+ let mut response = req_builder. send ( ) . expect ( "failed to get response" ) ;
288300 let status_code = response. status ( ) ;
289- let _res_bytes = response. bytes ( ) ;
301+ // Stream the body to avoid buffering the full payload in memory.
302+ let _ = std:: io:: copy ( & mut response, & mut std:: io:: sink ( ) ) ;
290303 let duration = start. elapsed ( ) ;
291304 let mbits = ( payload_size_bytes as f64 * 8.0 / 1_000_000.0 ) / duration. as_secs_f64 ( ) ;
292305 ( status_code, mbits, duration)
0 commit comments