Skip to content
Open
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
8 changes: 7 additions & 1 deletion round-based/Cargo.toml
Comment thread
theroguevigilante marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ udigest = { version = "0.2", default-features = false, features = ["alloc", "dig
trybuild = "1"
matches = "0.1"
futures = { version = "0.3", default-features = false }
tokio = { version = "1", features = ["macros"] }
tokio = { version = "1", features = ["macros", "time", "rt"] }
Comment thread
theroguevigilante marked this conversation as resolved.
hex = "0.4"

rand = "0.8"
Expand All @@ -48,14 +48,20 @@ sha2 = "0.10"

[features]
default = []
perf-profiler = ["std"]
state-machine = []
sim = ["state-machine"]
sim-async = ["sim", "tokio/sync", "tokio-stream", "futures-util/alloc"]
derive = ["round-based-derive"]
runtime-tokio = ["tokio"]
std = []

echo-broadcast = ["dep:digest", "dep:udigest"]

[[test]]
name = "derive"
required-features = ["derive"]

[[test]]
name = "perf_test"
required-features = ["perf-profiler"]
4 changes: 3 additions & 1 deletion round-based/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,14 @@
//! ## Join us in Discord!
//! Feel free to reach out to us [in Discord](https://discordapp.com/channels/905194001349627914/1285268686147424388)!

#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg, doc_cfg_hide))]
#![warn(unused_crate_dependencies, missing_docs)]
#![allow(async_fn_in_trait)]
#![no_std]

extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

/// Fixes false-positive of `unused_crate_dependencies` lint that only occur in the tests
#[cfg(test)]
Expand Down
3 changes: 3 additions & 0 deletions round-based/src/mpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,6 @@ where
{
MpcParty::connected_halves(incomings, outgoings)
}

#[cfg(feature = "perf-profiler")]
pub mod profiler;
Comment thread
theroguevigilante marked this conversation as resolved.
8 changes: 8 additions & 0 deletions round-based/src/mpc/profiler/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Performance profiler for MPC execution.

/// Performance reporting.
pub mod profiling;
/// Statistics aggregation.
pub mod stats;
/// Profiler wrapper.
pub mod wrapper;
207 changes: 207 additions & 0 deletions round-based/src/mpc/profiler/profiling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use std::fmt;
use std::time::{Duration, Instant};
use std::vec::Vec;

/// An event captured during MPC execution.
#[derive(Debug, Clone)]
pub enum Event {
/// Sent a message.
SendMsg {
/// Number of the round.
round: u16,
/// Time when `.send().await` was called.
started: Instant,
/// Time when `.send().await` has returned.
finished: Instant,
},
/// Received messages (completed a round).
RecvMsgs {
/// Number of the round.
round: u16,
/// Time when `.complete().await` was called.
started: Instant,
/// Time when `.complete().await` has returned.
finished: Instant,
},
/// Yielded to the scheduler.
Yielded {
/// Time when `.yield_now().await` was called.
started: Instant,
/// Time when `.yield_now().await` has returned.
finished: Instant,
},
}

impl Event {
/// Returns the round number associated with the event.
pub fn round(&self) -> u16 {
match self {
Event::SendMsg { round, .. } => *round,
Event::RecvMsgs { round, .. } => *round,
Event::Yielded { .. } => 0, // Global
}
}

/// Returns the duration of the event.
pub fn duration(&self) -> Duration {
match self {
Event::SendMsg {
started, finished, ..
} => *finished - *started,
Event::RecvMsgs {
started, finished, ..
} => *finished - *started,
Event::Yielded {
started, finished, ..
} => *finished - *started,
}
}
}

/// Statistics for a single round of an MPC protocol.
#[derive(Debug, Clone, Default)]
pub struct RoundStats {
/// Number of the round.
pub round: usize,
/// Time spent on computation during this round.
pub computation_time: Duration,
/// Time spent on sending messages during this round.
pub sent_io_time: Duration,
/// Time spent on receiving messages during this round.
pub recv_io_time: Duration,
/// Time spent on waiting for the scheduler (yield_now).
pub yield_time: Duration,
}

/// A full performance report for a single protocol execution.
#[derive(Debug, Clone, Default)]
pub struct PerfReport {
/// Statistics for each round.
pub rounds: Vec<RoundStats>,
}

impl PerfReport {
/// Builds a report from a sequence of events.
pub fn from_events(start_time: Instant, end_time: Instant, events: Vec<Event>) -> Self {
let mut report = Self::default();
let mut last_finished = start_time;

for event in events {
// Computation is the gap since the last event finished
let computation = event.started().duration_since(last_finished);
let round = event.round() as usize;

let (sent, recv, yielded) = match &event {
Event::SendMsg { .. } => (event.duration(), Duration::ZERO, Duration::ZERO),
Event::RecvMsgs { .. } => (Duration::ZERO, event.duration(), Duration::ZERO),
Event::Yielded { .. } => (Duration::ZERO, Duration::ZERO, event.duration()),
};

report.apply_stats(round, computation, sent, recv, yielded);
last_finished = event.finished();
}

// Add trailing computation
if end_time > last_finished {
report.apply_stats(
0,
end_time - last_finished,
Duration::ZERO,
Duration::ZERO,
Duration::ZERO,
);
}

report
}

fn apply_stats(
&mut self,
round: usize,
computation: Duration,
sent_io: Duration,
recv_io: Duration,
yield_time: Duration,
) {
if let Some(existing) = self.rounds.iter_mut().find(|r| r.round == round) {
existing.computation_time += computation;
existing.sent_io_time += sent_io;
existing.recv_io_time += recv_io;
existing.yield_time += yield_time;
return;
}
self.rounds.push(RoundStats {
round,
computation_time: computation,
sent_io_time: sent_io,
recv_io_time: recv_io,
yield_time,
});
}

/// Calculates the total computation time across all rounds.
pub fn total_computation(&self) -> Duration {
self.rounds.iter().map(|r| r.computation_time).sum()
}
/// Calculates the total I/O time spent on sending across all rounds.
pub fn total_sent_io(&self) -> Duration {
self.rounds.iter().map(|r| r.sent_io_time).sum()
}
/// Calculates the total I/O time spent on receiving across all rounds.
pub fn total_recv_io(&self) -> Duration {
self.rounds.iter().map(|r| r.recv_io_time).sum()
}
/// Calculates the total I/O time spent on yielding across all rounds.
pub fn total_yield(&self) -> Duration {
self.rounds.iter().map(|r| r.yield_time).sum()
}
/// Calculates the total I/O time across all rounds (send + recv + yield).
pub fn total_io(&self) -> Duration {
self.total_sent_io() + self.total_recv_io() + self.total_yield()
}
/// Calculates the total execution time (computation + I/O).
pub fn total_time(&self) -> Duration {
self.total_computation() + self.total_io()
}
}

impl Event {
fn started(&self) -> Instant {
match self {
Event::SendMsg { started, .. } => *started,
Event::RecvMsgs { started, .. } => *started,
Event::Yielded { started, .. } => *started,
}
}
fn finished(&self) -> Instant {
match self {
Event::SendMsg { finished, .. } => *finished,
Event::RecvMsgs { finished, .. } => *finished,
Event::Yielded { finished, .. } => *finished,
}
}
}

impl fmt::Display for PerfReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "=== MPC Performance Report ===")?;
for stat in &self.rounds {
writeln!(
f,
"Round {}: Computation: {:?}, Sent I/O: {:?}, Recv I/O: {:?}, Yield: {:?}",
stat.round,
stat.computation_time,
stat.sent_io_time,
stat.recv_io_time,
stat.yield_time
)?;
}
writeln!(f, "------------------------------")?;
writeln!(f, "Total Computation: {:?}", self.total_computation())?;
writeln!(f, "Total Sent I/O: {:?}", self.total_sent_io())?;
writeln!(f, "Total Recv I/O: {:?}", self.total_recv_io())?;
writeln!(f, "Total Yield: {:?}", self.total_yield())?;
writeln!(f, "Total Time: {:?}", self.total_time())?;
Ok(())
}
}
121 changes: 121 additions & 0 deletions round-based/src/mpc/profiler/stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! Statistics for MPC protocol execution.

use super::profiling::PerfReport;
use std::string::{String, ToString};
use std::time::Duration;
use std::vec::Vec;
use std::{format, println};

/// Aggregated statistics for a set of durations.
#[derive(Debug)]
pub struct AggregatedStats {
/// Name of the metric.
pub metric_name: String,
/// Mean duration.
pub mean: Duration,
/// Standard deviation of durations.
pub std_dev: Duration,
/// Median (50th percentile) duration.
pub p50: Duration,
/// 75th percentile duration.
pub p75: Duration,
/// 90th percentile duration.
pub p90: Duration,
}

impl std::fmt::Display for AggregatedStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Table-like formatting: {:<N} left-aligns the value and pads it to N characters for a clean grid
write!(
f,
"{:<20} | {:<12} | {:<12} | {:<12} | {:<12} | {:<12}",
self.metric_name,
format!("{:?}", self.mean),
format!("{:?}", self.std_dev),
format!("{:?}", self.p50),
format!("{:?}", self.p75),
format!("{:?}", self.p90)
)
Comment thread
theroguevigilante marked this conversation as resolved.
}
}

/// Analyzes a set of durations and returns aggregated statistics.
pub fn analyze_durations(name: &str, mut durations: Vec<Duration>) -> AggregatedStats {
if durations.is_empty() {
return AggregatedStats {
metric_name: name.to_string(),
mean: Duration::ZERO,
std_dev: Duration::ZERO,
p50: Duration::ZERO,
p75: Duration::ZERO,
p90: Duration::ZERO,
};
}

durations.sort_unstable();
let len = durations.len();

let sum: Duration = durations.iter().sum();
let mean = sum / len as u32;

let variance_secs = durations
.iter()
.map(|&d| {
let diff = d.abs_diff(mean);
diff.as_secs_f64().powi(2)
})
.sum::<f64>()
/ len as f64;

let std_dev = Duration::from_secs_f64(variance_secs.sqrt());

let p50 = durations[(len as f64 * 0.50).floor() as usize];
let p75 = durations[(len as f64 * 0.75).floor() as usize];
let p90 = durations[(len as f64 * 0.90).floor() as usize];

AggregatedStats {
metric_name: name.to_string(),
mean,
std_dev,
p50,
p75,
p90,
}
}

/// Helper to consume multiple reports and print aggregated analytics for all metrics.
pub fn analyze_reports(reports: &[PerfReport]) {
if reports.is_empty() {
println!("No reports to analyze.");
return;
}

let mut total_times = Vec::with_capacity(reports.len());
let mut comp_times = Vec::with_capacity(reports.len());
let mut sent_io_times = Vec::with_capacity(reports.len());
let mut recv_io_times = Vec::with_capacity(reports.len());
let mut yield_times = Vec::with_capacity(reports.len());

for report in reports {
total_times.push(report.total_time());
comp_times.push(report.total_computation());
sent_io_times.push(report.total_sent_io());
recv_io_times.push(report.total_recv_io());
yield_times.push(report.total_yield());
}

println!("\n=== MPC Execution Analytics ({} runs) ===", reports.len());
println!(
"{:<20} | {:<12} | {:<12} | {:<12} | {:<12} | {:<12}",
"Metric", "Mean", "Std Dev", "p50", "p75", "p90"
);
println!("{}", "-".repeat(90));
println!("{}", analyze_durations("Total Time", total_times));
println!("{}", analyze_durations("Computation", comp_times));
println!("{}", analyze_durations("Sent I/O", sent_io_times));
println!("{}", analyze_durations("Recv I/O", recv_io_times));
println!("{}", analyze_durations("Yield", yield_times));
println!(
"========================================================================================\n"
);
}
Loading