///|
/// Aggregate metrics for one fixed analysis window.
pub struct EventWindowMetric {
start : Int64
end : Int64
changes : Int
active_signals : Int
rising_edges : Int
falling_edges : Int
unknown_changes : Int
density : Double
} derive(Debug, Eq)
///|
/// Calculate event metrics for every fixed-width window.
pub fn event_window_metrics(
trace : Trace,
width : Int64,
) -> Array[EventWindowMetric] {
let result : Array[EventWindowMetric] = []
for window in time_windows(trace, width) {
let summary = summarize_window(trace, window.0, window.1)
let duration = Int64::max(1L, window.1 - window.0)
result.push({
start: summary.start,
end: summary.end,
changes: summary.changes,
active_signals: summary.signals,
rising_edges: summary.rising_edges,
falling_edges: summary.falling_edges,
unknown_changes: summary.unknown_changes,
density: summary.changes.to_double() / duration.to_double(),
})
}
result
}
///|
/// Render event metrics as CSV for charting.
pub fn event_window_metrics_csv(metrics : Array[EventWindowMetric]) -> String {
let out : Array[String] = [
"start,end,changes,active_signals,rising,falling,unknown,density",
]
for item in metrics {
out.push(
item.start.to_string() +
"," +
item.end.to_string() +
"," +
item.changes.to_string() +
"," +
item.active_signals.to_string() +
"," +
item.rising_edges.to_string() +
"," +
item.falling_edges.to_string() +
"," +
item.unknown_changes.to_string() +
"," +
item.density.to_string(),
)
}
out.join("\n")
}
///|
/// Return the busiest window by change count.
pub fn busiest_event_window(
metrics : Array[EventWindowMetric],
) -> EventWindowMetric? {
let mut best : EventWindowMetric? = None
for item in metrics {
match best {
Some(current) if current.changes >= item.changes => ()
_ => best = Some(item)
}
}
best
}
///|
/// Return the average event density across non-empty windows.
pub fn mean_event_density(metrics : Array[EventWindowMetric]) -> Double {
if metrics.length() == 0 {
0.0
} else {
metrics.fold(init=0.0, fn(total, item) { total + item.density }) /
metrics.length().to_double()
}
}
///|
/// Return a bounded window quality score based on unknown samples.
pub fn event_window_quality(item : EventWindowMetric) -> Int {
let penalty = Int::min(100, item.unknown_changes * 10)
100 - penalty
}
///|
/// Return whether windows cover the complete trace without gaps.
pub fn windows_cover_trace(trace : Trace, width : Int64) -> Bool {
let windows = time_windows(trace, width)
if windows.length() == 0 {
return trace.end_time < 0L
}
if windows[0].0 != 0L {
return false
}
for index in 1.. String {
let metrics = event_window_metrics(trace, width)
let busiest = match busiest_event_window(metrics) {
Some(item) => item.changes.to_string()
None => "0"
}
"windows=" +
metrics.length().to_string() +
" busiest_changes=" +
busiest +
" mean_density=" +
mean_event_density(metrics).to_string() +
" cover=" +
windows_cover_trace(trace, width).to_string()
}