Skip to content

Commit 9045778

Browse files
perf: stream download body and drain upload response (#254)
* ignore agents md * fix latency calculation * stream download body and drain upload response
1 parent 874ce85 commit 9045778

File tree

2 files changed

+32
-16
lines changed

2 files changed

+32
-16
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
.aider*
33
CLAUDE.md
44
GEMINI.md
5+
6+
# Local agent instructions
7+
AGENTS.md

src/speedtest.rs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ use reqwest::{blocking::Client, StatusCode};
1111
use serde::Serialize;
1212
use std::{
1313
fmt::Display,
14+
sync::atomic::{AtomicBool, Ordering},
1415
time::{Duration, Instant},
1516
};
1617

1718
const BASE_URL: &str = "https://speed.cloudflare.com";
1819
const DOWNLOAD_URL: &str = "__down?bytes=";
1920
const UPLOAD_URL: &str = "__up";
21+
static WARNED_NEGATIVE_LATENCY: AtomicBool = AtomicBool::new(false);
2022

2123
#[derive(Clone, Copy, Debug, Hash, Serialize, Eq, PartialEq)]
2224
pub 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

Comments
 (0)