testlog_parser.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #!/usr/bin/env python
  2. """ Parse XML test log file.
  3. This module serves as utility for other scripts.
  4. """
  5. from __future__ import print_function
  6. import collections
  7. import re
  8. import os.path
  9. import sys
  10. from xml.dom.minidom import parse
  11. if sys.version_info > (3,):
  12. long = int
  13. def cmp(a, b): return (a>b)-(a<b)
  14. class TestInfo(object):
  15. def __init__(self, xmlnode):
  16. self.fixture = xmlnode.getAttribute("classname")
  17. self.name = xmlnode.getAttribute("name")
  18. self.value_param = xmlnode.getAttribute("value_param")
  19. self.type_param = xmlnode.getAttribute("type_param")
  20. custom_status = xmlnode.getAttribute("custom_status")
  21. failures = xmlnode.getElementsByTagName("failure")
  22. if len(custom_status) > 0:
  23. self.status = custom_status
  24. elif len(failures) > 0:
  25. self.status = "failed"
  26. else:
  27. self.status = xmlnode.getAttribute("status")
  28. if self.name.startswith("DISABLED_"):
  29. if self.status == 'notrun':
  30. self.status = "disabled"
  31. self.fixture = self.fixture.replace("DISABLED_", "")
  32. self.name = self.name.replace("DISABLED_", "")
  33. self.properties = {
  34. prop.getAttribute("name") : prop.getAttribute("value")
  35. for prop in xmlnode.getElementsByTagName("property")
  36. if prop.hasAttribute("name") and prop.hasAttribute("value")
  37. }
  38. self.metrix = {}
  39. self.parseLongMetric(xmlnode, "bytesIn");
  40. self.parseLongMetric(xmlnode, "bytesOut");
  41. self.parseIntMetric(xmlnode, "samples");
  42. self.parseIntMetric(xmlnode, "outliers");
  43. self.parseFloatMetric(xmlnode, "frequency", 1);
  44. self.parseLongMetric(xmlnode, "min");
  45. self.parseLongMetric(xmlnode, "median");
  46. self.parseLongMetric(xmlnode, "gmean");
  47. self.parseLongMetric(xmlnode, "mean");
  48. self.parseLongMetric(xmlnode, "stddev");
  49. self.parseFloatMetric(xmlnode, "gstddev");
  50. self.parseFloatMetric(xmlnode, "time");
  51. self.parseLongMetric(xmlnode, "total_memory_usage");
  52. def parseLongMetric(self, xmlnode, name, default = 0):
  53. if name in self.properties:
  54. self.metrix[name] = long(self.properties[name])
  55. elif xmlnode.hasAttribute(name):
  56. self.metrix[name] = long(xmlnode.getAttribute(name))
  57. else:
  58. self.metrix[name] = default
  59. def parseIntMetric(self, xmlnode, name, default = 0):
  60. if name in self.properties:
  61. self.metrix[name] = int(self.properties[name])
  62. elif xmlnode.hasAttribute(name):
  63. self.metrix[name] = int(xmlnode.getAttribute(name))
  64. else:
  65. self.metrix[name] = default
  66. def parseFloatMetric(self, xmlnode, name, default = 0):
  67. if name in self.properties:
  68. self.metrix[name] = float(self.properties[name])
  69. elif xmlnode.hasAttribute(name):
  70. self.metrix[name] = float(xmlnode.getAttribute(name))
  71. else:
  72. self.metrix[name] = default
  73. def parseStringMetric(self, xmlnode, name, default = None):
  74. if name in self.properties:
  75. self.metrix[name] = self.properties[name].strip()
  76. elif xmlnode.hasAttribute(name):
  77. self.metrix[name] = xmlnode.getAttribute(name).strip()
  78. else:
  79. self.metrix[name] = default
  80. def get(self, name, units="ms"):
  81. if name == "classname":
  82. return self.fixture
  83. if name == "name":
  84. return self.name
  85. if name == "fullname":
  86. return self.__str__()
  87. if name == "value_param":
  88. return self.value_param
  89. if name == "type_param":
  90. return self.type_param
  91. if name == "status":
  92. return self.status
  93. val = self.metrix.get(name, None)
  94. if not val:
  95. return val
  96. if name == "time":
  97. return self.metrix.get("time")
  98. if name in ["gmean", "min", "mean", "median", "stddev"]:
  99. scale = 1.0
  100. frequency = self.metrix.get("frequency", 1.0) or 1.0
  101. if units == "ms":
  102. scale = 1000.0
  103. if units == "us" or units == "mks": # mks is typo error for microsecond (<= OpenCV 3.4)
  104. scale = 1000000.0
  105. if units == "ns":
  106. scale = 1000000000.0
  107. if units == "ticks":
  108. frequency = long(1)
  109. scale = long(1)
  110. return val * scale / frequency
  111. return val
  112. def dump(self, units="ms"):
  113. print("%s ->\t\033[1;31m%s\033[0m = \t%.2f%s" % (str(self), self.status, self.get("gmean", units), units))
  114. def getName(self):
  115. pos = self.name.find("/")
  116. if pos > 0:
  117. return self.name[:pos]
  118. return self.name
  119. def getFixture(self):
  120. if self.fixture.endswith(self.getName()):
  121. fixture = self.fixture[:-len(self.getName())]
  122. else:
  123. fixture = self.fixture
  124. if fixture.endswith("_"):
  125. fixture = fixture[:-1]
  126. return fixture
  127. def param(self):
  128. return '::'.join(filter(None, [self.type_param, self.value_param]))
  129. def shortName(self):
  130. name = self.getName()
  131. fixture = self.getFixture()
  132. return '::'.join(filter(None, [name, fixture]))
  133. def __str__(self):
  134. name = self.getName()
  135. fixture = self.getFixture()
  136. return '::'.join(filter(None, [name, fixture, self.type_param, self.value_param]))
  137. def __cmp__(self, other):
  138. r = cmp(self.fixture, other.fixture);
  139. if r != 0:
  140. return r
  141. if self.type_param:
  142. if other.type_param:
  143. r = cmp(self.type_param, other.type_param);
  144. if r != 0:
  145. return r
  146. else:
  147. return -1
  148. else:
  149. if other.type_param:
  150. return 1
  151. if self.value_param:
  152. if other.value_param:
  153. r = cmp(self.value_param, other.value_param);
  154. if r != 0:
  155. return r
  156. else:
  157. return -1
  158. else:
  159. if other.value_param:
  160. return 1
  161. return 0
  162. def __lt__(self, other):
  163. return self.__cmp__(other) == -1
  164. # This is a Sequence for compatibility with old scripts,
  165. # which treat parseLogFile's return value as a list.
  166. class TestRunInfo(object):
  167. def __init__(self, properties, tests):
  168. self.properties = properties
  169. self.tests = tests
  170. def __len__(self):
  171. return len(self.tests)
  172. def __getitem__(self, key):
  173. return self.tests[key]
  174. def parseLogFile(filename):
  175. log = parse(filename)
  176. properties = {
  177. attr_name[3:]: attr_value
  178. for (attr_name, attr_value) in log.documentElement.attributes.items()
  179. if attr_name.startswith('cv_')
  180. }
  181. tests = list(map(TestInfo, log.getElementsByTagName("testcase")))
  182. return TestRunInfo(properties, tests)
  183. if __name__ == "__main__":
  184. if len(sys.argv) < 2:
  185. print("Usage:\n", os.path.basename(sys.argv[0]), "<log_name>.xml")
  186. exit(0)
  187. for arg in sys.argv[1:]:
  188. print("Processing {}...".format(arg))
  189. run = parseLogFile(arg)
  190. print("Properties:")
  191. for (prop_name, prop_value) in run.properties.items():
  192. print("\t{} = {}".format(prop_name, prop_value))
  193. print("Tests:")
  194. for t in sorted(run.tests):
  195. t.dump()
  196. print()