blob: 38715ea8ead551b46d5618b186698520a83139f7 [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
Joe Onorato685029d2025-03-20 14:40:20 -0700205def CleanNinja():
206 """Remove the out directory, and then run lunch to initialize soong"""
207 def clean_ninja():
208 returncode = subprocess.call("rm out/*.ninja out/soong/*.ninja", shell=True)
209 if returncode != 0:
210 report_error(f"Build failed: {' '.join(cmd)}")
211 raise FatalError()
212 return Change(label="Remove ninja files", change=clean_ninja, undo=lambda: None)
213
214
Joe Onorato88ede352023-12-19 02:56:38 +0000215def NoChange():
216 """No change to the source tree."""
217 return Change(label="No change", change=lambda: None, undo=lambda: None)
218
219
Joe Onorato01277d42023-12-20 04:00:12 +0000220def Create(filename):
221 "Create an action to create `filename`. The parent directory must exist."
222 def create():
223 with open(filename, "w") as f:
224 pass
225 def delete():
226 os.remove(filename)
227 return Change(
228 label=f"Create {filename}",
229 change=create,
230 undo=delete,
231 )
232
233
Joe Onorato88ede352023-12-19 02:56:38 +0000234def Modify(filename, contents, before=None):
Joe Onorato01277d42023-12-20 04:00:12 +0000235 """Create an action to modify `filename` by appending the result of `contents`
236 before the last instances of `before` in the file.
Joe Onorato88ede352023-12-19 02:56:38 +0000237
238 Raises an error if `before` doesn't appear in the file.
239 """
240 orig = Snapshot(filename)
241 if before:
242 index = orig.contents.rfind(before)
243 if index < 0:
244 report_error(f"{filename}: Unable to find string '{before}' for modify operation.")
245 raise FatalError()
246 else:
247 index = len(orig.contents)
Joe Onorato01277d42023-12-20 04:00:12 +0000248 modified = FileSnapshot(filename, orig.contents[:index] + contents() + orig.contents[index:])
249 if False:
250 print(f"Modify: {filename}")
251 x = orig.contents.replace("\n", "\n ORIG")
252 print(f" ORIG {x}")
253 x = modified.contents.replace("\n", "\n MODIFIED")
254 print(f" MODIFIED {x}")
255
Joe Onorato88ede352023-12-19 02:56:38 +0000256 return Change(
257 label="Modify " + filename,
258 change=lambda: modified.write(),
259 undo=lambda: orig.write()
260 )
261
Yu Liu473bb052024-02-13 23:46:06 +0000262def ChangePublicApi():
263 change = AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
264 "@android.annotation.SuppressLint(\"UnflaggedApi\") public")
265 orig_current_text = Snapshot("frameworks/base/core/api/current.txt")
266
267 def undo():
268 change.undo()
269 orig_current_text.write()
270
271 return Change(
272 label=change.label,
273 change=change.change,
274 undo=lambda: undo()
275 )
276
Joe Onorato01277d42023-12-20 04:00:12 +0000277def AddJavaField(filename, prefix):
278 return Modify(filename,
279 lambda: f"{prefix} static final int BENCHMARK = {random.randint(0, 1000000)};\n",
280 before="}")
281
282
283def Comment(prefix, suffix=""):
284 return lambda: prefix + " " + str(uuid.uuid4()) + suffix
285
Joe Onorato88ede352023-12-19 02:56:38 +0000286
287class BenchmarkReport():
288 "Information about a run of the benchmark"
289
290 lunch: Lunch
291 "lunch combo"
292
293 benchmark: Benchmark
294 "The benchmark object."
295
296 iteration: int
297 "Which iteration of the benchmark"
298
299 log_dir: str
300 "Path the the log directory, relative to the root of the reports directory"
301
302 preroll_duration_ns: [int]
303 "Durations of the in nanoseconds."
304
305 duration_ns: int
306 "Duration of the measured portion of the benchmark in nanoseconds."
307
308 postroll_duration_ns: [int]
309 "Durations of the postrolls in nanoseconds."
310
311 complete: bool
312 "Whether the benchmark made it all the way through the postrolls."
313
314 def __init__(self, lunch, benchmark, iteration, log_dir):
315 self.lunch = lunch
316 self.benchmark = benchmark
317 self.iteration = iteration
318 self.log_dir = log_dir
319 self.preroll_duration_ns = []
320 self.duration_ns = -1
321 self.postroll_duration_ns = []
322 self.complete = False
323
324 def ToDict(self):
325 return {
326 "lunch": self.lunch.ToDict(),
327 "id": self.benchmark.id,
328 "title": self.benchmark.title,
329 "modules": self.benchmark.modules,
Liz Kammerefb66502024-01-25 11:08:10 -0500330 "dumpvars": self.benchmark.dumpvars,
Joe Onorato88ede352023-12-19 02:56:38 +0000331 "change": self.benchmark.change.label,
332 "iteration": self.iteration,
333 "log_dir": self.log_dir,
334 "preroll_duration_ns": self.preroll_duration_ns,
335 "duration_ns": self.duration_ns,
336 "postroll_duration_ns": self.postroll_duration_ns,
337 "complete": self.complete,
338 }
339
340class Runner():
341 """Runs the benchmarks."""
342
343 def __init__(self, options):
344 self._options = options
345 self._reports = []
346 self._complete = False
347
348 def Run(self):
349 """Run all of the user-selected benchmarks."""
LaMont Jones42abe8e2025-02-13 14:43:33 -0800350
351 # With `--list`, just list the benchmarks available.
352 if self._options.List():
353 print(" ".join(self._options.BenchmarkIds()))
354 return
355
Joe Onorato88ede352023-12-19 02:56:38 +0000356 # Clean out the log dir or create it if necessary
357 prepare_log_dir(self._options.LogDir())
358
359 try:
360 for lunch in self._options.Lunches():
361 print(lunch)
362 for benchmark in self._options.Benchmarks():
363 for iteration in range(self._options.Iterations()):
364 self._run_benchmark(lunch, benchmark, iteration)
365 self._complete = True
366 finally:
367 self._write_summary()
368
369
370 def _run_benchmark(self, lunch, benchmark, iteration):
371 """Run a single benchmark."""
Yu Liucda84242024-01-17 21:30:53 +0000372 benchmark_log_subdir = self._benchmark_log_dir(lunch, benchmark, iteration)
Joe Onorato88ede352023-12-19 02:56:38 +0000373 benchmark_log_dir = self._options.LogDir().joinpath(benchmark_log_subdir)
374
375 sys.stderr.write(f"STARTING BENCHMARK: {benchmark.id}\n")
376 sys.stderr.write(f" lunch: {lunch.Combine()}\n")
377 sys.stderr.write(f" iteration: {iteration}\n")
378 sys.stderr.write(f" benchmark_log_dir: {benchmark_log_dir}\n")
379
380 report = BenchmarkReport(lunch, benchmark, iteration, benchmark_log_subdir)
381 self._reports.append(report)
382
383 # Preroll builds
384 for i in range(benchmark.preroll):
Liz Kammerefb66502024-01-25 11:08:10 -0500385 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"pre_{i}"), benchmark)
Joe Onorato88ede352023-12-19 02:56:38 +0000386 report.preroll_duration_ns.append(ns)
387
388 sys.stderr.write(f"PERFORMING CHANGE: {benchmark.change.label}\n")
389 if not self._options.DryRun():
390 benchmark.change.change()
391 try:
392
393 # Measured build
Liz Kammerefb66502024-01-25 11:08:10 -0500394 ns = self._run_build(lunch, benchmark_log_dir.joinpath("measured"), benchmark)
Joe Onorato88ede352023-12-19 02:56:38 +0000395 report.duration_ns = ns
396
Joe Onorato05434642023-12-20 05:00:04 +0000397 dist_one = self._options.DistOne()
398 if dist_one:
399 # If we're disting just one benchmark, save the logs and we can stop here.
Liz Kammerefb66502024-01-25 11:08:10 -0500400 self._dist(utils.get_dist_dir(), benchmark.dumpvars)
Joe Onorato05434642023-12-20 05:00:04 +0000401 else:
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500402 self._dist(benchmark_log_dir, benchmark.dumpvars, store_metrics_only=True)
Joe Onorato05434642023-12-20 05:00:04 +0000403 # Postroll builds
Liz Kammerf67a6e82024-01-25 11:06:40 -0500404 for i in range(benchmark.postroll):
Joe Onorato05434642023-12-20 05:00:04 +0000405 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"post_{i}"),
Liz Kammerefb66502024-01-25 11:08:10 -0500406 benchmark)
Joe Onorato05434642023-12-20 05:00:04 +0000407 report.postroll_duration_ns.append(ns)
Joe Onorato88ede352023-12-19 02:56:38 +0000408
409 finally:
410 # Always undo, even if we crashed or the build failed and we stopped.
411 sys.stderr.write(f"UNDOING CHANGE: {benchmark.change.label}\n")
412 if not self._options.DryRun():
413 benchmark.change.undo()
414
415 self._write_summary()
416 sys.stderr.write(f"FINISHED BENCHMARK: {benchmark.id}\n")
417
Yu Liucda84242024-01-17 21:30:53 +0000418 def _benchmark_log_dir(self, lunch, benchmark, iteration):
Joe Onorato88ede352023-12-19 02:56:38 +0000419 """Construct the log directory fir a benchmark run."""
420 path = f"{lunch.Combine()}/{benchmark.id}"
421 # Zero pad to the correct length for correct alpha sorting
422 path += ("/%0" + str(len(str(self._options.Iterations()))) + "d") % iteration
423 return path
424
Liz Kammerefb66502024-01-25 11:08:10 -0500425 def _run_build(self, lunch, build_log_dir, benchmark):
Joe Onorato88ede352023-12-19 02:56:38 +0000426 """Builds the modules. Saves interesting log files to log_dir. Raises FatalError
427 if the build fails.
428 """
Joe Onorato685029d2025-03-20 14:40:20 -0700429 sys.stderr.write(f"STARTING BUILD {benchmark.build_description()} Logs to: {build_log_dir}\n")
Joe Onorato88ede352023-12-19 02:56:38 +0000430
431 before_ns = time.perf_counter_ns()
432 if not self._options.DryRun():
433 cmd = [
434 "build/soong/soong_ui.bash",
Liz Kammerefb66502024-01-25 11:08:10 -0500435 ] + benchmark.soong_command(self._options.root)
Joe Onorato88ede352023-12-19 02:56:38 +0000436 env = dict(os.environ)
437 env["TARGET_PRODUCT"] = lunch.target_product
438 env["TARGET_RELEASE"] = lunch.target_release
439 env["TARGET_BUILD_VARIANT"] = lunch.target_build_variant
440 returncode = subprocess.call(cmd, env=env)
441 if returncode != 0:
442 report_error(f"Build failed: {' '.join(cmd)}")
443 raise FatalError()
444
445 after_ns = time.perf_counter_ns()
446
447 # TODO: Copy some log files.
448
Liz Kammerefb66502024-01-25 11:08:10 -0500449 sys.stderr.write(f"FINISHED BUILD {benchmark.build_description()}\n")
Joe Onorato88ede352023-12-19 02:56:38 +0000450
451 return after_ns - before_ns
452
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500453 def _dist(self, dist_dir, dumpvars, store_metrics_only=False):
Yu Liucda84242024-01-17 21:30:53 +0000454 out_dir = utils.get_out_dir()
455 dest_dir = dist_dir.joinpath("logs")
Joe Onorato05434642023-12-20 05:00:04 +0000456 os.makedirs(dest_dir, exist_ok=True)
457 basenames = [
Joe Onorato05434642023-12-20 05:00:04 +0000458 "soong_build_metrics.pb",
459 "soong_metrics",
460 ]
Liz Kammer0b7bdee2024-01-24 14:22:19 -0500461 if not store_metrics_only:
462 basenames.extend([
463 "build.trace.gz",
464 "soong.log",
465 ])
Liz Kammerefb66502024-01-25 11:08:10 -0500466 if dumpvars:
467 basenames = ['dumpvars-'+b for b in basenames]
Joe Onorato05434642023-12-20 05:00:04 +0000468 for base in basenames:
469 src = out_dir.joinpath(base)
470 if src.exists():
471 sys.stderr.write(f"DIST: copied {src} to {dest_dir}\n")
472 shutil.copy(src, dest_dir)
473
Joe Onorato88ede352023-12-19 02:56:38 +0000474 def _write_summary(self):
475 # Write the results, even if the build failed or we crashed, including
476 # whether we finished all of the benchmarks.
477 data = {
478 "start_time": self._options.Timestamp().isoformat(),
479 "branch": self._options.Branch(),
480 "tag": self._options.Tag(),
481 "benchmarks": [report.ToDict() for report in self._reports],
482 "complete": self._complete,
483 }
484 with open(self._options.LogDir().joinpath("summary.json"), "w", encoding="utf-8") as f:
485 json.dump(data, f, indent=2, sort_keys=True)
486
487
488def benchmark_table(benchmarks):
489 rows = [("ID", "DESCRIPTION", "REBUILD"),]
Liz Kammerefb66502024-01-25 11:08:10 -0500490 rows += [(benchmark.id, benchmark.title, benchmark.build_description()) for benchmark in
Joe Onorato88ede352023-12-19 02:56:38 +0000491 benchmarks]
492 return rows
493
494
495def prepare_log_dir(directory):
496 if os.path.exists(directory):
497 # If it exists and isn't a directory, fail.
498 if not os.path.isdir(directory):
499 report_error(f"Log directory already exists but isn't a directory: {directory}")
500 raise FatalError()
501 # Make sure the directory is empty. Do this rather than deleting it to handle
502 # symlinks cleanly.
503 for filename in os.listdir(directory):
504 entry = os.path.join(directory, filename)
505 if os.path.isdir(entry):
506 shutil.rmtree(entry)
507 else:
508 os.unlink(entry)
509 else:
510 # Create it
511 os.makedirs(directory)
512
513
514class Options():
515 def __init__(self):
516 self._had_error = False
517
518 # Wall time clock when we started
519 self._timestamp = datetime.datetime.now(datetime.timezone.utc)
520
521 # Move to the root of the tree right away. Everything must happen from there.
522 self.root = utils.get_root()
523 if not self.root:
524 report_error("Unable to find root of tree from cwd.")
525 raise FatalError()
526 os.chdir(self.root)
527
528 # Initialize the Benchmarks. Note that this pre-loads all of the files, etc.
529 # Doing all that here forces us to fail fast if one of them can't load a required
530 # file, at the cost of a small startup speed. Don't make this do something slow
531 # like scan the whole tree.
532 self._init_benchmarks()
533
534 # Argument parsing
535 epilog = f"""
536benchmarks:
537{pretty.FormatTable(benchmark_table(self._benchmarks), prefix=" ")}
538"""
539
540 parser = argparse.ArgumentParser(
541 prog="benchmarks",
542 allow_abbrev=False, # Don't let people write unsupportable scripts.
543 formatter_class=argparse.RawDescriptionHelpFormatter,
544 epilog=epilog,
545 description="Run build system performance benchmarks.")
546 self.parser = parser
547
548 parser.add_argument("--log-dir",
549 help="Directory for logs. Default is $TOP/../benchmarks/.")
550 parser.add_argument("--dated-logs", action="store_true",
551 help="Append timestamp to log dir.")
552 parser.add_argument("-n", action="store_true", dest="dry_run",
553 help="Dry run. Don't run the build commands but do everything else.")
554 parser.add_argument("--tag",
555 help="Variant of the run, for when there are multiple perf runs.")
556 parser.add_argument("--lunch", nargs="*",
557 help="Lunch combos to test")
558 parser.add_argument("--iterations", type=int, default=1,
559 help="Number of iterations of each test to run.")
560 parser.add_argument("--branch", type=str,
561 help="Specify branch. Otherwise a guess will be made based on repo.")
562 parser.add_argument("--benchmark", nargs="*", default=[b.id for b in self._benchmarks],
563 metavar="BENCHMARKS",
564 help="Benchmarks to run. Default suite will be run if omitted.")
LaMont Jones42abe8e2025-02-13 14:43:33 -0800565 parser.add_argument("--list", action="store_true",
566 help="list the available benchmarks. No benchmark is run.")
Joe Onorato60c36ad2024-01-02 07:32:54 +0000567 parser.add_argument("--dist-one", action="store_true",
Joe Onorato05434642023-12-20 05:00:04 +0000568 help="Copy logs and metrics to the given dist dir. Requires that only"
569 + " one benchmark be supplied. Postroll steps will be skipped.")
Joe Onorato88ede352023-12-19 02:56:38 +0000570
571 self._args = parser.parse_args()
572
573 self._branch = self._branch()
574 self._log_dir = self._log_dir()
575 self._lunches = self._lunches()
576
577 # Validate the benchmark ids
578 all_ids = [benchmark.id for benchmark in self._benchmarks]
579 bad_ids = [id for id in self._args.benchmark if id not in all_ids]
580 if bad_ids:
581 for id in bad_ids:
582 self._error(f"Invalid benchmark: {id}")
583
Joe Onorato05434642023-12-20 05:00:04 +0000584 # --dist-one requires that only one benchmark be supplied
Joe Onorato60c36ad2024-01-02 07:32:54 +0000585 if self._args.dist_one and len(self.Benchmarks()) != 1:
LaMont Jonesecc326c2025-03-06 10:27:07 -0800586 self._error("--dist-one requires exactly one --benchmark.")
Joe Onorato05434642023-12-20 05:00:04 +0000587
Joe Onorato88ede352023-12-19 02:56:38 +0000588 if self._had_error:
589 raise FatalError()
590
591 def Timestamp(self):
592 return self._timestamp
593
594 def _branch(self):
595 """Return the branch, either from the command line or by guessing from repo."""
596 if self._args.branch:
597 return self._args.branch
598 try:
599 branch = subprocess.check_output(f"cd {self.root}/.repo/manifests"
600 + " && git rev-parse --abbrev-ref --symbolic-full-name @{u}",
601 shell=True, encoding="utf-8")
602 return branch.strip().split("/")[-1]
603 except subprocess.CalledProcessError as ex:
604 report_error("Can't get branch from .repo dir. Specify --branch argument")
605 report_error(str(ex))
606 raise FatalError()
607
608 def Branch(self):
609 return self._branch
610
611 def _log_dir(self):
612 "The log directory to use, based on the current options"
613 if self._args.log_dir:
614 d = pathlib.Path(self._args.log_dir).resolve().absolute()
615 else:
616 d = self.root.joinpath("..", utils.DEFAULT_REPORT_DIR)
617 if self._args.dated_logs:
618 d = d.joinpath(self._timestamp.strftime('%Y-%m-%d'))
619 d = d.joinpath(self._branch)
620 if self._args.tag:
621 d = d.joinpath(self._args.tag)
622 return d.resolve().absolute()
623
624 def LogDir(self):
625 return self._log_dir
626
627 def Benchmarks(self):
628 return [b for b in self._benchmarks if b.id in self._args.benchmark]
629
630 def Tag(self):
631 return self._args.tag
632
633 def DryRun(self):
634 return self._args.dry_run
635
LaMont Jones42abe8e2025-02-13 14:43:33 -0800636 def List(self):
637 return self._args.list
638
639 def BenchmarkIds(self) :
640 return [benchmark.id for benchmark in self._benchmarks]
641
Joe Onorato88ede352023-12-19 02:56:38 +0000642 def _lunches(self):
643 def parse_lunch(lunch):
644 parts = lunch.split("-")
645 if len(parts) != 3:
646 raise OptionsError(f"Invalid lunch combo: {lunch}")
647 return Lunch(parts[0], parts[1], parts[2])
648 # If they gave lunch targets on the command line use that
649 if self._args.lunch:
650 result = []
651 # Split into Lunch objects
652 for lunch in self._args.lunch:
653 try:
654 result.append(parse_lunch(lunch))
655 except OptionsError as ex:
656 self._error(ex.message)
657 return result
658 # Use whats in the environment
659 product = os.getenv("TARGET_PRODUCT")
660 release = os.getenv("TARGET_RELEASE")
661 variant = os.getenv("TARGET_BUILD_VARIANT")
662 if (not product) or (not release) or (not variant):
663 # If they didn't give us anything, fail rather than guessing. There's no good
664 # default for AOSP.
665 self._error("No lunch combo specified. Either pass --lunch argument or run lunch.")
666 return []
667 return [Lunch(product, release, variant),]
668
669 def Lunches(self):
670 return self._lunches
671
672 def Iterations(self):
673 return self._args.iterations
674
Joe Onorato05434642023-12-20 05:00:04 +0000675 def DistOne(self):
676 return self._args.dist_one
677
Joe Onorato88ede352023-12-19 02:56:38 +0000678 def _init_benchmarks(self):
679 """Initialize the list of benchmarks."""
680 # Assumes that we've already chdired to the root of the tree.
681 self._benchmarks = [
Liz Kammerefb66502024-01-25 11:08:10 -0500682 Benchmark(
683 id="full_lunch",
684 title="Lunch from clean out",
685 change=Clean(),
686 dumpvars=True,
687 preroll=0,
688 postroll=0,
689 ),
690 Benchmark(
691 id="noop_lunch",
692 title="Lunch with no change",
693 change=NoChange(),
694 dumpvars=True,
695 preroll=1,
696 postroll=0,
697 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000698 Benchmark(id="full",
Joe Onorato01277d42023-12-20 04:00:12 +0000699 title="Full build",
700 change=Clean(),
701 modules=["droid"],
702 preroll=0,
703 postroll=3,
704 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000705 Benchmark(id="nochange",
Joe Onorato01277d42023-12-20 04:00:12 +0000706 title="No change",
707 change=NoChange(),
708 modules=["droid"],
709 preroll=2,
710 postroll=3,
711 ),
712 Benchmark(id="unreferenced",
713 title="Create unreferenced file",
714 change=Create("bionic/unreferenced.txt"),
715 modules=["droid"],
716 preroll=1,
717 postroll=2,
718 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000719 Benchmark(id="modify_bp",
Joe Onorato01277d42023-12-20 04:00:12 +0000720 title="Modify Android.bp",
721 change=Modify("bionic/libc/Android.bp", Comment("//")),
722 modules=["droid"],
723 preroll=1,
724 postroll=3,
725 ),
Joe Onorato685029d2025-03-20 14:40:20 -0700726 Benchmark(id="full_analysis",
727 title="Full Analysis",
728 change=CleanNinja(),
729 modules=["nothing"],
730 preroll=1,
731 postroll=3,
732 ),
Joe Onorato01277d42023-12-20 04:00:12 +0000733 Benchmark(id="modify_stdio",
734 title="Modify stdio.cpp",
735 change=Modify("bionic/libc/stdio/stdio.cpp", Comment("//")),
736 modules=["libc"],
737 preroll=1,
738 postroll=2,
739 ),
740 Benchmark(id="modify_adbd",
741 title="Modify adbd",
742 change=Modify("packages/modules/adb/daemon/main.cpp", Comment("//")),
743 modules=["adbd"],
744 preroll=1,
745 postroll=2,
746 ),
747 Benchmark(id="services_private_field",
748 title="Add private field to ActivityManagerService.java",
749 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
750 "private"),
751 modules=["services"],
752 preroll=1,
753 postroll=2,
754 ),
755 Benchmark(id="services_public_field",
756 title="Add public field to ActivityManagerService.java",
757 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
758 "/** @hide */ public"),
759 modules=["services"],
760 preroll=1,
761 postroll=2,
762 ),
763 Benchmark(id="services_api",
764 title="Add API to ActivityManagerService.javaa",
765 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
766 "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
767 modules=["services"],
768 preroll=1,
769 postroll=2,
770 ),
771 Benchmark(id="framework_private_field",
772 title="Add private field to Settings.java",
773 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
774 "private"),
775 modules=["framework-minus-apex"],
776 preroll=1,
777 postroll=2,
778 ),
779 Benchmark(id="framework_public_field",
780 title="Add public field to Settings.java",
781 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
782 "/** @hide */ public"),
783 modules=["framework-minus-apex"],
784 preroll=1,
785 postroll=2,
786 ),
787 Benchmark(id="framework_api",
788 title="Add API to Settings.java",
Yu Liu473bb052024-02-13 23:46:06 +0000789 change=ChangePublicApi(),
790 modules=["api-stubs-docs-non-updatable-update-current-api", "framework-minus-apex"],
Joe Onorato01277d42023-12-20 04:00:12 +0000791 preroll=1,
792 postroll=2,
793 ),
794 Benchmark(id="modify_framework_resource",
795 title="Modify framework resource",
796 change=Modify("frameworks/base/core/res/res/values/config.xml",
797 lambda: str(uuid.uuid4()),
798 before="</string>"),
799 modules=["framework-minus-apex"],
800 preroll=1,
801 postroll=2,
802 ),
803 Benchmark(id="add_framework_resource",
804 title="Add framework resource",
805 change=Modify("frameworks/base/core/res/res/values/config.xml",
806 lambda: f"<string name=\"BENCHMARK\">{uuid.uuid4()}</string>",
807 before="</resources>"),
808 modules=["framework-minus-apex"],
809 preroll=1,
810 postroll=2,
811 ),
812 Benchmark(id="add_systemui_field",
813 title="Add SystemUI field",
814 change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
815 "public"),
816 modules=["SystemUI"],
817 preroll=1,
818 postroll=2,
819 ),
LaMont Jonesa64ce312025-01-09 17:24:24 -0800820 Benchmark(id="add_systemui_field_with_tests",
821 title="Add SystemUI field with tests",
822 change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
823 "public"),
824 modules=["SystemUiRavenTests"],
825 preroll=1,
826 postroll=2,
827 ),
LaMont Jones3a043082025-01-21 11:04:29 -0800828 Benchmark(id="systemui_flicker_add_log_call",
829 title="Add a Log call to flicker",
830 change=Modify("platform_testing/libraries/flicker/src/android/tools/flicker/FlickerServiceResultsCollector.kt",
831 lambda: f'Log.v(LOG_TAG, "BENCHMARK = {random.randint(0, 1000000)}");\n',
832 before="Log.v(LOG_TAG,"),
833 modules=["WMShellFlickerTestsPip"],
834 preroll=1,
835 postroll=2,
836 ),
837 Benchmark(id="systemui_core_add_log_call",
838 title="Add a Log call SystemUIApplication",
839 change=Modify("frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java",
840 lambda: f'Log.v(TAG, "BENCHMARK = {random.randint(0, 1000000)}");\n',
841 before="Log.wtf(TAG,"),
842 modules=["SystemUI-core"],
843 preroll=1,
844 postroll=2,
845 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000846 ]
847
848 def _error(self, message):
849 report_error(message)
850 self._had_error = True
851
852
853def report_error(message):
854 sys.stderr.write(f"error: {message}\n")
855
856
857def main(argv):
858 try:
859 options = Options()
860 runner = Runner(options)
861 runner.Run()
862 except FatalError:
863 sys.stderr.write(f"FAILED\n")
Yu Liuc6576ad2024-01-16 19:27:45 +0000864 sys.exit(1)
Joe Onorato88ede352023-12-19 02:56:38 +0000865
866
867if __name__ == "__main__":
868 main(sys.argv)