// SPDX-License-Identifier: AGPL-3.0-or-later //! Benchmark the performance of encoding and decoding entries and operations while also performing //! a full validation against a schema. //! //! An [`Entry`] and accompanying [`Operation`] are encoded and decoded for varying payload sizes //! and throughput is measured. use std::convert::TryInto; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use p2panda_rs::document::DocumentViewId; use p2panda_rs::entry::decode::decode_entry; use p2panda_rs::entry::encode::encode_entry; use p2panda_rs::entry::{EncodedEntry, Entry, EntryBuilder}; use p2panda_rs::hash::Hash; use p2panda_rs::identity::KeyPair; use p2panda_rs::operation::decode::decode_operation; use p2panda_rs::operation::encode::encode_operation; use p2panda_rs::operation::validate::validate_operation_with_entry; use p2panda_rs::operation::{EncodedOperation, OperationBuilder}; use p2panda_rs::schema::{FieldType, Schema, SchemaId, SchemaName}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; /// Encode an [`Entry`] and [`Operation`] given some string payload. fn run_encode( payload: &str, key_pair: &KeyPair, schema_id: &SchemaId, ) -> (EncodedEntry, EncodedOperation) { let operation = OperationBuilder::new(schema_id) .fields(&[("payload", payload.into())]) .build() .unwrap(); let encoded_operation = encode_operation(&operation).unwrap(); let entry = EntryBuilder::new() .log_id(&0.into()) .seq_num(&1.try_into().unwrap()) .sign(&encoded_operation, key_pair) .unwrap(); let encoded_entry = encode_entry(&entry).unwrap(); (encoded_entry, encoded_operation) } /// Decode an [`Entry`] and [`Operation`] from byte encodings. fn run_decode(encoded_entry: &EncodedEntry, encoded_operation: &EncodedOperation, schema: &Schema) { let entry = decode_entry(encoded_entry).unwrap(); let plain_operation = decode_operation(encoded_operation).unwrap(); validate_operation_with_entry( &entry, encoded_entry, None::<(&Entry, &Hash)>, None::<(&Entry, &Hash)>, &plain_operation, encoded_operation, schema, ) .unwrap(); } /// Construct a random string given a size. fn random_string(size: usize) -> String { let mut rng = thread_rng(); (0..size) .map(|_| rng.sample(Alphanumeric) as char) .collect() } fn get_benchmark_id(function_name: &str, size: &usize) -> BenchmarkId { static KB: usize = 1024; let benchmark_parameter = match size > &KB { false => format!("{} B", size), true => format!("{} KiB", size / KB), }; BenchmarkId::new(function_name, benchmark_parameter) } fn criterion_benchmark(c: &mut Criterion) { static KB: usize = 1024; let key_pair = KeyPair::new(); let schema_view_id = DocumentViewId::new(&[ "00201413ae916e6745ab715c1f5ab49c47d6773c3c0febd970ecf1039beed203b472" .parse() .unwrap(), ]); let schema_name = SchemaName::new("benchmark").expect("Schema name is valid"); let schema_id = SchemaId::Application(schema_name, schema_view_id); let schema = Schema::new( &schema_id, "Payload for measuring performance of encoding, decoding and validation", &[("payload", FieldType::String)], ) .unwrap(); // Test encoding performance for a range of payload sizes let mut encode_decode = c.benchmark_group("entry and operation"); for size in [16, KB, 16 * KB, 64 * KB, 256 * KB, 1024 * KB].iter() { let payload = random_string(*size); encode_decode.throughput(Throughput::Bytes(*size as u64)); encode_decode.bench_with_input(get_benchmark_id("encode", size), size, |b, &_size| { b.iter(|| run_encode(&payload, &key_pair, &schema_id)) }); } // Test decoding performance for a range of payload sizes for size in [16, KB, 16 * KB, 64 * KB, 256 * KB, 1024 * KB].iter() { let payload = random_string(*size); let (entry_encoded, operation_encoded) = run_encode(&payload, &key_pair, &schema_id); encode_decode.throughput(Throughput::Bytes(*size as u64)); encode_decode.bench_with_input(get_benchmark_id("decode", size), size, |b, &_size| { b.iter(|| run_decode(&entry_encoded, &operation_encoded, &schema)) }); } encode_decode.finish(); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches);