use crate::meta::{AppliedScope, Warning, WarningCode, WarningSeverity};
pub const NARROW_WINDOW_RATIO_PERCENT: i64 = 1;
pub fn check_time_window(
window_ns: Option<(i64, i64)>,
trace_span_ns: Option<i64>,
) -> Option<Warning> {
let (start, end) = window_ns?;
let span = trace_span_ns?;
if span <= 0 {
return None;
}
let width = end.saturating_sub(start);
if width <= 0 {
return None;
}
let ratio = width.saturating_mul(100) / span;
if ratio >= NARROW_WINDOW_RATIO_PERCENT {
return None;
}
Some(Warning {
severity: WarningSeverity::Warn,
code: WarningCode::NarrowWindow,
message: format!(
"time window {width} ns is under {NARROW_WINDOW_RATIO_PERCENT}% of the \
trace span ({span} ns); double-check units — `--from 100 --to 200` is \
100 ns, not 100 ms. Use unit suffixes (1ms, 10us) or the `@<ns>` \
absolute marker."
),
})
}
pub fn check_empty_result(row_count: usize, scope: &AppliedScope) -> Option<Warning> {
if row_count > 0 {
return None;
}
let mut filters: Vec<&'static str> = Vec::new();
if scope.device.is_some() {
filters.push("--device");
}
if scope.stream.is_some() {
filters.push("--stream");
}
if scope.native_pid.is_some() {
filters.push("native_pid");
}
if scope.kind.is_some() {
filters.push("--type");
}
if scope.nvtx_pattern.is_some() {
filters.push("--nvtx");
}
if scope.time_window_ns.is_some() {
filters.push("--from/--to");
}
if filters.is_empty() {
return None;
}
let joined = filters.join(", ");
Some(Warning {
severity: WarningSeverity::Warn,
code: WarningCode::EmptyWithScope,
message: format!(
"scope filter ({joined}) excluded every row. Drop a filter or widen the \
query — an empty list often means the scope didn't match anything in \
this trace."
),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn narrow_window_skips_when_inputs_missing() {
assert!(check_time_window(None, Some(1_000_000_000)).is_none());
assert!(check_time_window(Some((0, 100)), None).is_none());
}
#[test]
fn narrow_window_skips_when_ratio_above_threshold() {
assert!(check_time_window(Some((0, 100_000_000)), Some(1_000_000_000)).is_none());
}
#[test]
fn narrow_window_fires_when_ratio_below_threshold() -> anyhow::Result<()> {
let w = check_time_window(Some((0, 1_000)), Some(1_000_000_000))
.ok_or_else(|| anyhow::anyhow!("narrow window must trip the guard"))?;
assert!(matches!(w.code, WarningCode::NarrowWindow));
assert!(matches!(w.severity, WarningSeverity::Warn));
Ok(())
}
#[test]
fn narrow_window_handles_inverted_endpoints() {
assert!(check_time_window(Some((100, 50)), Some(1_000)).is_none());
}
#[test]
fn empty_result_skips_when_rows_present() {
let scope = AppliedScope {
device: Some(0),
..AppliedScope::default()
};
assert!(check_empty_result(1, &scope).is_none());
}
#[test]
fn empty_result_skips_when_no_filters_set() {
let scope = AppliedScope::default();
assert!(check_empty_result(0, &scope).is_none());
}
#[test]
fn empty_result_fires_when_device_filter_excluded_everything() -> anyhow::Result<()> {
let scope = AppliedScope {
device: Some(7),
..AppliedScope::default()
};
let w = check_empty_result(0, &scope).ok_or_else(|| {
anyhow::anyhow!("zero rows under explicit --device must trip the guard")
})?;
assert!(matches!(w.code, WarningCode::EmptyWithScope));
assert!(
w.message.contains("--device"),
"message must mention the failing filter: {}",
w.message
);
Ok(())
}
#[test]
fn empty_result_ignores_aggregated_over_marker() {
let scope = AppliedScope {
aggregated_over: vec!["device".to_string()],
..AppliedScope::default()
};
assert!(check_empty_result(0, &scope).is_none());
}
}