def main(log_dir: pathlib.Path): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = parse_cr(log_dir) XYs = { hostname: [(cr.response.iterations, cr.response.duration) for cr in result.challenge_responses] for (hostname, result) in results.items() } iterations = [ cr.response.iterations for (hostname, result) in results.items() for cr in result.challenge_responses ] durations = [ cr.response.duration for (hostname, result) in results.items() for cr in result.challenge_responses ] print_mean_ci("iterations", iterations) print_mean_ci("durations", durations) fig = plt.figure() ax = fig.gca() for (hostname, XY) in sorted(XYs.items(), key=lambda x: x[0]): X, Y = zip(*XY) ax.scatter(X, Y, label=hostname) ax.set_xlabel('Iterations') ax.set_ylabel('Time Taken (secs)') ax.legend() savefig(fig, log_dir / "graphs" / "cr_iterations_vs_timetaken.pdf") fig = plt.figure() ax = fig.gca() Xs = [] labels = [] for (hostname, XY) in sorted(XYs.items(), key=lambda x: x[0]): X, Y = zip(*XY) Xs.append(X) labels.append(hostname) ax.boxplot(Xs) ax.set_xticklabels(labels) ax.set_xlabel('Resource Rich Nodes') ax.set_ylabel('Iterations') savefig(fig, log_dir / "graphs" / "cr_iterations_boxplot.pdf")
def main(log_dir: pathlib.Path, tx_ymax: Optional[float], rx_ymax: Optional[float]): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = parse(log_dir, quiet=True) XYs_tx = { hostname: [(name, [ datetime.fromtimestamp(float(value.sniff_timestamp)) for value in values ], [packet_length(value) for value in values]) for (name, values) in result.tx.items()] for (hostname, result) in results.items() } XYs_rx = { hostname: [(name, [ datetime.fromtimestamp(float(value.sniff_timestamp)) for value in values ], [packet_length(value) for value in values]) for (name, values) in result.rx.items()] for (hostname, result) in results.items() } to_graph = { ("tx", tx_ymax): XYs_tx, ("rx", rx_ymax): XYs_rx, } bin_width = timedelta(minutes=6) min_time = min(r.min_snift_time for r in results.values()) max_time = min(r.max_snift_time for r in results.values()) min_time = datetime.fromtimestamp(min_time) max_time = datetime.fromtimestamp(max_time) bins = [min_time] while bins[-1] + bin_width < max_time: bins.append(bins[-1] + bin_width) bins.append(max_time) # Make the colors the same between rx and tx graphs kinds1 = { name for nvs in XYs_tx.values() for (name, times, lengths) in nvs } kinds2 = { name for nvs in XYs_rx.values() for (name, times, lengths) in nvs } kinds = kinds1 | kinds2 ckind = { kind: plt.cm.get_cmap('tab20')(i) for i, kind in enumerate(sorted(kinds)) } for ((name, ymax), XYs) in to_graph.items(): for (hostname, metric_values) in XYs.items(): fig = plt.figure() ax = fig.gca() labels, values, weights = zip( *sorted(metric_values, key=lambda x: x[0])) colors = [ckind[label] for label in labels] ax.hist(values, bins=bins, histtype='bar', stacked=True, label=labels, weights=weights, color=colors, rwidth=1) ax.set_xlabel('Time') ax.set_ylabel( f'Message Length (bytes) {"Sent" if name == "tx" else "Received"} During Window' ) ax.set_ylim(0, ymax) ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend(ncol=3, loc="center", fontsize="small", bbox_to_anchor=(0.5, 1.125)) savefig(fig, log_dir / "graphs" / f"{name}-by-type-{hostname}.pdf") # Table of the percentage of bytes in each category hostnames = sorted(set(XYs_tx.keys()) | set(XYs_rx.keys())) for hostname in hostnames: #print(hostname) log_file = log_dir / "graphs" / f"{hostname}-messages.tex" with open(log_file, "w") as f: print("\\begin{table}[t]", file=f) print("\\centering", file=f) print( "\\begin{tabular}{l S[table-format=6] S[table-format=3.1] S[table-format=6] S[table-format=3.1]}", file=f) print(" \\toprule", file=f) print( " ~ & \\multicolumn{2}{c}{Tx} & \\multicolumn{2}{c}{Rx} \\\\", file=f) print( " Category & {(\\si{\\byte})} & {(\\%)} & {(\\si{\\byte})} & {(\\%)} \\\\", file=f) print(" \\midrule", file=f) XY_tx = XYs_tx.get(hostname, []) XY_rx = XYs_rx.get(hostname, []) XY_tx = {name: sum(lengths) for (name, dates, lengths) in XY_tx} total_tx = sum(XY_tx.values()) XY_rx = {name: sum(lengths) for (name, dates, lengths) in XY_rx} total_rx = sum(XY_rx.values()) names = sorted(set(XY_tx.keys()) | set(XY_rx.keys())) for name in names: print( f"{name} & {XY_tx.get(name, 0)} & {round(100*XY_tx.get(name, 0)/total_tx,1)} & {XY_rx.get(name, 0)} & {round(100*XY_rx.get(name, 0)/total_rx,1)} \\\\", file=f) print("\\midrule", file=f) print(f"Total & {total_tx} & 100 & {total_rx} & 100 \\\\", file=f) print("\\bottomrule", file=f) print("\\end{tabular}", file=f) print(f"\\caption{{Message tx and rx for {hostname}}}", file=f) #print("\\label{tab:ram-flash-usage}", file=f) print("\\end{table}", file=f)
def main(log_dir: pathlib.Path, throw_on_error: bool = True, with_error_bars: bool = False): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = parse(log_dir, throw_on_error=throw_on_error) capabilities = { value.cr.capability for result in results.values() for value in result.throughput_updates } targets = { value.edge_id for result in results.values() for value in result.throughput_updates } CXYs = {(capability, direction): {(hostname, target): [(value.time, value.cr.throughput) for value in result.throughput_updates if value.edge_id == target if value.cr.capability == capability if value.cr.direction == direction] for (hostname, result) in results.items() for target in targets} for capability in capabilities for direction in ThroughputDirection} # TODO: Draw some bar graphs of which nodes tasks were submitted to for ((capability, direction), XYs) in CXYs.items(): fig = plt.figure() ax = fig.gca() for (label, XY) in sorted(XYs.items(), key=lambda x: x[0]): hostname, target = label if not XY: print(f"Skipping {label}") continue X, Y = zip(*XY) ax.plot( X, Y, label= f"{hostname_to_name(hostname)} eval {eui64_to_name(target)} dir {direction}" ) ax.set_xlabel('Time') ax.set_ylabel('Throughput (bytes/sec)') ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend(ncol=3, fontsize="small", loc="center", bbox_to_anchor=(0.5, 1.075)) savefig( fig, f"{log_dir}/graphs/throughput_vs_time_{capability}_{direction}.pdf" ) CXYs = {(capability, direction): {(hostname, target): [(value.time, value.tm_to.mean.mean, math.sqrt(value.tm_to.mean.var)) for value in result.throughput_updates if value.edge_id == target if value.cr.capability == capability if value.cr.direction == direction] for (hostname, result) in results.items() for target in targets} for capability in capabilities for direction in ThroughputDirection} # TODO: Draw some bar graphs of which nodes tasks were submitted to for ((capability, direction), XYs) in CXYs.items(): fig = plt.figure() ax = fig.gca() for (label, XY) in sorted(XYs.items(), key=lambda x: x[0]): hostname, target = label if not XY: print(f"Skipping {label}") continue X, Y, E = zip(*XY) if with_error_bars: ax.errorbar( X, Y, yerr=E, label= f"{hostname_to_name(hostname)} eval {eui64_to_name(target)} dir {direction}" ) else: ax.plot( X, Y, label= f"{hostname_to_name(hostname)} eval {eui64_to_name(target)} dir {direction}" ) ax.set_xlabel('Time') ax.set_ylabel('Throughput (bytes/sec)') ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend(ncol=3, fontsize="small", loc="center", bbox_to_anchor=(0.5, 1.075)) savefig( fig, f"{log_dir}/graphs/ave_throughput_vs_time_{capability}_{direction}.pdf" ) CXYs = {(capability, direction): {(hostname, target): [(value.time, value.tm_to.ewma.mean, math.sqrt(value.tm_to.ewma.var)) for value in result.throughput_updates if value.edge_id == target if value.cr.capability == capability if value.cr.direction == direction] for (hostname, result) in results.items() for target in targets} for capability in capabilities for direction in ThroughputDirection} # TODO: Draw some bar graphs of which nodes tasks were submitted to for ((capability, direction), XYs) in CXYs.items(): fig = plt.figure() ax = fig.gca() for (label, XY) in sorted(XYs.items(), key=lambda x: x[0]): hostname, target = label if not XY: print(f"Skipping {label}") continue X, Y, E = zip(*XY) if with_error_bars: ax.errorbar( X, Y, yerr=E, label= f"{hostname_to_name(hostname)} eval {eui64_to_name(target)} dir {direction}" ) else: ax.plot( X, Y, label= f"{hostname_to_name(hostname)} eval {eui64_to_name(target)} dir {direction}" ) ax.set_xlabel('Time') ax.set_ylabel('Throughput (bytes/sec)') ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend(ncol=3, fontsize="small", loc="center", bbox_to_anchor=(0.5, 1.075)) savefig( fig, f"{log_dir}/graphs/ewma_throughput_vs_time_{capability}_{direction}.pdf" )
def main(log_dir: pathlib.Path): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = parse_cr(log_dir) edge_labels = { up.edge_id for result in results.values() for up in result.tm_updates } # Show how the epoch changes over time XYs = {(hostname, eui64_to_name(edge_label)): [(up.time, up.tm_to.epoch) for up in result.tm_updates if up.edge_id == edge_label] for (hostname, result) in results.items() for edge_label in edge_labels} fig = plt.figure() ax = fig.gca() for (hostname, XY) in sorted(XYs.items(), key=lambda x: x[0]): X, Y = zip(*XY) ax.step(X, Y, label=f"{hostname[0]} evaluating {hostname[1]}") ax.set_xlabel('Time') ax.set_ylabel('Epoch Number') ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend() savefig(fig, log_dir / "graphs" / "cr_time_vs_epoch.pdf") # Show when the edge nodes were thought to be good or not event_types = {(hostname, eui64_to_name(edge_label)): [(up.time, not up.tm_to.bad) for up in result.tm_updates if up.edge_id == edge_label] for (hostname, result) in results.items() for edge_label in edge_labels} event_cause = {(hostname, eui64_to_name(edge_label)): [(up.time, up.cr.kind) for up in result.tm_updates if up.edge_id == edge_label if not up.cr.good if up.tm_to.bad] for (hostname, result) in results.items() for edge_label in edge_labels} #pprint(event_cause) fig = plt.figure() ax = fig.gca() y = 0 yticks = [] ytick_labels = [] cxs = defaultdict(list) cys = defaultdict(list) for (hostname, XY) in sorted(event_types.items(), key=lambda x: x[0]): true_list, false_list = squash_true_false_seq(XY) ax.broken_barh(true_list, (y, 0.9), color="lightgreen") ax.broken_barh(false_list, (y, 0.9), color="grey") # Record the causes of these changes causes = event_cause[hostname] for (ctime, cevent) in causes: cxs[cevent].append(ctime) cys[cevent].append(y + 0.45) yticks.append(y) ytick_labels.append(f"{hostname[0]}\neval {hostname[1]}") y += 1 for cevent in sorted(cxs): (shape, colour) = ChallengeResponseType_to_shape_and_color(cevent) ax.scatter(cxs[cevent], cys[cevent], label=latex_escape(cevent), c=colour, marker=shape) ax.set_yticks([x + 0.45 for x in yticks]) ax.set_yticklabels(ytick_labels) ax.set_xlabel('Time') ax.set_ylabel('Status') ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend() savefig(fig, log_dir / "graphs" / "cr_time_vs_good.pdf")
def main(log_dir: pathlib.Path): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = parse_cr(log_dir) pyterm_results = parse_pyterm(log_dir) print([r.behaviour_changes for r in results.values()]) if all(len(r.behaviour_changes) == 0 for r in results.values()): print("This graph type does not make sense for this result") return earliest = min(t for r in results.values() for (t, v) in r.behaviour_changes) latest = max(t for r in results.values() for (t, v) in r.behaviour_changes) # Find the latest time a task was submitted # Some times we might not have much behaviour changing to go on latest_task = max(t.time for r in pyterm_results.values() for t in r.tasks) latest = max(latest, latest_task) # Stacked bar graph showing how many tasks were offloaded to each node # over the time periods in which no changes occur # Create a graph showing when tasks where offloaded to nodes and that node was bad # Need to create some data ranges for well-behaved nodes, as they don't say when they are being bad event_types = { hostname_to_name(hostname): result.behaviour_changes + [(latest, result.behaviour_changes[-1][1])] if result.behaviour_changes else [(earliest, True), (latest, True)] for (hostname, result) in results.items() } # Calculate bins, need to include left edge of first bin and right edge of last bin bins = [t for (t, v) in event_types['rr6']] targets = { task.target for result in pyterm_results.values() for task in result.tasks } data = { target: [ task.time for result in pyterm_results.values() for task in result.tasks if task.target == target ] for target in targets } fig = plt.figure() ax = fig.gca() y = 0 yticks = [] ytick_labels = [] for (hostname, XY) in sorted(event_types.items(), key=lambda x: x[0]): true_list, false_list = squash_true_false_seq(XY) ax.broken_barh(true_list, (y, 0.9), color="lightgreen") ax.broken_barh(false_list, (y, 0.9), color="grey") yticks.append(y) ytick_labels.append(f"{hostname}") y += 1 ax2 = ax.twinx() hlabels, hdata = zip(*list(sorted(data.items(), key=lambda x: x[0]))) hlabels = [ip_to_name(l) for l in hlabels] ax2.hist(hdata, bins, stacked=True, histtype='bar', label=hlabels, rwidth=0.4) ax.set_yticks([x + 0.45 for x in yticks]) ax.set_yticklabels(ytick_labels) ax.set_xlabel('Time') ax.set_ylabel('Status') ax2.set_ylabel("Number of tasks submitted") ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax2.legend() savefig(fig, log_dir / "graphs" / "cr_offload_vs_behaviour.pdf")
def main(log_dir: pathlib.Path): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = parse_cr(log_dir) pyterm_results = parse_pyterm(log_dir) print([r.behaviour_changes for r in results.values()]) earliest = min(t for r in results.values() for (t, v) in r.behaviour_changes) latest = max(t for r in results.values() for (t, v) in r.behaviour_changes) # Find the latest time a task was submitted # Some times we might not have much behaviour changing to go on latest_task = max(t.time for r in pyterm_results.values() for t in r.tasks) latest = max(latest, latest_task) # Create a graph showing when tasks where offloaded to nodes and that node was bad # Need to create some data ranges for well-behaved nodes, as they don't say when they are being bad actual = { hostname_to_name(hostname): result.behaviour_changes + [(latest, result.behaviour_changes[-1][1])] if result.behaviour_changes else [(earliest, True), (latest, True)] for (hostname, result) in results.items() } edge_labels = {up.edge_id for result in pyterm_results.values() for up in result.tm_updates} belived = { (hostname, eui64_to_name(edge_label)): [ (up.time, not up.tm_to.bad) for up in result.tm_updates if up.edge_id == edge_label ] for (hostname, result) in pyterm_results.items() for edge_label in edge_labels } # Translate believed into whether the belief was correct or not correct = { (wsn, edge): belief_correct(results, actual[edge]) for ((wsn, edge), results) in belived.items() } targets = {task.target for result in pyterm_results.values() for task in result.tasks} data = { target: [ task.time for result in pyterm_results.values() for task in result.tasks if task.target == target ] for target in targets } fig = plt.figure() ax = fig.gca() y = 0 yticks = [] ytick_labels = [] legend = True x = plt.cm.get_cmap('tab10') # new tab10 """tp_colour = "#59a14f" tn_colour = "#4e79a7" fp_colour = "#b07aa1" fn_colour = "#9c755f" u_colour = "#bab0ac""" tp_colour = x(2) tn_colour = x(0) fp_colour = x(4) fn_colour = x(5) u_colour = x(7) summaries = {} for (hostname, XY) in sorted(correct.items(), key=lambda x: x[0]): result = squash_generic_seq(XY, ("TP", "TN", "FP", "FN", None)) summary = {k: sum(v[1].total_seconds() for v in vv) for (k, vv) in result.items() if k is not None} summary_total = sum(summary.values()) summary_pc = {k: round(v/summary_total, 2) for (k, v) in summary.items()} print(hostname, summary_pc) summaries[hostname] = f"\\ConfusionMatrix{{{summary_pc['TP']}}}{{{summary_pc['TN']}}}{{{summary_pc['FP']}}}{{{summary_pc['FN']}}}" ax.broken_barh(result["TP"], (y,0.9), color=tp_colour, label="TP" if legend else None) ax.broken_barh(result["TN"], (y,0.9), color=tn_colour, label="TN" if legend else None) ax.broken_barh(result["FP"], (y,0.9), color=fp_colour, label="FP" if legend else None) ax.broken_barh(result["FN"], (y,0.9), color=fn_colour, label="FN" if legend else None) #ax.broken_barh(result[None], (y,0.9), color=u_colour, label="U" if legend else None) yticks.append(y) ytick_labels.append(f"{hostname[0]}\\newline eval {hostname[1]}") y += 1 legend = False ax.set_yticks([x+0.45 for x in yticks]) ax.set_yticklabels(ytick_labels) ax.set_xlabel('Time') ax.set_ylabel('Status') ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend() savefig(fig, log_dir / "graphs" / "cr_correctly_evaluated.pdf") print("\\begin{table}[H]") wsns = list(sorted({k[0] for k in summaries.keys()})) rrs = list(sorted({k[1] for k in summaries.keys()})) print("\\centering") print("\\begin{tabular}{l c c c}") print(" & ".join(['~'] + wsns) + "\\\\") for rr in rrs: print(rr) for wsn in wsns: summary = summaries[(wsn, rr)] print("&", summary) print("\\\\") print("\\end{tabular}") print("\\end{table}")
def main(log_dir: pathlib.Path, ax2_ymax: float, throw_on_error: bool = True): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = parse(log_dir, throw_on_error=throw_on_error) capabilities = { value.capability for result in results.values() for value in result.trust_choose.values } targets = { value.target for result in results.values() for value in result.trust_choose.values } CXYs = { capability: {(hostname, target): [(value.time, value.value) for value in result.trust_choose.values if value.target == target if value.capability == capability] for (hostname, result) in results.items() for target in targets} for capability in capabilities } CHs = { capability: {(f"{hostname} eval {eui64_to_name(target)}"): [ task.time for task in result.tasks if isinstance(task.details, capability_to_task[capability]) if ip_to_name(task.target) == eui64_to_name(target) ] for (hostname, result) in results.items() for target in targets} for capability in capabilities } # TODO: Draw some bar graphs of which nodes tasks were submitted to for (capability, XYs) in CXYs.items(): fig = plt.figure() ax = fig.gca() Hs = CHs[capability] print(capability) pprint(Hs) labels, hs = zip(*list(sorted(Hs.items(), key=lambda x: x[0]))) ax2 = ax.twinx() ax2.hist(hs, bins=None, histtype='bar', label=labels, rwidth=0.6) for (label, XY) in sorted(XYs.items(), key=lambda x: x[0]): hostname, target = label X, Y = zip(*XY) ax.plot(X, Y, label=f"{hostname} eval {eui64_to_name(target)}") ax.set_xlabel('Time') ax.set_ylabel('Trust Value (lines)') ax2.set_ylabel('Number of tasks submitted (bars)') ax.set_ylim(0, 1) ax2.set_ylim(0, ax2_ymax) ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend(ncol=3, fontsize="small", loc="center", bbox_to_anchor=(0.5, 1.075)) savefig( fig, f"{log_dir}/graphs/banded_trust_value_vs_time_{capability}.pdf")
def main(log_dir: pathlib.Path, throw_on_error: bool = True): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = parse(log_dir, throw_on_error=throw_on_error) targets = { value.edge_id for result in results.values() for value in result.tm_updates if isinstance(value.cr, LastPing) } XYs = {(hostname, target): [(value.time, value.tm_to.ping - value.tm_from.ping) for value in result.tm_updates if isinstance(value.cr, LastPing) if value.edge_id == target] for (hostname, result) in results.items() for target in targets} fig = plt.figure() ax = fig.gca() for (label, XY) in sorted(XYs.items(), key=lambda x: x[0]): hostname, target = label if not XY: print(f"Skipping {label}") continue X, Y = zip(*XY) ax.plot( X, Y, label=f"{hostname_to_name(hostname)} eval {eui64_to_name(target)}") ax.set_xlabel('Time') ax.set_ylabel('Time between pings (ms)') ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend(ncol=3, fontsize="small", loc="center", bbox_to_anchor=(0.5, 1.075)) savefig(fig, f"{log_dir}/graphs/time_between_pings.pdf") fig = plt.figure() ax = fig.gca() Xs = [] labels = [] for (label, XY) in sorted(XYs.items(), key=lambda x: x[0]): hostname, target = label if not XY: print(f"Skipping {label}") continue _, Y = zip(*XY) Xs.append(Y) labels.append( f"{hostname_to_name(hostname)} eval {eui64_to_name(target)}") ax.hist(Xs, stacked=True) ax.set_xlabel('Time between pings (ms)') ax.set_ylabel('Count') savefig(fig, f"{log_dir}/graphs/time_between_pings_hist.pdf") fig = plt.figure() ax = fig.gca() Xs = [] labels = [] for (label, XY) in sorted(XYs.items(), key=lambda x: x[0]): hostname, target = label if not XY: print(f"Skipping {label}") continue _, Y = zip(*XY) Xs.append(Y) labels.append( f"{hostname_to_name(hostname)} eval {eui64_to_name(target)}") ax.boxplot(Xs, labels=labels) ax.set_xlabel('Target') ax.set_ylabel('Time between pings (ms)') ax.set_xticklabels(labels, rotation=45) savefig(fig, f"{log_dir}/graphs/time_between_pings_boxplot.pdf")
def main(log_dir: pathlib.Path): (log_dir / "graphs").mkdir(parents=True, exist_ok=True) results = profile_pyterm(log_dir) XYs = { hostname: [(cr.length, cr.seconds) for cr in result.stats_sha256] for (hostname, result) in results.items() } fig = plt.figure() ax = fig.gca() for (hostname, XY) in sorted(XYs.items(), key=lambda x: x[0]): X, Y = zip(*XY) ax.scatter(X, Y, label=hostname) ax.set_xlabel('Message Length') ax.set_ylabel('Time Taken (secs)') ax.legend() savefig(fig, log_dir / "graphs" / "crypto_perf_sha256_scatter.pdf") fig = plt.figure() ax = fig.gca() Ys = [] labels = [] for (hostname, result) in sorted(XYs.items(), key=lambda x: x[0]): X, Y = zip(*XY) Ys.append(Y) labels.append(hostname) ax.boxplot(Ys) ax.set_xticklabels(labels) ax.set_xlabel('Resource Rich Nodes') ax.set_ylabel('Time Taken (secs)') savefig(fig, log_dir / "graphs" / "crypto_perf_sha256_box.pdf") def round_down(x: float, a: float) -> float: return math.floor(x / a) * a def round_up(x: float, a: float) -> float: return math.ceil(x / a) * a names = { "stats_sha256_u": 1e-5, "stats_sha256_n": 1e-7, "stats_ecdh": 1e-3, "stats_sign": 1e-3, "stats_verify": 1e-3, "stats_encrypt_u": 1e-3, "stats_encrypt_n": 1e-3, "stats_decrypt_u": 1e-3, "stats_decrypt_n": 1e-3, } for (name, bin_width) in names.items(): fig = plt.figure() ax = fig.gca() labels = [] hs = [] hmin, hmax = float("+inf"), float("-inf") for (hostname, result) in sorted(results.items(), key=lambda x: x[0]): labels.append(hostname) h = getattr(result, name) hs.append(h) hmin = min(hmin, min(h)) hmax = max(hmax, max(h)) hmin = round_down(hmin, bin_width) hmax = round_up(hmax, bin_width) bins = np.arange(hmin, hmax, bin_width) ax.hist(hs, bins=bins, stacked=True, label=labels) if name == "stats_sha256_n": ax.set_xlim(0, 2e-6) ax.legend() ax.set_xlabel('Time Taken (secs)') ax.set_ylabel('Count') savefig(fig, log_dir / "graphs" / f"crypto_perf_{name}_hist.pdf")