1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
#!/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())
|