| #!/usr/bin/env python3 |
| # |
| # This file is part of the MicroPython project, http://micropython.org/ |
| # |
| # The MIT License (MIT) |
| # |
| # Copyright (c) 2020 Damien P. George |
| # |
| # Permission is hereby granted, free of charge, to any person obtaining a copy |
| # of this software and associated documentation files (the "Software"), to deal |
| # in the Software without restriction, including without limitation the rights |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| # copies of the Software, and to permit persons to whom the Software is |
| # furnished to do so, subject to the following conditions: |
| # |
| # The above copyright notice and this permission notice shall be included in |
| # all copies or substantial portions of the Software. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| # THE SOFTWARE. |
| |
| """ |
| This script is used to compute metrics, like code size, of the various ports. |
| |
| Typical usage is: |
| |
| $ ./tools/metrics.py build | tee size0 |
| <wait for build to complete> |
| $ git switch new-feature-branch |
| $ ./tools/metrics.py build | tee size1 |
| <wait for build to complete> |
| $ ./tools/metrics.py diff size0 size1 |
| |
| Other commands: |
| |
| $ ./tools/metrics.py sizes # print all firmware sizes |
| $ ./tools/metrics.py clean # clean all ports |
| |
| """ |
| |
| import sys, re, subprocess |
| |
| MAKE_FLAGS = ["-j3", "CFLAGS_EXTRA=-DNDEBUG"] |
| |
| |
| class PortData: |
| def __init__(self, name, dir, output, make_flags=None): |
| self.name = name |
| self.dir = dir |
| self.output = output |
| self.make_flags = make_flags |
| |
| |
| port_data = { |
| "b": PortData("bare-arm", "bare-arm", "build/firmware.elf"), |
| "m": PortData("minimal x86", "minimal", "build/firmware.elf"), |
| "u": PortData("unix x64", "unix", "micropython"), |
| "n": PortData("unix nanbox", "unix", "micropython-nanbox", "VARIANT=nanbox"), |
| "s": PortData("stm32", "stm32", "build-PYBV10/firmware.elf", "BOARD=PYBV10"), |
| "c": PortData("cc3200", "cc3200", "build/WIPY/release/application.axf", "BTARGET=application"), |
| "8": PortData("esp8266", "esp8266", "build-GENERIC/firmware.elf"), |
| "3": PortData("esp32", "esp32", "build-GENERIC/application.elf"), |
| "r": PortData("nrf", "nrf", "build-pca10040/firmware.elf"), |
| "d": PortData("samd", "samd", "build-ADAFRUIT_ITSYBITSY_M4_EXPRESS/firmware.elf"), |
| } |
| |
| |
| def syscmd(*args): |
| sys.stdout.flush() |
| a2 = [] |
| for a in args: |
| if isinstance(a, str): |
| a2.append(a) |
| elif a: |
| a2.extend(a) |
| subprocess.run(a2) |
| |
| |
| def parse_port_list(args): |
| if not args: |
| return list(port_data.values()) |
| else: |
| ports = [] |
| for arg in args: |
| for port_char in arg: |
| try: |
| ports.append(port_data[port_char]) |
| except KeyError: |
| print("unknown port:", port_char) |
| sys.exit(1) |
| return ports |
| |
| |
| def read_build_log(filename): |
| data = dict() |
| lines = [] |
| found_sizes = False |
| with open(filename) as f: |
| for line in f: |
| line = line.strip() |
| if line.strip() == "COMPUTING SIZES": |
| found_sizes = True |
| elif found_sizes: |
| lines.append(line) |
| is_size_line = False |
| for line in lines: |
| if is_size_line: |
| fields = line.split() |
| data[fields[-1]] = [int(f) for f in fields[:-2]] |
| is_size_line = False |
| else: |
| is_size_line = line.startswith("text\t ") |
| return data |
| |
| |
| def do_diff(args): |
| """Compute the difference between firmware sizes.""" |
| |
| if len(args) != 2: |
| print("usage: %s diff <out1> <out2>" % sys.argv[0]) |
| sys.exit(1) |
| |
| data1 = read_build_log(args[0]) |
| data2 = read_build_log(args[1]) |
| |
| for key, value1 in data1.items(): |
| value2 = data2[key] |
| for port in port_data.values(): |
| if key == "ports/{}/{}".format(port.dir, port.output): |
| name = port.name |
| break |
| data = [v2 - v1 for v1, v2 in zip(value1, value2)] |
| warn = "" |
| board = re.search(r"/build-([A-Za-z0-9_]+)/", key) |
| if board: |
| board = board.group(1) |
| else: |
| board = "" |
| if name == "cc3200": |
| delta = data[0] |
| percent = 100 * delta / value1[0] |
| if data[1] != 0: |
| warn += " %+u(data)" % data[1] |
| else: |
| delta = data[3] |
| percent = 100 * delta / value1[3] |
| if data[1] != 0: |
| warn += " %+u(data)" % data[1] |
| if data[2] != 0: |
| warn += " %+u(bss)" % data[2] |
| if warn: |
| warn = "[incl%s]" % warn |
| print("%11s: %+5u %+.3f%% %s%s" % (name, delta, percent, board, warn)) |
| |
| |
| def do_clean(args): |
| """Clean ports.""" |
| |
| ports = parse_port_list(args) |
| |
| print("CLEANING") |
| for port in ports: |
| syscmd("make", "-C", "ports/{}".format(port.dir), port.make_flags, "clean") |
| |
| |
| def do_build(args): |
| """Build ports and print firmware sizes.""" |
| |
| ports = parse_port_list(args) |
| |
| print("BUILDING MPY-CROSS") |
| syscmd("make", "-C", "mpy-cross", MAKE_FLAGS) |
| |
| print("BUILDING PORTS") |
| for port in ports: |
| syscmd("make", "-C", "ports/{}".format(port.dir), MAKE_FLAGS, port.make_flags) |
| |
| do_sizes(args) |
| |
| |
| def do_sizes(args): |
| """Compute and print sizes of firmware.""" |
| |
| ports = parse_port_list(args) |
| |
| print("COMPUTING SIZES") |
| for port in ports: |
| syscmd("size", "ports/{}/{}".format(port.dir, port.output)) |
| |
| |
| def main(): |
| # Get command to execute |
| if len(sys.argv) == 1: |
| print("Available commands:") |
| for cmd in globals(): |
| if cmd.startswith("do_"): |
| print(" {:9} {}".format(cmd[3:], globals()[cmd].__doc__)) |
| sys.exit(1) |
| cmd = sys.argv.pop(1) |
| |
| # Dispatch to desired command |
| try: |
| cmd = globals()["do_{}".format(cmd)] |
| except KeyError: |
| print("{}: unknown command '{}'".format(sys.argv[0], cmd)) |
| sys.exit(1) |
| cmd(sys.argv[1:]) |
| |
| |
| if __name__ == "__main__": |
| main() |