Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
8.8 KiB

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. # -------------------------------------------------------------------------------
  4. # ESPurna module memory analyser
  5. # xose.perez@gmail.com
  6. #
  7. # Based on:
  8. # https://github.com/letscontrolit/ESPEasy/blob/mega/memanalyzer.py
  9. # by psy0rz <edwin@datux.nl>
  10. # https://raw.githubusercontent.com/SmingHub/Sming/develop/tools/memanalyzer.py
  11. # by Slavey Karadzhov <slav@attachix.com>
  12. # https://github.com/Sermus/ESP8266_memory_analyzer
  13. # by Andrey Filimonov
  14. #
  15. # -------------------------------------------------------------------------------
  16. from __future__ import print_function
  17. import argparse
  18. import os
  19. import re
  20. import shlex
  21. import subprocess
  22. import sys
  23. from collections import OrderedDict
  24. from sortedcontainers import SortedDict
  25. if sys.version_info > (3, 0):
  26. from subprocess import getstatusoutput
  27. else:
  28. from commands import getstatusoutput
  29. # -------------------------------------------------------------------------------
  30. TOTAL_IRAM = 32786
  31. TOTAL_DRAM = 81920
  32. env = "esp8266-4m-ota"
  33. objdump_binary = "xtensa-lx106-elf-objdump"
  34. sections = OrderedDict([
  35. ("data", "Initialized Data (RAM)"),
  36. ("rodata", "ReadOnly Data (RAM)"),
  37. ("bss", "Uninitialized Data (RAM)"),
  38. ("text", "Cached Code (IRAM)"),
  39. ("irom0_text", "Uncached Code (SPI)")
  40. ])
  41. description = "ESPurna Memory Analyzer v0.1"
  42. # -------------------------------------------------------------------------------
  43. def file_size(file):
  44. try:
  45. return os.stat(file).st_size
  46. except OSError:
  47. return 0
  48. def analyse_memory(elf_file):
  49. command = "{} -t '{}'".format(objdump_binary, elf_file)
  50. response = subprocess.check_output(shlex.split(command))
  51. if isinstance(response, bytes):
  52. response = response.decode('utf-8')
  53. lines = response.split('\n')
  54. # print("{0: >10}|{1: >30}|{2: >12}|{3: >12}|{4: >8}".format("Section", "Description", "Start (hex)", "End (hex)", "Used space"));
  55. # print("------------------------------------------------------------------------------");
  56. ret = {}
  57. for (id_, _) in list(sections.items()):
  58. section_start_token = " _{}_start".format(id_)
  59. section_end_token = " _{}_end".format(id_)
  60. section_start = -1
  61. section_end = -1
  62. for line in lines:
  63. if section_start_token in line:
  64. data = line.split(' ')
  65. section_start = int(data[0], 16)
  66. if section_end_token in line:
  67. data = line.split(' ')
  68. section_end = int(data[0], 16)
  69. if section_start != -1 and section_end != -1:
  70. break
  71. section_length = section_end - section_start
  72. # if i < 3:
  73. # usedRAM += section_length
  74. # if i == 3:
  75. # usedIRAM = TOTAL_IRAM - section_length;
  76. ret[id_] = section_length
  77. # print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(id_, descr, section_start, section_end, section_length))
  78. # i += 1
  79. # print("Total Used RAM : {:d}".format(usedRAM))
  80. # print("Free RAM : {:d}".format(TOTAL_DRAM - usedRAM))
  81. # print("Free IRam : {:d}".format(usedIRAM))
  82. return ret
  83. def run(env_, modules_):
  84. flags = ""
  85. for item in modules_.items():
  86. flags += "-D%s_SUPPORT=%d " % item
  87. command = "ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\" ESPURNA_FLAGS=\"{}\" platformio run --silent --environment {} 2>/dev/null".format(flags, env_)
  88. subprocess.check_call(command, shell=True)
  89. def calc_free(module):
  90. free = 80 * 1024 - module['data'] - module['rodata'] - module['bss']
  91. free = free + (16 - free % 16)
  92. module['free'] = free
  93. def modules_get():
  94. modules_ = SortedDict()
  95. for line in open("espurna/config/arduino.h"):
  96. m = re.search(r'(\w*)_SUPPORT', line)
  97. if m:
  98. modules_[m.group(1)] = 0
  99. del modules_['LLMNR']
  100. del modules_['NETBIOS']
  101. return modules_
  102. if __name__ == '__main__':
  103. # Parse command line options
  104. parser = argparse.ArgumentParser(description=description)
  105. parser.add_argument("modules", nargs='*', help="Modules to test (use ALL to test them all)")
  106. parser.add_argument("-c", "--core", help="use core as base configuration instead of default", default=0, action='count')
  107. parser.add_argument("-l", "--list", help="list available modules", default=0, action='count')
  108. args = parser.parse_args()
  109. # Hello
  110. print()
  111. print(description)
  112. print()
  113. # Check xtensa-lx106-elf-objdump is in the path
  114. status, result = getstatusoutput(objdump_binary)
  115. if status != 2 and status != 512:
  116. print("xtensa-lx106-elf-objdump not found, please check it is in your PATH")
  117. sys.exit(1)
  118. # Load list of all modules
  119. available_modules = modules_get()
  120. if args.list > 0:
  121. print("List of available modules:\n")
  122. for key, value in available_modules.items():
  123. print("* " + key)
  124. print()
  125. sys.exit(0)
  126. # Which modules to test?
  127. test_modules = []
  128. if len(args.modules) > 0:
  129. if "ALL" in args.modules:
  130. test_modules = available_modules.keys()
  131. else:
  132. test_modules = args.modules
  133. # Check test modules exist
  134. for module in test_modules:
  135. if module not in available_modules:
  136. print("Module {} not found".format(module))
  137. sys.exit(2)
  138. # Define base configuration
  139. if args.core == 0:
  140. modules = SortedDict()
  141. for m in test_modules:
  142. modules[m] = 0
  143. else:
  144. modules = available_modules
  145. # Show init message
  146. if len(test_modules) > 0:
  147. print("Analyzing module(s) {} on top of {} configuration\n".format(", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT"))
  148. else:
  149. print("Analyzing {} configuration\n".format("CORE" if args.core > 0 else "DEFAULT"))
  150. output_format = "{:<20}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}"
  151. print(output_format.format(
  152. "Module",
  153. "Cache IRAM",
  154. "Init RAM",
  155. "R.O. RAM",
  156. "Uninit RAM",
  157. "Available RAM",
  158. "Flash ROM",
  159. "Binary size"
  160. ))
  161. print(output_format.replace("<", ">").format(
  162. "",
  163. ".text",
  164. ".data",
  165. ".rodata",
  166. ".bss",
  167. "heap + stack",
  168. ".irom0.text",
  169. ""
  170. ))
  171. print(output_format.format(
  172. "-" * 20,
  173. "-" * 15,
  174. "-" * 15,
  175. "-" * 15,
  176. "-" * 15,
  177. "-" * 15,
  178. "-" * 15,
  179. "-" * 15
  180. ))
  181. # Build the core without modules to get base memory usage
  182. run(env, modules)
  183. base = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
  184. base['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
  185. calc_free(base)
  186. print(output_format.format(
  187. "CORE" if args.core == 1 else "DEFAULT",
  188. base['text'],
  189. base['data'],
  190. base['rodata'],
  191. base['bss'],
  192. base['free'],
  193. base['irom0_text'],
  194. base['size'],
  195. ))
  196. # Test each module
  197. results = {}
  198. for module in test_modules:
  199. modules[module] = 1
  200. run(env, modules)
  201. results[module] = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
  202. results[module]['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
  203. calc_free(results[module])
  204. modules[module] = 0
  205. print(output_format.format(
  206. module,
  207. results[module]['text'] - base['text'],
  208. results[module]['data'] - base['data'],
  209. results[module]['rodata'] - base['rodata'],
  210. results[module]['bss'] - base['bss'],
  211. results[module]['free'] - base['free'],
  212. results[module]['irom0_text'] - base['irom0_text'],
  213. results[module]['size'] - base['size'],
  214. ))
  215. # Test all modules
  216. if len(test_modules) > 0:
  217. for module in test_modules:
  218. modules[module] = 1
  219. run(env, modules)
  220. total = analyse_memory(".pioenvs/{}/firmware.elf".format(env))
  221. total['size'] = file_size(".pioenvs/{}/firmware.bin".format(env))
  222. calc_free(total)
  223. if len(test_modules) > 1:
  224. print(output_format.format(
  225. "ALL MODULES",
  226. total['text'] - base['text'],
  227. total['data'] - base['data'],
  228. total['rodata'] - base['rodata'],
  229. total['bss'] - base['bss'],
  230. total['free'] - base['free'],
  231. total['irom0_text'] - base['irom0_text'],
  232. total['size'] - base['size'],
  233. ))
  234. print(output_format.format(
  235. "TOTAL",
  236. total['text'],
  237. total['data'],
  238. total['rodata'],
  239. total['bss'],
  240. total['irom0_text'],
  241. total['size'],
  242. ))
  243. print("\n")