mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	- Changed `make summary` to show a one line summary - Added `make lfs.csv` rule, which is useful for finding more info with other scripts - Fixed small issue in ./scripts/summary.py - Added *.ci (callgraph) and *.csv (script output) to CI
		
			
				
	
	
		
			280 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| #
 | |
| # Script to summarize the outputs of other scripts. Operates on CSV files.
 | |
| #
 | |
| 
 | |
| import functools as ft
 | |
| import collections as co
 | |
| import os
 | |
| import csv
 | |
| import re
 | |
| import math as m
 | |
| 
 | |
| # displayable fields
 | |
| Field = co.namedtuple('Field', 'name,parse,acc,key,fmt,repr,null,ratio')
 | |
| FIELDS = [
 | |
|     # name, parse, accumulate, fmt, print, null
 | |
|     Field('code',
 | |
|         lambda r: int(r['code_size']),
 | |
|         sum,
 | |
|         lambda r: r,
 | |
|         '%7s',
 | |
|         lambda r: r,
 | |
|         '-',
 | |
|         lambda old, new: (new-old)/old),
 | |
|     Field('data',
 | |
|         lambda r: int(r['data_size']),
 | |
|         sum,
 | |
|         lambda r: r,
 | |
|         '%7s',
 | |
|         lambda r: r,
 | |
|         '-',
 | |
|         lambda old, new: (new-old)/old),
 | |
|     Field('stack',
 | |
|         lambda r: float(r['stack_limit']),
 | |
|         max,
 | |
|         lambda r: r,
 | |
|         '%7s',
 | |
|         lambda r: '∞' if m.isinf(r) else int(r),
 | |
|         '-',
 | |
|         lambda old, new: (new-old)/old),
 | |
|     Field('structs',
 | |
|         lambda r: int(r['struct_size']),
 | |
|         sum,
 | |
|         lambda r: r,
 | |
|         '%8s',
 | |
|         lambda r: r,
 | |
|         '-',
 | |
|         lambda old, new: (new-old)/old),
 | |
|     Field('coverage',
 | |
|         lambda r: (int(r['coverage_hits']), int(r['coverage_count'])),
 | |
|         lambda rs: ft.reduce(lambda a, b: (a[0]+b[0], a[1]+b[1]), rs),
 | |
|         lambda r: r[0]/r[1],
 | |
|         '%19s',
 | |
|         lambda r: '%11s %7s' % ('%d/%d' % (r[0], r[1]), '%.1f%%' % (100*r[0]/r[1])),
 | |
|         '%11s %7s' % ('-', '-'),
 | |
|         lambda old, new: ((new[0]/new[1]) - (old[0]/old[1])))
 | |
| ]
 | |
| 
 | |
| 
 | |
| def main(**args):
 | |
|     def openio(path, mode='r'):
 | |
|         if path == '-':
 | |
|             if 'r' in mode:
 | |
|                 return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
 | |
|             else:
 | |
|                 return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
 | |
|         else:
 | |
|             return open(path, mode)
 | |
| 
 | |
|     # find results
 | |
|     results = co.defaultdict(lambda: {})
 | |
|     for path in args.get('csv_paths', '-'):
 | |
|         try:
 | |
|             with openio(path) as f:
 | |
|                 r = csv.DictReader(f)
 | |
|                 for result in r:
 | |
|                     file = result.pop('file', '')
 | |
|                     name = result.pop('name', '')
 | |
|                     prev = results[(file, name)]
 | |
|                     for field in FIELDS:
 | |
|                         try:
 | |
|                             r = field.parse(result)
 | |
|                             if field.name in prev:
 | |
|                                 results[(file, name)][field.name] = field.acc(
 | |
|                                     [prev[field.name], r])
 | |
|                             else:
 | |
|                                 results[(file, name)][field.name] = r
 | |
|                         except (KeyError, ValueError):
 | |
|                             pass
 | |
|         except FileNotFoundError:
 | |
|             pass
 | |
| 
 | |
|     # find fields
 | |
|     if args.get('all_fields'):
 | |
|         fields = FIELDS
 | |
|     elif args.get('fields') is not None:
 | |
|         fields_dict = {field.name: field for field in FIELDS}
 | |
|         fields = [fields_dict[f] for f in args['fields']]
 | |
|     else:
 | |
|         fields = []
 | |
|         for field in FIELDS:
 | |
|             if any(field.name in result for result in results.values()):
 | |
|                 fields.append(field)
 | |
| 
 | |
|     # find total for every field
 | |
|     total = {}
 | |
|     for result in results.values():
 | |
|         for field in fields:
 | |
|             if field.name in result and field.name in total:
 | |
|                 total[field.name] = field.acc(
 | |
|                     [total[field.name], result[field.name]])
 | |
|             elif field.name in result:
 | |
|                 total[field.name] = result[field.name]
 | |
| 
 | |
|     # find previous results?
 | |
|     if args.get('diff'):
 | |
|         prev_results = co.defaultdict(lambda: {})
 | |
|         try:
 | |
|             with openio(args['diff']) as f:
 | |
|                 r = csv.DictReader(f)
 | |
|                 for result in r:
 | |
|                     file = result.pop('file', '')
 | |
|                     name = result.pop('name', '')
 | |
|                     prev = prev_results[(file, name)]
 | |
|                     for field in FIELDS:
 | |
|                         try:
 | |
|                             r = field.parse(result)
 | |
|                             if field.name in prev:
 | |
|                                 prev_results[(file, name)][field.name] = field.acc(
 | |
|                                     [prev[field.name], r])
 | |
|                             else:
 | |
|                                 prev_results[(file, name)][field.name] = r
 | |
|                         except (KeyError, ValueError):
 | |
|                             pass
 | |
|         except FileNotFoundError:
 | |
|             pass
 | |
| 
 | |
|         prev_total = {}
 | |
|         for result in prev_results.values():
 | |
|             for field in fields:
 | |
|                 if field.name in result and field.name in prev_total:
 | |
|                     prev_total[field.name] = field.acc(
 | |
|                         [prev_total[field.name], result[field.name]])
 | |
|                 elif field.name in result:
 | |
|                     prev_total[field.name] = result[field.name]
 | |
| 
 | |
|     # print results
 | |
|     def dedup_entries(results, by='name'):
 | |
|         entries = co.defaultdict(lambda: {})
 | |
|         for (file, func), result in results.items():
 | |
|             entry = (file if by == 'file' else func)
 | |
|             prev = entries[entry]
 | |
|             for field in fields:
 | |
|                 if field.name in result and field.name in prev:
 | |
|                     entries[entry][field.name] = field.acc(
 | |
|                         [prev[field.name], result[field.name]])
 | |
|                 elif field.name in result:
 | |
|                     entries[entry][field.name] = result[field.name]
 | |
|         return entries
 | |
| 
 | |
|     def sorted_entries(entries):
 | |
|         if args.get('sort') is not None:
 | |
|             field = {field.name: field for field in FIELDS}[args['sort']]
 | |
|             return sorted(entries, key=lambda x: (
 | |
|                 -(field.key(x[1][field.name])) if field.name in x[1] else -1, x))
 | |
|         elif args.get('reverse_sort') is not None:
 | |
|             field = {field.name: field for field in FIELDS}[args['reverse_sort']]
 | |
|             return sorted(entries, key=lambda x: (
 | |
|                 +(field.key(x[1][field.name])) if field.name in x[1] else -1, x))
 | |
|         else:
 | |
|             return sorted(entries)
 | |
| 
 | |
|     def print_header(by=''):
 | |
|         if not args.get('diff'):
 | |
|             print('%-36s' % by, end='')
 | |
|             for field in fields:
 | |
|                 print((' '+field.fmt) % field.name, end='')
 | |
|             print()
 | |
|         else:
 | |
|             print('%-36s' % by, end='')
 | |
|             for field in fields:
 | |
|                 print((' '+field.fmt) % field.name, end='')
 | |
|                 print(' %-9s' % '', end='')
 | |
|             print()
 | |
| 
 | |
|     def print_entry(name, result):
 | |
|         print('%-36s' % name, end='')
 | |
|         for field in fields:
 | |
|             r = result.get(field.name)
 | |
|             if r is not None:
 | |
|                 print((' '+field.fmt) % field.repr(r), end='')
 | |
|             else:
 | |
|                 print((' '+field.fmt) % '-', end='')
 | |
|         print()
 | |
| 
 | |
|     def print_diff_entry(name, old, new):
 | |
|         print('%-36s' % name, end='')
 | |
|         for field in fields:
 | |
|             n = new.get(field.name)
 | |
|             if n is not None:
 | |
|                 print((' '+field.fmt) % field.repr(n), end='')
 | |
|             else:
 | |
|                 print((' '+field.fmt) % '-', end='')
 | |
|             o = old.get(field.name)
 | |
|             ratio = (
 | |
|                 0.0 if m.isinf(o or 0) and m.isinf(n or 0)
 | |
|                     else +float('inf') if m.isinf(n or 0)
 | |
|                     else -float('inf') if m.isinf(o or 0)
 | |
|                     else 0.0 if not o and not n
 | |
|                     else +1.0 if not o
 | |
|                     else -1.0 if not n
 | |
|                     else field.ratio(o, n))
 | |
|             print(' %-9s' % (
 | |
|                 '' if not ratio
 | |
|                     else '(+∞%)' if ratio > 0 and m.isinf(ratio)
 | |
|                     else '(-∞%)' if ratio < 0 and m.isinf(ratio)
 | |
|                     else '(%+.1f%%)' % (100*ratio)), end='')
 | |
|         print()
 | |
| 
 | |
|     def print_entries(by='name'):
 | |
|         entries = dedup_entries(results, by=by)
 | |
| 
 | |
|         if not args.get('diff'):
 | |
|             print_header(by=by)
 | |
|             for name, result in sorted_entries(entries.items()):
 | |
|                 print_entry(name, result)
 | |
|         else:
 | |
|             prev_entries = dedup_entries(prev_results, by=by)
 | |
|             print_header(by='%s (%d added, %d removed)' % (by,
 | |
|                 sum(1 for name in entries if name not in prev_entries),
 | |
|                 sum(1 for name in prev_entries if name not in entries)))
 | |
|             for name, result in sorted_entries(entries.items()):
 | |
|                 if args.get('all') or result != prev_entries.get(name, {}):
 | |
|                     print_diff_entry(name, prev_entries.get(name, {}), result)
 | |
| 
 | |
|     def print_totals():
 | |
|         if not args.get('diff'):
 | |
|             print_entry('TOTAL', total)
 | |
|         else:
 | |
|             print_diff_entry('TOTAL', prev_total, total)
 | |
| 
 | |
|     if args.get('summary'):
 | |
|         print_header()
 | |
|         print_totals()
 | |
|     elif args.get('files'):
 | |
|         print_entries(by='file')
 | |
|         print_totals()
 | |
|     else:
 | |
|         print_entries(by='name')
 | |
|         print_totals()
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     import argparse
 | |
|     import sys
 | |
|     parser = argparse.ArgumentParser(
 | |
|         description="Summarize measurements")
 | |
|     parser.add_argument('csv_paths', nargs='*', default='-',
 | |
|         help="Description of where to find *.csv files. May be a directory \
 | |
|             or list of paths. *.csv files will be merged to show the total \
 | |
|             coverage.")
 | |
|     parser.add_argument('-d', '--diff',
 | |
|         help="Specify CSV file to diff against.")
 | |
|     parser.add_argument('-a', '--all', action='store_true',
 | |
|         help="Show all objects, not just the ones that changed.")
 | |
|     parser.add_argument('-e', '--all-fields', action='store_true',
 | |
|         help="Show all fields, even those with no results.")
 | |
|     parser.add_argument('-f', '--fields', type=lambda x: re.split('\s*,\s*', x),
 | |
|         help="Comma separated list of fields to print, by default all fields \
 | |
|             that are found in the CSV files are printed.")
 | |
|     parser.add_argument('-s', '--sort',
 | |
|         help="Sort by this field.")
 | |
|     parser.add_argument('-S', '--reverse-sort',
 | |
|         help="Sort by this field, but backwards.")
 | |
|     parser.add_argument('-F', '--files', action='store_true',
 | |
|         help="Show file-level calls.")
 | |
|     parser.add_argument('-Y', '--summary', action='store_true',
 | |
|         help="Only show the totals.")
 | |
|     sys.exit(main(**vars(parser.parse_args())))
 |