123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- #!/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())
|