Skip to content

Instantly share code, notes, and snippets.

@geky
Created February 25, 2020 03:10
Show Gist options
  • Save geky/01a501b18de7df0465d615a598050ac6 to your computer and use it in GitHub Desktop.
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
#!/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