diff options
Diffstat (limited to 'scripts/buildstats-summary')
-rwxr-xr-x | scripts/buildstats-summary | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/scripts/buildstats-summary b/scripts/buildstats-summary new file mode 100755 index 0000000000..cc2a27722a --- /dev/null +++ b/scripts/buildstats-summary | |||
@@ -0,0 +1,140 @@ | |||
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", | ||
91 | metavar="BUILDSTATS", | ||
92 | nargs="?", | ||
93 | type=pathlib.Path, | ||
94 | help="Buildstats file, or latest if not specified", | ||
95 | ) | ||
96 | parser.add_argument( | ||
97 | "--sort", | ||
98 | "-s", | ||
99 | type=Sorting.from_string, | ||
100 | choices=list(Sorting), | ||
101 | default=Sorting.start, | ||
102 | help="Sort tasks", | ||
103 | ) | ||
104 | parser.add_argument( | ||
105 | "--shortest", | ||
106 | "-t", | ||
107 | type=int, | ||
108 | default=1, | ||
109 | metavar="SECS", | ||
110 | help="Hide tasks shorter than SECS seconds", | ||
111 | ) | ||
112 | parser.add_argument( | ||
113 | "--highlight", | ||
114 | "-g", | ||
115 | type=int, | ||
116 | default=60, | ||
117 | metavar="SECS", | ||
118 | help="Highlight tasks longer than SECS seconds (0 disabled)", | ||
119 | ) | ||
120 | |||
121 | args = parser.parse_args(argv) | ||
122 | |||
123 | # If a buildstats file wasn't specified, try to find the last one | ||
124 | if not args.buildstats: | ||
125 | try: | ||
126 | builddir = pathlib.Path(os.environ["BUILDDIR"]) | ||
127 | buildstats_dir = builddir / "tmp" / "buildstats" | ||
128 | args.buildstats = sorted(buildstats_dir.iterdir())[-1] | ||
129 | except KeyError: | ||
130 | print("Build environment has not been configured, cannot find buildstats") | ||
131 | return 1 | ||
132 | |||
133 | bs = read_buildstats(args.buildstats) | ||
134 | dump_buildstats(args, bs) | ||
135 | |||
136 | return 0 | ||
137 | |||
138 | |||
139 | if __name__ == "__main__": | ||
140 | sys.exit(main()) | ||