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.

252 lines
7.9 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. # -------------------------------------------------------------------------------
  26. TOTAL_IRAM = 32786
  27. TOTAL_DRAM = 81920
  28. env = "esp8266-4m-ota"
  29. objdump_binary = "xtensa-lx106-elf-objdump"
  30. sections = OrderedDict([
  31. ("data", "Initialized Data (RAM)"),
  32. ("rodata", "ReadOnly Data (RAM)"),
  33. ("bss", "Uninitialized Data (RAM)"),
  34. ("text", "Cached Code (IRAM)"),
  35. ("irom0_text", "Uncached Code (SPI)")
  36. ])
  37. description = "ESPurna Memory Analyzer v0.1"
  38. # -------------------------------------------------------------------------------
  39. def file_size(file):
  40. try:
  41. return os.stat(file).st_size
  42. except:
  43. return 0
  44. def analyse_memory(elf_file):
  45. command = "%s -t '%s' " % (objdump_binary, elf_file)
  46. response = subprocess.check_output(shlex.split(command))
  47. if isinstance(response, bytes):
  48. response = response.decode('utf-8')
  49. lines = response.split('\n')
  50. # print("{0: >10}|{1: >30}|{2: >12}|{3: >12}|{4: >8}".format("Section", "Description", "Start (hex)", "End (hex)", "Used space"));
  51. # print("------------------------------------------------------------------------------");
  52. ret = {}
  53. for (id_, descr) in list(sections.items()):
  54. section_start_token = " _%s_start" % id_
  55. section_end_token = " _%s_end" % id_
  56. section_start = -1
  57. section_end = -1
  58. for line in lines:
  59. if section_start_token in line:
  60. data = line.split(' ')
  61. section_start = int(data[0], 16)
  62. if section_end_token in line:
  63. data = line.split(' ')
  64. section_end = int(data[0], 16)
  65. if section_start != -1 and section_end != -1:
  66. break
  67. section_length = section_end - section_start
  68. # if i < 3:
  69. # usedRAM += section_length
  70. # if i == 3:
  71. # usedIRAM = TOTAL_IRAM - section_length;
  72. ret[id_] = section_length
  73. # print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(id_, descr, section_start, section_end, section_length))
  74. # i += 1
  75. # print("Total Used RAM : %d" % usedRAM)
  76. # print("Free RAM : %d" % (TOTAL_DRAM - usedRAM))
  77. # print("Free IRam : %d" % usedIRAM)
  78. return ret
  79. def run(env_, modules_):
  80. flags = ""
  81. for item in modules_.items():
  82. flags += "-D%s_SUPPORT=%d " % item
  83. command = "export ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s" % (flags, env_)
  84. subprocess.check_call(command, shell=True)
  85. def modules_get():
  86. modules_ = SortedDict()
  87. for line in open("espurna/config/arduino.h"):
  88. m = re.search(r'(\w*)_SUPPORT', line)
  89. if m:
  90. modules_[m.group(1)] = 0
  91. del modules_['LLMNR']
  92. del modules_['NETBIOS']
  93. return modules_
  94. try:
  95. # Parse command line options
  96. parser = argparse.ArgumentParser(description=description)
  97. parser.add_argument("modules", nargs='*', help="Modules to test (use ALL to test them all)")
  98. parser.add_argument("-c", "--core", help="use core as base configuration instead of default", default=0, action='count')
  99. parser.add_argument("-l", "--list", help="list available modules", default=0, action='count')
  100. args = parser.parse_args()
  101. # Hello
  102. print()
  103. print(description)
  104. print()
  105. # Check xtensa-lx106-elf-objdump is in the path
  106. status, result = subprocess.getstatusoutput(objdump_binary)
  107. if status != 512:
  108. print("xtensa-lx106-elf-objdump not found, please check it is in your PATH")
  109. sys.exit(1)
  110. # Load list of all modules
  111. available_modules = modules_get()
  112. if args.list > 0:
  113. print("List of available modules:\n")
  114. for key, value in available_modules.items():
  115. print("* " + key)
  116. print()
  117. sys.exit(0)
  118. # Which modules to test?
  119. test_modules = []
  120. if len(args.modules) > 0:
  121. if "ALL" in args.modules:
  122. test_modules = available_modules.keys()
  123. else:
  124. test_modules = args.modules
  125. # Check test modules exist
  126. for module in test_modules:
  127. if module not in available_modules:
  128. print("Module %s not found" % module)
  129. sys.exit(2)
  130. # Define base configuration
  131. if args.core == 0:
  132. modules = SortedDict()
  133. for m in test_modules:
  134. modules[m] = 0
  135. else:
  136. modules = available_modules
  137. # Show init message
  138. if len(test_modules) > 0:
  139. print("Analyzing module(s) %s on top of %s configuration\n" % (", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT"))
  140. else:
  141. print("Analyzing %s configuration\n" % ("CORE" if args.core > 0 else "DEFAULT"))
  142. output_format = "{:<20}|{:<11}|{:<11}|{:<11}|{:<11}|{:<11}|{:<12}"
  143. print(output_format.format(
  144. "Module",
  145. "Cache IRAM",
  146. "Init RAM",
  147. "R.O. RAM",
  148. "Uninit RAM",
  149. "Flash ROM",
  150. "Binary size"
  151. ))
  152. # Build the core without modules to get base memory usage
  153. run(env, modules)
  154. base = analyse_memory(".pioenvs/%s/firmware.elf" % env)
  155. base['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
  156. print(output_format.format(
  157. "CORE" if args.core == 1 else "DEFAULT",
  158. base['text'],
  159. base['data'],
  160. base['rodata'],
  161. base['bss'],
  162. base['irom0_text'],
  163. base['size'],
  164. ))
  165. # Test each module
  166. results = {}
  167. for module in test_modules:
  168. modules[module] = 1
  169. run(env, modules)
  170. results[module] = analyse_memory(".pioenvs/%s/firmware.elf" % env)
  171. results[module]['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
  172. modules[module] = 0
  173. print(output_format.format(
  174. module,
  175. results[module]['text'] - base['text'],
  176. results[module]['data'] - base['data'],
  177. results[module]['rodata'] - base['rodata'],
  178. results[module]['bss'] - base['bss'],
  179. results[module]['irom0_text'] - base['irom0_text'],
  180. results[module]['size'] - base['size'],
  181. ))
  182. # Test all modules
  183. if len(test_modules) > 0:
  184. for module in test_modules:
  185. modules[module] = 1
  186. run(env, modules)
  187. total = analyse_memory(".pioenvs/%s/firmware.elf" % env)
  188. total['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
  189. if len(test_modules) > 1:
  190. print(output_format.format(
  191. "ALL MODULES",
  192. total['text'] - base['text'],
  193. total['data'] - base['data'],
  194. total['rodata'] - base['rodata'],
  195. total['bss'] - base['bss'],
  196. total['irom0_text'] - base['irom0_text'],
  197. total['size'] - base['size'],
  198. ))
  199. print(output_format.format(
  200. "TOTAL",
  201. total['text'],
  202. total['data'],
  203. total['rodata'],
  204. total['bss'],
  205. total['irom0_text'],
  206. total['size'],
  207. ))
  208. except:
  209. raise
  210. subprocess.check_call("export ESPURNA_BOARD=\"\"; export ESPURNA_FLAGS=\"\"", shell=True)
  211. print("\n")