diff --git a/mixnet/v2/sim/analysis.py b/mixnet/v2/sim/analysis.py index 12f1e3b..ebb08e1 100644 --- a/mixnet/v2/sim/analysis.py +++ b/mixnet/v2/sim/analysis.py @@ -3,28 +3,41 @@ import seaborn from matplotlib import pyplot as plt from adversary import NodeState +from config import Config from simulation import Simulation class Analysis: - def __init__(self, sim: Simulation): + def __init__(self, sim: Simulation, config: Config): self.sim = sim + self.config = config def run(self): - self.bandwidth() - self.message_size_distribution() + message_size_df = self.message_size_distribution() + self.bandwidth(message_size_df) self.messages_emitted_around_interval() - self.mixed_messages_per_node_over_time() + if self.config.mixnet.is_mixing_on(): + self.mixed_messages_per_node_over_time() self.node_states() - def bandwidth(self): + def bandwidth(self, message_size_df: pd.DataFrame): dataframes = [] - for ingress_bandwidths, egress_bandwidths in zip(self.sim.p2p.measurement.ingress_bandwidth_per_time, self.sim.p2p.measurement.egress_bandwidth_per_time): + nonzero_ingresses = [] + nonzero_egresses = [] + for ingress_bandwidths, egress_bandwidths in zip(self.sim.p2p.measurement.ingress_bandwidth_per_time, + self.sim.p2p.measurement.egress_bandwidth_per_time): rows = [] for node in self.sim.p2p.nodes: - rows.append((node.id, ingress_bandwidths[node]/1024.0, egress_bandwidths[node]/1024.0)) + ingress = ingress_bandwidths[node] / 1024.0 + egress = egress_bandwidths[node] / 1024.0 + rows.append((node.id, ingress, egress)) + if ingress > 0: + nonzero_ingresses.append(ingress) + if egress > 0: + nonzero_egresses.append(egress) df = pd.DataFrame(rows, columns=["node_id", "ingress", "egress"]) dataframes.append(df) + times = range(len(dataframes)) df = pd.concat([df.assign(Time=time) for df, time in zip(dataframes, times)], ignore_index=True) df = df.pivot(index="Time", columns="node_id", values=["ingress", "egress"]) @@ -41,11 +54,25 @@ class Analysis: by_label = dict(zip(labels, handles)) plt.legend(by_label.values(), by_label.keys()) plt.grid(True) + + # Adding descriptions on the right size of the plot + ingress_series = pd.Series(nonzero_ingresses) + egress_series = pd.Series(nonzero_egresses) + desc = ( + f"message: {message_size_df["message_size"].mean():.0f} bytes\n" + f"{self.config.description()}\n\n" + f"[ingress(>0)]\nmean: {ingress_series.mean():.2f} KiB/s\nmax: {ingress_series.max():.2f} KiB/s\n\n" + f"[egress(>0)]\nmean: {egress_series.mean():.2f} KiB/s\nmax: {egress_series.max():.2f} KiB/s" + ) + plt.text(1.02, 0.5, desc, transform=plt.gca().transAxes, verticalalignment="center", fontsize=12) + plt.subplots_adjust(right=0.8) # Adjust layout to make room for the text + plt.show() - def message_size_distribution(self): + def message_size_distribution(self) -> pd.DataFrame: df = pd.DataFrame(self.sim.p2p.adversary.message_sizes, columns=["message_size"]) print(df.describe()) + return df def messages_emitted_around_interval(self): df = pd.DataFrame( @@ -89,10 +116,10 @@ class Analysis: df = pd.DataFrame(rows, columns=["time", "node_id", "state"]) plt.figure(figsize=(10, 6)) - seaborn.scatterplot(data=df, x="time", y="node_id", hue="state", palette={NodeState.SENDING: "red", NodeState.RECEIVING: "blue"}) + seaborn.scatterplot(data=df, x="time", y="node_id", hue="state", + palette={NodeState.SENDING: "red", NodeState.RECEIVING: "blue"}) plt.title("Node states over time") plt.xlabel("Time") plt.ylabel("Node ID") plt.legend(title="state") plt.show() - diff --git a/mixnet/v2/sim/config.py b/mixnet/v2/sim/config.py index 5a58474..15af39e 100644 --- a/mixnet/v2/sim/config.py +++ b/mixnet/v2/sim/config.py @@ -29,6 +29,12 @@ class Config: return config + def description(self): + return ( + f"{self.mixnet.description()}\n" + f"{self.p2p.description()}" + ) + @dataclass class SimulationConfig: @@ -55,7 +61,7 @@ class MixnetConfig: real_message_prob_weights: list[float] # A probability of sending a cover message within one cycle if not sending a real message cover_message_prob: float - # A maximum preparation time (delay) before sending the message + # A maximum preparation time (computation time) for a message sender before sending the message max_message_prep_time: float # A maximum delay of messages mixed in a mix node max_mix_delay: float @@ -73,6 +79,20 @@ class MixnetConfig: assert self.max_message_prep_time >= 0 assert self.max_mix_delay >= 0 + def description(self): + return ( + f"payload: {self.payload_size} bytes\n" + f"num_nodes: {self.num_nodes}\n" + f"num_mix_layers: {self.num_mix_layers}\n" + f"max_mix_delay: {self.max_mix_delay}\n" + f"msg_interval: {self.message_interval}\n" + f"real_msg_prob: {self.real_message_prob:.2f}\n" + f"cover_msg_prob: {self.cover_message_prob:.2f}" + ) + + def is_mixing_on(self) -> bool: + return self.num_mix_layers > 0 + @dataclass class P2pConfig: @@ -82,6 +102,11 @@ class P2pConfig: def validate(self): assert self.max_network_latency >= 0 + def description(self): + return ( + f"max_net_latency: {self.max_network_latency:.2f}" + ) + @dataclass class MeasurementConfig: diff --git a/mixnet/v2/sim/config.yaml b/mixnet/v2/sim/config.yaml index 36a37b2..065e2f0 100644 --- a/mixnet/v2/sim/config.yaml +++ b/mixnet/v2/sim/config.yaml @@ -20,7 +20,7 @@ mixnet: real_message_prob_weights: [3, 2, 5] # A probability of sending a cover message within a cycle if not sending a real message cover_message_prob: 0.2 - # A maximum preparation time (delay) before sending the message + # A maximum preparation time (computation time) for a message sender before sending the message max_message_prep_time: 0.05 # A maximum delay of messages mixed in a mix node max_mix_delay: 3 diff --git a/mixnet/v2/sim/main.py b/mixnet/v2/sim/main.py index a397f22..e22bece 100644 --- a/mixnet/v2/sim/main.py +++ b/mixnet/v2/sim/main.py @@ -13,6 +13,6 @@ if __name__ == "__main__": sim = Simulation(config) sim.run() - Analysis(sim).run() + Analysis(sim, config).run() print("Simulation complete!") \ No newline at end of file diff --git a/mixnet/v2/sim/node.py b/mixnet/v2/sim/node.py index f2a2596..71045e7 100644 --- a/mixnet/v2/sim/node.py +++ b/mixnet/v2/sim/node.py @@ -63,7 +63,7 @@ class Node: Creates a real or cover message @return: """ - if self.config.mixnet.num_mix_layers == 0: # if mixing is turned off + if not self.config.mixnet.is_mixing_on(): return self.build_payload() mixes = self.p2p.get_nodes(self.config.mixnet.num_mix_layers)