TestReport.py 9.1 KB

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