Merge pull request #4392 from MarceloSalazar/memap_enhancements

Enhance memap, and configure depth level
pull/4786/merge
Jimmy Brisson 2017-08-14 11:42:44 -05:00 committed by GitHub
commit 878557e267
7 changed files with 601 additions and 348 deletions

View File

@ -446,7 +446,7 @@ def build_project(src_paths, build_path, target, toolchain_name,
macros=None, inc_dirs=None, jobs=1, silent=False, macros=None, inc_dirs=None, jobs=1, silent=False,
report=None, properties=None, project_id=None, report=None, properties=None, project_id=None,
project_description=None, extra_verbose=False, config=None, project_description=None, extra_verbose=False, config=None,
app_config=None, build_profile=None): app_config=None, build_profile=None, stats_depth=None):
""" Build a project. A project may be a test or a user program. """ Build a project. A project may be a test or a user program.
Positional arguments: Positional arguments:
@ -475,6 +475,7 @@ def build_project(src_paths, build_path, target, toolchain_name,
config - a Config object to use instead of creating one config - a Config object to use instead of creating one
app_config - location of a chosen mbed_app.json file app_config - location of a chosen mbed_app.json file
build_profile - a dict of flags that will be passed to the compiler build_profile - a dict of flags that will be passed to the compiler
stats_depth - depth level for memap to display file/dirs
""" """
# Convert src_path to a list if needed # Convert src_path to a list if needed
@ -553,18 +554,18 @@ def build_project(src_paths, build_path, target, toolchain_name,
memap_table = '' memap_table = ''
if memap_instance: if memap_instance:
# Write output to stdout in text (pretty table) format # Write output to stdout in text (pretty table) format
memap_table = memap_instance.generate_output('table') memap_table = memap_instance.generate_output('table', stats_depth)
if not silent: if not silent:
print memap_table print memap_table
# Write output to file in JSON format # Write output to file in JSON format
map_out = join(build_path, name + "_map.json") map_out = join(build_path, name + "_map.json")
memap_instance.generate_output('json', map_out) memap_instance.generate_output('json', stats_depth, map_out)
# Write output to file in CSV format for the CI # Write output to file in CSV format for the CI
map_csv = join(build_path, name + "_map.csv") map_csv = join(build_path, name + "_map.csv")
memap_instance.generate_output('csv-ci', map_csv) memap_instance.generate_output('csv-ci', stats_depth, map_csv)
resources.detect_duplicates(toolchain) resources.detect_duplicates(toolchain)
@ -573,7 +574,7 @@ def build_project(src_paths, build_path, target, toolchain_name,
cur_result["elapsed_time"] = end - start cur_result["elapsed_time"] = end - start
cur_result["output"] = toolchain.get_output() + memap_table cur_result["output"] = toolchain.get_output() + memap_table
cur_result["result"] = "OK" cur_result["result"] = "OK"
cur_result["memory_usage"] = toolchain.map_outputs cur_result["memory_usage"] = memap_instance.mem_report
cur_result["bin"] = res cur_result["bin"] = res
cur_result["elf"] = splitext(res)[0] + ".elf" cur_result["elf"] = splitext(res)[0] + ".elf"
cur_result.update(toolchain.report) cur_result.update(toolchain.report)
@ -1323,7 +1324,7 @@ def print_build_memory_usage(report):
""" """
from prettytable import PrettyTable from prettytable import PrettyTable
columns_text = ['name', 'target', 'toolchain'] columns_text = ['name', 'target', 'toolchain']
columns_int = ['static_ram', 'stack', 'heap', 'total_ram', 'total_flash'] columns_int = ['static_ram', 'total_flash']
table = PrettyTable(columns_text + columns_int) table = PrettyTable(columns_text + columns_int)
for col in columns_text: for col in columns_text:
@ -1350,10 +1351,6 @@ def print_build_memory_usage(report):
record['toolchain_name'], record['toolchain_name'],
record['memory_usage'][-1]['summary'][ record['memory_usage'][-1]['summary'][
'static_ram'], 'static_ram'],
record['memory_usage'][-1]['summary']['stack'],
record['memory_usage'][-1]['summary']['heap'],
record['memory_usage'][-1]['summary'][
'total_ram'],
record['memory_usage'][-1]['summary'][ record['memory_usage'][-1]['summary'][
'total_flash'], 'total_flash'],
] ]

View File

@ -58,40 +58,47 @@ if __name__ == '__main__':
# Parse Options # Parse Options
parser = get_default_options_parser(add_app_config=True) parser = get_default_options_parser(add_app_config=True)
group = parser.add_mutually_exclusive_group(required=False) group = parser.add_mutually_exclusive_group(required=False)
group.add_argument("-p", group.add_argument(
"-p",
type=argparse_many(test_known), type=argparse_many(test_known),
dest="program", dest="program",
help="The index of the desired test program: [0-%d]" % (len(TESTS)-1)) help="The index of the desired test program: [0-%d]" % (len(TESTS)-1))
group.add_argument("-n", group.add_argument(
"-n",
type=argparse_many(test_name_known), type=argparse_many(test_name_known),
dest="program", dest="program",
help="The name of the desired test program") help="The name of the desired test program")
parser.add_argument("-j", "--jobs", parser.add_argument(
"-j", "--jobs",
type=int, type=int,
dest="jobs", dest="jobs",
default=0, default=0,
help="Number of concurrent jobs. Default: 0/auto (based on host machine's number of CPUs)") help="Number of concurrent jobs. Default: 0/auto (based on host machine's number of CPUs)")
parser.add_argument("-v", "--verbose", parser.add_argument(
"-v", "--verbose",
action="store_true", action="store_true",
dest="verbose", dest="verbose",
default=False, default=False,
help="Verbose diagnostic output") help="Verbose diagnostic output")
parser.add_argument("--silent", parser.add_argument(
"--silent",
action="store_true", action="store_true",
dest="silent", dest="silent",
default=False, default=False,
help="Silent diagnostic output (no copy, compile notification)") help="Silent diagnostic output (no copy, compile notification)")
parser.add_argument("-D", parser.add_argument(
"-D",
action="append", action="append",
dest="macros", dest="macros",
help="Add a macro definition") help="Add a macro definition")
group.add_argument("-S", "--supported-toolchains", group.add_argument(
"-S", "--supported-toolchains",
dest="supported_toolchains", dest="supported_toolchains",
default=False, default=False,
const="matrix", const="matrix",
@ -99,11 +106,19 @@ if __name__ == '__main__':
nargs="?", nargs="?",
help="Displays supported matrix of MCUs and toolchains") help="Displays supported matrix of MCUs and toolchains")
parser.add_argument('-f', '--filter', parser.add_argument(
'-f', '--filter',
dest='general_filter_regex', dest='general_filter_regex',
default=None, default=None,
help='For some commands you can use filter to filter out results') help='For some commands you can use filter to filter out results')
parser.add_argument(
"--stats-depth",
type=int,
dest="stats_depth",
default=2,
help="Depth level for static memory report")
# Local run # Local run
parser.add_argument("--automated", action="store_true", dest="automated", parser.add_argument("--automated", action="store_true", dest="automated",
default=False, help="Automated test") default=False, help="Automated test")
@ -277,7 +292,8 @@ if __name__ == '__main__':
inc_dirs=[dirname(MBED_LIBRARIES)], inc_dirs=[dirname(MBED_LIBRARIES)],
build_profile=extract_profile(parser, build_profile=extract_profile(parser,
options, options,
toolchain)) toolchain),
stats_depth=options.stats_depth)
print 'Image: %s'% bin_file print 'Image: %s'% bin_file
if options.disk: if options.disk:

View File

@ -8,13 +8,12 @@ import re
import csv import csv
import json import json
import argparse import argparse
from copy import deepcopy
from prettytable import PrettyTable from prettytable import PrettyTable
from utils import argparse_filestring_type, \ from utils import argparse_filestring_type, \
argparse_lowercase_hyphen_type, argparse_uppercase_type argparse_lowercase_hyphen_type, argparse_uppercase_type
DEBUG = False
RE_ARMCC = re.compile( RE_ARMCC = re.compile(
r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$') r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$')
RE_IAR = re.compile( RE_IAR = re.compile(
@ -38,22 +37,18 @@ class MemapParser(object):
# sections to print info (generic for all toolchains) # sections to print info (generic for all toolchains)
sections = ('.text', '.data', '.bss', '.heap', '.stack') sections = ('.text', '.data', '.bss', '.heap', '.stack')
def __init__(self, detailed_misc=False): def __init__(self):
""" General initialization """ General initialization
""" """
#
self.detailed_misc = detailed_misc
# list of all modules and their sections # list of all modules and their sections
self.modules = dict() self.modules = dict() # full list - doesn't change with depth
self.short_modules = dict() # short version with specific depth
# sections must be defined in this order to take irrelevant out # sections must be defined in this order to take irrelevant out
self.all_sections = self.sections + self.other_sections + \ self.all_sections = self.sections + self.other_sections + \
self.misc_flash_sections + ('unknown', 'OUTPUT') self.misc_flash_sections + ('unknown', 'OUTPUT')
# list of all object files and mappting to module names
self.object_to_module = dict()
# Memory report (sections + summary) # Memory report (sections + summary)
self.mem_report = [] self.mem_report = []
@ -62,23 +57,68 @@ class MemapParser(object):
self.subtotal = dict() self.subtotal = dict()
def module_add(self, module_name, size, section): self.misc_flash_mem = 0
def remove_unused_modules(self):
""" Removes modules/objects that were compiled but are not used
"""
# Using keys to be able to remove entry
for i in self.modules.keys():
size = 0
for k in self.print_sections:
size += self.modules[i][k]
if size == 0:
del self.modules[i]
def module_init(self, object_name):
""" Initialize a module. Just adds the name of the module
Positional arguments:
object_name - name of the entry to add
"""
if object_name not in self.modules:
temp_dic = dict()
for section_idx in self.all_sections:
temp_dic[section_idx] = 0
self.modules[object_name] = temp_dic
def module_add(self, object_name, size, section):
""" Adds a module / section to the list """ Adds a module / section to the list
Positional arguments: Positional arguments:
module_name - name of the module to add object_name - name of the entry to add
size - the size of the module being added size - the size of the module being added
section - the section the module contributes to section - the section the module contributes to
""" """
if module_name in self.modules: # Check if object is a sub-string of key
self.modules[module_name][section] += size for module_path in self.modules:
else:
temp_dic = dict() # this is required to differenciate: main.o vs xxxmain.o
module_split = os.path.basename(module_path)
obj_split = os.path.basename(object_name)
if module_split == obj_split:
self.modules[module_path][section] += size
return
new_module = dict()
for section_idx in self.all_sections: for section_idx in self.all_sections:
temp_dic[section_idx] = 0 new_module[section_idx] = 0
temp_dic[section] = size new_module[section] = size
self.modules[module_name] = temp_dic self.modules[object_name] = new_module
def module_replace(self, old_object, new_object):
""" Replaces an object name with a new one
"""
# Check if object is a sub-string of key
if old_object in self.modules:
self.modules[new_object] = self.modules[old_object]
del self.modules[old_object]
def check_new_section_gcc(self, line): def check_new_section_gcc(self, line):
""" Check whether a new section in a map file has been detected (only """ Check whether a new section in a map file has been detected (only
@ -99,43 +139,41 @@ class MemapParser(object):
return False # everything else, means no change in section return False # everything else, means no change in section
def path_object_to_module_name(self, txt): def parse_object_name_gcc(self, line):
""" Parse a path to object file to extract it's module and object data """ Parse a path to object file
Positional arguments: Positional arguments:
txt - the path to parse the object and module name from txt - the path to parse the object and module name from
""" """
txt = txt.replace('\\', '/') line = line.replace('\\', '/')
rex_mbed_os_name = r'^.+mbed-os\/(.+)\/(.+\.o)$' RE_OBJECT_FILE = r'^.+\/(.+\.o)$'
test_rex_mbed_os_name = re.match(rex_mbed_os_name, txt) test_re_mbed_os_name = re.match(RE_OBJECT_FILE, line)
if test_rex_mbed_os_name: if test_re_mbed_os_name:
object_name = test_rex_mbed_os_name.group(2) object_name = test_re_mbed_os_name.group(1)
data = test_rex_mbed_os_name.group(1).split('/')
ndata = len(data) # corner case: certain objects are provided by the GCC toolchain
if 'arm-none-eabi' in line:
object_name = '[lib]/misc/' + object_name
return object_name
if ndata == 1:
module_name = data[0]
else: else:
module_name = data[0] + '/' + data[1]
if self.detailed_misc: RE_LIBRARY_OBJECT_FILE = r'^.+\/(lib.+\.a)\((.+\.o)\)$'
return [module_name + '/' + object_name, object_name] test_re_obj_name = re.match(RE_LIBRARY_OBJECT_FILE, line)
if test_re_obj_name:
object_name = test_re_obj_name.group(1) + '/' + \
test_re_obj_name.group(2)
return '[lib]/' + object_name
else: else:
return [module_name, object_name] print "Malformed input found when parsing GCC map: %s" % line
return '[misc]'
elif self.detailed_misc:
rex_obj_name = r'^.+\/(.+\.o\)*)$'
test_rex_obj_name = re.match(rex_obj_name, txt)
if test_rex_obj_name:
object_name = test_rex_obj_name.group(1)
return ['Misc/' + object_name, ""]
return ['Misc', ""]
else:
return ['Misc', ""]
def parse_section_gcc(self, line): def parse_section_gcc(self, line):
""" Parse data from a section of gcc map file """ Parse data from a section of gcc map file
@ -147,37 +185,42 @@ class MemapParser(object):
Positional arguments: Positional arguments:
line - the line to parse a section from line - the line to parse a section from
""" """
rex_address_len_name = re.compile(
RE_STD_SECTION_GCC = re.compile(
r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$') r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$')
test_address_len_name = re.match(rex_address_len_name, line) test_address_len_name = re.match(RE_STD_SECTION_GCC, line)
if test_address_len_name: if test_address_len_name:
if int(test_address_len_name.group(2), 16) == 0: # size == 0 if int(test_address_len_name.group(2), 16) == 0: # size == 0
return ["", 0] # no valid entry return ["", 0] # no valid entry
else: else:
m_name, _ = self.path_object_to_module_name( o_name = self.parse_object_name_gcc(\
test_address_len_name.group(3)) test_address_len_name.group(3))
m_size = int(test_address_len_name.group(2), 16) o_size = int(test_address_len_name.group(2), 16)
return [m_name, m_size]
return [o_name, o_size]
else: # special corner case for *fill* sections else: # special corner case for *fill* sections
# example # example
# *fill* 0x0000abe4 0x4 # *fill* 0x0000abe4 0x4
rex_address_len = r'^\s+\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$'
test_address_len = re.match(rex_address_len, line) RE_FILL_SECTION_GCC = r'^\s+\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$'
test_address_len = re.match(RE_FILL_SECTION_GCC, line)
if test_address_len: if test_address_len:
if int(test_address_len.group(2), 16) == 0: # size == 0 if int(test_address_len.group(2), 16) == 0: # size == 0
return ["", 0] # no valid entry return ["", 0] # no valid entry
else: else:
m_name = 'Fill' o_name = '[fill]'
m_size = int(test_address_len.group(2), 16) o_size = int(test_address_len.group(2), 16)
return [m_name, m_size] return [o_name, o_size]
else: else:
return ["", 0] # no valid entry return ["", 0] # no valid entry
def parse_map_file_gcc(self, file_desc): def parse_map_file_gcc(self, file_desc):
""" Main logic to decode gcc map files """ Main logic to decode gcc map files
@ -205,18 +248,40 @@ class MemapParser(object):
elif change_section != False: elif change_section != False:
current_section = change_section current_section = change_section
[module_name, module_size] = self.parse_section_gcc(line) [object_name, object_size] = self.parse_section_gcc(line)
if module_size == 0 or module_name == "": if object_size == 0 or object_name == "":
pass pass
else: else:
self.module_add(module_name, module_size, current_section) self.module_add(object_name, object_size,\
current_section)
def parse_object_name_armcc(self, line):
""" Parse object file
Positional arguments:
line - the line containing the object or library
"""
# simple object (not library)
if line[-2] == '.' and line[-1] == 'o':
return line
else:
RE_OBJECT_ARMCC = r'(.+\.l)\((.+\.o)\)'
test_re_obj_name = re.match(RE_OBJECT_ARMCC, line)
if test_re_obj_name:
object_name = test_re_obj_name.group(1) + '/' + \
test_re_obj_name.group(2)
return '[lib]/' + object_name
else:
print "Malformed input found when parsing ARMCC map: %s" % line
return '[misc]'
if DEBUG:
print "Line: %s" % line,
print "Module: %s\tSection: %s\tSize: %s" % \
(module_name, current_section, module_size)
raw_input("----------")
def parse_section_armcc(self, line): def parse_section_armcc(self, line):
""" Parse data from an armcc map file """ Parse data from an armcc map file
@ -230,34 +295,47 @@ class MemapParser(object):
line - the line to parse the section data from line - the line to parse the section data from
""" """
test_rex_armcc = re.match(RE_ARMCC, line) test_re_armcc = re.match(RE_ARMCC, line)
if test_rex_armcc: if test_re_armcc:
size = int(test_rex_armcc.group(2), 16) size = int(test_re_armcc.group(2), 16)
if test_rex_armcc.group(4) == 'RO': if test_re_armcc.group(4) == 'RO':
section = '.text' section = '.text'
else: else:
if test_rex_armcc.group(3) == 'Data': if test_re_armcc.group(3) == 'Data':
section = '.data' section = '.data'
elif test_rex_armcc.group(3) == 'Zero': elif test_re_armcc.group(3) == 'Zero':
section = '.bss' section = '.bss'
else: else:
print "BUG armcc map parser" print "Malformed input found when parsing armcc map: %s" %\
raw_input() line
# lookup object in dictionary and return module name # check name of object or library
object_name = test_rex_armcc.group(6) object_name = self.parse_object_name_armcc(\
if object_name in self.object_to_module: test_re_armcc.group(6))
module_name = self.object_to_module[object_name]
else:
module_name = 'Misc'
return [module_name, size, section] return [object_name, size, section]
else: else:
return ["", 0, ""] # no valid entry return ["", 0, ""]
def parse_object_name_iar(self, line):
""" Parse object file
Positional arguments:
line - the line containing the object or library
"""
# simple object (not library)
if line[-2] == '.' and line[-1] == 'o':
object_name = line
return object_name
else:
return '[misc]'
def parse_section_iar(self, line): def parse_section_iar(self, line):
""" Parse data from an IAR map file """ Parse data from an IAR map file
@ -277,38 +355,34 @@ class MemapParser(object):
line - the line to parse section data from line - the line to parse section data from
""" """
test_rex_iar = re.match(RE_IAR, line) test_re_iar = re.match(RE_IAR, line)
if test_rex_iar: if test_re_iar:
size = int(test_rex_iar.group(4), 16) size = int(test_re_iar.group(4), 16)
if test_rex_iar.group(2) == 'const' or \ if test_re_iar.group(2) == 'const' or \
test_rex_iar.group(2) == 'ro code': test_re_iar.group(2) == 'ro code':
section = '.text' section = '.text'
elif test_rex_iar.group(2) == 'zero' or \ elif test_re_iar.group(2) == 'zero' or \
test_rex_iar.group(2) == 'uninit': test_re_iar.group(2) == 'uninit':
if test_rex_iar.group(1)[0:4] == 'HEAP': if test_re_iar.group(1)[0:4] == 'HEAP':
section = '.heap' section = '.heap'
elif test_rex_iar.group(1)[0:6] == 'CSTACK': elif test_re_iar.group(1)[0:6] == 'CSTACK':
section = '.stack' section = '.stack'
else: else:
section = '.bss' # default section section = '.bss' # default section
elif test_rex_iar.group(2) == 'inited': elif test_re_iar.group(2) == 'inited':
section = '.data' section = '.data'
else: else:
print "BUG IAR map parser" print "Malformed input found when parsing IAR map: %s" % line
raw_input()
# lookup object in dictionary and return module name # lookup object in dictionary and return module name
object_name = test_rex_iar.group(5) temp = test_re_iar.group(5)
if object_name in self.object_to_module: object_name = self.parse_object_name_iar(temp)
module_name = self.object_to_module[object_name]
else:
module_name = 'Misc'
return [module_name, size, section] return [object_name, size, section]
else: else:
return ["", 0, ""] # no valid entry return ["", 0, ""] # no valid entry
@ -330,12 +404,51 @@ class MemapParser(object):
# Start decoding the map file # Start decoding the map file
for line in infile: for line in infile:
[name, size, section] = self.parse_section_armcc(line) [object_name, object_size, section] = \
self.parse_section_armcc(line)
if size == 0 or name == "" or section == "": if object_size == 0 or object_name == "" or section == "":
pass pass
else: else:
self.module_add(name, size, section) self.module_add(object_name, object_size, section)
def check_new_library_iar(self, line):
"""
Searches for libraries and returns name. Example:
m7M_tls.a: [43]
"""
RE_LIBRARY_IAR = re.compile(r'^(.+\.a)\:.+$')
test_address_line = re.match(RE_LIBRARY_IAR, line)
if test_address_line:
return test_address_line.group(1)
else:
return ""
def check_new_object_lib_iar(self, line):
"""
Searches for objects within a library section and returns name. Example:
rt7M_tl.a: [44]
ABImemclr4.o 6
ABImemcpy_unaligned.o 118
ABImemset48.o 50
I64DivMod.o 238
I64DivZer.o 2
"""
RE_OBJECT_LIBRARY_IAR = re.compile(r'^\s+(.+\.o)\s.*')
test_address_line = re.match(RE_OBJECT_LIBRARY_IAR, line)
if test_address_line:
return test_address_line.group(1)
else:
return ""
def parse_map_file_iar(self, file_desc): def parse_map_file_iar(self, file_desc):
""" Main logic to decode IAR map files """ Main logic to decode IAR map files
@ -344,8 +457,8 @@ class MemapParser(object):
file_desc - a file like object to parse as an IAR map file file_desc - a file like object to parse as an IAR map file
""" """
# first round, search for objects
with file_desc as infile: with file_desc as infile:
# Search area to parse # Search area to parse
for line in infile: for line in infile:
if line.startswith(' Section '): if line.startswith(' Section '):
@ -361,45 +474,142 @@ class MemapParser(object):
else: else:
self.module_add(name, size, section) self.module_add(name, size, section)
def search_objects(self, path): if line.startswith('*** MODULE SUMMARY'): # finish section
""" Searches for object files and creates mapping: object --> module break
# Start decoding the map file
current_library = ""
for line in infile:
library = self.check_new_library_iar(line)
if library != "":
current_library = library
object_name = self.check_new_object_lib_iar(line)
if object_name != "" and current_library != "":
temp = '[lib]' + '/'+ current_library + '/'+ object_name
self.module_replace(object_name, temp)
export_formats = ["json", "csv-ci", "table"]
def list_dir_obj(self, path):
""" Searches all objects in BUILD directory and creates list
Positional arguments: Positional arguments:
path - the path to an object file path - the path to a map file
""" """
path = path.replace('\\', '/') path = path.replace('\\', '/')
# check location of map file # check location of map file
rex = r'^(.+)' + r'\/(.+\.map)$' RE_PATH_MAP_FILE = r'^(.+)\/.+\.map$'
test_rex = re.match(rex, path) test_re = re.match(RE_PATH_MAP_FILE, path)
if test_rex: if test_re:
search_path = test_rex.group(1) + '/mbed-os/' search_path = test_re.group(1)
else: else:
print "Warning: this doesn't look like an mbed project" print "Warning: this doesn't look like an mbed project"
return return
# create empty disctionary
self.modules = dict()
# search for object files
for root, _, obj_files in os.walk(search_path): for root, _, obj_files in os.walk(search_path):
for obj_file in obj_files: for obj_file in obj_files:
if obj_file.endswith(".o"): if obj_file.endswith(".o"):
module_name, object_name = self.path_object_to_module_name(
os.path.join(root, obj_file))
if object_name in self.object_to_module: txt = os.path.join(root, obj_file)
if DEBUG:
print "WARNING: multiple usages of object file: %s"\ txt = txt.replace('\\', '/')
% object_name
print " Current: %s" % \ # add relative path + object to list
self.object_to_module[object_name] self.module_init(txt[len(search_path)+1:])
print " New: %s" % module_name
print " " # The code below is a special case for TESTS.
else: # mbed-os lives in a separate location and we need to explicitly search
self.object_to_module.update({object_name:module_name}) # their object files skiping the TESTS folder (already scanned above)
# check location of mbed-os
RE_PATH_MAP_FILE = r'^(.+)\/mbed-os\/.*TESTS\/.+\.map$'
test_re = re.match(RE_PATH_MAP_FILE, path)
if test_re == None:
return
search_path = test_re.group(1)
# search for object files
for root, _, obj_files in os.walk(search_path):
for obj_file in obj_files:
if 'TESTS' not in root and obj_file.endswith(".o"):
txt = os.path.join(root, obj_file)
txt = txt.replace('\\', '/')
# add relative path + object to list
self.module_init(txt[len(search_path)+1:])
def reduce_depth(self, depth):
"""
prints list of directories and objects. Examples:
(1) depth = 1:
main.o
mbed-os
(2) depth = 2:
main.o
mbed-os/test.o
mbed-os/drivers
"""
# depth 0 or None shows all entries
if depth == 0 or depth == None:
self.short_modules = deepcopy(self.modules)
return
self.short_modules = dict()
# create reduced list
for line in self.modules:
data = line.split('/')
ndir = len(data)
temp = ''
count = 0
# iterate until the max depth level
max_level = min(depth, ndir)
# rebuild the path based on depth level
while count < max_level:
if count > 0: # ignore '/' from first entry
temp = temp + '/'
temp = temp + data[count]
count += 1
if temp not in self.short_modules:
temp_dic = dict()
for section_idx in self.all_sections:
temp_dic[section_idx] = 0
self.short_modules[temp] = temp_dic
for section_idx in self.all_sections:
self.short_modules[temp][section_idx] += \
self.modules[line][section_idx]
export_formats = ["json", "csv-ci", "table"] export_formats = ["json", "csv-ci", "table"]
def generate_output(self, export_format, file_output=None): def generate_output(self, export_format, depth, file_output=None):
""" Generates summary of memory map data """ Generates summary of memory map data
Positional arguments: Positional arguments:
@ -407,10 +617,14 @@ class MemapParser(object):
Keyword arguments: Keyword arguments:
file_desc - descriptor (either stdout or file) file_desc - descriptor (either stdout or file)
depth - directory depth on report
Returns: generated string for the 'table' format, otherwise None Returns: generated string for the 'table' format, otherwise None
""" """
self.reduce_depth(depth)
self.compute_report()
try: try:
if file_output: if file_output:
file_desc = open(file_output, 'wb') file_desc = open(file_output, 'wb')
@ -452,29 +666,14 @@ class MemapParser(object):
csv_module_section = [] csv_module_section = []
csv_sizes = [] csv_sizes = []
for i in sorted(self.modules): for i in sorted(self.short_modules):
for k in self.print_sections: for k in self.print_sections:
csv_module_section += [i+k] csv_module_section += [i+k]
csv_sizes += [self.modules[i][k]] csv_sizes += [self.short_modules[i][k]]
csv_module_section += ['static_ram'] csv_module_section += ['static_ram']
csv_sizes += [self.mem_summary['static_ram']] csv_sizes += [self.mem_summary['static_ram']]
csv_module_section += ['heap']
if self.mem_summary['heap'] == 0:
csv_sizes += ['unknown']
else:
csv_sizes += [self.mem_summary['heap']]
csv_module_section += ['stack']
if self.mem_summary['stack'] == 0:
csv_sizes += ['unknown']
else:
csv_sizes += [self.mem_summary['stack']]
csv_module_section += ['total_ram']
csv_sizes += [self.mem_summary['total_ram']]
csv_module_section += ['total_flash'] csv_module_section += ['total_flash']
csv_sizes += [self.mem_summary['total_flash']] csv_sizes += [self.mem_summary['total_flash']]
@ -486,9 +685,6 @@ class MemapParser(object):
def generate_table(self, file_desc): def generate_table(self, file_desc):
"""Generate a table from a memoy map """Generate a table from a memoy map
Positional arguments:
file_desc - the file to write out the final report to
Returns: string of the generated table Returns: string of the generated table
""" """
# Create table # Create table
@ -503,11 +699,11 @@ class MemapParser(object):
for i in list(self.print_sections): for i in list(self.print_sections):
table.align[i] = 'r' table.align[i] = 'r'
for i in sorted(self.modules): for i in sorted(self.short_modules):
row = [i] row = [i]
for k in self.print_sections: for k in self.print_sections:
row.append(self.modules[i][k]) row.append(self.short_modules[i][k])
table.add_row(row) table.add_row(row)
@ -520,23 +716,9 @@ class MemapParser(object):
output = table.get_string() output = table.get_string()
output += '\n' output += '\n'
if self.mem_summary['heap'] == 0:
output += "Allocated Heap: unknown\n"
else:
output += "Allocated Heap: %s bytes\n" % \
str(self.mem_summary['heap'])
if self.mem_summary['stack'] == 0:
output += "Allocated Stack: unknown\n"
else:
output += "Allocated Stack: %s bytes\n" % \
str(self.mem_summary['stack'])
output += "Total Static RAM memory (data + bss): %s bytes\n" % \ output += "Total Static RAM memory (data + bss): %s bytes\n" % \
str(self.mem_summary['static_ram']) str(self.mem_summary['static_ram'])
output += "Total RAM memory (data + bss + heap + stack): %s bytes\n" % \ output += "Total Flash memory (text + data): %s bytes\n" % \
str(self.mem_summary['total_ram'])
output += "Total Flash memory (text + data + misc): %s bytes\n" % \
str(self.mem_summary['total_flash']) str(self.mem_summary['total_flash'])
return output return output
@ -544,36 +726,27 @@ class MemapParser(object):
toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "GCC_CR", "IAR"] toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "GCC_CR", "IAR"]
def compute_report(self): def compute_report(self):
""" Generates summary of memory usage for main areas
"""
for k in self.sections: for k in self.sections:
self.subtotal[k] = 0 self.subtotal[k] = 0
for i in sorted(self.modules): for i in sorted(self.short_modules):
for k in self.sections: for k in self.sections:
self.subtotal[k] += self.modules[i][k] self.subtotal[k] += self.short_modules[i][k]
# Calculate misc flash sections
self.misc_flash_mem = 0
for i in self.modules:
for k in self.misc_flash_sections:
if self.modules[i][k]:
self.misc_flash_mem += self.modules[i][k]
self.mem_summary = { self.mem_summary = {
'static_ram': (self.subtotal['.data'] + self.subtotal['.bss']), 'static_ram': (self.subtotal['.data'] + self.subtotal['.bss']),
'heap': (self.subtotal['.heap']), 'total_flash': (self.subtotal['.text'] + self.subtotal['.data']),
'stack': (self.subtotal['.stack']),
'total_ram': (self.subtotal['.data'] + self.subtotal['.bss'] +
self.subtotal['.heap']+self.subtotal['.stack']),
'total_flash': (self.subtotal['.text'] + self.subtotal['.data'] +
self.misc_flash_mem),
} }
self.mem_report = [] self.mem_report = []
for i in sorted(self.modules): for i in sorted(self.short_modules):
self.mem_report.append({ self.mem_report.append({
"module":i, "module":i,
"size":{ "size":{
k:self.modules[i][k] for k in self.print_sections k:self.short_modules[i][k] for k in self.print_sections
} }
}) })
@ -592,19 +765,21 @@ class MemapParser(object):
result = True result = True
try: try:
with open(mapfile, 'r') as file_input: with open(mapfile, 'r') as file_input:
# Common to all toolchains: first search for objects in BUILD
self.list_dir_obj(os.path.abspath(mapfile))
if toolchain == "ARM" or toolchain == "ARM_STD" or\ if toolchain == "ARM" or toolchain == "ARM_STD" or\
toolchain == "ARM_MICRO": toolchain == "ARM_MICRO":
self.search_objects(os.path.abspath(mapfile))
self.parse_map_file_armcc(file_input) self.parse_map_file_armcc(file_input)
elif toolchain == "GCC_ARM" or toolchain == "GCC_CR": elif toolchain == "GCC_ARM" or toolchain == "GCC_CR":
self.parse_map_file_gcc(file_input) self.parse_map_file_gcc(file_input)
elif toolchain == "IAR": elif toolchain == "IAR":
self.search_objects(os.path.abspath(mapfile))
self.parse_map_file_iar(file_input) self.parse_map_file_iar(file_input)
else: else:
result = False result = False
self.compute_report() self.remove_unused_modules()
except IOError as error: except IOError as error:
print "I/O error({0}): {1}".format(error.errno, error.strerror) print "I/O error({0}): {1}".format(error.errno, error.strerror)
@ -614,7 +789,7 @@ class MemapParser(object):
def main(): def main():
"""Entry Point""" """Entry Point"""
version = '0.3.12' version = '0.4.0'
# Parser handling # Parser handling
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@ -631,6 +806,10 @@ def main():
required=True, required=True,
type=argparse_uppercase_type(MemapParser.toolchains, "toolchain")) type=argparse_uppercase_type(MemapParser.toolchains, "toolchain"))
parser.add_argument(
'-d', '--depth', dest='depth', type=int,
help='specify directory depth level to display report', required=False)
parser.add_argument( parser.add_argument(
'-o', '--output', help='output file name', required=False) '-o', '--output', help='output file name', required=False)
@ -643,30 +822,33 @@ def main():
parser.add_argument('-v', '--version', action='version', version=version) parser.add_argument('-v', '--version', action='version', version=version)
parser.add_argument('-d', '--detailed', action='store_true', help='Displays the elements in "Misc" in a detailed fashion', required=False)
# Parse/run command # Parse/run command
if len(sys.argv) <= 1: if len(sys.argv) <= 1:
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
args = parser.parse_args() args = parser.parse_args()
# Create memap object # Create memap object
memap = MemapParser(detailed_misc=args.detailed) memap = MemapParser()
# Parse and decode a map file # Parse and decode a map file
if args.file and args.toolchain: if args.file and args.toolchain:
if memap.parse(args.file, args.toolchain) is False: if memap.parse(args.file, args.toolchain) is False:
sys.exit(0) sys.exit(0)
if args.depth is None:
depth = 2 # default depth level
else:
depth = args.depth
returned_string = None returned_string = None
# Write output in file # Write output in file
if args.output != None: if args.output != None:
returned_string = memap.generate_output(args.export, args.output) returned_string = memap.generate_output(args.export, \
depth, args.output)
else: # Write output in screen else: # Write output in screen
returned_string = memap.generate_output(args.export) returned_string = memap.generate_output(args.export, depth)
if args.export == 'table' and returned_string: if args.export == 'table' and returned_string:
print returned_string print returned_string

View File

@ -100,6 +100,12 @@ if __name__ == '__main__':
default=False, default=False,
help="Verbose diagnostic output") help="Verbose diagnostic output")
parser.add_argument("--stats-depth",
type=int,
dest="stats_depth",
default=2,
help="Depth level for static memory report")
options = parser.parse_args() options = parser.parse_args()
# Filter tests by path if specified # Filter tests by path if specified
@ -215,7 +221,8 @@ if __name__ == '__main__':
jobs=options.jobs, jobs=options.jobs,
continue_on_build_fail=options.continue_on_build_fail, continue_on_build_fail=options.continue_on_build_fail,
app_config=options.app_config, app_config=options.app_config,
build_profile=profile) build_profile=profile,
stats_depth=options.stats_depth)
# If a path to a test spec is provided, write it to a file # If a path to a test spec is provided, write it to a file
if options.test_spec: if options.test_spec:

View File

@ -16,6 +16,7 @@ limitations under the License.
""" """
import sys import sys
import os import os
import json
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
sys.path.insert(0, ROOT) sys.path.insert(0, ROOT)
@ -42,55 +43,103 @@ class MemapParserTests(unittest.TestCase):
self.memap_parser = MemapParser() self.memap_parser = MemapParser()
self.memap_parser.modules = { self.memap_parser.modules = {
"Misc": { "mbed-os/targets/TARGET/TARGET_MCUS/api/pinmap.o": {
"unknown": 0, ".text": 1,
".ARM": 8, ".data": 2,
".ARM.extab": 0, ".bss": 3,
".init": 12,
"OUTPUT": 0,
".stack": 0,
".eh_frame": 0,
".fini_array": 4,
".heap": 0, ".heap": 0,
".stabstr": 0,
".interrupts_ram": 0,
".init_array": 0,
".stab": 0,
".ARM.attributes": 7347,
".bss": 8517,
".flash_config": 16,
".interrupts": 1024,
".data": 2325,
".ARM.exidx": 0,
".text": 59906,
".jcr": 0
},
"Fill": {
"unknown": 12,
".ARM": 0,
".ARM.extab": 0,
".init": 0,
"OUTPUT": 0,
".stack": 0, ".stack": 0,
".eh_frame": 0, ".interrupts_ram":0,
".fini_array": 0, ".init":0,
".heap": 65536, ".ARM.extab":0,
".stabstr": 0,
".interrupts_ram": 1024,
".init_array": 0,
".stab": 0,
".ARM.attributes": 0,
".bss": 2235,
".flash_config": 0,
".interrupts": 0,
".data": 3,
".ARM.exidx":0, ".ARM.exidx":0,
".text": 136, ".ARM.attributes":0,
".jcr": 0 ".eh_frame":0,
".init_array":0,
".fini_array":0,
".jcr":0,
".stab":0,
".stabstr":0,
".ARM.exidx":0,
".ARM":0,
".interrupts":0,
".flash_config":0,
"unknown":0,
"OUTPUT":0,
},
"[lib]/libc.a/lib_a-printf.o": {
".text": 4,
".data": 5,
".bss": 6,
".heap": 0,
".stack": 0,
".interrupts_ram":0,
".init":0,
".ARM.extab":0,
".ARM.exidx":0,
".ARM.attributes":0,
".eh_frame":0,
".init_array":0,
".fini_array":0,
".jcr":0,
".stab":0,
".stabstr":0,
".ARM.exidx":0,
".ARM":0,
".interrupts":0,
".flash_config":0,
"unknown":0,
"OUTPUT":0,
},
"main.o": {
".text": 7,
".data": 8,
".bss": 0,
".heap": 0,
".stack": 0,
".interrupts_ram":0,
".init":0,
".ARM.extab":0,
".ARM.exidx":0,
".ARM.attributes":0,
".eh_frame":0,
".init_array":0,
".fini_array":0,
".jcr":0,
".stab":0,
".stabstr":0,
".ARM.exidx":0,
".ARM":0,
".interrupts":0,
".flash_config":0,
"unknown":0,
"OUTPUT":0,
},
"test.o": {
".text": 0,
".data": 0,
".bss": 0,
".heap": 0,
".stack": 0,
".interrupts_ram":0,
".init":0,
".ARM.extab":0,
".ARM.exidx":0,
".ARM.attributes":0,
".eh_frame":0,
".init_array":0,
".fini_array":0,
".jcr":0,
".stab":0,
".stabstr":0,
".ARM.exidx":0,
".ARM":0,
".interrupts":0,
".flash_config":0,
"unknown":0,
"OUTPUT":0,
},
} }
}
self.memap_parser.compute_report()
def tearDown(self): def tearDown(self):
""" """
@ -100,10 +149,10 @@ class MemapParserTests(unittest.TestCase):
""" """
pass pass
def generate_test_helper(self, output_type, file_output=None): def generate_test_helper(self, output_type, depth, file_output=None):
""" """
Helper that ensures that the member variables "modules", "mem_report", Helper that ensures that the member variables "modules" is
and "mem_summary" are unchanged after calling "generate_output" unchanged after calling "generate_output"
:param output_type: type string that is passed to "generate_output" :param output_type: type string that is passed to "generate_output"
:param file_output: path to output file that is passed to "generate_output" :param file_output: path to output file that is passed to "generate_output"
@ -111,15 +160,12 @@ class MemapParserTests(unittest.TestCase):
""" """
old_modules = deepcopy(self.memap_parser.modules) old_modules = deepcopy(self.memap_parser.modules)
old_report = deepcopy(self.memap_parser.mem_report)
old_summary = deepcopy(self.memap_parser.mem_summary) self.memap_parser.generate_output(output_type, depth, file_output)
self.memap_parser.generate_output(output_type, file_output)
self.assertEqual(self.memap_parser.modules, old_modules, self.assertEqual(self.memap_parser.modules, old_modules,
"generate_output modified the 'modules' property") "generate_output modified the 'modules' property")
self.assertEqual(self.memap_parser.mem_report, old_report,
"generate_output modified the 'mem_report' property")
self.assertEqual(self.memap_parser.mem_summary, old_summary,
"generate_output modified the 'mem_summary' property")
def test_report_computed(self): def test_report_computed(self):
""" """
@ -127,11 +173,12 @@ class MemapParserTests(unittest.TestCase):
:return: :return:
""" """
self.assertTrue(self.memap_parser.mem_report)
self.memap_parser.generate_output('table', 2)
# Report is created after generating output
self.assertTrue(self.memap_parser.mem_summary) self.assertTrue(self.memap_parser.mem_summary)
self.assertEqual(self.memap_parser.mem_report[-1]['summary'], self.assertTrue(self.memap_parser.mem_report)
self.memap_parser.mem_summary,
"mem_report did not contain a correct copy of mem_summary")
def test_generate_output_table(self): def test_generate_output_table(self):
""" """
@ -139,7 +186,8 @@ class MemapParserTests(unittest.TestCase):
:return: :return:
""" """
self.generate_test_helper('table') depth = 2
self.generate_test_helper('table', depth)
def test_generate_output_json(self): def test_generate_output_json(self):
""" """
@ -148,7 +196,8 @@ class MemapParserTests(unittest.TestCase):
:return: :return:
""" """
file_name = '.json_test_output.json' file_name = '.json_test_output.json'
self.generate_test_helper('json', file_output=file_name) depth = 2
self.generate_test_helper('json', depth, file_output=file_name)
self.assertTrue(os.path.exists(file_name), "Failed to create json file") self.assertTrue(os.path.exists(file_name), "Failed to create json file")
os.remove(file_name) os.remove(file_name)
@ -159,7 +208,8 @@ class MemapParserTests(unittest.TestCase):
:return: :return:
""" """
file_name = '.csv_ci_test_output.csv' file_name = '.csv_ci_test_output.csv'
self.generate_test_helper('csv-ci', file_output=file_name) depth = 2
self.generate_test_helper('csv-ci', depth, file_output=file_name)
self.assertTrue(os.path.exists(file_name), "Failed to create csv-ci file") self.assertTrue(os.path.exists(file_name), "Failed to create csv-ci file")
os.remove(file_name) os.remove(file_name)

View File

@ -496,7 +496,8 @@ class SingleTestRunner(object):
properties=build_properties, properties=build_properties,
project_id=test_id, project_id=test_id,
project_description=test.get_description(), project_description=test.get_description(),
build_profile=profile) build_profile=profile,
stats_depth=stats_depth)
except Exception, e: except Exception, e:
project_name_str = project_name if project_name is not None else test_id project_name_str = project_name if project_name is not None else test_id
@ -2122,7 +2123,7 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
clean=False, notify=None, verbose=False, jobs=1, macros=None, clean=False, notify=None, verbose=False, jobs=1, macros=None,
silent=False, report=None, properties=None, silent=False, report=None, properties=None,
continue_on_build_fail=False, app_config=None, continue_on_build_fail=False, app_config=None,
build_profile=None): build_profile=None, stats_depth=None):
"""Given the data structure from 'find_tests' and the typical build parameters, """Given the data structure from 'find_tests' and the typical build parameters,
build all the tests build all the tests
@ -2172,7 +2173,8 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
'app_config': app_config, 'app_config': app_config,
'build_profile': build_profile, 'build_profile': build_profile,
'silent': True, 'silent': True,
'toolchain_paths': TOOLCHAIN_PATHS 'toolchain_paths': TOOLCHAIN_PATHS,
'stats_depth': stats_depth
} }
results.append(p.apply_async(build_test_worker, args, kwargs)) results.append(p.apply_async(build_test_worker, args, kwargs))

View File

@ -416,7 +416,6 @@ class mbedToolchain:
# Print output buffer # Print output buffer
self.output = str() self.output = str()
self.map_outputs = list() # Place to store memmap scan results in JSON like data structures
# uVisor spepcific rules # uVisor spepcific rules
if 'UVISOR' in self.target.features and 'UVISOR_SUPPORTED' in self.target.extra_labels: if 'UVISOR' in self.target.features and 'UVISOR_SUPPORTED' in self.target.extra_labels:
@ -1128,7 +1127,8 @@ class mbedToolchain:
self.progress("elf2bin", name) self.progress("elf2bin", name)
self.binary(r, elf, bin) self.binary(r, elf, bin)
self.map_outputs = self.mem_stats(map) # Initialize memap and process map file. This doesn't generate output.
self.mem_stats(map)
self.var("compile_succeded", True) self.var("compile_succeded", True)
self.var("binary", filename) self.var("binary", filename)
@ -1193,8 +1193,7 @@ class mbedToolchain:
def mem_stats(self, map): def mem_stats(self, map):
"""! Creates parser object """! Creates parser object
@param map Path to linker map file to parse and decode @param map Path to linker map file to parse and decode
@return Memory summary structure with memory usage statistics @return None
None if map file can't be opened and processed
""" """
toolchain = self.__class__.__name__ toolchain = self.__class__.__name__
@ -1209,10 +1208,10 @@ class mbedToolchain:
# Store the memap instance for later use # Store the memap instance for later use
self.memap_instance = memap self.memap_instance = memap
# Here we return memory statistics structure (constructed after # Note: memory statistics are not returned.
# call to generate_output) which contains raw data in bytes # Need call to generate_output later (depends on depth & output format)
# about sections + summary
return memap.mem_report return None
# Set the configuration data # Set the configuration data
def set_config_data(self, config_data): def set_config_data(self, config_data):