use super::funejson::Value; use super::math_round_div::round_div; use super::monitor_incidents::{incidents_from_value, Incident}; /// Reliability figures for the window from <= t < to. An incident counts when /// it overlaps the window; downtime is clipped to it, but MTTR and the /// longest incident use full durations, because an outage that began last /// month still took as long as it took to fix. /// /// # Panics /// Panics when from is after to, on an inconsistent incident, or on /// incidents out of order or overlapping. pub fn reliability_stats(incidents: &[Incident], from: i64, to: i64) -> ReliabilityStats { if from > to { panic!("from must not be after to: {} > {}", from, to); } let mut prev_end: Option = None; let (mut count, mut resolved, mut downtime, mut repair_total, mut longest) = (0i64, 0i64, 0i64, 0i64, 0i64); for x in incidents { if x.duration_seconds < 0 { panic!("durationSeconds must not be negative, received {}", x.duration_seconds); } if let Some(e) = x.end { if e < x.start { panic!("incident end must not be before its start: {} < {}", e, x.start); } if x.duration_seconds != e - x.start { panic!("durationSeconds must equal end - start: {} != {}", x.duration_seconds, e - x.start); } } // An ongoing incident ends, for now, where its duration says. let end = x.start + x.duration_seconds; if let Some(p) = prev_end { if x.start < p { panic!("incidents must be in time order and must not overlap: {} is before {}", x.start, p); } } prev_end = Some(end); if !(x.start < to && (x.start >= from || end > from)) { continue; } count += 1; downtime += (end.min(to) - x.start.max(from)).max(0); if x.end.is_some() { resolved += 1; repair_total += x.duration_seconds; } longest = longest.max(x.duration_seconds); } ReliabilityStats { incidents: count, resolved, downtime_seconds: downtime, mttr_seconds: if resolved > 0 { Some(round_div(repair_total, resolved, "half-up")) } else { None }, mtbf_seconds: if count > 0 { Some(round_div(to - from - downtime, count, "half-up")) } else { None }, longest_seconds: longest, } } fn opt_to_value(v: Option) -> Value { match v { Some(i) => Value::Int(i), None => Value::Null, } } pub fn reliability_stats_to_value(r: &ReliabilityStats) -> Value { Value::obj(vec![ ("incidents", Value::Int(r.incidents)), ("resolved", Value::Int(r.resolved)), ("downtimeSeconds", Value::Int(r.downtime_seconds)), ("mttrSeconds", opt_to_value(r.mttr_seconds)), ("mtbfSeconds", opt_to_value(r.mtbf_seconds)), ("longestSeconds", Value::Int(r.longest_seconds)), ]) } fn whole(v: &Value) -> i64 { match v { Value::Int(i) => *i, _ => panic!("from and to must be whole seconds"), } } pub fn fune_vector(args: &[Value]) -> Value { let incidents = incidents_from_value(&args[0]); reliability_stats_to_value(&reliability_stats(&incidents, whole(&args[1]), whole(&args[2]))) }