use std::sync::Arc;
use std::time::Duration;
use reqwest::Client;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use crate::errors::{EngineError, EngineResult};
use super::event::{emit, EngineEvent};
use super::models::{Server, TestReport};
use super::providers::Provider;
use super::scheduler;
#[derive(Debug, Clone, Copy)]
pub struct PingConfig {
pub samples: u32,
pub interval: Duration,
pub timeout: Duration,
}
impl Default for PingConfig {
fn default() -> Self {
Self {
samples: 10,
interval: Duration::from_millis(100),
timeout: Duration::from_secs(3),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct TransferConfig {
pub duration: Duration,
pub connections: usize,
pub sample_interval: Duration,
pub ema_alpha: f64,
pub upload_chunk_bytes: usize,
pub connect_timeout: Duration,
pub lead_in: Duration,
}
impl Default for TransferConfig {
fn default() -> Self {
Self {
duration: Duration::from_secs(10),
connections: 4,
sample_interval: Duration::from_millis(100),
ema_alpha: 0.2,
upload_chunk_bytes: 512 * 1024,
connect_timeout: Duration::from_secs(15),
lead_in: Duration::from_millis(1200),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EngineConfig {
pub ping: PingConfig,
pub transfer: TransferConfig,
}
pub struct Engine {
provider: Arc<dyn Provider>,
client: Client,
config: EngineConfig,
}
impl Engine {
pub fn new(provider: Arc<dyn Provider>, config: EngineConfig) -> EngineResult<Self> {
let client = super::network::client::build_client(
config.transfer.connect_timeout,
config.transfer.connections,
)?;
Ok(Self {
provider,
client,
config,
})
}
#[must_use]
pub fn provider_name(&self) -> &'static str {
self.provider.name()
}
pub async fn load_servers(&self) -> EngineResult<Vec<Server>> {
let servers = self.provider.fetch_servers().await?;
if servers.is_empty() {
return Err(EngineError::NoServers);
}
let reachable = super::network::health::filter_reachable(&self.client, servers).await;
if reachable.is_empty() {
return Err(EngineError::NoServers);
}
Ok(reachable)
}
pub async fn client_info(&self, server: &Server) -> Option<String> {
super::network::info::fetch_client_info(&self.client, &server.endpoints.ping).await
}
pub async fn run_test(
&self,
server: &Server,
events: mpsc::Sender<EngineEvent>,
cancel: CancellationToken,
) -> EngineResult<TestReport> {
let result =
scheduler::run_sequence(&self.client, server, &self.config, &events, &cancel).await;
match &result {
Ok(report) => {
emit(
&events,
EngineEvent::Finished {
report: report.clone(),
},
)
.await?;
}
Err(err) if !err.is_cancelled() => {
emit(
&events,
EngineEvent::Failed {
message: err.to_string(),
},
)
.await?;
}
Err(_) => {}
}
result
}
}