119ea8026Sopenharmony_ci#!/usr/bin/env python3 219ea8026Sopenharmony_ci# 319ea8026Sopenharmony_ci# Script to summarize the outputs of other scripts. Operates on CSV files. 419ea8026Sopenharmony_ci# 519ea8026Sopenharmony_ci# Example: 619ea8026Sopenharmony_ci# ./scripts/code.py lfs.o lfs_util.o -q -o lfs.code.csv 719ea8026Sopenharmony_ci# ./scripts/data.py lfs.o lfs_util.o -q -o lfs.data.csv 819ea8026Sopenharmony_ci# ./scripts/summary.py lfs.code.csv lfs.data.csv -q -o lfs.csv 919ea8026Sopenharmony_ci# ./scripts/summary.py -Y lfs.csv -f code=code_size,data=data_size 1019ea8026Sopenharmony_ci# 1119ea8026Sopenharmony_ci# Copyright (c) 2022, The littlefs authors. 1219ea8026Sopenharmony_ci# SPDX-License-Identifier: BSD-3-Clause 1319ea8026Sopenharmony_ci# 1419ea8026Sopenharmony_ci 1519ea8026Sopenharmony_ciimport collections as co 1619ea8026Sopenharmony_ciimport csv 1719ea8026Sopenharmony_ciimport functools as ft 1819ea8026Sopenharmony_ciimport itertools as it 1919ea8026Sopenharmony_ciimport math as m 2019ea8026Sopenharmony_ciimport os 2119ea8026Sopenharmony_ciimport re 2219ea8026Sopenharmony_ci 2319ea8026Sopenharmony_ci 2419ea8026Sopenharmony_ci# supported merge operations 2519ea8026Sopenharmony_ci# 2619ea8026Sopenharmony_ci# this is a terrible way to express these 2719ea8026Sopenharmony_ci# 2819ea8026Sopenharmony_ciOPS = { 2919ea8026Sopenharmony_ci 'sum': lambda xs: sum(xs[1:], start=xs[0]), 3019ea8026Sopenharmony_ci 'prod': lambda xs: m.prod(xs[1:], start=xs[0]), 3119ea8026Sopenharmony_ci 'min': min, 3219ea8026Sopenharmony_ci 'max': max, 3319ea8026Sopenharmony_ci 'mean': lambda xs: Float(sum(float(x) for x in xs) / len(xs)), 3419ea8026Sopenharmony_ci 'stddev': lambda xs: ( 3519ea8026Sopenharmony_ci lambda mean: Float( 3619ea8026Sopenharmony_ci m.sqrt(sum((float(x) - mean)**2 for x in xs) / len(xs))) 3719ea8026Sopenharmony_ci )(sum(float(x) for x in xs) / len(xs)), 3819ea8026Sopenharmony_ci 'gmean': lambda xs: Float(m.prod(float(x) for x in xs)**(1/len(xs))), 3919ea8026Sopenharmony_ci 'gstddev': lambda xs: ( 4019ea8026Sopenharmony_ci lambda gmean: Float( 4119ea8026Sopenharmony_ci m.exp(m.sqrt(sum(m.log(float(x)/gmean)**2 for x in xs) / len(xs))) 4219ea8026Sopenharmony_ci if gmean else m.inf) 4319ea8026Sopenharmony_ci )(m.prod(float(x) for x in xs)**(1/len(xs))), 4419ea8026Sopenharmony_ci} 4519ea8026Sopenharmony_ci 4619ea8026Sopenharmony_ci 4719ea8026Sopenharmony_ci# integer fields 4819ea8026Sopenharmony_ciclass Int(co.namedtuple('Int', 'x')): 4919ea8026Sopenharmony_ci __slots__ = () 5019ea8026Sopenharmony_ci def __new__(cls, x=0): 5119ea8026Sopenharmony_ci if isinstance(x, Int): 5219ea8026Sopenharmony_ci return x 5319ea8026Sopenharmony_ci if isinstance(x, str): 5419ea8026Sopenharmony_ci try: 5519ea8026Sopenharmony_ci x = int(x, 0) 5619ea8026Sopenharmony_ci except ValueError: 5719ea8026Sopenharmony_ci # also accept +-∞ and +-inf 5819ea8026Sopenharmony_ci if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x): 5919ea8026Sopenharmony_ci x = m.inf 6019ea8026Sopenharmony_ci elif re.match('^\s*-\s*(?:∞|inf)\s*$', x): 6119ea8026Sopenharmony_ci x = -m.inf 6219ea8026Sopenharmony_ci else: 6319ea8026Sopenharmony_ci raise 6419ea8026Sopenharmony_ci assert isinstance(x, int) or m.isinf(x), x 6519ea8026Sopenharmony_ci return super().__new__(cls, x) 6619ea8026Sopenharmony_ci 6719ea8026Sopenharmony_ci def __str__(self): 6819ea8026Sopenharmony_ci if self.x == m.inf: 6919ea8026Sopenharmony_ci return '∞' 7019ea8026Sopenharmony_ci elif self.x == -m.inf: 7119ea8026Sopenharmony_ci return '-∞' 7219ea8026Sopenharmony_ci else: 7319ea8026Sopenharmony_ci return str(self.x) 7419ea8026Sopenharmony_ci 7519ea8026Sopenharmony_ci def __int__(self): 7619ea8026Sopenharmony_ci assert not m.isinf(self.x) 7719ea8026Sopenharmony_ci return self.x 7819ea8026Sopenharmony_ci 7919ea8026Sopenharmony_ci def __float__(self): 8019ea8026Sopenharmony_ci return float(self.x) 8119ea8026Sopenharmony_ci 8219ea8026Sopenharmony_ci none = '%7s' % '-' 8319ea8026Sopenharmony_ci def table(self): 8419ea8026Sopenharmony_ci return '%7s' % (self,) 8519ea8026Sopenharmony_ci 8619ea8026Sopenharmony_ci diff_none = '%7s' % '-' 8719ea8026Sopenharmony_ci diff_table = table 8819ea8026Sopenharmony_ci 8919ea8026Sopenharmony_ci def diff_diff(self, other): 9019ea8026Sopenharmony_ci new = self.x if self else 0 9119ea8026Sopenharmony_ci old = other.x if other else 0 9219ea8026Sopenharmony_ci diff = new - old 9319ea8026Sopenharmony_ci if diff == +m.inf: 9419ea8026Sopenharmony_ci return '%7s' % '+∞' 9519ea8026Sopenharmony_ci elif diff == -m.inf: 9619ea8026Sopenharmony_ci return '%7s' % '-∞' 9719ea8026Sopenharmony_ci else: 9819ea8026Sopenharmony_ci return '%+7d' % diff 9919ea8026Sopenharmony_ci 10019ea8026Sopenharmony_ci def ratio(self, other): 10119ea8026Sopenharmony_ci new = self.x if self else 0 10219ea8026Sopenharmony_ci old = other.x if other else 0 10319ea8026Sopenharmony_ci if m.isinf(new) and m.isinf(old): 10419ea8026Sopenharmony_ci return 0.0 10519ea8026Sopenharmony_ci elif m.isinf(new): 10619ea8026Sopenharmony_ci return +m.inf 10719ea8026Sopenharmony_ci elif m.isinf(old): 10819ea8026Sopenharmony_ci return -m.inf 10919ea8026Sopenharmony_ci elif not old and not new: 11019ea8026Sopenharmony_ci return 0.0 11119ea8026Sopenharmony_ci elif not old: 11219ea8026Sopenharmony_ci return 1.0 11319ea8026Sopenharmony_ci else: 11419ea8026Sopenharmony_ci return (new-old) / old 11519ea8026Sopenharmony_ci 11619ea8026Sopenharmony_ci def __add__(self, other): 11719ea8026Sopenharmony_ci return self.__class__(self.x + other.x) 11819ea8026Sopenharmony_ci 11919ea8026Sopenharmony_ci def __sub__(self, other): 12019ea8026Sopenharmony_ci return self.__class__(self.x - other.x) 12119ea8026Sopenharmony_ci 12219ea8026Sopenharmony_ci def __mul__(self, other): 12319ea8026Sopenharmony_ci return self.__class__(self.x * other.x) 12419ea8026Sopenharmony_ci 12519ea8026Sopenharmony_ci# float fields 12619ea8026Sopenharmony_ciclass Float(co.namedtuple('Float', 'x')): 12719ea8026Sopenharmony_ci __slots__ = () 12819ea8026Sopenharmony_ci def __new__(cls, x=0.0): 12919ea8026Sopenharmony_ci if isinstance(x, Float): 13019ea8026Sopenharmony_ci return x 13119ea8026Sopenharmony_ci if isinstance(x, str): 13219ea8026Sopenharmony_ci try: 13319ea8026Sopenharmony_ci x = float(x) 13419ea8026Sopenharmony_ci except ValueError: 13519ea8026Sopenharmony_ci # also accept +-∞ and +-inf 13619ea8026Sopenharmony_ci if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x): 13719ea8026Sopenharmony_ci x = m.inf 13819ea8026Sopenharmony_ci elif re.match('^\s*-\s*(?:∞|inf)\s*$', x): 13919ea8026Sopenharmony_ci x = -m.inf 14019ea8026Sopenharmony_ci else: 14119ea8026Sopenharmony_ci raise 14219ea8026Sopenharmony_ci assert isinstance(x, float), x 14319ea8026Sopenharmony_ci return super().__new__(cls, x) 14419ea8026Sopenharmony_ci 14519ea8026Sopenharmony_ci def __str__(self): 14619ea8026Sopenharmony_ci if self.x == m.inf: 14719ea8026Sopenharmony_ci return '∞' 14819ea8026Sopenharmony_ci elif self.x == -m.inf: 14919ea8026Sopenharmony_ci return '-∞' 15019ea8026Sopenharmony_ci else: 15119ea8026Sopenharmony_ci return '%.1f' % self.x 15219ea8026Sopenharmony_ci 15319ea8026Sopenharmony_ci def __float__(self): 15419ea8026Sopenharmony_ci return float(self.x) 15519ea8026Sopenharmony_ci 15619ea8026Sopenharmony_ci none = Int.none 15719ea8026Sopenharmony_ci table = Int.table 15819ea8026Sopenharmony_ci diff_none = Int.diff_none 15919ea8026Sopenharmony_ci diff_table = Int.diff_table 16019ea8026Sopenharmony_ci diff_diff = Int.diff_diff 16119ea8026Sopenharmony_ci ratio = Int.ratio 16219ea8026Sopenharmony_ci __add__ = Int.__add__ 16319ea8026Sopenharmony_ci __sub__ = Int.__sub__ 16419ea8026Sopenharmony_ci __mul__ = Int.__mul__ 16519ea8026Sopenharmony_ci 16619ea8026Sopenharmony_ci# fractional fields, a/b 16719ea8026Sopenharmony_ciclass Frac(co.namedtuple('Frac', 'a,b')): 16819ea8026Sopenharmony_ci __slots__ = () 16919ea8026Sopenharmony_ci def __new__(cls, a=0, b=None): 17019ea8026Sopenharmony_ci if isinstance(a, Frac) and b is None: 17119ea8026Sopenharmony_ci return a 17219ea8026Sopenharmony_ci if isinstance(a, str) and b is None: 17319ea8026Sopenharmony_ci a, b = a.split('/', 1) 17419ea8026Sopenharmony_ci if b is None: 17519ea8026Sopenharmony_ci b = a 17619ea8026Sopenharmony_ci return super().__new__(cls, Int(a), Int(b)) 17719ea8026Sopenharmony_ci 17819ea8026Sopenharmony_ci def __str__(self): 17919ea8026Sopenharmony_ci return '%s/%s' % (self.a, self.b) 18019ea8026Sopenharmony_ci 18119ea8026Sopenharmony_ci def __float__(self): 18219ea8026Sopenharmony_ci return float(self.a) 18319ea8026Sopenharmony_ci 18419ea8026Sopenharmony_ci none = '%11s %7s' % ('-', '-') 18519ea8026Sopenharmony_ci def table(self): 18619ea8026Sopenharmony_ci t = self.a.x/self.b.x if self.b.x else 1.0 18719ea8026Sopenharmony_ci return '%11s %7s' % ( 18819ea8026Sopenharmony_ci self, 18919ea8026Sopenharmony_ci '∞%' if t == +m.inf 19019ea8026Sopenharmony_ci else '-∞%' if t == -m.inf 19119ea8026Sopenharmony_ci else '%.1f%%' % (100*t)) 19219ea8026Sopenharmony_ci 19319ea8026Sopenharmony_ci diff_none = '%11s' % '-' 19419ea8026Sopenharmony_ci def diff_table(self): 19519ea8026Sopenharmony_ci return '%11s' % (self,) 19619ea8026Sopenharmony_ci 19719ea8026Sopenharmony_ci def diff_diff(self, other): 19819ea8026Sopenharmony_ci new_a, new_b = self if self else (Int(0), Int(0)) 19919ea8026Sopenharmony_ci old_a, old_b = other if other else (Int(0), Int(0)) 20019ea8026Sopenharmony_ci return '%11s' % ('%s/%s' % ( 20119ea8026Sopenharmony_ci new_a.diff_diff(old_a).strip(), 20219ea8026Sopenharmony_ci new_b.diff_diff(old_b).strip())) 20319ea8026Sopenharmony_ci 20419ea8026Sopenharmony_ci def ratio(self, other): 20519ea8026Sopenharmony_ci new_a, new_b = self if self else (Int(0), Int(0)) 20619ea8026Sopenharmony_ci old_a, old_b = other if other else (Int(0), Int(0)) 20719ea8026Sopenharmony_ci new = new_a.x/new_b.x if new_b.x else 1.0 20819ea8026Sopenharmony_ci old = old_a.x/old_b.x if old_b.x else 1.0 20919ea8026Sopenharmony_ci return new - old 21019ea8026Sopenharmony_ci 21119ea8026Sopenharmony_ci def __add__(self, other): 21219ea8026Sopenharmony_ci return self.__class__(self.a + other.a, self.b + other.b) 21319ea8026Sopenharmony_ci 21419ea8026Sopenharmony_ci def __sub__(self, other): 21519ea8026Sopenharmony_ci return self.__class__(self.a - other.a, self.b - other.b) 21619ea8026Sopenharmony_ci 21719ea8026Sopenharmony_ci def __mul__(self, other): 21819ea8026Sopenharmony_ci return self.__class__(self.a * other.a, self.b + other.b) 21919ea8026Sopenharmony_ci 22019ea8026Sopenharmony_ci def __lt__(self, other): 22119ea8026Sopenharmony_ci self_t = self.a.x/self.b.x if self.b.x else 1.0 22219ea8026Sopenharmony_ci other_t = other.a.x/other.b.x if other.b.x else 1.0 22319ea8026Sopenharmony_ci return (self_t, self.a.x) < (other_t, other.a.x) 22419ea8026Sopenharmony_ci 22519ea8026Sopenharmony_ci def __gt__(self, other): 22619ea8026Sopenharmony_ci return self.__class__.__lt__(other, self) 22719ea8026Sopenharmony_ci 22819ea8026Sopenharmony_ci def __le__(self, other): 22919ea8026Sopenharmony_ci return not self.__gt__(other) 23019ea8026Sopenharmony_ci 23119ea8026Sopenharmony_ci def __ge__(self, other): 23219ea8026Sopenharmony_ci return not self.__lt__(other) 23319ea8026Sopenharmony_ci 23419ea8026Sopenharmony_ci# available types 23519ea8026Sopenharmony_ciTYPES = co.OrderedDict([ 23619ea8026Sopenharmony_ci ('int', Int), 23719ea8026Sopenharmony_ci ('float', Float), 23819ea8026Sopenharmony_ci ('frac', Frac) 23919ea8026Sopenharmony_ci]) 24019ea8026Sopenharmony_ci 24119ea8026Sopenharmony_ci 24219ea8026Sopenharmony_cidef infer(results, *, 24319ea8026Sopenharmony_ci by=None, 24419ea8026Sopenharmony_ci fields=None, 24519ea8026Sopenharmony_ci types={}, 24619ea8026Sopenharmony_ci ops={}, 24719ea8026Sopenharmony_ci renames=[], 24819ea8026Sopenharmony_ci **_): 24919ea8026Sopenharmony_ci # if fields not specified, try to guess from data 25019ea8026Sopenharmony_ci if fields is None: 25119ea8026Sopenharmony_ci fields = co.OrderedDict() 25219ea8026Sopenharmony_ci for r in results: 25319ea8026Sopenharmony_ci for k, v in r.items(): 25419ea8026Sopenharmony_ci if (by is None or k not in by) and v.strip(): 25519ea8026Sopenharmony_ci types_ = [] 25619ea8026Sopenharmony_ci for t in fields.get(k, TYPES.values()): 25719ea8026Sopenharmony_ci try: 25819ea8026Sopenharmony_ci t(v) 25919ea8026Sopenharmony_ci types_.append(t) 26019ea8026Sopenharmony_ci except ValueError: 26119ea8026Sopenharmony_ci pass 26219ea8026Sopenharmony_ci fields[k] = types_ 26319ea8026Sopenharmony_ci fields = list(k for k, v in fields.items() if v) 26419ea8026Sopenharmony_ci 26519ea8026Sopenharmony_ci # deduplicate fields 26619ea8026Sopenharmony_ci fields = list(co.OrderedDict.fromkeys(fields).keys()) 26719ea8026Sopenharmony_ci 26819ea8026Sopenharmony_ci # if by not specified, guess it's anything not in fields and not a 26919ea8026Sopenharmony_ci # source of a rename 27019ea8026Sopenharmony_ci if by is None: 27119ea8026Sopenharmony_ci by = co.OrderedDict() 27219ea8026Sopenharmony_ci for r in results: 27319ea8026Sopenharmony_ci # also ignore None keys, these are introduced by csv.DictReader 27419ea8026Sopenharmony_ci # when header + row mismatch 27519ea8026Sopenharmony_ci by.update((k, True) for k in r.keys() 27619ea8026Sopenharmony_ci if k is not None 27719ea8026Sopenharmony_ci and k not in fields 27819ea8026Sopenharmony_ci and not any(k == old_k for _, old_k in renames)) 27919ea8026Sopenharmony_ci by = list(by.keys()) 28019ea8026Sopenharmony_ci 28119ea8026Sopenharmony_ci # deduplicate fields 28219ea8026Sopenharmony_ci by = list(co.OrderedDict.fromkeys(by).keys()) 28319ea8026Sopenharmony_ci 28419ea8026Sopenharmony_ci # find best type for all fields 28519ea8026Sopenharmony_ci types_ = {} 28619ea8026Sopenharmony_ci for k in fields: 28719ea8026Sopenharmony_ci if k in types: 28819ea8026Sopenharmony_ci types_[k] = types[k] 28919ea8026Sopenharmony_ci else: 29019ea8026Sopenharmony_ci for t in TYPES.values(): 29119ea8026Sopenharmony_ci for r in results: 29219ea8026Sopenharmony_ci if k in r and r[k].strip(): 29319ea8026Sopenharmony_ci try: 29419ea8026Sopenharmony_ci t(r[k]) 29519ea8026Sopenharmony_ci except ValueError: 29619ea8026Sopenharmony_ci break 29719ea8026Sopenharmony_ci else: 29819ea8026Sopenharmony_ci types_[k] = t 29919ea8026Sopenharmony_ci break 30019ea8026Sopenharmony_ci else: 30119ea8026Sopenharmony_ci print("error: no type matches field %r?" % k) 30219ea8026Sopenharmony_ci sys.exit(-1) 30319ea8026Sopenharmony_ci types = types_ 30419ea8026Sopenharmony_ci 30519ea8026Sopenharmony_ci # does folding change the type? 30619ea8026Sopenharmony_ci types_ = {} 30719ea8026Sopenharmony_ci for k, t in types.items(): 30819ea8026Sopenharmony_ci types_[k] = ops.get(k, OPS['sum'])([t()]).__class__ 30919ea8026Sopenharmony_ci 31019ea8026Sopenharmony_ci 31119ea8026Sopenharmony_ci # create result class 31219ea8026Sopenharmony_ci def __new__(cls, **r): 31319ea8026Sopenharmony_ci return cls.__mro__[1].__new__(cls, 31419ea8026Sopenharmony_ci **{k: r.get(k, '') for k in by}, 31519ea8026Sopenharmony_ci **{k: r[k] if k in r and isinstance(r[k], list) 31619ea8026Sopenharmony_ci else [types[k](r[k])] if k in r 31719ea8026Sopenharmony_ci else [] 31819ea8026Sopenharmony_ci for k in fields}) 31919ea8026Sopenharmony_ci 32019ea8026Sopenharmony_ci def __add__(self, other): 32119ea8026Sopenharmony_ci return self.__class__( 32219ea8026Sopenharmony_ci **{k: getattr(self, k) for k in by}, 32319ea8026Sopenharmony_ci **{k: object.__getattribute__(self, k) 32419ea8026Sopenharmony_ci + object.__getattribute__(other, k) 32519ea8026Sopenharmony_ci for k in fields}) 32619ea8026Sopenharmony_ci 32719ea8026Sopenharmony_ci def __getattribute__(self, k): 32819ea8026Sopenharmony_ci if k in fields: 32919ea8026Sopenharmony_ci if object.__getattribute__(self, k): 33019ea8026Sopenharmony_ci return ops.get(k, OPS['sum'])(object.__getattribute__(self, k)) 33119ea8026Sopenharmony_ci else: 33219ea8026Sopenharmony_ci return None 33319ea8026Sopenharmony_ci return object.__getattribute__(self, k) 33419ea8026Sopenharmony_ci 33519ea8026Sopenharmony_ci return type('Result', (co.namedtuple('Result', by + fields),), { 33619ea8026Sopenharmony_ci '__slots__': (), 33719ea8026Sopenharmony_ci '__new__': __new__, 33819ea8026Sopenharmony_ci '__add__': __add__, 33919ea8026Sopenharmony_ci '__getattribute__': __getattribute__, 34019ea8026Sopenharmony_ci '_by': by, 34119ea8026Sopenharmony_ci '_fields': fields, 34219ea8026Sopenharmony_ci '_sort': fields, 34319ea8026Sopenharmony_ci '_types': types_, 34419ea8026Sopenharmony_ci }) 34519ea8026Sopenharmony_ci 34619ea8026Sopenharmony_ci 34719ea8026Sopenharmony_cidef fold(Result, results, *, 34819ea8026Sopenharmony_ci by=None, 34919ea8026Sopenharmony_ci defines=None, 35019ea8026Sopenharmony_ci **_): 35119ea8026Sopenharmony_ci if by is None: 35219ea8026Sopenharmony_ci by = Result._by 35319ea8026Sopenharmony_ci 35419ea8026Sopenharmony_ci for k in it.chain(by or [], (k for k, _ in defines or [])): 35519ea8026Sopenharmony_ci if k not in Result._by and k not in Result._fields: 35619ea8026Sopenharmony_ci print("error: could not find field %r?" % k) 35719ea8026Sopenharmony_ci sys.exit(-1) 35819ea8026Sopenharmony_ci 35919ea8026Sopenharmony_ci # filter by matching defines 36019ea8026Sopenharmony_ci if defines is not None: 36119ea8026Sopenharmony_ci results_ = [] 36219ea8026Sopenharmony_ci for r in results: 36319ea8026Sopenharmony_ci if all(getattr(r, k) in vs for k, vs in defines): 36419ea8026Sopenharmony_ci results_.append(r) 36519ea8026Sopenharmony_ci results = results_ 36619ea8026Sopenharmony_ci 36719ea8026Sopenharmony_ci # organize results into conflicts 36819ea8026Sopenharmony_ci folding = co.OrderedDict() 36919ea8026Sopenharmony_ci for r in results: 37019ea8026Sopenharmony_ci name = tuple(getattr(r, k) for k in by) 37119ea8026Sopenharmony_ci if name not in folding: 37219ea8026Sopenharmony_ci folding[name] = [] 37319ea8026Sopenharmony_ci folding[name].append(r) 37419ea8026Sopenharmony_ci 37519ea8026Sopenharmony_ci # merge conflicts 37619ea8026Sopenharmony_ci folded = [] 37719ea8026Sopenharmony_ci for name, rs in folding.items(): 37819ea8026Sopenharmony_ci folded.append(sum(rs[1:], start=rs[0])) 37919ea8026Sopenharmony_ci 38019ea8026Sopenharmony_ci return folded 38119ea8026Sopenharmony_ci 38219ea8026Sopenharmony_cidef table(Result, results, diff_results=None, *, 38319ea8026Sopenharmony_ci by=None, 38419ea8026Sopenharmony_ci fields=None, 38519ea8026Sopenharmony_ci sort=None, 38619ea8026Sopenharmony_ci summary=False, 38719ea8026Sopenharmony_ci all=False, 38819ea8026Sopenharmony_ci percent=False, 38919ea8026Sopenharmony_ci **_): 39019ea8026Sopenharmony_ci all_, all = all, __builtins__.all 39119ea8026Sopenharmony_ci 39219ea8026Sopenharmony_ci if by is None: 39319ea8026Sopenharmony_ci by = Result._by 39419ea8026Sopenharmony_ci if fields is None: 39519ea8026Sopenharmony_ci fields = Result._fields 39619ea8026Sopenharmony_ci types = Result._types 39719ea8026Sopenharmony_ci 39819ea8026Sopenharmony_ci # fold again 39919ea8026Sopenharmony_ci results = fold(Result, results, by=by) 40019ea8026Sopenharmony_ci if diff_results is not None: 40119ea8026Sopenharmony_ci diff_results = fold(Result, diff_results, by=by) 40219ea8026Sopenharmony_ci 40319ea8026Sopenharmony_ci # organize by name 40419ea8026Sopenharmony_ci table = { 40519ea8026Sopenharmony_ci ','.join(str(getattr(r, k) or '') for k in by): r 40619ea8026Sopenharmony_ci for r in results} 40719ea8026Sopenharmony_ci diff_table = { 40819ea8026Sopenharmony_ci ','.join(str(getattr(r, k) or '') for k in by): r 40919ea8026Sopenharmony_ci for r in diff_results or []} 41019ea8026Sopenharmony_ci names = list(table.keys() | diff_table.keys()) 41119ea8026Sopenharmony_ci 41219ea8026Sopenharmony_ci # sort again, now with diff info, note that python's sort is stable 41319ea8026Sopenharmony_ci names.sort() 41419ea8026Sopenharmony_ci if diff_results is not None: 41519ea8026Sopenharmony_ci names.sort(key=lambda n: tuple( 41619ea8026Sopenharmony_ci types[k].ratio( 41719ea8026Sopenharmony_ci getattr(table.get(n), k, None), 41819ea8026Sopenharmony_ci getattr(diff_table.get(n), k, None)) 41919ea8026Sopenharmony_ci for k in fields), 42019ea8026Sopenharmony_ci reverse=True) 42119ea8026Sopenharmony_ci if sort: 42219ea8026Sopenharmony_ci for k, reverse in reversed(sort): 42319ea8026Sopenharmony_ci names.sort( 42419ea8026Sopenharmony_ci key=lambda n: tuple( 42519ea8026Sopenharmony_ci (getattr(table[n], k),) 42619ea8026Sopenharmony_ci if getattr(table.get(n), k, None) is not None else () 42719ea8026Sopenharmony_ci for k in ([k] if k else [ 42819ea8026Sopenharmony_ci k for k in Result._sort if k in fields])), 42919ea8026Sopenharmony_ci reverse=reverse ^ (not k or k in Result._fields)) 43019ea8026Sopenharmony_ci 43119ea8026Sopenharmony_ci 43219ea8026Sopenharmony_ci # build up our lines 43319ea8026Sopenharmony_ci lines = [] 43419ea8026Sopenharmony_ci 43519ea8026Sopenharmony_ci # header 43619ea8026Sopenharmony_ci header = [] 43719ea8026Sopenharmony_ci header.append('%s%s' % ( 43819ea8026Sopenharmony_ci ','.join(by), 43919ea8026Sopenharmony_ci ' (%d added, %d removed)' % ( 44019ea8026Sopenharmony_ci sum(1 for n in table if n not in diff_table), 44119ea8026Sopenharmony_ci sum(1 for n in diff_table if n not in table)) 44219ea8026Sopenharmony_ci if diff_results is not None and not percent else '') 44319ea8026Sopenharmony_ci if not summary else '') 44419ea8026Sopenharmony_ci if diff_results is None: 44519ea8026Sopenharmony_ci for k in fields: 44619ea8026Sopenharmony_ci header.append(k) 44719ea8026Sopenharmony_ci elif percent: 44819ea8026Sopenharmony_ci for k in fields: 44919ea8026Sopenharmony_ci header.append(k) 45019ea8026Sopenharmony_ci else: 45119ea8026Sopenharmony_ci for k in fields: 45219ea8026Sopenharmony_ci header.append('o'+k) 45319ea8026Sopenharmony_ci for k in fields: 45419ea8026Sopenharmony_ci header.append('n'+k) 45519ea8026Sopenharmony_ci for k in fields: 45619ea8026Sopenharmony_ci header.append('d'+k) 45719ea8026Sopenharmony_ci header.append('') 45819ea8026Sopenharmony_ci lines.append(header) 45919ea8026Sopenharmony_ci 46019ea8026Sopenharmony_ci def table_entry(name, r, diff_r=None, ratios=[]): 46119ea8026Sopenharmony_ci entry = [] 46219ea8026Sopenharmony_ci entry.append(name) 46319ea8026Sopenharmony_ci if diff_results is None: 46419ea8026Sopenharmony_ci for k in fields: 46519ea8026Sopenharmony_ci entry.append(getattr(r, k).table() 46619ea8026Sopenharmony_ci if getattr(r, k, None) is not None 46719ea8026Sopenharmony_ci else types[k].none) 46819ea8026Sopenharmony_ci elif percent: 46919ea8026Sopenharmony_ci for k in fields: 47019ea8026Sopenharmony_ci entry.append(getattr(r, k).diff_table() 47119ea8026Sopenharmony_ci if getattr(r, k, None) is not None 47219ea8026Sopenharmony_ci else types[k].diff_none) 47319ea8026Sopenharmony_ci else: 47419ea8026Sopenharmony_ci for k in fields: 47519ea8026Sopenharmony_ci entry.append(getattr(diff_r, k).diff_table() 47619ea8026Sopenharmony_ci if getattr(diff_r, k, None) is not None 47719ea8026Sopenharmony_ci else types[k].diff_none) 47819ea8026Sopenharmony_ci for k in fields: 47919ea8026Sopenharmony_ci entry.append(getattr(r, k).diff_table() 48019ea8026Sopenharmony_ci if getattr(r, k, None) is not None 48119ea8026Sopenharmony_ci else types[k].diff_none) 48219ea8026Sopenharmony_ci for k in fields: 48319ea8026Sopenharmony_ci entry.append(types[k].diff_diff( 48419ea8026Sopenharmony_ci getattr(r, k, None), 48519ea8026Sopenharmony_ci getattr(diff_r, k, None))) 48619ea8026Sopenharmony_ci if diff_results is None: 48719ea8026Sopenharmony_ci entry.append('') 48819ea8026Sopenharmony_ci elif percent: 48919ea8026Sopenharmony_ci entry.append(' (%s)' % ', '.join( 49019ea8026Sopenharmony_ci '+∞%' if t == +m.inf 49119ea8026Sopenharmony_ci else '-∞%' if t == -m.inf 49219ea8026Sopenharmony_ci else '%+.1f%%' % (100*t) 49319ea8026Sopenharmony_ci for t in ratios)) 49419ea8026Sopenharmony_ci else: 49519ea8026Sopenharmony_ci entry.append(' (%s)' % ', '.join( 49619ea8026Sopenharmony_ci '+∞%' if t == +m.inf 49719ea8026Sopenharmony_ci else '-∞%' if t == -m.inf 49819ea8026Sopenharmony_ci else '%+.1f%%' % (100*t) 49919ea8026Sopenharmony_ci for t in ratios 50019ea8026Sopenharmony_ci if t) 50119ea8026Sopenharmony_ci if any(ratios) else '') 50219ea8026Sopenharmony_ci return entry 50319ea8026Sopenharmony_ci 50419ea8026Sopenharmony_ci # entries 50519ea8026Sopenharmony_ci if not summary: 50619ea8026Sopenharmony_ci for name in names: 50719ea8026Sopenharmony_ci r = table.get(name) 50819ea8026Sopenharmony_ci if diff_results is None: 50919ea8026Sopenharmony_ci diff_r = None 51019ea8026Sopenharmony_ci ratios = None 51119ea8026Sopenharmony_ci else: 51219ea8026Sopenharmony_ci diff_r = diff_table.get(name) 51319ea8026Sopenharmony_ci ratios = [ 51419ea8026Sopenharmony_ci types[k].ratio( 51519ea8026Sopenharmony_ci getattr(r, k, None), 51619ea8026Sopenharmony_ci getattr(diff_r, k, None)) 51719ea8026Sopenharmony_ci for k in fields] 51819ea8026Sopenharmony_ci if not all_ and not any(ratios): 51919ea8026Sopenharmony_ci continue 52019ea8026Sopenharmony_ci lines.append(table_entry(name, r, diff_r, ratios)) 52119ea8026Sopenharmony_ci 52219ea8026Sopenharmony_ci # total 52319ea8026Sopenharmony_ci r = next(iter(fold(Result, results, by=[])), None) 52419ea8026Sopenharmony_ci if diff_results is None: 52519ea8026Sopenharmony_ci diff_r = None 52619ea8026Sopenharmony_ci ratios = None 52719ea8026Sopenharmony_ci else: 52819ea8026Sopenharmony_ci diff_r = next(iter(fold(Result, diff_results, by=[])), None) 52919ea8026Sopenharmony_ci ratios = [ 53019ea8026Sopenharmony_ci types[k].ratio( 53119ea8026Sopenharmony_ci getattr(r, k, None), 53219ea8026Sopenharmony_ci getattr(diff_r, k, None)) 53319ea8026Sopenharmony_ci for k in fields] 53419ea8026Sopenharmony_ci lines.append(table_entry('TOTAL', r, diff_r, ratios)) 53519ea8026Sopenharmony_ci 53619ea8026Sopenharmony_ci # find the best widths, note that column 0 contains the names and column -1 53719ea8026Sopenharmony_ci # the ratios, so those are handled a bit differently 53819ea8026Sopenharmony_ci widths = [ 53919ea8026Sopenharmony_ci ((max(it.chain([w], (len(l[i]) for l in lines)))+1+4-1)//4)*4-1 54019ea8026Sopenharmony_ci for w, i in zip( 54119ea8026Sopenharmony_ci it.chain([23], it.repeat(7)), 54219ea8026Sopenharmony_ci range(len(lines[0])-1))] 54319ea8026Sopenharmony_ci 54419ea8026Sopenharmony_ci # print our table 54519ea8026Sopenharmony_ci for line in lines: 54619ea8026Sopenharmony_ci print('%-*s %s%s' % ( 54719ea8026Sopenharmony_ci widths[0], line[0], 54819ea8026Sopenharmony_ci ' '.join('%*s' % (w, x) 54919ea8026Sopenharmony_ci for w, x in zip(widths[1:], line[1:-1])), 55019ea8026Sopenharmony_ci line[-1])) 55119ea8026Sopenharmony_ci 55219ea8026Sopenharmony_ci 55319ea8026Sopenharmony_cidef openio(path, mode='r', buffering=-1): 55419ea8026Sopenharmony_ci # allow '-' for stdin/stdout 55519ea8026Sopenharmony_ci if path == '-': 55619ea8026Sopenharmony_ci if mode == 'r': 55719ea8026Sopenharmony_ci return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering) 55819ea8026Sopenharmony_ci else: 55919ea8026Sopenharmony_ci return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering) 56019ea8026Sopenharmony_ci else: 56119ea8026Sopenharmony_ci return open(path, mode, buffering) 56219ea8026Sopenharmony_ci 56319ea8026Sopenharmony_cidef main(csv_paths, *, 56419ea8026Sopenharmony_ci by=None, 56519ea8026Sopenharmony_ci fields=None, 56619ea8026Sopenharmony_ci defines=None, 56719ea8026Sopenharmony_ci sort=None, 56819ea8026Sopenharmony_ci **args): 56919ea8026Sopenharmony_ci # separate out renames 57019ea8026Sopenharmony_ci renames = list(it.chain.from_iterable( 57119ea8026Sopenharmony_ci ((k, v) for v in vs) 57219ea8026Sopenharmony_ci for k, vs in it.chain(by or [], fields or []))) 57319ea8026Sopenharmony_ci if by is not None: 57419ea8026Sopenharmony_ci by = [k for k, _ in by] 57519ea8026Sopenharmony_ci if fields is not None: 57619ea8026Sopenharmony_ci fields = [k for k, _ in fields] 57719ea8026Sopenharmony_ci 57819ea8026Sopenharmony_ci # figure out types 57919ea8026Sopenharmony_ci types = {} 58019ea8026Sopenharmony_ci for t in TYPES.keys(): 58119ea8026Sopenharmony_ci for k in args.get(t, []): 58219ea8026Sopenharmony_ci if k in types: 58319ea8026Sopenharmony_ci print("error: conflicting type for field %r?" % k) 58419ea8026Sopenharmony_ci sys.exit(-1) 58519ea8026Sopenharmony_ci types[k] = TYPES[t] 58619ea8026Sopenharmony_ci # rename types? 58719ea8026Sopenharmony_ci if renames: 58819ea8026Sopenharmony_ci types_ = {} 58919ea8026Sopenharmony_ci for new_k, old_k in renames: 59019ea8026Sopenharmony_ci if old_k in types: 59119ea8026Sopenharmony_ci types_[new_k] = types[old_k] 59219ea8026Sopenharmony_ci types.update(types_) 59319ea8026Sopenharmony_ci 59419ea8026Sopenharmony_ci # figure out merge operations 59519ea8026Sopenharmony_ci ops = {} 59619ea8026Sopenharmony_ci for o in OPS.keys(): 59719ea8026Sopenharmony_ci for k in args.get(o, []): 59819ea8026Sopenharmony_ci if k in ops: 59919ea8026Sopenharmony_ci print("error: conflicting op for field %r?" % k) 60019ea8026Sopenharmony_ci sys.exit(-1) 60119ea8026Sopenharmony_ci ops[k] = OPS[o] 60219ea8026Sopenharmony_ci # rename ops? 60319ea8026Sopenharmony_ci if renames: 60419ea8026Sopenharmony_ci ops_ = {} 60519ea8026Sopenharmony_ci for new_k, old_k in renames: 60619ea8026Sopenharmony_ci if old_k in ops: 60719ea8026Sopenharmony_ci ops_[new_k] = ops[old_k] 60819ea8026Sopenharmony_ci ops.update(ops_) 60919ea8026Sopenharmony_ci 61019ea8026Sopenharmony_ci # find CSV files 61119ea8026Sopenharmony_ci results = [] 61219ea8026Sopenharmony_ci for path in csv_paths: 61319ea8026Sopenharmony_ci try: 61419ea8026Sopenharmony_ci with openio(path) as f: 61519ea8026Sopenharmony_ci reader = csv.DictReader(f, restval='') 61619ea8026Sopenharmony_ci for r in reader: 61719ea8026Sopenharmony_ci # rename fields? 61819ea8026Sopenharmony_ci if renames: 61919ea8026Sopenharmony_ci # make a copy so renames can overlap 62019ea8026Sopenharmony_ci r_ = {} 62119ea8026Sopenharmony_ci for new_k, old_k in renames: 62219ea8026Sopenharmony_ci if old_k in r: 62319ea8026Sopenharmony_ci r_[new_k] = r[old_k] 62419ea8026Sopenharmony_ci r.update(r_) 62519ea8026Sopenharmony_ci 62619ea8026Sopenharmony_ci results.append(r) 62719ea8026Sopenharmony_ci except FileNotFoundError: 62819ea8026Sopenharmony_ci pass 62919ea8026Sopenharmony_ci 63019ea8026Sopenharmony_ci # homogenize 63119ea8026Sopenharmony_ci Result = infer(results, 63219ea8026Sopenharmony_ci by=by, 63319ea8026Sopenharmony_ci fields=fields, 63419ea8026Sopenharmony_ci types=types, 63519ea8026Sopenharmony_ci ops=ops, 63619ea8026Sopenharmony_ci renames=renames) 63719ea8026Sopenharmony_ci results_ = [] 63819ea8026Sopenharmony_ci for r in results: 63919ea8026Sopenharmony_ci if not any(k in r and r[k].strip() 64019ea8026Sopenharmony_ci for k in Result._fields): 64119ea8026Sopenharmony_ci continue 64219ea8026Sopenharmony_ci try: 64319ea8026Sopenharmony_ci results_.append(Result(**{ 64419ea8026Sopenharmony_ci k: r[k] for k in Result._by + Result._fields 64519ea8026Sopenharmony_ci if k in r and r[k].strip()})) 64619ea8026Sopenharmony_ci except TypeError: 64719ea8026Sopenharmony_ci pass 64819ea8026Sopenharmony_ci results = results_ 64919ea8026Sopenharmony_ci 65019ea8026Sopenharmony_ci # fold 65119ea8026Sopenharmony_ci results = fold(Result, results, by=by, defines=defines) 65219ea8026Sopenharmony_ci 65319ea8026Sopenharmony_ci # sort, note that python's sort is stable 65419ea8026Sopenharmony_ci results.sort() 65519ea8026Sopenharmony_ci if sort: 65619ea8026Sopenharmony_ci for k, reverse in reversed(sort): 65719ea8026Sopenharmony_ci results.sort( 65819ea8026Sopenharmony_ci key=lambda r: tuple( 65919ea8026Sopenharmony_ci (getattr(r, k),) if getattr(r, k) is not None else () 66019ea8026Sopenharmony_ci for k in ([k] if k else Result._sort)), 66119ea8026Sopenharmony_ci reverse=reverse ^ (not k or k in Result._fields)) 66219ea8026Sopenharmony_ci 66319ea8026Sopenharmony_ci # write results to CSV 66419ea8026Sopenharmony_ci if args.get('output'): 66519ea8026Sopenharmony_ci with openio(args['output'], 'w') as f: 66619ea8026Sopenharmony_ci writer = csv.DictWriter(f, Result._by + Result._fields) 66719ea8026Sopenharmony_ci writer.writeheader() 66819ea8026Sopenharmony_ci for r in results: 66919ea8026Sopenharmony_ci # note we need to go through getattr to resolve lazy fields 67019ea8026Sopenharmony_ci writer.writerow({ 67119ea8026Sopenharmony_ci k: getattr(r, k) for k in Result._by + Result._fields}) 67219ea8026Sopenharmony_ci 67319ea8026Sopenharmony_ci # find previous results? 67419ea8026Sopenharmony_ci if args.get('diff'): 67519ea8026Sopenharmony_ci diff_results = [] 67619ea8026Sopenharmony_ci try: 67719ea8026Sopenharmony_ci with openio(args['diff']) as f: 67819ea8026Sopenharmony_ci reader = csv.DictReader(f, restval='') 67919ea8026Sopenharmony_ci for r in reader: 68019ea8026Sopenharmony_ci # rename fields? 68119ea8026Sopenharmony_ci if renames: 68219ea8026Sopenharmony_ci # make a copy so renames can overlap 68319ea8026Sopenharmony_ci r_ = {} 68419ea8026Sopenharmony_ci for new_k, old_k in renames: 68519ea8026Sopenharmony_ci if old_k in r: 68619ea8026Sopenharmony_ci r_[new_k] = r[old_k] 68719ea8026Sopenharmony_ci r.update(r_) 68819ea8026Sopenharmony_ci 68919ea8026Sopenharmony_ci if not any(k in r and r[k].strip() 69019ea8026Sopenharmony_ci for k in Result._fields): 69119ea8026Sopenharmony_ci continue 69219ea8026Sopenharmony_ci try: 69319ea8026Sopenharmony_ci diff_results.append(Result(**{ 69419ea8026Sopenharmony_ci k: r[k] for k in Result._by + Result._fields 69519ea8026Sopenharmony_ci if k in r and r[k].strip()})) 69619ea8026Sopenharmony_ci except TypeError: 69719ea8026Sopenharmony_ci pass 69819ea8026Sopenharmony_ci except FileNotFoundError: 69919ea8026Sopenharmony_ci pass 70019ea8026Sopenharmony_ci 70119ea8026Sopenharmony_ci # fold 70219ea8026Sopenharmony_ci diff_results = fold(Result, diff_results, by=by, defines=defines) 70319ea8026Sopenharmony_ci 70419ea8026Sopenharmony_ci # print table 70519ea8026Sopenharmony_ci if not args.get('quiet'): 70619ea8026Sopenharmony_ci table(Result, results, 70719ea8026Sopenharmony_ci diff_results if args.get('diff') else None, 70819ea8026Sopenharmony_ci by=by, 70919ea8026Sopenharmony_ci fields=fields, 71019ea8026Sopenharmony_ci sort=sort, 71119ea8026Sopenharmony_ci **args) 71219ea8026Sopenharmony_ci 71319ea8026Sopenharmony_ci 71419ea8026Sopenharmony_ciif __name__ == "__main__": 71519ea8026Sopenharmony_ci import argparse 71619ea8026Sopenharmony_ci import sys 71719ea8026Sopenharmony_ci parser = argparse.ArgumentParser( 71819ea8026Sopenharmony_ci description="Summarize measurements in CSV files.", 71919ea8026Sopenharmony_ci allow_abbrev=False) 72019ea8026Sopenharmony_ci parser.add_argument( 72119ea8026Sopenharmony_ci 'csv_paths', 72219ea8026Sopenharmony_ci nargs='*', 72319ea8026Sopenharmony_ci help="Input *.csv files.") 72419ea8026Sopenharmony_ci parser.add_argument( 72519ea8026Sopenharmony_ci '-q', '--quiet', 72619ea8026Sopenharmony_ci action='store_true', 72719ea8026Sopenharmony_ci help="Don't show anything, useful with -o.") 72819ea8026Sopenharmony_ci parser.add_argument( 72919ea8026Sopenharmony_ci '-o', '--output', 73019ea8026Sopenharmony_ci help="Specify CSV file to store results.") 73119ea8026Sopenharmony_ci parser.add_argument( 73219ea8026Sopenharmony_ci '-d', '--diff', 73319ea8026Sopenharmony_ci help="Specify CSV file to diff against.") 73419ea8026Sopenharmony_ci parser.add_argument( 73519ea8026Sopenharmony_ci '-a', '--all', 73619ea8026Sopenharmony_ci action='store_true', 73719ea8026Sopenharmony_ci help="Show all, not just the ones that changed.") 73819ea8026Sopenharmony_ci parser.add_argument( 73919ea8026Sopenharmony_ci '-p', '--percent', 74019ea8026Sopenharmony_ci action='store_true', 74119ea8026Sopenharmony_ci help="Only show percentage change, not a full diff.") 74219ea8026Sopenharmony_ci parser.add_argument( 74319ea8026Sopenharmony_ci '-b', '--by', 74419ea8026Sopenharmony_ci action='append', 74519ea8026Sopenharmony_ci type=lambda x: ( 74619ea8026Sopenharmony_ci lambda k,v=None: (k, v.split(',') if v is not None else ()) 74719ea8026Sopenharmony_ci )(*x.split('=', 1)), 74819ea8026Sopenharmony_ci help="Group by this field. Can rename fields with new_name=old_name.") 74919ea8026Sopenharmony_ci parser.add_argument( 75019ea8026Sopenharmony_ci '-f', '--field', 75119ea8026Sopenharmony_ci dest='fields', 75219ea8026Sopenharmony_ci action='append', 75319ea8026Sopenharmony_ci type=lambda x: ( 75419ea8026Sopenharmony_ci lambda k,v=None: (k, v.split(',') if v is not None else ()) 75519ea8026Sopenharmony_ci )(*x.split('=', 1)), 75619ea8026Sopenharmony_ci help="Show this field. Can rename fields with new_name=old_name.") 75719ea8026Sopenharmony_ci parser.add_argument( 75819ea8026Sopenharmony_ci '-D', '--define', 75919ea8026Sopenharmony_ci dest='defines', 76019ea8026Sopenharmony_ci action='append', 76119ea8026Sopenharmony_ci type=lambda x: (lambda k,v: (k, set(v.split(','))))(*x.split('=', 1)), 76219ea8026Sopenharmony_ci help="Only include results where this field is this value. May include " 76319ea8026Sopenharmony_ci "comma-separated options.") 76419ea8026Sopenharmony_ci class AppendSort(argparse.Action): 76519ea8026Sopenharmony_ci def __call__(self, parser, namespace, value, option): 76619ea8026Sopenharmony_ci if namespace.sort is None: 76719ea8026Sopenharmony_ci namespace.sort = [] 76819ea8026Sopenharmony_ci namespace.sort.append((value, True if option == '-S' else False)) 76919ea8026Sopenharmony_ci parser.add_argument( 77019ea8026Sopenharmony_ci '-s', '--sort', 77119ea8026Sopenharmony_ci nargs='?', 77219ea8026Sopenharmony_ci action=AppendSort, 77319ea8026Sopenharmony_ci help="Sort by this field.") 77419ea8026Sopenharmony_ci parser.add_argument( 77519ea8026Sopenharmony_ci '-S', '--reverse-sort', 77619ea8026Sopenharmony_ci nargs='?', 77719ea8026Sopenharmony_ci action=AppendSort, 77819ea8026Sopenharmony_ci help="Sort by this field, but backwards.") 77919ea8026Sopenharmony_ci parser.add_argument( 78019ea8026Sopenharmony_ci '-Y', '--summary', 78119ea8026Sopenharmony_ci action='store_true', 78219ea8026Sopenharmony_ci help="Only show the total.") 78319ea8026Sopenharmony_ci parser.add_argument( 78419ea8026Sopenharmony_ci '--int', 78519ea8026Sopenharmony_ci action='append', 78619ea8026Sopenharmony_ci help="Treat these fields as ints.") 78719ea8026Sopenharmony_ci parser.add_argument( 78819ea8026Sopenharmony_ci '--float', 78919ea8026Sopenharmony_ci action='append', 79019ea8026Sopenharmony_ci help="Treat these fields as floats.") 79119ea8026Sopenharmony_ci parser.add_argument( 79219ea8026Sopenharmony_ci '--frac', 79319ea8026Sopenharmony_ci action='append', 79419ea8026Sopenharmony_ci help="Treat these fields as fractions.") 79519ea8026Sopenharmony_ci parser.add_argument( 79619ea8026Sopenharmony_ci '--sum', 79719ea8026Sopenharmony_ci action='append', 79819ea8026Sopenharmony_ci help="Add these fields (the default).") 79919ea8026Sopenharmony_ci parser.add_argument( 80019ea8026Sopenharmony_ci '--prod', 80119ea8026Sopenharmony_ci action='append', 80219ea8026Sopenharmony_ci help="Multiply these fields.") 80319ea8026Sopenharmony_ci parser.add_argument( 80419ea8026Sopenharmony_ci '--min', 80519ea8026Sopenharmony_ci action='append', 80619ea8026Sopenharmony_ci help="Take the minimum of these fields.") 80719ea8026Sopenharmony_ci parser.add_argument( 80819ea8026Sopenharmony_ci '--max', 80919ea8026Sopenharmony_ci action='append', 81019ea8026Sopenharmony_ci help="Take the maximum of these fields.") 81119ea8026Sopenharmony_ci parser.add_argument( 81219ea8026Sopenharmony_ci '--mean', 81319ea8026Sopenharmony_ci action='append', 81419ea8026Sopenharmony_ci help="Average these fields.") 81519ea8026Sopenharmony_ci parser.add_argument( 81619ea8026Sopenharmony_ci '--stddev', 81719ea8026Sopenharmony_ci action='append', 81819ea8026Sopenharmony_ci help="Find the standard deviation of these fields.") 81919ea8026Sopenharmony_ci parser.add_argument( 82019ea8026Sopenharmony_ci '--gmean', 82119ea8026Sopenharmony_ci action='append', 82219ea8026Sopenharmony_ci help="Find the geometric mean of these fields.") 82319ea8026Sopenharmony_ci parser.add_argument( 82419ea8026Sopenharmony_ci '--gstddev', 82519ea8026Sopenharmony_ci action='append', 82619ea8026Sopenharmony_ci help="Find the geometric standard deviation of these fields.") 82719ea8026Sopenharmony_ci sys.exit(main(**{k: v 82819ea8026Sopenharmony_ci for k, v in vars(parser.parse_intermixed_args()).items() 82919ea8026Sopenharmony_ci if v is not None})) 830