make_graphs.py 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. #!/usr/bin/env python3
  2. import os
  3. import sys
  4. import json
  5. from math import sqrt
  6. import numpy as np
  7. import matplotlib.pyplot as plt
  8. from contextlib import contextmanager
  9. directory = os.path.expanduser('../dhtpir_simulation/library')
  10. sys.path.insert(1, directory)
  11. 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
  12. NODE_TYPE_OPTIONS = {
  13. "DHTPIR_Quorum": {
  14. 1: {
  15. "legend": "RCP+QP+DHTPIR, 1 core",
  16. "marker": '^',
  17. "linestyle": (0, (3,1,3,1,1,1)),
  18. "color": "red"
  19. },
  20. 2: {
  21. "legend": "RCP+QP+DHTPIR, 2 cores",
  22. "marker": '>',
  23. "linestyle": (0, (3,1,3,1,1,1)),
  24. "color": "orange"
  25. },
  26. 4: {
  27. "legend": "RCP+QP+DHTPIR, 4 cores",
  28. "marker": 'v',
  29. "linestyle": (0, (3,1,3,1,1,1)),
  30. "color": "pink"
  31. },
  32. },
  33. "QPLastHop_Quorum": {
  34. 1: {
  35. "legend": "RCP+QP+LastHop",
  36. "marker": 'X',
  37. "linestyle": (0, (1,1,3,1,1,1)),
  38. "color": "blue"
  39. }
  40. },
  41. "QP_Quorum": {
  42. 1: {
  43. "legend": "RCP+QP",
  44. "marker": "*",
  45. "linestyle": (0, (3,1,1,1)),
  46. "color": "green"
  47. }
  48. },
  49. "RCP_Quorum": {
  50. 1: {
  51. "legend": "RCP",
  52. "marker": "h",
  53. "linestyle": (0, (1,1)),
  54. "color": "magenta"
  55. }
  56. },
  57. "Base_Node": {
  58. 1: {
  59. "legend": "Base",
  60. "marker": '.',
  61. "linestyle": (0, (3,1)),
  62. "color": "purple"
  63. }
  64. }
  65. }
  66. MAX_CORES = 8
  67. ##
  68. # This functionality allows us to temporarily change our working directory
  69. #
  70. # @input newdir - the new directory (relative to our current position) we want to be in
  71. @contextmanager
  72. def cd(newdir, makenew):
  73. prevdir = os.getcwd()
  74. directory = os.path.expanduser(newdir)
  75. if not os.path.exists(directory) and makenew:
  76. os.makedirs(directory)
  77. os.chdir(directory)
  78. try:
  79. yield
  80. finally:
  81. os.chdir(prevdir)
  82. def readData(dataDirectory):
  83. nodeData = {}
  84. clientData = {}
  85. realDirectory = os.path.expanduser(dataDirectory)
  86. for nodeType in os.listdir(realDirectory):
  87. if not nodeType.startswith('.') and not nodeType.endswith('.tar.gz'):
  88. nodeData[nodeType] = {}
  89. clientData[nodeType] = {}
  90. for numQuorums in os.listdir(os.path.join(realDirectory, nodeType)):
  91. if not numQuorums.startswith('.'):
  92. nodeData[nodeType][numQuorums] = {}
  93. clientData[nodeType][numQuorums] = {}
  94. for numNodes in os.listdir(os.path.join(realDirectory, nodeType, numQuorums)):
  95. if not numNodes.startswith('.'):
  96. nodeData[nodeType][numQuorums][numNodes] = {}
  97. clientData[nodeType][numQuorums][numNodes] = {}
  98. for numDocuments in os.listdir(os.path.join(realDirectory, nodeType, numQuorums, numNodes)):
  99. if not numDocuments.startswith('.'):
  100. nodeData[nodeType][numQuorums][numNodes][numDocuments] = {}
  101. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_rounds'] = []
  102. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_sent'] = []
  103. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_recv'] = []
  104. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_sent'] = []
  105. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_recv'] = []
  106. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_total'] = []
  107. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_rounds'] = []
  108. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_sent'] = []
  109. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_recv'] = []
  110. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_sent'] = []
  111. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_recv'] = []
  112. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_total'] = []
  113. nodeData[nodeType][numQuorums][numNodes][numDocuments]['throughputs'] = {}
  114. [nodeData[nodeType][numQuorums][numNodes][numDocuments]['throughputs'].update({i: []}) for i in range(1, MAX_CORES + 1)]
  115. clientData[nodeType][numQuorums][numNodes][numDocuments] = {}
  116. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_rounds'] = []
  117. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_sent'] = []
  118. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_recv'] = []
  119. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_sent'] = []
  120. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_recv'] = []
  121. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_total'] = []
  122. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_rounds'] = []
  123. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_sent'] = []
  124. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_recv'] = []
  125. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_sent'] = []
  126. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_recv'] = []
  127. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_total'] = []
  128. clientData[nodeType][numQuorums][numNodes][numDocuments]['latencies'] = {}
  129. [clientData[nodeType][numQuorums][numNodes][numDocuments]['latencies'].update({i: []}) for i in range(1, MAX_CORES + 1)]
  130. for seed in os.listdir(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments)):
  131. if not seed.startswith('.'):
  132. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed] = {}
  133. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['throughput_valid'] = False
  134. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'] = {}
  135. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_direct_accesses'] = {}
  136. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['phf_generations'] = {}
  137. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'] = {}
  138. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'] = {}
  139. clientData[nodeType][numQuorums][numNodes][numDocuments][seed] = {}
  140. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['latency_valid'] = False
  141. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'] = {}
  142. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_direct_accesses'] = {}
  143. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['phf_generations'] = {}
  144. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'] = {}
  145. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'] = {}
  146. try:
  147. with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'all_node_calculations.out')), 'r') as nodeBonusFile:
  148. ft_range_access = False
  149. ft_direct_access = False
  150. phf_generation = False
  151. pir_retrieval = False
  152. database_ot_access = False
  153. for line in nodeBonusFile:
  154. if line.rstrip() == "FT Range Accesses":
  155. ft_range_access = True
  156. elif line.rstrip() == "FT Direct Accesses":
  157. ft_range_access = False
  158. ft_direct_access = True
  159. elif line.rstrip() == "PHF Generations":
  160. ft_direct_access = False
  161. phf_generation = True
  162. elif line.rstrip() == "PIR Retrievals":
  163. phf_generation = False
  164. pir_retrieval = True
  165. elif line.rstrip() == "Database OT Accesses":
  166. ft_direct_access = False
  167. database_ot_access = True
  168. else:
  169. k = float(line.rstrip().split(',')[0])
  170. v = float(line.rstrip().split(',')[1])
  171. if ft_range_access:
  172. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'][k] = v
  173. elif ft_direct_access:
  174. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_direct_accesses'][k] = v
  175. elif phf_generation:
  176. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['phf_generations'][k] = v
  177. elif pir_retrieval:
  178. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'][k] = v
  179. elif database_ot_access:
  180. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'][k] = v
  181. nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['throughput_valid'] = True
  182. except FileNotFoundError as e:
  183. pass
  184. try:
  185. with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'avg_node.out')), 'r') as nodeFile:
  186. values = [float(x.split(',')[0]) for x in nodeFile.read().split('\n')[1:] if len(x) > 0]
  187. ##
  188. # For all values here, the data was stored as the advertised value averaged across all nodes
  189. # (of which there are numQuorums * numNodes, since numNodes == number of nodes per quorum)
  190. # What we actually want to measure, though, is the advertised value averaged across all queries,
  191. # because we want to know, when I make a query, how many bytes/rounds/whatever does it incur?
  192. # Hence, the weird multiplications/divisions going on here.
  193. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_rounds'].append(values[0] * int(numQuorums) * int(numNodes) / int(numDocuments))
  194. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_sent'].append(values[1] * int(numQuorums) * int(numNodes) / int(numDocuments))
  195. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_recv'].append(values[2] * int(numQuorums) * int(numNodes) / int(numDocuments))
  196. total_bytes_sent = values[3] * int(numQuorums) * int(numNodes)
  197. total_bytes_recv = values[4] * int(numQuorums) * int(numNodes)
  198. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_sent'].append(total_bytes_sent / int(numDocuments))
  199. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_recv'].append(total_bytes_recv / int(numDocuments))
  200. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_total'].append((total_bytes_sent + total_bytes_recv) / int(numDocuments))
  201. send_transmission_time = total_bytes_sent / AVERAGE_SERVER_BANDWIDTH_ESTIMATE
  202. recv_transmission_time = total_bytes_recv / AVERAGE_SERVER_BANDWIDTH_ESTIMATE
  203. ##
  204. # Although RTT matters to calculate real latency,
  205. # it doesn't inhibit the amount of data that any given node can push out at a time,
  206. # which is all we actually care about here.
  207. baseline_latency = send_transmission_time + recv_transmission_time
  208. extra_latency = 0
  209. ##
  210. # the group operations for OT are basically null,
  211. # but the system has to encrypt the whole database every time
  212. # (QP finger tables, only present in QP, QP+LastHop, and DHTPIR)
  213. # (note that, the encryption has to happen when the ranges are requested,
  214. # not on the direct access, so we don't use the ft_direct_accesses value)
  215. size_of_one_ft_element = (SIZE_OF_IP_ADDRESS * int(numNodes) + SIZE_OF_KEY)
  216. for k, v in nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'].items():
  217. extra_latency += (size_of_one_ft_element * k * v) / ENCRYPTION_SPEED_ESTIMATE
  218. ##
  219. # Now we're doing OT on the database, with the same caveat as before
  220. # (OT on the database only present in QP+LastHop)
  221. for k, v in nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'].items():
  222. ##
  223. # Here, we divide by numNodes because for any OT access, only one node in the quorum actually performs it.
  224. # To get the amount of time any one node in a quorum spends calculating for a PIR retrieval, we have to factor that out
  225. extra_latency += (SIZE_OF_CHUNK * k * v) / ENCRYPTION_SPEED_ESTIMATE
  226. ##
  227. # For PIR, the server crypto time is relevant (and calculated here),
  228. # but the client crypto time is negligible (and ignored here)
  229. # (PIR stuff only present in DHTPIR)
  230. avg_db_size = int(numDocuments) / int(numQuorums)
  231. for k, v in nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'].items():
  232. estimates = [abs(avg_db_size - k*i) for i in range(9)]
  233. blocking_factor = estimates.index(min(estimates))
  234. extra_latency += (blocking_factor * SIZE_OF_CHUNK * k * v) / PIR_SPEED_ESTIMATE
  235. ##
  236. # For both the below values, we divide by numDocuments because
  237. # what we've calculated so far is in units: (s / numDocuments*lookups), not (s / lookups)
  238. # And we divide by numNodes because
  239. # what we've calculated counts each node together,
  240. # while it makes more sense to calculate the amount of time it takes a node to service any query
  241. # divided by the chance that that node participates in any given action
  242. # (remember that, for all OT stuff, in the simulation, only one node has to respond)
  243. # (also note that, for PIR, the way it was recorded counted numNodes copies of each individual PIR query,
  244. # because it just pulled data from nodes individually, and all participate in each PIR response)
  245. baseline_latency /= int(numDocuments) * int(numNodes)
  246. extra_latency /= int(numDocuments) * int(numNodes)
  247. # And then, those values are in s / lookup, not lookups / s, so invert them
  248. num_lookups_bound_by_network = baseline_latency ** -1
  249. num_lookups_bound_by_cpu = {}
  250. if extra_latency == 0:
  251. [num_lookups_bound_by_cpu.update({i: num_lookups_bound_by_network}) for i in range(1, MAX_CORES + 1)]
  252. else:
  253. [num_lookups_bound_by_cpu.update({i: (extra_latency / i) ** -1}) for i in range(1, MAX_CORES + 1)]
  254. # if seed == "b" and (nodeType == "DHTPIR_Quorum" or nodeType == "QPLastHop_Quorum"):
  255. # print(nodeType + "/" + numQuorums + "/" + numNodes + "/" + numDocuments + "/" + seed)
  256. # print ("Network: " + str(num_lookups_bound_by_network))
  257. # print ("CPU: " + str(num_lookups_bound_by_cpu))
  258. if nodeData[nodeType][numQuorums][numNodes][numDocuments][seed]['throughput_valid'] or nodeType == "Base_Node" or nodeType == "RCP_Quorum":
  259. [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)]
  260. except FileNotFoundError as e:
  261. pass
  262. try:
  263. with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'avg_node_pub.out')), 'r') as nodeFile:
  264. values = [float(x.split(',')[0]) for x in nodeFile.read().split('\n')[1:] if len(x) > 0]
  265. ##
  266. # For all values here, the data was stored as the advertised value averaged across all nodes
  267. # (of which there are numQuorums * numNodes, since numNodes == number of nodes per quorum)
  268. # What we actually want to measure, though, is the advertised value averaged across all queries,
  269. # because we want to know, when I make a query, how many bytes/rounds/whatever does it incur?
  270. # Hence, the weird multiplications/divisions going on here.
  271. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_rounds'].append(values[0] * int(numQuorums) * int(numNodes) / int(numDocuments))
  272. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_sent'].append(values[1] * int(numQuorums) * int(numNodes) / int(numDocuments))
  273. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_recv'].append(values[2] * int(numQuorums) * int(numNodes) / int(numDocuments))
  274. total_bytes_sent = values[3] * int(numQuorums) * int(numNodes)
  275. total_bytes_recv = values[4] * int(numQuorums) * int(numNodes)
  276. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_sent'].append(total_bytes_sent / int(numDocuments))
  277. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_recv'].append(total_bytes_recv / int(numDocuments))
  278. nodeData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_total'].append((total_bytes_sent + total_bytes_recv) / int(numDocuments))
  279. except FileNotFoundError as e:
  280. pass
  281. try:
  282. with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'client_latency.out')), 'r') as clientBonusFile:
  283. ft_range_access = False
  284. ft_direct_access = False
  285. pir_retrieval = False
  286. for line in clientBonusFile:
  287. if line.rstrip() == "FT Range Accesses":
  288. ft_range_access = True
  289. elif line.rstrip() == "FT Direct Accesses":
  290. ft_range_access = False
  291. ft_direct_access = True
  292. elif line.rstrip() == "PIR Retrievals":
  293. ft_direct_access = False
  294. pir_retrieval = True
  295. elif line.rstrip() == "Database OT Accesses":
  296. ft_direct_access = False
  297. database_ot_access = True
  298. else:
  299. k = float(line.rstrip().split(',')[0])
  300. v = float(line.rstrip().split(',')[1])
  301. if ft_range_access:
  302. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'][k] = v
  303. elif ft_direct_access:
  304. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_direct_accesses'][k] = v
  305. elif pir_retrieval:
  306. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'][k] = v
  307. elif database_ot_access:
  308. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'][k] = v
  309. clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['latency_valid'] = True
  310. except FileNotFoundError as e:
  311. pass
  312. try:
  313. with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'client.out')), 'r') as clientFile:
  314. values = [float(x) for x in clientFile.read().split('\n')[0].split(',')]
  315. total_rounds_participated = values[0]
  316. total_bytes_sent = values[3]
  317. total_bytes_recv = values[4]
  318. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_rounds'].append(total_rounds_participated / int(numDocuments))
  319. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_sent'].append(values[1] / int(numDocuments))
  320. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_messages_recv'].append(values[2] / int(numDocuments))
  321. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_sent'].append(total_bytes_sent / int(numDocuments))
  322. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_recv'].append(total_bytes_recv / int(numDocuments))
  323. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_bytes_total'].append((total_bytes_sent + total_bytes_recv) / int(numDocuments))
  324. rtt_portion = AVERAGE_RTT_ESTIMATE * total_rounds_participated
  325. send_transmission_time = total_bytes_sent / AVERAGE_CLIENT_BANDWIDTH_ESTIMATE
  326. recv_transmission_time = total_bytes_recv / AVERAGE_CLIENT_BANDWIDTH_ESTIMATE
  327. baseline_latency = rtt_portion + send_transmission_time + recv_transmission_time
  328. extra_latency = 0
  329. ##
  330. # the group operations for OT are basically null,
  331. # but the system has to encrypt the whole database every time
  332. # (QP finger tables, only present in QP, QP+LastHop, and DHTPIR)
  333. # (note that, the encryption has to happen when the ranges are requested,
  334. # not on the direct access, so we don't use the ft_direct_accesses value)
  335. size_of_one_ft_element = (SIZE_OF_IP_ADDRESS * int(numNodes) + SIZE_OF_KEY)
  336. for k, v in clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['ft_range_accesses'].items():
  337. extra_latency += (size_of_one_ft_element * k * v) / ENCRYPTION_SPEED_ESTIMATE
  338. ##
  339. # Now we're doing OT on the database, with the same caveat as before
  340. # (OT on the database only present in QP+LastHop)
  341. for k, v in clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['database_ot_accesses'].items():
  342. extra_latency += (SIZE_OF_CHUNK * k * v) / ENCRYPTION_SPEED_ESTIMATE
  343. ##
  344. # For PIR, the server crypto time is relevant (and calculated here),
  345. # but the client crypto time is negligible (and ignored here)
  346. # (PIR stuff only present in DHTPIR)
  347. avg_db_size = int(numDocuments) / int(numQuorums)
  348. for k, v in clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['pir_retrievals'].items():
  349. estimates = [abs(avg_db_size - k*i) for i in range(9)]
  350. blocking_factor = estimates.index(min(estimates))
  351. extra_latency += (blocking_factor * SIZE_OF_CHUNK * k * v) / PIR_SPEED_ESTIMATE
  352. ##
  353. # For both the below values, we divide by numDocuments because
  354. # what we've calculated so far is in units: (s / numDocuments*lookups), not (s / lookups)
  355. baseline_latency /= int(numDocuments)
  356. extra_latency /= int(numDocuments)
  357. cpu_latencies = {}
  358. [cpu_latencies.update({i: extra_latency / i}) for i in range(1, MAX_CORES + 1)]
  359. if clientData[nodeType][numQuorums][numNodes][numDocuments][seed]['latency_valid'] or nodeType == "Base_Node" or nodeType == "RCP_Quorum":
  360. [clientData[nodeType][numQuorums][numNodes][numDocuments]['latencies'][i].append(baseline_latency + cpu_latencies[i]) for i in range(1, MAX_CORES + 1)]
  361. except FileNotFoundError as e:
  362. pass
  363. try:
  364. with open(os.path.expanduser(os.path.join(realDirectory, nodeType, numQuorums, numNodes, numDocuments, seed, 'client_pub.out')), 'r') as clientFile:
  365. values = [float(x) for x in clientFile.read().split('\n')[0].split(',')]
  366. total_rounds_participated = values[0]
  367. total_bytes_sent = values[3]
  368. total_bytes_recv = values[4]
  369. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_rounds'].append(total_rounds_participated / int(numDocuments))
  370. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_sent'].append(values[1] / int(numDocuments))
  371. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_messages_recv'].append(values[2] / int(numDocuments))
  372. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_sent'].append(total_bytes_sent / int(numDocuments))
  373. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_recv'].append(total_bytes_recv / int(numDocuments))
  374. clientData[nodeType][numQuorums][numNodes][numDocuments]['num_pub_bytes_total'].append((total_bytes_sent + total_bytes_recv) / int(numDocuments))
  375. except FileNotFoundError as e:
  376. pass
  377. return nodeData, clientData
  378. def plotBasicComparison(data, numQuorums, numNodes, whichGraph, comparisonGuidelines, **kwargs):
  379. title = kwargs['title'] if 'title' in kwargs else ''
  380. xLabel = kwargs['xLabel'] if 'xLabel' in kwargs else ''
  381. yLabel = kwargs['yLabel'] if 'yLabel' in kwargs else ''
  382. fileNameStr = kwargs['fileNameStr'] if 'fileNameStr' in kwargs else f"basic-{whichGraph}-{numQuorums}-{numNodes}"
  383. legendLoc = kwargs['legendLoc'] if 'legendLoc' in kwargs else "best"
  384. legendBBoxAnchor = kwargs['legendBBoxAnchor'] if 'legendBBoxAnchor' in kwargs else (0, 1)
  385. nodeTypeSelection = kwargs['nodeTypeSelection'] if 'nodeTypeSelection' in kwargs else ['DHTPIR_Quorum', 'QPLastHop_Quorum', 'QP_Quorum', 'RCP_Quorum', 'Base_Node']
  386. loglog = kwargs['loglog'] if 'loglog' in kwargs else True
  387. yLim = kwargs['yLim'] if 'yLim' in kwargs else False
  388. aspect = kwargs['aspect'] if 'aspect' in kwargs else None
  389. fig = plt.figure()
  390. ax = fig.gca()
  391. for selection in nodeTypeSelection:
  392. xs = []
  393. xTickLabels = []
  394. ys = []
  395. yErrs = []
  396. if isinstance(selection, list):
  397. nodeName = selection[0]
  398. cores = selection[1]
  399. else:
  400. nodeName = selection
  401. cores = 1
  402. legend = NODE_TYPE_OPTIONS[nodeName][cores]['legend']
  403. marker = NODE_TYPE_OPTIONS[nodeName][cores]['marker']
  404. linestyle = NODE_TYPE_OPTIONS[nodeName][cores]['linestyle']
  405. color = NODE_TYPE_OPTIONS[nodeName][cores]['color']
  406. realNumQuorums = str(numQuorums)
  407. realNumNodes = str(numNodes)
  408. if nodeName == "Base_Node":
  409. realNumQuorums = str(numQuorums * numNodes)
  410. realNumNodes = "1"
  411. for x in comparisonGuidelines:
  412. if whichGraph == 'latencies' or whichGraph == 'throughputs':
  413. curr_data = data[nodeName][realNumQuorums][realNumNodes][f"{x*numQuorums}"][whichGraph][cores]
  414. else:
  415. curr_data = data[nodeName][realNumQuorums][realNumNodes][f"{x*numQuorums}"][whichGraph]
  416. mean = np.mean(curr_data)
  417. std = np.std(curr_data)
  418. sqrt_len = sqrt(len(curr_data))
  419. xs.append(x)
  420. xTickLabels.append(f"{x}" if x < 1000 else f"{x/1000}k")
  421. ys.append(mean)
  422. yErrs.append(Z_STAR * std / sqrt_len)
  423. line, _, _ = ax.errorbar(xs, ys, yerr=yErrs, capsize=7.0, label=legend, marker=marker, linestyle=linestyle, color=color)
  424. ax.set_title(title, fontsize='x-large')
  425. ax.set_xlabel(xLabel, fontsize='large')
  426. ax.set_ylabel(yLabel, fontsize='large')
  427. ax.set_xscale("log")
  428. ax.set_xticks(comparisonGuidelines)
  429. ax.set_xticklabels(xTickLabels)
  430. if loglog:
  431. ax.set_yscale("log")
  432. else:
  433. bottom, top = ax.get_ylim()
  434. bottom = (0 if bottom > 0 else bottom)
  435. ax.set_ylim(bottom=bottom)
  436. if yLim:
  437. ax.set_ylim(bottom=yLim[0], top=yLim[1])
  438. if aspect:
  439. ax.set_aspect(aspect, adjustable='box')
  440. legend = ax.legend(loc=legendLoc, bbox_to_anchor=legendBBoxAnchor, fontsize='large')
  441. fig.savefig(f"{fileNameStr}.pdf", bbox_inches='tight')
  442. plt.close(fig)
  443. def plotAdvancedComparison(data, whichGraph, comparisonGuidelines, **kwargs):
  444. title = kwargs['title'] if 'title' in kwargs else ''
  445. xLabel = kwargs['xLabel'] if 'xLabel' in kwargs else ''
  446. yLabel = kwargs['yLabel'] if 'yLabel' in kwargs else ''
  447. fileNameStr = kwargs['fileNameStr'] if 'fileNameStr' in kwargs else f"advanced-{whichGraph}"
  448. legendLoc = kwargs['legendLoc'] if 'legendLoc' in kwargs else "best"
  449. legendBBoxAnchor = kwargs['legendBBoxAnchor'] if 'legendBBoxAnchor' in kwargs else (0, 1)
  450. nodeTypeSelection = kwargs['nodeTypeSelection'] if 'nodeTypeSelection' in kwargs else ['DHTPIR_Quorum', 'QPLastHop_Quorum', 'QP_Quorum', 'RCP_Quorum', 'Base_Node']
  451. loglog = kwargs['loglog'] if 'loglog' in kwargs else True
  452. yLim = kwargs['yLim'] if 'yLim' in kwargs else False
  453. fig = plt.figure()
  454. ax = fig.gca()
  455. xTicks = []
  456. xTickLabels = []
  457. for selection in nodeTypeSelection:
  458. xs = []
  459. ys = []
  460. yErrs = []
  461. if isinstance(selection, list):
  462. nodeName = selection[0]
  463. cores = selection[1]
  464. else:
  465. nodeName = selection
  466. cores = 1
  467. legend = NODE_TYPE_OPTIONS[nodeName][cores]['legend']
  468. marker = NODE_TYPE_OPTIONS[nodeName][cores]['marker']
  469. linestyle = NODE_TYPE_OPTIONS[nodeName][cores]['linestyle']
  470. color = NODE_TYPE_OPTIONS[nodeName][cores]['color']
  471. for comparison in comparisonGuidelines:
  472. numQuorums = comparison[0]
  473. numNodes = comparison[1]
  474. numDocuments = comparison[2]
  475. realNumQuorums = str(numQuorums)
  476. realNumNodes = str(numNodes)
  477. realNumDocuments = str(numDocuments)
  478. if nodeName == "Base_Node":
  479. realNumQuorums = str(int(numQuorums) * int(numNodes))
  480. realNumNodes = "1"
  481. if len(xTicks) < len(comparisonGuidelines):
  482. xTicks.append(numQuorums)
  483. xTickLabels.append((f"{numQuorums}" if numQuorums < 1000 else f"{numQuorums/1000}k") + f",{str(numNodes)}")
  484. if whichGraph == 'latencies' or whichGraph == 'throughputs':
  485. curr_data = data[nodeName][realNumQuorums][realNumNodes][realNumDocuments][whichGraph][cores]
  486. else:
  487. curr_data = data[nodeName][realNumQuorums][realNumNodes][realNumDocuments][whichGraph]
  488. mean = np.mean(curr_data)
  489. std = np.std(curr_data)
  490. sqrt_len = sqrt(len(curr_data))
  491. xs.append(numQuorums)
  492. ys.append(mean)
  493. yErrs.append(Z_STAR * std / sqrt_len)
  494. line, _, _ = ax.errorbar(xs, ys, yerr=yErrs, capsize=7.0, label=legend, marker=marker, linestyle=linestyle, color=color)
  495. ax.set_title(title, fontsize='large')
  496. ax.set_xlabel(xLabel, fontsize='medium')
  497. ax.set_ylabel(yLabel, fontsize='medium')
  498. ax.set_xscale("log")
  499. ax.set_xticks([], minor=True)
  500. ax.set_xticks(xTicks)
  501. ax.set_xticklabels(xTickLabels)
  502. bottom, top = ax.get_ylim()
  503. if loglog:
  504. ax.set_yscale("log")
  505. else:
  506. bottom = (0 if bottom > 0 else bottom)
  507. ax.set_ylim(bottom=bottom)
  508. if top > 100000:
  509. yTickLabels = ['{:}K'.format(int(int(x)/1000)) for x in ax.get_yticks().tolist()]
  510. ax.set_yticklabels(yTickLabels)
  511. if yLim:
  512. ax.set_ylim(bottom=yLim[0], top=yLim[1])
  513. legend = ax.legend(loc=legendLoc, bbox_to_anchor=legendBBoxAnchor, fontsize='medium')
  514. with cd('../plots', True):
  515. fig.savefig(f"{fileNameStr}.pdf", bbox_extra_artists=(legend,), bbox_inches='tight')
  516. plt.close(fig)
  517. def main(dataDirectory, plotOptionsFile):
  518. nodeData, clientData = readData(dataDirectory)
  519. plotOptions = []
  520. with open(plotOptionsFile, 'r') as options:
  521. plotOptions = json.load(options)
  522. for option in plotOptions:
  523. if option['type'].lower() == 'basic' or option['type'].lower() == 'b':
  524. try:
  525. data = nodeData if (option['data'].lower() == "node" or option['data'].lower() == "n") else clientData
  526. numQuorums = option['numQuorums']
  527. numNodes = option['numNodes']
  528. whichGraph = option['whichGraph']
  529. comparisonGuidelines = option["comparisonGuidelines"]
  530. except KeyError as e:
  531. continue
  532. kwargs = {}
  533. if "title" in option:
  534. kwargs["title"] = option["title"]
  535. if "xLabel" in option:
  536. kwargs["xLabel"] = option["xLabel"]
  537. if "yLabel" in option:
  538. kwargs["yLabel"] = option["yLabel"]
  539. if "fileNameStr" in option:
  540. kwargs["fileNameStr"] = option["fileNameStr"]
  541. if "legendLoc" in option:
  542. kwargs["legendLoc"] = option["legendLoc"]
  543. if "legendBBoxAnchor" in option:
  544. anchor = (option["legendBBoxAnchor"][0], option["legendBBoxAnchor"][1])
  545. kwargs["legendBBoxAnchor"] = anchor
  546. if "nodeTypeSelection" in option:
  547. kwargs["nodeTypeSelection"] = option["nodeTypeSelection"]
  548. if "loglog" in option:
  549. kwargs["loglog"] = option["loglog"]
  550. if "yLim" in option:
  551. kwargs["yLim"] = option["yLim"]
  552. if "aspect" in option:
  553. kwargs["aspect"] = option["aspect"]
  554. plotBasicComparison(data, numQuorums, numNodes, whichGraph, comparisonGuidelines, **kwargs)
  555. else:
  556. try:
  557. data = nodeData if (option['data'].lower() == "node" or option['data'].lower() == "n") else clientData
  558. whichGraph = option['whichGraph']
  559. comparisonGuidelines = option["comparisonGuidelines"]
  560. except KeyError as e:
  561. continue
  562. kwargs = {}
  563. if "title" in option:
  564. kwargs["title"] = option["title"]
  565. if "xLabel" in option:
  566. kwargs["xLabel"] = option["xLabel"]
  567. if "yLabel" in option:
  568. kwargs["yLabel"] = option["yLabel"]
  569. if "fileNameStr" in option:
  570. kwargs["fileNameStr"] = option["fileNameStr"]
  571. if "legendLoc" in option:
  572. kwargs["legendLoc"] = option["legendLoc"]
  573. if "legendBBoxAnchor" in option:
  574. anchor = (option["legendBBoxAnchor"][0], option["legendBBoxAnchor"][1])
  575. kwargs["legendBBoxAnchor"] = anchor
  576. if "nodeTypeSelection" in option:
  577. kwargs["nodeTypeSelection"] = option["nodeTypeSelection"]
  578. if "loglog" in option:
  579. kwargs["loglog"] = option["loglog"]
  580. if "yLim" in option:
  581. kwargs["yLim"] = option["yLim"]
  582. plotAdvancedComparison(data, whichGraph, comparisonGuidelines, **kwargs)
  583. if __name__ == "__main__":
  584. main("../outputs", "plots.json")