Mirror of espurna firmware for wireless switches and more
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.

303 lines
9.2 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 k, v in modules_.items():
  86. flags += "-D{}_SUPPORT={:d} ".format(k, v)
  87. os_env = os.environ.copy()
  88. os_env["ESPURNA_BOARD"] = "WEMOS_D1_MINI_RELAYSHIELD"
  89. os_env["ESPURNA_FLAGS"] = flags
  90. os_env["ESPURNA_PIO_SHARED_LIBRARIES"] = "y"
  91. command = "platformio run --silent --environment {} 2>/dev/null".format(env_)
  92. subprocess.check_call(command, shell=True)
  93. def calc_free(module):
  94. free = 80 * 1024 - module['data'] - module['rodata'] - module['bss']
  95. free = free + (16 - free % 16)
  96. module['free'] = free
  97. def modules_get():
  98. modules_ = SortedDict()
  99. for line in open("espurna/config/arduino.h"):
  100. m = re.search(r'(\w*)_SUPPORT', line)
  101. if m:
  102. modules_[m.group(1)] = 0
  103. del modules_['LLMNR']
  104. del modules_['NETBIOS']
  105. return modules_
  106. if __name__ == '__main__':
  107. # Parse command line options
  108. parser = argparse.ArgumentParser(description=description)
  109. parser.add_argument("modules", nargs='*', help="Modules to test (use ALL to test them all)")
  110. parser.add_argument("-c", "--core", help="use core as base configuration instead of default", default=0, action='count')
  111. parser.add_argument("-l", "--list", help="list available modules", default=0, action='count')
  112. args = parser.parse_args()
  113. # Hello
  114. print()
  115. print(description)
  116. print()
  117. # Check xtensa-lx106-elf-objdump is in the path
  118. status, result = getstatusoutput(objdump_binary)
  119. if status != 2 and status != 512:
  120. print("xtensa-lx106-elf-objdump not found, please check it is in your PATH")
  121. sys.exit(1)
  122. # Load list of all modules
  123. available_modules = modules_get()
  124. if args.list > 0:
  125. print("List of available modules:\n")
  126. for key, value in available_modules.items():
  127. print("* " + key)
  128. print()
  129. sys.exit(0)
  130. # Which modules to test?
  131. test_modules = []
  132. if len(args.modules) > 0:
  133. if "ALL" in args.modules:
  134. test_modules = available_modules.keys()
  135. else:
  136. test_modules = args.modules
  137. # Check test modules exist
  138. for module in test_modules:
  139. if module not in available_modules:
  140. print("Module {} not found".format(module))
  141. sys.exit(2)
  142. # Define base configuration
  143. if args.core == 0:
  144. modules = SortedDict()
  145. for m in test_modules:
  146. modules[m] = 0
  147. else:
  148. modules = available_modules
  149. # Show init message
  150. if len(test_modules) > 0:
  151. print("Analyzing module(s) {} on top of {} configuration\n".format(", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT"))
  152. else:
  153. print("Analyzing {} configuration\n".format("CORE" if args.core > 0 else "DEFAULT"))
  154. output_format = "{:<20}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}"
  155. print(output_format.format(
  156. "Module",
  157. "Cache IRAM",
  158. "Init RAM",
  159. "R.O. RAM",
  160. "Uninit RAM",
  161. "Available RAM",
  162. "Flash ROM",
  163. "Binary size"
  164. ))
  165. print(output_format.replace("<", ">").format(
  166. "",
  167. ".text",
  168. ".data",
  169. ".rodata",
  170. ".bss",
  171. "heap + stack",
  172. ".irom0.text",
  173. ""
  174. ))
  175. print(output_format.format(
  176. "-" * 20,
  177. "-" * 15,
  178. "-" * 15,
  179. "-" * 15,
  180. "-" * 15,
  181. "-" * 15,
  182. "-" * 15,
  183. "-" * 15
  184. ))
  185. # Build the core without modules to get base memory usage
  186. run(env, modules)
  187. base = analyse_memory(".pio/build/{}/firmware.elf".format(env))
  188. base['size'] = file_size(".pio/build/{}/firmware.bin".format(env))
  189. calc_free(base)
  190. print(output_format.format(
  191. "CORE" if args.core == 1 else "DEFAULT",
  192. base['text'],
  193. base['data'],
  194. base['rodata'],
  195. base['bss'],
  196. base['free'],
  197. base['irom0_text'],
  198. base['size'],
  199. ))
  200. # Test each module
  201. results = {}
  202. for module in test_modules:
  203. modules[module] = 1
  204. run(env, modules)
  205. results[module] = analyse_memory(".pio/build/{}/firmware.elf".format(env))
  206. results[module]['size'] = file_size(".pio/build/{}/firmware.bin".format(env))
  207. calc_free(results[module])
  208. modules[module] = 0
  209. print(output_format.format(
  210. module,
  211. results[module]['text'] - base['text'],
  212. results[module]['data'] - base['data'],
  213. results[module]['rodata'] - base['rodata'],
  214. results[module]['bss'] - base['bss'],
  215. results[module]['free'] - base['free'],
  216. results[module]['irom0_text'] - base['irom0_text'],
  217. results[module]['size'] - base['size'],
  218. ))
  219. # Test all modules
  220. if len(test_modules) > 0:
  221. for module in test_modules:
  222. modules[module] = 1
  223. run(env, modules)
  224. total = analyse_memory(".pio/build/{}/firmware.elf".format(env))
  225. total['size'] = file_size(".pio/build/{}/firmware.bin".format(env))
  226. calc_free(total)
  227. print(output_format.format(
  228. "-" * 20,
  229. "-" * 15,
  230. "-" * 15,
  231. "-" * 15,
  232. "-" * 15,
  233. "-" * 15,
  234. "-" * 15,
  235. "-" * 15
  236. ))
  237. if len(test_modules) > 1:
  238. print(output_format.format(
  239. "ALL MODULES",
  240. total['text'] - base['text'],
  241. total['data'] - base['data'],
  242. total['rodata'] - base['rodata'],
  243. total['bss'] - base['bss'],
  244. total['free'] - base['free'],
  245. total['irom0_text'] - base['irom0_text'],
  246. total['size'] - base['size'],
  247. ))
  248. print(output_format.format(
  249. "TOTAL",
  250. total['text'],
  251. total['data'],
  252. total['rodata'],
  253. total['bss'],
  254. total['free'],
  255. total['irom0_text'],
  256. total['size'],
  257. ))
  258. print("\n")