blob: 228c1d0c944c6eababad4f93c5e9838ffc88c3e0 [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
Joe Onorato01277d42023-12-20 04:00:12 +000026import random
27import re
Joe Onorato88ede352023-12-19 02:56:38 +000028import shutil
29import subprocess
30import time
Joe Onorato01277d42023-12-20 04:00:12 +000031import uuid
Liz Kammerefb66502024-01-25 11:08:10 -050032from typing import Optional
Joe Onorato88ede352023-12-19 02:56:38 +000033
34import pretty
35import utils
36
37
38class FatalError(Exception):
39 def __init__(self):
40 pass
41
42
43class OptionsError(Exception):
44 def __init__(self, message):
45 self.message = message
46
47
48@dataclasses.dataclass(frozen=True)
49class Lunch:
50 "Lunch combination"
51
52 target_product: str
53 "TARGET_PRODUCT"
54
55 target_release: str
56 "TARGET_RELEASE"
57
58 target_build_variant: str
59 "TARGET_BUILD_VARIANT"
60
61 def ToDict(self):
62 return {
63 "TARGET_PRODUCT": self.target_product,
64 "TARGET_RELEASE": self.target_release,
65 "TARGET_BUILD_VARIANT": self.target_build_variant,
66 }
67
68 def Combine(self):
69 return f"{self.target_product}-{self.target_release}-{self.target_build_variant}"
70
71
72@dataclasses.dataclass(frozen=True)
73class Change:
74 "A change that we make to the tree, and how to undo it"
75 label: str
76 "String to print in the log when the change is made"
77
78 change: callable
79 "Function to change the source tree"
80
81 undo: callable
82 "Function to revert the source tree to its previous condition in the most minimal way possible."
83
Liz Kammerefb66502024-01-25 11:08:10 -050084_DUMPVARS_VARS=[
85 "COMMON_LUNCH_CHOICES",
86 "HOST_PREBUILT_TAG",
87 "print",
88 "PRODUCT_OUT",
89 "report_config",
90 "TARGET_ARCH",
91 "TARGET_BUILD_VARIANT",
92 "TARGET_DEVICE",
93 "TARGET_PRODUCT",
94]
95
96_DUMPVARS_ABS_VARS =[
97 "ANDROID_CLANG_PREBUILTS",
98 "ANDROID_JAVA_HOME",
99 "ANDROID_JAVA_TOOLCHAIN",
100 "ANDROID_PREBUILTS",
101 "HOST_OUT",
102 "HOST_OUT_EXECUTABLES",
103 "HOST_OUT_TESTCASES",
104 "OUT_DIR",
105 "print",
106 "PRODUCT_OUT",
107 "SOONG_HOST_OUT",
108 "SOONG_HOST_OUT_EXECUTABLES",
109 "TARGET_OUT_TESTCASES",
110]
Joe Onorato88ede352023-12-19 02:56:38 +0000111
112@dataclasses.dataclass(frozen=True)
113class Benchmark:
114 "Something we measure"
115
116 id: str
117 "Short ID for the benchmark, for the command line"
118
119 title: str
120 "Title for reports"
121
122 change: Change
123 "Source tree modification for the benchmark that will be measured"
124
Liz Kammerefb66502024-01-25 11:08:10 -0500125 dumpvars: Optional[bool] = False
126 "If specified, soong will run in dumpvars mode rather than build-mode."
127
128 modules: Optional[list[str]] = None
Joe Onorato88ede352023-12-19 02:56:38 +0000129 "Build modules to build on soong command line"
130
Liz Kammerefb66502024-01-25 11:08:10 -0500131 preroll: Optional[int] = 0
Joe Onorato88ede352023-12-19 02:56:38 +0000132 "Number of times to run the build command to stabilize"
133
Liz Kammerefb66502024-01-25 11:08:10 -0500134 postroll: Optional[int] = 3
Joe Onorato88ede352023-12-19 02:56:38 +0000135 "Number of times to run the build command after reverting the action to stabilize"
136
Liz Kammerefb66502024-01-25 11:08:10 -0500137 def build_description(self):
138 "Short description of the benchmark's Soong invocation."
139 if self.dumpvars:
140 return "dumpvars"
141 elif self.modules:
142 return " ".join(self.modules)
143 return ""
144
145
146 def soong_command(self, root):
147 "Command line args to soong_ui for this benchmark."
148 if self.dumpvars:
149 return [
150 "--dumpvars-mode",
151 f"--vars=\"{' '.join(_DUMPVARS_VARS)}\"",
152 f"--abs-vars=\"{' '.join(_DUMPVARS_ABS_VARS)}\"",
153 "--var-prefix=var_cache_",
154 "--abs-var-prefix=abs_var_cache_",
155 ]
156 elif self.modules:
157 return [
158 "--build-mode",
159 "--all-modules",
160 f"--dir={root}",
161 "--skip-metrics-upload",
162 ] + self.modules
163 else:
164 raise Exception("Benchmark must specify dumpvars or modules")
165
Joe Onorato88ede352023-12-19 02:56:38 +0000166
167@dataclasses.dataclass(frozen=True)
168class FileSnapshot:
169 "Snapshot of a file's contents."
170
171 filename: str
172 "The file that was snapshottened"
173
174 contents: str
175 "The contents of the file"
176
177 def write(self):
178 "Write the contents back to the file"
179 with open(self.filename, "w") as f:
180 f.write(self.contents)
181
182
183def Snapshot(filename):
184 """Return a FileSnapshot with the file's current contents."""
185 with open(filename) as f:
186 contents = f.read()
187 return FileSnapshot(filename, contents)
188
189
190def Clean():
191 """Remove the out directory."""
192 def remove_out():
Yu Liucda84242024-01-17 21:30:53 +0000193 out_dir = utils.get_out_dir()
Liz Kammer864dd432024-01-24 14:09:11 -0500194 #only remove actual contents, in case out is a symlink (as is the case for cog)
Yu Liucda84242024-01-17 21:30:53 +0000195 if os.path.exists(out_dir):
Liz Kammer864dd432024-01-24 14:09:11 -0500196 for filename in os.listdir(out_dir):
197 p = os.path.join(out_dir, filename)
198 if os.path.isfile(p) or os.path.islink(p):
199 os.remove(p)
200 elif os.path.isdir(p):
201 shutil.rmtree(p)
Joe Onorato88ede352023-12-19 02:56:38 +0000202 return Change(label="Remove out", change=remove_out, undo=lambda: None)
203
204
205def NoChange():
206 """No change to the source tree."""
207 return Change(label="No change", change=lambda: None, undo=lambda: None)
208
209
Joe Onorato01277d42023-12-20 04:00:12 +0000210def Create(filename):
211 "Create an action to create `filename`. The parent directory must exist."
212 def create():
213 with open(filename, "w") as f:
214 pass
215 def delete():
216 os.remove(filename)
217 return Change(
218 label=f"Create {filename}",
219 change=create,
220 undo=delete,
221 )
222
223
Joe Onorato88ede352023-12-19 02:56:38 +0000224def Modify(filename, contents, before=None):
Joe Onorato01277d42023-12-20 04:00:12 +0000225 """Create an action to modify `filename` by appending the result of `contents`
226 before the last instances of `before` in the file.
Joe Onorato88ede352023-12-19 02:56:38 +0000227
228 Raises an error if `before` doesn't appear in the file.
229 """
230 orig = Snapshot(filename)
231 if before:
232 index = orig.contents.rfind(before)
233 if index < 0:
234 report_error(f"{filename}: Unable to find string '{before}' for modify operation.")
235 raise FatalError()
236 else:
237 index = len(orig.contents)
Joe Onorato01277d42023-12-20 04:00:12 +0000238 modified = FileSnapshot(filename, orig.contents[:index] + contents() + orig.contents[index:])
239 if False:
240 print(f"Modify: {filename}")
241 x = orig.contents.replace("\n", "\n ORIG")
242 print(f" ORIG {x}")
243 x = modified.contents.replace("\n", "\n MODIFIED")
244 print(f" MODIFIED {x}")
245
Joe Onorato88ede352023-12-19 02:56:38 +0000246 return Change(
247 label="Modify " + filename,
248 change=lambda: modified.write(),
249 undo=lambda: orig.write()
250 )
251
Yu Liu473bb052024-02-13 23:46:06 +0000252def ChangePublicApi():
253 change = AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
254 "@android.annotation.SuppressLint(\"UnflaggedApi\") public")
255 orig_current_text = Snapshot("frameworks/base/core/api/current.txt")
256
257 def undo():
258 change.undo()
259 orig_current_text.write()
260
261 return Change(
262 label=change.label,
263 change=change.change,
264 undo=lambda: undo()
265 )
266
Joe Onorato01277d42023-12-20 04:00:12 +0000267def AddJavaField(filename, prefix):
268 return Modify(filename,
269 lambda: f"{prefix} static final int BENCHMARK = {random.randint(0, 1000000)};\n",
270 before="}")
271
272
273def Comment(prefix, suffix=""):
274 return lambda: prefix + " " + str(uuid.uuid4()) + suffix
275
Joe Onorato88ede352023-12-19 02:56:38 +0000276
277class BenchmarkReport():
278 "Information about a run of the benchmark"
279
280 lunch: Lunch
281 "lunch combo"
282
283 benchmark: Benchmark
284 "The benchmark object."
285
286 iteration: int
287 "Which iteration of the benchmark"
288
289 log_dir: str
290 "Path the the log directory, relative to the root of the reports directory"
291
292 preroll_duration_ns: [int]
293 "Durations of the in nanoseconds."
294
295 duration_ns: int
296 "Duration of the measured portion of the benchmark in nanoseconds."
297
298 postroll_duration_ns: [int]
299 "Durations of the postrolls in nanoseconds."
300
301 complete: bool
302 "Whether the benchmark made it all the way through the postrolls."
303
304 def __init__(self, lunch, benchmark, iteration, log_dir):
305 self.lunch = lunch
306 self.benchmark = benchmark
307 self.iteration = iteration
308 self.log_dir = log_dir
309 self.preroll_duration_ns = []
310 self.duration_ns = -1
311 self.postroll_duration_ns = []
312 self.complete = False
313
314 def ToDict(self):
315 return {
316 "lunch": self.lunch.ToDict(),
317 "id": self.benchmark.id,
318 "title": self.benchmark.title,
319 "modules": self.benchmark.modules,
Liz Kammerefb66502024-01-25 11:08:10 -0500320 "dumpvars": self.benchmark.dumpvars,
Joe Onorato88ede352023-12-19 02:56:38 +0000321 "change": self.benchmark.change.label,
322 "iteration": self.iteration,
323 "log_dir": self.log_dir,
324 "preroll_duration_ns": self.preroll_duration_ns,
325 "duration_ns": self.duration_ns,
326 "postroll_duration_ns": self.postroll_duration_ns,
327 "complete": self.complete,
328 }
329
330class Runner():
331 """Runs the benchmarks."""
332
333 def __init__(self, options):
334 self._options = options
335 self._reports = []
336 self._complete = False
337
338 def Run(self):
339 """Run all of the user-selected benchmarks."""
LaMont Jones42abe8e2025-02-13 14:43:33 -0800340
341 # With `--list`, just list the benchmarks available.
342 if self._options.List():
343 print(" ".join(self._options.BenchmarkIds()))
344 return
345
Joe Onorato88ede352023-12-19 02:56:38 +0000346 # Clean out the log dir or create it if necessary
347 prepare_log_dir(self._options.LogDir())
348
349 try:
350 for lunch in self._options.Lunches():
351 print(lunch)
352 for benchmark in self._options.Benchmarks():
353 for iteration in range(self._options.Iterations()):
354 self._run_benchmark(lunch, benchmark, iteration)
355 self._complete = True
356 finally:
357 self._write_summary()
358
359
360 def _run_benchmark(self, lunch, benchmark, iteration):
361 """Run a single benchmark."""
Yu Liucda84242024-01-17 21:30:53 +0000362 benchmark_log_subdir = self._benchmark_log_dir(lunch, benchmark, iteration)
Joe Onorato88ede352023-12-19 02:56:38 +0000363 benchmark_log_dir = self._options.LogDir().joinpath(benchmark_log_subdir)
364
365 sys.stderr.write(f"STARTING BENCHMARK: {benchmark.id}\n")
366 sys.stderr.write(f" lunch: {lunch.Combine()}\n")
367 sys.stderr.write(f" iteration: {iteration}\n")
368 sys.stderr.write(f" benchmark_log_dir: {benchmark_log_dir}\n")
369
370 report = BenchmarkReport(lunch, benchmark, iteration, benchmark_log_subdir)
371 self._reports.append(report)
372
373 # Preroll builds
374 for i in range(benchmark.preroll):
Liz Kammerefb66502024-01-25 11:08:10 -0500375 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"pre_{i}"), benchmark)
Joe Onorato88ede352023-12-19 02:56:38 +0000376 report.preroll_duration_ns.append(ns)
377
378 sys.stderr.write(f"PERFORMING CHANGE: {benchmark.change.label}\n")
379 if not self._options.DryRun():
380 benchmark.change.change()
381 try:
382
383 # Measured build
Liz Kammerefb66502024-01-25 11:08:10 -0500384 ns = self._run_build(lunch, benchmark_log_dir.joinpath("measured"), benchmark)
Joe Onorato88ede352023-12-19 02:56:38 +0000385 report.duration_ns = ns
386
Joe Onorato05434642023-12-20 05:00:04 +0000387 dist_one = self._options.DistOne()
388 if dist_one:
389 # If we're disting just one benchmark, save the logs and we can stop here.
Liz Kammerefb66502024-01-25 11:08:10 -0500390 self._dist(utils.get_dist_dir(), benchmark.dumpvars)
Joe Onorato05434642023-12-20 05:00:04 +0000391 else:
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500392 self._dist(benchmark_log_dir, benchmark.dumpvars, store_metrics_only=True)
Joe Onorato05434642023-12-20 05:00:04 +0000393 # Postroll builds
Liz Kammerf67a6e82024-01-25 11:06:40 -0500394 for i in range(benchmark.postroll):
Joe Onorato05434642023-12-20 05:00:04 +0000395 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"post_{i}"),
Liz Kammerefb66502024-01-25 11:08:10 -0500396 benchmark)
Joe Onorato05434642023-12-20 05:00:04 +0000397 report.postroll_duration_ns.append(ns)
Joe Onorato88ede352023-12-19 02:56:38 +0000398
399 finally:
400 # Always undo, even if we crashed or the build failed and we stopped.
401 sys.stderr.write(f"UNDOING CHANGE: {benchmark.change.label}\n")
402 if not self._options.DryRun():
403 benchmark.change.undo()
404
405 self._write_summary()
406 sys.stderr.write(f"FINISHED BENCHMARK: {benchmark.id}\n")
407
Yu Liucda84242024-01-17 21:30:53 +0000408 def _benchmark_log_dir(self, lunch, benchmark, iteration):
Joe Onorato88ede352023-12-19 02:56:38 +0000409 """Construct the log directory fir a benchmark run."""
410 path = f"{lunch.Combine()}/{benchmark.id}"
411 # Zero pad to the correct length for correct alpha sorting
412 path += ("/%0" + str(len(str(self._options.Iterations()))) + "d") % iteration
413 return path
414
Liz Kammerefb66502024-01-25 11:08:10 -0500415 def _run_build(self, lunch, build_log_dir, benchmark):
Joe Onorato88ede352023-12-19 02:56:38 +0000416 """Builds the modules. Saves interesting log files to log_dir. Raises FatalError
417 if the build fails.
418 """
Liz Kammerefb66502024-01-25 11:08:10 -0500419 sys.stderr.write(f"STARTING BUILD {benchmark.build_description()}\n")
Joe Onorato88ede352023-12-19 02:56:38 +0000420
421 before_ns = time.perf_counter_ns()
422 if not self._options.DryRun():
423 cmd = [
424 "build/soong/soong_ui.bash",
Liz Kammerefb66502024-01-25 11:08:10 -0500425 ] + benchmark.soong_command(self._options.root)
Joe Onorato88ede352023-12-19 02:56:38 +0000426 env = dict(os.environ)
427 env["TARGET_PRODUCT"] = lunch.target_product
428 env["TARGET_RELEASE"] = lunch.target_release
429 env["TARGET_BUILD_VARIANT"] = lunch.target_build_variant
430 returncode = subprocess.call(cmd, env=env)
431 if returncode != 0:
432 report_error(f"Build failed: {' '.join(cmd)}")
433 raise FatalError()
434
435 after_ns = time.perf_counter_ns()
436
437 # TODO: Copy some log files.
438
Liz Kammerefb66502024-01-25 11:08:10 -0500439 sys.stderr.write(f"FINISHED BUILD {benchmark.build_description()}\n")
Joe Onorato88ede352023-12-19 02:56:38 +0000440
441 return after_ns - before_ns
442
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500443 def _dist(self, dist_dir, dumpvars, store_metrics_only=False):
Yu Liucda84242024-01-17 21:30:53 +0000444 out_dir = utils.get_out_dir()
445 dest_dir = dist_dir.joinpath("logs")
Joe Onorato05434642023-12-20 05:00:04 +0000446 os.makedirs(dest_dir, exist_ok=True)
447 basenames = [
Joe Onorato05434642023-12-20 05:00:04 +0000448 "soong_build_metrics.pb",
449 "soong_metrics",
450 ]
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500451 if not store_metrics_only:
452 basenames.extend([
453 "build.trace.gz",
454 "soong.log",
455 ])
Liz Kammerefb66502024-01-25 11:08:10 -0500456 if dumpvars:
457 basenames = ['dumpvars-'+b for b in basenames]
Joe Onorato05434642023-12-20 05:00:04 +0000458 for base in basenames:
459 src = out_dir.joinpath(base)
460 if src.exists():
461 sys.stderr.write(f"DIST: copied {src} to {dest_dir}\n")
462 shutil.copy(src, dest_dir)
463
Joe Onorato88ede352023-12-19 02:56:38 +0000464 def _write_summary(self):
465 # Write the results, even if the build failed or we crashed, including
466 # whether we finished all of the benchmarks.
467 data = {
468 "start_time": self._options.Timestamp().isoformat(),
469 "branch": self._options.Branch(),
470 "tag": self._options.Tag(),
471 "benchmarks": [report.ToDict() for report in self._reports],
472 "complete": self._complete,
473 }
474 with open(self._options.LogDir().joinpath("summary.json"), "w", encoding="utf-8") as f:
475 json.dump(data, f, indent=2, sort_keys=True)
476
477
478def benchmark_table(benchmarks):
479 rows = [("ID", "DESCRIPTION", "REBUILD"),]
Liz Kammerefb66502024-01-25 11:08:10 -0500480 rows += [(benchmark.id, benchmark.title, benchmark.build_description()) for benchmark in
Joe Onorato88ede352023-12-19 02:56:38 +0000481 benchmarks]
482 return rows
483
484
485def prepare_log_dir(directory):
486 if os.path.exists(directory):
487 # If it exists and isn't a directory, fail.
488 if not os.path.isdir(directory):
489 report_error(f"Log directory already exists but isn't a directory: {directory}")
490 raise FatalError()
491 # Make sure the directory is empty. Do this rather than deleting it to handle
492 # symlinks cleanly.
493 for filename in os.listdir(directory):
494 entry = os.path.join(directory, filename)
495 if os.path.isdir(entry):
496 shutil.rmtree(entry)
497 else:
498 os.unlink(entry)
499 else:
500 # Create it
501 os.makedirs(directory)
502
503
504class Options():
505 def __init__(self):
506 self._had_error = False
507
508 # Wall time clock when we started
509 self._timestamp = datetime.datetime.now(datetime.timezone.utc)
510
511 # Move to the root of the tree right away. Everything must happen from there.
512 self.root = utils.get_root()
513 if not self.root:
514 report_error("Unable to find root of tree from cwd.")
515 raise FatalError()
516 os.chdir(self.root)
517
518 # Initialize the Benchmarks. Note that this pre-loads all of the files, etc.
519 # Doing all that here forces us to fail fast if one of them can't load a required
520 # file, at the cost of a small startup speed. Don't make this do something slow
521 # like scan the whole tree.
522 self._init_benchmarks()
523
524 # Argument parsing
525 epilog = f"""
526benchmarks:
527{pretty.FormatTable(benchmark_table(self._benchmarks), prefix=" ")}
528"""
529
530 parser = argparse.ArgumentParser(
531 prog="benchmarks",
532 allow_abbrev=False, # Don't let people write unsupportable scripts.
533 formatter_class=argparse.RawDescriptionHelpFormatter,
534 epilog=epilog,
535 description="Run build system performance benchmarks.")
536 self.parser = parser
537
538 parser.add_argument("--log-dir",
539 help="Directory for logs. Default is $TOP/../benchmarks/.")
540 parser.add_argument("--dated-logs", action="store_true",
541 help="Append timestamp to log dir.")
542 parser.add_argument("-n", action="store_true", dest="dry_run",
543 help="Dry run. Don't run the build commands but do everything else.")
544 parser.add_argument("--tag",
545 help="Variant of the run, for when there are multiple perf runs.")
546 parser.add_argument("--lunch", nargs="*",
547 help="Lunch combos to test")
548 parser.add_argument("--iterations", type=int, default=1,
549 help="Number of iterations of each test to run.")
550 parser.add_argument("--branch", type=str,
551 help="Specify branch. Otherwise a guess will be made based on repo.")
552 parser.add_argument("--benchmark", nargs="*", default=[b.id for b in self._benchmarks],
553 metavar="BENCHMARKS",
554 help="Benchmarks to run. Default suite will be run if omitted.")
LaMont Jones42abe8e2025-02-13 14:43:33 -0800555 parser.add_argument("--list", action="store_true",
556 help="list the available benchmarks. No benchmark is run.")
Joe Onorato60c36ad2024-01-02 07:32:54 +0000557 parser.add_argument("--dist-one", action="store_true",
Joe Onorato05434642023-12-20 05:00:04 +0000558 help="Copy logs and metrics to the given dist dir. Requires that only"
559 + " one benchmark be supplied. Postroll steps will be skipped.")
Joe Onorato88ede352023-12-19 02:56:38 +0000560
561 self._args = parser.parse_args()
562
563 self._branch = self._branch()
564 self._log_dir = self._log_dir()
565 self._lunches = self._lunches()
566
567 # Validate the benchmark ids
568 all_ids = [benchmark.id for benchmark in self._benchmarks]
569 bad_ids = [id for id in self._args.benchmark if id not in all_ids]
570 if bad_ids:
571 for id in bad_ids:
572 self._error(f"Invalid benchmark: {id}")
573
Joe Onorato05434642023-12-20 05:00:04 +0000574 # --dist-one requires that only one benchmark be supplied
Joe Onorato60c36ad2024-01-02 07:32:54 +0000575 if self._args.dist_one and len(self.Benchmarks()) != 1:
Joe Onorato05434642023-12-20 05:00:04 +0000576 self._error("--dist-one requires that exactly one --benchmark.")
577
Joe Onorato88ede352023-12-19 02:56:38 +0000578 if self._had_error:
579 raise FatalError()
580
581 def Timestamp(self):
582 return self._timestamp
583
584 def _branch(self):
585 """Return the branch, either from the command line or by guessing from repo."""
586 if self._args.branch:
587 return self._args.branch
588 try:
589 branch = subprocess.check_output(f"cd {self.root}/.repo/manifests"
590 + " && git rev-parse --abbrev-ref --symbolic-full-name @{u}",
591 shell=True, encoding="utf-8")
592 return branch.strip().split("/")[-1]
593 except subprocess.CalledProcessError as ex:
594 report_error("Can't get branch from .repo dir. Specify --branch argument")
595 report_error(str(ex))
596 raise FatalError()
597
598 def Branch(self):
599 return self._branch
600
601 def _log_dir(self):
602 "The log directory to use, based on the current options"
603 if self._args.log_dir:
604 d = pathlib.Path(self._args.log_dir).resolve().absolute()
605 else:
606 d = self.root.joinpath("..", utils.DEFAULT_REPORT_DIR)
607 if self._args.dated_logs:
608 d = d.joinpath(self._timestamp.strftime('%Y-%m-%d'))
609 d = d.joinpath(self._branch)
610 if self._args.tag:
611 d = d.joinpath(self._args.tag)
612 return d.resolve().absolute()
613
614 def LogDir(self):
615 return self._log_dir
616
617 def Benchmarks(self):
618 return [b for b in self._benchmarks if b.id in self._args.benchmark]
619
620 def Tag(self):
621 return self._args.tag
622
623 def DryRun(self):
624 return self._args.dry_run
625
LaMont Jones42abe8e2025-02-13 14:43:33 -0800626 def List(self):
627 return self._args.list
628
629 def BenchmarkIds(self) :
630 return [benchmark.id for benchmark in self._benchmarks]
631
Joe Onorato88ede352023-12-19 02:56:38 +0000632 def _lunches(self):
633 def parse_lunch(lunch):
634 parts = lunch.split("-")
635 if len(parts) != 3:
636 raise OptionsError(f"Invalid lunch combo: {lunch}")
637 return Lunch(parts[0], parts[1], parts[2])
638 # If they gave lunch targets on the command line use that
639 if self._args.lunch:
640 result = []
641 # Split into Lunch objects
642 for lunch in self._args.lunch:
643 try:
644 result.append(parse_lunch(lunch))
645 except OptionsError as ex:
646 self._error(ex.message)
647 return result
648 # Use whats in the environment
649 product = os.getenv("TARGET_PRODUCT")
650 release = os.getenv("TARGET_RELEASE")
651 variant = os.getenv("TARGET_BUILD_VARIANT")
652 if (not product) or (not release) or (not variant):
653 # If they didn't give us anything, fail rather than guessing. There's no good
654 # default for AOSP.
655 self._error("No lunch combo specified. Either pass --lunch argument or run lunch.")
656 return []
657 return [Lunch(product, release, variant),]
658
659 def Lunches(self):
660 return self._lunches
661
662 def Iterations(self):
663 return self._args.iterations
664
Joe Onorato05434642023-12-20 05:00:04 +0000665 def DistOne(self):
666 return self._args.dist_one
667
Joe Onorato88ede352023-12-19 02:56:38 +0000668 def _init_benchmarks(self):
669 """Initialize the list of benchmarks."""
670 # Assumes that we've already chdired to the root of the tree.
671 self._benchmarks = [
Liz Kammerefb66502024-01-25 11:08:10 -0500672 Benchmark(
673 id="full_lunch",
674 title="Lunch from clean out",
675 change=Clean(),
676 dumpvars=True,
677 preroll=0,
678 postroll=0,
679 ),
680 Benchmark(
681 id="noop_lunch",
682 title="Lunch with no change",
683 change=NoChange(),
684 dumpvars=True,
685 preroll=1,
686 postroll=0,
687 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000688 Benchmark(id="full",
Joe Onorato01277d42023-12-20 04:00:12 +0000689 title="Full build",
690 change=Clean(),
691 modules=["droid"],
692 preroll=0,
693 postroll=3,
694 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000695 Benchmark(id="nochange",
Joe Onorato01277d42023-12-20 04:00:12 +0000696 title="No change",
697 change=NoChange(),
698 modules=["droid"],
699 preroll=2,
700 postroll=3,
701 ),
702 Benchmark(id="unreferenced",
703 title="Create unreferenced file",
704 change=Create("bionic/unreferenced.txt"),
705 modules=["droid"],
706 preroll=1,
707 postroll=2,
708 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000709 Benchmark(id="modify_bp",
Joe Onorato01277d42023-12-20 04:00:12 +0000710 title="Modify Android.bp",
711 change=Modify("bionic/libc/Android.bp", Comment("//")),
712 modules=["droid"],
713 preroll=1,
714 postroll=3,
715 ),
716 Benchmark(id="modify_stdio",
717 title="Modify stdio.cpp",
718 change=Modify("bionic/libc/stdio/stdio.cpp", Comment("//")),
719 modules=["libc"],
720 preroll=1,
721 postroll=2,
722 ),
723 Benchmark(id="modify_adbd",
724 title="Modify adbd",
725 change=Modify("packages/modules/adb/daemon/main.cpp", Comment("//")),
726 modules=["adbd"],
727 preroll=1,
728 postroll=2,
729 ),
730 Benchmark(id="services_private_field",
731 title="Add private field to ActivityManagerService.java",
732 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
733 "private"),
734 modules=["services"],
735 preroll=1,
736 postroll=2,
737 ),
738 Benchmark(id="services_public_field",
739 title="Add public field to ActivityManagerService.java",
740 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
741 "/** @hide */ public"),
742 modules=["services"],
743 preroll=1,
744 postroll=2,
745 ),
746 Benchmark(id="services_api",
747 title="Add API to ActivityManagerService.javaa",
748 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
749 "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
750 modules=["services"],
751 preroll=1,
752 postroll=2,
753 ),
754 Benchmark(id="framework_private_field",
755 title="Add private field to Settings.java",
756 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
757 "private"),
758 modules=["framework-minus-apex"],
759 preroll=1,
760 postroll=2,
761 ),
762 Benchmark(id="framework_public_field",
763 title="Add public field to Settings.java",
764 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
765 "/** @hide */ public"),
766 modules=["framework-minus-apex"],
767 preroll=1,
768 postroll=2,
769 ),
770 Benchmark(id="framework_api",
771 title="Add API to Settings.java",
Yu Liu473bb052024-02-13 23:46:06 +0000772 change=ChangePublicApi(),
773 modules=["api-stubs-docs-non-updatable-update-current-api", "framework-minus-apex"],
Joe Onorato01277d42023-12-20 04:00:12 +0000774 preroll=1,
775 postroll=2,
776 ),
777 Benchmark(id="modify_framework_resource",
778 title="Modify framework resource",
779 change=Modify("frameworks/base/core/res/res/values/config.xml",
780 lambda: str(uuid.uuid4()),
781 before="</string>"),
782 modules=["framework-minus-apex"],
783 preroll=1,
784 postroll=2,
785 ),
786 Benchmark(id="add_framework_resource",
787 title="Add framework resource",
788 change=Modify("frameworks/base/core/res/res/values/config.xml",
789 lambda: f"<string name=\"BENCHMARK\">{uuid.uuid4()}</string>",
790 before="</resources>"),
791 modules=["framework-minus-apex"],
792 preroll=1,
793 postroll=2,
794 ),
795 Benchmark(id="add_systemui_field",
796 title="Add SystemUI field",
797 change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
798 "public"),
799 modules=["SystemUI"],
800 preroll=1,
801 postroll=2,
802 ),
LaMont Jonesa64ce312025-01-09 17:24:24 -0800803 Benchmark(id="add_systemui_field_with_tests",
804 title="Add SystemUI field with tests",
805 change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
806 "public"),
807 modules=["SystemUiRavenTests"],
808 preroll=1,
809 postroll=2,
810 ),
LaMont Jones3a043082025-01-21 11:04:29 -0800811 Benchmark(id="systemui_flicker_add_log_call",
812 title="Add a Log call to flicker",
813 change=Modify("platform_testing/libraries/flicker/src/android/tools/flicker/FlickerServiceResultsCollector.kt",
814 lambda: f'Log.v(LOG_TAG, "BENCHMARK = {random.randint(0, 1000000)}");\n',
815 before="Log.v(LOG_TAG,"),
816 modules=["WMShellFlickerTestsPip"],
817 preroll=1,
818 postroll=2,
819 ),
820 Benchmark(id="systemui_core_add_log_call",
821 title="Add a Log call SystemUIApplication",
822 change=Modify("frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java",
823 lambda: f'Log.v(TAG, "BENCHMARK = {random.randint(0, 1000000)}");\n',
824 before="Log.wtf(TAG,"),
825 modules=["SystemUI-core"],
826 preroll=1,
827 postroll=2,
828 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000829 ]
830
831 def _error(self, message):
832 report_error(message)
833 self._had_error = True
834
835
836def report_error(message):
837 sys.stderr.write(f"error: {message}\n")
838
839
840def main(argv):
841 try:
842 options = Options()
843 runner = Runner(options)
844 runner.Run()
845 except FatalError:
846 sys.stderr.write(f"FAILED\n")
Yu Liuc6576ad2024-01-16 19:27:45 +0000847 sys.exit(1)
Joe Onorato88ede352023-12-19 02:56:38 +0000848
849
850if __name__ == "__main__":
851 main(sys.argv)