@@ -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,63 +182,72 @@ pub fn test_latency(client: &Client) -> f64 {
180182 let req_builder = client. get ( url) ;
181183
182184 let start = Instant :: now ( ) ;
183- let response = match req_builder. send ( ) {
185+ let mut response = match req_builder. send ( ) {
184186 Ok ( res) => res,
185187 Err ( e) => {
186188 log:: error!( "Failed to get response for latency test: {}" , e) ;
187189 return 0.0 ;
188190 }
189191 } ;
190192 let _status_code = response. status ( ) ;
191- let duration = start. elapsed ( ) . as_secs_f64 ( ) * 1_000.0 ;
193+ // Drain body to complete the request; ignore errors.
194+ let _ = std:: io:: copy ( & mut response, & mut std:: io:: sink ( ) ) ;
195+ let total_ms = start. elapsed ( ) . as_secs_f64 ( ) * 1_000.0 ;
192196
193197 // Try to extract cfRequestDuration from Server-Timing header
194- let cf_req_duration = match response. headers ( ) . get ( "Server-Timing" ) {
198+ let re = match Regex :: new ( r"cfRequestDuration;dur=([\d.]+)" ) {
199+ Ok ( re) => re,
200+ Err ( e) => {
201+ log:: error!( "Failed to compile regex: {}" , e) ;
202+ return total_ms;
203+ }
204+ } ;
205+
206+ let server_timing = match response. headers ( ) . get ( "Server-Timing" ) {
195207 Some ( header_value) => match header_value. to_str ( ) {
196- Ok ( header_str) => {
197- let re = match Regex :: new ( r"cfRequestDuration;dur=([\d.]+)" ) {
198- Ok ( re) => re,
199- Err ( e) => {
200- log:: error!( "Failed to compile regex: {}" , e) ;
201- return duration; // Return full duration if we can't parse server timing
202- }
203- } ;
204-
205- match re. captures ( header_str) {
206- Some ( captures) => match captures. get ( 1 ) {
207- Some ( dur_match) => match dur_match. as_str ( ) . parse :: < f64 > ( ) {
208- Ok ( parsed) => parsed,
209- Err ( e) => {
210- log:: error!( "Failed to parse cfRequestDuration: {}" , e) ;
211- return duration;
212- }
213- } ,
214- None => {
215- log:: debug!( "No cfRequestDuration found in Server-Timing header" ) ;
216- return duration;
217- }
218- } ,
219- None => {
220- log:: debug!( "Server-Timing header doesn't match expected format" ) ;
221- return duration;
222- }
223- }
224- }
208+ Ok ( s) => s,
225209 Err ( e) => {
226210 log:: error!( "Failed to convert Server-Timing header to string: {}" , e) ;
227- return duration ;
211+ return total_ms ;
228212 }
229213 } ,
230214 None => {
231215 log:: debug!( "No Server-Timing header in response" ) ;
232- return duration;
216+ return total_ms;
217+ }
218+ } ;
219+
220+ let cf_req_duration: f64 = match re. captures ( server_timing) {
221+ Some ( captures) => match captures. get ( 1 ) {
222+ Some ( dur_match) => match dur_match. as_str ( ) . parse :: < f64 > ( ) {
223+ Ok ( parsed) => parsed,
224+ Err ( e) => {
225+ log:: error!( "Failed to parse cfRequestDuration: {}" , e) ;
226+ return total_ms;
227+ }
228+ } ,
229+ None => {
230+ log:: debug!( "No cfRequestDuration found in Server-Timing header" ) ;
231+ return total_ms;
232+ }
233+ } ,
234+ None => {
235+ log:: debug!( "Server-Timing header doesn't match expected format" ) ;
236+ return total_ms;
233237 }
234238 } ;
235239
236- let mut req_latency = duration - cf_req_duration;
240+ let mut req_latency = total_ms - cf_req_duration;
241+ log:: debug!(
242+ "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}"
243+ ) ;
237244 if req_latency < 0.0 {
238- log:: warn!( "Negative latency calculated: {req_latency}ms, using 0.0ms instead" ) ;
239- req_latency = 0.0 ;
245+ if !WARNED_NEGATIVE_LATENCY . swap ( true , Ordering :: Relaxed ) {
246+ log:: warn!(
247+ "negative latency after server timing subtraction; clamping to 0.0 (total_ms={total_ms:.3} cf_req_duration_ms={cf_req_duration:.3})"
248+ ) ;
249+ }
250+ req_latency = 0.0
240251 }
241252 req_latency
242253}
@@ -296,7 +307,7 @@ pub fn test_upload(client: &Client, payload_size_bytes: usize, output_format: Ou
296307 let req_builder = client. post ( url) . body ( payload) ;
297308
298309 let start = Instant :: now ( ) ;
299- let response = match req_builder. send ( ) {
310+ let mut response = match req_builder. send ( ) {
300311 Ok ( res) => res,
301312 Err ( e) => {
302313 log:: error!( "Failed to send upload request: {}" , e) ;
@@ -307,6 +318,8 @@ pub fn test_upload(client: &Client, payload_size_bytes: usize, output_format: Ou
307318 let duration = start. elapsed ( ) ;
308319 let mbits = ( payload_size_bytes as f64 * 8.0 / 1_000_000.0 ) / duration. as_secs_f64 ( ) ;
309320
321+ // Drain response after timing so we don't skew upload measurement.
322+ let _ = std:: io:: copy ( & mut response, & mut std:: io:: sink ( ) ) ;
310323 if output_format == OutputFormat :: StdOut {
311324 print_current_speed ( mbits, duration, status_code, payload_size_bytes) ;
312325 }
@@ -322,15 +335,16 @@ pub fn test_download(
322335 let req_builder = client. get ( url) ;
323336
324337 let start = Instant :: now ( ) ;
325- let response = match req_builder. send ( ) {
338+ let mut response = match req_builder. send ( ) {
326339 Ok ( res) => res,
327340 Err ( e) => {
328341 log:: error!( "Failed to send download request: {}" , e) ;
329342 return 0.0 ;
330343 }
331344 } ;
332345 let status_code = response. status ( ) ;
333- let _res_bytes = response. bytes ( ) ;
346+ // Stream the body to avoid buffering the full payload in memory.
347+ let _ = std:: io:: copy ( & mut response, & mut std:: io:: sink ( ) ) ;
334348 let duration = start. elapsed ( ) ;
335349 let mbits = ( payload_size_bytes as f64 * 8.0 / 1_000_000.0 ) / duration. as_secs_f64 ( ) ;
336350
0 commit comments