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.

250 lines
7.7 KiB

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