|  | #!/usr/bin/env python3 | 
|  | # | 
|  | # Dump a summary of the specified buildstats to the terminal, filtering and | 
|  | # sorting by walltime. | 
|  | # | 
|  | # SPDX-License-Identifier: GPL-2.0-only | 
|  |  | 
|  | import argparse | 
|  | import dataclasses | 
|  | import datetime | 
|  | import enum | 
|  | import os | 
|  | import pathlib | 
|  | import sys | 
|  |  | 
|  | scripts_path = os.path.dirname(os.path.realpath(__file__)) | 
|  | sys.path.append(os.path.join(scripts_path, "lib")) | 
|  | import buildstats | 
|  |  | 
|  |  | 
|  | @dataclasses.dataclass | 
|  | class Task: | 
|  | recipe: str | 
|  | task: str | 
|  | start: datetime.datetime | 
|  | duration: datetime.timedelta | 
|  |  | 
|  |  | 
|  | class Sorting(enum.Enum): | 
|  | start = 1 | 
|  | duration = 2 | 
|  |  | 
|  | # argparse integration | 
|  | def __str__(self) -> str: | 
|  | return self.name | 
|  |  | 
|  | def __repr__(self) -> str: | 
|  | return self.name | 
|  |  | 
|  | @staticmethod | 
|  | def from_string(s: str): | 
|  | try: | 
|  | return Sorting[s] | 
|  | except KeyError: | 
|  | return s | 
|  |  | 
|  |  | 
|  | def read_buildstats(path: pathlib.Path) -> buildstats.BuildStats: | 
|  | if not path.exists(): | 
|  | raise Exception(f"No such file or directory: {path}") | 
|  | if path.is_file(): | 
|  | return buildstats.BuildStats.from_file_json(path) | 
|  | if (path / "build_stats").is_file(): | 
|  | return buildstats.BuildStats.from_dir(path) | 
|  | raise Exception(f"Cannot find buildstats in {path}") | 
|  |  | 
|  |  | 
|  | def dump_buildstats(args, bs: buildstats.BuildStats): | 
|  | tasks = [] | 
|  | for recipe in bs.values(): | 
|  | for task, stats in recipe.tasks.items(): | 
|  | t = Task( | 
|  | recipe.name, | 
|  | task, | 
|  | datetime.datetime.fromtimestamp(stats["start_time"]), | 
|  | datetime.timedelta(seconds=int(stats.walltime)), | 
|  | ) | 
|  | tasks.append(t) | 
|  |  | 
|  | tasks.sort(key=lambda t: getattr(t, args.sort.name)) | 
|  |  | 
|  | minimum = datetime.timedelta(seconds=args.shortest) | 
|  | highlight = datetime.timedelta(seconds=args.highlight) | 
|  |  | 
|  | for t in tasks: | 
|  | if t.duration >= minimum: | 
|  | line = f"{t.duration}    {t.recipe}:{t.task}" | 
|  | if args.highlight and t.duration >= highlight: | 
|  | print(f"\033[1m{line}\033[0m") | 
|  | else: | 
|  | print(line) | 
|  |  | 
|  |  | 
|  | def main(argv=None) -> int: | 
|  | parser = argparse.ArgumentParser( | 
|  | formatter_class=argparse.ArgumentDefaultsHelpFormatter | 
|  | ) | 
|  |  | 
|  | parser.add_argument( | 
|  | "buildstats", metavar="BUILDSTATS", help="Buildstats file", type=pathlib.Path | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--sort", | 
|  | "-s", | 
|  | type=Sorting.from_string, | 
|  | choices=list(Sorting), | 
|  | default=Sorting.start, | 
|  | help="Sort tasks", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--shortest", | 
|  | "-t", | 
|  | type=int, | 
|  | default=1, | 
|  | metavar="SECS", | 
|  | help="Hide tasks shorter than SECS seconds", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--highlight", | 
|  | "-g", | 
|  | type=int, | 
|  | default=60, | 
|  | metavar="SECS", | 
|  | help="Highlight tasks longer than SECS seconds (0 disabled)", | 
|  | ) | 
|  |  | 
|  | args = parser.parse_args(argv) | 
|  |  | 
|  | bs = read_buildstats(args.buildstats) | 
|  | dump_buildstats(args, bs) | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | sys.exit(main()) |