blob: 807e546a175c8828639b216597f0adb555fefb5a [file] [log] [blame]
Joe Onorato88ede352023-12-19 02:56:38 +00001#!/usr/bin/env python3
2# Copyright (C) 2023 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17if __name__ == "__main__":
18 sys.dont_write_bytecode = True
19
20import argparse
21import dataclasses
22import datetime
23import json
24import os
25import pathlib
26import statistics
27import zoneinfo
Joe Onoratob9ff8e42024-05-20 14:21:40 -070028import csv
Joe Onorato88ede352023-12-19 02:56:38 +000029
30import pretty
31import utils
32
33# TODO:
34# - Flag if the last postroll build was more than 15 seconds or something. That's
35# an indicator that something is amiss.
36# - Add a mode to print all of the values for multi-iteration runs
37# - Add a flag to reorder the tags
38# - Add a flag to reorder the headers in order to show grouping more clearly.
39
40
41def FindSummaries(args):
42 def find_summaries(directory):
43 return [str(p.resolve()) for p in pathlib.Path(directory).glob("**/summary.json")]
44 if not args:
45 # If they didn't give an argument, use the default dir
46 root = utils.get_root()
47 if not root:
48 return []
49 return find_summaries(root.joinpath("..", utils.DEFAULT_REPORT_DIR))
50 results = list()
51 for arg in args:
52 if os.path.isfile(arg):
53 # If it's a file add that
54 results.append(arg)
55 elif os.path.isdir(arg):
56 # If it's a directory, find all of the files there
57 results += find_summaries(arg)
58 else:
59 sys.stderr.write(f"Invalid summary argument: {arg}\n")
60 sys.exit(1)
61 return sorted(list(results))
62
63
64def LoadSummary(filename):
65 with open(filename) as f:
66 return json.load(f)
67
68# Columns:
69# Date
70# Branch
71# Tag
72# --
73# Lunch
74# Rows:
75# Benchmark
76
Joe Onorato88ede352023-12-19 02:56:38 +000077def lunch_str(d):
78 "Convert a lunch dict to a string"
79 return f"{d['TARGET_PRODUCT']}-{d['TARGET_RELEASE']}-{d['TARGET_BUILD_VARIANT']}"
80
81def group_by(l, key):
82 "Return a list of tuples, grouped by key, sorted by key"
83 result = {}
84 for item in l:
85 result.setdefault(key(item), []).append(item)
86 return [(k, v) for k, v in result.items()]
87
88
89class Table:
Joe Onorato6b408262024-01-19 16:35:02 +000090 def __init__(self, row_title, fixed_titles=[]):
Joe Onorato88ede352023-12-19 02:56:38 +000091 self._data = {}
92 self._rows = []
93 self._cols = []
Joe Onorato6b408262024-01-19 16:35:02 +000094 self._fixed_cols = {}
95 self._titles = [row_title] + fixed_titles
Joe Onorato88ede352023-12-19 02:56:38 +000096
97 def Set(self, column_key, row_key, data):
98 self._data[(column_key, row_key)] = data
99 if not column_key in self._cols:
100 self._cols.append(column_key)
101 if not row_key in self._rows:
102 self._rows.append(row_key)
103
Joe Onorato6b408262024-01-19 16:35:02 +0000104 def SetFixedCol(self, row_key, columns):
105 self._fixed_cols[row_key] = columns
106
Joe Onoratob9ff8e42024-05-20 14:21:40 -0700107 def Write(self, out, fmt):
Joe Onorato88ede352023-12-19 02:56:38 +0000108 table = []
109 # Expand the column items
110 for row in zip(*self._cols):
111 if row.count(row[0]) == len(row):
112 continue
Joe Onorato6b408262024-01-19 16:35:02 +0000113 table.append([""] * len(self._titles) + [col for col in row])
Joe Onorato88ede352023-12-19 02:56:38 +0000114 if table:
Joe Onorato6b408262024-01-19 16:35:02 +0000115 # Update the last row of the header with title and add separator
116 for i in range(len(self._titles)):
117 table[len(table)-1][i] = self._titles[i]
Joe Onoratob9ff8e42024-05-20 14:21:40 -0700118 if fmt == "table":
119 table.append(pretty.SEPARATOR)
Joe Onorato88ede352023-12-19 02:56:38 +0000120 # Populate the data
121 for row in self._rows:
Joe Onorato6b408262024-01-19 16:35:02 +0000122 table.append([str(row)]
123 + self._fixed_cols[row]
124 + [str(self._data.get((col, row), "")) for col in self._cols])
Joe Onoratob9ff8e42024-05-20 14:21:40 -0700125 if fmt == "csv":
126 csv.writer(sys.stdout, quoting=csv.QUOTE_MINIMAL).writerows(table)
127 else:
128 out.write(pretty.FormatTable(table, alignments="LL"))
Joe Onorato88ede352023-12-19 02:56:38 +0000129
130
Joe Onoratob9ff8e42024-05-20 14:21:40 -0700131def format_duration_sec(ns, fmt_sec):
Joe Onorato88ede352023-12-19 02:56:38 +0000132 "Format a duration in ns to second precision"
133 sec = round(ns / 1000000000)
Joe Onoratob9ff8e42024-05-20 14:21:40 -0700134 if fmt_sec:
135 return f"{sec}"
136 else:
137 h, sec = divmod(sec, 60*60)
138 m, sec = divmod(sec, 60)
139 result = ""
140 if h > 0:
141 result += f"{h:2d}h "
142 if h > 0 or m > 0:
143 result += f"{m:2d}m "
144 return result + f"{sec:2d}s"
Joe Onorato88ede352023-12-19 02:56:38 +0000145
Joe Onorato67c944b2023-12-21 13:29:18 -0800146
Joe Onorato88ede352023-12-19 02:56:38 +0000147def main(argv):
148 parser = argparse.ArgumentParser(
149 prog="format_benchmarks",
150 allow_abbrev=False, # Don't let people write unsupportable scripts.
151 description="Print analysis tables for benchmarks")
152
Joe Onoratob9ff8e42024-05-20 14:21:40 -0700153 parser.add_argument("--csv", action="store_true",
154 help="Print in CSV instead of table.")
155
156 parser.add_argument("--sec", action="store_true",
157 help="Print in seconds instead of minutes and seconds")
158
Joe Onorato67c944b2023-12-21 13:29:18 -0800159 parser.add_argument("--tags", nargs="*",
160 help="The tags to print, in order.")
161
Joe Onorato88ede352023-12-19 02:56:38 +0000162 parser.add_argument("summaries", nargs="*",
163 help="A summary.json file or a directory in which to look for summaries.")
164
165 args = parser.parse_args()
166
167 # Load the summaries
168 summaries = [(s, LoadSummary(s)) for s in FindSummaries(args.summaries)]
169
170 # Convert to MTV time
171 for filename, s in summaries:
172 dt = datetime.datetime.fromisoformat(s["start_time"])
173 dt = dt.astimezone(zoneinfo.ZoneInfo("America/Los_Angeles"))
174 s["datetime"] = dt
175 s["date"] = datetime.date(dt.year, dt.month, dt.day)
176
Joe Onorato67c944b2023-12-21 13:29:18 -0800177 # Filter out tags we don't want
178 if args.tags:
179 summaries = [(f, s) for f, s in summaries if s.get("tag", "") in args.tags]
180
181 # If they supplied tags, sort in that order, otherwise sort by tag
182 if args.tags:
183 tagsort = lambda tag: args.tags.index(tag)
184 else:
185 tagsort = lambda tag: tag
186
Joe Onorato88ede352023-12-19 02:56:38 +0000187 # Sort the summaries
Joe Onorato67c944b2023-12-21 13:29:18 -0800188 summaries.sort(key=lambda s: (s[1]["date"], s[1]["branch"], tagsort(s[1]["tag"])))
Joe Onorato88ede352023-12-19 02:56:38 +0000189
190 # group the benchmarks by column and iteration
191 def bm_key(b):
192 return (
193 lunch_str(b["lunch"]),
194 )
195 for filename, summary in summaries:
196 summary["columns"] = [(key, group_by(bms, lambda b: b["id"])) for key, bms
197 in group_by(summary["benchmarks"], bm_key)]
198
199 # Build the table
Joe Onorato6b408262024-01-19 16:35:02 +0000200 table = Table("Benchmark", ["Rebuild"])
Joe Onorato88ede352023-12-19 02:56:38 +0000201 for filename, summary in summaries:
202 for key, column in summary["columns"]:
203 for id, cell in column:
204 duration_ns = statistics.median([b["duration_ns"] for b in cell])
Joe Onorato159cc8c2024-05-16 12:54:27 -0700205 modules = cell[0]["modules"]
206 if not modules:
207 modules = ["---"]
208 table.SetFixedCol(cell[0]["title"], [" ".join(modules)])
Joe Onorato519c9da2024-01-02 16:46:26 -0800209 table.Set(tuple([summary["date"].strftime("%Y-%m-%d"),
Joe Onorato88ede352023-12-19 02:56:38 +0000210 summary["branch"],
211 summary["tag"]]
212 + list(key)),
Joe Onoratob9ff8e42024-05-20 14:21:40 -0700213 cell[0]["title"], format_duration_sec(duration_ns, args.sec))
Joe Onorato88ede352023-12-19 02:56:38 +0000214
Joe Onoratob9ff8e42024-05-20 14:21:40 -0700215 table.Write(sys.stdout, "csv" if args.csv else "table")
Joe Onorato88ede352023-12-19 02:56:38 +0000216
217if __name__ == "__main__":
218 main(sys.argv)
219