blob: 6998ecd5c2d5d32eae0045547d0da5163d5ee063 [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."""
340 # Clean out the log dir or create it if necessary
341 prepare_log_dir(self._options.LogDir())
342
343 try:
344 for lunch in self._options.Lunches():
345 print(lunch)
346 for benchmark in self._options.Benchmarks():
347 for iteration in range(self._options.Iterations()):
348 self._run_benchmark(lunch, benchmark, iteration)
349 self._complete = True
350 finally:
351 self._write_summary()
352
353
354 def _run_benchmark(self, lunch, benchmark, iteration):
355 """Run a single benchmark."""
Yu Liucda84242024-01-17 21:30:53 +0000356 benchmark_log_subdir = self._benchmark_log_dir(lunch, benchmark, iteration)
Joe Onorato88ede352023-12-19 02:56:38 +0000357 benchmark_log_dir = self._options.LogDir().joinpath(benchmark_log_subdir)
358
359 sys.stderr.write(f"STARTING BENCHMARK: {benchmark.id}\n")
360 sys.stderr.write(f" lunch: {lunch.Combine()}\n")
361 sys.stderr.write(f" iteration: {iteration}\n")
362 sys.stderr.write(f" benchmark_log_dir: {benchmark_log_dir}\n")
363
364 report = BenchmarkReport(lunch, benchmark, iteration, benchmark_log_subdir)
365 self._reports.append(report)
366
367 # Preroll builds
368 for i in range(benchmark.preroll):
Liz Kammerefb66502024-01-25 11:08:10 -0500369 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"pre_{i}"), benchmark)
Joe Onorato88ede352023-12-19 02:56:38 +0000370 report.preroll_duration_ns.append(ns)
371
372 sys.stderr.write(f"PERFORMING CHANGE: {benchmark.change.label}\n")
373 if not self._options.DryRun():
374 benchmark.change.change()
375 try:
376
377 # Measured build
Liz Kammerefb66502024-01-25 11:08:10 -0500378 ns = self._run_build(lunch, benchmark_log_dir.joinpath("measured"), benchmark)
Joe Onorato88ede352023-12-19 02:56:38 +0000379 report.duration_ns = ns
380
Joe Onorato05434642023-12-20 05:00:04 +0000381 dist_one = self._options.DistOne()
382 if dist_one:
383 # If we're disting just one benchmark, save the logs and we can stop here.
Liz Kammerefb66502024-01-25 11:08:10 -0500384 self._dist(utils.get_dist_dir(), benchmark.dumpvars)
Joe Onorato05434642023-12-20 05:00:04 +0000385 else:
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500386 self._dist(benchmark_log_dir, benchmark.dumpvars, store_metrics_only=True)
Joe Onorato05434642023-12-20 05:00:04 +0000387 # Postroll builds
Liz Kammerf67a6e82024-01-25 11:06:40 -0500388 for i in range(benchmark.postroll):
Joe Onorato05434642023-12-20 05:00:04 +0000389 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"post_{i}"),
Liz Kammerefb66502024-01-25 11:08:10 -0500390 benchmark)
Joe Onorato05434642023-12-20 05:00:04 +0000391 report.postroll_duration_ns.append(ns)
Joe Onorato88ede352023-12-19 02:56:38 +0000392
393 finally:
394 # Always undo, even if we crashed or the build failed and we stopped.
395 sys.stderr.write(f"UNDOING CHANGE: {benchmark.change.label}\n")
396 if not self._options.DryRun():
397 benchmark.change.undo()
398
399 self._write_summary()
400 sys.stderr.write(f"FINISHED BENCHMARK: {benchmark.id}\n")
401
Yu Liucda84242024-01-17 21:30:53 +0000402 def _benchmark_log_dir(self, lunch, benchmark, iteration):
Joe Onorato88ede352023-12-19 02:56:38 +0000403 """Construct the log directory fir a benchmark run."""
404 path = f"{lunch.Combine()}/{benchmark.id}"
405 # Zero pad to the correct length for correct alpha sorting
406 path += ("/%0" + str(len(str(self._options.Iterations()))) + "d") % iteration
407 return path
408
Liz Kammerefb66502024-01-25 11:08:10 -0500409 def _run_build(self, lunch, build_log_dir, benchmark):
Joe Onorato88ede352023-12-19 02:56:38 +0000410 """Builds the modules. Saves interesting log files to log_dir. Raises FatalError
411 if the build fails.
412 """
Liz Kammerefb66502024-01-25 11:08:10 -0500413 sys.stderr.write(f"STARTING BUILD {benchmark.build_description()}\n")
Joe Onorato88ede352023-12-19 02:56:38 +0000414
415 before_ns = time.perf_counter_ns()
416 if not self._options.DryRun():
417 cmd = [
418 "build/soong/soong_ui.bash",
Liz Kammerefb66502024-01-25 11:08:10 -0500419 ] + benchmark.soong_command(self._options.root)
Joe Onorato88ede352023-12-19 02:56:38 +0000420 env = dict(os.environ)
421 env["TARGET_PRODUCT"] = lunch.target_product
422 env["TARGET_RELEASE"] = lunch.target_release
423 env["TARGET_BUILD_VARIANT"] = lunch.target_build_variant
424 returncode = subprocess.call(cmd, env=env)
425 if returncode != 0:
426 report_error(f"Build failed: {' '.join(cmd)}")
427 raise FatalError()
428
429 after_ns = time.perf_counter_ns()
430
431 # TODO: Copy some log files.
432
Liz Kammerefb66502024-01-25 11:08:10 -0500433 sys.stderr.write(f"FINISHED BUILD {benchmark.build_description()}\n")
Joe Onorato88ede352023-12-19 02:56:38 +0000434
435 return after_ns - before_ns
436
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500437 def _dist(self, dist_dir, dumpvars, store_metrics_only=False):
Yu Liucda84242024-01-17 21:30:53 +0000438 out_dir = utils.get_out_dir()
439 dest_dir = dist_dir.joinpath("logs")
Joe Onorato05434642023-12-20 05:00:04 +0000440 os.makedirs(dest_dir, exist_ok=True)
441 basenames = [
Joe Onorato05434642023-12-20 05:00:04 +0000442 "soong_build_metrics.pb",
443 "soong_metrics",
444 ]
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500445 if not store_metrics_only:
446 basenames.extend([
447 "build.trace.gz",
448 "soong.log",
449 ])
Liz Kammerefb66502024-01-25 11:08:10 -0500450 if dumpvars:
451 basenames = ['dumpvars-'+b for b in basenames]
Joe Onorato05434642023-12-20 05:00:04 +0000452 for base in basenames:
453 src = out_dir.joinpath(base)
454 if src.exists():
455 sys.stderr.write(f"DIST: copied {src} to {dest_dir}\n")
456 shutil.copy(src, dest_dir)
457
Joe Onorato88ede352023-12-19 02:56:38 +0000458 def _write_summary(self):
459 # Write the results, even if the build failed or we crashed, including
460 # whether we finished all of the benchmarks.
461 data = {
462 "start_time": self._options.Timestamp().isoformat(),
463 "branch": self._options.Branch(),
464 "tag": self._options.Tag(),
465 "benchmarks": [report.ToDict() for report in self._reports],
466 "complete": self._complete,
467 }
468 with open(self._options.LogDir().joinpath("summary.json"), "w", encoding="utf-8") as f:
469 json.dump(data, f, indent=2, sort_keys=True)
470
471
472def benchmark_table(benchmarks):
473 rows = [("ID", "DESCRIPTION", "REBUILD"),]
Liz Kammerefb66502024-01-25 11:08:10 -0500474 rows += [(benchmark.id, benchmark.title, benchmark.build_description()) for benchmark in
Joe Onorato88ede352023-12-19 02:56:38 +0000475 benchmarks]
476 return rows
477
478
479def prepare_log_dir(directory):
480 if os.path.exists(directory):
481 # If it exists and isn't a directory, fail.
482 if not os.path.isdir(directory):
483 report_error(f"Log directory already exists but isn't a directory: {directory}")
484 raise FatalError()
485 # Make sure the directory is empty. Do this rather than deleting it to handle
486 # symlinks cleanly.
487 for filename in os.listdir(directory):
488 entry = os.path.join(directory, filename)
489 if os.path.isdir(entry):
490 shutil.rmtree(entry)
491 else:
492 os.unlink(entry)
493 else:
494 # Create it
495 os.makedirs(directory)
496
497
498class Options():
499 def __init__(self):
500 self._had_error = False
501
502 # Wall time clock when we started
503 self._timestamp = datetime.datetime.now(datetime.timezone.utc)
504
505 # Move to the root of the tree right away. Everything must happen from there.
506 self.root = utils.get_root()
507 if not self.root:
508 report_error("Unable to find root of tree from cwd.")
509 raise FatalError()
510 os.chdir(self.root)
511
512 # Initialize the Benchmarks. Note that this pre-loads all of the files, etc.
513 # Doing all that here forces us to fail fast if one of them can't load a required
514 # file, at the cost of a small startup speed. Don't make this do something slow
515 # like scan the whole tree.
516 self._init_benchmarks()
517
518 # Argument parsing
519 epilog = f"""
520benchmarks:
521{pretty.FormatTable(benchmark_table(self._benchmarks), prefix=" ")}
522"""
523
524 parser = argparse.ArgumentParser(
525 prog="benchmarks",
526 allow_abbrev=False, # Don't let people write unsupportable scripts.
527 formatter_class=argparse.RawDescriptionHelpFormatter,
528 epilog=epilog,
529 description="Run build system performance benchmarks.")
530 self.parser = parser
531
532 parser.add_argument("--log-dir",
533 help="Directory for logs. Default is $TOP/../benchmarks/.")
534 parser.add_argument("--dated-logs", action="store_true",
535 help="Append timestamp to log dir.")
536 parser.add_argument("-n", action="store_true", dest="dry_run",
537 help="Dry run. Don't run the build commands but do everything else.")
538 parser.add_argument("--tag",
539 help="Variant of the run, for when there are multiple perf runs.")
540 parser.add_argument("--lunch", nargs="*",
541 help="Lunch combos to test")
542 parser.add_argument("--iterations", type=int, default=1,
543 help="Number of iterations of each test to run.")
544 parser.add_argument("--branch", type=str,
545 help="Specify branch. Otherwise a guess will be made based on repo.")
546 parser.add_argument("--benchmark", nargs="*", default=[b.id for b in self._benchmarks],
547 metavar="BENCHMARKS",
548 help="Benchmarks to run. Default suite will be run if omitted.")
Joe Onorato60c36ad2024-01-02 07:32:54 +0000549 parser.add_argument("--dist-one", action="store_true",
Joe Onorato05434642023-12-20 05:00:04 +0000550 help="Copy logs and metrics to the given dist dir. Requires that only"
551 + " one benchmark be supplied. Postroll steps will be skipped.")
Joe Onorato88ede352023-12-19 02:56:38 +0000552
553 self._args = parser.parse_args()
554
555 self._branch = self._branch()
556 self._log_dir = self._log_dir()
557 self._lunches = self._lunches()
558
559 # Validate the benchmark ids
560 all_ids = [benchmark.id for benchmark in self._benchmarks]
561 bad_ids = [id for id in self._args.benchmark if id not in all_ids]
562 if bad_ids:
563 for id in bad_ids:
564 self._error(f"Invalid benchmark: {id}")
565
Joe Onorato05434642023-12-20 05:00:04 +0000566 # --dist-one requires that only one benchmark be supplied
Joe Onorato60c36ad2024-01-02 07:32:54 +0000567 if self._args.dist_one and len(self.Benchmarks()) != 1:
Joe Onorato05434642023-12-20 05:00:04 +0000568 self._error("--dist-one requires that exactly one --benchmark.")
569
Joe Onorato88ede352023-12-19 02:56:38 +0000570 if self._had_error:
571 raise FatalError()
572
573 def Timestamp(self):
574 return self._timestamp
575
576 def _branch(self):
577 """Return the branch, either from the command line or by guessing from repo."""
578 if self._args.branch:
579 return self._args.branch
580 try:
581 branch = subprocess.check_output(f"cd {self.root}/.repo/manifests"
582 + " && git rev-parse --abbrev-ref --symbolic-full-name @{u}",
583 shell=True, encoding="utf-8")
584 return branch.strip().split("/")[-1]
585 except subprocess.CalledProcessError as ex:
586 report_error("Can't get branch from .repo dir. Specify --branch argument")
587 report_error(str(ex))
588 raise FatalError()
589
590 def Branch(self):
591 return self._branch
592
593 def _log_dir(self):
594 "The log directory to use, based on the current options"
595 if self._args.log_dir:
596 d = pathlib.Path(self._args.log_dir).resolve().absolute()
597 else:
598 d = self.root.joinpath("..", utils.DEFAULT_REPORT_DIR)
599 if self._args.dated_logs:
600 d = d.joinpath(self._timestamp.strftime('%Y-%m-%d'))
601 d = d.joinpath(self._branch)
602 if self._args.tag:
603 d = d.joinpath(self._args.tag)
604 return d.resolve().absolute()
605
606 def LogDir(self):
607 return self._log_dir
608
609 def Benchmarks(self):
610 return [b for b in self._benchmarks if b.id in self._args.benchmark]
611
612 def Tag(self):
613 return self._args.tag
614
615 def DryRun(self):
616 return self._args.dry_run
617
618 def _lunches(self):
619 def parse_lunch(lunch):
620 parts = lunch.split("-")
621 if len(parts) != 3:
622 raise OptionsError(f"Invalid lunch combo: {lunch}")
623 return Lunch(parts[0], parts[1], parts[2])
624 # If they gave lunch targets on the command line use that
625 if self._args.lunch:
626 result = []
627 # Split into Lunch objects
628 for lunch in self._args.lunch:
629 try:
630 result.append(parse_lunch(lunch))
631 except OptionsError as ex:
632 self._error(ex.message)
633 return result
634 # Use whats in the environment
635 product = os.getenv("TARGET_PRODUCT")
636 release = os.getenv("TARGET_RELEASE")
637 variant = os.getenv("TARGET_BUILD_VARIANT")
638 if (not product) or (not release) or (not variant):
639 # If they didn't give us anything, fail rather than guessing. There's no good
640 # default for AOSP.
641 self._error("No lunch combo specified. Either pass --lunch argument or run lunch.")
642 return []
643 return [Lunch(product, release, variant),]
644
645 def Lunches(self):
646 return self._lunches
647
648 def Iterations(self):
649 return self._args.iterations
650
Joe Onorato05434642023-12-20 05:00:04 +0000651 def DistOne(self):
652 return self._args.dist_one
653
Joe Onorato88ede352023-12-19 02:56:38 +0000654 def _init_benchmarks(self):
655 """Initialize the list of benchmarks."""
656 # Assumes that we've already chdired to the root of the tree.
657 self._benchmarks = [
Liz Kammerefb66502024-01-25 11:08:10 -0500658 Benchmark(
659 id="full_lunch",
660 title="Lunch from clean out",
661 change=Clean(),
662 dumpvars=True,
663 preroll=0,
664 postroll=0,
665 ),
666 Benchmark(
667 id="noop_lunch",
668 title="Lunch with no change",
669 change=NoChange(),
670 dumpvars=True,
671 preroll=1,
672 postroll=0,
673 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000674 Benchmark(id="full",
Joe Onorato01277d42023-12-20 04:00:12 +0000675 title="Full build",
676 change=Clean(),
677 modules=["droid"],
678 preroll=0,
679 postroll=3,
680 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000681 Benchmark(id="nochange",
Joe Onorato01277d42023-12-20 04:00:12 +0000682 title="No change",
683 change=NoChange(),
684 modules=["droid"],
685 preroll=2,
686 postroll=3,
687 ),
688 Benchmark(id="unreferenced",
689 title="Create unreferenced file",
690 change=Create("bionic/unreferenced.txt"),
691 modules=["droid"],
692 preroll=1,
693 postroll=2,
694 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000695 Benchmark(id="modify_bp",
Joe Onorato01277d42023-12-20 04:00:12 +0000696 title="Modify Android.bp",
697 change=Modify("bionic/libc/Android.bp", Comment("//")),
698 modules=["droid"],
699 preroll=1,
700 postroll=3,
701 ),
702 Benchmark(id="modify_stdio",
703 title="Modify stdio.cpp",
704 change=Modify("bionic/libc/stdio/stdio.cpp", Comment("//")),
705 modules=["libc"],
706 preroll=1,
707 postroll=2,
708 ),
709 Benchmark(id="modify_adbd",
710 title="Modify adbd",
711 change=Modify("packages/modules/adb/daemon/main.cpp", Comment("//")),
712 modules=["adbd"],
713 preroll=1,
714 postroll=2,
715 ),
716 Benchmark(id="services_private_field",
717 title="Add private field to ActivityManagerService.java",
718 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
719 "private"),
720 modules=["services"],
721 preroll=1,
722 postroll=2,
723 ),
724 Benchmark(id="services_public_field",
725 title="Add public field to ActivityManagerService.java",
726 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
727 "/** @hide */ public"),
728 modules=["services"],
729 preroll=1,
730 postroll=2,
731 ),
732 Benchmark(id="services_api",
733 title="Add API to ActivityManagerService.javaa",
734 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
735 "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
736 modules=["services"],
737 preroll=1,
738 postroll=2,
739 ),
740 Benchmark(id="framework_private_field",
741 title="Add private field to Settings.java",
742 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
743 "private"),
744 modules=["framework-minus-apex"],
745 preroll=1,
746 postroll=2,
747 ),
748 Benchmark(id="framework_public_field",
749 title="Add public field to Settings.java",
750 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
751 "/** @hide */ public"),
752 modules=["framework-minus-apex"],
753 preroll=1,
754 postroll=2,
755 ),
756 Benchmark(id="framework_api",
757 title="Add API to Settings.java",
Yu Liu473bb052024-02-13 23:46:06 +0000758 change=ChangePublicApi(),
759 modules=["api-stubs-docs-non-updatable-update-current-api", "framework-minus-apex"],
Joe Onorato01277d42023-12-20 04:00:12 +0000760 preroll=1,
761 postroll=2,
762 ),
763 Benchmark(id="modify_framework_resource",
764 title="Modify framework resource",
765 change=Modify("frameworks/base/core/res/res/values/config.xml",
766 lambda: str(uuid.uuid4()),
767 before="</string>"),
768 modules=["framework-minus-apex"],
769 preroll=1,
770 postroll=2,
771 ),
772 Benchmark(id="add_framework_resource",
773 title="Add framework resource",
774 change=Modify("frameworks/base/core/res/res/values/config.xml",
775 lambda: f"<string name=\"BENCHMARK\">{uuid.uuid4()}</string>",
776 before="</resources>"),
777 modules=["framework-minus-apex"],
778 preroll=1,
779 postroll=2,
780 ),
781 Benchmark(id="add_systemui_field",
782 title="Add SystemUI field",
783 change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
784 "public"),
785 modules=["SystemUI"],
786 preroll=1,
787 postroll=2,
788 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000789 ]
790
791 def _error(self, message):
792 report_error(message)
793 self._had_error = True
794
795
796def report_error(message):
797 sys.stderr.write(f"error: {message}\n")
798
799
800def main(argv):
801 try:
802 options = Options()
803 runner = Runner(options)
804 runner.Run()
805 except FatalError:
806 sys.stderr.write(f"FAILED\n")
Yu Liuc6576ad2024-01-16 19:27:45 +0000807 sys.exit(1)
Joe Onorato88ede352023-12-19 02:56:38 +0000808
809
810if __name__ == "__main__":
811 main(sys.argv)