TestReport.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. """
  2. this module generates markdown format test report for throughput test.
  3. The test report contains 2 parts:
  4. 1. throughput with different configs
  5. 2. throughput with RSSI
  6. """
  7. import os
  8. class ThroughputForConfigsReport(object):
  9. THROUGHPUT_TYPES = ['tcp_tx', 'tcp_rx', 'udp_tx', 'udp_rx']
  10. REPORT_FILE_NAME = 'ThroughputForConfigs.md'
  11. def __init__(self, output_path, ap_ssid, throughput_results, sdkconfig_files):
  12. """
  13. :param ap_ssid: the ap we expected to use
  14. :param throughput_results: config with the following type::
  15. {
  16. "config_name": {
  17. "tcp_tx": result,
  18. "tcp_rx": result,
  19. "udp_tx": result,
  20. "udp_rx": result,
  21. },
  22. "config_name2": {},
  23. }
  24. """
  25. self.output_path = output_path
  26. self.ap_ssid = ap_ssid
  27. self.results = throughput_results
  28. self.sdkconfigs = dict()
  29. for config_name in sdkconfig_files:
  30. self.sdkconfigs[config_name] = self._parse_config_file(sdkconfig_files[config_name])
  31. if not os.path.exists(output_path):
  32. os.makedirs(output_path)
  33. self.sort_order = list(self.sdkconfigs.keys())
  34. self.sort_order.sort()
  35. @staticmethod
  36. def _parse_config_file(config_file_path):
  37. sdkconfig = {}
  38. with open(config_file_path, 'r') as f:
  39. for line in f:
  40. if not line.isspace():
  41. if line[0] == '#':
  42. continue
  43. name, value = line.split('=')
  44. value = value.strip('\r\n')
  45. sdkconfig[name] = value if value else 'n'
  46. return sdkconfig
  47. def _generate_the_difference_between_configs(self):
  48. """
  49. generate markdown list for different configs::
  50. default: esp-idf default
  51. low:
  52. * `config name 1`: old value -> new value
  53. * `config name 2`: old value -> new value
  54. * ...
  55. ...
  56. """
  57. data = '## Config Definition:\r\n\r\n'
  58. def find_difference(base, new):
  59. _difference = {}
  60. all_configs = set(base.keys())
  61. all_configs.update(set(new.keys()))
  62. for _config in all_configs:
  63. try:
  64. _base_value = base[_config]
  65. except KeyError:
  66. _base_value = 'null'
  67. try:
  68. _new_value = new[_config]
  69. except KeyError:
  70. _new_value = 'null'
  71. if _base_value != _new_value:
  72. _difference[_config] = '{} -> {}'.format(_base_value, _new_value)
  73. return _difference
  74. for i, _config_name in enumerate(self.sort_order):
  75. current_config = self.sdkconfigs[_config_name]
  76. if i > 0:
  77. previous_config_name = self.sort_order[i - 1]
  78. previous_config = self.sdkconfigs[previous_config_name]
  79. else:
  80. previous_config = previous_config_name = None
  81. if previous_config:
  82. # log the difference
  83. difference = find_difference(previous_config, current_config)
  84. data += '* {} (compared to {}):\r\n'.format(_config_name, previous_config_name)
  85. for diff_name in difference:
  86. data += ' * `{}`: {}\r\n'.format(diff_name, difference[diff_name])
  87. return data
  88. def _generate_report_for_one_type(self, throughput_type):
  89. """
  90. generate markdown table with the following format::
  91. | config name | throughput (Mbps) | free heap size (bytes) |
  92. |-------------|-------------------|------------------------|
  93. | default | 32.11 | 147500 |
  94. | low | 32.11 | 147000 |
  95. | medium | 33.22 | 120000 |
  96. | high | 43.11 | 100000 |
  97. | max | 45.22 | 79000 |
  98. """
  99. empty = True
  100. ret = '\r\n### {} {}\r\n\r\n'.format(*throughput_type.split('_'))
  101. ret += '| config name | throughput (Mbps) | free heap size (bytes) |\r\n'
  102. ret += '|-------------|-------------------|------------------------|\r\n'
  103. for config in self.sort_order:
  104. try:
  105. result = self.results[config][throughput_type]
  106. throughput = '{:.02f}'.format(max(result.throughput_by_att[self.ap_ssid].values()))
  107. heap_size = str(result.heap_size)
  108. # although markdown table will do alignment
  109. # do align here for better text editor presentation
  110. ret += '| {:<12}| {:<18}| {:<23}|\r\n'.format(config, throughput, heap_size)
  111. empty = False
  112. except KeyError:
  113. pass
  114. return ret if not empty else ''
  115. def generate_report(self):
  116. data = '# Throughput for different configs\r\n'
  117. data += '\r\nAP: {}\r\n'.format(self.ap_ssid)
  118. for throughput_type in self.THROUGHPUT_TYPES:
  119. data += self._generate_report_for_one_type(throughput_type)
  120. data += '\r\n------\r\n'
  121. data += self._generate_the_difference_between_configs()
  122. with open(os.path.join(self.output_path, self.REPORT_FILE_NAME), 'w') as f:
  123. f.write(data)
  124. class ThroughputVsRssiReport(object):
  125. REPORT_FILE_NAME = 'ThroughputVsRssi.md'
  126. def __init__(self, output_path, throughput_results):
  127. """
  128. :param throughput_results: config with the following type::
  129. {
  130. "tcp_tx": result,
  131. "tcp_rx": result,
  132. "udp_tx": result,
  133. "udp_rx": result,
  134. }
  135. """
  136. self.output_path = output_path
  137. self.raw_data_path = os.path.join(output_path, 'raw_data')
  138. self.results = throughput_results
  139. self.throughput_types = list(self.results.keys())
  140. self.throughput_types.sort()
  141. if not os.path.exists(self.raw_data_path):
  142. os.makedirs(self.raw_data_path)
  143. def _generate_summary(self):
  144. """
  145. generate summary with the following format::
  146. | item | curve analysis | max throughput (Mbps) |
  147. |---------|----------------|-----------------------|
  148. | tcp tx | Success | 32.11 |
  149. | tcp rx | Success | 32.11 |
  150. | udp tx | Success | 45.22 |
  151. | udp rx | Failed | 55.44 |
  152. """
  153. ret = '\r\n### Summary\r\n\r\n'
  154. ret += '| item | curve analysis | max throughput (Mbps) |\r\n'
  155. ret += '|---------|----------------|-----------------------|\r\n'
  156. for _type in self.throughput_types:
  157. result = self.results[_type]
  158. max_throughput = 0.0
  159. curve_analysis = 'Failed' if result.error_list else 'Success'
  160. for ap_ssid in result.throughput_by_att:
  161. _max_for_ap = max(result.throughput_by_rssi[ap_ssid].values())
  162. if _max_for_ap > max_throughput:
  163. max_throughput = _max_for_ap
  164. max_throughput = '{:.02f}'.format(max_throughput)
  165. ret += '| {:<8}| {:<15}| {:<22}|\r\n'.format('{}_{}'.format(result.proto, result.direction),
  166. curve_analysis, max_throughput)
  167. return ret
  168. def _generate_report_for_one_type(self, result):
  169. """
  170. generate markdown table with the following format::
  171. ### tcp rx
  172. Errors:
  173. * detected error 1
  174. * ...
  175. AP: ap_ssid
  176. ![throughput Vs RSSI](path to figure)
  177. AP: ap_ssid
  178. ![throughput Vs RSSI](path to figure)
  179. """
  180. result.post_analysis()
  181. ret = '\r\n### {} {}\r\n'.format(result.proto, result.direction)
  182. if result.error_list:
  183. ret += '\r\nErrors:\r\n\r\n'
  184. for error in result.error_list:
  185. ret += '* ' + error + '\r\n'
  186. for ap_ssid in result.throughput_by_rssi:
  187. ret += '\r\nAP: {}\r\n'.format(ap_ssid)
  188. # draw figure
  189. file_name = result.draw_throughput_figure(self.raw_data_path, ap_ssid, 'rssi')
  190. result.draw_throughput_figure(self.raw_data_path, ap_ssid, 'att')
  191. result.draw_rssi_vs_att_figure(self.raw_data_path, ap_ssid)
  192. ret += '\r\n[throughput Vs RSSI]({})\r\n'.format(os.path.join('raw_data', file_name))
  193. return ret
  194. def generate_report(self):
  195. data = '# Throughput Vs RSSI\r\n'
  196. data += self._generate_summary()
  197. for _type in self.throughput_types:
  198. data += self._generate_report_for_one_type(self.results[_type])
  199. with open(os.path.join(self.output_path, self.REPORT_FILE_NAME), 'w') as f:
  200. f.write(data)