diff --git a/scripts/codemapd3.py b/scripts/codemapd3.py index a277ae02..3132cc5a 100755 --- a/scripts/codemapd3.py +++ b/scripts/codemapd3.py @@ -74,8 +74,6 @@ def iself(path): with open(path, 'rb') as f: return f.read(4) == b'\x7fELF' - -# TODO adopt this default scheme elsewhere? # parse different data representations def dat(x, *args): try: @@ -105,87 +103,6 @@ def dat(x, *args): else: raise -def collect(csv_paths, defines=[]): - # collect results from CSV files - fields = [] - results = [] - for path in csv_paths: - try: - with openio(path) as f: - reader = csv.DictReader(f, restval='') - fields.extend( - k for k in reader.fieldnames - if k not in fields) - for r in reader: - # filter by matching defines - if not all(k in r and r[k] in vs for k, vs in defines): - continue - - results.append(r) - except FileNotFoundError: - pass - - return fields, results - -def fold(results, by=None, fields=None, defines=[]): - # filter by matching defines - if defines: - results_ = [] - for r in results: - if all(k in r and r[k] in vs for k, vs in defines): - results_.append(r) - results = results_ - - if by: - # find all 'by' values - keys = set() - for r in results: - keys.add(tuple(r.get(k, '') for k in by)) - keys = sorted(keys) - - # collect datasets - datasets = co.OrderedDict() - dataattrs = co.OrderedDict() - for key in (keys if by else [()]): - for field in fields: - # organize by 'by' and field - dataset = [] - dataattr = {} - for r in results: - # filter by 'by' - if by and not all( - k in r and r[k] == v - for k, v in zip(by, key)): - continue - - # find field - if field is not None: - if field not in r: - continue - try: - v = dat(r[field]) - except ValueError: - continue - else: - v = None - - # do _not_ sum v here, it's tempting but risks - # incorrect and misleading results - dataset.append(v) - - # include all fields in dataattrs in case we use - # them for % modifiers - dataattr.update(r) - - # hide 'field' if there is only one field - key_ = key - if len(fields or []) > 1 or not key_: - key_ += (field,) - datasets[key_] = dataset - dataattrs[key_] = dataattr - - return datasets, dataattrs - # a representation of optionally key-mapped attrs class Attr: def __init__(self, attrs, *, @@ -283,7 +200,6 @@ class Attr: return len(self.keyed) -# TODO adopt this elsewhere (try_dat -> default) # parse %-escaped strings def punescape(s, attrs=None): if attrs is None: @@ -715,7 +631,6 @@ def main(paths, output, *, for f in fs: with f: - # TODO adopt this rename (json -> is_json) in other scripts # csv or json? assume json starts with [ is_json = (f.buffer.peek(1)[:1] == b'[') @@ -780,7 +695,6 @@ def main(paths, output, *, # ctx things, including any arguments if 'ctx_size' in r: functions[r['function']]['ctx'] = dat(r['ctx_size']) - # TODO, what if not children in ctx? in stack? if 'children' in r: if 'args' not in functions[r['function']]: functions[r['function']]['args'] = [] @@ -795,55 +709,12 @@ def main(paths, output, *, functions[r['function']]['attrs'] = {} functions[r['function']]['attrs'].update(r) -# import pprint -# pprint.pprint(functions) - -# # figure out paths -# obj_paths = [] -# ci_paths = [] -# for path in paths: -# if iself(path): -# obj_paths.append(path) -# else: -# ci_paths.append(path) -# -# # find code/stack/ctx sizes -# functions = co.OrderedDict() -# if obj_paths: -# for r in collect_code(obj_paths, **args): -# if r['function'] not in functions: -# functions[r['function']] = {} -# functions[r['function']]['code'] = dat(r['code_size']) -# if ci_paths: -# for r in collect_stack(ci_paths, **args): -# if r['function'] not in functions: -# functions[r['function']] = {} -# functions[r['function']]['frame'] = dat(r['stack_frame']) -# functions[r['function']]['stack'] = dat(r['stack_limit'], mt.inf) -# if 'children' in r: -# functions[r['function']]['children'] = [ -# r_['function'] for r_ in r['children']] -# if obj_paths: -# for r in collect_ctx(obj_paths, **args): -# if r['function'] not in functions: -# functions[r['function']] = {} -# functions[r['function']]['ctx'] = dat(r['ctx_size']) -# if 'children' in r: -# functions[r['function']]['args'] = [ -# (r_['function'], dat(r_['ctx_size'])) -# for r_ in r['children']] - # stack.py returns infinity for recursive functions, so we need to # recompute a bounded stack limit to show something useful def limitof(k, f, seen=set()): # found a cycle? stop here if k in seen: return 0 -# # cached? -# if not hasattr(limitof, 'cache'): -# limitof.cache = {} -# if k in limitof.cache: -# return limitof.cache[k] limit = 0 for child in f.get('children', []): @@ -851,7 +722,6 @@ def main(paths, output, *, continue limit = max(limit, limitof(child, functions[child], seen | {k})) -# limitof.cache[k] = f['frame'] + limit return f['frame'] + limit for k, f in functions.items(): @@ -918,96 +788,12 @@ def main(paths, output, *, for f in functions.values() for k, v in f['attrs'].items()} -# import pprint -# pprint.pprint(functions) - # assign colors to subsystems, note this is after sorting, but # before tile generation, we want code and stack tiles to have the # same color if they're in the same subsystem for i, (k, s) in enumerate(subsystems.items()): s['color'] = punescape(colors_[i, (k,)], s['attrs'] | s) -# # TODO make this configurable? -# stack_ratio = 1/5 - -# # use colors for top of tree -# for i, t in enumerate(tile.children): -# for t_ in t.tiles(): -# t_.color = colors_[i, t_.key] - -# # build functions -# datasets = co.OrderedDict() -# dataattrs = co.OrderedDict() -# for k, v in functions.items(): -# name = ('_'.join(k.split('_')[:2]), k) -# -# try: -# if 'code' in v: -# datasets[name] = [dat(v['code'])] -# except ValueError: -# pass -# -# # bring over everything else -# if name not in dataattrs: -# dataattrs[name] = {} -# dataattrs[name].update(v | { -# 'subsystem': name[0], -# 'name': name[1], -# 'code': datasets[name][0], -# 'stack': v.get('stack', 0), -# 'ctx': v.get('ctx', 0)}) - -# # break down by namespace -# # TODO namespace depth? -# results_ = [] -# for r in results: -# name = (r['function'] if 'function' in r -# else r['struct'] if 'struct' in r -# else r['name'] if name in r -# else '?') -# results_.append({ -# 'subsystem': '_'.join(name.split('_')[:2]), -# 'name': name, -# 'code': r['code'] if 'code' in r -# else r['size'] if 'size' in r -# else '0', -# 'frame': r['frame'] if 'code' in r -# else '0', -# 'limit': r['stack'] if 'stack' in r -# else r['limit'] if 'limit' in r -# else '0', -# 'ctx': r['ctx'] if 'ctx' in r -# else '0'}) -# results = results_ -# -# by = ['subsystem', 'name'] -# fields = ['code'] -# labels_ = Attr(['%(name)s%ncode %(code)d%nstack %(stack)d%nctx %(ctx)d']) -# -# if not by and not fields: -# print("error: needs --by or --fields to figure out fields", -# file=sys.stderr) -# sys.exit(-1) -# -# # if by not specified, guess it's anything not in fields/labels/defines -# if not by: -# by = [k for k in fields_ -# if k not in (fields or []) -# and k not in (labels or []) -# and not any(k == k_ for k_, _ in defines)] -# -# # if fields not specified, guess it's anything not in by/labels/defines -# if not fields: -# fields = [k for k in fields_ -# if k not in (by or []) -# and k not in (labels or []) -# and not any(k == k_ for k_, _ in defines)] -# -# # then extract the requested dataset -# datasets, dataattrs = fold(results, by, fields, defines) -# -# import pprint -# pprint.pprint(datasets) # build code heirarchy code = Tile.merge( @@ -1064,51 +850,6 @@ def main(paths, output, *, attrs=a) for a in args) -# ctxs[k].attrs = {'name': k, 'subsystem': f['subsystem']} -# print(ctxs[k]) - - -# children = [] -# for k, f in functions.items(): -# children.append(Tile( -# k, -# f['code'], -# attrs=f)) -# -## for key, dataset in datasets.items(): -## for i, v in enumerate(dataset): -## children.append(Tile( -## key + ((str(i),) if len(dataset) > 1 else ()), -## v, -## attrs=dataattrs[key])) -# -# tile = Tile.merge(children) -# -# # merge attrs -# for t in tile.tiles(): -# if t.children: -# t.attrs = {k: v -# for t_ in t.leaves() -# for k, v in t_.attrs.items()} -# # also sum fields here in case they're used by % modifiers, -# # note other fields are _not_ summed -# for k in fields: -# t.attrs[k] = sum(t_.value -# for t_ in t.leaves() -# if len(fields) == 1 or t_.key[len(by)] == k) -# -# # assign colors/labels before sorting to keep things reproducible -# -# # use colors for top of tree -# for i, t in enumerate(tile.children): -# for t_ in t.tiles(): -# t_.color = colors_[i, t_.key] -# -# # and labels everywhere -# for i, t in enumerate(tile.tiles()): -# if (i, t.key) in labels_: -# t.label = punescape(labels_[i, t.key], t.attrs) - # scale width/height if requested now that we have our data if (to_scale is not None and (width is None or height is None)): @@ -1275,6 +1016,7 @@ def main(paths, output, *, # align to pixel boundaries stack.align() + # create svg file with openio(output, 'w') as f: def writeln(s=''): @@ -1293,9 +1035,6 @@ def main(paths, output, *, 'font: %(font_size)dpx %(font)s; ' 'background-color: %(background)s; ' 'user-select: %(user_select)s;">' % dict( -# 'pointer-events="none" ' -# 'onkeydown="keydown(this,event)" ' -# 'onkeyup="keydown(this,event)" ' width=width_, height=height_, font=','.join(font), @@ -1344,21 +1083,6 @@ def main(paths, output, *, f.write('') f.write('') -# # put all our tiles in a big group to catch clicks -# f.write('') -# # add an invisible rect to make things more clickable -# f.write('' % dict( -# x=0, -# y=y__, -# width=width_, -# height=height_ - y__)) -# f.write('') - # create code tiles for i, t in enumerate(code.leaves()): # skip anything with zero weight/height after aligning things @@ -1394,7 +1118,6 @@ def main(paths, output, *, 'data-y="%(y)d" ' 'data-width="%(width)d" ' 'data-height="%(height)d" ' - # 'cursor="pointer" ' 'onmouseenter="enter_tile(this,event)" ' 'onmouseleave="leave_tile(this,event)" ' 'onclick="click_tile(this,event)">' % dict( @@ -1450,10 +1173,6 @@ def main(paths, output, *, f.write('') f.write('') -# # show the deepest stack/ctx by default -# default = max(functions.values(), -# key=lambda f: f.get('stack', 0) + f.get('ctx', 0)) - # create stack/ctx tiles if not no_stack and (not no_ctx or not no_frames): for i, k in enumerate(functions.keys()): @@ -1496,24 +1215,6 @@ def main(paths, output, *, - code.y - padding)) f.write('') -# # create a group to catch stack-enter events -# f.write('' % dict( -# func=k)) -# # add an invisible rect to make things more clickable -# f.write('' % dict( -# x=code.x + code.width + 2, -# y=y__, -# width=width_ - (code.x + code.width + 2), -# height=height_ - y__)) -# f.write('') - # create ctx tiles if not no_ctx: for j, t in enumerate(ctxs[k].leaves()): @@ -1521,7 +1222,6 @@ def main(paths, output, *, if t.width == 0 or t.height == 0: continue -# print(ctx) label__ = labels_[j, (t.attrs['name'],)] if label__ is not None: label__ = punescape(label__, t.attrs['attrs'] | t.attrs) @@ -1547,7 +1247,6 @@ def main(paths, output, *, 'data-y="%(y)d" ' 'data-width="%(width)d" ' 'data-height="%(height)d" ' - # 'cursor="pointer" ' 'onmouseenter="enter_tile(this,event)" ' 'onmouseleave="leave_tile(this,event)" ' 'onclick="click_tile(this,event)">' % dict( @@ -1651,7 +1350,6 @@ def main(paths, output, *, 'data-y="%(y)d" ' 'data-width="%(width)d" ' 'data-height="%(height)d" ' -# 'cursor="pointer" ' 'onmouseenter="enter_tile(this,event)" ' 'onmouseleave="leave_tile(this,event)" ' 'onclick="click_tile(this,event)"' % dict( @@ -1711,10 +1409,6 @@ def main(paths, output, *, f.write('') -# f.write('') - -# f.write('') - if not no_javascript: # arrowhead for arrows f.write('') @@ -1731,32 +1425,12 @@ def main(paths, output, *, f.write('') f.write('') - # TODO why are we running into NaNs? for v1? - # javascript for arrows # # why tf does svg support javascript? f.write('') f.write('') @@ -2764,7 +2055,6 @@ if __name__ == "__main__": v.strip()) )(*x.split('=', 1)) if '=' in x else x.strip(), - # TODO % modifiers in color everywhere? chars? formats? etc? help="Add a color to use. Can be assigned to a specific " "function/subsystem. Accepts %% modifiers.") parser.add_argument( @@ -2874,7 +2164,6 @@ if __name__ == "__main__": tuple(float(v) for v in x.split(':', 1)) if ':' in x else (float(x), 1)), help="Aspect ratio to use with --to-scale. Defaults to 1:1.") - # TODO add tiny to treemap.py / codemap.py? parser.add_argument( '-t', '--tiny', action='store_true',