pub mod html;
pub mod interpret;
pub mod json;
pub mod metrics;
pub mod sarif;
use crate::correlate::Trace;
use crate::detect::Finding;
use crate::detect::correlate_cross::CrossTraceCorrelation;
use crate::report::interpret::InterpretationLevel;
use crate::score::carbon::{CarbonReport, RegionBreakdown};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Report {
pub analysis: Analysis,
pub findings: Vec<Finding>,
pub green_summary: GreenSummary,
pub quality_gate: QualityGate,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub per_endpoint_io_ops: Vec<PerEndpointIoOps>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub correlations: Vec<CrossTraceCorrelation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Analysis {
pub duration_ms: u64,
pub events_processed: usize,
pub traces_analyzed: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GreenSummary {
pub total_io_ops: usize,
pub avoidable_io_ops: usize,
pub io_waste_ratio: f64,
pub io_waste_ratio_band: InterpretationLevel,
pub top_offenders: Vec<TopOffender>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub co2: Option<CarbonReport>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub regions: Vec<RegionBreakdown>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transport_gco2: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PerEndpointIoOps {
pub service: String,
pub endpoint: String,
pub io_ops: usize,
}
#[must_use]
pub fn compute_per_endpoint_io_ops(traces: &[Trace]) -> Vec<PerEndpointIoOps> {
let mut counts: BTreeMap<(&str, &str), usize> = BTreeMap::new();
for trace in traces {
for span in &trace.spans {
let key = (
span.event.service.as_str(),
span.event.source.endpoint.as_str(),
);
*counts.entry(key).or_insert(0) += 1;
}
}
counts
.into_iter()
.map(|((service, endpoint), io_ops)| PerEndpointIoOps {
service: service.to_string(),
endpoint: endpoint.to_string(),
io_ops,
})
.collect()
}
impl GreenSummary {
#[must_use]
pub fn disabled(total_io_ops: usize) -> Self {
Self {
total_io_ops,
avoidable_io_ops: 0,
io_waste_ratio: 0.0,
io_waste_ratio_band: InterpretationLevel::Healthy,
top_offenders: vec![],
co2: None,
regions: vec![],
transport_gco2: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TopOffender {
pub endpoint: String,
pub service: String,
pub io_intensity_score: f64,
pub io_intensity_band: InterpretationLevel,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub co2_grams: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityGate {
pub passed: bool,
pub rules: Vec<QualityRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityRule {
pub rule: String,
pub threshold: f64,
pub actual: f64,
pub passed: bool,
}
pub trait ReportSink {
type Error: std::error::Error;
fn emit(&self, report: &Report) -> Result<(), Self::Error>;
}