plot_streams.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #!/usr/bin/env python3
  2. #
  3. import gzip
  4. import pickle
  5. import matplotlib.pylab as plt
  6. import numpy as np
  7. #
  8. def plot_cells(stream, plot_widget, time_offset=None, clickable=False, label=None, color=None):
  9. num_bytes = np.cumsum(stream['length'])
  10. timestamps = np.asarray(stream['timestamp'])
  11. #
  12. if time_offset is not None:
  13. timestamps = timestamps-time_offset
  14. #
  15. return ax.step(timestamps, num_bytes/(1024**2), where='post', label=label, color=color, picker=(5 if clickable else None))[0]
  16. #
  17. def onresize(event):
  18. # matplotlib axes size only scales based on fractions (even with tight_layout), so we manually calculate a fixed padding size
  19. w = event.width/event.canvas.figure.dpi
  20. h = event.height/event.canvas.figure.dpi
  21. w_padding = 0.8 # in inches
  22. h_padding = 0.6 # in inches
  23. #
  24. for ax in event.canvas.figure.axes:
  25. for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] + ax.get_xticklabels() + ax.get_yticklabels()):
  26. item.set_fontsize(11+0.5*(w*h/(5**2)))
  27. #
  28. #
  29. event.canvas.figure.subplots_adjust(left=w_padding/w+0.01, right=1-((w_padding/2)/w), top=1-(h_padding/h), bottom=h_padding/h+0.01)
  30. #
  31. def onscroll(event):
  32. scale = 2**(-event.step)
  33. ax = event.inaxes
  34. mouse_x = event.xdata
  35. mouse_y = event.ydata
  36. old_xlim = ax.get_xlim()
  37. old_ylim = ax.get_ylim()
  38. #
  39. ax.set_xlim([old_xlim[0]*scale+mouse_x*(1-scale), old_xlim[1]*scale+mouse_x*(1-scale)])
  40. ax.set_ylim([old_ylim[0]*scale+mouse_y*(1-scale), old_ylim[1]*scale+mouse_y*(1-scale)])
  41. #
  42. event.canvas.draw()
  43. #
  44. def onpick(event, lines):
  45. this_line = event.artist
  46. if event.mouseevent.button == 1 and not event.mouseevent.dblclick and event.mouseevent.key is None:
  47. # if the mouse is single-clicked and no keyboard key is held down
  48. if this_line.get_visible() and this_line in lines.keys():
  49. this_info = [x[1] for x in lines.items() if x[0] == this_line][0]
  50. #
  51. for_legend = []
  52. for (line, info) in lines.items():
  53. if info['measureme_id'] != this_info['measureme_id']:
  54. line.set_visible(False)
  55. else:
  56. line.set_visible(True)
  57. for_legend.append(line)
  58. #
  59. #
  60. event.mouseevent.inaxes.legend(for_legend, [x.get_label() for x in for_legend], loc='upper right')
  61. event.canvas.draw_idle()
  62. #
  63. #
  64. #
  65. def onclick(event, lines):
  66. if event.button == 1 and event.dblclick and event.key is None:
  67. # if the mouse is double-clicked and no keyboard key is held down
  68. for (line, info) in lines.items():
  69. if info['is_main_plot']:
  70. line.set_visible(True)
  71. else:
  72. line.set_visible(False)
  73. #
  74. #
  75. event.inaxes.get_legend().remove()
  76. event.canvas.draw_idle()
  77. #
  78. #
  79. def onmotion(event, pan_settings):
  80. if event.inaxes is not None and event.key == 'control' and event.button == 1:
  81. ax = event.inaxes
  82. pixel_to_data = ax.transData.inverted()
  83. current_pos = pixel_to_data.transform_point((event.x, event.y))
  84. last_pos = pixel_to_data.transform_point((pan_settings['start_x'], pan_settings['start_y']))
  85. #
  86. old_xlim = ax.get_xlim()
  87. old_ylim = ax.get_ylim()
  88. #
  89. ax.set_xlim([old_xlim[0]+(last_pos[0]-current_pos[0]), old_xlim[1]+(last_pos[0]-current_pos[0])])
  90. ax.set_ylim([old_ylim[0]+(last_pos[1]-current_pos[1]), old_ylim[1]+(last_pos[1]-current_pos[1])])
  91. #
  92. event.canvas.draw_idle()
  93. #
  94. pan_settings['start_x'] = event.x
  95. pan_settings['start_y'] = event.y
  96. #
  97. def onkeypress(event, lines):
  98. if event.key == 'control':
  99. num_points = 0
  100. range_x = event.inaxes.xaxis.get_view_interval()
  101. range_y = event.inaxes.yaxis.get_view_interval()
  102. #
  103. for line in [l for l in lines if l.get_visible()]:
  104. data_x = line.get_xdata(orig=True)
  105. data_y = line.get_ydata(orig=True)
  106. #
  107. num_points += ((data_x>=range_x[0]) & (data_x<=range_x[1]) & (data_y>=range_y[0]) & (data_y<=range_y[1])).sum()
  108. # how many points are being rendered
  109. #
  110. for (line, info) in lines.items():
  111. data_x = line.get_xdata(orig=True)
  112. data_y = line.get_ydata(orig=True)
  113. line.orig_x = data_x
  114. line.orig_y = data_y
  115. line.orig_drawstyle = line.get_drawstyle()
  116. #
  117. subsample_spacing = max(1, int(num_points/10000))
  118. # the constant can be decreased to speed up plotting on slower computers
  119. #
  120. mask = np.ones(len(data_x))
  121. mask[::subsample_spacing] = 0
  122. line.set_xdata(data_x[mask==0])
  123. line.set_ydata(data_y[mask==0])
  124. line.set_drawstyle('default')
  125. #
  126. event.canvas.draw_idle()
  127. #
  128. #
  129. def onkeyrelease(event, lines):
  130. if event.key == 'control':
  131. for (line, info) in lines.items():
  132. line.set_xdata(line.orig_x)
  133. line.set_ydata(line.orig_y)
  134. line.set_drawstyle(line.orig_drawstyle)
  135. #
  136. event.canvas.draw_idle()
  137. #
  138. #
  139. def get_complimentary_color(color_index):
  140. return (color_index+1 if color_index%2==0 else color_index-1)
  141. #
  142. if __name__ == '__main__':
  143. with gzip.GzipFile('processed-data.pickle.gz', 'rb') as f:
  144. streams = pickle.load(f)
  145. #
  146. fig, ax = plt.subplots()#constrained_layout=True
  147. #
  148. start_time = min([hop[t]['timestamp'][0] for m in streams for s in streams[m] for d in streams[m][s]
  149. for hop in streams[m][s][d] for t in ('received','sent')])
  150. #
  151. lines = {}
  152. assigned_colors = []
  153. colormap = plt.get_cmap('tab20') #'tab10'
  154. direction_shortforms = {'forward':'fwd', 'backward':'bwd'}
  155. transmission_shortforms = {'received':'recv', 'sent':'sent'}
  156. #
  157. for measureme_id in streams:
  158. # for each circuit
  159. for stream_id in streams[measureme_id]:
  160. # for each stream
  161. direction = 'forward'
  162. for hop_index in range(len(streams[measureme_id][stream_id][direction])):
  163. # for each hop in the circuit (including the OP)
  164. for transmission in ('received', 'sent'):
  165. data = streams[measureme_id][stream_id][direction][hop_index][transmission]
  166. #
  167. if hop_index == len(streams[measureme_id][stream_id][direction])-1:
  168. guard_fingerprint = streams[measureme_id][stream_id][direction][1]['fingerprint']
  169. if guard_fingerprint not in assigned_colors:
  170. assigned_colors.append(guard_fingerprint)
  171. #
  172. if transmission == 'sent':
  173. color_index = assigned_colors.index(guard_fingerprint)
  174. else:
  175. color_index = get_complimentary_color(assigned_colors.index(guard_fingerprint))
  176. #
  177. else:
  178. color_index = hop_index*2 + ('received', 'sent').index(transmission)
  179. #
  180. is_main_plot = (hop_index == len(streams[measureme_id][stream_id][direction])-1 and transmission == 'sent')
  181. direction_label = direction_shortforms[direction]
  182. transmission_label = transmission_shortforms[transmission]
  183. label = 'hop={}, {:.4}, {:.4}, mid={}, sid={}'.format(hop_index, direction_label, transmission_label, measureme_id, stream_id)
  184. line = plot_cells(data, ax, time_offset=start_time, clickable=is_main_plot, label=label, color=colormap(color_index))
  185. if not is_main_plot:
  186. line.set_visible(False)
  187. #
  188. lines[line] = {'measureme_id':measureme_id, 'stream_id':stream_id, 'direction':direction,
  189. 'hop_index':hop_index, 'transmission':transmission, 'is_main_plot':is_main_plot}
  190. #
  191. #
  192. #
  193. #
  194. ax.set_xlabel('Time (s)')
  195. ax.set_ylabel('Data (MiB)')
  196. ax.set_title('Test')
  197. fig.tight_layout(pad=0)
  198. #ax.set_ylim(0, None)
  199. #
  200. fig.canvas.mpl_connect('resize_event', onresize)
  201. fig.canvas.mpl_connect('scroll_event', onscroll)
  202. fig.canvas.mpl_connect('pick_event', lambda event,lines=lines: onpick(event, lines))
  203. fig.canvas.mpl_connect('button_press_event', lambda event,lines=lines: onclick(event, lines))
  204. fig.canvas.mpl_connect('motion_notify_event', lambda event,pan_settings={'start_x':0,'start_y':0}: onmotion(event, pan_settings))
  205. fig.canvas.mpl_connect('key_press_event', lambda event,lines=lines: onkeypress(event, lines))
  206. fig.canvas.mpl_connect('key_release_event', lambda event,lines=lines: onkeyrelease(event, lines))
  207. #
  208. plt.show(fig)
  209. #