diff --git a/scripts/amor.py b/scripts/amor.py index 3d5c1826..131f53a1 100755 --- a/scripts/amor.py +++ b/scripts/amor.py @@ -46,11 +46,15 @@ def dat(x): def collect(csv_paths, renames=[], 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: # apply any renames if renames: @@ -69,15 +73,15 @@ def collect(csv_paths, renames=[], defines=[]): except FileNotFoundError: pass - return results + return fields, results def main(csv_paths, output, *, amor=False, per=False, + by=None, meas=None, iter=None, size=None, - by=None, fields=None, defines=[]): # default to amortizing and per-byte results if size is present @@ -95,40 +99,43 @@ def main(csv_paths, output, *, if fields is not None: fields = [k for k, _ in fields] + if by is None and fields is None: + print("error: needs --by or --fields to figure out fields") + sys.exit(-1) + # collect results from csv files - results = collect(csv_paths, renames, defines) + fields_, results = collect(csv_paths, renames, defines) - # if fields not specified, try to guess from data - if fields is None: - fields = co.OrderedDict() - for r in results: - for k, v in r.items(): - if k not in (by or []) and k != iter and v.strip(): - try: - dat(v) - fields[k] = True - except ValueError: - fields[k] = False - fields = list(k for k,v in fields.items() if v) - - # if by not specified, guess it's anything not in iter/fields and not a - # source of a rename + # if by not specified, guess it's anything not in + # iter/size/fields/renames/defines if by is None: - by = co.OrderedDict() - for r in results: - # also ignore None keys, these are introduced by csv.DictReader - # when header + row mismatch - by.update((k, True) for k in r.keys() - if k is not None - and k != iter - and k not in fields - and not any(k == old_k for _, old_k in renames)) - by = list(by.keys()) + by = [ + k for k in fields_ + if k != iter + and k != size + and k not in (fields or []) + and not any(k == old_k for _, old_k in renames) + and not any(k == k_ for k_, _ in defines)] + + # if fields not specified, guess it's anything not in + # by/iter/size/renames/defines + if fields is None: + fields = [ + k for k in fields_ + if k not in (by or []) + and k != iter + and k != size + and not any(k == old_k for _, old_k in renames) + and not any(k == k_ for k_, _ in defines)] + + # add meas to by if it isn't already present + if meas is not None and meas not in by: + by.append(meas) # convert iter/fields to ints/floats for r in results: - for k in {iter} | set(fields) | ({size} if size is not None else {}): - if k in r: + for k in it.chain([iter], [size] if size is not None else [], fields): + if k in r and isinstance(r[k], str): r[k] = dat(r[k]) if r[k].strip() else 0 # organize by 'by' values @@ -141,7 +148,7 @@ def main(csv_paths, output, *, # for each key compute the amortized results amors = [] for key, rs in results.items(): - # keep a running sum for each fied + # keep a running sum for each field sums = {f: 0 for f in fields} size_ = 0 for j, (i, r) in enumerate(sorted( @@ -171,7 +178,7 @@ def main(csv_paths, output, *, # write results to CSV with openio(output, 'w') as f: writer = csv.DictWriter(f, - by + ([meas] if meas not in by else []) + [iter] + fields) + by + [iter] + ([size] if size is not None else []) + fields) writer.writeheader() for r in amors: writer.writerow(r) @@ -199,6 +206,16 @@ if __name__ == "__main__": '--per', action='store_true', help="Compute per-byte results.") + parser.add_argument( + '-b', '--by', + action='append', + type=lambda x: ( + lambda k, vs=None: ( + k.strip(), + tuple(v.strip() for v in vs.split(',')) + if vs is not None else ()) + )(*x.split('=', 1)), + help="Group by this field. Can rename fields with new_name=old_name.") parser.add_argument( '-m', '--meas', help="Optional name of measurement name field. If provided, the name " @@ -210,16 +227,6 @@ if __name__ == "__main__": parser.add_argument( '-n', '--size', help="Optional name of size field.") - parser.add_argument( - '-b', '--by', - action='append', - type=lambda x: ( - lambda k, vs=None: ( - k.strip(), - tuple(v.strip() for v in vs.split(',')) - if vs is not None else ()) - )(*x.split('=', 1)), - help="Group by this field. Can rename fields with new_name=old_name.") parser.add_argument( '-f', '--field', dest='fields', diff --git a/scripts/avg.py b/scripts/avg.py index 1be4e8d8..1d78f5d9 100755 --- a/scripts/avg.py +++ b/scripts/avg.py @@ -46,11 +46,15 @@ def dat(x): def collect(csv_paths, renames=[], 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: # apply any renames if renames: @@ -69,7 +73,7 @@ def collect(csv_paths, renames=[], defines=[]): except FileNotFoundError: pass - return results + return fields, results def main(csv_paths, output, *, sum=False, @@ -81,8 +85,8 @@ def main(csv_paths, output, *, stddev=False, gmean=False, gstddev=False, - meas=None, by=None, + meas=None, seeds=None, fields=None, defines=[]): @@ -113,40 +117,41 @@ def main(csv_paths, output, *, if fields is not None: fields = [k for k, _ in fields] + if by is None and fields is None: + print("error: needs --by or --fields to figure out fields") + sys.exit(-1) + # collect results from csv files - results = collect(csv_paths, renames, defines) + fields_, results = collect(csv_paths, renames, defines) - # if fields not specified, try to guess from data - if fields is None: - fields = co.OrderedDict() - for r in results: - for k, v in r.items(): - if k not in (by or []) and k not in (seeds or []) and v.strip(): - try: - dat(v) - fields[k] = True - except ValueError: - fields[k] = False - fields = list(k for k,v in fields.items() if v) - - # if by not specified, guess it's anything not in seeds/fields and not a - # source of a rename + # if by not specified, guess it's anything not in + # seeds/fields/renames/defines if by is None: - by = co.OrderedDict() - for r in results: - # also ignore None keys, these are introduced by csv.DictReader - # when header + row mismatch - by.update((k, True) for k in r.keys() - if k is not None - and k not in (seeds or []) - and k not in fields - and not any(k == old_k for _, old_k in renames)) - by = list(by.keys()) + by = [ + k for k in fields_ + if k not in (seeds or []) + and k not in (fields or []) + and not any(k == old_k for _, old_k in renames) + and not any(k == k_ for k_, _ in defines)] + + # if fields not specified, guess it's anything not in + # by/seeds/renames/defines + if fields is None: + fields = [ + k for k in fields_ + if k not in (by or []) + and k not in (seeds or []) + and not any(k == old_k for _, old_k in renames) + and not any(k == k_ for k_, _ in defines)] + + # add meas to by if it isn't already present + if meas is not None and meas not in by: + by.append(meas) # convert fields to ints/floats for r in results: for k in fields: - if k in r: + if k in r and isinstance(r[k], str): r[k] = dat(r[k]) if r[k].strip() else 0 # organize by 'by' values @@ -162,11 +167,10 @@ def main(csv_paths, output, *, vs = {f: [] for f in fields} meas__ = None for r in rs: - if all(k in r and r[k] == v for k, v in zip(by, key)): - for f in fields: - vs[f].append(r.get(f, 0)) - if meas is not None and meas in r: - meas__ = r[meas] + for f in fields: + vs[f].append(r.get(f, 0)) + if meas is not None and meas in r: + meas__ = r[meas] def append(meas_, f_): avgs.append( @@ -197,8 +201,7 @@ def main(csv_paths, output, *, # write results to CSVS with openio(output, 'w') as f: - writer = csv.DictWriter(f, - by + ([meas] if meas not in by else []) + fields) + writer = csv.DictWriter(f, by + fields) writer.writeheader() for r in avgs: writer.writerow(r) @@ -254,10 +257,6 @@ if __name__ == "__main__": '--gstddev', action='store_true', help="Compute the geometric standard deviation.") - parser.add_argument( - '-m', '--meas', - help="Optional name of measurement name field. If provided, the name " - "will be modified with +amor or +per.") parser.add_argument( '-b', '--by', action='append', @@ -268,6 +267,10 @@ if __name__ == "__main__": if vs is not None else ()) )(*x.split('=', 1)), help="Group by this field. Can rename fields with new_name=old_name.") + parser.add_argument( + '-m', '--meas', + help="Optional name of measurement name field. If provided, the name " + "will be modified with +amor or +per.") parser.add_argument( '-s', '--seed', dest='seeds', diff --git a/scripts/code.py b/scripts/code.py index 7f2e1960..f8787dc6 100755 --- a/scripts/code.py +++ b/scripts/code.py @@ -315,10 +315,7 @@ def collect(obj_paths, *, return results -def fold(Result, results, *, - by=None, - defines=[], - **_): +def fold(Result, results, by=None, defines=[]): if by is None: by = Result._by diff --git a/scripts/cov.py b/scripts/cov.py index d6a2d10a..cbea75e7 100755 --- a/scripts/cov.py +++ b/scripts/cov.py @@ -297,10 +297,7 @@ def collect(gcda_paths, *, return results -def fold(Result, results, *, - by=None, - defines=[], - **_): +def fold(Result, results, by=None, defines=[]): if by is None: by = Result._by diff --git a/scripts/data.py b/scripts/data.py index 8d008e10..3077f6a0 100755 --- a/scripts/data.py +++ b/scripts/data.py @@ -315,10 +315,7 @@ def collect(obj_paths, *, return results -def fold(Result, results, *, - by=None, - defines=[], - **_): +def fold(Result, results, by=None, defines=[]): if by is None: by = Result._by diff --git a/scripts/perf.py b/scripts/perf.py index d229a605..a0cd323f 100755 --- a/scripts/perf.py +++ b/scripts/perf.py @@ -627,10 +627,7 @@ def collect(perf_paths, *, return results -def fold(Result, results, *, - by=None, - defines=[], - **_): +def fold(Result, results, by=None, defines=[]): if by is None: by = Result._by diff --git a/scripts/perfbd.py b/scripts/perfbd.py index e5b6225b..4912587e 100755 --- a/scripts/perfbd.py +++ b/scripts/perfbd.py @@ -593,10 +593,7 @@ def collect(obj_path, trace_paths, *, return results -def fold(Result, results, *, - by=None, - defines=[], - **_): +def fold(Result, results, by=None, defines=[]): if by is None: by = Result._by diff --git a/scripts/plot.py b/scripts/plot.py index e9a0ad6e..da1f2e8d 100755 --- a/scripts/plot.py +++ b/scripts/plot.py @@ -445,11 +445,15 @@ class Plot: def collect(csv_paths, renames=[], 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: # apply any renames if renames: @@ -468,7 +472,7 @@ def collect(csv_paths, renames=[], defines=[]): except FileNotFoundError: pass - return results + return fields, results def fold(results, by=None, x=None, y=None, defines=[]): # filter by matching defines @@ -479,29 +483,16 @@ def fold(results, by=None, x=None, y=None, defines=[]): results_.append(r) results = results_ - # if y not specified, try to guess from data - if not y: - y = co.OrderedDict() - for r in results: - for k, v in r.items(): - if (not by or k not in by) and v.strip(): - try: - dat(v) - y[k] = True - except ValueError: - y[k] = False - y = list(k for k,v in y.items() if v) - if by: # find all 'by' values - ks = set() + keys = set() for r in results: - ks.add(tuple(r.get(k, '') for k in by)) - ks = sorted(ks) + keys.add(tuple(r.get(k, '') for k in by)) + keys = sorted(keys) # collect all datasets datasets = co.OrderedDict() - for ks_ in (ks if by else [()]): + for key in (keys if by else [()]): for x_ in (x if x else [None]): for y_ in y: # organize by 'by', x, and y @@ -511,7 +502,7 @@ def fold(results, by=None, x=None, y=None, defines=[]): # filter by 'by' if by and not all( k in r and r[k] == v - for k, v in zip(by, ks_)): + for k, v in zip(by, key)): continue # find xs @@ -542,8 +533,8 @@ def fold(results, by=None, x=None, y=None, defines=[]): # hide x/y if there is only one field k_x = x_ if len(x or []) > 1 else '' - k_y = y_ if len(y or []) > 1 or (not ks_ and not k_x) else '' - datasets[ks_ + (k_x, k_y)] = dataset + k_y = y_ if len(y or []) > 1 or (not key and not k_x) else '' + datasets[key + (k_x, k_y)] = dataset return datasets @@ -904,13 +895,17 @@ def main(csv_paths, *, all_defines = sorted(all_defines.items()) # separate out renames - renames = list(it.chain.from_iterable( + all_renames = list(it.chain.from_iterable( ((k, v) for v in vs) for k, vs in it.chain(all_by, all_x, all_y))) all_by = [k for k, _ in all_by] all_x = [k for k, _ in all_x] all_y = [k for k, _ in all_y] + if not all_by and not all_y: + print("error: needs --by or -y to figure out fields") + sys.exit(-1) + # create a grid of subplots grid = Grid.fromargs(**subplot, subplots=subplots) @@ -994,10 +989,19 @@ def main(csv_paths, *, f.writeln = writeln # first collect results from CSV files - results = collect(csv_paths, renames, all_defines) + fields_, results = collect(csv_paths, all_renames, all_defines) + + # if y not specified, guess it's anything not in by/defines/x/renames + all_y_ = all_y + if not all_y: + all_y_ = [ + k for k in fields_ + if k not in all_by + and not any(k == k_ for k_, _ in all_defines) + and not any(k == old_k for _, old_k in all_renames)] # then extract the requested datasets - datasets_ = fold(results, all_by, all_x, all_y) + datasets_ = fold(results, all_by, all_x, all_y_) # figure out colors/chars here so that subplot defines # don't change them later, that'd be bad @@ -1143,7 +1147,7 @@ def main(csv_paths, *, # data can be constrained by subplot-specific defines, # so re-extract for each plot - subdatasets = fold(results, all_by, all_x, all_y, define_) + subdatasets = fold(results, all_by, all_x, all_y_, define_) # filter by subplot x/y subdatasets = co.OrderedDict([(name, dataset) diff --git a/scripts/plotmpl.py b/scripts/plotmpl.py index 1ba9174f..bd98e595 100755 --- a/scripts/plotmpl.py +++ b/scripts/plotmpl.py @@ -191,11 +191,15 @@ def dat(x): def collect(csv_paths, renames=[], 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: # apply any renames if renames: @@ -214,7 +218,7 @@ def collect(csv_paths, renames=[], defines=[]): except FileNotFoundError: pass - return results + return fields, results def fold(results, by=None, x=None, y=None, defines=[]): # filter by matching defines @@ -225,29 +229,16 @@ def fold(results, by=None, x=None, y=None, defines=[]): results_.append(r) results = results_ - # if y not specified, try to guess from data - if not y: - y = co.OrderedDict() - for r in results: - for k, v in r.items(): - if (not by or k not in by) and v.strip(): - try: - dat(v) - y[k] = True - except ValueError: - y[k] = False - y = list(k for k,v in y.items() if v) - if by: # find all 'by' values - ks = set() + keys = set() for r in results: - ks.add(tuple(r.get(k, '') for k in by)) - ks = sorted(ks) + keys.add(tuple(r.get(k, '') for k in by)) + keys = sorted(keys) # collect all datasets datasets = co.OrderedDict() - for ks_ in (ks if by else [()]): + for key in (keys if by else [()]): for x_ in (x if x else [None]): for y_ in y: # organize by 'by', x, and y @@ -257,7 +248,7 @@ def fold(results, by=None, x=None, y=None, defines=[]): # filter by 'by' if by and not all( k in r and r[k] == v - for k, v in zip(by, ks_)): + for k, v in zip(by, key)): continue # find xs @@ -288,8 +279,8 @@ def fold(results, by=None, x=None, y=None, defines=[]): # hide x/y if there is only one field k_x = x_ if len(x or []) > 1 else '' - k_y = y_ if len(y or []) > 1 or (not ks_ and not k_x) else '' - datasets[ks_ + (k_x, k_y)] = dataset + k_y = y_ if len(y or []) > 1 or (not key and not k_x) else '' + datasets[key + (k_x, k_y)] = dataset return datasets @@ -746,15 +737,27 @@ def main(csv_paths, output, *, all_defines = sorted(all_defines.items()) # separate out renames - renames = list(it.chain.from_iterable( + all_renames = list(it.chain.from_iterable( ((k, v) for v in vs) for k, vs in it.chain(all_by, all_x, all_y))) all_by = [k for k, _ in all_by] all_x = [k for k, _ in all_x] all_y = [k for k, _ in all_y] + if not all_by and not all_y: + print("error: needs --by or -y to figure out fields") + sys.exit(-1) + # first collect results from CSV files - results = collect(csv_paths, renames, all_defines) + fields_, results = collect(csv_paths, all_renames, all_defines) + + # if y not specified, guess it's anything not in by/defines/x/renames + if not all_y: + all_y = [ + k for k in fields_ + if k not in all_by + and not any(k == k_ for k_, _ in all_defines) + and not any(k == old_k for _, old_k in all_renames)] # then extract the requested datasets datasets_ = fold(results, all_by, all_x, all_y) diff --git a/scripts/stack.py b/scripts/stack.py index 05b34df4..e0b36e19 100755 --- a/scripts/stack.py +++ b/scripts/stack.py @@ -273,10 +273,7 @@ def collect(ci_paths, *, return results -def fold(Result, results, *, - by=None, - defines=[], - **_): +def fold(Result, results, by=None, defines=[]): if by is None: by = Result._by diff --git a/scripts/structs.py b/scripts/structs.py index 3a062289..450cd502 100755 --- a/scripts/structs.py +++ b/scripts/structs.py @@ -264,10 +264,7 @@ def collect(obj_paths, *, return results -def fold(Result, results, *, - by=None, - defines=[], - **_): +def fold(Result, results, by=None, defines=[]): if by is None: by = Result._by diff --git a/scripts/summary.py b/scripts/summary.py index b6026e69..5c60e65e 100755 --- a/scripts/summary.py +++ b/scripts/summary.py @@ -251,11 +251,15 @@ def openio(path, mode='r', buffering=-1): def collect(csv_paths, renames=[], 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: # apply any renames if renames: @@ -274,49 +278,34 @@ def collect(csv_paths, renames=[], defines=[]): except FileNotFoundError: pass - return results + return fields, results -def infer(results, *, +def infer(fields_, results, by=None, fields=None, types={}, ops={}, renames=[], - **_): - # if fields not specified, try to guess from data - if fields is None: - fields = co.OrderedDict() - for r in results: - for k, v in r.items(): - if (by is None or k not in by) and v.strip(): - types_ = [] - for t in fields.get(k, TYPES.values()): - try: - t(v) - types_.append(t) - except ValueError: - pass - fields[k] = types_ - fields = list(k for k, v in fields.items() if v) - - # deduplicate fields - fields = list(co.OrderedDict.fromkeys(fields).keys()) - - # if by not specified, guess it's anything not in fields and not a - # source of a rename + defines=[]): + # if by not specified, guess it's anything not in fields/renames/defines if by is None: - by = co.OrderedDict() - for r in results: - # also ignore None keys, these are introduced by csv.DictReader - # when header + row mismatch - by.update((k, True) for k in r.keys() - if k is not None - and k not in fields - and not any(k == old_k for _, old_k in renames)) - by = list(by.keys()) + by = [ + k for k in fields_ + if k not in (fields or []) + and not any(k == old_k for _, old_k in renames) + and not any(k == k_ for k_, _ in defines)] - # deduplicate fields + # if fields not specified, guess it's anything not in by/renames/defines + if fields is None: + fields = [ + k for k in fields_ + if k not in (by or []) + and not any(k == old_k for _, old_k in renames) + and not any(k == k_ for k_, _ in defines)] + + # deduplicate by/fields by = list(co.OrderedDict.fromkeys(by).keys()) + fields = list(co.OrderedDict.fromkeys(fields).keys()) # find best type for all fields types_ = {} @@ -381,10 +370,7 @@ def infer(results, *, }) -def fold(Result, results, *, - by=None, - defines=[], - **_): +def fold(Result, results, by=None, defines=[]): if by is None: by = Result._by @@ -634,16 +620,21 @@ def main(csv_paths, *, ops_[new_k] = ops[old_k] ops.update(ops_) + if by is None and fields is None: + print("error: needs --by or --fields to figure out fields") + sys.exit(-1) + # find CSV files - results = collect(csv_paths, renames=renames, defines=defines) + fields_, results = collect(csv_paths, renames, defines) # homogenize - Result = infer(results, + Result = infer(fields_, results, by=by, fields=fields, types=types, ops=ops, - renames=renames) + renames=renames, + defines=defines) results_ = [] for r in results: if not any(k in r and r[k].strip() @@ -682,7 +673,7 @@ def main(csv_paths, *, # find previous results? if args.get('diff'): - diff_results = collect([args['diff']], renames=renames, defines=defines) + _, diff_results = collect([args['diff']], renames, defines) diff_results_ = [] for r in diff_results: if not any(k in r and r[k].strip()