plot_traces 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #!/usr/bin/env python3
  2. # Plot the network traces output when TRACE_SOCKIO is set to 1
  3. # ("make TRACE_SOCKIO=1" to build in this configuration)
  4. # Args: log_directory
  5. # Outputs trace.tex (and the files pdflatex builds, including the target
  6. # trace.pdf) into the log directory
  7. import glob
  8. import math
  9. import os
  10. import re
  11. import sys
  12. if len(sys.argv) != 2:
  13. print(f"Usage: {sys.argv[0]} log_directory", file=sys.stderr)
  14. sys.exit(1)
  15. # This will throw an exception if the directory does not exist
  16. os.chdir(sys.argv[1])
  17. nodelogs = glob.glob("s*.log")
  18. nodelogs.sort(key=lambda filename : int(filename[1:-4]))
  19. # Pass 1: For each sender and receiver, make a list of each message
  20. # queued from that sender to receiver, noting its queue start time,
  21. # queue end time, size, and type. Also gather all timestamp labels for
  22. # each node
  23. queued_messages = {}
  24. labels = {}
  25. min_ts = None
  26. max_ts = None
  27. for nodelog in nodelogs:
  28. node = nodelog[:-4]
  29. labels[node] = []
  30. with open(nodelog) as logf:
  31. queueing = {}
  32. for logline in logf:
  33. logline = logline.rstrip()
  34. if matches := re.match(
  35. r'(\d+\.\d+): RTE queueing (\d+) bytes to (\S+)',
  36. logline):
  37. [ts, size, recv] = matches.groups()
  38. assert(recv not in queueing)
  39. tsf = float(ts)
  40. queueing[recv] = \
  41. { 'queue_start': tsf, 'size': size }
  42. if min_ts is None or min_ts > tsf:
  43. min_ts = tsf
  44. elif matches := re.match(
  45. r'(\d+\.\d+): RTE queued (\d+) bytes to (\S+)',
  46. logline):
  47. [ts, size, recv] = matches.groups()
  48. assert(recv in queueing)
  49. assert(queueing[recv]['size'] == size)
  50. if (node, recv) not in queued_messages:
  51. queued_messages[(node, recv)] = []
  52. msg = {
  53. 'queue_start': queueing[recv]['queue_start'],
  54. 'queue_end': float(ts),
  55. 'size': size,
  56. 'type': 'RTE',
  57. }
  58. queued_messages[(node, recv)].append(msg)
  59. del queueing[recv]
  60. elif matches := re.match(
  61. r'(\d+\.\d+): Epoch \d+ (start|complete)',
  62. logline):
  63. [ts, typ] = matches.groups()
  64. tsf = float(ts)
  65. if typ == 'start':
  66. typ = 'S'
  67. elif typ == 'complete':
  68. typ = 'F'
  69. labels[node].append((tsf, typ))
  70. if min_ts is None or min_ts > tsf:
  71. min_ts = tsf
  72. if max_ts is None or max_ts < tsf:
  73. max_ts = tsf
  74. elif matches := re.match(
  75. r'(\d+\.\d+): Round (.*) complete',
  76. logline):
  77. [ts, rnd] = matches.groups()
  78. tsf = float(ts)
  79. if rnd == '11':
  80. rnd = 'A'
  81. elif rnd == '12':
  82. rnd = 'B'
  83. elif rnd == '13':
  84. rnd = 'C'
  85. labels[node].append((tsf, rnd))
  86. if max_ts is None or max_ts < tsf:
  87. max_ts = tsf
  88. elif matches := re.match(
  89. r'(\d+\.\d+): (Begin|End) Waksman networks precompute',
  90. logline):
  91. [ts, typ] = matches.groups()
  92. tsf = float(ts)
  93. if typ == 'Begin':
  94. typ = 'P'
  95. elif typ == 'End':
  96. typ = 'W'
  97. labels[node].append((tsf, typ))
  98. if min_ts is None or min_ts > tsf:
  99. min_ts = tsf
  100. if max_ts is None or max_ts < tsf:
  101. max_ts = tsf
  102. elif matches := re.match(
  103. r'(\d+\.\d+): Begin ingestion',
  104. logline):
  105. [ts] = matches.groups()
  106. tsf = float(ts)
  107. labels[node].append((tsf, 'i'))
  108. if max_ts is None or max_ts < tsf:
  109. max_ts = tsf
  110. elif matches := re.match(
  111. r'(\d+\.\d+): End ingestion of \d+ messages',
  112. logline):
  113. [ts] = matches.groups()
  114. tsf = float(ts)
  115. labels[node].append((tsf, 'I'))
  116. if max_ts is None or max_ts < tsf:
  117. max_ts = tsf
  118. # Pass 2: For each sender and receiver, note the receive start time and
  119. # receive end time for each message in the queued_messages list
  120. messages = {}
  121. for nodelog in nodelogs:
  122. node = nodelog[:-4]
  123. with open(nodelog) as logf:
  124. receiving = {}
  125. for logline in logf:
  126. logline = logline.rstrip()
  127. if matches := re.match(
  128. r'(\d+\.\d+): RTE receiving (\d+) bytes from (\S+)',
  129. logline):
  130. [ts, size, snd] = matches.groups()
  131. assert(snd not in receiving)
  132. receiving[snd] = \
  133. { 'recv_start': float(ts), 'size': size }
  134. elif matches := re.match(
  135. r'(\d+\.\d+): RTE received (\d+) bytes from (\S+)',
  136. logline):
  137. [ts, size, snd] = matches.groups()
  138. assert(snd in receiving)
  139. assert(receiving[snd]['size'] == size)
  140. assert(queued_messages[(snd, node)][0]['size'] == size)
  141. tsf = float(ts)
  142. if (snd, node) not in messages:
  143. messages[(snd, node)] = []
  144. msg = queued_messages[(snd, node)].pop(0)
  145. msg['recv_start'] = receiving[snd]['recv_start']
  146. msg['recv_end'] = tsf
  147. if max_ts is None or max_ts < tsf:
  148. max_ts = tsf
  149. messages[(snd, node)].append(msg)
  150. del receiving[snd]
  151. # Write a latex file that draws the messages
  152. # ((max_ts-min_ts)*timescale) must be at most 570
  153. # Timescale (cm per second)
  154. timescale = min(570/(max_ts-min_ts), 2)
  155. # Nodescale (cm between nodes)
  156. nodescale = 1
  157. # Seconds between time label ticks
  158. time_tick = math.ceil(1/timescale)
  159. with open("trace.tex", "w") as tf:
  160. print(r'''\documentclass{article}
  161. \usepackage[paperwidth=%fcm,paperheight=%fcm,margin=1cm]{geometry}
  162. \usepackage{tikz}
  163. \usepackage{times}
  164. \setlength\parindent{0pt}
  165. \pagestyle{empty}
  166. \begin{document}
  167. \begin{tikzpicture}''' % (((max_ts-min_ts)*timescale)+2.5,
  168. (len(nodelogs)+1)*nodescale+2.5), file=tf)
  169. nodenum = 0
  170. nodepos = {}
  171. print(r'''\draw[thick] (0,0) -- ++(%lf,0);''' %
  172. ((max_ts-min_ts)*timescale), file=tf)
  173. ts=0
  174. while ts<(max_ts-min_ts):
  175. print(r'''\draw [thick] (%lf,.1) node [anchor=south] { %s } -- ++(0,-.1);''' %
  176. (ts*timescale, str(ts)), file=tf)
  177. ts+=time_tick
  178. for nodelog in nodelogs:
  179. node = nodelog[:-4]
  180. nodenum += 1
  181. nodepos[node] = -nodenum * nodescale
  182. print(r'''\node [anchor=east] at (0,%lf) { %s };
  183. \draw[thick] (0,%lf) -- ++(%lf,0);''' %
  184. (nodepos[node], node, nodepos[node],
  185. (max_ts-min_ts)*timescale), file=tf)
  186. for (snd,recv) in messages:
  187. for msg in messages[(snd,recv)]:
  188. print(r'''%% %s %s %s %lf %lf %lf %lf''' % (snd, recv,
  189. msg['size'], msg['queue_start'], msg['queue_end'],
  190. msg['recv_start'], msg['recv_end']), file=tf)
  191. print(r'''\fill [fill=%s,fill opacity=.2] (%lf,%lf) --
  192. (%lf,%lf) -- (%lf,%lf) -- (%lf,%lf) -- cycle;''' %
  193. ('black',
  194. (msg['queue_start']-min_ts)*timescale, nodepos[snd],
  195. (msg['queue_end']-min_ts)*timescale, nodepos[snd],
  196. (msg['recv_end']-min_ts)*timescale, nodepos[recv],
  197. (msg['recv_start']-min_ts)*timescale, nodepos[recv]),
  198. file=tf)
  199. for node in labels:
  200. for (ts,label) in labels[node]:
  201. print(r'''%% %lf''' % ts, file=tf)
  202. print(r'''\draw [thick] (%lf,%lf) node [anchor=south] { %s } -- ++(0,-.1);''' %
  203. ((ts-min_ts)*timescale, nodepos[node]+.1, label),
  204. file=tf)
  205. print(r'''\end{tikzpicture}
  206. \end{document}''', file=tf)
  207. os.system("pdflatex trace")