///|
pub(all) enum ReportSeverity {
Stable
Watch
ActionRequired
Emergency
} derive(Eq, Debug)
///|
pub(all) struct HealthReport {
service : String
target : SloTarget
statistics : WindowStatistics
latest : BudgetReport
peak_burn_x100 : Int
incident_count : Int
severity : ReportSeverity
incidents : IncidentTimeline
} derive(Eq, Debug)
///|
fn report_severity(
latest : BudgetReport,
stats : WindowStatistics,
peak_burn_x100 : Int,
incidents : Int,
) -> ReportSeverity {
if !latest.healthy() {
Emergency
} else if peak_burn_x100 >= 500 || stats.error_rate_bp() >= 100 {
ActionRequired
} else if incidents > 0 || peak_burn_x100 >= 100 {
Watch
} else {
Stable
}
}
///|
pub fn HealthReport::from_windows(
service : String,
target : SloTarget,
windows : Array[RequestWindow],
incidents : IncidentTimeline,
) -> HealthReport {
let stats = WindowStatistics::from_windows(windows)
let latest_window = if windows.length() == 0 {
RequestWindow::new("latest", 1, 0, 0)
} else {
windows[windows.length() - 1]
}
let latest = evaluate_budget(target, latest_window)
let mut peak = 0
for window in windows {
let burn = calculate_burn_rate(target, window)
if burn.burn_rate_x100 > peak {
peak = burn.burn_rate_x100
}
}
let severity = report_severity(latest, stats, peak, incidents.count())
{
service,
target,
statistics: stats,
latest,
peak_burn_x100: peak,
incident_count: incidents.count(),
severity,
incidents,
}
}
///|
pub fn HealthReport::healthy(self : HealthReport) -> Bool {
self.severity == Stable || self.severity == Watch
}
///|
pub fn HealthReport::severity_name(self : HealthReport) -> String {
match self.severity {
Stable => "stable"
Watch => "watch"
ActionRequired => "action_required"
Emergency => "emergency"
}
}
///|
pub fn HealthReport::risk_score(self : HealthReport) -> Int {
let base = match self.severity {
Stable => 0
Watch => 25
ActionRequired => 60
Emergency => 100
}
let burn_penalty = if self.peak_burn_x100 > 100 { 10 } else { 0 }
let incident_penalty = if self.incident_count > 0 { 10 } else { 0 }
let score = base + burn_penalty + incident_penalty
if score > 100 {
100
} else {
score
}
}
///|
pub fn HealthReport::recommendation(self : HealthReport) -> String {
match self.severity {
Stable => "release may proceed"
Watch => "keep the release under observation"
ActionRequired => "pause rollout and inspect recent errors"
Emergency => "stop rollout and page the on-call owner"
}
}
///|
pub fn HealthReport::to_json(self : HealthReport) -> String {
"{\"service\":\"\{escape_json(self.service)}\",\"severity\":\"\{self.severity_name()}\",\"risk_score\":\{self.risk_score()},\"peak_burn_x100\":\{self.peak_burn_x100},\"incident_count\":\{self.incident_count},\"recommendation\":\"\{self.recommendation()}\",\"latest\":\{self.latest.to_json()},\"statistics\":\{self.statistics.to_json()}}"
}
///|
pub fn HealthReport::to_markdown(self : HealthReport) -> String {
let result = StringBuilder()
result.write_string("# SLO health report\n\n")
result.write_string("- Service: \{self.service}\n")
result.write_string("- Severity: \{self.severity_name()}\n")
result.write_string("- Risk score: \{self.risk_score()}/100\n")
result.write_string(
"- Availability: \{self.statistics.average_availability_bp} bp\n",
)
result.write_string(
"- Peak burn: \{self.peak_burn_x100 / 100}.\{two_digits(self.peak_burn_x100 % 100)}x\n",
)
result.write_string("- Incidents: \{self.incident_count}\n")
result.write_string("\nRecommendation: \{self.recommendation()}\n")
result.to_string()
}
///|
pub fn HealthReport::to_metrics(self : HealthReport) -> String {
let labels = "service=\"\{escape_json(self.service)}\""
"moonslokit_health_risk_score{\{labels}} \{self.risk_score()}\nmoonslokit_health_peak_burn_x100{\{labels}} \{self.peak_burn_x100}\nmoonslokit_health_incidents{\{labels}} \{self.incident_count}\n"
}
///|
pub fn compare_reports(before : HealthReport, after : HealthReport) -> String {
let direction = if after.risk_score() < before.risk_score() {
"improved"
} else if after.risk_score() > before.risk_score() {
"regressed"
} else {
"unchanged"
}
"\{before.service}: \{direction} (\{before.risk_score()} -> \{after.risk_score()})"
}
///|
pub fn severity_from_score(score : Int) -> ReportSeverity {
if score >= 80 {
Emergency
} else if score >= 50 {
ActionRequired
} else if score >= 20 {
Watch
} else {
Stable
}
}
///|
pub fn report_is_regression(
before : HealthReport,
after : HealthReport,
) -> Bool {
after.risk_score() > before.risk_score()
}