parallel_debug.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. #! /usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2007-2010 (ita)
  4. """
  5. Debugging helper for parallel compilation, outputs
  6. a file named pdebug.svg in the source directory::
  7. def options(opt):
  8. opt.load('parallel_debug')
  9. def build(bld):
  10. ...
  11. """
  12. import re, sys, threading, time, traceback
  13. try:
  14. from Queue import Queue
  15. except:
  16. from queue import Queue
  17. from waflib import Runner, Options, Task, Logs, Errors
  18. SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  19. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
  20. <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0"
  21. x="${project.x}" y="${project.y}" width="${project.width}" height="${project.height}" id="svg602" xml:space="preserve">
  22. <style type='text/css' media='screen'>
  23. g.over rect { stroke:#FF0000; fill-opacity:0.4 }
  24. </style>
  25. <script type='text/javascript'><![CDATA[
  26. var svg = document.getElementsByTagName('svg')[0];
  27. svg.addEventListener('mouseover', function(e) {
  28. var g = e.target.parentNode;
  29. var x = document.getElementById('r_' + g.id);
  30. if (x) {
  31. g.setAttribute('class', g.getAttribute('class') + ' over');
  32. x.setAttribute('class', x.getAttribute('class') + ' over');
  33. showInfo(e, g.id, e.target.attributes.tooltip.value);
  34. }
  35. }, false);
  36. svg.addEventListener('mouseout', function(e) {
  37. var g = e.target.parentNode;
  38. var x = document.getElementById('r_' + g.id);
  39. if (x) {
  40. g.setAttribute('class', g.getAttribute('class').replace(' over', ''));
  41. x.setAttribute('class', x.getAttribute('class').replace(' over', ''));
  42. hideInfo(e);
  43. }
  44. }, false);
  45. function showInfo(evt, txt, details) {
  46. ${if project.tooltip}
  47. tooltip = document.getElementById('tooltip');
  48. var t = document.getElementById('tooltiptext');
  49. t.firstChild.data = txt + " " + details;
  50. var x = evt.clientX + 9;
  51. if (x > 250) { x -= t.getComputedTextLength() + 16; }
  52. var y = evt.clientY + 20;
  53. tooltip.setAttribute("transform", "translate(" + x + "," + y + ")");
  54. tooltip.setAttributeNS(null, "visibility", "visible");
  55. var r = document.getElementById('tooltiprect');
  56. r.setAttribute('width', t.getComputedTextLength() + 6);
  57. ${endif}
  58. }
  59. function hideInfo(evt) {
  60. var tooltip = document.getElementById('tooltip');
  61. tooltip.setAttributeNS(null,"visibility","hidden");
  62. }
  63. ]]></script>
  64. <!-- inkscape requires a big rectangle or it will not export the pictures properly -->
  65. <rect
  66. x='${project.x}' y='${project.y}' width='${project.width}' height='${project.height}'
  67. style="font-size:10;fill:#ffffff;fill-opacity:0.01;fill-rule:evenodd;stroke:#ffffff;"></rect>
  68. ${if project.title}
  69. <text x="${project.title_x}" y="${project.title_y}"
  70. style="font-size:15px; text-anchor:middle; font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">${project.title}</text>
  71. ${endif}
  72. ${for cls in project.groups}
  73. <g id='${cls.classname}'>
  74. ${for rect in cls.rects}
  75. <rect x='${rect.x}' y='${rect.y}' width='${rect.width}' height='${rect.height}' tooltip='${rect.name}' style="font-size:10;fill:${rect.color};fill-rule:evenodd;stroke:#000000;stroke-width:0.4;" />
  76. ${endfor}
  77. </g>
  78. ${endfor}
  79. ${for info in project.infos}
  80. <g id='r_${info.classname}'>
  81. <rect x='${info.x}' y='${info.y}' width='${info.width}' height='${info.height}' style="font-size:10;fill:${info.color};fill-rule:evenodd;stroke:#000000;stroke-width:0.4;" />
  82. <text x="${info.text_x}" y="${info.text_y}"
  83. style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
  84. >${info.text}</text>
  85. </g>
  86. ${endfor}
  87. ${if project.tooltip}
  88. <g transform="translate(0,0)" visibility="hidden" id="tooltip">
  89. <rect id="tooltiprect" y="-15" x="-3" width="1" height="20" style="stroke:black;fill:#edefc2;stroke-width:1"/>
  90. <text id="tooltiptext" style="font-family:Arial; font-size:12;fill:black;"> </text>
  91. </g>
  92. ${endif}
  93. </svg>
  94. """
  95. COMPILE_TEMPLATE = '''def f(project):
  96. lst = []
  97. def xml_escape(value):
  98. return value.replace("&", "&amp;").replace('"', "&quot;").replace("'", "&apos;").replace("<", "&lt;").replace(">", "&gt;")
  99. %s
  100. return ''.join(lst)
  101. '''
  102. reg_act = re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<code>[^}]*?)\})", re.M)
  103. def compile_template(line):
  104. extr = []
  105. def repl(match):
  106. g = match.group
  107. if g('dollar'):
  108. return "$"
  109. elif g('backslash'):
  110. return "\\"
  111. elif g('subst'):
  112. extr.append(g('code'))
  113. return "<<|@|>>"
  114. return None
  115. line2 = reg_act.sub(repl, line)
  116. params = line2.split('<<|@|>>')
  117. assert(extr)
  118. indent = 0
  119. buf = []
  120. app = buf.append
  121. def app(txt):
  122. buf.append(indent * '\t' + txt)
  123. for x in range(len(extr)):
  124. if params[x]:
  125. app("lst.append(%r)" % params[x])
  126. f = extr[x]
  127. if f.startswith(('if', 'for')):
  128. app(f + ':')
  129. indent += 1
  130. elif f.startswith('py:'):
  131. app(f[3:])
  132. elif f.startswith(('endif', 'endfor')):
  133. indent -= 1
  134. elif f.startswith(('else', 'elif')):
  135. indent -= 1
  136. app(f + ':')
  137. indent += 1
  138. elif f.startswith('xml:'):
  139. app('lst.append(xml_escape(%s))' % f[4:])
  140. else:
  141. #app('lst.append((%s) or "cannot find %s")' % (f, f))
  142. app('lst.append(str(%s))' % f)
  143. if extr:
  144. if params[-1]:
  145. app("lst.append(%r)" % params[-1])
  146. fun = COMPILE_TEMPLATE % "\n\t".join(buf)
  147. # uncomment the following to debug the template
  148. #for i, x in enumerate(fun.splitlines()):
  149. # print i, x
  150. return Task.funex(fun)
  151. # red #ff4d4d
  152. # green #4da74d
  153. # lila #a751ff
  154. color2code = {
  155. 'GREEN' : '#4da74d',
  156. 'YELLOW' : '#fefe44',
  157. 'PINK' : '#a751ff',
  158. 'RED' : '#cc1d1d',
  159. 'BLUE' : '#6687bb',
  160. 'CYAN' : '#34e2e2',
  161. }
  162. mp = {}
  163. info = [] # list of (text,color)
  164. def map_to_color(name):
  165. if name in mp:
  166. return mp[name]
  167. try:
  168. cls = Task.classes[name]
  169. except KeyError:
  170. return color2code['RED']
  171. if cls.color in mp:
  172. return mp[cls.color]
  173. if cls.color in color2code:
  174. return color2code[cls.color]
  175. return color2code['RED']
  176. def process(self):
  177. m = self.generator.bld.producer
  178. try:
  179. # TODO another place for this?
  180. del self.generator.bld.task_sigs[self.uid()]
  181. except KeyError:
  182. pass
  183. self.generator.bld.producer.set_running(1, self)
  184. try:
  185. ret = self.run()
  186. except Exception:
  187. self.err_msg = traceback.format_exc()
  188. self.hasrun = Task.EXCEPTION
  189. # TODO cleanup
  190. m.error_handler(self)
  191. return
  192. if ret:
  193. self.err_code = ret
  194. self.hasrun = Task.CRASHED
  195. else:
  196. try:
  197. self.post_run()
  198. except Errors.WafError:
  199. pass
  200. except Exception:
  201. self.err_msg = traceback.format_exc()
  202. self.hasrun = Task.EXCEPTION
  203. else:
  204. self.hasrun = Task.SUCCESS
  205. if self.hasrun != Task.SUCCESS:
  206. m.error_handler(self)
  207. self.generator.bld.producer.set_running(-1, self)
  208. Task.Task.process_back = Task.Task.process
  209. Task.Task.process = process
  210. old_start = Runner.Parallel.start
  211. def do_start(self):
  212. try:
  213. Options.options.dband
  214. except AttributeError:
  215. self.bld.fatal('use def options(opt): opt.load("parallel_debug")!')
  216. self.taskinfo = Queue()
  217. old_start(self)
  218. if self.dirty:
  219. make_picture(self)
  220. Runner.Parallel.start = do_start
  221. lock_running = threading.Lock()
  222. def set_running(self, by, tsk):
  223. with lock_running:
  224. try:
  225. cache = self.lock_cache
  226. except AttributeError:
  227. cache = self.lock_cache = {}
  228. i = 0
  229. if by > 0:
  230. vals = cache.values()
  231. for i in range(self.numjobs):
  232. if i not in vals:
  233. cache[tsk] = i
  234. break
  235. else:
  236. i = cache[tsk]
  237. del cache[tsk]
  238. self.taskinfo.put( (i, id(tsk), time.time(), tsk.__class__.__name__, self.processed, self.count, by, ",".join(map(str, tsk.outputs))) )
  239. Runner.Parallel.set_running = set_running
  240. def name2class(name):
  241. return name.replace(' ', '_').replace('.', '_')
  242. def make_picture(producer):
  243. # first, cast the parameters
  244. if not hasattr(producer.bld, 'path'):
  245. return
  246. tmp = []
  247. try:
  248. while True:
  249. tup = producer.taskinfo.get(False)
  250. tmp.append(list(tup))
  251. except:
  252. pass
  253. try:
  254. ini = float(tmp[0][2])
  255. except:
  256. return
  257. if not info:
  258. seen = []
  259. for x in tmp:
  260. name = x[3]
  261. if not name in seen:
  262. seen.append(name)
  263. else:
  264. continue
  265. info.append((name, map_to_color(name)))
  266. info.sort(key=lambda x: x[0])
  267. thread_count = 0
  268. acc = []
  269. for x in tmp:
  270. thread_count += x[6]
  271. acc.append("%d %d %f %r %d %d %d %s" % (x[0], x[1], x[2] - ini, x[3], x[4], x[5], thread_count, x[7]))
  272. data_node = producer.bld.path.make_node('pdebug.dat')
  273. data_node.write('\n'.join(acc))
  274. tmp = [lst[:2] + [float(lst[2]) - ini] + lst[3:] for lst in tmp]
  275. st = {}
  276. for l in tmp:
  277. if not l[0] in st:
  278. st[l[0]] = len(st.keys())
  279. tmp = [ [st[lst[0]]] + lst[1:] for lst in tmp ]
  280. THREAD_AMOUNT = len(st.keys())
  281. st = {}
  282. for l in tmp:
  283. if not l[1] in st:
  284. st[l[1]] = len(st.keys())
  285. tmp = [ [lst[0]] + [st[lst[1]]] + lst[2:] for lst in tmp ]
  286. BAND = Options.options.dband
  287. seen = {}
  288. acc = []
  289. for x in range(len(tmp)):
  290. line = tmp[x]
  291. id = line[1]
  292. if id in seen:
  293. continue
  294. seen[id] = True
  295. begin = line[2]
  296. thread_id = line[0]
  297. for y in range(x + 1, len(tmp)):
  298. line = tmp[y]
  299. if line[1] == id:
  300. end = line[2]
  301. #print id, thread_id, begin, end
  302. #acc.append( ( 10*thread_id, 10*(thread_id+1), 10*begin, 10*end ) )
  303. acc.append( (BAND * begin, BAND*thread_id, BAND*end - BAND*begin, BAND, line[3], line[7]) )
  304. break
  305. if Options.options.dmaxtime < 0.1:
  306. gwidth = 1
  307. for x in tmp:
  308. m = BAND * x[2]
  309. if m > gwidth:
  310. gwidth = m
  311. else:
  312. gwidth = BAND * Options.options.dmaxtime
  313. ratio = float(Options.options.dwidth) / gwidth
  314. gwidth = Options.options.dwidth
  315. gheight = BAND * (THREAD_AMOUNT + len(info) + 1.5)
  316. # simple data model for our template
  317. class tobject(object):
  318. pass
  319. model = tobject()
  320. model.x = 0
  321. model.y = 0
  322. model.width = gwidth + 4
  323. model.height = gheight + 4
  324. model.tooltip = not Options.options.dnotooltip
  325. model.title = Options.options.dtitle
  326. model.title_x = gwidth / 2
  327. model.title_y = gheight + - 5
  328. groups = {}
  329. for (x, y, w, h, clsname, name) in acc:
  330. try:
  331. groups[clsname].append((x, y, w, h, name))
  332. except:
  333. groups[clsname] = [(x, y, w, h, name)]
  334. # groups of rectangles (else js highlighting is slow)
  335. model.groups = []
  336. for cls in groups:
  337. g = tobject()
  338. model.groups.append(g)
  339. g.classname = name2class(cls)
  340. g.rects = []
  341. for (x, y, w, h, name) in groups[cls]:
  342. r = tobject()
  343. g.rects.append(r)
  344. r.x = 2 + x * ratio
  345. r.y = 2 + y
  346. r.width = w * ratio
  347. r.height = h
  348. r.name = name
  349. r.color = map_to_color(cls)
  350. cnt = THREAD_AMOUNT
  351. # caption
  352. model.infos = []
  353. for (text, color) in info:
  354. inf = tobject()
  355. model.infos.append(inf)
  356. inf.classname = name2class(text)
  357. inf.x = 2 + BAND
  358. inf.y = 5 + (cnt + 0.5) * BAND
  359. inf.width = BAND/2
  360. inf.height = BAND/2
  361. inf.color = color
  362. inf.text = text
  363. inf.text_x = 2 + 2 * BAND
  364. inf.text_y = 5 + (cnt + 0.5) * BAND + 10
  365. cnt += 1
  366. # write the file...
  367. template1 = compile_template(SVG_TEMPLATE)
  368. txt = template1(model)
  369. node = producer.bld.path.make_node('pdebug.svg')
  370. node.write(txt)
  371. Logs.warn('Created the diagram %r', node)
  372. def options(opt):
  373. opt.add_option('--dtitle', action='store', default='Parallel build representation for %r' % ' '.join(sys.argv),
  374. help='title for the svg diagram', dest='dtitle')
  375. opt.add_option('--dwidth', action='store', type='int', help='diagram width', default=800, dest='dwidth')
  376. opt.add_option('--dtime', action='store', type='float', help='recording interval in seconds', default=0.009, dest='dtime')
  377. opt.add_option('--dband', action='store', type='int', help='band width', default=22, dest='dband')
  378. opt.add_option('--dmaxtime', action='store', type='float', help='maximum time, for drawing fair comparisons', default=0, dest='dmaxtime')
  379. opt.add_option('--dnotooltip', action='store_true', help='disable tooltips', default=False, dest='dnotooltip')