Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,33 @@ futures = "0.3.30"
clap = { version = "4.5.4", features = ["derive"] }
serde = { version = "1.0", features = ["derive"], optional = true }
serde_bytes = "0.11"
arbitrary = { version = "1", features = ["derive"], optional = true }

[dev-dependencies]
tracing-test = "0.2.4"
tracing-subscriber = "0.3.19"
udp-stream = "0.0.12"
arbitrary = { version = "1.4.1", features = ["derive"] }
criterion = { version = "0.5.1", features = ["html_reports", "async_tokio"] }
rand = "0.9.1"

[build-dependencies]
convert_case = "0.6.0"
quote = "1.0"
proc-macro2 = "1.0.24"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.64"
arbitrary = { version = "1.4.1", features = ["derive"] }

[features]
local_runner = []
arbitrary = ["dep:arbitrary"]
default = ["serde"]

[[bench]]
name = "bench_codec"
harness = false

[[bench]]
name = "bench_parser"
harness = false
129 changes: 129 additions & 0 deletions benches/bench_codec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
mod helper;

use bluerobotics_ping::{codec::PingCodec, message::ProtocolMessage};
use criterion::{
black_box, criterion_group, criterion_main, AxisScale, BatchSize, BenchmarkId, Criterion,
PlotConfiguration, Throughput,
};
use futures::{SinkExt, StreamExt};
use helper::protocol_message_from_messages;
use rand::{rngs::StdRng, SeedableRng};
use tokio_util::codec::{Decoder, Encoder, FramedRead, FramedWrite};

fn benchmark_decode(c: &mut Criterion) {
let seed = 42;
let mut rng: StdRng = SeedableRng::seed_from_u64(seed);

let mut group = c.benchmark_group("decode");
group
.measurement_time(std::time::Duration::from_secs(10))
.significance_level(0.01)
.sample_size(1000)
.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));

for messages_count in &vec![1, 10, 100, 1000, 10000] {
group.throughput(Throughput::Elements(*messages_count));

let buffer = helper::create_random_protocol_messages(&mut rng, *messages_count as usize)
.iter()
.map(|protocol_message| protocol_message.serialized())
.flatten()
.collect::<Vec<u8>>();

group.bench_function(BenchmarkId::new("sync", messages_count), |b| {
b.to_async(&helper::rt()).iter_batched(
|| {
let buffer = bytes::BytesMut::from(buffer.as_slice());
(buffer, PingCodec::new())
},
|(mut buffer, mut codec)| async move {
for _ in 0..*messages_count {
let _protocol_message =
black_box(codec.decode(&mut buffer).unwrap().unwrap());
}
},
BatchSize::SmallInput,
)
});

group.bench_function(BenchmarkId::new("async-framed", messages_count), |b| {
b.to_async(&helper::rt()).iter_batched(
|| {
let codec = PingCodec::new();
let framed_read = FramedRead::new(buffer.as_slice(), codec);

framed_read
},
|mut framed_read| async move {
for _ in 0..*messages_count {
let _protocol_message =
black_box(framed_read.next().await.unwrap().unwrap());
}
},
BatchSize::SmallInput,
)
});
}

group.finish();
}

fn benchmark_encode(c: &mut Criterion) {
let seed = 42;
let mut rng: StdRng = SeedableRng::seed_from_u64(seed);

let mut group = c.benchmark_group("encode");
group
.measurement_time(std::time::Duration::from_secs(10))
.significance_level(0.1)
.sample_size(1000)
.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));

for messages_count in &vec![1, 10, 100, 1000, 10000] {
group.throughput(Throughput::Elements(*messages_count));

let protocol_messages = helper::create_random_messages(&mut rng, *messages_count as usize)
.iter()
.map(protocol_message_from_messages)
.collect::<Vec<ProtocolMessage>>();
let buffer: Vec<u8> =
Vec::with_capacity(*messages_count as usize * size_of::<ProtocolMessage>());

group.bench_function(BenchmarkId::new("sync", messages_count), |b| {
b.to_async(&helper::rt()).iter_batched(
|| {
let buffer = bytes::BytesMut::from(buffer.as_slice());
(protocol_messages.clone(), buffer, PingCodec::new())
},
|(protocol_messages, mut buffer, mut codec)| async move {
for protocol_message in protocol_messages {
codec.encode(protocol_message, &mut buffer).unwrap();
}
},
BatchSize::SmallInput,
)
});

group.bench_function(BenchmarkId::new("async-framed", messages_count), |b| {
b.to_async(&helper::rt()).iter_batched(
|| {
let codec = PingCodec::new();
let framed_write = FramedWrite::new(buffer.clone(), codec);

(protocol_messages.clone(), framed_write)
},
|(messages, mut framed_write)| async move {
for protocol_message in messages {
framed_write.send(protocol_message).await.unwrap();
}
},
BatchSize::SmallInput,
)
});
}

group.finish();
}

criterion_group!(benches, benchmark_decode, benchmark_encode);
criterion_main!(benches);
83 changes: 83 additions & 0 deletions benches/bench_parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
mod helper;

use bluerobotics_ping::{message::ProtocolMessage, Messages};
use criterion::{
black_box, criterion_group, criterion_main, AxisScale, BatchSize, BenchmarkId, Criterion,
PlotConfiguration, Throughput,
};
use helper::protocol_message_from_messages;
use rand::{rngs::StdRng, SeedableRng};

fn benchmark_protocol_message_to_messages(c: &mut Criterion) {
let seed = 42;
let mut rng: StdRng = SeedableRng::seed_from_u64(seed);

let mut group = c.benchmark_group("protocol_message_to_messages");
group
.measurement_time(std::time::Duration::from_secs(10))
.significance_level(0.01)
.sample_size(1000)
.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));

for messages_count in &vec![1, 100, 10000] {
group.throughput(Throughput::Elements(*messages_count));

let protocol_messages = helper::create_random_messages(&mut rng, *messages_count as usize)
.iter()
.map(protocol_message_from_messages)
.collect::<Vec<ProtocolMessage>>();

group.bench_function(BenchmarkId::new("sync", messages_count), |b| {
b.to_async(&helper::rt()).iter_batched(
|| protocol_messages.clone(),
|protocol_messages| async move {
for protocol_message in &protocol_messages {
let _message = black_box(Messages::try_from(protocol_message).unwrap());
}
},
BatchSize::SmallInput,
)
});
}

group.finish();
}

fn benchmark_messages_to_protocol_message(c: &mut Criterion) {
let seed = 42;
let mut rng: StdRng = SeedableRng::seed_from_u64(seed);

let mut group = c.benchmark_group("messages_to_protocol_message");
group
.measurement_time(std::time::Duration::from_secs(10))
.significance_level(0.01)
.sample_size(1000)
.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));

for messages_count in &vec![1, 100, 10000] {
group.throughput(Throughput::Elements(*messages_count));

let messages = helper::create_random_messages(&mut rng, *messages_count as usize);

group.bench_function(BenchmarkId::new("sync", messages_count), |b| {
b.to_async(&helper::rt()).iter_batched(
|| messages.clone(),
|messages| async move {
for message in &messages {
let _protocol_message = black_box(protocol_message_from_messages(message));
}
},
BatchSize::SmallInput,
)
});
}

group.finish();
}

criterion_group!(
benches,
benchmark_protocol_message_to_messages,
benchmark_messages_to_protocol_message
);
criterion_main!(benches);
55 changes: 55 additions & 0 deletions benches/helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use bluerobotics_ping::{message::ProtocolMessage, Messages};
use rand::rngs::StdRng;
use tokio::runtime::Runtime;

pub fn rt() -> Runtime {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("criterion-tokio-rt")
.build()
.unwrap()
}

#[cfg(feature = "arbitrary")]
pub fn create_random_messages(mut rng: &mut StdRng, count: usize) -> Vec<Messages> {
use arbitrary::Arbitrary;

let mut messages = Vec::with_capacity(count);

for _ in 0..count {
let mut buf = [0u8; 1024]; // plenty of bytes available
rand::RngCore::fill_bytes(&mut rng, &mut buf);

let mut u = arbitrary::Unstructured::new(&buf);

if let Ok(msg) = Messages::arbitrary(&mut u) {
messages.push(msg);
}
}

messages
}
#[cfg(not(feature = "arbitrary"))]
pub fn create_random_messages(_rng: &mut StdRng, _count: usize) -> Vec<Messages> {
panic!("Missing 'arbitrary' feature. Re-run it with `--features=arbitrary`")
}

pub fn create_random_protocol_messages(rng: &mut StdRng, count: usize) -> Vec<ProtocolMessage> {
create_random_messages(rng, count)
.iter()
.map(protocol_message_from_messages)
.collect()
}

#[inline(always)]
pub fn protocol_message_from_messages(message: &Messages) -> ProtocolMessage {
let mut protocol_message = ProtocolMessage::new();
match message {
Messages::Ping360(message) => protocol_message.set_message(message),
Messages::Omniscan450(message) => protocol_message.set_message(message),
Messages::Bluebps(message) => protocol_message.set_message(message),
Messages::Ping1D(message) => protocol_message.set_message(message),
Messages::Common(message) => protocol_message.set_message(message),
}
protocol_message
}
1 change: 1 addition & 0 deletions build/binder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ pub fn generate<W: Write>(modules: Vec<String>, out: &mut W) {

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#enum_ident

#try_from_ident
Expand Down
8 changes: 8 additions & 0 deletions build/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ macro_rules! ident {
}

#[derive(Debug)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
struct VectorType {
size_type: Option<PayloadType>,
data_type: PayloadType,
}

#[derive(Debug)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
enum PayloadType {
CHAR,
U8,
Expand Down Expand Up @@ -92,6 +94,7 @@ impl PayloadType {
}

#[derive(Debug)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
struct Payload {
name: String,
description: Option<String>,
Expand Down Expand Up @@ -141,6 +144,7 @@ impl Payload {
}

#[derive(Debug)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
struct MessageDefinition {
name: String,
id: u16,
Expand All @@ -151,6 +155,7 @@ struct MessageDefinition {
}

#[derive(Debug, PartialEq)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
enum MessageDefinitionCategory {
Set,
Get,
Expand Down Expand Up @@ -451,6 +456,7 @@ impl MessageDefinition {
quote! {
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[doc = #comment]
pub struct #struct_name {
#(#variables)*
Expand Down Expand Up @@ -488,6 +494,7 @@ pub fn emit_protocol_wrapper() -> TokenStream {
quote! {
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub struct PingProtocolHead {
pub source_device_id: u8,
pub destiny_device_id: u8,
Expand Down Expand Up @@ -616,6 +623,7 @@ pub fn generate<R: Read, W: Write>(input: &mut R, output_rust: &mut W) {
let message_enums = quote! {
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub enum Messages {
#(#message_enums)*
}
Expand Down