#!/usr/bin/env python3 import os import sys import json from math import sqrt import numpy as np import matplotlib.pyplot as plt from contextlib import contextmanager directory = os.path.expanduser('../dhtpir_simulation/library') sys.path.insert(1, directory) from dht_common import SIZE_OF_IP_ADDRESS, SIZE_OF_KEY, AVERAGE_RTT_ESTIMATE, AVERAGE_CLIENT_BANDWIDTH_ESTIMATE, AVERAGE_SERVER_BANDWIDTH_ESTIMATE, ENCRYPTION_SPEED_ESTIMATE, PIR_SPEED_ESTIMATE, SIZE_OF_CHUNK, Z_STAR NODE_TYPE_OPTIONS = { "DHTPIR_Quorum": { 1: { "legend": "RCP+QP+DHTPIR, 1 core", "marker": '^', "linestyle": (0, (3,1,3,1,1,1)), "color": "red" }, 2: { "legend": "RCP+QP+DHTPIR, 2 cores", "marker": '>', "linestyle": (0, (3,1,3,1,1,1)), "color": "orange" }, 4: { "legend": "RCP+QP+DHTPIR, 4 cores", "marker": 'v', "linestyle": (0, (3,1,3,1,1,1)), "color": "pink" }, }, "QPLastHop_Quorum": { 1: { "legend": "RCP+QP+LastHop", "marker": 'X', "linestyle": (0, (1,1,3,1,1,1)), "color": "blue" } }, "QP_Quorum": { 1: { "legend": "RCP+QP", "marker": "*", "linestyle": (0, (3,1,1,1)), "color": "green" } }, "RCP_Quorum": { 1: { "legend": "RCP", "marker": "h", "linestyle": (0, (1,1)), "color": "magenta" } }, "Base_Node": { 1: { "legend": "Base", "marker": '.', "linestyle": (0, (3,1)), "color": "purple" } } } MAX_CORES = 8 ## # This functionality allows us to temporarily change our working directory # # @input newdir - the new directory (relative to our current position) we want to be in @contextmanager def cd(newdir, makenew): prevdir = os.getcwd() directory = os.path.expanduser(newdir) if not os.path.exists(directory) and makenew: os.makedirs(directory) os.chdir(directory) try: yield finally: os.chdir(prevdir) def readData(dataDirectory): nodeData = {} clientData = {} realDirectory = os.path.expanduser(dataDirectory) for nodeType in os.listdir(realDirectory): if not nodeType.startswith('.') and not nodeType.endswith('.tar.gz'): nodeData[nodeType] = {} clientData[nodeType] = {} for numQuorums in os.listdir(os.path.join(realDirectory, nodeType)): if not numQuorums.startswith('.'): nodeData[nodeType][numQuorums] = {} clientData[nodeType][numQuorums] = {} for numNodes in os.listdir(os.path.join(realDirectory, nodeType, numQuorums)): if not numNodes.startswith('.'): nodeData[nodeType][numQuorums][numNodes] = {} clientData[nodeType][numQuorums][numNodes] = {} for numDocuments in os.listdir(os.path.join(realDirectory, nodeType, numQuorums, numNodes)): if not numDocuments.startswith('.'): nodeData[nodeType][numQuorums][numNodes][numDocuments] = {} nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_rounds'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_sent'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_recv'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_sent'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_recv'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_total'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_rounds'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_sent'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_recv'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_sent'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_recv'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_total'] = [] nodeData[nodeType][numQuorums][numNodes][numDocuments]['throughputs'] = {} [nodeData[nodeType][numQuorums][numNodes][numDocuments]['throughputs'].update({i: []}) for i in range(1, MAX_CORES + 1)] clientData[nodeType][numQuorums][numNodes][numDocuments] = {} clientData[nodeType][numQuorums][numNodes][numDocuments]['num_rounds'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_sent'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_recv'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_sent'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_recv'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_total'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_rounds'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_sent'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_recv'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_sent'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_recv'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_total'] = [] clientData[nodeType][numQuorums][numNodes][numDocuments]['latencies'] = {} [clientData[nodeType][numQuorums][numNodes][numDocuments]['latencies'].update({i: []}) for i in range(1, MAX_CORES + 1)] for seed in os.listdir(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments)): if not seed.startswith('.'): nodeData[nodeType][numQuorums][numNodes][numDocuments][seed] = {} nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['throughput_valid'] = False nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'] = {} nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_direct_accesses'] = {} nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['phf_generations'] = {} nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'] = {} nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'] = {} clientData[nodeType][numQuorums][numNodes][numDocuments][seed] = {} clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['latency_valid'] = False clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'] = {} clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_direct_accesses'] = {} clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['phf_generations'] = {} clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'] = {} clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'] = {} try: with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'all_node_calculations.out')), 'r') as nodeBonusFile: ft_range_access = False ft_direct_access = False phf_generation = False pir_retrieval = False database_ot_access = False for line in nodeBonusFile: if line.rstrip() == "FT Range Accesses": ft_range_access = True elif line.rstrip() == "FT Direct Accesses": ft_range_access = False ft_direct_access = True elif line.rstrip() == "PHF Generations": ft_direct_access = False phf_generation = True elif line.rstrip() == "PIR Retrievals": phf_generation = False pir_retrieval = True elif line.rstrip() == "Database OT Accesses": ft_direct_access = False database_ot_access = True else: k = float(line.rstrip().split(',')[0]) v = float(line.rstrip().split(',')[1]) if ft_range_access: nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'][k] = v elif ft_direct_access: nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_direct_accesses'][k] = v elif phf_generation: nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['phf_generations'][k] = v elif pir_retrieval: nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'][k] = v elif database_ot_access: nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'][k] = v nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['throughput_valid'] = True except FileNotFoundError as e: pass try: with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'avg_node.out')), 'r') as nodeFile: values = [float(x.split(',')[0]) for x in nodeFile.read().split('\n')[1:] if len(x) > 0] ## # For all values here, the data was stored as the advertised value averaged across all nodes # (of which there are numQuorums * numNodes, since numNodes == number of nodes per quorum) # What we actually want to measure, though, is the advertised value averaged across all queries, # because we want to know, when I make a query, how many bytes/rounds/whatever does it incur? # Hence, the weird multiplications/divisions going on here. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_rounds'].append(values[0] * int(numQuorums) * int(numNodes) / int(numDocuments)) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_sent'].append(values[1] * int(numQuorums) * int(numNodes) / int(numDocuments)) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_recv'].append(values[2] * int(numQuorums) * int(numNodes) / int(numDocuments)) total_bytes_sent = values[3] * int(numQuorums) * int(numNodes) total_bytes_recv = values[4] * int(numQuorums) * int(numNodes) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_sent'].append(total_bytes_sent / int(numDocuments)) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_recv'].append(total_bytes_recv / int(numDocuments)) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_total'].append((total_bytes_sent + total_bytes_recv) / int(numDocuments)) send_transmission_time = total_bytes_sent / AVERAGE_SERVER_BANDWIDTH_ESTIMATE recv_transmission_time = total_bytes_recv / AVERAGE_SERVER_BANDWIDTH_ESTIMATE ## # Although RTT matters to calculate real latency, # it doesn't inhibit the amount of data that any given node can push out at a time, # which is all we actually care about here. baseline_latency = send_transmission_time + recv_transmission_time extra_latency = 0 ## # the group operations for OT are basically null, # but the system has to encrypt the whole database every time # (QP finger tables, only present in QP, QP+LastHop, and DHTPIR) # (note that, the encryption has to happen when the ranges are requested, # not on the direct access, so we don't use the ft_direct_accesses value) size_of_one_ft_element = (SIZE_OF_IP_ADDRESS * int(numNodes) + SIZE_OF_KEY) for k, v in nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'].items(): extra_latency += (size_of_one_ft_element * k * v) / ENCRYPTION_SPEED_ESTIMATE ## # Now we're doing OT on the database, with the same caveat as before # (OT on the database only present in QP+LastHop) for k, v in nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'].items(): ## # Here, we divide by numNodes because for any OT access, only one node in the quorum actually performs it. # To get the amount of time any one node in a quorum spends calculating for a PIR retrieval, we have to factor that out extra_latency += (SIZE_OF_CHUNK * k * v) / ENCRYPTION_SPEED_ESTIMATE ## # For PIR, the server crypto time is relevant (and calculated here), # but the client crypto time is negligible (and ignored here) # (PIR stuff only present in DHTPIR) avg_db_size = int(numDocuments) / int(numQuorums) for k, v in nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'].items(): estimates = [abs(avg_db_size - k*i) for i in range(9)] blocking_factor = estimates.index(min(estimates)) extra_latency += (blocking_factor * SIZE_OF_CHUNK * k * v) / PIR_SPEED_ESTIMATE ## # For both the below values, we divide by numDocuments because # what we've calculated so far is in units: (s / numDocuments*lookups), not (s / lookups) # And we divide by numNodes because # what we've calculated counts each node together, # while it makes more sense to calculate the amount of time it takes a node to service any query # divided by the chance that that node participates in any given action # (remember that, for all OT stuff, in the simulation, only one node has to respond) # (also note that, for PIR, the way it was recorded counted numNodes copies of each individual PIR query, # because it just pulled data from nodes individually, and all participate in each PIR response) baseline_latency /= int(numDocuments) * int(numNodes) extra_latency /= int(numDocuments) * int(numNodes) # And then, those values are in s / lookup, not lookups / s, so invert them num_lookups_bound_by_network = baseline_latency ** -1 num_lookups_bound_by_cpu = {} if extra_latency == 0: [num_lookups_bound_by_cpu.update({i: num_lookups_bound_by_network}) for i in range(1, MAX_CORES + 1)] else: [num_lookups_bound_by_cpu.update({i: (extra_latency / i) ** -1}) for i in range(1, MAX_CORES + 1)] # if seed == "b" and (nodeType == "DHTPIR_Quorum" or nodeType == "QPLastHop_Quorum"): # print(nodeType + "/" + numQuorums + "/" + numNodes + "/" + numDocuments + "/" + seed) # print ("Network: " + str(num_lookups_bound_by_network)) # print ("CPU: " + str(num_lookups_bound_by_cpu)) if nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['throughput_valid'] or nodeType == "Base_Node" or nodeType == "RCP_Quorum": [nodeData[nodeType][numQuorums][numNodes][numDocuments]['throughputs'][i].append(min(num_lookups_bound_by_network, num_lookups_bound_by_cpu[i]) * int(numQuorums)) for i in range(1, MAX_CORES + 1)] except FileNotFoundError as e: pass try: with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'avg_node_pub.out')), 'r') as nodeFile: values = [float(x.split(',')[0]) for x in nodeFile.read().split('\n')[1:] if len(x) > 0] ## # For all values here, the data was stored as the advertised value averaged across all nodes # (of which there are numQuorums * numNodes, since numNodes == number of nodes per quorum) # What we actually want to measure, though, is the advertised value averaged across all queries, # because we want to know, when I make a query, how many bytes/rounds/whatever does it incur? # Hence, the weird multiplications/divisions going on here. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_rounds'].append(values[0] * int(numQuorums) * int(numNodes) / int(numDocuments)) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_sent'].append(values[1] * int(numQuorums) * int(numNodes) / int(numDocuments)) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_recv'].append(values[2] * int(numQuorums) * int(numNodes) / int(numDocuments)) total_bytes_sent = values[3] * int(numQuorums) * int(numNodes) total_bytes_recv = values[4] * int(numQuorums) * int(numNodes) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_sent'].append(total_bytes_sent / int(numDocuments)) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_recv'].append(total_bytes_recv / int(numDocuments)) nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_total'].append((total_bytes_sent + total_bytes_recv) / int(numDocuments)) except FileNotFoundError as e: pass try: with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'client_latency.out')), 'r') as clientBonusFile: ft_range_access = False ft_direct_access = False pir_retrieval = False for line in clientBonusFile: if line.rstrip() == "FT Range Accesses": ft_range_access = True elif line.rstrip() == "FT Direct Accesses": ft_range_access = False ft_direct_access = True elif line.rstrip() == "PIR Retrievals": ft_direct_access = False pir_retrieval = True elif line.rstrip() == "Database OT Accesses": ft_direct_access = False database_ot_access = True else: k = float(line.rstrip().split(',')[0]) v = float(line.rstrip().split(',')[1]) if ft_range_access: clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'][k] = v elif ft_direct_access: clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_direct_accesses'][k] = v elif pir_retrieval: clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'][k] = v elif database_ot_access: clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'][k] = v clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['latency_valid'] = True except FileNotFoundError as e: pass try: with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'client.out')), 'r') as clientFile: values = [float(x) for x in clientFile.read().split('\n')[0].split(',')] total_rounds_participated = values[0] total_bytes_sent = values[3] total_bytes_recv = values[4] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_rounds'].append(total_rounds_participated / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_sent'].append(values[1] / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_recv'].append(values[2] / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_sent'].append(total_bytes_sent / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_recv'].append(total_bytes_recv / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_total'].append((total_bytes_sent + total_bytes_recv) / int(numDocuments)) rtt_portion = AVERAGE_RTT_ESTIMATE * total_rounds_participated send_transmission_time = total_bytes_sent / AVERAGE_CLIENT_BANDWIDTH_ESTIMATE recv_transmission_time = total_bytes_recv / AVERAGE_CLIENT_BANDWIDTH_ESTIMATE baseline_latency = rtt_portion + send_transmission_time + recv_transmission_time extra_latency = 0 ## # the group operations for OT are basically null, # but the system has to encrypt the whole database every time # (QP finger tables, only present in QP, QP+LastHop, and DHTPIR) # (note that, the encryption has to happen when the ranges are requested, # not on the direct access, so we don't use the ft_direct_accesses value) size_of_one_ft_element = (SIZE_OF_IP_ADDRESS * int(numNodes) + SIZE_OF_KEY) for k, v in clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'].items(): extra_latency += (size_of_one_ft_element * k * v) / ENCRYPTION_SPEED_ESTIMATE ## # Now we're doing OT on the database, with the same caveat as before # (OT on the database only present in QP+LastHop) for k, v in clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'].items(): extra_latency += (SIZE_OF_CHUNK * k * v) / ENCRYPTION_SPEED_ESTIMATE ## # For PIR, the server crypto time is relevant (and calculated here), # but the client crypto time is negligible (and ignored here) # (PIR stuff only present in DHTPIR) avg_db_size = int(numDocuments) / int(numQuorums) for k, v in clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'].items(): estimates = [abs(avg_db_size - k*i) for i in range(9)] blocking_factor = estimates.index(min(estimates)) extra_latency += (blocking_factor * SIZE_OF_CHUNK * k * v) / PIR_SPEED_ESTIMATE ## # For both the below values, we divide by numDocuments because # what we've calculated so far is in units: (s / numDocuments*lookups), not (s / lookups) baseline_latency /= int(numDocuments) extra_latency /= int(numDocuments) cpu_latencies = {} [cpu_latencies.update({i: extra_latency / i}) for i in range(1, MAX_CORES + 1)] if clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['latency_valid'] or nodeType == "Base_Node" or nodeType == "RCP_Quorum": [clientData[nodeType][numQuorums][numNodes][numDocuments]['latencies'][i].append(baseline_latency + cpu_latencies[i]) for i in range(1, MAX_CORES + 1)] except FileNotFoundError as e: pass try: with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'client_pub.out')), 'r') as clientFile: values = [float(x) for x in clientFile.read().split('\n')[0].split(',')] total_rounds_participated = values[0] total_bytes_sent = values[3] total_bytes_recv = values[4] clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_rounds'].append(total_rounds_participated / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_sent'].append(values[1] / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_recv'].append(values[2] / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_sent'].append(total_bytes_sent / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_recv'].append(total_bytes_recv / int(numDocuments)) clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_total'].append((total_bytes_sent + total_bytes_recv) / int(numDocuments)) except FileNotFoundError as e: pass return nodeData, clientData def plotBasicComparison(data, numQuorums, numNodes, whichGraph, comparisonGuidelines, **kwargs): title = kwargs['title'] if 'title' in kwargs else '' xLabel = kwargs['xLabel'] if 'xLabel' in kwargs else '' yLabel = kwargs['yLabel'] if 'yLabel' in kwargs else '' fileNameStr = kwargs['fileNameStr'] if 'fileNameStr' in kwargs else f"basic-{whichGraph}-{numQuorums}-{numNodes}" legendLoc = kwargs['legendLoc'] if 'legendLoc' in kwargs else "best" legendBBoxAnchor = kwargs['legendBBoxAnchor'] if 'legendBBoxAnchor' in kwargs else (0, 1) nodeTypeSelection = kwargs['nodeTypeSelection'] if 'nodeTypeSelection' in kwargs else ['DHTPIR_Quorum', 'QPLastHop_Quorum', 'QP_Quorum', 'RCP_Quorum', 'Base_Node'] loglog = kwargs['loglog'] if 'loglog' in kwargs else True yLim = kwargs['yLim'] if 'yLim' in kwargs else False aspect = kwargs['aspect'] if 'aspect' in kwargs else None fig = plt.figure() ax = fig.gca() for selection in nodeTypeSelection: xs = [] xTickLabels = [] ys = [] yErrs = [] if isinstance(selection, list): nodeName = selection[0] cores = selection[1] else: nodeName = selection cores = 1 legend = NODE_TYPE_OPTIONS[nodeName][cores]['legend'] marker = NODE_TYPE_OPTIONS[nodeName][cores]['marker'] linestyle = NODE_TYPE_OPTIONS[nodeName][cores]['linestyle'] color = NODE_TYPE_OPTIONS[nodeName][cores]['color'] realNumQuorums = str(numQuorums) realNumNodes = str(numNodes) if nodeName == "Base_Node": realNumQuorums = str(numQuorums * numNodes) realNumNodes = "1" for x in comparisonGuidelines: if whichGraph == 'latencies' or whichGraph == 'throughputs': curr_data = data[nodeName][realNumQuorums][realNumNodes][f"{x*numQuorums}"][whichGraph][cores] else: curr_data = data[nodeName][realNumQuorums][realNumNodes][f"{x*numQuorums}"][whichGraph] mean = np.mean(curr_data) std = np.std(curr_data) sqrt_len = sqrt(len(curr_data)) xs.append(x) xTickLabels.append(f"{x}" if x < 1000 else f"{x/1000}k") ys.append(mean) yErrs.append(Z_STAR * std / sqrt_len) line, _, _ = ax.errorbar(xs, ys, yerr=yErrs, capsize=7.0, label=legend, marker=marker, linestyle=linestyle, color=color) ax.set_title(title, fontsize='x-large') ax.set_xlabel(xLabel, fontsize='large') ax.set_ylabel(yLabel, fontsize='large') ax.set_xscale("log") ax.set_xticks(comparisonGuidelines) ax.set_xticklabels(xTickLabels) if loglog: ax.set_yscale("log") else: bottom, top = ax.get_ylim() bottom = (0 if bottom > 0 else bottom) ax.set_ylim(bottom=bottom) if yLim: ax.set_ylim(bottom=yLim[0], top=yLim[1]) if aspect: ax.set_aspect(aspect, adjustable='box') legend = ax.legend(loc=legendLoc, bbox_to_anchor=legendBBoxAnchor, fontsize='large') fig.savefig(f"{fileNameStr}.pdf", bbox_inches='tight') plt.close(fig) def plotAdvancedComparison(data, whichGraph, comparisonGuidelines, **kwargs): title = kwargs['title'] if 'title' in kwargs else '' xLabel = kwargs['xLabel'] if 'xLabel' in kwargs else '' yLabel = kwargs['yLabel'] if 'yLabel' in kwargs else '' fileNameStr = kwargs['fileNameStr'] if 'fileNameStr' in kwargs else f"advanced-{whichGraph}" legendLoc = kwargs['legendLoc'] if 'legendLoc' in kwargs else "best" legendBBoxAnchor = kwargs['legendBBoxAnchor'] if 'legendBBoxAnchor' in kwargs else (0, 1) nodeTypeSelection = kwargs['nodeTypeSelection'] if 'nodeTypeSelection' in kwargs else ['DHTPIR_Quorum', 'QPLastHop_Quorum', 'QP_Quorum', 'RCP_Quorum', 'Base_Node'] loglog = kwargs['loglog'] if 'loglog' in kwargs else True yLim = kwargs['yLim'] if 'yLim' in kwargs else False fig = plt.figure() ax = fig.gca() xTicks = [] xTickLabels = [] for selection in nodeTypeSelection: xs = [] ys = [] yErrs = [] if isinstance(selection, list): nodeName = selection[0] cores = selection[1] else: nodeName = selection cores = 1 legend = NODE_TYPE_OPTIONS[nodeName][cores]['legend'] marker = NODE_TYPE_OPTIONS[nodeName][cores]['marker'] linestyle = NODE_TYPE_OPTIONS[nodeName][cores]['linestyle'] color = NODE_TYPE_OPTIONS[nodeName][cores]['color'] for comparison in comparisonGuidelines: numQuorums = comparison[0] numNodes = comparison[1] numDocuments = comparison[2] realNumQuorums = str(numQuorums) realNumNodes = str(numNodes) realNumDocuments = str(numDocuments) if nodeName == "Base_Node": realNumQuorums = str(int(numQuorums) * int(numNodes)) realNumNodes = "1" if len(xTicks) < len(comparisonGuidelines): xTicks.append(numQuorums) xTickLabels.append((f"{numQuorums}" if numQuorums < 1000 else f"{numQuorums/1000}k") + f",{str(numNodes)}") if whichGraph == 'latencies' or whichGraph == 'throughputs': curr_data = data[nodeName][realNumQuorums][realNumNodes][realNumDocuments][whichGraph][cores] else: curr_data = data[nodeName][realNumQuorums][realNumNodes][realNumDocuments][whichGraph] mean = np.mean(curr_data) std = np.std(curr_data) sqrt_len = sqrt(len(curr_data)) xs.append(numQuorums) ys.append(mean) yErrs.append(Z_STAR * std / sqrt_len) line, _, _ = ax.errorbar(xs, ys, yerr=yErrs, capsize=7.0, label=legend, marker=marker, linestyle=linestyle, color=color) ax.set_title(title, fontsize='large') ax.set_xlabel(xLabel, fontsize='medium') ax.set_ylabel(yLabel, fontsize='medium') ax.set_xscale("log") ax.set_xticks([], minor=True) ax.set_xticks(xTicks) ax.set_xticklabels(xTickLabels) bottom, top = ax.get_ylim() if loglog: ax.set_yscale("log") else: bottom = (0 if bottom > 0 else bottom) ax.set_ylim(bottom=bottom) if top > 100000: yTickLabels = ['{:}K'.format(int(int(x)/1000)) for x in ax.get_yticks().tolist()] ax.set_yticklabels(yTickLabels) if yLim: ax.set_ylim(bottom=yLim[0], top=yLim[1]) legend = ax.legend(loc=legendLoc, bbox_to_anchor=legendBBoxAnchor, fontsize='medium') with cd('../plots', True): fig.savefig(f"{fileNameStr}.pdf", bbox_extra_artists=(legend,), bbox_inches='tight') plt.close(fig) def main(dataDirectory, plotOptionsFile): nodeData, clientData = readData(dataDirectory) plotOptions = [] with open(plotOptionsFile, 'r') as options: plotOptions = json.load(options) for option in plotOptions: if option['type'].lower() == 'basic' or option['type'].lower() == 'b': try: data = nodeData if (option['data'].lower() == "node" or option['data'].lower() == "n") else clientData numQuorums = option['numQuorums'] numNodes = option['numNodes'] whichGraph = option['whichGraph'] comparisonGuidelines = option["comparisonGuidelines"] except KeyError as e: continue kwargs = {} if "title" in option: kwargs["title"] = option["title"] if "xLabel" in option: kwargs["xLabel"] = option["xLabel"] if "yLabel" in option: kwargs["yLabel"] = option["yLabel"] if "fileNameStr" in option: kwargs["fileNameStr"] = option["fileNameStr"] if "legendLoc" in option: kwargs["legendLoc"] = option["legendLoc"] if "legendBBoxAnchor" in option: anchor = (option["legendBBoxAnchor"][0], option["legendBBoxAnchor"][1]) kwargs["legendBBoxAnchor"] = anchor if "nodeTypeSelection" in option: kwargs["nodeTypeSelection"] = option["nodeTypeSelection"] if "loglog" in option: kwargs["loglog"] = option["loglog"] if "yLim" in option: kwargs["yLim"] = option["yLim"] if "aspect" in option: kwargs["aspect"] = option["aspect"] plotBasicComparison(data, numQuorums, numNodes, whichGraph, comparisonGuidelines, **kwargs) else: try: data = nodeData if (option['data'].lower() == "node" or option['data'].lower() == "n") else clientData whichGraph = option['whichGraph'] comparisonGuidelines = option["comparisonGuidelines"] except KeyError as e: continue kwargs = {} if "title" in option: kwargs["title"] = option["title"] if "xLabel" in option: kwargs["xLabel"] = option["xLabel"] if "yLabel" in option: kwargs["yLabel"] = option["yLabel"] if "fileNameStr" in option: kwargs["fileNameStr"] = option["fileNameStr"] if "legendLoc" in option: kwargs["legendLoc"] = option["legendLoc"] if "legendBBoxAnchor" in option: anchor = (option["legendBBoxAnchor"][0], option["legendBBoxAnchor"][1]) kwargs["legendBBoxAnchor"] = anchor if "nodeTypeSelection" in option: kwargs["nodeTypeSelection"] = option["nodeTypeSelection"] if "loglog" in option: kwargs["loglog"] = option["loglog"] if "yLim" in option: kwargs["yLim"] = option["yLim"] plotAdvancedComparison(data, whichGraph, comparisonGuidelines, **kwargs) if __name__ == "__main__": main("../outputs", "plots.json")