table_formatter.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. #!/usr/bin/env python
  2. """ Prints data in a table format.
  3. This module serves as utility for other scripts.
  4. """
  5. from __future__ import print_function
  6. import sys, re, os.path, stat, math
  7. try:
  8. from html import escape
  9. except ImportError:
  10. from cgi import escape # Python 2.7
  11. from optparse import OptionParser
  12. from color import getColorizer, dummyColorizer
  13. class tblCell(object):
  14. def __init__(self, text, value = None, props = None):
  15. self.text = text
  16. self.value = value
  17. self.props = props
  18. class tblColumn(object):
  19. def __init__(self, caption, title = None, props = None):
  20. self.text = caption
  21. self.title = title
  22. self.props = props
  23. class tblRow(object):
  24. def __init__(self, colsNum, props = None):
  25. self.cells = [None] * colsNum
  26. self.props = props
  27. def htmlEncode(str):
  28. return '<br/>'.join([escape(s) for s in str])
  29. class table(object):
  30. def_align = "left"
  31. def_valign = "middle"
  32. def_color = None
  33. def_colspan = 1
  34. def_rowspan = 1
  35. def_bold = False
  36. def_italic = False
  37. def_text="-"
  38. def __init__(self, caption = None, format=None):
  39. self.format = format
  40. self.is_markdown = self.format == 'markdown'
  41. self.is_tabs = self.format == 'tabs'
  42. self.columns = {}
  43. self.rows = []
  44. self.ridx = -1;
  45. self.caption = caption
  46. pass
  47. def newRow(self, **properties):
  48. if len(self.rows) - 1 == self.ridx:
  49. self.rows.append(tblRow(len(self.columns), properties))
  50. else:
  51. self.rows[self.ridx + 1].props = properties
  52. self.ridx += 1
  53. return self.rows[self.ridx]
  54. def trimLastRow(self):
  55. if self.rows:
  56. self.rows.pop()
  57. if self.ridx >= len(self.rows):
  58. self.ridx = len(self.rows) - 1
  59. def newColumn(self, name, caption, title = None, **properties):
  60. if name in self.columns:
  61. index = self.columns[name].index
  62. else:
  63. index = len(self.columns)
  64. if isinstance(caption, tblColumn):
  65. caption.index = index
  66. self.columns[name] = caption
  67. return caption
  68. else:
  69. col = tblColumn(caption, title, properties)
  70. col.index = index
  71. self.columns[name] = col
  72. return col
  73. def getColumn(self, name):
  74. if isinstance(name, str):
  75. return self.columns.get(name, None)
  76. else:
  77. vals = [v for v in self.columns.values() if v.index == name]
  78. if vals:
  79. return vals[0]
  80. return None
  81. def newCell(self, col_name, text, value = None, **properties):
  82. if self.ridx < 0:
  83. self.newRow()
  84. col = self.getColumn(col_name)
  85. row = self.rows[self.ridx]
  86. if not col:
  87. return None
  88. if isinstance(text, tblCell):
  89. cl = text
  90. else:
  91. cl = tblCell(text, value, properties)
  92. row.cells[col.index] = cl
  93. return cl
  94. def layoutTable(self):
  95. columns = self.columns.values()
  96. columns = sorted(columns, key=lambda c: c.index)
  97. colspanned = []
  98. rowspanned = []
  99. self.headerHeight = 1
  100. rowsToAppend = 0
  101. for col in columns:
  102. self.measureCell(col)
  103. if col.height > self.headerHeight:
  104. self.headerHeight = col.height
  105. col.minwidth = col.width
  106. col.line = None
  107. for r in range(len(self.rows)):
  108. row = self.rows[r]
  109. row.minheight = 1
  110. for i in range(len(row.cells)):
  111. cell = row.cells[i]
  112. if row.cells[i] is None:
  113. continue
  114. cell.line = None
  115. self.measureCell(cell)
  116. colspan = int(self.getValue("colspan", cell))
  117. rowspan = int(self.getValue("rowspan", cell))
  118. if colspan > 1:
  119. colspanned.append((r,i))
  120. if i + colspan > len(columns):
  121. colspan = len(columns) - i
  122. cell.colspan = colspan
  123. #clear spanned cells
  124. for j in range(i+1, min(len(row.cells), i + colspan)):
  125. row.cells[j] = None
  126. elif columns[i].minwidth < cell.width:
  127. columns[i].minwidth = cell.width
  128. if rowspan > 1:
  129. rowspanned.append((r,i))
  130. rowsToAppend2 = r + colspan - len(self.rows)
  131. if rowsToAppend2 > rowsToAppend:
  132. rowsToAppend = rowsToAppend2
  133. cell.rowspan = rowspan
  134. #clear spanned cells
  135. for j in range(r+1, min(len(self.rows), r + rowspan)):
  136. if len(self.rows[j].cells) > i:
  137. self.rows[j].cells[i] = None
  138. elif row.minheight < cell.height:
  139. row.minheight = cell.height
  140. self.ridx = len(self.rows) - 1
  141. for r in range(rowsToAppend):
  142. self.newRow()
  143. self.rows[len(self.rows) - 1].minheight = 1
  144. while colspanned:
  145. colspanned_new = []
  146. for r, c in colspanned:
  147. cell = self.rows[r].cells[c]
  148. sum([col.minwidth for col in columns[c:c + cell.colspan]])
  149. cell.awailable = sum([col.minwidth for col in columns[c:c + cell.colspan]]) + cell.colspan - 1
  150. if cell.awailable < cell.width:
  151. colspanned_new.append((r,c))
  152. colspanned = colspanned_new
  153. if colspanned:
  154. r,c = colspanned[0]
  155. cell = self.rows[r].cells[c]
  156. cols = columns[c:c + cell.colspan]
  157. total = cell.awailable - cell.colspan + 1
  158. budget = cell.width - cell.awailable
  159. spent = 0
  160. s = 0
  161. for col in cols:
  162. s += col.minwidth
  163. addition = s * budget / total - spent
  164. spent += addition
  165. col.minwidth += addition
  166. while rowspanned:
  167. rowspanned_new = []
  168. for r, c in rowspanned:
  169. cell = self.rows[r].cells[c]
  170. cell.awailable = sum([row.minheight for row in self.rows[r:r + cell.rowspan]])
  171. if cell.awailable < cell.height:
  172. rowspanned_new.append((r,c))
  173. rowspanned = rowspanned_new
  174. if rowspanned:
  175. r,c = rowspanned[0]
  176. cell = self.rows[r].cells[c]
  177. rows = self.rows[r:r + cell.rowspan]
  178. total = cell.awailable
  179. budget = cell.height - cell.awailable
  180. spent = 0
  181. s = 0
  182. for row in rows:
  183. s += row.minheight
  184. addition = s * budget / total - spent
  185. spent += addition
  186. row.minheight += addition
  187. return columns
  188. def measureCell(self, cell):
  189. text = self.getValue("text", cell)
  190. cell.text = self.reformatTextValue(text)
  191. cell.height = len(cell.text)
  192. cell.width = len(max(cell.text, key = lambda line: len(line)))
  193. def reformatTextValue(self, value):
  194. if sys.version_info >= (2,7):
  195. unicode = str
  196. if isinstance(value, str):
  197. vstr = value
  198. elif isinstance(value, unicode):
  199. vstr = str(value)
  200. else:
  201. try:
  202. vstr = '\n'.join([str(v) for v in value])
  203. except TypeError:
  204. vstr = str(value)
  205. return vstr.splitlines()
  206. def adjustColWidth(self, cols, width):
  207. total = sum([c.minWidth for c in cols])
  208. if total + len(cols) - 1 >= width:
  209. return
  210. budget = width - len(cols) + 1 - total
  211. spent = 0
  212. s = 0
  213. for col in cols:
  214. s += col.minWidth
  215. addition = s * budget / total - spent
  216. spent += addition
  217. col.minWidth += addition
  218. def getValue(self, name, *elements):
  219. for el in elements:
  220. try:
  221. return getattr(el, name)
  222. except AttributeError:
  223. pass
  224. try:
  225. val = el.props[name]
  226. if val:
  227. return val
  228. except AttributeError:
  229. pass
  230. except KeyError:
  231. pass
  232. try:
  233. return getattr(self.__class__, "def_" + name)
  234. except AttributeError:
  235. return None
  236. def consolePrintTable(self, out):
  237. columns = self.layoutTable()
  238. colrizer = getColorizer(out) if not (self.is_markdown or self.is_tabs) else dummyColorizer(out)
  239. if self.caption:
  240. out.write("%s%s%s" % ( os.linesep, os.linesep.join(self.reformatTextValue(self.caption)), os.linesep * 2))
  241. headerRow = tblRow(len(columns), {"align": "center", "valign": "top", "bold": True, "header": True})
  242. headerRow.cells = columns
  243. headerRow.minheight = self.headerHeight
  244. self.consolePrintRow2(colrizer, headerRow, columns)
  245. for i in range(0, len(self.rows)):
  246. self.consolePrintRow2(colrizer, i, columns)
  247. def consolePrintRow2(self, out, r, columns):
  248. if isinstance(r, tblRow):
  249. row = r
  250. r = -1
  251. else:
  252. row = self.rows[r]
  253. #evaluate initial values for line numbers
  254. i = 0
  255. while i < len(row.cells):
  256. cell = row.cells[i]
  257. colspan = self.getValue("colspan", cell)
  258. if cell is not None:
  259. cell.wspace = sum([col.minwidth for col in columns[i:i + colspan]]) + colspan - 1
  260. if cell.line is None:
  261. if r < 0:
  262. rows = [row]
  263. else:
  264. rows = self.rows[r:r + self.getValue("rowspan", cell)]
  265. cell.line = self.evalLine(cell, rows, columns[i])
  266. if len(rows) > 1:
  267. for rw in rows:
  268. rw.cells[i] = cell
  269. i += colspan
  270. #print content
  271. if self.is_markdown:
  272. out.write("|")
  273. for c in row.cells:
  274. text = ' '.join(self.getValue('text', c) or [])
  275. out.write(text + "|")
  276. out.write(os.linesep)
  277. elif self.is_tabs:
  278. cols_to_join=[' '.join(self.getValue('text', c) or []) for c in row.cells]
  279. out.write('\t'.join(cols_to_join))
  280. out.write(os.linesep)
  281. else:
  282. for ln in range(row.minheight):
  283. i = 0
  284. while i < len(row.cells):
  285. if i > 0:
  286. out.write(" ")
  287. cell = row.cells[i]
  288. column = columns[i]
  289. if cell is None:
  290. out.write(" " * column.minwidth)
  291. i += 1
  292. else:
  293. self.consolePrintLine(cell, row, column, out)
  294. i += self.getValue("colspan", cell)
  295. if self.is_markdown:
  296. out.write("|")
  297. out.write(os.linesep)
  298. if self.is_markdown and row.props.get('header', False):
  299. out.write("|")
  300. for th in row.cells:
  301. align = self.getValue("align", th)
  302. if align == 'center':
  303. out.write(":-:|")
  304. elif align == 'right':
  305. out.write("--:|")
  306. else:
  307. out.write("---|")
  308. out.write(os.linesep)
  309. def consolePrintLine(self, cell, row, column, out):
  310. if cell.line < 0 or cell.line >= cell.height:
  311. line = ""
  312. else:
  313. line = cell.text[cell.line]
  314. width = cell.wspace
  315. align = self.getValue("align", ((None, cell)[isinstance(cell, tblCell)]), row, column)
  316. if align == "right":
  317. pattern = "%" + str(width) + "s"
  318. elif align == "center":
  319. pattern = "%" + str((width - len(line)) // 2 + len(line)) + "s" + " " * (width - len(line) - (width - len(line)) // 2)
  320. else:
  321. pattern = "%-" + str(width) + "s"
  322. out.write(pattern % line, color = self.getValue("color", cell, row, column))
  323. cell.line += 1
  324. def evalLine(self, cell, rows, column):
  325. height = cell.height
  326. valign = self.getValue("valign", cell, rows[0], column)
  327. space = sum([row.minheight for row in rows])
  328. if valign == "bottom":
  329. return height - space
  330. if valign == "middle":
  331. return (height - space + 1) // 2
  332. return 0
  333. def htmlPrintTable(self, out, embeedcss = False):
  334. columns = self.layoutTable()
  335. if embeedcss:
  336. out.write("<div style=\"font-family: Lucida Console, Courier New, Courier;font-size: 16px;color:#3e4758;\">\n<table style=\"background:none repeat scroll 0 0 #FFFFFF;border-collapse:collapse;font-family:'Lucida Sans Unicode','Lucida Grande',Sans-Serif;font-size:14px;margin:20px;text-align:left;width:480px;margin-left: auto;margin-right: auto;white-space:nowrap;\">\n")
  337. else:
  338. out.write("<div class=\"tableFormatter\">\n<table class=\"tbl\">\n")
  339. if self.caption:
  340. if embeedcss:
  341. out.write(" <caption style=\"font:italic 16px 'Trebuchet MS',Verdana,Arial,Helvetica,sans-serif;padding:0 0 5px;text-align:right;white-space:normal;\">%s</caption>\n" % htmlEncode(self.reformatTextValue(self.caption)))
  342. else:
  343. out.write(" <caption>%s</caption>\n" % htmlEncode(self.reformatTextValue(self.caption)))
  344. out.write(" <thead>\n")
  345. headerRow = tblRow(len(columns), {"align": "center", "valign": "top", "bold": True, "header": True})
  346. headerRow.cells = columns
  347. header_rows = [headerRow]
  348. header_rows.extend([row for row in self.rows if self.getValue("header")])
  349. last_row = header_rows[len(header_rows) - 1]
  350. for row in header_rows:
  351. out.write(" <tr>\n")
  352. for th in row.cells:
  353. align = self.getValue("align", ((None, th)[isinstance(th, tblCell)]), row, row)
  354. valign = self.getValue("valign", th, row)
  355. cssclass = self.getValue("cssclass", th)
  356. attr = ""
  357. if align:
  358. attr += " align=\"%s\"" % align
  359. if valign:
  360. attr += " valign=\"%s\"" % valign
  361. if cssclass:
  362. attr += " class=\"%s\"" % cssclass
  363. css = ""
  364. if embeedcss:
  365. css = " style=\"border:none;color:#003399;font-size:16px;font-weight:normal;white-space:nowrap;padding:3px 10px;\""
  366. if row == last_row:
  367. css = css[:-1] + "padding-bottom:5px;\""
  368. out.write(" <th%s%s>\n" % (attr, css))
  369. if th is not None:
  370. out.write(" %s\n" % htmlEncode(th.text))
  371. out.write(" </th>\n")
  372. out.write(" </tr>\n")
  373. out.write(" </thead>\n <tbody>\n")
  374. rows = [row for row in self.rows if not self.getValue("header")]
  375. for r in range(len(rows)):
  376. row = rows[r]
  377. rowattr = ""
  378. cssclass = self.getValue("cssclass", row)
  379. if cssclass:
  380. rowattr += " class=\"%s\"" % cssclass
  381. out.write(" <tr%s>\n" % (rowattr))
  382. i = 0
  383. while i < len(row.cells):
  384. column = columns[i]
  385. td = row.cells[i]
  386. if isinstance(td, int):
  387. i += td
  388. continue
  389. colspan = self.getValue("colspan", td)
  390. rowspan = self.getValue("rowspan", td)
  391. align = self.getValue("align", td, row, column)
  392. valign = self.getValue("valign", td, row, column)
  393. color = self.getValue("color", td, row, column)
  394. bold = self.getValue("bold", td, row, column)
  395. italic = self.getValue("italic", td, row, column)
  396. style = ""
  397. attr = ""
  398. if color:
  399. style += "color:%s;" % color
  400. if bold:
  401. style += "font-weight: bold;"
  402. if italic:
  403. style += "font-style: italic;"
  404. if align and align != "left":
  405. attr += " align=\"%s\"" % align
  406. if valign and valign != "middle":
  407. attr += " valign=\"%s\"" % valign
  408. if colspan > 1:
  409. attr += " colspan=\"%s\"" % colspan
  410. if rowspan > 1:
  411. attr += " rowspan=\"%s\"" % rowspan
  412. for q in range(r+1, min(r+rowspan, len(rows))):
  413. rows[q].cells[i] = colspan
  414. if style:
  415. attr += " style=\"%s\"" % style
  416. css = ""
  417. if embeedcss:
  418. css = " style=\"border:none;border-bottom:1px solid #CCCCCC;color:#666699;padding:6px 8px;white-space:nowrap;\""
  419. if r == 0:
  420. css = css[:-1] + "border-top:2px solid #6678B1;\""
  421. out.write(" <td%s%s>\n" % (attr, css))
  422. if td is not None:
  423. out.write(" %s\n" % htmlEncode(td.text))
  424. out.write(" </td>\n")
  425. i += colspan
  426. out.write(" </tr>\n")
  427. out.write(" </tbody>\n</table>\n</div>\n")
  428. def htmlPrintHeader(out, title = None):
  429. if title:
  430. titletag = "<title>%s</title>\n" % htmlEncode([str(title)])
  431. else:
  432. titletag = ""
  433. out.write("""<!DOCTYPE HTML>
  434. <html>
  435. <head>
  436. <meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
  437. %s<style type="text/css">
  438. html, body {font-family: Lucida Console, Courier New, Courier;font-size: 16px;color:#3e4758;}
  439. .tbl{background:none repeat scroll 0 0 #FFFFFF;border-collapse:collapse;font-family:"Lucida Sans Unicode","Lucida Grande",Sans-Serif;font-size:14px;margin:20px;text-align:left;width:480px;margin-left: auto;margin-right: auto;white-space:nowrap;}
  440. .tbl span{display:block;white-space:nowrap;}
  441. .tbl thead tr:last-child th {padding-bottom:5px;}
  442. .tbl tbody tr:first-child td {border-top:3px solid #6678B1;}
  443. .tbl th{border:none;color:#003399;font-size:16px;font-weight:normal;white-space:nowrap;padding:3px 10px;}
  444. .tbl td{border:none;border-bottom:1px solid #CCCCCC;color:#666699;padding:6px 8px;white-space:nowrap;}
  445. .tbl tbody tr:hover td{color:#000099;}
  446. .tbl caption{font:italic 16px "Trebuchet MS",Verdana,Arial,Helvetica,sans-serif;padding:0 0 5px;text-align:right;white-space:normal;}
  447. .firstingroup {border-top:2px solid #6678B1;}
  448. </style>
  449. <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
  450. <script type="text/javascript">
  451. function abs(val) { return val < 0 ? -val : val }
  452. $(function(){
  453. //generate filter rows
  454. $("div.tableFormatter table.tbl").each(function(tblIdx, tbl) {
  455. var head = $("thead", tbl)
  456. var filters = $("<tr></tr>")
  457. var hasAny = false
  458. $("tr:first th", head).each(function(colIdx, col) {
  459. col = $(col)
  460. var cell
  461. var id = "t" + tblIdx + "r" + colIdx
  462. if (col.hasClass("col_name")){
  463. cell = $("<th><input id='" + id + "' name='" + id + "' type='text' style='width:100%%' class='filter_col_name' title='Regular expression for name filtering (&quot;resize.*640x480&quot; - resize tests on VGA resolution)'></input></th>")
  464. hasAny = true
  465. }
  466. else if (col.hasClass("col_rel")){
  467. cell = $("<th><input id='" + id + "' name='" + id + "' type='text' style='width:100%%' class='filter_col_rel' title='Filter out lines with a x-factor of acceleration less than Nx'></input></th>")
  468. hasAny = true
  469. }
  470. else if (col.hasClass("col_cr")){
  471. cell = $("<th><input id='" + id + "' name='" + id + "' type='text' style='width:100%%' class='filter_col_cr' title='Filter out lines with a percentage of acceleration less than N%%'></input></th>")
  472. hasAny = true
  473. }
  474. else
  475. cell = $("<th></th>")
  476. cell.appendTo(filters)
  477. })
  478. if (hasAny){
  479. $(tbl).wrap("<form id='form_t" + tblIdx + "' method='get' action=''></form>")
  480. $("<input it='test' type='submit' value='Apply Filters' style='margin-left:10px;'></input>")
  481. .appendTo($("th:last", filters.appendTo(head)))
  482. }
  483. })
  484. //get filter values
  485. var vars = []
  486. var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&')
  487. for(var i = 0; i < hashes.length; ++i)
  488. {
  489. hash = hashes[i].split('=')
  490. vars.push(decodeURIComponent(hash[0]))
  491. vars[decodeURIComponent(hash[0])] = decodeURIComponent(hash[1]);
  492. }
  493. //set filter values
  494. for(var i = 0; i < vars.length; ++i)
  495. $("#" + vars[i]).val(vars[vars[i]])
  496. //apply filters
  497. $("div.tableFormatter table.tbl").each(function(tblIdx, tbl) {
  498. filters = $("input:text", tbl)
  499. var predicate = function(row) {return true;}
  500. var empty = true
  501. $.each($("input:text", tbl), function(i, flt) {
  502. flt = $(flt)
  503. var val = flt.val()
  504. var pred = predicate;
  505. if(val) {
  506. empty = false
  507. var colIdx = parseInt(flt.attr("id").slice(flt.attr("id").indexOf('r') + 1))
  508. if(flt.hasClass("filter_col_name")) {
  509. var re = new RegExp(val);
  510. predicate = function(row) {
  511. if (re.exec($(row.get(colIdx)).text()) == null)
  512. return false
  513. return pred(row)
  514. }
  515. } else if(flt.hasClass("filter_col_rel")) {
  516. var percent = parseFloat(val)
  517. if (percent < 0) {
  518. predicate = function(row) {
  519. var val = parseFloat($(row.get(colIdx)).text())
  520. if (!val || val >= 1 || val > 1+percent)
  521. return false
  522. return pred(row)
  523. }
  524. } else {
  525. predicate = function(row) {
  526. var val = parseFloat($(row.get(colIdx)).text())
  527. if (!val || val < percent)
  528. return false
  529. return pred(row)
  530. }
  531. }
  532. } else if(flt.hasClass("filter_col_cr")) {
  533. var percent = parseFloat(val)
  534. predicate = function(row) {
  535. var val = parseFloat($(row.get(colIdx)).text())
  536. if (!val || val < percent)
  537. return false
  538. return pred(row)
  539. }
  540. }
  541. }
  542. });
  543. if (!empty){
  544. $("tbody tr", tbl).each(function (i, tbl_row) {
  545. if(!predicate($("td", tbl_row)))
  546. $(tbl_row).remove()
  547. })
  548. if($("tbody tr", tbl).length == 0) {
  549. $("<tr><td colspan='"+$("thead tr:first th", tbl).length+"'>No results matching your search criteria</td></tr>")
  550. .appendTo($("tbody", tbl))
  551. }
  552. }
  553. })
  554. })
  555. </script>
  556. </head>
  557. <body>
  558. """ % titletag)
  559. def htmlPrintFooter(out):
  560. out.write("</body>\n</html>")
  561. def getStdoutFilename():
  562. try:
  563. if os.name == "nt":
  564. import msvcrt, ctypes
  565. handle = msvcrt.get_osfhandle(sys.stdout.fileno())
  566. size = ctypes.c_ulong(1024)
  567. nameBuffer = ctypes.create_string_buffer(size.value)
  568. ctypes.windll.kernel32.GetFinalPathNameByHandleA(handle, nameBuffer, size, 4)
  569. return nameBuffer.value
  570. else:
  571. return os.readlink('/proc/self/fd/1')
  572. except:
  573. return ""
  574. def detectHtmlOutputType(requestedType):
  575. if requestedType in ['txt', 'markdown']:
  576. return False
  577. elif requestedType in ["html", "moinwiki"]:
  578. return True
  579. else:
  580. if sys.stdout.isatty():
  581. return False
  582. else:
  583. outname = getStdoutFilename()
  584. if outname:
  585. if outname.endswith(".htm") or outname.endswith(".html"):
  586. return True
  587. else:
  588. return False
  589. else:
  590. return False
  591. def getRelativeVal(test, test0, metric):
  592. if not test or not test0:
  593. return None
  594. val0 = test0.get(metric, "s")
  595. if not val0:
  596. return None
  597. val = test.get(metric, "s")
  598. if not val or val == 0:
  599. return None
  600. return float(val0)/val
  601. def getCycleReduction(test, test0, metric):
  602. if not test or not test0:
  603. return None
  604. val0 = test0.get(metric, "s")
  605. if not val0 or val0 == 0:
  606. return None
  607. val = test.get(metric, "s")
  608. if not val:
  609. return None
  610. return (1.0-float(val)/val0)*100
  611. def getScore(test, test0, metric):
  612. if not test or not test0:
  613. return None
  614. m0 = float(test.get("gmean", None))
  615. m1 = float(test0.get("gmean", None))
  616. if m0 == 0 or m1 == 0:
  617. return None
  618. s0 = float(test.get("gstddev", None))
  619. s1 = float(test0.get("gstddev", None))
  620. s = math.sqrt(s0*s0 + s1*s1)
  621. m0 = math.log(m0)
  622. m1 = math.log(m1)
  623. if s == 0:
  624. return None
  625. return (m0-m1)/s
  626. metrix_table = \
  627. {
  628. "name": ("Name of Test", lambda test,test0,units: str(test)),
  629. "samples": ("Number of\ncollected samples", lambda test,test0,units: test.get("samples", units)),
  630. "outliers": ("Number of\noutliers", lambda test,test0,units: test.get("outliers", units)),
  631. "gmean": ("Geometric mean", lambda test,test0,units: test.get("gmean", units)),
  632. "mean": ("Mean", lambda test,test0,units: test.get("mean", units)),
  633. "min": ("Min", lambda test,test0,units: test.get("min", units)),
  634. "median": ("Median", lambda test,test0,units: test.get("median", units)),
  635. "stddev": ("Standard deviation", lambda test,test0,units: test.get("stddev", units)),
  636. "gstddev": ("Standard deviation of Ln(time)", lambda test,test0,units: test.get("gstddev")),
  637. "gmean%": ("Geometric mean (relative)", lambda test,test0,units: getRelativeVal(test, test0, "gmean")),
  638. "mean%": ("Mean (relative)", lambda test,test0,units: getRelativeVal(test, test0, "mean")),
  639. "min%": ("Min (relative)", lambda test,test0,units: getRelativeVal(test, test0, "min")),
  640. "median%": ("Median (relative)", lambda test,test0,units: getRelativeVal(test, test0, "median")),
  641. "stddev%": ("Standard deviation (relative)", lambda test,test0,units: getRelativeVal(test, test0, "stddev")),
  642. "gstddev%": ("Standard deviation of Ln(time) (relative)", lambda test,test0,units: getRelativeVal(test, test0, "gstddev")),
  643. "gmean$": ("Geometric mean (cycle reduction)", lambda test,test0,units: getCycleReduction(test, test0, "gmean")),
  644. "mean$": ("Mean (cycle reduction)", lambda test,test0,units: getCycleReduction(test, test0, "mean")),
  645. "min$": ("Min (cycle reduction)", lambda test,test0,units: getCycleReduction(test, test0, "min")),
  646. "median$": ("Median (cycle reduction)", lambda test,test0,units: getCycleReduction(test, test0, "median")),
  647. "stddev$": ("Standard deviation (cycle reduction)", lambda test,test0,units: getCycleReduction(test, test0, "stddev")),
  648. "gstddev$": ("Standard deviation of Ln(time) (cycle reduction)", lambda test,test0,units: getCycleReduction(test, test0, "gstddev")),
  649. "score": ("SCORE", lambda test,test0,units: getScore(test, test0, "gstddev")),
  650. }
  651. def formatValue(val, metric, units = None):
  652. if val is None:
  653. return "-"
  654. if metric.endswith("%"):
  655. return "%.2f" % val
  656. if metric.endswith("$"):
  657. return "%.2f%%" % val
  658. if metric.endswith("S"):
  659. if val > 3.5:
  660. return "SLOWER"
  661. if val < -3.5:
  662. return "FASTER"
  663. if val > -1.5 and val < 1.5:
  664. return " "
  665. if val < 0:
  666. return "faster"
  667. if val > 0:
  668. return "slower"
  669. #return "%.4f" % val
  670. if units:
  671. return "%.3f %s" % (val, units)
  672. else:
  673. return "%.3f" % val
  674. if __name__ == "__main__":
  675. if len(sys.argv) < 2:
  676. print("Usage:\n", os.path.basename(sys.argv[0]), "<log_name>.xml")
  677. exit(0)
  678. parser = OptionParser()
  679. parser.add_option("-o", "--output", dest="format", help="output results in text format (can be 'txt', 'html', 'markdown' or 'auto' - default)", metavar="FMT", default="auto")
  680. parser.add_option("-m", "--metric", dest="metric", help="output metric", metavar="NAME", default="gmean")
  681. parser.add_option("-u", "--units", dest="units", help="units for output values (s, ms (default), us, ns or ticks)", metavar="UNITS", default="ms")
  682. (options, args) = parser.parse_args()
  683. options.generateHtml = detectHtmlOutputType(options.format)
  684. if options.metric not in metrix_table:
  685. options.metric = "gmean"
  686. #print options
  687. #print args
  688. # tbl = table()
  689. # tbl.newColumn("first", "qqqq", align = "left")
  690. # tbl.newColumn("second", "wwww\nz\nx\n")
  691. # tbl.newColumn("third", "wwasdas")
  692. #
  693. # tbl.newCell(0, "ccc111", align = "right")
  694. # tbl.newCell(1, "dddd1")
  695. # tbl.newCell(2, "8768756754")
  696. # tbl.newRow()
  697. # tbl.newCell(0, "1\n2\n3\n4\n5\n6\n7", align = "center", colspan = 2, rowspan = 2)
  698. # tbl.newCell(2, "xxx\nqqq", align = "center", colspan = 1, valign = "middle")
  699. # tbl.newRow()
  700. # tbl.newCell(2, "+", align = "center", colspan = 1, valign = "middle")
  701. # tbl.newRow()
  702. # tbl.newCell(0, "vcvvbasdsadassdasdasv", align = "right", colspan = 2)
  703. # tbl.newCell(2, "dddd1")
  704. # tbl.newRow()
  705. # tbl.newCell(0, "vcvvbv")
  706. # tbl.newCell(1, "3445324", align = "right")
  707. # tbl.newCell(2, None)
  708. # tbl.newCell(1, "0000")
  709. # if sys.stdout.isatty():
  710. # tbl.consolePrintTable(sys.stdout)
  711. # else:
  712. # htmlPrintHeader(sys.stdout)
  713. # tbl.htmlPrintTable(sys.stdout)
  714. # htmlPrintFooter(sys.stdout)
  715. import testlog_parser
  716. if options.generateHtml:
  717. htmlPrintHeader(sys.stdout, "Tables demo")
  718. getter = metrix_table[options.metric][1]
  719. for arg in args:
  720. tests = testlog_parser.parseLogFile(arg)
  721. tbl = table(arg, format=options.format)
  722. tbl.newColumn("name", "Name of Test", align = "left")
  723. tbl.newColumn("value", metrix_table[options.metric][0], align = "center", bold = "true")
  724. for t in sorted(tests):
  725. tbl.newRow()
  726. tbl.newCell("name", str(t))
  727. status = t.get("status")
  728. if status != "run":
  729. tbl.newCell("value", status)
  730. else:
  731. val = getter(t, None, options.units)
  732. if val:
  733. if options.metric.endswith("%"):
  734. tbl.newCell("value", "%.2f" % val, val)
  735. else:
  736. tbl.newCell("value", "%.3f %s" % (val, options.units), val)
  737. else:
  738. tbl.newCell("value", "-")
  739. if options.generateHtml:
  740. tbl.htmlPrintTable(sys.stdout)
  741. else:
  742. tbl.consolePrintTable(sys.stdout)
  743. if options.generateHtml:
  744. htmlPrintFooter(sys.stdout)