use rudb_common::{Result, Value};
use rudb_encoding::tally;
use rudb_functions::{codec_metric_fields, write_metric_fields};
use rudb_metrics::{LoadProfile, Stage, StageTotals, recent_loads};
use rudb_plan::{Plan, Slice};
use crate::metadata::{Metadata, text};
pub(crate) fn write_metrics(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
let mut rows = Vec::new();
for load in recent_loads() {
let mut total = StageTotals::default();
for stage in Stage::ALL {
let spent = load.stage(stage);
if spent.charged == 0 && spent.waits == 0 {
continue;
}
total.cpu_ns = total.cpu_ns.saturating_add(spent.cpu_ns);
total.waits = total.waits.saturating_add(spent.waits);
total.wait_ns = total.wait_ns.saturating_add(spent.wait_ns);
rows.push(row(&load, stage.name(), &spent, false));
}
let convert = load.stage(Stage::Convert);
let pages = load.stage(Stage::Pages);
total.wall_ns = load.elapsed_ns();
total.rows = convert.rows.max(pages.rows);
total.bytes_in = pages.bytes_in;
total.bytes_out = [Stage::Dictionary, Stage::Write, Stage::Publish]
.into_iter()
.map(|stage| load.stage(stage).bytes_out)
.fold(0_u64, u64::saturating_add);
rows.push(row(&load, "total", &total, true));
}
Metadata::new("rudb_write_metrics", &write_metric_fields(), &rows, plan, index, columns)
}
pub(crate) fn codec_metrics(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
let codecs = tally::codecs();
let family_nanos = |family: &str| -> u64 {
codecs
.iter()
.filter(|codec| codec.family == family)
.map(|codec| codec.nanos)
.fold(0, u64::saturating_add)
};
let rows: Vec<Vec<Value>> = codecs
.iter()
.map(|codec| {
vec![
text(codec.family),
text(codec.name),
Value::BigInt(signed(codec.offers)),
Value::BigInt(signed(codec.kept)),
ratio(codec.kept, codec.offers),
Value::Double(millis(codec.nanos)),
ratio(codec.nanos, family_nanos(codec.family)),
]
})
.collect();
Metadata::new("rudb_codec_metrics", &codec_metric_fields(), &rows, plan, index, columns)
}
#[expect(clippy::cast_precision_loss, reason = "a share does not need 53 bits")]
fn ratio(part: u64, whole: u64) -> Value {
if whole == 0 { Value::Null } else { Value::Double(part as f64 / whole as f64) }
}
fn row(load: &LoadProfile, stage: &str, spent: &StageTotals, total: bool) -> Vec<Value> {
let (accounted, resident) = if total {
(
Value::BigInt(signed(load.accounted_peak())),
load.peak_rss().map_or(Value::Null, |peak| Value::BigInt(signed(peak))),
)
} else {
(Value::Null, Value::Null)
};
vec![
Value::BigInt(signed(load.id())),
text(load.target()),
text(stage),
Value::Double(millis(spent.wall_ns)),
Value::Double(millis(spent.cpu_ns)),
Value::BigInt(signed(spent.bytes_in)),
Value::BigInt(signed(spent.bytes_out)),
Value::BigInt(signed(spent.rows)),
Value::BigInt(signed(spent.waits)),
Value::Double(millis(spent.wait_ns)),
Value::Boolean(load.finished()),
accounted,
resident,
]
}
#[expect(clippy::cast_precision_loss, reason = "a millisecond reading does not need 53 bits")]
fn millis(nanos: u64) -> f64 {
nanos as f64 / 1e6
}
fn signed(value: u64) -> i64 {
i64::try_from(value).unwrap_or(i64::MAX)
}