diff options
Diffstat (limited to 'scripts/buildstats-summary')
-rwxr-xr-x | scripts/buildstats-summary | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/scripts/buildstats-summary b/scripts/buildstats-summary new file mode 100755 index 0000000000..b10c671b29 --- /dev/null +++ b/scripts/buildstats-summary | |||
@@ -0,0 +1,126 @@ | |||
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 | |||
8 | import argparse | ||
9 | import dataclasses | ||
10 | import datetime | ||
11 | import enum | ||
12 | import os | ||
13 | import pathlib | ||
14 | import sys | ||
15 | |||
16 | scripts_path = os.path.dirname(os.path.realpath(__file__)) | ||
17 | sys.path.append(os.path.join(scripts_path, "lib")) | ||
18 | import buildstats | ||
19 | |||
20 | |||
21 | @dataclasses.dataclass | ||
22 | class Task: | ||
23 | recipe: str | ||
24 | task: str | ||
25 | start: datetime.datetime | ||
26 | duration: datetime.timedelta | ||
27 | |||
28 | |||
29 | class Sorting(enum.Enum): | ||
30 | start = 1 | ||
31 | duration = 2 | ||
32 | |||
33 | # argparse integration | ||
34 | def __str__(self) -> str: | ||
35 | return self.name | ||
36 | |||
37 | def __repr__(self) -> str: | ||
38 | return self.name | ||
39 | |||
40 | @staticmethod | ||
41 | def from_string(s: str): | ||
42 | try: | ||
43 | return Sorting[s] | ||
44 | except KeyError: | ||
45 | return s | ||
46 | |||
47 | |||
48 | def read_buildstats(path: pathlib.Path) -> buildstats.BuildStats: | ||
49 | if not path.exists(): | ||
50 | raise Exception(f"No such file or directory: {path}") | ||
51 | if path.is_file(): | ||
52 | return buildstats.BuildStats.from_file_json(path) | ||
53 | if (path / "build_stats").is_file(): | ||
54 | return buildstats.BuildStats.from_dir(path) | ||
55 | raise Exception(f"Cannot find buildstats in {path}") | ||
56 | |||
57 | |||
58 | def dump_buildstats(args, bs: buildstats.BuildStats): | ||
59 | tasks = [] | ||
60 | for recipe in bs.values(): | ||
61 | for task, stats in recipe.tasks.items(): | ||
62 | t = Task( | ||
63 | recipe.name, | ||
64 | task, | ||
65 | datetime.datetime.fromtimestamp(stats["start_time"]), | ||
66 | datetime.timedelta(seconds=int(stats.walltime)), | ||
67 | ) | ||
68 | tasks.append(t) | ||
69 | |||
70 | tasks.sort(key=lambda t: getattr(t, args.sort.name)) | ||
71 | |||
72 | minimum = datetime.timedelta(seconds=args.shortest) | ||
73 | highlight = datetime.timedelta(seconds=args.highlight) | ||
74 | |||
75 | for t in tasks: | ||
76 | if t.duration >= minimum: | ||
77 | line = f"{t.duration} {t.recipe}:{t.task}" | ||
78 | if args.highlight and t.duration >= highlight: | ||
79 | print(f"\033[1m{line}\033[0m") | ||
80 | else: | ||
81 | print(line) | ||
82 | |||
83 | |||
84 | def main(argv=None) -> int: | ||
85 | parser = argparse.ArgumentParser( | ||
86 | formatter_class=argparse.ArgumentDefaultsHelpFormatter | ||
87 | ) | ||
88 | |||
89 | parser.add_argument( | ||
90 | "buildstats", metavar="BUILDSTATS", help="Buildstats file", type=pathlib.Path | ||
91 | ) | ||
92 | parser.add_argument( | ||
93 | "--sort", | ||
94 | "-s", | ||
95 | type=Sorting.from_string, | ||
96 | choices=list(Sorting), | ||
97 | default=Sorting.start, | ||
98 | help="Sort tasks", | ||
99 | ) | ||
100 | parser.add_argument( | ||
101 | "--shortest", | ||
102 | "-t", | ||
103 | type=int, | ||
104 | default=1, | ||
105 | metavar="SECS", | ||
106 | help="Hide tasks shorter than SECS seconds", | ||
107 | ) | ||
108 | parser.add_argument( | ||
109 | "--highlight", | ||
110 | "-g", | ||
111 | type=int, | ||
112 | default=60, | ||
113 | metavar="SECS", | ||
114 | help="Highlight tasks longer than SECS seconds (0 disabled)", | ||
115 | ) | ||
116 | |||
117 | args = parser.parse_args(argv) | ||
118 | |||
119 | bs = read_buildstats(args.buildstats) | ||
120 | dump_buildstats(args, bs) | ||
121 | |||
122 | return 0 | ||
123 | |||
124 | |||
125 | if __name__ == "__main__": | ||
126 | sys.exit(main()) | ||