PageRenderTime 51ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/rpython/tool/logparser.py

https://bitbucket.org/kkris/pypy
Python | 437 lines | 423 code | 3 blank | 11 comment | 22 complexity | b87cdef9b9c235931be39ab95296c359 MD5 | raw file
  1. #! /usr/bin/env python
  2. """
  3. Syntax:
  4. python logparser.py <action> <logfilename> <output> <options...>
  5. Actions:
  6. draw-time draw a timeline image of the log (format PNG by default)
  7. print-summary print a summary of the log
  8. """
  9. import sys, re
  10. from rpython.rlib.debug import DebugLog
  11. from rpython.tool import progressbar
  12. def parse_log_file(filename, verbose=True):
  13. f = open(filename, 'r')
  14. if f.read(2) == 'BZ':
  15. f.close()
  16. import bz2
  17. f = bz2.BZ2File(filename, 'r')
  18. else:
  19. f.seek(0)
  20. lines = f.readlines()
  21. f.close()
  22. #
  23. return parse_log(lines, verbose=verbose)
  24. def parse_log(lines, verbose=False):
  25. color = "(?:\x1b.*?m)?"
  26. r_start = re.compile(color + r"\[([0-9a-fA-F]+)\] \{([\w-]+)" + color + "$")
  27. r_stop = re.compile(color + r"\[([0-9a-fA-F]+)\] ([\w-]+)\}" + color + "$")
  28. lasttime = 0
  29. log = DebugLog()
  30. time_decrase = False
  31. performance_log = True
  32. nested = 0
  33. #
  34. if verbose and sys.stdout.isatty():
  35. progress = progressbar.ProgressBar(color='green')
  36. counter = 0
  37. else:
  38. progress = None
  39. single_percent = len(lines) / 100
  40. if verbose:
  41. vnext = 0
  42. else:
  43. vnext = -1
  44. for i, line in enumerate(lines):
  45. if i == vnext:
  46. if progress is not None:
  47. progress.render(counter)
  48. counter += 1
  49. vnext += single_percent
  50. else:
  51. sys.stderr.write('%d%%..' % int(100.0*i/len(lines)))
  52. vnext += 500000
  53. line = line.rstrip()
  54. match = r_start.match(line)
  55. if match:
  56. record = log.debug_start
  57. nested += 1
  58. else:
  59. match = r_stop.match(line)
  60. if match:
  61. record = log.debug_stop
  62. nested -= 1
  63. else:
  64. log.debug_print(line)
  65. performance_log = performance_log and nested == 0
  66. continue
  67. time = int(int(match.group(1), 16))
  68. time_decrase = time_decrase or time < lasttime
  69. lasttime = time
  70. record(match.group(2), time=int(match.group(1), 16))
  71. if verbose:
  72. sys.stderr.write('loaded\n')
  73. if performance_log and time_decrase:
  74. print ("The time decreases! The log file may have been"
  75. " produced on a multi-CPU machine and the process"
  76. " moved between CPUs.")
  77. return log
  78. def extract_category(log, catprefix='', toplevel=False):
  79. got = []
  80. resulttext = []
  81. for entry in log:
  82. if entry[0] == 'debug_print':
  83. resulttext.append(entry[1])
  84. elif len(entry) == 4:
  85. got.extend(extract_category(
  86. entry[3], catprefix, toplevel=entry[0].startswith(catprefix)))
  87. else:
  88. resulttext.append('... LOG TRUCATED ...')
  89. if toplevel:
  90. resulttext.append('')
  91. got.insert(0, '\n'.join(resulttext))
  92. return got
  93. def print_log(log):
  94. for entry in log:
  95. if entry[0] == 'debug_print':
  96. print entry[1]
  97. else:
  98. print "{%s" % entry[0]
  99. if len(entry)>3:
  100. print_log(entry[3])
  101. print "%s}" % entry[0]
  102. def kill_category(log, catprefix=''):
  103. newlog = []
  104. for entry in log:
  105. if not entry[0].startswith(catprefix):
  106. if len(entry) > 3:
  107. newlog.append(entry[:3] +
  108. (kill_category(entry[3], catprefix),))
  109. else:
  110. newlog.append(entry)
  111. return newlog
  112. def getsubcategories(log):
  113. return [entry for entry in log if entry[0] != 'debug_print']
  114. def gettimebounds(log):
  115. # returns (mintime, maxtime)
  116. maincats = getsubcategories(log)
  117. return (maincats[0][1], maincats[-1][2])
  118. def gettotaltimes(log):
  119. # returns a dict {'label' or None: totaltime}
  120. def rectime(category1, timestart1, timestop1, subcats):
  121. substartstop = []
  122. for entry in getsubcategories(subcats):
  123. rectime(*entry)
  124. substartstop.append(entry[1:3]) # (start, stop)
  125. # compute the total time for category1 as the part of the
  126. # interval [timestart1, timestop1] that is not covered by
  127. # any interval from one of the subcats.
  128. mytime = 0
  129. substartstop.sort()
  130. for substart, substop in substartstop:
  131. if substart >= timestop1:
  132. break
  133. if substart > timestart1:
  134. mytime += substart - timestart1
  135. if timestart1 < substop:
  136. timestart1 = substop
  137. if timestart1 < timestop1:
  138. mytime += timestop1 - timestart1
  139. #
  140. try:
  141. result[category1] += mytime
  142. except KeyError:
  143. result[category1] = mytime
  144. #
  145. result = {}
  146. timestart0, timestop0 = gettimebounds(log)
  147. rectime(None, timestart0, timestop0, log)
  148. return result
  149. # ____________________________________________________________
  150. COLORS = {
  151. None: (248, 248, 248),
  152. '': (160, 160, 160),
  153. 'gc-': (224, 0, 0),
  154. 'gc-minor': (192, 0, 16),
  155. 'gc-collect': (255, 0, 0),
  156. 'jit-': (0, 224, 0),
  157. 'jit-running': (192, 255, 160),
  158. 'jit-tracing': (0, 255, 0),
  159. 'jit-optimize': (160, 255, 0),
  160. 'jit-backend': (0, 255, 144),
  161. 'jit-blackhole': (0, 160, 0),
  162. }
  163. SUMMARY = {
  164. None: 'normal execution',
  165. '': 'other',
  166. 'gc-': 'gc',
  167. 'jit-': 'jit',
  168. 'jit-running': 'jit-running',
  169. }
  170. def getcolor(category):
  171. while category not in COLORS:
  172. category = category[:-1]
  173. return COLORS[category]
  174. def getlightercolor((r, g, b)):
  175. return ((r*2+255)//3, (g*2+255)//3, (b*2+255)//3)
  176. def getdarkercolor((r, g, b)):
  177. return (r*2//3, g*2//3, b*2//3)
  178. def getlabel(text, _cache={}):
  179. try:
  180. return _cache[text]
  181. except KeyError:
  182. pass
  183. from PIL import Image, ImageDraw
  184. if None not in _cache:
  185. image = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
  186. draw = ImageDraw.Draw(image)
  187. _cache[None] = draw
  188. else:
  189. draw = _cache[None]
  190. sx, sy = draw.textsize(text)
  191. texthoriz = Image.new("RGBA", (sx, sy), (0, 0, 0, 0))
  192. ImageDraw.Draw(texthoriz).text((0, 0), text, fill=(0, 0, 0))
  193. textvert = texthoriz.rotate(90)
  194. _cache[text] = sx, sy, texthoriz, textvert
  195. return _cache[text]
  196. def bevelrect(draw, (x1, y1, x2, y2), color):
  197. if x2 <= x1:
  198. x2 = x1 + 1 # minimal width
  199. elif x2 >= x1 + 4:
  200. draw.line((x1, y1+1, x1, y2-1), fill=getlightercolor(color))
  201. x1 += 1
  202. x2 -= 1
  203. draw.line((x2, y1+1, x2, y2-1), fill=getdarkercolor(color))
  204. draw.line((x1, y1, x2-1, y1), fill=getlightercolor(color))
  205. y1 += 1
  206. y2 -= 1
  207. draw.line((x1, y2, x2-1, y2), fill=getdarkercolor(color))
  208. draw.rectangle((x1, y1, x2-1, y2-1), fill=color)
  209. # ----------
  210. def get_timeline_image(log, width, height):
  211. from PIL import Image, ImageDraw
  212. timestart0, timestop0 = gettimebounds(log)
  213. assert timestop0 > timestart0
  214. timefactor = float(width) / (timestop0 - timestart0)
  215. #
  216. def recdraw(sublist, subheight):
  217. firstx1 = None
  218. for category1, timestart1, timestop1, subcats in sublist:
  219. x1 = int((timestart1 - timestart0) * timefactor)
  220. x2 = int((timestop1 - timestart0) * timefactor)
  221. y1 = (height - subheight) / 2
  222. y2 = y1 + subheight
  223. y1 = int(y1)
  224. y2 = int(y2)
  225. color = getcolor(category1)
  226. if firstx1 is None:
  227. firstx1 = x1
  228. bevelrect(draw, (x1, y1, x2, y2), color)
  229. subcats = getsubcategories(subcats)
  230. if subcats:
  231. x2 = recdraw(subcats, subheight * 0.94) - 1
  232. sx, sy, texthoriz, textvert = getlabel(category1)
  233. if sx <= x2-x1-8:
  234. image.paste(texthoriz, (x1+5, y1+5), texthoriz)
  235. elif sy <= x2-x1-2:
  236. image.paste(textvert, (x1+1, y1+5), textvert)
  237. return firstx1
  238. #
  239. image = Image.new("RGBA", (width, height), (255, 255, 255, 0))
  240. draw = ImageDraw.Draw(image)
  241. recdraw(getsubcategories(log), height)
  242. return image
  243. # ----------
  244. def render_histogram(times, time0, labels, width, barheight):
  245. # Render a histogram showing horizontal time bars are given by the
  246. # 'times' dictionary. Each entry has the label specified by 'labels',
  247. # or by default the key used in 'times'.
  248. from PIL import Image, ImageDraw
  249. times = [(time, key) for (key, time) in times.items()]
  250. times.sort()
  251. times.reverse()
  252. images = []
  253. for time, key in times:
  254. fraction = float(time) / time0
  255. if fraction < 0.01:
  256. break
  257. color = getcolor(key)
  258. image = Image.new("RGBA", (width, barheight), (255, 255, 255, 0))
  259. draw = ImageDraw.Draw(image)
  260. x2 = int(fraction * width)
  261. bevelrect(draw, (0, 0, x2, barheight), color)
  262. # draw the labels "x%" and "key"
  263. percent = "%.1f%%" % (100.0 * fraction,)
  264. s1x, s1y, textpercent, vtextpercent = getlabel(percent)
  265. s2x, _, textlabel, _ = getlabel(labels.get(key, key))
  266. t1x = 5
  267. if t1x + s1x >= x2 - 3:
  268. if t1x + s1y < x2 - 3:
  269. textpercent = vtextpercent
  270. s1x = s1y
  271. else:
  272. t1x = x2 + 6
  273. t2x = t1x + s1x + 12
  274. if t2x + s2x >= x2 - 3:
  275. t2x = max(t2x, x2 + 8)
  276. image.paste(textpercent, (t1x, 5), textpercent)
  277. image.paste(textlabel, (t2x, 5), textlabel)
  278. images.append(image)
  279. if not images:
  280. return None
  281. return combine(images, spacing=0, border=1, horizontal=False)
  282. def get_timesummary_single_image(totaltimes, totaltime0, componentdict,
  283. width, barheight):
  284. # Compress the totaltimes dict so that its only entries left are
  285. # from componentdict. We do that by taking the times assigned to
  286. # subkeys in totaltimes and adding them to the superkeys specified
  287. # in componentdict.
  288. totaltimes = totaltimes.copy()
  289. for key, value in totaltimes.items():
  290. if key in componentdict:
  291. continue
  292. del totaltimes[key]
  293. if key is not None:
  294. while key not in componentdict:
  295. key = key[:-1]
  296. try:
  297. totaltimes[key] += value
  298. except KeyError:
  299. totaltimes[key] = value
  300. return render_histogram(totaltimes, totaltime0, componentdict,
  301. width, barheight)
  302. def get_timesummary_image(log, summarywidth, summarybarheight):
  303. timestart0, timestop0 = gettimebounds(log)
  304. totaltime0 = timestop0 - timestart0
  305. totaltimes = gettotaltimes(log)
  306. spacing = 50
  307. width = (summarywidth - spacing) // 2
  308. img1 = get_timesummary_single_image(totaltimes, totaltime0, SUMMARY,
  309. width, summarybarheight)
  310. if None in totaltimes:
  311. del totaltimes[None]
  312. img2 = render_histogram(totaltimes, totaltime0, {},
  313. width, summarybarheight)
  314. if img2 is None:
  315. return img1
  316. return combine([img1, img2], spacing=spacing, horizontal=True)
  317. # ----------
  318. def combine(imagelist, spacing=50, border=0, horizontal=False):
  319. if len(imagelist) <= 1 and not border:
  320. return imagelist[0]
  321. from PIL import Image, ImageDraw
  322. wlist = [image.size[0] for image in imagelist]
  323. hlist = [image.size[1] for image in imagelist]
  324. if horizontal:
  325. w = sum(wlist) + spacing*(len(imagelist)-1)
  326. h = max(hlist)
  327. else:
  328. w = max(wlist)
  329. h = sum(hlist) + spacing*(len(imagelist)-1)
  330. w += 2*border
  331. h += 2*border
  332. bigimage = Image.new("RGBA", (w, h), (255, 255, 255, 0))
  333. if border:
  334. draw = ImageDraw.Draw(bigimage)
  335. draw.rectangle((0, 0, w-1, border-1), fill=(0, 0, 0))
  336. draw.rectangle((0, h-border, w-1, h-1), fill=(0, 0, 0))
  337. draw.rectangle((0, 0, border-1, h-1), fill=(0, 0, 0))
  338. draw.rectangle((w-1, 0, w-border, h-1), fill=(0, 0, 0))
  339. x = border
  340. y = border
  341. for image in imagelist:
  342. bigimage.paste(image, (x, y))
  343. if horizontal:
  344. x += image.size[0] + spacing
  345. else:
  346. y += image.size[1] + spacing
  347. return bigimage
  348. def draw_timeline_image(log, output=None, mainwidth=3000, mainheight=150,
  349. summarywidth=850, summarybarheight=40):
  350. mainwidth = int(mainwidth)
  351. mainheight = int(mainheight)
  352. summarywidth = int(summarywidth)
  353. summarybarheight = int(summarybarheight)
  354. images = []
  355. if mainwidth > 0 and mainheight > 0:
  356. images.append(get_timeline_image(log, mainwidth, mainheight))
  357. if summarywidth > 0 and summarybarheight > 0:
  358. images.append(get_timesummary_image(log, summarywidth,
  359. summarybarheight))
  360. image = combine(images, horizontal=False)
  361. if output is None:
  362. image.save(sys.stdout, format='png')
  363. else:
  364. image.save(output)
  365. def print_summary(log, out):
  366. totaltimes = gettotaltimes(log)
  367. if out == '-':
  368. outfile = sys.stdout
  369. else:
  370. outfile = open(out, "w")
  371. l = totaltimes.items()
  372. l.sort(cmp=lambda a, b: cmp(b[1], a[1]))
  373. total = sum([b for a, b in l])
  374. for a, b in l:
  375. if a is None:
  376. a = 'interpret'
  377. s = " " * (50 - len(a))
  378. print >>outfile, a, s, str(b*100/total) + "%"
  379. if out != '-':
  380. outfile.close()
  381. # ____________________________________________________________
  382. ACTIONS = {
  383. 'draw-time': (draw_timeline_image, ['output=',
  384. 'mainwidth=', 'mainheight=',
  385. 'summarywidth=', 'summarybarheight=',
  386. ]),
  387. 'print-summary': (print_summary, []),
  388. }
  389. if __name__ == '__main__':
  390. import getopt
  391. if len(sys.argv) < 3:
  392. print __doc__
  393. sys.exit(2)
  394. action = sys.argv[1]
  395. func, longopts = ACTIONS[action]
  396. options, args = getopt.gnu_getopt(sys.argv[2:], '', longopts)
  397. if len(args) != 2:
  398. print __doc__
  399. sys.exit(2)
  400. kwds = {}
  401. for name, value in options:
  402. assert name.startswith('--')
  403. kwds[name[2:]] = value
  404. log = parse_log_file(args[0])
  405. func(log, args[1], **kwds)