Introduce "Program Memory Usage"

This commit is contained in:
Ivan Kravets
2018-06-04 14:09:48 +03:00
parent 2fb8128791
commit bfc94d36e3
5 changed files with 121 additions and 42 deletions

View File

@ -4,11 +4,16 @@ Release Notes
PlatformIO 3.0 PlatformIO 3.0
-------------- --------------
3.5.4 (2018-??-??) 3.6.0 (2018-??-??)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
* Check maximum allowed firmware size for programming/uploading using `platformio run --target checkprogsize <http://docs.platformio.org/en/latest/userguide/cmd_run.html#cmdoption-platformio-run-t>`__ * `Program Memory Usage <http://docs.platformio.org/en/latest/faq.html#program-memory-usage>`_
(`issue #1412 <https://github.com/platformio/platformio-core/issues/1412>`_)
- Print human-readable memory usage information after a build and before uploading
- Print detailed memory usage information with "sections" and "addresses"
in `verbose mode <http://docs.platformio.org/en/latest/userguide/cmd_run.html#cmdoption-platformio-run-v>`__
- Check maximum allowed "program" and "data" sizes before uploading/programming
(`issue #1412 <https://github.com/platformio/platformio-core/issues/1412>`_)
3.5.3 (2018-06-01) 3.5.3 (2018-06-01)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~

2
docs

Submodule docs updated: 91eae26417...22c4d0109e

View File

@ -20,7 +20,7 @@ from os.path import expanduser, join
from time import time from time import time
from SCons.Script import (ARGUMENTS, COMMAND_LINE_TARGETS, DEFAULT_TARGETS, from SCons.Script import (ARGUMENTS, COMMAND_LINE_TARGETS, DEFAULT_TARGETS,
Action, AllowSubstExceptions, AlwaysBuild, AllowSubstExceptions, AlwaysBuild, Default,
DefaultEnvironment, Variables) DefaultEnvironment, Variables)
from platformio import util from platformio import util
@ -164,15 +164,34 @@ for item in env.GetExtraScripts("pre"):
env.SConscript("$BUILD_SCRIPT") env.SConscript("$BUILD_SCRIPT")
AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS + ["size"]))
AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS + ["size"]))
if "UPLOAD_FLAGS" in env: if "UPLOAD_FLAGS" in env:
env.Prepend(UPLOADERFLAGS=["$UPLOAD_FLAGS"]) env.Prepend(UPLOADERFLAGS=["$UPLOAD_FLAGS"])
for item in env.GetExtraScripts("post"): for item in env.GetExtraScripts("post"):
env.SConscript(item, exports="env") env.SConscript(item, exports="env")
##############################################################################
# Checking program size
if env.get("SIZETOOL") and "nobuild" not in COMMAND_LINE_TARGETS:
env.Depends(["upload", "program"], "checkprogsize")
# Replace platform's "size" target with our
_new_targets = [t for t in DEFAULT_TARGETS if str(t) != "size"]
Default(None)
Default(_new_targets)
Default("checkprogsize")
# Print configured protocols
env.AddPreAction(
["upload", "program"],
env.VerboseAction(lambda source, target, env: env.PrintUploadInfo(),
"Configuring upload protocol..."))
AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS))
AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS))
##############################################################################
if "envdump" in COMMAND_LINE_TARGETS: if "envdump" in COMMAND_LINE_TARGETS:
print env.Dump() print env.Dump()
env.Exit(0) env.Exit(0)
@ -189,7 +208,3 @@ if "idedata" in COMMAND_LINE_TARGETS:
"See explanation in FAQ > Troubleshooting > Building\n" "See explanation in FAQ > Troubleshooting > Building\n"
"http://docs.platformio.org/page/faq.html\n\n") "http://docs.platformio.org/page/faq.html\n\n")
env.Exit(1) env.Exit(1)
env.AddPreAction(["upload", "program"],
Action(lambda source, target, env: env.PrintUploadInfo(),
"Configuring upload protocol..."))

View File

@ -14,6 +14,7 @@
from __future__ import absolute_import from __future__ import absolute_import
import re
import sys import sys
from fnmatch import fnmatch from fnmatch import fnmatch
from os import environ from os import environ
@ -21,11 +22,13 @@ from os.path import isfile, join
from shutil import copyfile from shutil import copyfile
from time import sleep from time import sleep
from SCons.Node.Alias import Alias from SCons.Script import ARGUMENTS
from serial import Serial, SerialException from serial import Serial, SerialException
from platformio import util from platformio import util
# pylint: disable=unused-argument
def FlushSerialBuffer(env, port): def FlushSerialBuffer(env, port):
s = Serial(env.subst(port)) s = Serial(env.subst(port))
@ -45,7 +48,7 @@ def TouchSerialPort(env, port, baudrate):
s = Serial(port=port, baudrate=baudrate) s = Serial(port=port, baudrate=baudrate)
s.setDTR(False) s.setDTR(False)
s.close() s.close()
except: # pylint: disable=W0702 except: # pylint: disable=bare-except
pass pass
sleep(0.4) # DO NOT REMOVE THAT (required by SAM-BA based boards) sleep(0.4) # DO NOT REMOVE THAT (required by SAM-BA based boards)
@ -88,7 +91,7 @@ def WaitForNewSerialPort(env, before):
return new_port return new_port
def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument def AutodetectUploadPort(*args, **kwargs):
env = args[0] env = args[0]
def _get_pattern(): def _get_pattern():
@ -173,7 +176,7 @@ def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument
env.Exit(1) env.Exit(1)
def UploadToDisk(_, target, source, env): # pylint: disable=W0613,W0621 def UploadToDisk(_, target, source, env):
assert "UPLOAD_PORT" in env assert "UPLOAD_PORT" in env
progname = env.subst("$PROGNAME") progname = env.subst("$PROGNAME")
for ext in ("bin", "hex"): for ext in ("bin", "hex"):
@ -186,32 +189,87 @@ def UploadToDisk(_, target, source, env): # pylint: disable=W0613,W0621
"(Some boards may require manual hard reset)" "(Some boards may require manual hard reset)"
def CheckUploadSize(_, target, source, env): # pylint: disable=W0613,W0621 def CheckUploadSize(_, target, source, env):
if "BOARD" not in env: check_conditions = [
return env.get("BOARD"),
max_size = int(env.BoardConfig().get("upload.maximum_size", 0)) env.get("SIZETOOL") or env.get("SIZECHECKCMD")
if max_size == 0 or "SIZETOOL" not in env:
return
sysenv = environ.copy()
sysenv['PATH'] = str(env['ENV']['PATH'])
cmd = [
env.subst("$SIZETOOL"), "-B",
str(source[0] if isinstance(target[0], Alias) else target[0])
] ]
result = util.exec_command(cmd, env=sysenv) if not all(check_conditions):
if result['returncode'] != 0: return
program_max_size = int(env.BoardConfig().get("upload.maximum_size", 0))
data_max_size = int(env.BoardConfig().get("upload.maximum_ram_size", 0))
if program_max_size == 0:
return return
print result['out'].strip()
line = result['out'].strip().splitlines()[1] def _configure_defaults():
values = [v.strip() for v in line.split("\t")] env.Replace(
used_size = int(values[0]) + int(values[1]) SIZECHECKCMD="$SIZETOOL -B -d $SOURCES",
SIZEPROGREGEXP=r"^(\d+)\s+(\d+)\s+\d+\s",
SIZEDATAREGEXP=r"^\d+\s+(\d+)\s+(\d+)\s+\d+")
if used_size > max_size: def _get_size_output():
cmd = env.get("SIZECHECKCMD")
if not cmd:
return None
if not isinstance(cmd, list):
cmd = cmd.split()
cmd = [arg.replace("$SOURCES", str(source[0])) for arg in cmd if arg]
sysenv = environ.copy()
sysenv['PATH'] = str(env['ENV']['PATH'])
result = util.exec_command(env.subst(cmd), env=sysenv)
if result['returncode'] != 0:
return None
return result['out'].strip()
def _calculate_size(output, pattern):
if not output or not pattern:
return -1
size = 0
regexp = re.compile(pattern)
for line in output.split("\n"):
line = line.strip()
if not line:
continue
match = regexp.search(line)
if not match:
continue
size += sum(int(value) for value in match.groups())
return size
def _format_availale_bytes(value, total):
percent_raw = float(value) / float(total)
blocks_per_progress = 10
used_blocks = int(round(blocks_per_progress * percent_raw))
if used_blocks > blocks_per_progress:
used_blocks = blocks_per_progress
return "[{:{}}] {: 6.1%} (used {:d} bytes from {:d} bytes)".format(
"=" * used_blocks, blocks_per_progress, percent_raw, value, total)
if not env.get("SIZECHECKCMD") and not env.get("SIZEPROGREGEXP"):
_configure_defaults()
output = _get_size_output()
program_size = _calculate_size(output, env.get("SIZEPROGREGEXP"))
data_size = _calculate_size(output, env.get("SIZEDATAREGEXP"))
print "Memory Usage -> http://bit.ly/pio-memory-usage"
if data_max_size and data_size > -1:
print "DATA: %s" % _format_availale_bytes(data_size, data_max_size)
if program_size > -1:
print "PROGRAM: %s" % _format_availale_bytes(program_size,
program_max_size)
if int(ARGUMENTS.get("PIOVERBOSE", 0)):
print output
# raise error
if data_max_size and data_size > data_max_size:
sys.stderr.write(
"Error: The data size (%d bytes) is greater "
"than maximum allowed (%s bytes)\n" % (data_size, data_max_size))
env.Exit(1)
if program_size > program_max_size:
sys.stderr.write("Error: The program size (%d bytes) is greater " sys.stderr.write("Error: The program size (%d bytes) is greater "
"than maximum allowed (%s bytes)\n" % (used_size, "than maximum allowed (%s bytes)\n" %
max_size)) (program_size, program_max_size))
env.Exit(1) env.Exit(1)

View File

@ -107,12 +107,13 @@ def BuildProgram(env):
program = env.Program( program = env.Program(
join("$BUILD_DIR", env.subst("$PROGNAME")), env['PIOBUILDFILES']) join("$BUILD_DIR", env.subst("$PROGNAME")), env['PIOBUILDFILES'])
env.Replace(PIOMAINPROG=program)
checksize_action = env.VerboseAction(env.CheckUploadSize, AlwaysBuild(
"Checking program size") env.Alias(
AlwaysBuild(env.Alias("checkprogsize", program, checksize_action)) "checkprogsize", program,
if set(["upload", "program"]) & set(COMMAND_LINE_TARGETS): env.VerboseAction(env.CheckUploadSize,
env.AddPostAction(program, checksize_action) "Checking size $PIOMAINPROG")))
return program return program