use std::hint::black_box;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use metering::interval::Sparte;
use metering::interval::{MeterInterval, QualityFlag};
use metering::measurement_series::{MeasurementSeries, MeasurementSource};
use meterstore::encode::{StoredSeries, from_record_batch, to_record_batch};
use meterstore::planner::{TimeRange, split};
use meterstore::watermark::TieringWatermark;
use meterstore::{ScopedVersion, Version, VersionScope};
use rust_decimal::Decimal;
use time::macros::datetime;
use time::{Duration, OffsetDateTime};
const START: OffsetDateTime = datetime!(2026-07-20 00:00 UTC);
const INTERVALS_PER_DAY: usize = 96;
fn delivery(meter: usize) -> StoredSeries {
let intervals: Vec<MeterInterval> = (0..INTERVALS_PER_DAY)
.map(|i| {
let from = START + Duration::minutes(15 * i as i64);
MeterInterval {
from,
to: from + Duration::minutes(15),
value: Decimal::new(100 + (i as i64 * 7) % 500, 2),
quality: QualityFlag::Measured,
obis_code: "1-0:1.8.0".parse().ok(),
}
})
.collect();
let prefix = format!("{:010}", 1_000_000_000u64 + meter as u64);
let check = metering::MaloId::compute_check_digit(&prefix).expect("ten digits");
let series = MeasurementSeries::new(
format!("{prefix}{check}").parse().expect("valid MaLo-ID"),
"1-0:1.8.0".parse().ok(),
intervals,
MeasurementSource::Mscons {
pid: 13_005,
message_ref: None,
sender_mp_id: "9900000000001".parse().expect("a valid Marktpartner-ID"),
},
START,
);
StoredSeries::new(
series,
ScopedVersion::new(
VersionScope::for_interval("9900000000001", START, Sparte::Strom).expect("scope"),
Version::new(20_260_701_000_001).expect("version"),
),
START,
)
}
fn deliveries(n: usize) -> Vec<StoredSeries> {
(0..n).map(delivery).collect()
}
fn bench_encode(c: &mut Criterion) {
let mut group = c.benchmark_group("encode");
for meters in [1usize, 10, 100] {
let batch = deliveries(meters);
let rows = (meters * INTERVALS_PER_DAY) as u64;
group.throughput(Throughput::Elements(rows));
group.bench_with_input(BenchmarkId::from_parameter(rows), &batch, |b, batch| {
b.iter(|| to_record_batch(black_box(batch)).expect("encode"));
});
}
group.finish();
}
fn bench_decode(c: &mut Criterion) {
let mut group = c.benchmark_group("decode");
for meters in [1usize, 10, 100] {
let batch = to_record_batch(&deliveries(meters)).expect("encode");
let rows = (meters * INTERVALS_PER_DAY) as u64;
group.throughput(Throughput::Elements(rows));
group.bench_with_input(BenchmarkId::from_parameter(rows), &batch, |b, batch| {
b.iter(|| from_record_batch(black_box(batch)).expect("decode"));
});
}
group.finish();
}
fn bench_round_trip(c: &mut Criterion) {
let batch = deliveries(10);
let rows = (10 * INTERVALS_PER_DAY) as u64;
let mut group = c.benchmark_group("round_trip");
group.throughput(Throughput::Elements(rows));
group.bench_function("encode_then_decode", |b| {
b.iter(|| {
let encoded = to_record_batch(black_box(&batch)).expect("encode");
from_record_batch(black_box(&encoded)).expect("decode")
});
});
group.finish();
}
fn bench_split(c: &mut Criterion) {
let watermark = TieringWatermark::new(START);
let spanning = TimeRange::between(START - Duration::days(7), START + Duration::days(7));
c.bench_function("planner/split_spanning", |b| {
b.iter(|| split(black_box(spanning), black_box(watermark)));
});
}
fn bench_predicate(c: &mut Criterion) {
use datafusion::logical_expr::{col, lit};
use datafusion::scalar::ScalarValue;
let ts = |t: OffsetDateTime| {
lit(ScalarValue::TimestampMicrosecond(
Some((t.unix_timestamp_nanos() / 1_000) as i64),
Some("UTC".into()),
))
};
let filters = vec![
col("from").gt_eq(ts(START)),
col("from").lt(ts(START + Duration::days(30))),
col("malo_id").eq(lit("12345678905")),
];
c.bench_function("planner/time_range", |b| {
b.iter(|| meterstore::planner::time_range(black_box(&filters)));
});
}
fn bench_elision(c: &mut Criterion) {
use meterstore::planner::{FileStats, version};
let mut group = c.benchmark_group("planner/elision");
for files in [16usize, 256, 4096] {
let stats: Vec<FileStats> = (0..files)
.map(|day| {
let start = time::OffsetDateTime::UNIX_EPOCH + time::Duration::days(day as i64);
FileStats::single(
20_260_701_000_001 + day as i128,
(start, start + time::Duration::hours(23)),
)
})
.collect();
group.throughput(Throughput::Elements(files as u64));
group.bench_with_input(BenchmarkId::from_parameter(files), &stats, |b, stats| {
b.iter(|| version::plan(black_box(stats)))
});
}
group.finish();
}
criterion_group!(
benches,
bench_encode,
bench_decode,
bench_round_trip,
bench_split,
bench_predicate,
bench_elision,
);
criterion_main!(benches);