jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

443 lines
11KB

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