1eb8dc403SDave Cobbley#!/usr/bin/env python3 2c342db35SBrad Bishop# 3*92b42cb3SPatrick Williams# Copyright OpenEmbedded Contributors 4*92b42cb3SPatrick Williams# 5c342db35SBrad Bishop# SPDX-License-Identifier: GPL-2.0-only 6c342db35SBrad Bishop# 7eb8dc403SDave Cobbley 8eb8dc403SDave Cobbleyimport argparse 9eb8dc403SDave Cobbleyimport os 10eb8dc403SDave Cobbleyimport re 11eb8dc403SDave Cobbleyimport sys 12eb8dc403SDave Cobbley 13eb8dc403SDave Cobbleyarg_parser = argparse.ArgumentParser( 14eb8dc403SDave Cobbley description=""" 15eb8dc403SDave CobbleyReports time consumed for one or more task in a format similar to the standard 16eb8dc403SDave CobbleyBash 'time' builtin. Optionally sorts tasks by real (wall-clock), user (user 17eb8dc403SDave Cobbleyspace CPU), or sys (kernel CPU) time. 18eb8dc403SDave Cobbley""") 19eb8dc403SDave Cobbley 20eb8dc403SDave Cobbleyarg_parser.add_argument( 21eb8dc403SDave Cobbley "paths", 22eb8dc403SDave Cobbley metavar="path", 23eb8dc403SDave Cobbley nargs="+", 24eb8dc403SDave Cobbley help=""" 25eb8dc403SDave CobbleyA path containing task buildstats. If the path is a directory, e.g. 26eb8dc403SDave Cobbleybuild/tmp/buildstats, then all task found (recursively) in it will be 27eb8dc403SDave Cobbleyprocessed. If the path is a single task buildstat, e.g. 28eb8dc403SDave Cobbleybuild/tmp/buildstats/20161018083535/foo-1.0-r0/do_compile, then just that 29eb8dc403SDave Cobbleybuildstat will be processed. Multiple paths can be specified to process all of 30eb8dc403SDave Cobbleythem. Files whose names do not start with "do_" are ignored. 31eb8dc403SDave Cobbley""") 32eb8dc403SDave Cobbley 33eb8dc403SDave Cobbleyarg_parser.add_argument( 34eb8dc403SDave Cobbley "--sort", 35eb8dc403SDave Cobbley choices=("none", "real", "user", "sys"), 36eb8dc403SDave Cobbley default="none", 37eb8dc403SDave Cobbley help=""" 38eb8dc403SDave CobbleyThe measurement to sort the output by. Defaults to 'none', which means to sort 39eb8dc403SDave Cobbleyby the order paths were given on the command line. For other options, tasks are 40eb8dc403SDave Cobbleysorted in descending order from the highest value. 41eb8dc403SDave Cobbley""") 42eb8dc403SDave Cobbley 43eb8dc403SDave Cobbleyargs = arg_parser.parse_args() 44eb8dc403SDave Cobbley 45eb8dc403SDave Cobbley# Field names and regexes for parsing out their values from buildstat files 46eb8dc403SDave Cobbleyfield_regexes = (("elapsed", ".*Elapsed time: ([0-9.]+)"), 47eb8dc403SDave Cobbley ("user", "rusage ru_utime: ([0-9.]+)"), 48eb8dc403SDave Cobbley ("sys", "rusage ru_stime: ([0-9.]+)"), 49eb8dc403SDave Cobbley ("child user", "Child rusage ru_utime: ([0-9.]+)"), 50eb8dc403SDave Cobbley ("child sys", "Child rusage ru_stime: ([0-9.]+)")) 51eb8dc403SDave Cobbley 52eb8dc403SDave Cobbley# A list of (<path>, <dict>) tuples, where <path> is the path of a do_* task 53eb8dc403SDave Cobbley# buildstat file and <dict> maps fields from the file to their values 54eb8dc403SDave Cobbleytask_infos = [] 55eb8dc403SDave Cobbley 56eb8dc403SDave Cobbleydef save_times_for_task(path): 57eb8dc403SDave Cobbley """Saves information for the buildstat file 'path' in 'task_infos'.""" 58eb8dc403SDave Cobbley 59eb8dc403SDave Cobbley if not os.path.basename(path).startswith("do_"): 60eb8dc403SDave Cobbley return 61eb8dc403SDave Cobbley 62eb8dc403SDave Cobbley with open(path) as f: 63eb8dc403SDave Cobbley fields = {} 64eb8dc403SDave Cobbley 65eb8dc403SDave Cobbley for line in f: 66eb8dc403SDave Cobbley for name, regex in field_regexes: 67eb8dc403SDave Cobbley match = re.match(regex, line) 68eb8dc403SDave Cobbley if match: 69eb8dc403SDave Cobbley fields[name] = float(match.group(1)) 70eb8dc403SDave Cobbley break 71eb8dc403SDave Cobbley 72eb8dc403SDave Cobbley # Check that all expected fields were present 73eb8dc403SDave Cobbley for name, regex in field_regexes: 74eb8dc403SDave Cobbley if name not in fields: 75eb8dc403SDave Cobbley print("Warning: Skipping '{}' because no field matching '{}' could be found" 76eb8dc403SDave Cobbley .format(path, regex), 77eb8dc403SDave Cobbley file=sys.stderr) 78eb8dc403SDave Cobbley return 79eb8dc403SDave Cobbley 80eb8dc403SDave Cobbley task_infos.append((path, fields)) 81eb8dc403SDave Cobbley 82eb8dc403SDave Cobbleydef save_times_for_dir(path): 83eb8dc403SDave Cobbley """Runs save_times_for_task() for each file in path and its subdirs, recursively.""" 84eb8dc403SDave Cobbley 85eb8dc403SDave Cobbley # Raise an exception for os.walk() errors instead of ignoring them 86eb8dc403SDave Cobbley def walk_onerror(e): 87eb8dc403SDave Cobbley raise e 88eb8dc403SDave Cobbley 89eb8dc403SDave Cobbley for root, _, files in os.walk(path, onerror=walk_onerror): 90eb8dc403SDave Cobbley for fname in files: 91eb8dc403SDave Cobbley save_times_for_task(os.path.join(root, fname)) 92eb8dc403SDave Cobbley 93eb8dc403SDave Cobbleyfor path in args.paths: 94eb8dc403SDave Cobbley if os.path.isfile(path): 95eb8dc403SDave Cobbley save_times_for_task(path) 96eb8dc403SDave Cobbley else: 97eb8dc403SDave Cobbley save_times_for_dir(path) 98eb8dc403SDave Cobbley 99eb8dc403SDave Cobbleydef elapsed_time(task_info): 100eb8dc403SDave Cobbley return task_info[1]["elapsed"] 101eb8dc403SDave Cobbley 102eb8dc403SDave Cobbleydef tot_user_time(task_info): 103eb8dc403SDave Cobbley return task_info[1]["user"] + task_info[1]["child user"] 104eb8dc403SDave Cobbley 105eb8dc403SDave Cobbleydef tot_sys_time(task_info): 106eb8dc403SDave Cobbley return task_info[1]["sys"] + task_info[1]["child sys"] 107eb8dc403SDave Cobbley 108eb8dc403SDave Cobbleyif args.sort != "none": 109eb8dc403SDave Cobbley sort_fn = {"real": elapsed_time, "user": tot_user_time, "sys": tot_sys_time} 110eb8dc403SDave Cobbley task_infos.sort(key=sort_fn[args.sort], reverse=True) 111eb8dc403SDave Cobbley 112eb8dc403SDave Cobbleyfirst_entry = True 113eb8dc403SDave Cobbley 114eb8dc403SDave Cobbley# Catching BrokenPipeError avoids annoying errors when the output is piped into 115eb8dc403SDave Cobbley# e.g. 'less' or 'head' and not completely read 116eb8dc403SDave Cobbleytry: 117eb8dc403SDave Cobbley for task_info in task_infos: 118eb8dc403SDave Cobbley real = elapsed_time(task_info) 119eb8dc403SDave Cobbley user = tot_user_time(task_info) 120eb8dc403SDave Cobbley sys = tot_sys_time(task_info) 121eb8dc403SDave Cobbley 122eb8dc403SDave Cobbley if not first_entry: 123eb8dc403SDave Cobbley print() 124eb8dc403SDave Cobbley first_entry = False 125eb8dc403SDave Cobbley 126eb8dc403SDave Cobbley # Mimic Bash's 'time' builtin 127eb8dc403SDave Cobbley print("{}:\n" 128eb8dc403SDave Cobbley "real\t{}m{:.3f}s\n" 129eb8dc403SDave Cobbley "user\t{}m{:.3f}s\n" 130eb8dc403SDave Cobbley "sys\t{}m{:.3f}s" 131eb8dc403SDave Cobbley .format(task_info[0], 132eb8dc403SDave Cobbley int(real//60), real%60, 133eb8dc403SDave Cobbley int(user//60), user%60, 134eb8dc403SDave Cobbley int(sys//60), sys%60)) 135eb8dc403SDave Cobbley 136eb8dc403SDave Cobbleyexcept BrokenPipeError: 137eb8dc403SDave Cobbley pass 138