1#!/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 random 27import re 28import shutil 29import subprocess 30import time 31import uuid 32from typing import Optional 33 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 84_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] 111 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 125 dumpvars: Optional[bool] = False 126 "If specified, soong will run in dumpvars mode rather than build-mode." 127 128 modules: Optional[list[str]] = None 129 "Build modules to build on soong command line" 130 131 preroll: Optional[int] = 0 132 "Number of times to run the build command to stabilize" 133 134 postroll: Optional[int] = 3 135 "Number of times to run the build command after reverting the action to stabilize" 136 137 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 166 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(): 193 out_dir = utils.get_out_dir() 194 #only remove actual contents, in case out is a symlink (as is the case for cog) 195 if os.path.exists(out_dir): 196 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) 202 return Change(label="Remove out", change=remove_out, undo=lambda: None) 203 204 205def 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 215def NoChange(): 216 """No change to the source tree.""" 217 return Change(label="No change", change=lambda: None, undo=lambda: None) 218 219 220def 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 234def Modify(filename, contents, before=None): 235 """Create an action to modify `filename` by appending the result of `contents` 236 before the last instances of `before` in the file. 237 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) 248 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 256 return Change( 257 label="Modify " + filename, 258 change=lambda: modified.write(), 259 undo=lambda: orig.write() 260 ) 261 262def 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 277def 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 286 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, 330 "dumpvars": self.benchmark.dumpvars, 331 "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.""" 350 351 # With `--list`, just list the benchmarks available. 352 if self._options.List(): 353 print(" ".join(self._options.BenchmarkIds())) 354 return 355 356 # 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.""" 372 benchmark_log_subdir = self._benchmark_log_dir(lunch, benchmark, iteration) 373 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): 385 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"pre_{i}"), benchmark) 386 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 394 ns = self._run_build(lunch, benchmark_log_dir.joinpath("measured"), benchmark) 395 report.duration_ns = ns 396 397 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. 400 self._dist(utils.get_dist_dir(), benchmark.dumpvars) 401 else: 402 self._dist(benchmark_log_dir, benchmark.dumpvars, store_metrics_only=True) 403 # Postroll builds 404 for i in range(benchmark.postroll): 405 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"post_{i}"), 406 benchmark) 407 report.postroll_duration_ns.append(ns) 408 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 418 def _benchmark_log_dir(self, lunch, benchmark, iteration): 419 """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 425 def _run_build(self, lunch, build_log_dir, benchmark): 426 """Builds the modules. Saves interesting log files to log_dir. Raises FatalError 427 if the build fails. 428 """ 429 sys.stderr.write(f"STARTING BUILD {benchmark.build_description()} Logs to: {build_log_dir}\n") 430 431 before_ns = time.perf_counter_ns() 432 if not self._options.DryRun(): 433 cmd = [ 434 "build/soong/soong_ui.bash", 435 ] + benchmark.soong_command(self._options.root) 436 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 449 sys.stderr.write(f"FINISHED BUILD {benchmark.build_description()}\n") 450 451 return after_ns - before_ns 452 453 def _dist(self, dist_dir, dumpvars, store_metrics_only=False): 454 out_dir = utils.get_out_dir() 455 dest_dir = dist_dir.joinpath("logs") 456 os.makedirs(dest_dir, exist_ok=True) 457 basenames = [ 458 "soong_build_metrics.pb", 459 "soong_metrics", 460 ] 461 if not store_metrics_only: 462 basenames.extend([ 463 "build.trace.gz", 464 "soong.log", 465 ]) 466 if dumpvars: 467 basenames = ['dumpvars-'+b for b in basenames] 468 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 474 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"),] 490 rows += [(benchmark.id, benchmark.title, benchmark.build_description()) for benchmark in 491 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.") 565 parser.add_argument("--list", action="store_true", 566 help="list the available benchmarks. No benchmark is run.") 567 parser.add_argument("--dist-one", action="store_true", 568 help="Copy logs and metrics to the given dist dir. Requires that only" 569 + " one benchmark be supplied. Postroll steps will be skipped.") 570 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 584 # --dist-one requires that only one benchmark be supplied 585 if self._args.dist_one and len(self.Benchmarks()) != 1: 586 self._error("--dist-one requires exactly one --benchmark.") 587 588 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 636 def List(self): 637 return self._args.list 638 639 def BenchmarkIds(self) : 640 return [benchmark.id for benchmark in self._benchmarks] 641 642 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 675 def DistOne(self): 676 return self._args.dist_one 677 678 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 = [ 682 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 ), 698 Benchmark(id="full", 699 title="Full build", 700 change=Clean(), 701 modules=["droid"], 702 preroll=0, 703 postroll=3, 704 ), 705 Benchmark(id="nochange", 706 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 ), 719 Benchmark(id="modify_bp", 720 title="Modify Android.bp", 721 change=Modify("bionic/libc/Android.bp", Comment("//")), 722 modules=["droid"], 723 preroll=1, 724 postroll=3, 725 ), 726 Benchmark(id="full_analysis", 727 title="Full Analysis", 728 change=CleanNinja(), 729 modules=["nothing"], 730 preroll=1, 731 postroll=3, 732 ), 733 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", 789 change=ChangePublicApi(), 790 modules=["api-stubs-docs-non-updatable-update-current-api", "framework-minus-apex"], 791 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 ), 820 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 ), 828 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 ), 846 ] 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") 864 sys.exit(1) 865 866 867if __name__ == "__main__": 868 main(sys.argv) 869