buildstats-summary 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. #!/usr/bin/env python3
  2. #
  3. # Dump a summary of the specified buildstats to the terminal, filtering and
  4. # sorting by walltime.
  5. #
  6. # SPDX-License-Identifier: GPL-2.0-only
  7. import argparse
  8. import dataclasses
  9. import datetime
  10. import enum
  11. import os
  12. import pathlib
  13. import sys
  14. scripts_path = os.path.dirname(os.path.realpath(__file__))
  15. sys.path.append(os.path.join(scripts_path, "lib"))
  16. import buildstats
  17. @dataclasses.dataclass
  18. class Task:
  19. recipe: str
  20. task: str
  21. start: datetime.datetime
  22. duration: datetime.timedelta
  23. class Sorting(enum.Enum):
  24. start = 1
  25. duration = 2
  26. # argparse integration
  27. def __str__(self) -> str:
  28. return self.name
  29. def __repr__(self) -> str:
  30. return self.name
  31. @staticmethod
  32. def from_string(s: str):
  33. try:
  34. return Sorting[s]
  35. except KeyError:
  36. return s
  37. def read_buildstats(path: pathlib.Path) -> buildstats.BuildStats:
  38. if not path.exists():
  39. raise Exception(f"No such file or directory: {path}")
  40. if path.is_file():
  41. return buildstats.BuildStats.from_file_json(path)
  42. if (path / "build_stats").is_file():
  43. return buildstats.BuildStats.from_dir(path)
  44. raise Exception(f"Cannot find buildstats in {path}")
  45. def dump_buildstats(args, bs: buildstats.BuildStats):
  46. tasks = []
  47. for recipe in bs.values():
  48. for task, stats in recipe.tasks.items():
  49. t = Task(
  50. recipe.name,
  51. task,
  52. datetime.datetime.fromtimestamp(stats["start_time"]),
  53. datetime.timedelta(seconds=int(stats.walltime)),
  54. )
  55. tasks.append(t)
  56. tasks.sort(key=lambda t: getattr(t, args.sort.name))
  57. minimum = datetime.timedelta(seconds=args.shortest)
  58. highlight = datetime.timedelta(seconds=args.highlight)
  59. for t in tasks:
  60. if t.duration >= minimum:
  61. line = f"{t.duration} {t.recipe}:{t.task}"
  62. if args.highlight and t.duration >= highlight:
  63. print(f"\033[1m{line}\033[0m")
  64. else:
  65. print(line)
  66. def main(argv=None) -> int:
  67. parser = argparse.ArgumentParser(
  68. formatter_class=argparse.ArgumentDefaultsHelpFormatter
  69. )
  70. parser.add_argument(
  71. "buildstats", metavar="BUILDSTATS", help="Buildstats file", type=pathlib.Path
  72. )
  73. parser.add_argument(
  74. "--sort",
  75. "-s",
  76. type=Sorting.from_string,
  77. choices=list(Sorting),
  78. default=Sorting.start,
  79. help="Sort tasks",
  80. )
  81. parser.add_argument(
  82. "--shortest",
  83. "-t",
  84. type=int,
  85. default=1,
  86. metavar="SECS",
  87. help="Hide tasks shorter than SECS seconds",
  88. )
  89. parser.add_argument(
  90. "--highlight",
  91. "-g",
  92. type=int,
  93. default=60,
  94. metavar="SECS",
  95. help="Highlight tasks longer than SECS seconds (0 disabled)",
  96. )
  97. args = parser.parse_args(argv)
  98. bs = read_buildstats(args.buildstats)
  99. dump_buildstats(args, bs)
  100. return 0
  101. if __name__ == "__main__":
  102. sys.exit(main())