use std::time::{Duration, Instant};
use bytes::{Bytes, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
use wireframe::codec::{FrameCodec, LengthDelimitedFrameCodec, examples::HotlineFrameCodec};
pub const SMALL_PAYLOAD_BYTES: usize = 32;
pub const LARGE_PAYLOAD_BYTES: usize = 64 * 1024;
pub const VALIDATION_ITERATIONS: u64 = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodecUnderTest {
LengthDelimited,
Hotline,
}
impl CodecUnderTest {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::LengthDelimited => "length_delimited",
Self::Hotline => "hotline",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PayloadClass {
Small,
Large,
}
impl PayloadClass {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Small => "small",
Self::Large => "large",
}
}
#[must_use]
pub const fn len(self) -> usize {
match self {
Self::Small => SMALL_PAYLOAD_BYTES,
Self::Large => LARGE_PAYLOAD_BYTES,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BenchmarkWorkload {
pub codec: CodecUnderTest,
pub payload_class: PayloadClass,
}
impl BenchmarkWorkload {
#[must_use]
pub fn label(self) -> String {
format!("{}_{}", self.codec.label(), self.payload_class.label())
}
}
#[must_use]
pub const fn benchmark_workloads() -> [BenchmarkWorkload; 4] {
[
BenchmarkWorkload {
codec: CodecUnderTest::LengthDelimited,
payload_class: PayloadClass::Small,
},
BenchmarkWorkload {
codec: CodecUnderTest::LengthDelimited,
payload_class: PayloadClass::Large,
},
BenchmarkWorkload {
codec: CodecUnderTest::Hotline,
payload_class: PayloadClass::Small,
},
BenchmarkWorkload {
codec: CodecUnderTest::Hotline,
payload_class: PayloadClass::Large,
},
]
}
#[must_use]
pub fn payload_for_class(class: PayloadClass) -> Bytes {
let mut payload = Vec::with_capacity(class.len());
let mut next_byte = 0_u8;
for _ in 0..class.len() {
payload.push(next_byte);
next_byte = if next_byte == 250 { 0 } else { next_byte + 1 };
}
Bytes::from(payload)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Measurement {
pub operations: u64,
pub payload_bytes: u64,
pub elapsed: Duration,
}
pub fn measure_encode(workload: BenchmarkWorkload, iterations: u64) -> Result<Measurement, String> {
let payload = payload_for_class(workload.payload_class);
let payload_len = payload.len() as u64;
if iterations == 0 {
return Ok(Measurement {
operations: 0,
payload_bytes: 0,
elapsed: Duration::ZERO,
});
}
match workload.codec {
CodecUnderTest::LengthDelimited => {
measure_encode_length_delimited(payload, payload_len, iterations)
}
CodecUnderTest::Hotline => measure_encode_hotline(payload, payload_len, iterations),
}
}
pub fn measure_decode(workload: BenchmarkWorkload, iterations: u64) -> Result<Measurement, String> {
if iterations == 0 {
return Ok(Measurement {
operations: 0,
payload_bytes: 0,
elapsed: Duration::ZERO,
});
}
let payload = payload_for_class(workload.payload_class);
let payload_len = payload.len() as u64;
match workload.codec {
CodecUnderTest::LengthDelimited => {
measure_decode_length_delimited(payload, payload_len, iterations)
}
CodecUnderTest::Hotline => measure_decode_hotline(payload, payload_len, iterations),
}
}
macro_rules! codec_measure_encode_fn {
($fn_name:ident, $codec_type:ty, $label_str:literal) => {
fn $fn_name(
payload: Bytes,
payload_len: u64,
iterations: u64,
) -> Result<Measurement, String> {
let codec = <$codec_type>::new(LARGE_PAYLOAD_BYTES + 4096);
let mut encoder = codec.encoder();
let mut wire = BytesMut::new();
let started = Instant::now();
for _ in 0..iterations {
wire.clear();
encoder
.encode(codec.wrap_payload(payload.clone()), &mut wire)
.map_err(|err| format!("{} encode failed: {err}", $label_str))?;
}
Ok(Measurement {
operations: iterations,
payload_bytes: iterations.saturating_mul(payload_len),
elapsed: started.elapsed(),
})
}
};
}
codec_measure_encode_fn!(
measure_encode_length_delimited,
LengthDelimitedFrameCodec,
"length-delimited"
);
codec_measure_encode_fn!(measure_encode_hotline, HotlineFrameCodec, "hotline");
macro_rules! codec_measure_decode_fn {
($fn_name:ident, $codec_type:ty, $label_str:literal, $frame_payload_path:path) => {
fn $fn_name(
payload: Bytes,
payload_len: u64,
iterations: u64,
) -> Result<Measurement, String> {
let codec = <$codec_type>::new(LARGE_PAYLOAD_BYTES + 4096);
let mut seed_encoder = codec.encoder();
let mut encoded = BytesMut::new();
seed_encoder
.encode(codec.wrap_payload(payload), &mut encoded)
.map_err(|err| format!("{} seed encode failed: {err}", $label_str))?;
let encoded = encoded.freeze();
let mut decoder = codec.decoder();
let started = Instant::now();
for _ in 0..iterations {
let mut wire = BytesMut::from(encoded.as_ref());
let frame = decoder
.decode(&mut wire)
.map_err(|err| format!("{} decode failed: {err}", $label_str))?
.ok_or_else(|| format!("{} decode produced no frame", $label_str))?;
if $frame_payload_path(&frame).is_empty() {
return Err(format!("{} decode produced empty payload", $label_str));
}
}
Ok(Measurement {
operations: iterations,
payload_bytes: iterations.saturating_mul(payload_len),
elapsed: started.elapsed(),
})
}
};
}
codec_measure_decode_fn!(
measure_decode_length_delimited,
LengthDelimitedFrameCodec,
"length-delimited",
LengthDelimitedFrameCodec::frame_payload
);
codec_measure_decode_fn!(
measure_decode_hotline,
HotlineFrameCodec,
"hotline",
HotlineFrameCodec::frame_payload
);