More robust test attempts

This commit is contained in:
dynco-nym
2026-01-19 17:42:59 +01:00
parent e1eee567cf
commit 760204fffd
2 changed files with 92 additions and 50 deletions
+9 -1
View File
@@ -138,6 +138,10 @@ pub struct Socks5Args {
#[arg(long, default_value_t = 10)]
test_count: u64,
/// stops socks5 test early after this many failed attempts
#[arg(long, default_value_t = 3)]
failure_count_cutoff: usize,
}
#[derive(Default, Debug)]
@@ -708,6 +712,7 @@ impl Probe {
self.socks5_args.socks5_json_rpc_url_list.clone(),
self.socks5_args.mixnet_client_timeout_sec,
self.socks5_args.test_count,
self.socks5_args.failure_count_cutoff,
)
.await
{
@@ -1671,6 +1676,7 @@ async fn do_socks5_connectivity_test(
json_rpc_endpoints: Vec<String>,
mixnet_client_timeout: u64,
test_run_count: u64,
failure_count_cutoff: usize,
) -> anyhow::Result<Socks5ProbeResults> {
info!(
"Starting SOCKS5 test through Network Requester: {}",
@@ -1771,7 +1777,9 @@ async fn do_socks5_connectivity_test(
let test =
HttpsConnectivityTest::new(test_run_count, mixnet_client_timeout, json_rpc_endpoints);
results.https_connectivity = test.run_tests(socks5_client.socks5_url()).await;
results.https_connectivity = test
.run_tests(socks5_client.socks5_url(), failure_count_cutoff)
.await;
// cleanup
socks5_client.disconnect().await;
+83 -49
View File
@@ -22,9 +22,11 @@ impl HttpsConnectivityTest {
}
}
pub async fn run_tests(self, socks5_url: String) -> HttpsConnectivityResult {
let mut result = HttpsConnectivityResult::default();
pub async fn run_tests(
self,
socks5_url: String,
failure_count_cutoff: usize,
) -> HttpsConnectivityResult {
let proxy = match reqwest::Proxy::all(socks5_url) {
Ok(p) => p,
Err(e) => {
@@ -47,56 +49,42 @@ impl HttpsConnectivityTest {
}
};
let mut successful_runs = 0u64;
for i in 1..self.test_count + 1 {
let mut results = Vec::new();
for i in 1..=self.test_count {
info!("Running test {}/{}", i, self.test_count);
let interim_res = self.perform_https_request(&client).await;
if interim_res.https_success
&& let Some(latency_ms) = interim_res.https_latency_ms
{
successful_runs += 1;
result.https_latency_ms = Some(
result
.https_latency_ms
.map_or(latency_ms, |existing| existing + latency_ms),
if interim_res.success {
info!(
"{}/{} latency: {}ms",
i,
self.test_count,
interim_res.latency_ms.unwrap_or(0)
);
result.https_success = true;
result.https_status_code = interim_res.https_status_code;
info!("{}/{} latency: {}ms", i, self.test_count, latency_ms);
} else if let Some(new_error) = interim_res.error {
result.error = Some(result.error.map_or(new_error.clone(), |existing| {
format!("{},{}", existing, new_error)
}))
}
// too many failed runs: return early
let unsuccessful_runs = i - successful_runs;
if successful_runs < 2 && unsuccessful_runs > 2 {
// if < 2 runs, we don't have to calculate average before returning
results.push(interim_res);
// early exit
let unsuccessful = results.iter().filter(|r| !r.success).count();
if unsuccessful > failure_count_cutoff {
warn!("Too many failed runs: returning early...");
return result;
break;
}
}
result.https_latency_ms = result
.https_latency_ms
.map(|latency| latency / successful_runs);
info!(
"AVG latency over {} runs (in ms): {:?}",
successful_runs, result.https_latency_ms
);
result
let final_result = HttpsConnectivityResult::from_results(results);
info!("AVG latency (in ms): {:?}", final_result.https_latency_ms);
final_result
}
async fn perform_https_request(&self, client: &reqwest::Client) -> HttpsConnectivityResult {
async fn perform_https_request(&self, client: &reqwest::Client) -> SingleHttpsTestResult {
use tokio::time::Instant;
// TODO dz instead of initializing a mutable default, then mutating fields, use constructors for outcome
let start = Instant::now();
let mut error_msg = String::new();
// TODO dz utilize others as fallback
// let endpoint = self.json_rpc_test_endpoints.first().unwrap();
for endpoint in self.json_rpc_test_endpoints.iter() {
info!(
"Testing against {} with timeout {}s",
@@ -120,17 +108,18 @@ impl HttpsConnectivityTest {
// Deserialize body into JsonRpcResponse
match response.json::<JsonRpcResponse>().await {
Ok(JsonRpcResponse::Ok { .. }) => {
let res = HttpsConnectivityResult::success(
status.as_u16(),
elapsed.as_millis() as u64,
endpoint.to_string(),
);
debug!(
"HTTPS test completed: status={}, latency={}ms",
status.as_u16(),
elapsed.as_millis()
);
return res;
return SingleHttpsTestResult {
success: true,
status_code: Some(status.as_u16()),
latency_ms: Some(elapsed.as_millis() as u64),
endpoint_used: Some(endpoint.to_string()),
error: None,
};
}
Ok(JsonRpcResponse::Err { error, .. }) => {
warn!("JSON-RPC error: {} (code: {})", error.message, error.code);
@@ -153,10 +142,25 @@ impl HttpsConnectivityTest {
}
}
HttpsConnectivityResult::with_error(error_msg)
SingleHttpsTestResult {
success: false,
status_code: None,
latency_ms: None,
endpoint_used: None,
error: Some(error_msg),
}
}
}
/// single HTTPS test attempt
struct SingleHttpsTestResult {
success: bool,
status_code: Option<u16>,
latency_ms: Option<u64>,
endpoint_used: Option<String>,
error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct HttpsConnectivityResult {
/// successfully completed HTTPS request
@@ -186,13 +190,43 @@ impl HttpsConnectivityResult {
}
}
pub fn success(status_code: u16, latency: u64, endpoint_used: String) -> Self {
fn from_results(results: Vec<SingleHttpsTestResult>) -> Self {
let successes: Vec<_> = results.iter().filter(|r| r.success).collect();
let errors: Vec<_> = results.iter().filter_map(|r| r.error.as_ref()).collect();
if successes.is_empty() {
return Self::with_error(
errors
.into_iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", "),
);
}
// average latency from successful runs
let total_latency: u64 = successes.iter().filter_map(|r| r.latency_ms).sum();
let avg_latency = total_latency / successes.len() as u64;
// use the last successful result for status_code and endpoint
let last_success = successes.last().unwrap();
Self {
https_success: true,
https_status_code: Some(status_code),
https_latency_ms: Some(latency),
endpoint_used: Some(endpoint_used),
error: None,
https_status_code: last_success.status_code,
https_latency_ms: Some(avg_latency),
endpoint_used: last_success.endpoint_used.clone(),
error: if errors.is_empty() {
None
} else {
Some(
errors
.into_iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", "),
)
},
}
}
}