Created
February 25, 2020 03:10
-
-
Save geky/01a501b18de7df0465d615a598050ac6 to your computer and use it in GitHub Desktop.
Finds the costs of functions/files in an mbed project and creates a spreadsheet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import subprocess | |
import os | |
import re | |
import collections | |
import itertools | |
import csv | |
import argparse | |
PROJECT = os.path.basename(os.getcwd()) | |
BUILD = 'PROFILE/BUILD' | |
def rmsuffix(s, ps): | |
ps = ps if isinstance(ps, list) else [ps] | |
while True: | |
ns = s | |
for p in ps: | |
if ns.endswith(p): | |
ns = ns[:-len(p)] | |
if ns == s: | |
return s | |
s = ns | |
def rmprefix(s, ps): | |
ps = ps if isinstance(ps, list) else [ps] | |
while True: | |
ns = s | |
for p in ps: | |
if ns.startswith(p): | |
ns = ns[len(p):] | |
if ns == s: | |
return s | |
s = ns | |
def main(): | |
parser = argparse.ArgumentParser(description='Binary sizes + PC sample combiner') | |
parser.add_argument('--samples', help="csv file(s) containing samples", nargs="*", default=[]) | |
args = parser.parse_args() | |
syms = {} | |
# create build dir | |
try: | |
os.makedirs(BUILD) | |
except OSError: | |
pass | |
with open(os.path.join(BUILD, '.mbedignore'), 'w') as f: | |
f.write('*\n') | |
subprocess.check_call(['mbed', 'compile', | |
'--profile', 'release', '--build', BUILD]) | |
# Find static memory usage | |
release_syms = subprocess.check_output(['arm-none-eabi-nm', '-p', '-S', | |
os.path.join(BUILD, PROJECT+'_application.elf')]) | |
for m in re.finditer( | |
'^([0-9a-fA-F]+)\s([0-9a-fA-F]+)\s(\w)\s(\w+)$', | |
release_syms, re.M): | |
off, size, type, name = m.groups() | |
size = int(size, 16) | |
type = type.upper() | |
syms[name] = syms.get(name, {}) | |
if type in {'T', 'W', 'V'}: | |
syms[name]['text'] = size | |
elif type in {'D'}: | |
syms[name]['data'] = size | |
elif type in {'B'}: | |
syms[name]['bss'] = size | |
else: | |
print 'Found unknown symbol type: %s (%s)' % (type, name) | |
# Find samples | |
sample_files = [] | |
for i, sample_file in enumerate(args.samples): | |
try: | |
with open(sample_file) as f: | |
sample_file = i # rmsuffix(sample_file, '.csv') | |
for _,_,_,name,_,_,_ in csv.reader(f): | |
if name and name in syms: | |
syms[name][('samples', sample_file)] = syms[name].get(('samples', sample_file), 0) + 1 | |
except OSError: | |
pass | |
sample_files.append(sample_file) | |
# Find function dependencies in disassembly | |
disas = subprocess.check_output(['arm-none-eabi-objdump', '-d', | |
os.path.join(BUILD, PROJECT+'_application.elf')]) | |
currentsym = '' | |
for line in itertools.chain(disas.splitlines(), ['']): | |
m = re.match('^[0-9a-fA-F]+ <(\w+)>:', line) | |
if m: | |
currentsym = m.group(1) | |
else: | |
m = re.match('^ .*<(\w+)(\+[x0-9a-fA-F])?>', line) | |
if m: | |
targetsym = m.group(1) | |
if currentsym in syms and targetsym in syms and currentsym != targetsym: | |
syms[currentsym]['children'] = ( | |
syms[currentsym].get('children', set()) | {targetsym}) | |
syms[targetsym]['parents'] = ( | |
syms[targetsym].get('parents', set()) | {currentsym}) | |
# Find source file for each function | |
file_list = subprocess.check_output([ | |
'find', BUILD, '-name', '*.o']).strip().split('\n') | |
file_syms = subprocess.check_output(['arm-none-eabi-nm', '-p', '-S', '-A'] + file_list) | |
for m in re.finditer( | |
'^([^:]*):([0-9a-fA-F]+)\s([0-9a-fA-F]+)\s(\w)\s(\w+)$', | |
file_syms, re.M): | |
file, _, _, _, name = m.groups() | |
file = rmprefix(file, [BUILD, '/']) | |
if name in syms: | |
syms[name]['file'] = file | |
# Clean up missing values | |
for s in syms.itervalues(): | |
s['file'] = s.get('file', '?') | |
s['text'] = s.get('text', 0) | |
s['data'] = s.get('data', 0) | |
s['bss'] = s.get('bss', 0) | |
for sample_file in sample_files: | |
s[('samples', sample_file)] = s.get(('samples', sample_file), 0) | |
s['children'] = s.get('children', set()) | |
s['parents'] = s.get('parents', set()) | |
# Organize by file, write to csv | |
files = {} | |
for name, s in syms.iteritems(): | |
o = s['file'] | |
files[o] = files.get(o, {}) | |
files[o]['text'] = files[o].get('text', 0) + s['text'] | |
files[o]['data'] = files[o].get('data', 0) + s['data'] | |
files[o]['bss'] = files[o].get('bss', 0) + s['bss'] | |
for sample_file in sample_files: | |
files[o][('samples', sample_file)] = files[o].get(('samples', sample_file), 0) + s[('samples', sample_file)] | |
files[o]['children'] = files[o].get('children', set()) | set( | |
syms[c].get('file', '?') for c in s['children']) | |
files[o]['parents'] = files[o].get('parents', set()) | set( | |
syms[c].get('file', '?') for c in s['parents']) | |
print 'Writing PROFILE/results-files.csv...' | |
with open('PROFILE/results-files.csv', 'w') as f: | |
writer = csv.writer(f) | |
writer.writerow(['file','text','data','bss'] + | |
['samples (%s)' % sf for sf in sample_files] + | |
['parents', 'children']) | |
for o, s in sorted(files.iteritems()): | |
if o != '?': | |
for d in itertools.izip_longest(*( | |
[[o], [s['text']], [s['data']], [s['bss']]] + | |
[[s[('samples', sf)]] for sf in sample_files] + | |
[(u for u in s['parents'] if u != '?' and u != o)] + | |
[(u for u in s['children'] if u != '?' and u != o)]), | |
fillvalue=''): | |
writer.writerow(d) | |
print 'Writing PROFILE/results-functions.csv...' | |
with open('PROFILE/results-functions.csv', 'w') as f: | |
writer = csv.writer(f) | |
writer.writerow(['file','function','text','data','bss'] + | |
['samples (%s)' % sf for sf in sample_files] + | |
['parents', 'children']) | |
for o in sorted(files.iterkeys()): | |
for fn, s in sorted((fn, s) for fn, s in syms.iteritems() | |
if s['file'] == o): | |
for d in itertools.izip_longest(*( | |
[[o], [fn], [s['text']], [s['data']], [s['bss']]] + | |
[[s[('samples', sf)]] for sf in sample_files] + | |
[s['parents'], s['children']]), | |
fillvalue=''): | |
writer.writerow(d) | |
# Summary | |
total_text = sum(s['text'] for s in syms.itervalues()) | |
total_data = sum(s['data'] for s in syms.itervalues()) | |
total_bss = sum(s['bss'] for s in syms.itervalues()) | |
total_samples = [sum(s[('samples', sf)] for s in syms.itervalues()) | |
for sf in sample_files] | |
print '--- totals ---' | |
print ' '.join('%012s' % c for c in ['text', 'data', 'bss'] + | |
['samples (%s)' % sf for sf in sample_files]) | |
print ' '.join('%012s' % c for c in [total_text, total_data, total_bss] + total_samples) | |
if __name__ == "__main__": | |
import sys | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment