-
Notifications
You must be signed in to change notification settings - Fork 10
feat(profiler): implement generic PerfProfiler for MPC protocols #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
theroguevigilante
wants to merge
6
commits into
LFDT-Lockness:m
Choose a base branch
from
theroguevigilante:feat/generic-profiler
base: m
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
da76e14
feat(profiler): implement generic PerfProfiler for MPC protocols
theroguevigilante 21c4c7d
feat(profiler): feature-gate profiler behind perf-profiler
theroguevigilante 10e74cb
fix(round-based): enforce strict no_std and fix tests, fmt
theroguevigilante b3bb752
feat(profiler): redesign MPC profiler for full lifecycle
theroguevigilante bd93f1f
feat(profiler): thread-safe event stream with survival handle
theroguevigilante 7ab4357
refactor(stats): analytics for all the metrics
theroguevigilante File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(()) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| ) | ||
|
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" | ||
| ); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.