Skip to content

Commit

Permalink
Table output for segment size script (#8551)
Browse files Browse the repository at this point in the history
* Table output for segment size script
Also include maximum aka total for every segment key
Re-format the file and clean-up the resulting data dict
* revert to line output
* used, percentage
* unicodes
* shorter desc, headers
  • Loading branch information
mcspr committed May 11, 2022
1 parent 8d5dda0 commit da4a19f
Showing 1 changed file with 141 additions and 59 deletions.
200 changes: 141 additions & 59 deletions tools/sizes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,74 +17,156 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from __future__ import print_function
import argparse
import os
import subprocess
import sys

def get_segment_hints(iram):
hints = {}
hints['ICACHE'] = ' - flash instruction cache'
hints['IROM'] = ' - code in flash (default or ICACHE_FLASH_ATTR)'
hints['IRAM'] = ' / ' + str(iram) + ' - code in IRAM (IRAM_ATTR, ISRs...)'
hints['DATA'] = ') - initialized variables (global, static) in RAM/HEAP'
hints['RODATA'] = ') / 81920 - constants (global, static) in RAM/HEAP'
hints['BSS'] = ') - zeroed variables (global, static) in RAM/HEAP'
return hints

def get_segment_sizes(elf, path):
sizes = {}
sizes['ICACHE'] = 0
sizes['IROM'] = 0
sizes['IRAM'] = 0
sizes['DATA'] = 0
sizes['RODATA'] = 0
sizes['BSS'] = 0
p = subprocess.Popen([path + "/xtensa-lx106-elf-size", '-A', elf], stdout=subprocess.PIPE, universal_newlines=True )
lines = p.stdout.readlines()
for line in lines:
words = line.split()
if line.startswith('.irom0.text'):
sizes['IROM'] = sizes['IROM'] + int(words[1])
elif line.startswith('.text'): # Gets .text and .text1
sizes['IRAM'] = sizes['IRAM'] + int(words[1])
elif line.startswith('.data'): # Gets .text and .text1
sizes['DATA'] = sizes['DATA'] + int(words[1])
elif line.startswith('.rodata'): # Gets .text and .text1
sizes['RODATA'] = sizes['RODATA'] + int(words[1])
elif line.startswith('.bss'): # Gets .text and .text1
sizes['BSS'] = sizes['BSS'] + int(words[1])


def get_segment_sizes(elf, path, mmu):
iram_size = 0
iheap_size = 0
icache_size = 32168

for line in mmu.split():
words = line.split("=")
if line.startswith("-DMMU_IRAM_SIZE"):
iram_size = int(words[1], 16)
elif line.startswith("-DMMU_ICACHE_SIZE"):
icache_size = int(words[1], 16)
elif line.startswith("-DMMU_SEC_HEAP_SIZE"):
iheap_size = int(words[1], 16)

sizes = [
[
"Variables and constants in RAM (global, static)",
[
{
"DATA": 0,
"RODATA": 0,
"BSS": 0,
},
80192,
],
],
[
"Instruction RAM (IRAM_ATTR, ICACHE_RAM_ATTR)",
[
{
"ICACHE": icache_size,
"IHEAP": iheap_size,
"IRAM": 0,
},
65536,
],
],
["Code in flash (default, ICACHE_FLASH_ATTR)", [{"IROM": 0}, 1048576]],
]

section_mapping = (
(".irom0.text", "IROM"),
(".text", "IRAM"),
(".data", "DATA"),
(".rodata", "RODATA"),
(".bss", "BSS"),
)

cmd = [os.path.join(path, "xtensa-lx106-elf-size"), "-A", elf]
with subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) as proc:
lines = proc.stdout.readlines()
for line in lines:
words = line.split()
for section, target in section_mapping:
if not line.startswith(section):
continue
for group, (segments, total) in sizes:
if target in segments:
segments[target] += int(words[1])
assert segments[target] <= total

return sizes

def get_mmu_sizes(mmu, sizes):
iram = 0x8000
sizes['ICACHE'] = 0x8000
lines = mmu.split(' ')
for line in lines:
words = line.split('=')
if line.startswith('-DMMU_IRAM_SIZE'):
iram = int(words[1], 16)
elif line.startswith('-DMMU_ICACHE_SIZE'):
sizes['ICACHE'] = int(words[1], 16)
return [iram, sizes]

def percentage(lhs, rhs):
return "{}%".format(int(100.0 * float(lhs) / float(rhs)))


HINTS = {
"ICACHE": "reserved space for flash instruction cache",
"IRAM": "code in IRAM",
"IHEAP": "secondary heap space",
"IROM": "code in flash",
"DATA": "initialized variables",
"RODATA": "constants",
"BSS": "zeroed variables",
}


def safe_prefix(n, length):
if n == length:
return "`--"

return "|--"


def prefix(n, length):
if n == length:
return "└──"

return "├──"


def filter_segments(segments):
used = 0
number = 0
available = []

for (segment, size) in segments.items():
if not size:
continue
used += size
number += 1
available.append((number, segment, size))

return (number, used, available)


def main():
parser = argparse.ArgumentParser(description='Report the different segment sizes of a compiled ELF file')
parser.add_argument('-e', '--elf', action='store', required=True, help='Path to the Arduino sketch ELF')
parser.add_argument('-p', '--path', action='store', required=True, help='Path to Xtensa toolchain binaries')
parser.add_argument('-i', '--mmu', action='store', required=False, help='MMU build options')
parser = argparse.ArgumentParser(
description="Report the different segment sizes of a compiled ELF file"
)
parser.add_argument(
"-e",
"--elf",
action="store",
required=True,
help="Path to the Arduino sketch ELF",
)
parser.add_argument(
"-p",
"--path",
action="store",
required=True,
help="Path to Xtensa toolchain binaries",
)
parser.add_argument(
"-i", "--mmu", action="store", required=False, help="MMU build options"
)

args = parser.parse_args()
sizes = get_segment_sizes(args.elf, args.path)
[iram, sizes] = get_mmu_sizes(args.mmu, sizes)
hints = get_segment_hints(iram)
sizes = get_segment_sizes(args.elf, args.path, args.mmu)

for group, (segments, total) in sizes:
number, used, segments = filter_segments(segments)

sys.stderr.write("Executable segment sizes:" + os.linesep)
for k in sizes.keys():
sys.stderr.write("%-7s: %-5d %s %s" % (k, sizes[k], hints[k], os.linesep))
print(f". {group:<8}, used {used} / {total} bytes ({percentage(used, total)})")
print("| SEGMENT BYTES DESCRIPTION")
for n, segment, size in segments:
try:
print(f"{prefix(n, number)} ", end="")
except UnicodeEncodeError:
print(f"{safe_prefix(n, number)} ", end="")
print(f"{segment:<8} {size:<8} {HINTS[segment]:<16}")

return 0

if __name__ == '__main__':
sys.exit(main())
if __name__ == "__main__":
main()

15 comments on commit da4a19f

@jjsuwa-sys3175
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, my Arduino IDE (on Win64) becomes no size output...

scrnshot

@mcspr
Copy link
Collaborator Author

@mcspr mcspr commented on da4a19f May 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jjsuwa-sys3175 see #8550
flash-agnostic PR erased those props, it was missed in the review since I use IDE pretty rarely.
but I do have boards.txt.py rewrite & fix, will update asap

@mcspr
Copy link
Collaborator Author

@mcspr mcspr commented on da4a19f May 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, nvm, will check...

@jjsuwa-sys3175
Copy link
Contributor

@jjsuwa-sys3175 jjsuwa-sys3175 commented on da4a19f May 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arduino IDE console log must be redirected to stderr.

  • print("...") -> sys.stderr.write("...\n")
  • print("...", end="") -> sys.stderr.write("...")

but another problem is unveil:
scrnshot
except UnicodeEncodeError: seem not to work as intended, because this is regarding about font system rather than text encoding i guess.

(note: font for Arduino IDE editor can be configured via preferences.txt, but console log cannot AFAIK)

@mcspr
Copy link
Collaborator Author

@mcspr mcspr commented on da4a19f May 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is weird, I wonder if there is a difference between 1.8.15 and 1.8.19?

This is portable .exe running on Windows 11. Mind, there might also be differences between different Windows versions? b/c that looks like Windows 7 / 8

@jjsuwa-sys3175
Copy link
Contributor

@jjsuwa-sys3175 jjsuwa-sys3175 commented on da4a19f May 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i use installer (*.exe) version of Arduino IDE 1.8.15 or 1.8.19 on Windows7 SP1 x64 Japanase NLV.
Both "English" and "System Default (Japanese)" @ prefs "Editor Language" are same result as shown above.

by the way, i don't see the last two lines, "Sketch uses N bytes..." and "Global variables use..." before i know it...
(the same applies to previous ver of sizes.py, but OK if back to 3.0.2)

sorry, that's what #8550 means

@mcspr
Copy link
Collaborator Author

@mcspr mcspr commented on da4a19f May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you check whether 2.0.0rc version display anything?
https://github.com/arduino/arduino-ide/releases

We could use dashes here, since I am not really sure how we'd check that these symbols can be rendered at all.
I have not checked anything with Win7 VM, though, and what are the rules for these box-drawing chars support (specifically ,, and ) and what fonts are installed
(or if it is something with the Japanese language support in Windows or Java distribution of the IDE)

@jjsuwa-sys3175
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scrnshot

stdout (not stderr) log capture works well in 2.0.0-rc6 but box-drawing char font not.

imho it should be avoided multi-platform coding depends on the specific display font property/availability, that is ultimate act of environment-dependent behavior...

@mcspr
Copy link
Collaborator Author

@mcspr mcspr commented on da4a19f May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bummer. it is true, but also yet another reason we can't have nice things :>

at least my understanding was it is a pretty old character set, which would've been expected to be included even in Win7 time.
and the fact there's no fallback to non-unicode output? why python encoding part works at all?
Win+r, charmap.exe does not have box-drawing chars at all?

@jjsuwa-sys3175
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the fact there's no fallback to non-unicode output? why python encoding part works at all?
Win+r, charmap.exe does not have box-drawing chars at all?

charcode encoding/decoding part and display font rendering part are independent each other.
charmap.exe, web browser (Firefox 100.1), Arduino editor pane and even notepad.exe render such chars well... but only Arduino log output pane cannot.

@mcspr
Copy link
Collaborator Author

@mcspr mcspr commented on da4a19f May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but, that is still the point, what can we do besides not printing those characters? my expectation we would see those characters at least substituted from some font on the system
maybe we want to ask Arduino IDE guys about it, then?

@jjsuwa-sys3175
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, my estimate was wrong and the true cause was found - by default, python for Windows uses ANSI code page (depends on user's locale) encoding for standard I/O via anon pipe.
fortunately, since python 3.7, -X utf8 enables UTF-8 Mode (PEP 540).

let's try to modify platform.txt line 151...

recipe.objcopy.hex.3.pattern="{runtime.tools.python3.path}/python3" -X utf8 -I "{runtime.tools.sizes}" --elf "{build.path}/{build.project_name}.elf" --path "{runtime.tools.xtensa-lx106-elf-gcc.path}/bin" --mmu "{build.mmuflags}"

scrnshot
bingo!

@mcspr
Copy link
Collaborator Author

@mcspr mcspr commented on da4a19f May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the remaining questions are

  • is it safe default for anything other than win (... and I think properties can be overloaded per OS by .windows suffix?
  • should we just replace Sketch uses... with our output? no more red-on-black text as well

@d-a-v
Copy link
Collaborator

@d-a-v d-a-v commented on da4a19f May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* is it safe default for anything other than win (... and I think properties can be overloaded per OS by `.windows` suffix?

Linux is OK

@jjsuwa-sys3175
Copy link
Contributor

@jjsuwa-sys3175 jjsuwa-sys3175 commented on da4a19f May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • imho almost linux distros uses utf-8 by default
  • Sketch uses... is by Arduino IDE's own, not urs

note: -X utf8 requires python 3.7+

Please sign in to comment.