xref: /openbmc/openbmc/poky/scripts/task-time (revision 92b42cb3)
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