| #!/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", | |
| nargs="?", | |
| type=pathlib.Path, | |
| help="Buildstats file, or latest if not specified", | |
| ) | |
| 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) | |
| # If a buildstats file wasn't specified, try to find the last one | |
| if not args.buildstats: | |
| try: | |
| builddir = pathlib.Path(os.environ["BUILDDIR"]) | |
| buildstats_dir = builddir / "tmp" / "buildstats" | |
| args.buildstats = sorted(buildstats_dir.iterdir())[-1] | |
| except KeyError: | |
| print("Build environment has not been configured, cannot find buildstats") | |
| return 1 | |
| bs = read_buildstats(args.buildstats) | |
| dump_buildstats(args, bs) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) |