summaryrefslogtreecommitdiffstats
path: root/scripts/buildstats-summary
blob: b10c671b29bd21ff00ddf90caf259ab1a0032e04 (plain)
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
#!/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", help="Buildstats file", type=pathlib.Path
    )
    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)

    bs = read_buildstats(args.buildstats)
    dump_buildstats(args, bs)

    return 0


if __name__ == "__main__":
    sys.exit(main())