119ea8026Sopenharmony_ci#!/usr/bin/env python3 219ea8026Sopenharmony_ci# 319ea8026Sopenharmony_ci# Script to find data size at the function level. Basically just a big wrapper 419ea8026Sopenharmony_ci# around nm with some extra conveniences for comparing builds. Heavily inspired 519ea8026Sopenharmony_ci# by Linux's Bloat-O-Meter. 619ea8026Sopenharmony_ci# 719ea8026Sopenharmony_ci# Example: 819ea8026Sopenharmony_ci# ./scripts/data.py lfs.o lfs_util.o -Ssize 919ea8026Sopenharmony_ci# 1019ea8026Sopenharmony_ci# Copyright (c) 2022, The littlefs authors. 1119ea8026Sopenharmony_ci# Copyright (c) 2020, Arm Limited. All rights reserved. 1219ea8026Sopenharmony_ci# SPDX-License-Identifier: BSD-3-Clause 1319ea8026Sopenharmony_ci# 1419ea8026Sopenharmony_ci 1519ea8026Sopenharmony_ciimport collections as co 1619ea8026Sopenharmony_ciimport csv 1719ea8026Sopenharmony_ciimport difflib 1819ea8026Sopenharmony_ciimport itertools as it 1919ea8026Sopenharmony_ciimport math as m 2019ea8026Sopenharmony_ciimport os 2119ea8026Sopenharmony_ciimport re 2219ea8026Sopenharmony_ciimport shlex 2319ea8026Sopenharmony_ciimport subprocess as sp 2419ea8026Sopenharmony_ci 2519ea8026Sopenharmony_ci 2619ea8026Sopenharmony_ciNM_PATH = ['nm'] 2719ea8026Sopenharmony_ciNM_TYPES = 'dDbB' 2819ea8026Sopenharmony_ciOBJDUMP_PATH = ['objdump'] 2919ea8026Sopenharmony_ci 3019ea8026Sopenharmony_ci 3119ea8026Sopenharmony_ci# integer fields 3219ea8026Sopenharmony_ciclass Int(co.namedtuple('Int', 'x')): 3319ea8026Sopenharmony_ci __slots__ = () 3419ea8026Sopenharmony_ci def __new__(cls, x=0): 3519ea8026Sopenharmony_ci if isinstance(x, Int): 3619ea8026Sopenharmony_ci return x 3719ea8026Sopenharmony_ci if isinstance(x, str): 3819ea8026Sopenharmony_ci try: 3919ea8026Sopenharmony_ci x = int(x, 0) 4019ea8026Sopenharmony_ci except ValueError: 4119ea8026Sopenharmony_ci # also accept +-∞ and +-inf 4219ea8026Sopenharmony_ci if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x): 4319ea8026Sopenharmony_ci x = m.inf 4419ea8026Sopenharmony_ci elif re.match('^\s*-\s*(?:∞|inf)\s*$', x): 4519ea8026Sopenharmony_ci x = -m.inf 4619ea8026Sopenharmony_ci else: 4719ea8026Sopenharmony_ci raise 4819ea8026Sopenharmony_ci assert isinstance(x, int) or m.isinf(x), x 4919ea8026Sopenharmony_ci return super().__new__(cls, x) 5019ea8026Sopenharmony_ci 5119ea8026Sopenharmony_ci def __str__(self): 5219ea8026Sopenharmony_ci if self.x == m.inf: 5319ea8026Sopenharmony_ci return '∞' 5419ea8026Sopenharmony_ci elif self.x == -m.inf: 5519ea8026Sopenharmony_ci return '-∞' 5619ea8026Sopenharmony_ci else: 5719ea8026Sopenharmony_ci return str(self.x) 5819ea8026Sopenharmony_ci 5919ea8026Sopenharmony_ci def __int__(self): 6019ea8026Sopenharmony_ci assert not m.isinf(self.x) 6119ea8026Sopenharmony_ci return self.x 6219ea8026Sopenharmony_ci 6319ea8026Sopenharmony_ci def __float__(self): 6419ea8026Sopenharmony_ci return float(self.x) 6519ea8026Sopenharmony_ci 6619ea8026Sopenharmony_ci none = '%7s' % '-' 6719ea8026Sopenharmony_ci def table(self): 6819ea8026Sopenharmony_ci return '%7s' % (self,) 6919ea8026Sopenharmony_ci 7019ea8026Sopenharmony_ci diff_none = '%7s' % '-' 7119ea8026Sopenharmony_ci diff_table = table 7219ea8026Sopenharmony_ci 7319ea8026Sopenharmony_ci def diff_diff(self, other): 7419ea8026Sopenharmony_ci new = self.x if self else 0 7519ea8026Sopenharmony_ci old = other.x if other else 0 7619ea8026Sopenharmony_ci diff = new - old 7719ea8026Sopenharmony_ci if diff == +m.inf: 7819ea8026Sopenharmony_ci return '%7s' % '+∞' 7919ea8026Sopenharmony_ci elif diff == -m.inf: 8019ea8026Sopenharmony_ci return '%7s' % '-∞' 8119ea8026Sopenharmony_ci else: 8219ea8026Sopenharmony_ci return '%+7d' % diff 8319ea8026Sopenharmony_ci 8419ea8026Sopenharmony_ci def ratio(self, other): 8519ea8026Sopenharmony_ci new = self.x if self else 0 8619ea8026Sopenharmony_ci old = other.x if other else 0 8719ea8026Sopenharmony_ci if m.isinf(new) and m.isinf(old): 8819ea8026Sopenharmony_ci return 0.0 8919ea8026Sopenharmony_ci elif m.isinf(new): 9019ea8026Sopenharmony_ci return +m.inf 9119ea8026Sopenharmony_ci elif m.isinf(old): 9219ea8026Sopenharmony_ci return -m.inf 9319ea8026Sopenharmony_ci elif not old and not new: 9419ea8026Sopenharmony_ci return 0.0 9519ea8026Sopenharmony_ci elif not old: 9619ea8026Sopenharmony_ci return 1.0 9719ea8026Sopenharmony_ci else: 9819ea8026Sopenharmony_ci return (new-old) / old 9919ea8026Sopenharmony_ci 10019ea8026Sopenharmony_ci def __add__(self, other): 10119ea8026Sopenharmony_ci return self.__class__(self.x + other.x) 10219ea8026Sopenharmony_ci 10319ea8026Sopenharmony_ci def __sub__(self, other): 10419ea8026Sopenharmony_ci return self.__class__(self.x - other.x) 10519ea8026Sopenharmony_ci 10619ea8026Sopenharmony_ci def __mul__(self, other): 10719ea8026Sopenharmony_ci return self.__class__(self.x * other.x) 10819ea8026Sopenharmony_ci 10919ea8026Sopenharmony_ci# data size results 11019ea8026Sopenharmony_ciclass DataResult(co.namedtuple('DataResult', [ 11119ea8026Sopenharmony_ci 'file', 'function', 11219ea8026Sopenharmony_ci 'size'])): 11319ea8026Sopenharmony_ci _by = ['file', 'function'] 11419ea8026Sopenharmony_ci _fields = ['size'] 11519ea8026Sopenharmony_ci _sort = ['size'] 11619ea8026Sopenharmony_ci _types = {'size': Int} 11719ea8026Sopenharmony_ci 11819ea8026Sopenharmony_ci __slots__ = () 11919ea8026Sopenharmony_ci def __new__(cls, file='', function='', size=0): 12019ea8026Sopenharmony_ci return super().__new__(cls, file, function, 12119ea8026Sopenharmony_ci Int(size)) 12219ea8026Sopenharmony_ci 12319ea8026Sopenharmony_ci def __add__(self, other): 12419ea8026Sopenharmony_ci return DataResult(self.file, self.function, 12519ea8026Sopenharmony_ci self.size + other.size) 12619ea8026Sopenharmony_ci 12719ea8026Sopenharmony_ci 12819ea8026Sopenharmony_cidef openio(path, mode='r', buffering=-1): 12919ea8026Sopenharmony_ci # allow '-' for stdin/stdout 13019ea8026Sopenharmony_ci if path == '-': 13119ea8026Sopenharmony_ci if mode == 'r': 13219ea8026Sopenharmony_ci return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering) 13319ea8026Sopenharmony_ci else: 13419ea8026Sopenharmony_ci return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering) 13519ea8026Sopenharmony_ci else: 13619ea8026Sopenharmony_ci return open(path, mode, buffering) 13719ea8026Sopenharmony_ci 13819ea8026Sopenharmony_cidef collect(obj_paths, *, 13919ea8026Sopenharmony_ci nm_path=NM_PATH, 14019ea8026Sopenharmony_ci nm_types=NM_TYPES, 14119ea8026Sopenharmony_ci objdump_path=OBJDUMP_PATH, 14219ea8026Sopenharmony_ci sources=None, 14319ea8026Sopenharmony_ci everything=False, 14419ea8026Sopenharmony_ci **args): 14519ea8026Sopenharmony_ci size_pattern = re.compile( 14619ea8026Sopenharmony_ci '^(?P<size>[0-9a-fA-F]+)' + 14719ea8026Sopenharmony_ci ' (?P<type>[%s])' % re.escape(nm_types) + 14819ea8026Sopenharmony_ci ' (?P<func>.+?)$') 14919ea8026Sopenharmony_ci line_pattern = re.compile( 15019ea8026Sopenharmony_ci '^\s+(?P<no>[0-9]+)' 15119ea8026Sopenharmony_ci '(?:\s+(?P<dir>[0-9]+))?' 15219ea8026Sopenharmony_ci '\s+.*' 15319ea8026Sopenharmony_ci '\s+(?P<path>[^\s]+)$') 15419ea8026Sopenharmony_ci info_pattern = re.compile( 15519ea8026Sopenharmony_ci '^(?:.*(?P<tag>DW_TAG_[a-z_]+).*' 15619ea8026Sopenharmony_ci '|.*DW_AT_name.*:\s*(?P<name>[^:\s]+)\s*' 15719ea8026Sopenharmony_ci '|.*DW_AT_decl_file.*:\s*(?P<file>[0-9]+)\s*)$') 15819ea8026Sopenharmony_ci 15919ea8026Sopenharmony_ci results = [] 16019ea8026Sopenharmony_ci for path in obj_paths: 16119ea8026Sopenharmony_ci # guess the source, if we have debug-info we'll replace this later 16219ea8026Sopenharmony_ci file = re.sub('(\.o)?$', '.c', path, 1) 16319ea8026Sopenharmony_ci 16419ea8026Sopenharmony_ci # find symbol sizes 16519ea8026Sopenharmony_ci results_ = [] 16619ea8026Sopenharmony_ci # note nm-path may contain extra args 16719ea8026Sopenharmony_ci cmd = nm_path + ['--size-sort', path] 16819ea8026Sopenharmony_ci if args.get('verbose'): 16919ea8026Sopenharmony_ci print(' '.join(shlex.quote(c) for c in cmd)) 17019ea8026Sopenharmony_ci proc = sp.Popen(cmd, 17119ea8026Sopenharmony_ci stdout=sp.PIPE, 17219ea8026Sopenharmony_ci stderr=sp.PIPE if not args.get('verbose') else None, 17319ea8026Sopenharmony_ci universal_newlines=True, 17419ea8026Sopenharmony_ci errors='replace', 17519ea8026Sopenharmony_ci close_fds=False) 17619ea8026Sopenharmony_ci for line in proc.stdout: 17719ea8026Sopenharmony_ci m = size_pattern.match(line) 17819ea8026Sopenharmony_ci if m: 17919ea8026Sopenharmony_ci func = m.group('func') 18019ea8026Sopenharmony_ci # discard internal functions 18119ea8026Sopenharmony_ci if not everything and func.startswith('__'): 18219ea8026Sopenharmony_ci continue 18319ea8026Sopenharmony_ci results_.append(DataResult( 18419ea8026Sopenharmony_ci file, func, 18519ea8026Sopenharmony_ci int(m.group('size'), 16))) 18619ea8026Sopenharmony_ci proc.wait() 18719ea8026Sopenharmony_ci if proc.returncode != 0: 18819ea8026Sopenharmony_ci if not args.get('verbose'): 18919ea8026Sopenharmony_ci for line in proc.stderr: 19019ea8026Sopenharmony_ci sys.stdout.write(line) 19119ea8026Sopenharmony_ci sys.exit(-1) 19219ea8026Sopenharmony_ci 19319ea8026Sopenharmony_ci 19419ea8026Sopenharmony_ci # try to figure out the source file if we have debug-info 19519ea8026Sopenharmony_ci dirs = {} 19619ea8026Sopenharmony_ci files = {} 19719ea8026Sopenharmony_ci # note objdump-path may contain extra args 19819ea8026Sopenharmony_ci cmd = objdump_path + ['--dwarf=rawline', path] 19919ea8026Sopenharmony_ci if args.get('verbose'): 20019ea8026Sopenharmony_ci print(' '.join(shlex.quote(c) for c in cmd)) 20119ea8026Sopenharmony_ci proc = sp.Popen(cmd, 20219ea8026Sopenharmony_ci stdout=sp.PIPE, 20319ea8026Sopenharmony_ci stderr=sp.PIPE if not args.get('verbose') else None, 20419ea8026Sopenharmony_ci universal_newlines=True, 20519ea8026Sopenharmony_ci errors='replace', 20619ea8026Sopenharmony_ci close_fds=False) 20719ea8026Sopenharmony_ci for line in proc.stdout: 20819ea8026Sopenharmony_ci # note that files contain references to dirs, which we 20919ea8026Sopenharmony_ci # dereference as soon as we see them as each file table follows a 21019ea8026Sopenharmony_ci # dir table 21119ea8026Sopenharmony_ci m = line_pattern.match(line) 21219ea8026Sopenharmony_ci if m: 21319ea8026Sopenharmony_ci if not m.group('dir'): 21419ea8026Sopenharmony_ci # found a directory entry 21519ea8026Sopenharmony_ci dirs[int(m.group('no'))] = m.group('path') 21619ea8026Sopenharmony_ci else: 21719ea8026Sopenharmony_ci # found a file entry 21819ea8026Sopenharmony_ci dir = int(m.group('dir')) 21919ea8026Sopenharmony_ci if dir in dirs: 22019ea8026Sopenharmony_ci files[int(m.group('no'))] = os.path.join( 22119ea8026Sopenharmony_ci dirs[dir], 22219ea8026Sopenharmony_ci m.group('path')) 22319ea8026Sopenharmony_ci else: 22419ea8026Sopenharmony_ci files[int(m.group('no'))] = m.group('path') 22519ea8026Sopenharmony_ci proc.wait() 22619ea8026Sopenharmony_ci if proc.returncode != 0: 22719ea8026Sopenharmony_ci if not args.get('verbose'): 22819ea8026Sopenharmony_ci for line in proc.stderr: 22919ea8026Sopenharmony_ci sys.stdout.write(line) 23019ea8026Sopenharmony_ci # do nothing on error, we don't need objdump to work, source files 23119ea8026Sopenharmony_ci # may just be inaccurate 23219ea8026Sopenharmony_ci pass 23319ea8026Sopenharmony_ci 23419ea8026Sopenharmony_ci defs = {} 23519ea8026Sopenharmony_ci is_func = False 23619ea8026Sopenharmony_ci f_name = None 23719ea8026Sopenharmony_ci f_file = None 23819ea8026Sopenharmony_ci # note objdump-path may contain extra args 23919ea8026Sopenharmony_ci cmd = objdump_path + ['--dwarf=info', path] 24019ea8026Sopenharmony_ci if args.get('verbose'): 24119ea8026Sopenharmony_ci print(' '.join(shlex.quote(c) for c in cmd)) 24219ea8026Sopenharmony_ci proc = sp.Popen(cmd, 24319ea8026Sopenharmony_ci stdout=sp.PIPE, 24419ea8026Sopenharmony_ci stderr=sp.PIPE if not args.get('verbose') else None, 24519ea8026Sopenharmony_ci universal_newlines=True, 24619ea8026Sopenharmony_ci errors='replace', 24719ea8026Sopenharmony_ci close_fds=False) 24819ea8026Sopenharmony_ci for line in proc.stdout: 24919ea8026Sopenharmony_ci # state machine here to find definitions 25019ea8026Sopenharmony_ci m = info_pattern.match(line) 25119ea8026Sopenharmony_ci if m: 25219ea8026Sopenharmony_ci if m.group('tag'): 25319ea8026Sopenharmony_ci if is_func: 25419ea8026Sopenharmony_ci defs[f_name] = files.get(f_file, '?') 25519ea8026Sopenharmony_ci is_func = (m.group('tag') == 'DW_TAG_subprogram') 25619ea8026Sopenharmony_ci elif m.group('name'): 25719ea8026Sopenharmony_ci f_name = m.group('name') 25819ea8026Sopenharmony_ci elif m.group('file'): 25919ea8026Sopenharmony_ci f_file = int(m.group('file')) 26019ea8026Sopenharmony_ci if is_func: 26119ea8026Sopenharmony_ci defs[f_name] = files.get(f_file, '?') 26219ea8026Sopenharmony_ci proc.wait() 26319ea8026Sopenharmony_ci if proc.returncode != 0: 26419ea8026Sopenharmony_ci if not args.get('verbose'): 26519ea8026Sopenharmony_ci for line in proc.stderr: 26619ea8026Sopenharmony_ci sys.stdout.write(line) 26719ea8026Sopenharmony_ci # do nothing on error, we don't need objdump to work, source files 26819ea8026Sopenharmony_ci # may just be inaccurate 26919ea8026Sopenharmony_ci pass 27019ea8026Sopenharmony_ci 27119ea8026Sopenharmony_ci for r in results_: 27219ea8026Sopenharmony_ci # find best matching debug symbol, this may be slightly different 27319ea8026Sopenharmony_ci # due to optimizations 27419ea8026Sopenharmony_ci if defs: 27519ea8026Sopenharmony_ci # exact match? avoid difflib if we can for speed 27619ea8026Sopenharmony_ci if r.function in defs: 27719ea8026Sopenharmony_ci file = defs[r.function] 27819ea8026Sopenharmony_ci else: 27919ea8026Sopenharmony_ci _, file = max( 28019ea8026Sopenharmony_ci defs.items(), 28119ea8026Sopenharmony_ci key=lambda d: difflib.SequenceMatcher(None, 28219ea8026Sopenharmony_ci d[0], 28319ea8026Sopenharmony_ci r.function, False).ratio()) 28419ea8026Sopenharmony_ci else: 28519ea8026Sopenharmony_ci file = r.file 28619ea8026Sopenharmony_ci 28719ea8026Sopenharmony_ci # ignore filtered sources 28819ea8026Sopenharmony_ci if sources is not None: 28919ea8026Sopenharmony_ci if not any( 29019ea8026Sopenharmony_ci os.path.abspath(file) == os.path.abspath(s) 29119ea8026Sopenharmony_ci for s in sources): 29219ea8026Sopenharmony_ci continue 29319ea8026Sopenharmony_ci else: 29419ea8026Sopenharmony_ci # default to only cwd 29519ea8026Sopenharmony_ci if not everything and not os.path.commonpath([ 29619ea8026Sopenharmony_ci os.getcwd(), 29719ea8026Sopenharmony_ci os.path.abspath(file)]) == os.getcwd(): 29819ea8026Sopenharmony_ci continue 29919ea8026Sopenharmony_ci 30019ea8026Sopenharmony_ci # simplify path 30119ea8026Sopenharmony_ci if os.path.commonpath([ 30219ea8026Sopenharmony_ci os.getcwd(), 30319ea8026Sopenharmony_ci os.path.abspath(file)]) == os.getcwd(): 30419ea8026Sopenharmony_ci file = os.path.relpath(file) 30519ea8026Sopenharmony_ci else: 30619ea8026Sopenharmony_ci file = os.path.abspath(file) 30719ea8026Sopenharmony_ci 30819ea8026Sopenharmony_ci results.append(r._replace(file=file)) 30919ea8026Sopenharmony_ci 31019ea8026Sopenharmony_ci return results 31119ea8026Sopenharmony_ci 31219ea8026Sopenharmony_ci 31319ea8026Sopenharmony_cidef fold(Result, results, *, 31419ea8026Sopenharmony_ci by=None, 31519ea8026Sopenharmony_ci defines=None, 31619ea8026Sopenharmony_ci **_): 31719ea8026Sopenharmony_ci if by is None: 31819ea8026Sopenharmony_ci by = Result._by 31919ea8026Sopenharmony_ci 32019ea8026Sopenharmony_ci for k in it.chain(by or [], (k for k, _ in defines or [])): 32119ea8026Sopenharmony_ci if k not in Result._by and k not in Result._fields: 32219ea8026Sopenharmony_ci print("error: could not find field %r?" % k) 32319ea8026Sopenharmony_ci sys.exit(-1) 32419ea8026Sopenharmony_ci 32519ea8026Sopenharmony_ci # filter by matching defines 32619ea8026Sopenharmony_ci if defines is not None: 32719ea8026Sopenharmony_ci results_ = [] 32819ea8026Sopenharmony_ci for r in results: 32919ea8026Sopenharmony_ci if all(getattr(r, k) in vs for k, vs in defines): 33019ea8026Sopenharmony_ci results_.append(r) 33119ea8026Sopenharmony_ci results = results_ 33219ea8026Sopenharmony_ci 33319ea8026Sopenharmony_ci # organize results into conflicts 33419ea8026Sopenharmony_ci folding = co.OrderedDict() 33519ea8026Sopenharmony_ci for r in results: 33619ea8026Sopenharmony_ci name = tuple(getattr(r, k) for k in by) 33719ea8026Sopenharmony_ci if name not in folding: 33819ea8026Sopenharmony_ci folding[name] = [] 33919ea8026Sopenharmony_ci folding[name].append(r) 34019ea8026Sopenharmony_ci 34119ea8026Sopenharmony_ci # merge conflicts 34219ea8026Sopenharmony_ci folded = [] 34319ea8026Sopenharmony_ci for name, rs in folding.items(): 34419ea8026Sopenharmony_ci folded.append(sum(rs[1:], start=rs[0])) 34519ea8026Sopenharmony_ci 34619ea8026Sopenharmony_ci return folded 34719ea8026Sopenharmony_ci 34819ea8026Sopenharmony_cidef table(Result, results, diff_results=None, *, 34919ea8026Sopenharmony_ci by=None, 35019ea8026Sopenharmony_ci fields=None, 35119ea8026Sopenharmony_ci sort=None, 35219ea8026Sopenharmony_ci summary=False, 35319ea8026Sopenharmony_ci all=False, 35419ea8026Sopenharmony_ci percent=False, 35519ea8026Sopenharmony_ci **_): 35619ea8026Sopenharmony_ci all_, all = all, __builtins__.all 35719ea8026Sopenharmony_ci 35819ea8026Sopenharmony_ci if by is None: 35919ea8026Sopenharmony_ci by = Result._by 36019ea8026Sopenharmony_ci if fields is None: 36119ea8026Sopenharmony_ci fields = Result._fields 36219ea8026Sopenharmony_ci types = Result._types 36319ea8026Sopenharmony_ci 36419ea8026Sopenharmony_ci # fold again 36519ea8026Sopenharmony_ci results = fold(Result, results, by=by) 36619ea8026Sopenharmony_ci if diff_results is not None: 36719ea8026Sopenharmony_ci diff_results = fold(Result, diff_results, by=by) 36819ea8026Sopenharmony_ci 36919ea8026Sopenharmony_ci # organize by name 37019ea8026Sopenharmony_ci table = { 37119ea8026Sopenharmony_ci ','.join(str(getattr(r, k) or '') for k in by): r 37219ea8026Sopenharmony_ci for r in results} 37319ea8026Sopenharmony_ci diff_table = { 37419ea8026Sopenharmony_ci ','.join(str(getattr(r, k) or '') for k in by): r 37519ea8026Sopenharmony_ci for r in diff_results or []} 37619ea8026Sopenharmony_ci names = list(table.keys() | diff_table.keys()) 37719ea8026Sopenharmony_ci 37819ea8026Sopenharmony_ci # sort again, now with diff info, note that python's sort is stable 37919ea8026Sopenharmony_ci names.sort() 38019ea8026Sopenharmony_ci if diff_results is not None: 38119ea8026Sopenharmony_ci names.sort(key=lambda n: tuple( 38219ea8026Sopenharmony_ci types[k].ratio( 38319ea8026Sopenharmony_ci getattr(table.get(n), k, None), 38419ea8026Sopenharmony_ci getattr(diff_table.get(n), k, None)) 38519ea8026Sopenharmony_ci for k in fields), 38619ea8026Sopenharmony_ci reverse=True) 38719ea8026Sopenharmony_ci if sort: 38819ea8026Sopenharmony_ci for k, reverse in reversed(sort): 38919ea8026Sopenharmony_ci names.sort( 39019ea8026Sopenharmony_ci key=lambda n: tuple( 39119ea8026Sopenharmony_ci (getattr(table[n], k),) 39219ea8026Sopenharmony_ci if getattr(table.get(n), k, None) is not None else () 39319ea8026Sopenharmony_ci for k in ([k] if k else [ 39419ea8026Sopenharmony_ci k for k in Result._sort if k in fields])), 39519ea8026Sopenharmony_ci reverse=reverse ^ (not k or k in Result._fields)) 39619ea8026Sopenharmony_ci 39719ea8026Sopenharmony_ci 39819ea8026Sopenharmony_ci # build up our lines 39919ea8026Sopenharmony_ci lines = [] 40019ea8026Sopenharmony_ci 40119ea8026Sopenharmony_ci # header 40219ea8026Sopenharmony_ci header = [] 40319ea8026Sopenharmony_ci header.append('%s%s' % ( 40419ea8026Sopenharmony_ci ','.join(by), 40519ea8026Sopenharmony_ci ' (%d added, %d removed)' % ( 40619ea8026Sopenharmony_ci sum(1 for n in table if n not in diff_table), 40719ea8026Sopenharmony_ci sum(1 for n in diff_table if n not in table)) 40819ea8026Sopenharmony_ci if diff_results is not None and not percent else '') 40919ea8026Sopenharmony_ci if not summary else '') 41019ea8026Sopenharmony_ci if diff_results is None: 41119ea8026Sopenharmony_ci for k in fields: 41219ea8026Sopenharmony_ci header.append(k) 41319ea8026Sopenharmony_ci elif percent: 41419ea8026Sopenharmony_ci for k in fields: 41519ea8026Sopenharmony_ci header.append(k) 41619ea8026Sopenharmony_ci else: 41719ea8026Sopenharmony_ci for k in fields: 41819ea8026Sopenharmony_ci header.append('o'+k) 41919ea8026Sopenharmony_ci for k in fields: 42019ea8026Sopenharmony_ci header.append('n'+k) 42119ea8026Sopenharmony_ci for k in fields: 42219ea8026Sopenharmony_ci header.append('d'+k) 42319ea8026Sopenharmony_ci header.append('') 42419ea8026Sopenharmony_ci lines.append(header) 42519ea8026Sopenharmony_ci 42619ea8026Sopenharmony_ci def table_entry(name, r, diff_r=None, ratios=[]): 42719ea8026Sopenharmony_ci entry = [] 42819ea8026Sopenharmony_ci entry.append(name) 42919ea8026Sopenharmony_ci if diff_results is None: 43019ea8026Sopenharmony_ci for k in fields: 43119ea8026Sopenharmony_ci entry.append(getattr(r, k).table() 43219ea8026Sopenharmony_ci if getattr(r, k, None) is not None 43319ea8026Sopenharmony_ci else types[k].none) 43419ea8026Sopenharmony_ci elif percent: 43519ea8026Sopenharmony_ci for k in fields: 43619ea8026Sopenharmony_ci entry.append(getattr(r, k).diff_table() 43719ea8026Sopenharmony_ci if getattr(r, k, None) is not None 43819ea8026Sopenharmony_ci else types[k].diff_none) 43919ea8026Sopenharmony_ci else: 44019ea8026Sopenharmony_ci for k in fields: 44119ea8026Sopenharmony_ci entry.append(getattr(diff_r, k).diff_table() 44219ea8026Sopenharmony_ci if getattr(diff_r, k, None) is not None 44319ea8026Sopenharmony_ci else types[k].diff_none) 44419ea8026Sopenharmony_ci for k in fields: 44519ea8026Sopenharmony_ci entry.append(getattr(r, k).diff_table() 44619ea8026Sopenharmony_ci if getattr(r, k, None) is not None 44719ea8026Sopenharmony_ci else types[k].diff_none) 44819ea8026Sopenharmony_ci for k in fields: 44919ea8026Sopenharmony_ci entry.append(types[k].diff_diff( 45019ea8026Sopenharmony_ci getattr(r, k, None), 45119ea8026Sopenharmony_ci getattr(diff_r, k, None))) 45219ea8026Sopenharmony_ci if diff_results is None: 45319ea8026Sopenharmony_ci entry.append('') 45419ea8026Sopenharmony_ci elif percent: 45519ea8026Sopenharmony_ci entry.append(' (%s)' % ', '.join( 45619ea8026Sopenharmony_ci '+∞%' if t == +m.inf 45719ea8026Sopenharmony_ci else '-∞%' if t == -m.inf 45819ea8026Sopenharmony_ci else '%+.1f%%' % (100*t) 45919ea8026Sopenharmony_ci for t in ratios)) 46019ea8026Sopenharmony_ci else: 46119ea8026Sopenharmony_ci entry.append(' (%s)' % ', '.join( 46219ea8026Sopenharmony_ci '+∞%' if t == +m.inf 46319ea8026Sopenharmony_ci else '-∞%' if t == -m.inf 46419ea8026Sopenharmony_ci else '%+.1f%%' % (100*t) 46519ea8026Sopenharmony_ci for t in ratios 46619ea8026Sopenharmony_ci if t) 46719ea8026Sopenharmony_ci if any(ratios) else '') 46819ea8026Sopenharmony_ci return entry 46919ea8026Sopenharmony_ci 47019ea8026Sopenharmony_ci # entries 47119ea8026Sopenharmony_ci if not summary: 47219ea8026Sopenharmony_ci for name in names: 47319ea8026Sopenharmony_ci r = table.get(name) 47419ea8026Sopenharmony_ci if diff_results is None: 47519ea8026Sopenharmony_ci diff_r = None 47619ea8026Sopenharmony_ci ratios = None 47719ea8026Sopenharmony_ci else: 47819ea8026Sopenharmony_ci diff_r = diff_table.get(name) 47919ea8026Sopenharmony_ci ratios = [ 48019ea8026Sopenharmony_ci types[k].ratio( 48119ea8026Sopenharmony_ci getattr(r, k, None), 48219ea8026Sopenharmony_ci getattr(diff_r, k, None)) 48319ea8026Sopenharmony_ci for k in fields] 48419ea8026Sopenharmony_ci if not all_ and not any(ratios): 48519ea8026Sopenharmony_ci continue 48619ea8026Sopenharmony_ci lines.append(table_entry(name, r, diff_r, ratios)) 48719ea8026Sopenharmony_ci 48819ea8026Sopenharmony_ci # total 48919ea8026Sopenharmony_ci r = next(iter(fold(Result, results, by=[])), None) 49019ea8026Sopenharmony_ci if diff_results is None: 49119ea8026Sopenharmony_ci diff_r = None 49219ea8026Sopenharmony_ci ratios = None 49319ea8026Sopenharmony_ci else: 49419ea8026Sopenharmony_ci diff_r = next(iter(fold(Result, diff_results, by=[])), None) 49519ea8026Sopenharmony_ci ratios = [ 49619ea8026Sopenharmony_ci types[k].ratio( 49719ea8026Sopenharmony_ci getattr(r, k, None), 49819ea8026Sopenharmony_ci getattr(diff_r, k, None)) 49919ea8026Sopenharmony_ci for k in fields] 50019ea8026Sopenharmony_ci lines.append(table_entry('TOTAL', r, diff_r, ratios)) 50119ea8026Sopenharmony_ci 50219ea8026Sopenharmony_ci # find the best widths, note that column 0 contains the names and column -1 50319ea8026Sopenharmony_ci # the ratios, so those are handled a bit differently 50419ea8026Sopenharmony_ci widths = [ 50519ea8026Sopenharmony_ci ((max(it.chain([w], (len(l[i]) for l in lines)))+1+4-1)//4)*4-1 50619ea8026Sopenharmony_ci for w, i in zip( 50719ea8026Sopenharmony_ci it.chain([23], it.repeat(7)), 50819ea8026Sopenharmony_ci range(len(lines[0])-1))] 50919ea8026Sopenharmony_ci 51019ea8026Sopenharmony_ci # print our table 51119ea8026Sopenharmony_ci for line in lines: 51219ea8026Sopenharmony_ci print('%-*s %s%s' % ( 51319ea8026Sopenharmony_ci widths[0], line[0], 51419ea8026Sopenharmony_ci ' '.join('%*s' % (w, x) 51519ea8026Sopenharmony_ci for w, x in zip(widths[1:], line[1:-1])), 51619ea8026Sopenharmony_ci line[-1])) 51719ea8026Sopenharmony_ci 51819ea8026Sopenharmony_ci 51919ea8026Sopenharmony_cidef main(obj_paths, *, 52019ea8026Sopenharmony_ci by=None, 52119ea8026Sopenharmony_ci fields=None, 52219ea8026Sopenharmony_ci defines=None, 52319ea8026Sopenharmony_ci sort=None, 52419ea8026Sopenharmony_ci **args): 52519ea8026Sopenharmony_ci # find sizes 52619ea8026Sopenharmony_ci if not args.get('use', None): 52719ea8026Sopenharmony_ci results = collect(obj_paths, **args) 52819ea8026Sopenharmony_ci else: 52919ea8026Sopenharmony_ci results = [] 53019ea8026Sopenharmony_ci with openio(args['use']) as f: 53119ea8026Sopenharmony_ci reader = csv.DictReader(f, restval='') 53219ea8026Sopenharmony_ci for r in reader: 53319ea8026Sopenharmony_ci try: 53419ea8026Sopenharmony_ci results.append(DataResult( 53519ea8026Sopenharmony_ci **{k: r[k] for k in DataResult._by 53619ea8026Sopenharmony_ci if k in r and r[k].strip()}, 53719ea8026Sopenharmony_ci **{k: r['data_'+k] for k in DataResult._fields 53819ea8026Sopenharmony_ci if 'data_'+k in r and r['data_'+k].strip()})) 53919ea8026Sopenharmony_ci except TypeError: 54019ea8026Sopenharmony_ci pass 54119ea8026Sopenharmony_ci 54219ea8026Sopenharmony_ci # fold 54319ea8026Sopenharmony_ci results = fold(DataResult, results, by=by, defines=defines) 54419ea8026Sopenharmony_ci 54519ea8026Sopenharmony_ci # sort, note that python's sort is stable 54619ea8026Sopenharmony_ci results.sort() 54719ea8026Sopenharmony_ci if sort: 54819ea8026Sopenharmony_ci for k, reverse in reversed(sort): 54919ea8026Sopenharmony_ci results.sort( 55019ea8026Sopenharmony_ci key=lambda r: tuple( 55119ea8026Sopenharmony_ci (getattr(r, k),) if getattr(r, k) is not None else () 55219ea8026Sopenharmony_ci for k in ([k] if k else DataResult._sort)), 55319ea8026Sopenharmony_ci reverse=reverse ^ (not k or k in DataResult._fields)) 55419ea8026Sopenharmony_ci 55519ea8026Sopenharmony_ci # write results to CSV 55619ea8026Sopenharmony_ci if args.get('output'): 55719ea8026Sopenharmony_ci with openio(args['output'], 'w') as f: 55819ea8026Sopenharmony_ci writer = csv.DictWriter(f, 55919ea8026Sopenharmony_ci (by if by is not None else DataResult._by) 56019ea8026Sopenharmony_ci + ['data_'+k for k in ( 56119ea8026Sopenharmony_ci fields if fields is not None else DataResult._fields)]) 56219ea8026Sopenharmony_ci writer.writeheader() 56319ea8026Sopenharmony_ci for r in results: 56419ea8026Sopenharmony_ci writer.writerow( 56519ea8026Sopenharmony_ci {k: getattr(r, k) for k in ( 56619ea8026Sopenharmony_ci by if by is not None else DataResult._by)} 56719ea8026Sopenharmony_ci | {'data_'+k: getattr(r, k) for k in ( 56819ea8026Sopenharmony_ci fields if fields is not None else DataResult._fields)}) 56919ea8026Sopenharmony_ci 57019ea8026Sopenharmony_ci # find previous results? 57119ea8026Sopenharmony_ci if args.get('diff'): 57219ea8026Sopenharmony_ci diff_results = [] 57319ea8026Sopenharmony_ci try: 57419ea8026Sopenharmony_ci with openio(args['diff']) as f: 57519ea8026Sopenharmony_ci reader = csv.DictReader(f, restval='') 57619ea8026Sopenharmony_ci for r in reader: 57719ea8026Sopenharmony_ci if not any('data_'+k in r and r['data_'+k].strip() 57819ea8026Sopenharmony_ci for k in DataResult._fields): 57919ea8026Sopenharmony_ci continue 58019ea8026Sopenharmony_ci try: 58119ea8026Sopenharmony_ci diff_results.append(DataResult( 58219ea8026Sopenharmony_ci **{k: r[k] for k in DataResult._by 58319ea8026Sopenharmony_ci if k in r and r[k].strip()}, 58419ea8026Sopenharmony_ci **{k: r['data_'+k] for k in DataResult._fields 58519ea8026Sopenharmony_ci if 'data_'+k in r and r['data_'+k].strip()})) 58619ea8026Sopenharmony_ci except TypeError: 58719ea8026Sopenharmony_ci pass 58819ea8026Sopenharmony_ci except FileNotFoundError: 58919ea8026Sopenharmony_ci pass 59019ea8026Sopenharmony_ci 59119ea8026Sopenharmony_ci # fold 59219ea8026Sopenharmony_ci diff_results = fold(DataResult, diff_results, by=by, defines=defines) 59319ea8026Sopenharmony_ci 59419ea8026Sopenharmony_ci # print table 59519ea8026Sopenharmony_ci if not args.get('quiet'): 59619ea8026Sopenharmony_ci table(DataResult, results, 59719ea8026Sopenharmony_ci diff_results if args.get('diff') else None, 59819ea8026Sopenharmony_ci by=by if by is not None else ['function'], 59919ea8026Sopenharmony_ci fields=fields, 60019ea8026Sopenharmony_ci sort=sort, 60119ea8026Sopenharmony_ci **args) 60219ea8026Sopenharmony_ci 60319ea8026Sopenharmony_ci 60419ea8026Sopenharmony_ciif __name__ == "__main__": 60519ea8026Sopenharmony_ci import argparse 60619ea8026Sopenharmony_ci import sys 60719ea8026Sopenharmony_ci parser = argparse.ArgumentParser( 60819ea8026Sopenharmony_ci description="Find data size at the function level.", 60919ea8026Sopenharmony_ci allow_abbrev=False) 61019ea8026Sopenharmony_ci parser.add_argument( 61119ea8026Sopenharmony_ci 'obj_paths', 61219ea8026Sopenharmony_ci nargs='*', 61319ea8026Sopenharmony_ci help="Input *.o files.") 61419ea8026Sopenharmony_ci parser.add_argument( 61519ea8026Sopenharmony_ci '-v', '--verbose', 61619ea8026Sopenharmony_ci action='store_true', 61719ea8026Sopenharmony_ci help="Output commands that run behind the scenes.") 61819ea8026Sopenharmony_ci parser.add_argument( 61919ea8026Sopenharmony_ci '-q', '--quiet', 62019ea8026Sopenharmony_ci action='store_true', 62119ea8026Sopenharmony_ci help="Don't show anything, useful with -o.") 62219ea8026Sopenharmony_ci parser.add_argument( 62319ea8026Sopenharmony_ci '-o', '--output', 62419ea8026Sopenharmony_ci help="Specify CSV file to store results.") 62519ea8026Sopenharmony_ci parser.add_argument( 62619ea8026Sopenharmony_ci '-u', '--use', 62719ea8026Sopenharmony_ci help="Don't parse anything, use this CSV file.") 62819ea8026Sopenharmony_ci parser.add_argument( 62919ea8026Sopenharmony_ci '-d', '--diff', 63019ea8026Sopenharmony_ci help="Specify CSV file to diff against.") 63119ea8026Sopenharmony_ci parser.add_argument( 63219ea8026Sopenharmony_ci '-a', '--all', 63319ea8026Sopenharmony_ci action='store_true', 63419ea8026Sopenharmony_ci help="Show all, not just the ones that changed.") 63519ea8026Sopenharmony_ci parser.add_argument( 63619ea8026Sopenharmony_ci '-p', '--percent', 63719ea8026Sopenharmony_ci action='store_true', 63819ea8026Sopenharmony_ci help="Only show percentage change, not a full diff.") 63919ea8026Sopenharmony_ci parser.add_argument( 64019ea8026Sopenharmony_ci '-b', '--by', 64119ea8026Sopenharmony_ci action='append', 64219ea8026Sopenharmony_ci choices=DataResult._by, 64319ea8026Sopenharmony_ci help="Group by this field.") 64419ea8026Sopenharmony_ci parser.add_argument( 64519ea8026Sopenharmony_ci '-f', '--field', 64619ea8026Sopenharmony_ci dest='fields', 64719ea8026Sopenharmony_ci action='append', 64819ea8026Sopenharmony_ci choices=DataResult._fields, 64919ea8026Sopenharmony_ci help="Show this field.") 65019ea8026Sopenharmony_ci parser.add_argument( 65119ea8026Sopenharmony_ci '-D', '--define', 65219ea8026Sopenharmony_ci dest='defines', 65319ea8026Sopenharmony_ci action='append', 65419ea8026Sopenharmony_ci type=lambda x: (lambda k,v: (k, set(v.split(','))))(*x.split('=', 1)), 65519ea8026Sopenharmony_ci help="Only include results where this field is this value.") 65619ea8026Sopenharmony_ci class AppendSort(argparse.Action): 65719ea8026Sopenharmony_ci def __call__(self, parser, namespace, value, option): 65819ea8026Sopenharmony_ci if namespace.sort is None: 65919ea8026Sopenharmony_ci namespace.sort = [] 66019ea8026Sopenharmony_ci namespace.sort.append((value, True if option == '-S' else False)) 66119ea8026Sopenharmony_ci parser.add_argument( 66219ea8026Sopenharmony_ci '-s', '--sort', 66319ea8026Sopenharmony_ci nargs='?', 66419ea8026Sopenharmony_ci action=AppendSort, 66519ea8026Sopenharmony_ci help="Sort by this field.") 66619ea8026Sopenharmony_ci parser.add_argument( 66719ea8026Sopenharmony_ci '-S', '--reverse-sort', 66819ea8026Sopenharmony_ci nargs='?', 66919ea8026Sopenharmony_ci action=AppendSort, 67019ea8026Sopenharmony_ci help="Sort by this field, but backwards.") 67119ea8026Sopenharmony_ci parser.add_argument( 67219ea8026Sopenharmony_ci '-Y', '--summary', 67319ea8026Sopenharmony_ci action='store_true', 67419ea8026Sopenharmony_ci help="Only show the total.") 67519ea8026Sopenharmony_ci parser.add_argument( 67619ea8026Sopenharmony_ci '-F', '--source', 67719ea8026Sopenharmony_ci dest='sources', 67819ea8026Sopenharmony_ci action='append', 67919ea8026Sopenharmony_ci help="Only consider definitions in this file. Defaults to anything " 68019ea8026Sopenharmony_ci "in the current directory.") 68119ea8026Sopenharmony_ci parser.add_argument( 68219ea8026Sopenharmony_ci '--everything', 68319ea8026Sopenharmony_ci action='store_true', 68419ea8026Sopenharmony_ci help="Include builtin and libc specific symbols.") 68519ea8026Sopenharmony_ci parser.add_argument( 68619ea8026Sopenharmony_ci '--nm-types', 68719ea8026Sopenharmony_ci default=NM_TYPES, 68819ea8026Sopenharmony_ci help="Type of symbols to report, this uses the same single-character " 68919ea8026Sopenharmony_ci "type-names emitted by nm. Defaults to %r." % NM_TYPES) 69019ea8026Sopenharmony_ci parser.add_argument( 69119ea8026Sopenharmony_ci '--nm-path', 69219ea8026Sopenharmony_ci type=lambda x: x.split(), 69319ea8026Sopenharmony_ci default=NM_PATH, 69419ea8026Sopenharmony_ci help="Path to the nm executable, may include flags. " 69519ea8026Sopenharmony_ci "Defaults to %r." % NM_PATH) 69619ea8026Sopenharmony_ci parser.add_argument( 69719ea8026Sopenharmony_ci '--objdump-path', 69819ea8026Sopenharmony_ci type=lambda x: x.split(), 69919ea8026Sopenharmony_ci default=OBJDUMP_PATH, 70019ea8026Sopenharmony_ci help="Path to the objdump executable, may include flags. " 70119ea8026Sopenharmony_ci "Defaults to %r." % OBJDUMP_PATH) 70219ea8026Sopenharmony_ci sys.exit(main(**{k: v 70319ea8026Sopenharmony_ci for k, v in vars(parser.parse_intermixed_args()).items() 70419ea8026Sopenharmony_ci if v is not None})) 705