perf: optimize monitor runner, fix SSE leak, deduplicate shared utils
This commit is contained in:
@@ -37,11 +37,28 @@ async fn main() -> Result<()> {
|
||||
|
||||
let in_flight: Arc<Mutex<HashSet<String>>> = Arc::new(Mutex::new(HashSet::new()));
|
||||
|
||||
let shutdown = tokio::signal::ctrl_c();
|
||||
tokio::pin!(shutdown);
|
||||
|
||||
loop {
|
||||
match runner::fetch_and_run(&client, &coordinator_url, &monitor_token, ®ion, &in_flight).await {
|
||||
Ok(n) => { if n > 0 { info!("Spawned {n} checks"); } },
|
||||
Err(e) => error!("Check cycle failed: {e}"),
|
||||
tokio::select! {
|
||||
_ = &mut shutdown => {
|
||||
info!("Shutdown signal received, waiting for in-flight checks...");
|
||||
let deadline = tokio::time::Instant::now() + Duration::from_secs(35);
|
||||
while !in_flight.lock().await.is_empty() && tokio::time::Instant::now() < deadline {
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
info!("Shutdown complete");
|
||||
break;
|
||||
}
|
||||
_ = sleep(Duration::from_millis(1000)) => {
|
||||
match runner::fetch_and_run(&client, &coordinator_url, &monitor_token, ®ion, &in_flight).await {
|
||||
Ok(n) => { if n > 0 { info!("Spawned {n} checks"); } },
|
||||
Err(e) => error!("Check cycle failed: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -105,9 +105,13 @@ pub fn evaluate(query: &Value, response: &Response) -> Result<bool> {
|
||||
Value::Object(m) => m,
|
||||
_ => bail!("$select expects an object {{ selector: condition }}"),
|
||||
};
|
||||
// Parse HTML once for all selectors
|
||||
let doc = Html::parse_document(&response.body);
|
||||
for (selector, condition) in sel_obj {
|
||||
let selected = css_select(&response.body, selector)
|
||||
.map(Value::String)
|
||||
let sel = Selector::parse(selector)
|
||||
.map_err(|_| anyhow::anyhow!("Invalid CSS selector: {selector}"))?;
|
||||
let selected = doc.select(&sel).next()
|
||||
.map(|el| Value::String(el.text().collect::<String>().trim().to_string()))
|
||||
.unwrap_or(Value::Null);
|
||||
if !eval_condition(condition, &selected, response)? { return Ok(false); }
|
||||
}
|
||||
@@ -244,7 +248,10 @@ fn eval_op(op: &str, field_val: &Value, val: &Value, response: &Response) -> Res
|
||||
// If no comparison operator follows, just check existence
|
||||
selected.is_some()
|
||||
}
|
||||
_ => true, // unknown op — skip
|
||||
_ => {
|
||||
tracing::warn!("Unknown query operator: {op}");
|
||||
false
|
||||
}
|
||||
};
|
||||
Ok(ok)
|
||||
}
|
||||
|
||||
+29
-17
@@ -8,6 +8,17 @@ use std::time::Instant;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
// Cache native root certs per OS thread to avoid reloading from disk on every check.
|
||||
thread_local! {
|
||||
static ROOT_CERTS: Arc<Vec<ureq::tls::Certificate<'static>>> = Arc::new(
|
||||
rustls_native_certs::load_native_certs()
|
||||
.certs
|
||||
.into_iter()
|
||||
.map(|c| ureq::tls::Certificate::from_der(c.as_ref()).to_owned())
|
||||
.collect()
|
||||
);
|
||||
}
|
||||
|
||||
/// Fetch due monitors from coordinator, run them, post results back.
|
||||
pub async fn fetch_and_run(
|
||||
client: &reqwest::Client,
|
||||
@@ -34,9 +45,10 @@ pub async fn fetch_and_run(
|
||||
let n = monitors.len();
|
||||
if n == 0 { return Ok(0); }
|
||||
|
||||
// run_id is computed deterministically per monitor+interval bucket so all regions
|
||||
// checking within the same scheduled window share the same ID.
|
||||
// Format: first 8 chars of monitor_id + ':' + floor(scheduled_at_epoch / interval_s)
|
||||
// Shared read-only strings — clone the Arc instead of allocating per monitor
|
||||
let coordinator_url: Arc<str> = Arc::from(coordinator_url);
|
||||
let token: Arc<str> = Arc::from(token);
|
||||
let region: Arc<str> = Arc::from(region);
|
||||
|
||||
// Spawn all checks — fire and forget, skip if already in-flight
|
||||
let mut spawned = 0usize;
|
||||
@@ -51,9 +63,9 @@ pub async fn fetch_and_run(
|
||||
}
|
||||
spawned += 1;
|
||||
let client = client.clone();
|
||||
let coordinator_url = coordinator_url.to_string();
|
||||
let token = token.to_string();
|
||||
let region_owned = region.to_string();
|
||||
let coordinator_url = Arc::clone(&coordinator_url);
|
||||
let token = Arc::clone(&token);
|
||||
let region_owned = Arc::clone(®ion);
|
||||
// run_id: hash(monitor_id, interval_bucket) — same across all regions for this window
|
||||
let run_id_owned = {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
@@ -96,7 +108,7 @@ pub async fn fetch_and_run(
|
||||
error: Some(format!("timed out after {}ms", timeout_ms)),
|
||||
cert_expiry_days: None,
|
||||
meta: None,
|
||||
region: if region_owned.is_empty() { None } else { Some(region_owned.clone()) },
|
||||
region: if region_owned.is_empty() { None } else { Some(region_owned.to_string()) },
|
||||
run_id: Some(run_id_owned.clone()),
|
||||
},
|
||||
};
|
||||
@@ -139,7 +151,13 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<Result<(u16, HashMap<String, String>, String), String>>();
|
||||
std::thread::spawn(move || {
|
||||
let _ = tx.send(run_check_blocking(&url, &method_clone, req_headers.as_ref(), req_body.as_deref(), timeout));
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
run_check_blocking(&url, &method_clone, req_headers.as_ref(), req_body.as_deref(), timeout)
|
||||
}));
|
||||
let _ = tx.send(match result {
|
||||
Ok(r) => r,
|
||||
Err(_) => Err("check panicked".to_string()),
|
||||
});
|
||||
});
|
||||
|
||||
let curl_result = tokio::time::timeout(timeout + std::time::Duration::from_secs(2), rx)
|
||||
@@ -244,17 +262,11 @@ fn run_check_blocking(
|
||||
body: Option<&str>,
|
||||
timeout: std::time::Duration,
|
||||
) -> Result<(u16, HashMap<String, String>, String), String> {
|
||||
// Load system CA certs so we can verify chains from Cloudflare and other
|
||||
// CAs not included in the bundled webpki-roots.
|
||||
let root_certs: Vec<ureq::tls::Certificate<'static>> =
|
||||
rustls_native_certs::load_native_certs()
|
||||
.certs
|
||||
.into_iter()
|
||||
.map(|c| ureq::tls::Certificate::from_der(c.as_ref()).to_owned())
|
||||
.collect();
|
||||
// Reuse cached root certs (loaded once per OS thread via thread_local)
|
||||
let root_certs = ROOT_CERTS.with(|c| Arc::clone(c));
|
||||
|
||||
let tls = ureq::tls::TlsConfig::builder()
|
||||
.root_certs(ureq::tls::RootCerts::Specific(Arc::new(root_certs)))
|
||||
.root_certs(ureq::tls::RootCerts::Specific(root_certs))
|
||||
.build();
|
||||
|
||||
let agent = ureq::Agent::config_builder()
|
||||
|
||||
Reference in New Issue
Block a user