119ea8026Sopenharmony_ci#!/usr/bin/env python3 219ea8026Sopenharmony_ci# 319ea8026Sopenharmony_ci# Script to aggregate and report Linux perf results. 419ea8026Sopenharmony_ci# 519ea8026Sopenharmony_ci# Example: 619ea8026Sopenharmony_ci# ./scripts/perf.py -R -obench.perf ./runners/bench_runner 719ea8026Sopenharmony_ci# ./scripts/perf.py bench.perf -j -Flfs.c -Flfs_util.c -Scycles 819ea8026Sopenharmony_ci# 919ea8026Sopenharmony_ci# Copyright (c) 2022, The littlefs authors. 1019ea8026Sopenharmony_ci# SPDX-License-Identifier: BSD-3-Clause 1119ea8026Sopenharmony_ci# 1219ea8026Sopenharmony_ci 1319ea8026Sopenharmony_ciimport bisect 1419ea8026Sopenharmony_ciimport collections as co 1519ea8026Sopenharmony_ciimport csv 1619ea8026Sopenharmony_ciimport errno 1719ea8026Sopenharmony_ciimport fcntl 1819ea8026Sopenharmony_ciimport functools as ft 1919ea8026Sopenharmony_ciimport itertools as it 2019ea8026Sopenharmony_ciimport math as m 2119ea8026Sopenharmony_ciimport multiprocessing as mp 2219ea8026Sopenharmony_ciimport os 2319ea8026Sopenharmony_ciimport re 2419ea8026Sopenharmony_ciimport shlex 2519ea8026Sopenharmony_ciimport shutil 2619ea8026Sopenharmony_ciimport subprocess as sp 2719ea8026Sopenharmony_ciimport tempfile 2819ea8026Sopenharmony_ciimport zipfile 2919ea8026Sopenharmony_ci 3019ea8026Sopenharmony_ci# TODO support non-zip perf results? 3119ea8026Sopenharmony_ci 3219ea8026Sopenharmony_ci 3319ea8026Sopenharmony_ciPERF_PATH = ['perf'] 3419ea8026Sopenharmony_ciPERF_EVENTS = 'cycles,branch-misses,branches,cache-misses,cache-references' 3519ea8026Sopenharmony_ciPERF_FREQ = 100 3619ea8026Sopenharmony_ciOBJDUMP_PATH = ['objdump'] 3719ea8026Sopenharmony_ciTHRESHOLD = (0.5, 0.85) 3819ea8026Sopenharmony_ci 3919ea8026Sopenharmony_ci 4019ea8026Sopenharmony_ci# integer fields 4119ea8026Sopenharmony_ciclass Int(co.namedtuple('Int', 'x')): 4219ea8026Sopenharmony_ci __slots__ = () 4319ea8026Sopenharmony_ci def __new__(cls, x=0): 4419ea8026Sopenharmony_ci if isinstance(x, Int): 4519ea8026Sopenharmony_ci return x 4619ea8026Sopenharmony_ci if isinstance(x, str): 4719ea8026Sopenharmony_ci try: 4819ea8026Sopenharmony_ci x = int(x, 0) 4919ea8026Sopenharmony_ci except ValueError: 5019ea8026Sopenharmony_ci # also accept +-∞ and +-inf 5119ea8026Sopenharmony_ci if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x): 5219ea8026Sopenharmony_ci x = m.inf 5319ea8026Sopenharmony_ci elif re.match('^\s*-\s*(?:∞|inf)\s*$', x): 5419ea8026Sopenharmony_ci x = -m.inf 5519ea8026Sopenharmony_ci else: 5619ea8026Sopenharmony_ci raise 5719ea8026Sopenharmony_ci assert isinstance(x, int) or m.isinf(x), x 5819ea8026Sopenharmony_ci return super().__new__(cls, x) 5919ea8026Sopenharmony_ci 6019ea8026Sopenharmony_ci def __str__(self): 6119ea8026Sopenharmony_ci if self.x == m.inf: 6219ea8026Sopenharmony_ci return '∞' 6319ea8026Sopenharmony_ci elif self.x == -m.inf: 6419ea8026Sopenharmony_ci return '-∞' 6519ea8026Sopenharmony_ci else: 6619ea8026Sopenharmony_ci return str(self.x) 6719ea8026Sopenharmony_ci 6819ea8026Sopenharmony_ci def __int__(self): 6919ea8026Sopenharmony_ci assert not m.isinf(self.x) 7019ea8026Sopenharmony_ci return self.x 7119ea8026Sopenharmony_ci 7219ea8026Sopenharmony_ci def __float__(self): 7319ea8026Sopenharmony_ci return float(self.x) 7419ea8026Sopenharmony_ci 7519ea8026Sopenharmony_ci none = '%7s' % '-' 7619ea8026Sopenharmony_ci def table(self): 7719ea8026Sopenharmony_ci return '%7s' % (self,) 7819ea8026Sopenharmony_ci 7919ea8026Sopenharmony_ci diff_none = '%7s' % '-' 8019ea8026Sopenharmony_ci diff_table = table 8119ea8026Sopenharmony_ci 8219ea8026Sopenharmony_ci def diff_diff(self, other): 8319ea8026Sopenharmony_ci new = self.x if self else 0 8419ea8026Sopenharmony_ci old = other.x if other else 0 8519ea8026Sopenharmony_ci diff = new - old 8619ea8026Sopenharmony_ci if diff == +m.inf: 8719ea8026Sopenharmony_ci return '%7s' % '+∞' 8819ea8026Sopenharmony_ci elif diff == -m.inf: 8919ea8026Sopenharmony_ci return '%7s' % '-∞' 9019ea8026Sopenharmony_ci else: 9119ea8026Sopenharmony_ci return '%+7d' % diff 9219ea8026Sopenharmony_ci 9319ea8026Sopenharmony_ci def ratio(self, other): 9419ea8026Sopenharmony_ci new = self.x if self else 0 9519ea8026Sopenharmony_ci old = other.x if other else 0 9619ea8026Sopenharmony_ci if m.isinf(new) and m.isinf(old): 9719ea8026Sopenharmony_ci return 0.0 9819ea8026Sopenharmony_ci elif m.isinf(new): 9919ea8026Sopenharmony_ci return +m.inf 10019ea8026Sopenharmony_ci elif m.isinf(old): 10119ea8026Sopenharmony_ci return -m.inf 10219ea8026Sopenharmony_ci elif not old and not new: 10319ea8026Sopenharmony_ci return 0.0 10419ea8026Sopenharmony_ci elif not old: 10519ea8026Sopenharmony_ci return 1.0 10619ea8026Sopenharmony_ci else: 10719ea8026Sopenharmony_ci return (new-old) / old 10819ea8026Sopenharmony_ci 10919ea8026Sopenharmony_ci def __add__(self, other): 11019ea8026Sopenharmony_ci return self.__class__(self.x + other.x) 11119ea8026Sopenharmony_ci 11219ea8026Sopenharmony_ci def __sub__(self, other): 11319ea8026Sopenharmony_ci return self.__class__(self.x - other.x) 11419ea8026Sopenharmony_ci 11519ea8026Sopenharmony_ci def __mul__(self, other): 11619ea8026Sopenharmony_ci return self.__class__(self.x * other.x) 11719ea8026Sopenharmony_ci 11819ea8026Sopenharmony_ci# perf results 11919ea8026Sopenharmony_ciclass PerfResult(co.namedtuple('PerfResult', [ 12019ea8026Sopenharmony_ci 'file', 'function', 'line', 12119ea8026Sopenharmony_ci 'cycles', 'bmisses', 'branches', 'cmisses', 'caches', 12219ea8026Sopenharmony_ci 'children'])): 12319ea8026Sopenharmony_ci _by = ['file', 'function', 'line'] 12419ea8026Sopenharmony_ci _fields = ['cycles', 'bmisses', 'branches', 'cmisses', 'caches'] 12519ea8026Sopenharmony_ci _sort = ['cycles', 'bmisses', 'cmisses', 'branches', 'caches'] 12619ea8026Sopenharmony_ci _types = { 12719ea8026Sopenharmony_ci 'cycles': Int, 12819ea8026Sopenharmony_ci 'bmisses': Int, 'branches': Int, 12919ea8026Sopenharmony_ci 'cmisses': Int, 'caches': Int} 13019ea8026Sopenharmony_ci 13119ea8026Sopenharmony_ci __slots__ = () 13219ea8026Sopenharmony_ci def __new__(cls, file='', function='', line=0, 13319ea8026Sopenharmony_ci cycles=0, bmisses=0, branches=0, cmisses=0, caches=0, 13419ea8026Sopenharmony_ci children=[]): 13519ea8026Sopenharmony_ci return super().__new__(cls, file, function, int(Int(line)), 13619ea8026Sopenharmony_ci Int(cycles), Int(bmisses), Int(branches), Int(cmisses), Int(caches), 13719ea8026Sopenharmony_ci children) 13819ea8026Sopenharmony_ci 13919ea8026Sopenharmony_ci def __add__(self, other): 14019ea8026Sopenharmony_ci return PerfResult(self.file, self.function, self.line, 14119ea8026Sopenharmony_ci self.cycles + other.cycles, 14219ea8026Sopenharmony_ci self.bmisses + other.bmisses, 14319ea8026Sopenharmony_ci self.branches + other.branches, 14419ea8026Sopenharmony_ci self.cmisses + other.cmisses, 14519ea8026Sopenharmony_ci self.caches + other.caches, 14619ea8026Sopenharmony_ci self.children + other.children) 14719ea8026Sopenharmony_ci 14819ea8026Sopenharmony_ci 14919ea8026Sopenharmony_cidef openio(path, mode='r', buffering=-1): 15019ea8026Sopenharmony_ci # allow '-' for stdin/stdout 15119ea8026Sopenharmony_ci if path == '-': 15219ea8026Sopenharmony_ci if mode == 'r': 15319ea8026Sopenharmony_ci return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering) 15419ea8026Sopenharmony_ci else: 15519ea8026Sopenharmony_ci return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering) 15619ea8026Sopenharmony_ci else: 15719ea8026Sopenharmony_ci return open(path, mode, buffering) 15819ea8026Sopenharmony_ci 15919ea8026Sopenharmony_ci# run perf as a subprocess, storing measurements into a zip file 16019ea8026Sopenharmony_cidef record(command, *, 16119ea8026Sopenharmony_ci output=None, 16219ea8026Sopenharmony_ci perf_freq=PERF_FREQ, 16319ea8026Sopenharmony_ci perf_period=None, 16419ea8026Sopenharmony_ci perf_events=PERF_EVENTS, 16519ea8026Sopenharmony_ci perf_path=PERF_PATH, 16619ea8026Sopenharmony_ci **args): 16719ea8026Sopenharmony_ci # create a temporary file for perf to write to, as far as I can tell 16819ea8026Sopenharmony_ci # this is strictly needed because perf's pipe-mode only works with stdout 16919ea8026Sopenharmony_ci with tempfile.NamedTemporaryFile('rb') as f: 17019ea8026Sopenharmony_ci # figure out our perf invocation 17119ea8026Sopenharmony_ci perf = perf_path + list(filter(None, [ 17219ea8026Sopenharmony_ci 'record', 17319ea8026Sopenharmony_ci '-F%s' % perf_freq 17419ea8026Sopenharmony_ci if perf_freq is not None 17519ea8026Sopenharmony_ci and perf_period is None else None, 17619ea8026Sopenharmony_ci '-c%s' % perf_period 17719ea8026Sopenharmony_ci if perf_period is not None else None, 17819ea8026Sopenharmony_ci '-B', 17919ea8026Sopenharmony_ci '-g', 18019ea8026Sopenharmony_ci '--all-user', 18119ea8026Sopenharmony_ci '-e%s' % perf_events, 18219ea8026Sopenharmony_ci '-o%s' % f.name])) 18319ea8026Sopenharmony_ci 18419ea8026Sopenharmony_ci # run our command 18519ea8026Sopenharmony_ci try: 18619ea8026Sopenharmony_ci if args.get('verbose'): 18719ea8026Sopenharmony_ci print(' '.join(shlex.quote(c) for c in perf + command)) 18819ea8026Sopenharmony_ci err = sp.call(perf + command, close_fds=False) 18919ea8026Sopenharmony_ci 19019ea8026Sopenharmony_ci except KeyboardInterrupt: 19119ea8026Sopenharmony_ci err = errno.EOWNERDEAD 19219ea8026Sopenharmony_ci 19319ea8026Sopenharmony_ci # synchronize access 19419ea8026Sopenharmony_ci z = os.open(output, os.O_RDWR | os.O_CREAT) 19519ea8026Sopenharmony_ci fcntl.flock(z, fcntl.LOCK_EX) 19619ea8026Sopenharmony_ci 19719ea8026Sopenharmony_ci # copy measurements into our zip file 19819ea8026Sopenharmony_ci with os.fdopen(z, 'r+b') as z: 19919ea8026Sopenharmony_ci with zipfile.ZipFile(z, 'a', 20019ea8026Sopenharmony_ci compression=zipfile.ZIP_DEFLATED, 20119ea8026Sopenharmony_ci compresslevel=1) as z: 20219ea8026Sopenharmony_ci with z.open('perf.%d' % os.getpid(), 'w') as g: 20319ea8026Sopenharmony_ci shutil.copyfileobj(f, g) 20419ea8026Sopenharmony_ci 20519ea8026Sopenharmony_ci # forward the return code 20619ea8026Sopenharmony_ci return err 20719ea8026Sopenharmony_ci 20819ea8026Sopenharmony_ci 20919ea8026Sopenharmony_ci# try to only process each dso onceS 21019ea8026Sopenharmony_ci# 21119ea8026Sopenharmony_ci# note this only caches with the non-keyword arguments 21219ea8026Sopenharmony_cidef multiprocessing_cache(f): 21319ea8026Sopenharmony_ci local_cache = {} 21419ea8026Sopenharmony_ci manager = mp.Manager() 21519ea8026Sopenharmony_ci global_cache = manager.dict() 21619ea8026Sopenharmony_ci lock = mp.Lock() 21719ea8026Sopenharmony_ci 21819ea8026Sopenharmony_ci def multiprocessing_cache(*args, **kwargs): 21919ea8026Sopenharmony_ci # check local cache? 22019ea8026Sopenharmony_ci if args in local_cache: 22119ea8026Sopenharmony_ci return local_cache[args] 22219ea8026Sopenharmony_ci # check global cache? 22319ea8026Sopenharmony_ci with lock: 22419ea8026Sopenharmony_ci if args in global_cache: 22519ea8026Sopenharmony_ci v = global_cache[args] 22619ea8026Sopenharmony_ci local_cache[args] = v 22719ea8026Sopenharmony_ci return v 22819ea8026Sopenharmony_ci # fall back to calling the function 22919ea8026Sopenharmony_ci v = f(*args, **kwargs) 23019ea8026Sopenharmony_ci global_cache[args] = v 23119ea8026Sopenharmony_ci local_cache[args] = v 23219ea8026Sopenharmony_ci return v 23319ea8026Sopenharmony_ci 23419ea8026Sopenharmony_ci return multiprocessing_cache 23519ea8026Sopenharmony_ci 23619ea8026Sopenharmony_ci@multiprocessing_cache 23719ea8026Sopenharmony_cidef collect_syms_and_lines(obj_path, *, 23819ea8026Sopenharmony_ci objdump_path=None, 23919ea8026Sopenharmony_ci **args): 24019ea8026Sopenharmony_ci symbol_pattern = re.compile( 24119ea8026Sopenharmony_ci '^(?P<addr>[0-9a-fA-F]+)' 24219ea8026Sopenharmony_ci '\s+.*' 24319ea8026Sopenharmony_ci '\s+(?P<size>[0-9a-fA-F]+)' 24419ea8026Sopenharmony_ci '\s+(?P<name>[^\s]+)\s*$') 24519ea8026Sopenharmony_ci line_pattern = re.compile( 24619ea8026Sopenharmony_ci '^\s+(?:' 24719ea8026Sopenharmony_ci # matches dir/file table 24819ea8026Sopenharmony_ci '(?P<no>[0-9]+)' 24919ea8026Sopenharmony_ci '(?:\s+(?P<dir>[0-9]+))?' 25019ea8026Sopenharmony_ci '\s+.*' 25119ea8026Sopenharmony_ci '\s+(?P<path>[^\s]+)' 25219ea8026Sopenharmony_ci # matches line opcodes 25319ea8026Sopenharmony_ci '|' '\[[^\]]*\]\s+' 25419ea8026Sopenharmony_ci '(?:' 25519ea8026Sopenharmony_ci '(?P<op_special>Special)' 25619ea8026Sopenharmony_ci '|' '(?P<op_copy>Copy)' 25719ea8026Sopenharmony_ci '|' '(?P<op_end>End of Sequence)' 25819ea8026Sopenharmony_ci '|' 'File .*?to (?:entry )?(?P<op_file>\d+)' 25919ea8026Sopenharmony_ci '|' 'Line .*?to (?P<op_line>[0-9]+)' 26019ea8026Sopenharmony_ci '|' '(?:Address|PC) .*?to (?P<op_addr>[0x0-9a-fA-F]+)' 26119ea8026Sopenharmony_ci '|' '.' ')*' 26219ea8026Sopenharmony_ci ')$', re.IGNORECASE) 26319ea8026Sopenharmony_ci 26419ea8026Sopenharmony_ci # figure out symbol addresses and file+line ranges 26519ea8026Sopenharmony_ci syms = {} 26619ea8026Sopenharmony_ci sym_at = [] 26719ea8026Sopenharmony_ci cmd = objdump_path + ['-t', obj_path] 26819ea8026Sopenharmony_ci if args.get('verbose'): 26919ea8026Sopenharmony_ci print(' '.join(shlex.quote(c) for c in cmd)) 27019ea8026Sopenharmony_ci proc = sp.Popen(cmd, 27119ea8026Sopenharmony_ci stdout=sp.PIPE, 27219ea8026Sopenharmony_ci stderr=sp.PIPE if not args.get('verbose') else None, 27319ea8026Sopenharmony_ci universal_newlines=True, 27419ea8026Sopenharmony_ci errors='replace', 27519ea8026Sopenharmony_ci close_fds=False) 27619ea8026Sopenharmony_ci for line in proc.stdout: 27719ea8026Sopenharmony_ci m = symbol_pattern.match(line) 27819ea8026Sopenharmony_ci if m: 27919ea8026Sopenharmony_ci name = m.group('name') 28019ea8026Sopenharmony_ci addr = int(m.group('addr'), 16) 28119ea8026Sopenharmony_ci size = int(m.group('size'), 16) 28219ea8026Sopenharmony_ci # ignore zero-sized symbols 28319ea8026Sopenharmony_ci if not size: 28419ea8026Sopenharmony_ci continue 28519ea8026Sopenharmony_ci # note multiple symbols can share a name 28619ea8026Sopenharmony_ci if name not in syms: 28719ea8026Sopenharmony_ci syms[name] = set() 28819ea8026Sopenharmony_ci syms[name].add((addr, size)) 28919ea8026Sopenharmony_ci sym_at.append((addr, name, size)) 29019ea8026Sopenharmony_ci proc.wait() 29119ea8026Sopenharmony_ci if proc.returncode != 0: 29219ea8026Sopenharmony_ci if not args.get('verbose'): 29319ea8026Sopenharmony_ci for line in proc.stderr: 29419ea8026Sopenharmony_ci sys.stdout.write(line) 29519ea8026Sopenharmony_ci # assume no debug-info on failure 29619ea8026Sopenharmony_ci pass 29719ea8026Sopenharmony_ci 29819ea8026Sopenharmony_ci # sort and keep largest/first when duplicates 29919ea8026Sopenharmony_ci sym_at.sort(key=lambda x: (x[0], -x[2], x[1])) 30019ea8026Sopenharmony_ci sym_at_ = [] 30119ea8026Sopenharmony_ci for addr, name, size in sym_at: 30219ea8026Sopenharmony_ci if len(sym_at_) == 0 or sym_at_[-1][0] != addr: 30319ea8026Sopenharmony_ci sym_at_.append((addr, name, size)) 30419ea8026Sopenharmony_ci sym_at = sym_at_ 30519ea8026Sopenharmony_ci 30619ea8026Sopenharmony_ci # state machine for dwarf line numbers, note that objdump's 30719ea8026Sopenharmony_ci # decodedline seems to have issues with multiple dir/file 30819ea8026Sopenharmony_ci # tables, which is why we need this 30919ea8026Sopenharmony_ci lines = [] 31019ea8026Sopenharmony_ci line_at = [] 31119ea8026Sopenharmony_ci dirs = {} 31219ea8026Sopenharmony_ci files = {} 31319ea8026Sopenharmony_ci op_file = 1 31419ea8026Sopenharmony_ci op_line = 1 31519ea8026Sopenharmony_ci op_addr = 0 31619ea8026Sopenharmony_ci cmd = objdump_path + ['--dwarf=rawline', obj_path] 31719ea8026Sopenharmony_ci if args.get('verbose'): 31819ea8026Sopenharmony_ci print(' '.join(shlex.quote(c) for c in cmd)) 31919ea8026Sopenharmony_ci proc = sp.Popen(cmd, 32019ea8026Sopenharmony_ci stdout=sp.PIPE, 32119ea8026Sopenharmony_ci stderr=sp.PIPE if not args.get('verbose') else None, 32219ea8026Sopenharmony_ci universal_newlines=True, 32319ea8026Sopenharmony_ci errors='replace', 32419ea8026Sopenharmony_ci close_fds=False) 32519ea8026Sopenharmony_ci for line in proc.stdout: 32619ea8026Sopenharmony_ci m = line_pattern.match(line) 32719ea8026Sopenharmony_ci if m: 32819ea8026Sopenharmony_ci if m.group('no') and not m.group('dir'): 32919ea8026Sopenharmony_ci # found a directory entry 33019ea8026Sopenharmony_ci dirs[int(m.group('no'))] = m.group('path') 33119ea8026Sopenharmony_ci elif m.group('no'): 33219ea8026Sopenharmony_ci # found a file entry 33319ea8026Sopenharmony_ci dir = int(m.group('dir')) 33419ea8026Sopenharmony_ci if dir in dirs: 33519ea8026Sopenharmony_ci files[int(m.group('no'))] = os.path.join( 33619ea8026Sopenharmony_ci dirs[dir], 33719ea8026Sopenharmony_ci m.group('path')) 33819ea8026Sopenharmony_ci else: 33919ea8026Sopenharmony_ci files[int(m.group('no'))] = m.group('path') 34019ea8026Sopenharmony_ci else: 34119ea8026Sopenharmony_ci # found a state machine update 34219ea8026Sopenharmony_ci if m.group('op_file'): 34319ea8026Sopenharmony_ci op_file = int(m.group('op_file'), 0) 34419ea8026Sopenharmony_ci if m.group('op_line'): 34519ea8026Sopenharmony_ci op_line = int(m.group('op_line'), 0) 34619ea8026Sopenharmony_ci if m.group('op_addr'): 34719ea8026Sopenharmony_ci op_addr = int(m.group('op_addr'), 0) 34819ea8026Sopenharmony_ci 34919ea8026Sopenharmony_ci if (m.group('op_special') 35019ea8026Sopenharmony_ci or m.group('op_copy') 35119ea8026Sopenharmony_ci or m.group('op_end')): 35219ea8026Sopenharmony_ci file = os.path.abspath(files.get(op_file, '?')) 35319ea8026Sopenharmony_ci lines.append((file, op_line, op_addr)) 35419ea8026Sopenharmony_ci line_at.append((op_addr, file, op_line)) 35519ea8026Sopenharmony_ci 35619ea8026Sopenharmony_ci if m.group('op_end'): 35719ea8026Sopenharmony_ci op_file = 1 35819ea8026Sopenharmony_ci op_line = 1 35919ea8026Sopenharmony_ci op_addr = 0 36019ea8026Sopenharmony_ci proc.wait() 36119ea8026Sopenharmony_ci if proc.returncode != 0: 36219ea8026Sopenharmony_ci if not args.get('verbose'): 36319ea8026Sopenharmony_ci for line in proc.stderr: 36419ea8026Sopenharmony_ci sys.stdout.write(line) 36519ea8026Sopenharmony_ci # assume no debug-info on failure 36619ea8026Sopenharmony_ci pass 36719ea8026Sopenharmony_ci 36819ea8026Sopenharmony_ci # sort and keep first when duplicates 36919ea8026Sopenharmony_ci lines.sort() 37019ea8026Sopenharmony_ci lines_ = [] 37119ea8026Sopenharmony_ci for file, line, addr in lines: 37219ea8026Sopenharmony_ci if len(lines_) == 0 or lines_[-1][0] != file or lines[-1][1] != line: 37319ea8026Sopenharmony_ci lines_.append((file, line, addr)) 37419ea8026Sopenharmony_ci lines = lines_ 37519ea8026Sopenharmony_ci 37619ea8026Sopenharmony_ci # sort and keep first when duplicates 37719ea8026Sopenharmony_ci line_at.sort() 37819ea8026Sopenharmony_ci line_at_ = [] 37919ea8026Sopenharmony_ci for addr, file, line in line_at: 38019ea8026Sopenharmony_ci if len(line_at_) == 0 or line_at_[-1][0] != addr: 38119ea8026Sopenharmony_ci line_at_.append((addr, file, line)) 38219ea8026Sopenharmony_ci line_at = line_at_ 38319ea8026Sopenharmony_ci 38419ea8026Sopenharmony_ci return syms, sym_at, lines, line_at 38519ea8026Sopenharmony_ci 38619ea8026Sopenharmony_ci 38719ea8026Sopenharmony_cidef collect_decompressed(path, *, 38819ea8026Sopenharmony_ci perf_path=PERF_PATH, 38919ea8026Sopenharmony_ci sources=None, 39019ea8026Sopenharmony_ci everything=False, 39119ea8026Sopenharmony_ci propagate=0, 39219ea8026Sopenharmony_ci depth=1, 39319ea8026Sopenharmony_ci **args): 39419ea8026Sopenharmony_ci sample_pattern = re.compile( 39519ea8026Sopenharmony_ci '(?P<comm>\w+)' 39619ea8026Sopenharmony_ci '\s+(?P<pid>\w+)' 39719ea8026Sopenharmony_ci '\s+(?P<time>[\w.]+):' 39819ea8026Sopenharmony_ci '\s*(?P<period>\w+)' 39919ea8026Sopenharmony_ci '\s+(?P<event>[^:]+):') 40019ea8026Sopenharmony_ci frame_pattern = re.compile( 40119ea8026Sopenharmony_ci '\s+(?P<addr>\w+)' 40219ea8026Sopenharmony_ci '\s+(?P<sym>[^\s\+]+)(?:\+(?P<off>\w+))?' 40319ea8026Sopenharmony_ci '\s+\((?P<dso>[^\)]+)\)') 40419ea8026Sopenharmony_ci events = { 40519ea8026Sopenharmony_ci 'cycles': 'cycles', 40619ea8026Sopenharmony_ci 'branch-misses': 'bmisses', 40719ea8026Sopenharmony_ci 'branches': 'branches', 40819ea8026Sopenharmony_ci 'cache-misses': 'cmisses', 40919ea8026Sopenharmony_ci 'cache-references': 'caches'} 41019ea8026Sopenharmony_ci 41119ea8026Sopenharmony_ci # note perf_path may contain extra args 41219ea8026Sopenharmony_ci cmd = perf_path + [ 41319ea8026Sopenharmony_ci 'script', 41419ea8026Sopenharmony_ci '-i%s' % path] 41519ea8026Sopenharmony_ci if args.get('verbose'): 41619ea8026Sopenharmony_ci print(' '.join(shlex.quote(c) for c in cmd)) 41719ea8026Sopenharmony_ci proc = sp.Popen(cmd, 41819ea8026Sopenharmony_ci stdout=sp.PIPE, 41919ea8026Sopenharmony_ci stderr=sp.PIPE if not args.get('verbose') else None, 42019ea8026Sopenharmony_ci universal_newlines=True, 42119ea8026Sopenharmony_ci errors='replace', 42219ea8026Sopenharmony_ci close_fds=False) 42319ea8026Sopenharmony_ci 42419ea8026Sopenharmony_ci last_filtered = False 42519ea8026Sopenharmony_ci last_event = '' 42619ea8026Sopenharmony_ci last_period = 0 42719ea8026Sopenharmony_ci last_stack = [] 42819ea8026Sopenharmony_ci deltas = co.defaultdict(lambda: {}) 42919ea8026Sopenharmony_ci syms_ = co.defaultdict(lambda: {}) 43019ea8026Sopenharmony_ci at_cache = {} 43119ea8026Sopenharmony_ci results = {} 43219ea8026Sopenharmony_ci 43319ea8026Sopenharmony_ci def commit(): 43419ea8026Sopenharmony_ci # tail-recursively propagate measurements 43519ea8026Sopenharmony_ci for i in range(len(last_stack)): 43619ea8026Sopenharmony_ci results_ = results 43719ea8026Sopenharmony_ci for j in reversed(range(i+1)): 43819ea8026Sopenharmony_ci if i+1-j > depth: 43919ea8026Sopenharmony_ci break 44019ea8026Sopenharmony_ci 44119ea8026Sopenharmony_ci # propagate 44219ea8026Sopenharmony_ci name = last_stack[j] 44319ea8026Sopenharmony_ci if name not in results_: 44419ea8026Sopenharmony_ci results_[name] = (co.defaultdict(lambda: 0), {}) 44519ea8026Sopenharmony_ci results_[name][0][last_event] += last_period 44619ea8026Sopenharmony_ci 44719ea8026Sopenharmony_ci # recurse 44819ea8026Sopenharmony_ci results_ = results_[name][1] 44919ea8026Sopenharmony_ci 45019ea8026Sopenharmony_ci for line in proc.stdout: 45119ea8026Sopenharmony_ci # we need to process a lot of data, so wait to use regex as late 45219ea8026Sopenharmony_ci # as possible 45319ea8026Sopenharmony_ci if not line.startswith('\t'): 45419ea8026Sopenharmony_ci if last_filtered: 45519ea8026Sopenharmony_ci commit() 45619ea8026Sopenharmony_ci last_filtered = False 45719ea8026Sopenharmony_ci 45819ea8026Sopenharmony_ci if line: 45919ea8026Sopenharmony_ci m = sample_pattern.match(line) 46019ea8026Sopenharmony_ci if m and m.group('event') in events: 46119ea8026Sopenharmony_ci last_filtered = True 46219ea8026Sopenharmony_ci last_event = m.group('event') 46319ea8026Sopenharmony_ci last_period = int(m.group('period'), 0) 46419ea8026Sopenharmony_ci last_stack = [] 46519ea8026Sopenharmony_ci 46619ea8026Sopenharmony_ci elif last_filtered: 46719ea8026Sopenharmony_ci m = frame_pattern.match(line) 46819ea8026Sopenharmony_ci if m: 46919ea8026Sopenharmony_ci # filter out internal/kernel functions 47019ea8026Sopenharmony_ci if not everything and ( 47119ea8026Sopenharmony_ci m.group('sym').startswith('__') 47219ea8026Sopenharmony_ci or m.group('sym').startswith('0') 47319ea8026Sopenharmony_ci or m.group('sym').startswith('-') 47419ea8026Sopenharmony_ci or m.group('sym').startswith('[') 47519ea8026Sopenharmony_ci or m.group('dso').startswith('/usr/lib')): 47619ea8026Sopenharmony_ci continue 47719ea8026Sopenharmony_ci 47819ea8026Sopenharmony_ci dso = m.group('dso') 47919ea8026Sopenharmony_ci sym = m.group('sym') 48019ea8026Sopenharmony_ci off = int(m.group('off'), 0) if m.group('off') else 0 48119ea8026Sopenharmony_ci addr_ = int(m.group('addr'), 16) 48219ea8026Sopenharmony_ci 48319ea8026Sopenharmony_ci # get the syms/lines for the dso, this is cached 48419ea8026Sopenharmony_ci syms, sym_at, lines, line_at = collect_syms_and_lines( 48519ea8026Sopenharmony_ci dso, 48619ea8026Sopenharmony_ci **args) 48719ea8026Sopenharmony_ci 48819ea8026Sopenharmony_ci # ASLR is tricky, we have symbols+offsets, but static symbols 48919ea8026Sopenharmony_ci # means we may have multiple options for each symbol. 49019ea8026Sopenharmony_ci # 49119ea8026Sopenharmony_ci # To try to solve this, we use previous seen symbols to build 49219ea8026Sopenharmony_ci # confidence for the correct ASLR delta. This means we may 49319ea8026Sopenharmony_ci # guess incorrectly for early symbols, but this will only affect 49419ea8026Sopenharmony_ci # a few samples. 49519ea8026Sopenharmony_ci if sym in syms: 49619ea8026Sopenharmony_ci sym_addr_ = addr_ - off 49719ea8026Sopenharmony_ci 49819ea8026Sopenharmony_ci # track possible deltas? 49919ea8026Sopenharmony_ci for sym_addr, size in syms[sym]: 50019ea8026Sopenharmony_ci delta = sym_addr - sym_addr_ 50119ea8026Sopenharmony_ci if delta not in deltas[dso]: 50219ea8026Sopenharmony_ci deltas[dso][delta] = sum( 50319ea8026Sopenharmony_ci abs(a_+delta - a) 50419ea8026Sopenharmony_ci for s, (a_, _) in syms_[dso].items() 50519ea8026Sopenharmony_ci for a, _ in syms[s]) 50619ea8026Sopenharmony_ci for delta in deltas[dso].keys(): 50719ea8026Sopenharmony_ci deltas[dso][delta] += abs(sym_addr_+delta - sym_addr) 50819ea8026Sopenharmony_ci syms_[dso][sym] = sym_addr_, size 50919ea8026Sopenharmony_ci 51019ea8026Sopenharmony_ci # guess the best delta 51119ea8026Sopenharmony_ci delta, _ = min(deltas[dso].items(), 51219ea8026Sopenharmony_ci key=lambda x: (x[1], x[0])) 51319ea8026Sopenharmony_ci addr = addr_ + delta 51419ea8026Sopenharmony_ci 51519ea8026Sopenharmony_ci # cached? 51619ea8026Sopenharmony_ci if (dso,addr) in at_cache: 51719ea8026Sopenharmony_ci cached = at_cache[(dso,addr)] 51819ea8026Sopenharmony_ci if cached is None: 51919ea8026Sopenharmony_ci # cache says to skip 52019ea8026Sopenharmony_ci continue 52119ea8026Sopenharmony_ci file, line = cached 52219ea8026Sopenharmony_ci else: 52319ea8026Sopenharmony_ci # find file+line 52419ea8026Sopenharmony_ci i = bisect.bisect(line_at, addr, key=lambda x: x[0]) 52519ea8026Sopenharmony_ci if i > 0: 52619ea8026Sopenharmony_ci _, file, line = line_at[i-1] 52719ea8026Sopenharmony_ci else: 52819ea8026Sopenharmony_ci file, line = re.sub('(\.o)?$', '.c', dso, 1), 0 52919ea8026Sopenharmony_ci 53019ea8026Sopenharmony_ci # ignore filtered sources 53119ea8026Sopenharmony_ci if sources is not None: 53219ea8026Sopenharmony_ci if not any( 53319ea8026Sopenharmony_ci os.path.abspath(file) == os.path.abspath(s) 53419ea8026Sopenharmony_ci for s in sources): 53519ea8026Sopenharmony_ci at_cache[(dso,addr)] = None 53619ea8026Sopenharmony_ci continue 53719ea8026Sopenharmony_ci else: 53819ea8026Sopenharmony_ci # default to only cwd 53919ea8026Sopenharmony_ci if not everything and not os.path.commonpath([ 54019ea8026Sopenharmony_ci os.getcwd(), 54119ea8026Sopenharmony_ci os.path.abspath(file)]) == os.getcwd(): 54219ea8026Sopenharmony_ci at_cache[(dso,addr)] = None 54319ea8026Sopenharmony_ci continue 54419ea8026Sopenharmony_ci 54519ea8026Sopenharmony_ci # simplify path 54619ea8026Sopenharmony_ci if os.path.commonpath([ 54719ea8026Sopenharmony_ci os.getcwd(), 54819ea8026Sopenharmony_ci os.path.abspath(file)]) == os.getcwd(): 54919ea8026Sopenharmony_ci file = os.path.relpath(file) 55019ea8026Sopenharmony_ci else: 55119ea8026Sopenharmony_ci file = os.path.abspath(file) 55219ea8026Sopenharmony_ci 55319ea8026Sopenharmony_ci at_cache[(dso,addr)] = file, line 55419ea8026Sopenharmony_ci else: 55519ea8026Sopenharmony_ci file, line = re.sub('(\.o)?$', '.c', dso, 1), 0 55619ea8026Sopenharmony_ci 55719ea8026Sopenharmony_ci last_stack.append((file, sym, line)) 55819ea8026Sopenharmony_ci 55919ea8026Sopenharmony_ci # stop propogating? 56019ea8026Sopenharmony_ci if propagate and len(last_stack) >= propagate: 56119ea8026Sopenharmony_ci commit() 56219ea8026Sopenharmony_ci last_filtered = False 56319ea8026Sopenharmony_ci if last_filtered: 56419ea8026Sopenharmony_ci commit() 56519ea8026Sopenharmony_ci 56619ea8026Sopenharmony_ci proc.wait() 56719ea8026Sopenharmony_ci if proc.returncode != 0: 56819ea8026Sopenharmony_ci if not args.get('verbose'): 56919ea8026Sopenharmony_ci for line in proc.stderr: 57019ea8026Sopenharmony_ci sys.stdout.write(line) 57119ea8026Sopenharmony_ci sys.exit(-1) 57219ea8026Sopenharmony_ci 57319ea8026Sopenharmony_ci # rearrange results into result type 57419ea8026Sopenharmony_ci def to_results(results): 57519ea8026Sopenharmony_ci results_ = [] 57619ea8026Sopenharmony_ci for name, (r, children) in results.items(): 57719ea8026Sopenharmony_ci results_.append(PerfResult(*name, 57819ea8026Sopenharmony_ci **{events[k]: v for k, v in r.items()}, 57919ea8026Sopenharmony_ci children=to_results(children))) 58019ea8026Sopenharmony_ci return results_ 58119ea8026Sopenharmony_ci 58219ea8026Sopenharmony_ci return to_results(results) 58319ea8026Sopenharmony_ci 58419ea8026Sopenharmony_cidef collect_job(path, i, **args): 58519ea8026Sopenharmony_ci # decompress into a temporary file, this is to work around 58619ea8026Sopenharmony_ci # some limitations of perf 58719ea8026Sopenharmony_ci with zipfile.ZipFile(path) as z: 58819ea8026Sopenharmony_ci with z.open(i) as f: 58919ea8026Sopenharmony_ci with tempfile.NamedTemporaryFile('wb') as g: 59019ea8026Sopenharmony_ci shutil.copyfileobj(f, g) 59119ea8026Sopenharmony_ci g.flush() 59219ea8026Sopenharmony_ci 59319ea8026Sopenharmony_ci return collect_decompressed(g.name, **args) 59419ea8026Sopenharmony_ci 59519ea8026Sopenharmony_cidef starapply(args): 59619ea8026Sopenharmony_ci f, args, kwargs = args 59719ea8026Sopenharmony_ci return f(*args, **kwargs) 59819ea8026Sopenharmony_ci 59919ea8026Sopenharmony_cidef collect(perf_paths, *, 60019ea8026Sopenharmony_ci jobs=None, 60119ea8026Sopenharmony_ci **args): 60219ea8026Sopenharmony_ci # automatic job detection? 60319ea8026Sopenharmony_ci if jobs == 0: 60419ea8026Sopenharmony_ci jobs = len(os.sched_getaffinity(0)) 60519ea8026Sopenharmony_ci 60619ea8026Sopenharmony_ci records = [] 60719ea8026Sopenharmony_ci for path in perf_paths: 60819ea8026Sopenharmony_ci # each .perf file is actually a zip file containing perf files from 60919ea8026Sopenharmony_ci # multiple runs 61019ea8026Sopenharmony_ci with zipfile.ZipFile(path) as z: 61119ea8026Sopenharmony_ci records.extend((path, i) for i in z.infolist()) 61219ea8026Sopenharmony_ci 61319ea8026Sopenharmony_ci # we're dealing with a lot of data but also surprisingly 61419ea8026Sopenharmony_ci # parallelizable 61519ea8026Sopenharmony_ci if jobs is not None: 61619ea8026Sopenharmony_ci results = [] 61719ea8026Sopenharmony_ci with mp.Pool(jobs) as p: 61819ea8026Sopenharmony_ci for results_ in p.imap_unordered( 61919ea8026Sopenharmony_ci starapply, 62019ea8026Sopenharmony_ci ((collect_job, (path, i), args) for path, i in records)): 62119ea8026Sopenharmony_ci results.extend(results_) 62219ea8026Sopenharmony_ci else: 62319ea8026Sopenharmony_ci results = [] 62419ea8026Sopenharmony_ci for path, i in records: 62519ea8026Sopenharmony_ci results.extend(collect_job(path, i, **args)) 62619ea8026Sopenharmony_ci 62719ea8026Sopenharmony_ci return results 62819ea8026Sopenharmony_ci 62919ea8026Sopenharmony_ci 63019ea8026Sopenharmony_cidef fold(Result, results, *, 63119ea8026Sopenharmony_ci by=None, 63219ea8026Sopenharmony_ci defines=None, 63319ea8026Sopenharmony_ci **_): 63419ea8026Sopenharmony_ci if by is None: 63519ea8026Sopenharmony_ci by = Result._by 63619ea8026Sopenharmony_ci 63719ea8026Sopenharmony_ci for k in it.chain(by or [], (k for k, _ in defines or [])): 63819ea8026Sopenharmony_ci if k not in Result._by and k not in Result._fields: 63919ea8026Sopenharmony_ci print("error: could not find field %r?" % k) 64019ea8026Sopenharmony_ci sys.exit(-1) 64119ea8026Sopenharmony_ci 64219ea8026Sopenharmony_ci # filter by matching defines 64319ea8026Sopenharmony_ci if defines is not None: 64419ea8026Sopenharmony_ci results_ = [] 64519ea8026Sopenharmony_ci for r in results: 64619ea8026Sopenharmony_ci if all(getattr(r, k) in vs for k, vs in defines): 64719ea8026Sopenharmony_ci results_.append(r) 64819ea8026Sopenharmony_ci results = results_ 64919ea8026Sopenharmony_ci 65019ea8026Sopenharmony_ci # organize results into conflicts 65119ea8026Sopenharmony_ci folding = co.OrderedDict() 65219ea8026Sopenharmony_ci for r in results: 65319ea8026Sopenharmony_ci name = tuple(getattr(r, k) for k in by) 65419ea8026Sopenharmony_ci if name not in folding: 65519ea8026Sopenharmony_ci folding[name] = [] 65619ea8026Sopenharmony_ci folding[name].append(r) 65719ea8026Sopenharmony_ci 65819ea8026Sopenharmony_ci # merge conflicts 65919ea8026Sopenharmony_ci folded = [] 66019ea8026Sopenharmony_ci for name, rs in folding.items(): 66119ea8026Sopenharmony_ci folded.append(sum(rs[1:], start=rs[0])) 66219ea8026Sopenharmony_ci 66319ea8026Sopenharmony_ci # fold recursively 66419ea8026Sopenharmony_ci folded_ = [] 66519ea8026Sopenharmony_ci for r in folded: 66619ea8026Sopenharmony_ci folded_.append(r._replace(children=fold( 66719ea8026Sopenharmony_ci Result, r.children, 66819ea8026Sopenharmony_ci by=by, 66919ea8026Sopenharmony_ci defines=defines))) 67019ea8026Sopenharmony_ci folded = folded_ 67119ea8026Sopenharmony_ci 67219ea8026Sopenharmony_ci return folded 67319ea8026Sopenharmony_ci 67419ea8026Sopenharmony_cidef table(Result, results, diff_results=None, *, 67519ea8026Sopenharmony_ci by=None, 67619ea8026Sopenharmony_ci fields=None, 67719ea8026Sopenharmony_ci sort=None, 67819ea8026Sopenharmony_ci summary=False, 67919ea8026Sopenharmony_ci all=False, 68019ea8026Sopenharmony_ci percent=False, 68119ea8026Sopenharmony_ci depth=1, 68219ea8026Sopenharmony_ci **_): 68319ea8026Sopenharmony_ci all_, all = all, __builtins__.all 68419ea8026Sopenharmony_ci 68519ea8026Sopenharmony_ci if by is None: 68619ea8026Sopenharmony_ci by = Result._by 68719ea8026Sopenharmony_ci if fields is None: 68819ea8026Sopenharmony_ci fields = Result._fields 68919ea8026Sopenharmony_ci types = Result._types 69019ea8026Sopenharmony_ci 69119ea8026Sopenharmony_ci # fold again 69219ea8026Sopenharmony_ci results = fold(Result, results, by=by) 69319ea8026Sopenharmony_ci if diff_results is not None: 69419ea8026Sopenharmony_ci diff_results = fold(Result, diff_results, by=by) 69519ea8026Sopenharmony_ci 69619ea8026Sopenharmony_ci # organize by name 69719ea8026Sopenharmony_ci table = { 69819ea8026Sopenharmony_ci ','.join(str(getattr(r, k) or '') for k in by): r 69919ea8026Sopenharmony_ci for r in results} 70019ea8026Sopenharmony_ci diff_table = { 70119ea8026Sopenharmony_ci ','.join(str(getattr(r, k) or '') for k in by): r 70219ea8026Sopenharmony_ci for r in diff_results or []} 70319ea8026Sopenharmony_ci names = list(table.keys() | diff_table.keys()) 70419ea8026Sopenharmony_ci 70519ea8026Sopenharmony_ci # sort again, now with diff info, note that python's sort is stable 70619ea8026Sopenharmony_ci names.sort() 70719ea8026Sopenharmony_ci if diff_results is not None: 70819ea8026Sopenharmony_ci names.sort(key=lambda n: tuple( 70919ea8026Sopenharmony_ci types[k].ratio( 71019ea8026Sopenharmony_ci getattr(table.get(n), k, None), 71119ea8026Sopenharmony_ci getattr(diff_table.get(n), k, None)) 71219ea8026Sopenharmony_ci for k in fields), 71319ea8026Sopenharmony_ci reverse=True) 71419ea8026Sopenharmony_ci if sort: 71519ea8026Sopenharmony_ci for k, reverse in reversed(sort): 71619ea8026Sopenharmony_ci names.sort( 71719ea8026Sopenharmony_ci key=lambda n: tuple( 71819ea8026Sopenharmony_ci (getattr(table[n], k),) 71919ea8026Sopenharmony_ci if getattr(table.get(n), k, None) is not None else () 72019ea8026Sopenharmony_ci for k in ([k] if k else [ 72119ea8026Sopenharmony_ci k for k in Result._sort if k in fields])), 72219ea8026Sopenharmony_ci reverse=reverse ^ (not k or k in Result._fields)) 72319ea8026Sopenharmony_ci 72419ea8026Sopenharmony_ci 72519ea8026Sopenharmony_ci # build up our lines 72619ea8026Sopenharmony_ci lines = [] 72719ea8026Sopenharmony_ci 72819ea8026Sopenharmony_ci # header 72919ea8026Sopenharmony_ci header = [] 73019ea8026Sopenharmony_ci header.append('%s%s' % ( 73119ea8026Sopenharmony_ci ','.join(by), 73219ea8026Sopenharmony_ci ' (%d added, %d removed)' % ( 73319ea8026Sopenharmony_ci sum(1 for n in table if n not in diff_table), 73419ea8026Sopenharmony_ci sum(1 for n in diff_table if n not in table)) 73519ea8026Sopenharmony_ci if diff_results is not None and not percent else '') 73619ea8026Sopenharmony_ci if not summary else '') 73719ea8026Sopenharmony_ci if diff_results is None: 73819ea8026Sopenharmony_ci for k in fields: 73919ea8026Sopenharmony_ci header.append(k) 74019ea8026Sopenharmony_ci elif percent: 74119ea8026Sopenharmony_ci for k in fields: 74219ea8026Sopenharmony_ci header.append(k) 74319ea8026Sopenharmony_ci else: 74419ea8026Sopenharmony_ci for k in fields: 74519ea8026Sopenharmony_ci header.append('o'+k) 74619ea8026Sopenharmony_ci for k in fields: 74719ea8026Sopenharmony_ci header.append('n'+k) 74819ea8026Sopenharmony_ci for k in fields: 74919ea8026Sopenharmony_ci header.append('d'+k) 75019ea8026Sopenharmony_ci header.append('') 75119ea8026Sopenharmony_ci lines.append(header) 75219ea8026Sopenharmony_ci 75319ea8026Sopenharmony_ci def table_entry(name, r, diff_r=None, ratios=[]): 75419ea8026Sopenharmony_ci entry = [] 75519ea8026Sopenharmony_ci entry.append(name) 75619ea8026Sopenharmony_ci if diff_results is None: 75719ea8026Sopenharmony_ci for k in fields: 75819ea8026Sopenharmony_ci entry.append(getattr(r, k).table() 75919ea8026Sopenharmony_ci if getattr(r, k, None) is not None 76019ea8026Sopenharmony_ci else types[k].none) 76119ea8026Sopenharmony_ci elif percent: 76219ea8026Sopenharmony_ci for k in fields: 76319ea8026Sopenharmony_ci entry.append(getattr(r, k).diff_table() 76419ea8026Sopenharmony_ci if getattr(r, k, None) is not None 76519ea8026Sopenharmony_ci else types[k].diff_none) 76619ea8026Sopenharmony_ci else: 76719ea8026Sopenharmony_ci for k in fields: 76819ea8026Sopenharmony_ci entry.append(getattr(diff_r, k).diff_table() 76919ea8026Sopenharmony_ci if getattr(diff_r, k, None) is not None 77019ea8026Sopenharmony_ci else types[k].diff_none) 77119ea8026Sopenharmony_ci for k in fields: 77219ea8026Sopenharmony_ci entry.append(getattr(r, k).diff_table() 77319ea8026Sopenharmony_ci if getattr(r, k, None) is not None 77419ea8026Sopenharmony_ci else types[k].diff_none) 77519ea8026Sopenharmony_ci for k in fields: 77619ea8026Sopenharmony_ci entry.append(types[k].diff_diff( 77719ea8026Sopenharmony_ci getattr(r, k, None), 77819ea8026Sopenharmony_ci getattr(diff_r, k, None))) 77919ea8026Sopenharmony_ci if diff_results is None: 78019ea8026Sopenharmony_ci entry.append('') 78119ea8026Sopenharmony_ci elif percent: 78219ea8026Sopenharmony_ci entry.append(' (%s)' % ', '.join( 78319ea8026Sopenharmony_ci '+∞%' if t == +m.inf 78419ea8026Sopenharmony_ci else '-∞%' if t == -m.inf 78519ea8026Sopenharmony_ci else '%+.1f%%' % (100*t) 78619ea8026Sopenharmony_ci for t in ratios)) 78719ea8026Sopenharmony_ci else: 78819ea8026Sopenharmony_ci entry.append(' (%s)' % ', '.join( 78919ea8026Sopenharmony_ci '+∞%' if t == +m.inf 79019ea8026Sopenharmony_ci else '-∞%' if t == -m.inf 79119ea8026Sopenharmony_ci else '%+.1f%%' % (100*t) 79219ea8026Sopenharmony_ci for t in ratios 79319ea8026Sopenharmony_ci if t) 79419ea8026Sopenharmony_ci if any(ratios) else '') 79519ea8026Sopenharmony_ci return entry 79619ea8026Sopenharmony_ci 79719ea8026Sopenharmony_ci # entries 79819ea8026Sopenharmony_ci if not summary: 79919ea8026Sopenharmony_ci for name in names: 80019ea8026Sopenharmony_ci r = table.get(name) 80119ea8026Sopenharmony_ci if diff_results is None: 80219ea8026Sopenharmony_ci diff_r = None 80319ea8026Sopenharmony_ci ratios = None 80419ea8026Sopenharmony_ci else: 80519ea8026Sopenharmony_ci diff_r = diff_table.get(name) 80619ea8026Sopenharmony_ci ratios = [ 80719ea8026Sopenharmony_ci types[k].ratio( 80819ea8026Sopenharmony_ci getattr(r, k, None), 80919ea8026Sopenharmony_ci getattr(diff_r, k, None)) 81019ea8026Sopenharmony_ci for k in fields] 81119ea8026Sopenharmony_ci if not all_ and not any(ratios): 81219ea8026Sopenharmony_ci continue 81319ea8026Sopenharmony_ci lines.append(table_entry(name, r, diff_r, ratios)) 81419ea8026Sopenharmony_ci 81519ea8026Sopenharmony_ci # total 81619ea8026Sopenharmony_ci r = next(iter(fold(Result, results, by=[])), None) 81719ea8026Sopenharmony_ci if diff_results is None: 81819ea8026Sopenharmony_ci diff_r = None 81919ea8026Sopenharmony_ci ratios = None 82019ea8026Sopenharmony_ci else: 82119ea8026Sopenharmony_ci diff_r = next(iter(fold(Result, diff_results, by=[])), None) 82219ea8026Sopenharmony_ci ratios = [ 82319ea8026Sopenharmony_ci types[k].ratio( 82419ea8026Sopenharmony_ci getattr(r, k, None), 82519ea8026Sopenharmony_ci getattr(diff_r, k, None)) 82619ea8026Sopenharmony_ci for k in fields] 82719ea8026Sopenharmony_ci lines.append(table_entry('TOTAL', r, diff_r, ratios)) 82819ea8026Sopenharmony_ci 82919ea8026Sopenharmony_ci # find the best widths, note that column 0 contains the names and column -1 83019ea8026Sopenharmony_ci # the ratios, so those are handled a bit differently 83119ea8026Sopenharmony_ci widths = [ 83219ea8026Sopenharmony_ci ((max(it.chain([w], (len(l[i]) for l in lines)))+1+4-1)//4)*4-1 83319ea8026Sopenharmony_ci for w, i in zip( 83419ea8026Sopenharmony_ci it.chain([23], it.repeat(7)), 83519ea8026Sopenharmony_ci range(len(lines[0])-1))] 83619ea8026Sopenharmony_ci 83719ea8026Sopenharmony_ci # adjust the name width based on the expected call depth, though 83819ea8026Sopenharmony_ci # note this doesn't really work with unbounded recursion 83919ea8026Sopenharmony_ci if not summary and not m.isinf(depth): 84019ea8026Sopenharmony_ci widths[0] += 4*(depth-1) 84119ea8026Sopenharmony_ci 84219ea8026Sopenharmony_ci # print the tree recursively 84319ea8026Sopenharmony_ci print('%-*s %s%s' % ( 84419ea8026Sopenharmony_ci widths[0], lines[0][0], 84519ea8026Sopenharmony_ci ' '.join('%*s' % (w, x) 84619ea8026Sopenharmony_ci for w, x in zip(widths[1:], lines[0][1:-1])), 84719ea8026Sopenharmony_ci lines[0][-1])) 84819ea8026Sopenharmony_ci 84919ea8026Sopenharmony_ci if not summary: 85019ea8026Sopenharmony_ci def recurse(results_, depth_, prefixes=('', '', '', '')): 85119ea8026Sopenharmony_ci # rebuild our tables at each layer 85219ea8026Sopenharmony_ci table_ = { 85319ea8026Sopenharmony_ci ','.join(str(getattr(r, k) or '') for k in by): r 85419ea8026Sopenharmony_ci for r in results_} 85519ea8026Sopenharmony_ci names_ = list(table_.keys()) 85619ea8026Sopenharmony_ci 85719ea8026Sopenharmony_ci # sort again at each layer, keep in mind the numbers are 85819ea8026Sopenharmony_ci # changing as we descend 85919ea8026Sopenharmony_ci names_.sort() 86019ea8026Sopenharmony_ci if sort: 86119ea8026Sopenharmony_ci for k, reverse in reversed(sort): 86219ea8026Sopenharmony_ci names_.sort( 86319ea8026Sopenharmony_ci key=lambda n: tuple( 86419ea8026Sopenharmony_ci (getattr(table_[n], k),) 86519ea8026Sopenharmony_ci if getattr(table_.get(n), k, None) is not None 86619ea8026Sopenharmony_ci else () 86719ea8026Sopenharmony_ci for k in ([k] if k else [ 86819ea8026Sopenharmony_ci k for k in Result._sort if k in fields])), 86919ea8026Sopenharmony_ci reverse=reverse ^ (not k or k in Result._fields)) 87019ea8026Sopenharmony_ci 87119ea8026Sopenharmony_ci for i, name in enumerate(names_): 87219ea8026Sopenharmony_ci r = table_[name] 87319ea8026Sopenharmony_ci is_last = (i == len(names_)-1) 87419ea8026Sopenharmony_ci 87519ea8026Sopenharmony_ci print('%s%-*s %s' % ( 87619ea8026Sopenharmony_ci prefixes[0+is_last], 87719ea8026Sopenharmony_ci widths[0] - ( 87819ea8026Sopenharmony_ci len(prefixes[0+is_last]) 87919ea8026Sopenharmony_ci if not m.isinf(depth) else 0), 88019ea8026Sopenharmony_ci name, 88119ea8026Sopenharmony_ci ' '.join('%*s' % (w, x) 88219ea8026Sopenharmony_ci for w, x in zip( 88319ea8026Sopenharmony_ci widths[1:], 88419ea8026Sopenharmony_ci table_entry(name, r)[1:])))) 88519ea8026Sopenharmony_ci 88619ea8026Sopenharmony_ci # recurse? 88719ea8026Sopenharmony_ci if depth_ > 1: 88819ea8026Sopenharmony_ci recurse( 88919ea8026Sopenharmony_ci r.children, 89019ea8026Sopenharmony_ci depth_-1, 89119ea8026Sopenharmony_ci (prefixes[2+is_last] + "|-> ", 89219ea8026Sopenharmony_ci prefixes[2+is_last] + "'-> ", 89319ea8026Sopenharmony_ci prefixes[2+is_last] + "| ", 89419ea8026Sopenharmony_ci prefixes[2+is_last] + " ")) 89519ea8026Sopenharmony_ci 89619ea8026Sopenharmony_ci # we have enough going on with diffing to make the top layer 89719ea8026Sopenharmony_ci # a special case 89819ea8026Sopenharmony_ci for name, line in zip(names, lines[1:-1]): 89919ea8026Sopenharmony_ci print('%-*s %s%s' % ( 90019ea8026Sopenharmony_ci widths[0], line[0], 90119ea8026Sopenharmony_ci ' '.join('%*s' % (w, x) 90219ea8026Sopenharmony_ci for w, x in zip(widths[1:], line[1:-1])), 90319ea8026Sopenharmony_ci line[-1])) 90419ea8026Sopenharmony_ci 90519ea8026Sopenharmony_ci if name in table and depth > 1: 90619ea8026Sopenharmony_ci recurse( 90719ea8026Sopenharmony_ci table[name].children, 90819ea8026Sopenharmony_ci depth-1, 90919ea8026Sopenharmony_ci ("|-> ", 91019ea8026Sopenharmony_ci "'-> ", 91119ea8026Sopenharmony_ci "| ", 91219ea8026Sopenharmony_ci " ")) 91319ea8026Sopenharmony_ci 91419ea8026Sopenharmony_ci print('%-*s %s%s' % ( 91519ea8026Sopenharmony_ci widths[0], lines[-1][0], 91619ea8026Sopenharmony_ci ' '.join('%*s' % (w, x) 91719ea8026Sopenharmony_ci for w, x in zip(widths[1:], lines[-1][1:-1])), 91819ea8026Sopenharmony_ci lines[-1][-1])) 91919ea8026Sopenharmony_ci 92019ea8026Sopenharmony_ci 92119ea8026Sopenharmony_cidef annotate(Result, results, *, 92219ea8026Sopenharmony_ci annotate=None, 92319ea8026Sopenharmony_ci threshold=None, 92419ea8026Sopenharmony_ci branches=False, 92519ea8026Sopenharmony_ci caches=False, 92619ea8026Sopenharmony_ci **args): 92719ea8026Sopenharmony_ci # figure out the threshold 92819ea8026Sopenharmony_ci if threshold is None: 92919ea8026Sopenharmony_ci t0, t1 = THRESHOLD 93019ea8026Sopenharmony_ci elif len(threshold) == 1: 93119ea8026Sopenharmony_ci t0, t1 = threshold[0], threshold[0] 93219ea8026Sopenharmony_ci else: 93319ea8026Sopenharmony_ci t0, t1 = threshold 93419ea8026Sopenharmony_ci t0, t1 = min(t0, t1), max(t0, t1) 93519ea8026Sopenharmony_ci 93619ea8026Sopenharmony_ci if not branches and not caches: 93719ea8026Sopenharmony_ci tk = 'cycles' 93819ea8026Sopenharmony_ci elif branches: 93919ea8026Sopenharmony_ci tk = 'bmisses' 94019ea8026Sopenharmony_ci else: 94119ea8026Sopenharmony_ci tk = 'cmisses' 94219ea8026Sopenharmony_ci 94319ea8026Sopenharmony_ci # find max cycles 94419ea8026Sopenharmony_ci max_ = max(it.chain((float(getattr(r, tk)) for r in results), [1])) 94519ea8026Sopenharmony_ci 94619ea8026Sopenharmony_ci for path in co.OrderedDict.fromkeys(r.file for r in results).keys(): 94719ea8026Sopenharmony_ci # flatten to line info 94819ea8026Sopenharmony_ci results = fold(Result, results, by=['file', 'line']) 94919ea8026Sopenharmony_ci table = {r.line: r for r in results if r.file == path} 95019ea8026Sopenharmony_ci 95119ea8026Sopenharmony_ci # calculate spans to show 95219ea8026Sopenharmony_ci if not annotate: 95319ea8026Sopenharmony_ci spans = [] 95419ea8026Sopenharmony_ci last = None 95519ea8026Sopenharmony_ci func = None 95619ea8026Sopenharmony_ci for line, r in sorted(table.items()): 95719ea8026Sopenharmony_ci if float(getattr(r, tk)) / max_ >= t0: 95819ea8026Sopenharmony_ci if last is not None and line - last.stop <= args['context']: 95919ea8026Sopenharmony_ci last = range( 96019ea8026Sopenharmony_ci last.start, 96119ea8026Sopenharmony_ci line+1+args['context']) 96219ea8026Sopenharmony_ci else: 96319ea8026Sopenharmony_ci if last is not None: 96419ea8026Sopenharmony_ci spans.append((last, func)) 96519ea8026Sopenharmony_ci last = range( 96619ea8026Sopenharmony_ci line-args['context'], 96719ea8026Sopenharmony_ci line+1+args['context']) 96819ea8026Sopenharmony_ci func = r.function 96919ea8026Sopenharmony_ci if last is not None: 97019ea8026Sopenharmony_ci spans.append((last, func)) 97119ea8026Sopenharmony_ci 97219ea8026Sopenharmony_ci with open(path) as f: 97319ea8026Sopenharmony_ci skipped = False 97419ea8026Sopenharmony_ci for i, line in enumerate(f): 97519ea8026Sopenharmony_ci # skip lines not in spans? 97619ea8026Sopenharmony_ci if not annotate and not any(i+1 in s for s, _ in spans): 97719ea8026Sopenharmony_ci skipped = True 97819ea8026Sopenharmony_ci continue 97919ea8026Sopenharmony_ci 98019ea8026Sopenharmony_ci if skipped: 98119ea8026Sopenharmony_ci skipped = False 98219ea8026Sopenharmony_ci print('%s@@ %s:%d: %s @@%s' % ( 98319ea8026Sopenharmony_ci '\x1b[36m' if args['color'] else '', 98419ea8026Sopenharmony_ci path, 98519ea8026Sopenharmony_ci i+1, 98619ea8026Sopenharmony_ci next(iter(f for _, f in spans)), 98719ea8026Sopenharmony_ci '\x1b[m' if args['color'] else '')) 98819ea8026Sopenharmony_ci 98919ea8026Sopenharmony_ci # build line 99019ea8026Sopenharmony_ci if line.endswith('\n'): 99119ea8026Sopenharmony_ci line = line[:-1] 99219ea8026Sopenharmony_ci 99319ea8026Sopenharmony_ci r = table.get(i+1) 99419ea8026Sopenharmony_ci if r is not None and ( 99519ea8026Sopenharmony_ci float(r.cycles) > 0 99619ea8026Sopenharmony_ci if not branches and not caches 99719ea8026Sopenharmony_ci else float(r.bmisses) > 0 or float(r.branches) > 0 99819ea8026Sopenharmony_ci if branches 99919ea8026Sopenharmony_ci else float(r.cmisses) > 0 or float(r.caches) > 0): 100019ea8026Sopenharmony_ci line = '%-*s // %s' % ( 100119ea8026Sopenharmony_ci args['width'], 100219ea8026Sopenharmony_ci line, 100319ea8026Sopenharmony_ci '%s cycles' % r.cycles 100419ea8026Sopenharmony_ci if not branches and not caches 100519ea8026Sopenharmony_ci else '%s bmisses, %s branches' % (r.bmisses, r.branches) 100619ea8026Sopenharmony_ci if branches 100719ea8026Sopenharmony_ci else '%s cmisses, %s caches' % (r.cmisses, r.caches)) 100819ea8026Sopenharmony_ci 100919ea8026Sopenharmony_ci if args['color']: 101019ea8026Sopenharmony_ci if float(getattr(r, tk)) / max_ >= t1: 101119ea8026Sopenharmony_ci line = '\x1b[1;31m%s\x1b[m' % line 101219ea8026Sopenharmony_ci elif float(getattr(r, tk)) / max_ >= t0: 101319ea8026Sopenharmony_ci line = '\x1b[35m%s\x1b[m' % line 101419ea8026Sopenharmony_ci 101519ea8026Sopenharmony_ci print(line) 101619ea8026Sopenharmony_ci 101719ea8026Sopenharmony_ci 101819ea8026Sopenharmony_cidef report(perf_paths, *, 101919ea8026Sopenharmony_ci by=None, 102019ea8026Sopenharmony_ci fields=None, 102119ea8026Sopenharmony_ci defines=None, 102219ea8026Sopenharmony_ci sort=None, 102319ea8026Sopenharmony_ci branches=False, 102419ea8026Sopenharmony_ci caches=False, 102519ea8026Sopenharmony_ci **args): 102619ea8026Sopenharmony_ci # figure out what color should be 102719ea8026Sopenharmony_ci if args.get('color') == 'auto': 102819ea8026Sopenharmony_ci args['color'] = sys.stdout.isatty() 102919ea8026Sopenharmony_ci elif args.get('color') == 'always': 103019ea8026Sopenharmony_ci args['color'] = True 103119ea8026Sopenharmony_ci else: 103219ea8026Sopenharmony_ci args['color'] = False 103319ea8026Sopenharmony_ci 103419ea8026Sopenharmony_ci # depth of 0 == m.inf 103519ea8026Sopenharmony_ci if args.get('depth') == 0: 103619ea8026Sopenharmony_ci args['depth'] = m.inf 103719ea8026Sopenharmony_ci 103819ea8026Sopenharmony_ci # find sizes 103919ea8026Sopenharmony_ci if not args.get('use', None): 104019ea8026Sopenharmony_ci results = collect(perf_paths, **args) 104119ea8026Sopenharmony_ci else: 104219ea8026Sopenharmony_ci results = [] 104319ea8026Sopenharmony_ci with openio(args['use']) as f: 104419ea8026Sopenharmony_ci reader = csv.DictReader(f, restval='') 104519ea8026Sopenharmony_ci for r in reader: 104619ea8026Sopenharmony_ci if not any('perf_'+k in r and r['perf_'+k].strip() 104719ea8026Sopenharmony_ci for k in PerfResult._fields): 104819ea8026Sopenharmony_ci continue 104919ea8026Sopenharmony_ci try: 105019ea8026Sopenharmony_ci results.append(PerfResult( 105119ea8026Sopenharmony_ci **{k: r[k] for k in PerfResult._by 105219ea8026Sopenharmony_ci if k in r and r[k].strip()}, 105319ea8026Sopenharmony_ci **{k: r['perf_'+k] for k in PerfResult._fields 105419ea8026Sopenharmony_ci if 'perf_'+k in r and r['perf_'+k].strip()})) 105519ea8026Sopenharmony_ci except TypeError: 105619ea8026Sopenharmony_ci pass 105719ea8026Sopenharmony_ci 105819ea8026Sopenharmony_ci # fold 105919ea8026Sopenharmony_ci results = fold(PerfResult, results, by=by, defines=defines) 106019ea8026Sopenharmony_ci 106119ea8026Sopenharmony_ci # sort, note that python's sort is stable 106219ea8026Sopenharmony_ci results.sort() 106319ea8026Sopenharmony_ci if sort: 106419ea8026Sopenharmony_ci for k, reverse in reversed(sort): 106519ea8026Sopenharmony_ci results.sort( 106619ea8026Sopenharmony_ci key=lambda r: tuple( 106719ea8026Sopenharmony_ci (getattr(r, k),) if getattr(r, k) is not None else () 106819ea8026Sopenharmony_ci for k in ([k] if k else PerfResult._sort)), 106919ea8026Sopenharmony_ci reverse=reverse ^ (not k or k in PerfResult._fields)) 107019ea8026Sopenharmony_ci 107119ea8026Sopenharmony_ci # write results to CSV 107219ea8026Sopenharmony_ci if args.get('output'): 107319ea8026Sopenharmony_ci with openio(args['output'], 'w') as f: 107419ea8026Sopenharmony_ci writer = csv.DictWriter(f, 107519ea8026Sopenharmony_ci (by if by is not None else PerfResult._by) 107619ea8026Sopenharmony_ci + ['perf_'+k for k in ( 107719ea8026Sopenharmony_ci fields if fields is not None else PerfResult._fields)]) 107819ea8026Sopenharmony_ci writer.writeheader() 107919ea8026Sopenharmony_ci for r in results: 108019ea8026Sopenharmony_ci writer.writerow( 108119ea8026Sopenharmony_ci {k: getattr(r, k) for k in ( 108219ea8026Sopenharmony_ci by if by is not None else PerfResult._by)} 108319ea8026Sopenharmony_ci | {'perf_'+k: getattr(r, k) for k in ( 108419ea8026Sopenharmony_ci fields if fields is not None else PerfResult._fields)}) 108519ea8026Sopenharmony_ci 108619ea8026Sopenharmony_ci # find previous results? 108719ea8026Sopenharmony_ci if args.get('diff'): 108819ea8026Sopenharmony_ci diff_results = [] 108919ea8026Sopenharmony_ci try: 109019ea8026Sopenharmony_ci with openio(args['diff']) as f: 109119ea8026Sopenharmony_ci reader = csv.DictReader(f, restval='') 109219ea8026Sopenharmony_ci for r in reader: 109319ea8026Sopenharmony_ci if not any('perf_'+k in r and r['perf_'+k].strip() 109419ea8026Sopenharmony_ci for k in PerfResult._fields): 109519ea8026Sopenharmony_ci continue 109619ea8026Sopenharmony_ci try: 109719ea8026Sopenharmony_ci diff_results.append(PerfResult( 109819ea8026Sopenharmony_ci **{k: r[k] for k in PerfResult._by 109919ea8026Sopenharmony_ci if k in r and r[k].strip()}, 110019ea8026Sopenharmony_ci **{k: r['perf_'+k] for k in PerfResult._fields 110119ea8026Sopenharmony_ci if 'perf_'+k in r and r['perf_'+k].strip()})) 110219ea8026Sopenharmony_ci except TypeError: 110319ea8026Sopenharmony_ci pass 110419ea8026Sopenharmony_ci except FileNotFoundError: 110519ea8026Sopenharmony_ci pass 110619ea8026Sopenharmony_ci 110719ea8026Sopenharmony_ci # fold 110819ea8026Sopenharmony_ci diff_results = fold(PerfResult, diff_results, by=by, defines=defines) 110919ea8026Sopenharmony_ci 111019ea8026Sopenharmony_ci # print table 111119ea8026Sopenharmony_ci if not args.get('quiet'): 111219ea8026Sopenharmony_ci if args.get('annotate') or args.get('threshold'): 111319ea8026Sopenharmony_ci # annotate sources 111419ea8026Sopenharmony_ci annotate(PerfResult, results, 111519ea8026Sopenharmony_ci branches=branches, 111619ea8026Sopenharmony_ci caches=caches, 111719ea8026Sopenharmony_ci **args) 111819ea8026Sopenharmony_ci else: 111919ea8026Sopenharmony_ci # print table 112019ea8026Sopenharmony_ci table(PerfResult, results, 112119ea8026Sopenharmony_ci diff_results if args.get('diff') else None, 112219ea8026Sopenharmony_ci by=by if by is not None else ['function'], 112319ea8026Sopenharmony_ci fields=fields if fields is not None 112419ea8026Sopenharmony_ci else ['cycles'] if not branches and not caches 112519ea8026Sopenharmony_ci else ['bmisses', 'branches'] if branches 112619ea8026Sopenharmony_ci else ['cmisses', 'caches'], 112719ea8026Sopenharmony_ci sort=sort, 112819ea8026Sopenharmony_ci **args) 112919ea8026Sopenharmony_ci 113019ea8026Sopenharmony_ci 113119ea8026Sopenharmony_cidef main(**args): 113219ea8026Sopenharmony_ci if args.get('record'): 113319ea8026Sopenharmony_ci return record(**args) 113419ea8026Sopenharmony_ci else: 113519ea8026Sopenharmony_ci return report(**args) 113619ea8026Sopenharmony_ci 113719ea8026Sopenharmony_ci 113819ea8026Sopenharmony_ciif __name__ == "__main__": 113919ea8026Sopenharmony_ci import argparse 114019ea8026Sopenharmony_ci import sys 114119ea8026Sopenharmony_ci 114219ea8026Sopenharmony_ci # bit of a hack, but parse_intermixed_args and REMAINDER are 114319ea8026Sopenharmony_ci # incompatible, so we need to figure out what we want before running 114419ea8026Sopenharmony_ci # argparse 114519ea8026Sopenharmony_ci if '-R' in sys.argv or '--record' in sys.argv: 114619ea8026Sopenharmony_ci nargs = argparse.REMAINDER 114719ea8026Sopenharmony_ci else: 114819ea8026Sopenharmony_ci nargs = '*' 114919ea8026Sopenharmony_ci 115019ea8026Sopenharmony_ci argparse.ArgumentParser._handle_conflict_ignore = lambda *_: None 115119ea8026Sopenharmony_ci argparse._ArgumentGroup._handle_conflict_ignore = lambda *_: None 115219ea8026Sopenharmony_ci parser = argparse.ArgumentParser( 115319ea8026Sopenharmony_ci description="Aggregate and report Linux perf results.", 115419ea8026Sopenharmony_ci allow_abbrev=False, 115519ea8026Sopenharmony_ci conflict_handler='ignore') 115619ea8026Sopenharmony_ci parser.add_argument( 115719ea8026Sopenharmony_ci 'perf_paths', 115819ea8026Sopenharmony_ci nargs=nargs, 115919ea8026Sopenharmony_ci help="Input *.perf files.") 116019ea8026Sopenharmony_ci parser.add_argument( 116119ea8026Sopenharmony_ci '-v', '--verbose', 116219ea8026Sopenharmony_ci action='store_true', 116319ea8026Sopenharmony_ci help="Output commands that run behind the scenes.") 116419ea8026Sopenharmony_ci parser.add_argument( 116519ea8026Sopenharmony_ci '-q', '--quiet', 116619ea8026Sopenharmony_ci action='store_true', 116719ea8026Sopenharmony_ci help="Don't show anything, useful with -o.") 116819ea8026Sopenharmony_ci parser.add_argument( 116919ea8026Sopenharmony_ci '-o', '--output', 117019ea8026Sopenharmony_ci help="Specify CSV file to store results.") 117119ea8026Sopenharmony_ci parser.add_argument( 117219ea8026Sopenharmony_ci '-u', '--use', 117319ea8026Sopenharmony_ci help="Don't parse anything, use this CSV file.") 117419ea8026Sopenharmony_ci parser.add_argument( 117519ea8026Sopenharmony_ci '-d', '--diff', 117619ea8026Sopenharmony_ci help="Specify CSV file to diff against.") 117719ea8026Sopenharmony_ci parser.add_argument( 117819ea8026Sopenharmony_ci '-a', '--all', 117919ea8026Sopenharmony_ci action='store_true', 118019ea8026Sopenharmony_ci help="Show all, not just the ones that changed.") 118119ea8026Sopenharmony_ci parser.add_argument( 118219ea8026Sopenharmony_ci '-p', '--percent', 118319ea8026Sopenharmony_ci action='store_true', 118419ea8026Sopenharmony_ci help="Only show percentage change, not a full diff.") 118519ea8026Sopenharmony_ci parser.add_argument( 118619ea8026Sopenharmony_ci '-b', '--by', 118719ea8026Sopenharmony_ci action='append', 118819ea8026Sopenharmony_ci choices=PerfResult._by, 118919ea8026Sopenharmony_ci help="Group by this field.") 119019ea8026Sopenharmony_ci parser.add_argument( 119119ea8026Sopenharmony_ci '-f', '--field', 119219ea8026Sopenharmony_ci dest='fields', 119319ea8026Sopenharmony_ci action='append', 119419ea8026Sopenharmony_ci choices=PerfResult._fields, 119519ea8026Sopenharmony_ci help="Show this field.") 119619ea8026Sopenharmony_ci parser.add_argument( 119719ea8026Sopenharmony_ci '-D', '--define', 119819ea8026Sopenharmony_ci dest='defines', 119919ea8026Sopenharmony_ci action='append', 120019ea8026Sopenharmony_ci type=lambda x: (lambda k,v: (k, set(v.split(','))))(*x.split('=', 1)), 120119ea8026Sopenharmony_ci help="Only include results where this field is this value.") 120219ea8026Sopenharmony_ci class AppendSort(argparse.Action): 120319ea8026Sopenharmony_ci def __call__(self, parser, namespace, value, option): 120419ea8026Sopenharmony_ci if namespace.sort is None: 120519ea8026Sopenharmony_ci namespace.sort = [] 120619ea8026Sopenharmony_ci namespace.sort.append((value, True if option == '-S' else False)) 120719ea8026Sopenharmony_ci parser.add_argument( 120819ea8026Sopenharmony_ci '-s', '--sort', 120919ea8026Sopenharmony_ci nargs='?', 121019ea8026Sopenharmony_ci action=AppendSort, 121119ea8026Sopenharmony_ci help="Sort by this field.") 121219ea8026Sopenharmony_ci parser.add_argument( 121319ea8026Sopenharmony_ci '-S', '--reverse-sort', 121419ea8026Sopenharmony_ci nargs='?', 121519ea8026Sopenharmony_ci action=AppendSort, 121619ea8026Sopenharmony_ci help="Sort by this field, but backwards.") 121719ea8026Sopenharmony_ci parser.add_argument( 121819ea8026Sopenharmony_ci '-Y', '--summary', 121919ea8026Sopenharmony_ci action='store_true', 122019ea8026Sopenharmony_ci help="Only show the total.") 122119ea8026Sopenharmony_ci parser.add_argument( 122219ea8026Sopenharmony_ci '-F', '--source', 122319ea8026Sopenharmony_ci dest='sources', 122419ea8026Sopenharmony_ci action='append', 122519ea8026Sopenharmony_ci help="Only consider definitions in this file. Defaults to anything " 122619ea8026Sopenharmony_ci "in the current directory.") 122719ea8026Sopenharmony_ci parser.add_argument( 122819ea8026Sopenharmony_ci '--everything', 122919ea8026Sopenharmony_ci action='store_true', 123019ea8026Sopenharmony_ci help="Include builtin and libc specific symbols.") 123119ea8026Sopenharmony_ci parser.add_argument( 123219ea8026Sopenharmony_ci '--branches', 123319ea8026Sopenharmony_ci action='store_true', 123419ea8026Sopenharmony_ci help="Show branches and branch misses.") 123519ea8026Sopenharmony_ci parser.add_argument( 123619ea8026Sopenharmony_ci '--caches', 123719ea8026Sopenharmony_ci action='store_true', 123819ea8026Sopenharmony_ci help="Show cache accesses and cache misses.") 123919ea8026Sopenharmony_ci parser.add_argument( 124019ea8026Sopenharmony_ci '-P', '--propagate', 124119ea8026Sopenharmony_ci type=lambda x: int(x, 0), 124219ea8026Sopenharmony_ci help="Depth to propagate samples up the call-stack. 0 propagates up " 124319ea8026Sopenharmony_ci "to the entry point, 1 does no propagation. Defaults to 0.") 124419ea8026Sopenharmony_ci parser.add_argument( 124519ea8026Sopenharmony_ci '-Z', '--depth', 124619ea8026Sopenharmony_ci nargs='?', 124719ea8026Sopenharmony_ci type=lambda x: int(x, 0), 124819ea8026Sopenharmony_ci const=0, 124919ea8026Sopenharmony_ci help="Depth of function calls to show. 0 shows all calls but may not " 125019ea8026Sopenharmony_ci "terminate!") 125119ea8026Sopenharmony_ci parser.add_argument( 125219ea8026Sopenharmony_ci '-A', '--annotate', 125319ea8026Sopenharmony_ci action='store_true', 125419ea8026Sopenharmony_ci help="Show source files annotated with coverage info.") 125519ea8026Sopenharmony_ci parser.add_argument( 125619ea8026Sopenharmony_ci '-T', '--threshold', 125719ea8026Sopenharmony_ci nargs='?', 125819ea8026Sopenharmony_ci type=lambda x: tuple(float(x) for x in x.split(',')), 125919ea8026Sopenharmony_ci const=THRESHOLD, 126019ea8026Sopenharmony_ci help="Show lines with samples above this threshold as a percent of " 126119ea8026Sopenharmony_ci "all lines. Defaults to %s." % ','.join(str(t) for t in THRESHOLD)) 126219ea8026Sopenharmony_ci parser.add_argument( 126319ea8026Sopenharmony_ci '-c', '--context', 126419ea8026Sopenharmony_ci type=lambda x: int(x, 0), 126519ea8026Sopenharmony_ci default=3, 126619ea8026Sopenharmony_ci help="Show n additional lines of context. Defaults to 3.") 126719ea8026Sopenharmony_ci parser.add_argument( 126819ea8026Sopenharmony_ci '-W', '--width', 126919ea8026Sopenharmony_ci type=lambda x: int(x, 0), 127019ea8026Sopenharmony_ci default=80, 127119ea8026Sopenharmony_ci help="Assume source is styled with this many columns. Defaults to 80.") 127219ea8026Sopenharmony_ci parser.add_argument( 127319ea8026Sopenharmony_ci '--color', 127419ea8026Sopenharmony_ci choices=['never', 'always', 'auto'], 127519ea8026Sopenharmony_ci default='auto', 127619ea8026Sopenharmony_ci help="When to use terminal colors. Defaults to 'auto'.") 127719ea8026Sopenharmony_ci parser.add_argument( 127819ea8026Sopenharmony_ci '-j', '--jobs', 127919ea8026Sopenharmony_ci nargs='?', 128019ea8026Sopenharmony_ci type=lambda x: int(x, 0), 128119ea8026Sopenharmony_ci const=0, 128219ea8026Sopenharmony_ci help="Number of processes to use. 0 spawns one process per core.") 128319ea8026Sopenharmony_ci parser.add_argument( 128419ea8026Sopenharmony_ci '--perf-path', 128519ea8026Sopenharmony_ci type=lambda x: x.split(), 128619ea8026Sopenharmony_ci help="Path to the perf executable, may include flags. " 128719ea8026Sopenharmony_ci "Defaults to %r." % PERF_PATH) 128819ea8026Sopenharmony_ci parser.add_argument( 128919ea8026Sopenharmony_ci '--objdump-path', 129019ea8026Sopenharmony_ci type=lambda x: x.split(), 129119ea8026Sopenharmony_ci default=OBJDUMP_PATH, 129219ea8026Sopenharmony_ci help="Path to the objdump executable, may include flags. " 129319ea8026Sopenharmony_ci "Defaults to %r." % OBJDUMP_PATH) 129419ea8026Sopenharmony_ci 129519ea8026Sopenharmony_ci # record flags 129619ea8026Sopenharmony_ci record_parser = parser.add_argument_group('record options') 129719ea8026Sopenharmony_ci record_parser.add_argument( 129819ea8026Sopenharmony_ci 'command', 129919ea8026Sopenharmony_ci nargs=nargs, 130019ea8026Sopenharmony_ci help="Command to run.") 130119ea8026Sopenharmony_ci record_parser.add_argument( 130219ea8026Sopenharmony_ci '-R', '--record', 130319ea8026Sopenharmony_ci action='store_true', 130419ea8026Sopenharmony_ci help="Run a command and aggregate perf measurements.") 130519ea8026Sopenharmony_ci record_parser.add_argument( 130619ea8026Sopenharmony_ci '-o', '--output', 130719ea8026Sopenharmony_ci help="Output file. Uses flock to synchronize. This is stored as a " 130819ea8026Sopenharmony_ci "zip-file of multiple perf results.") 130919ea8026Sopenharmony_ci record_parser.add_argument( 131019ea8026Sopenharmony_ci '--perf-freq', 131119ea8026Sopenharmony_ci help="perf sampling frequency. This is passed directly to perf. " 131219ea8026Sopenharmony_ci "Defaults to %r." % PERF_FREQ) 131319ea8026Sopenharmony_ci record_parser.add_argument( 131419ea8026Sopenharmony_ci '--perf-period', 131519ea8026Sopenharmony_ci help="perf sampling period. This is passed directly to perf.") 131619ea8026Sopenharmony_ci record_parser.add_argument( 131719ea8026Sopenharmony_ci '--perf-events', 131819ea8026Sopenharmony_ci help="perf events to record. This is passed directly to perf. " 131919ea8026Sopenharmony_ci "Defaults to %r." % PERF_EVENTS) 132019ea8026Sopenharmony_ci record_parser.add_argument( 132119ea8026Sopenharmony_ci '--perf-path', 132219ea8026Sopenharmony_ci type=lambda x: x.split(), 132319ea8026Sopenharmony_ci help="Path to the perf executable, may include flags. " 132419ea8026Sopenharmony_ci "Defaults to %r." % PERF_PATH) 132519ea8026Sopenharmony_ci 132619ea8026Sopenharmony_ci # avoid intermixed/REMAINDER conflict, see above 132719ea8026Sopenharmony_ci if nargs == argparse.REMAINDER: 132819ea8026Sopenharmony_ci args = parser.parse_args() 132919ea8026Sopenharmony_ci else: 133019ea8026Sopenharmony_ci args = parser.parse_intermixed_args() 133119ea8026Sopenharmony_ci 133219ea8026Sopenharmony_ci # perf_paths/command overlap, so need to do some munging here 133319ea8026Sopenharmony_ci args.command = args.perf_paths 133419ea8026Sopenharmony_ci if args.record: 133519ea8026Sopenharmony_ci if not args.command: 133619ea8026Sopenharmony_ci print('error: no command specified?') 133719ea8026Sopenharmony_ci sys.exit(-1) 133819ea8026Sopenharmony_ci if not args.output: 133919ea8026Sopenharmony_ci print('error: no output file specified?') 134019ea8026Sopenharmony_ci sys.exit(-1) 134119ea8026Sopenharmony_ci 134219ea8026Sopenharmony_ci sys.exit(main(**{k: v 134319ea8026Sopenharmony_ci for k, v in vars(args).items() 134419ea8026Sopenharmony_ci if v is not None})) 1345