blob: 06e0009b8231439bc52407bce9f261c51609f0a5 [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
Joe Onorato88ede352023-12-19 02:56:38 +000032
33import pretty
34import utils
35
36
37class FatalError(Exception):
38 def __init__(self):
39 pass
40
41
42class OptionsError(Exception):
43 def __init__(self, message):
44 self.message = message
45
46
47@dataclasses.dataclass(frozen=True)
48class Lunch:
49 "Lunch combination"
50
51 target_product: str
52 "TARGET_PRODUCT"
53
54 target_release: str
55 "TARGET_RELEASE"
56
57 target_build_variant: str
58 "TARGET_BUILD_VARIANT"
59
60 def ToDict(self):
61 return {
62 "TARGET_PRODUCT": self.target_product,
63 "TARGET_RELEASE": self.target_release,
64 "TARGET_BUILD_VARIANT": self.target_build_variant,
65 }
66
67 def Combine(self):
68 return f"{self.target_product}-{self.target_release}-{self.target_build_variant}"
69
70
71@dataclasses.dataclass(frozen=True)
72class Change:
73 "A change that we make to the tree, and how to undo it"
74 label: str
75 "String to print in the log when the change is made"
76
77 change: callable
78 "Function to change the source tree"
79
80 undo: callable
81 "Function to revert the source tree to its previous condition in the most minimal way possible."
82
83
84@dataclasses.dataclass(frozen=True)
85class Benchmark:
86 "Something we measure"
87
88 id: str
89 "Short ID for the benchmark, for the command line"
90
91 title: str
92 "Title for reports"
93
94 change: Change
95 "Source tree modification for the benchmark that will be measured"
96
97 modules: list[str]
98 "Build modules to build on soong command line"
99
100 preroll: int
101 "Number of times to run the build command to stabilize"
102
103 postroll: int
104 "Number of times to run the build command after reverting the action to stabilize"
105
106
107@dataclasses.dataclass(frozen=True)
108class FileSnapshot:
109 "Snapshot of a file's contents."
110
111 filename: str
112 "The file that was snapshottened"
113
114 contents: str
115 "The contents of the file"
116
117 def write(self):
118 "Write the contents back to the file"
119 with open(self.filename, "w") as f:
120 f.write(self.contents)
121
122
123def Snapshot(filename):
124 """Return a FileSnapshot with the file's current contents."""
125 with open(filename) as f:
126 contents = f.read()
127 return FileSnapshot(filename, contents)
128
129
130def Clean():
131 """Remove the out directory."""
132 def remove_out():
Yu Liucda84242024-01-17 21:30:53 +0000133 out_dir = utils.get_out_dir()
Liz Kammer864dd432024-01-24 14:09:11 -0500134 #only remove actual contents, in case out is a symlink (as is the case for cog)
Yu Liucda84242024-01-17 21:30:53 +0000135 if os.path.exists(out_dir):
Liz Kammer864dd432024-01-24 14:09:11 -0500136 for filename in os.listdir(out_dir):
137 p = os.path.join(out_dir, filename)
138 if os.path.isfile(p) or os.path.islink(p):
139 os.remove(p)
140 elif os.path.isdir(p):
141 shutil.rmtree(p)
Joe Onorato88ede352023-12-19 02:56:38 +0000142 return Change(label="Remove out", change=remove_out, undo=lambda: None)
143
144
145def NoChange():
146 """No change to the source tree."""
147 return Change(label="No change", change=lambda: None, undo=lambda: None)
148
149
Joe Onorato01277d42023-12-20 04:00:12 +0000150def Create(filename):
151 "Create an action to create `filename`. The parent directory must exist."
152 def create():
153 with open(filename, "w") as f:
154 pass
155 def delete():
156 os.remove(filename)
157 return Change(
158 label=f"Create {filename}",
159 change=create,
160 undo=delete,
161 )
162
163
Joe Onorato88ede352023-12-19 02:56:38 +0000164def Modify(filename, contents, before=None):
Joe Onorato01277d42023-12-20 04:00:12 +0000165 """Create an action to modify `filename` by appending the result of `contents`
166 before the last instances of `before` in the file.
Joe Onorato88ede352023-12-19 02:56:38 +0000167
168 Raises an error if `before` doesn't appear in the file.
169 """
170 orig = Snapshot(filename)
171 if before:
172 index = orig.contents.rfind(before)
173 if index < 0:
174 report_error(f"{filename}: Unable to find string '{before}' for modify operation.")
175 raise FatalError()
176 else:
177 index = len(orig.contents)
Joe Onorato01277d42023-12-20 04:00:12 +0000178 modified = FileSnapshot(filename, orig.contents[:index] + contents() + orig.contents[index:])
179 if False:
180 print(f"Modify: {filename}")
181 x = orig.contents.replace("\n", "\n ORIG")
182 print(f" ORIG {x}")
183 x = modified.contents.replace("\n", "\n MODIFIED")
184 print(f" MODIFIED {x}")
185
Joe Onorato88ede352023-12-19 02:56:38 +0000186 return Change(
187 label="Modify " + filename,
188 change=lambda: modified.write(),
189 undo=lambda: orig.write()
190 )
191
Joe Onorato01277d42023-12-20 04:00:12 +0000192def AddJavaField(filename, prefix):
193 return Modify(filename,
194 lambda: f"{prefix} static final int BENCHMARK = {random.randint(0, 1000000)};\n",
195 before="}")
196
197
198def Comment(prefix, suffix=""):
199 return lambda: prefix + " " + str(uuid.uuid4()) + suffix
200
Joe Onorato88ede352023-12-19 02:56:38 +0000201
202class BenchmarkReport():
203 "Information about a run of the benchmark"
204
205 lunch: Lunch
206 "lunch combo"
207
208 benchmark: Benchmark
209 "The benchmark object."
210
211 iteration: int
212 "Which iteration of the benchmark"
213
214 log_dir: str
215 "Path the the log directory, relative to the root of the reports directory"
216
217 preroll_duration_ns: [int]
218 "Durations of the in nanoseconds."
219
220 duration_ns: int
221 "Duration of the measured portion of the benchmark in nanoseconds."
222
223 postroll_duration_ns: [int]
224 "Durations of the postrolls in nanoseconds."
225
226 complete: bool
227 "Whether the benchmark made it all the way through the postrolls."
228
229 def __init__(self, lunch, benchmark, iteration, log_dir):
230 self.lunch = lunch
231 self.benchmark = benchmark
232 self.iteration = iteration
233 self.log_dir = log_dir
234 self.preroll_duration_ns = []
235 self.duration_ns = -1
236 self.postroll_duration_ns = []
237 self.complete = False
238
239 def ToDict(self):
240 return {
241 "lunch": self.lunch.ToDict(),
242 "id": self.benchmark.id,
243 "title": self.benchmark.title,
244 "modules": self.benchmark.modules,
245 "change": self.benchmark.change.label,
246 "iteration": self.iteration,
247 "log_dir": self.log_dir,
248 "preroll_duration_ns": self.preroll_duration_ns,
249 "duration_ns": self.duration_ns,
250 "postroll_duration_ns": self.postroll_duration_ns,
251 "complete": self.complete,
252 }
253
254class Runner():
255 """Runs the benchmarks."""
256
257 def __init__(self, options):
258 self._options = options
259 self._reports = []
260 self._complete = False
261
262 def Run(self):
263 """Run all of the user-selected benchmarks."""
264 # Clean out the log dir or create it if necessary
265 prepare_log_dir(self._options.LogDir())
266
267 try:
268 for lunch in self._options.Lunches():
269 print(lunch)
270 for benchmark in self._options.Benchmarks():
271 for iteration in range(self._options.Iterations()):
272 self._run_benchmark(lunch, benchmark, iteration)
273 self._complete = True
274 finally:
275 self._write_summary()
276
277
278 def _run_benchmark(self, lunch, benchmark, iteration):
279 """Run a single benchmark."""
Yu Liucda84242024-01-17 21:30:53 +0000280 benchmark_log_subdir = self._benchmark_log_dir(lunch, benchmark, iteration)
Joe Onorato88ede352023-12-19 02:56:38 +0000281 benchmark_log_dir = self._options.LogDir().joinpath(benchmark_log_subdir)
282
283 sys.stderr.write(f"STARTING BENCHMARK: {benchmark.id}\n")
284 sys.stderr.write(f" lunch: {lunch.Combine()}\n")
285 sys.stderr.write(f" iteration: {iteration}\n")
286 sys.stderr.write(f" benchmark_log_dir: {benchmark_log_dir}\n")
287
288 report = BenchmarkReport(lunch, benchmark, iteration, benchmark_log_subdir)
289 self._reports.append(report)
290
291 # Preroll builds
292 for i in range(benchmark.preroll):
293 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"pre_{i}"), benchmark.modules)
294 report.preroll_duration_ns.append(ns)
295
296 sys.stderr.write(f"PERFORMING CHANGE: {benchmark.change.label}\n")
297 if not self._options.DryRun():
298 benchmark.change.change()
299 try:
300
301 # Measured build
302 ns = self._run_build(lunch, benchmark_log_dir.joinpath("measured"), benchmark.modules)
303 report.duration_ns = ns
304
Joe Onorato05434642023-12-20 05:00:04 +0000305 dist_one = self._options.DistOne()
306 if dist_one:
307 # If we're disting just one benchmark, save the logs and we can stop here.
Yu Liucda84242024-01-17 21:30:53 +0000308 self._dist(utils.get_dist_dir())
Joe Onorato05434642023-12-20 05:00:04 +0000309 else:
310 # Postroll builds
Liz Kammerf67a6e82024-01-25 11:06:40 -0500311 for i in range(benchmark.postroll):
Joe Onorato05434642023-12-20 05:00:04 +0000312 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"post_{i}"),
313 benchmark.modules)
314 report.postroll_duration_ns.append(ns)
Joe Onorato88ede352023-12-19 02:56:38 +0000315
316 finally:
317 # Always undo, even if we crashed or the build failed and we stopped.
318 sys.stderr.write(f"UNDOING CHANGE: {benchmark.change.label}\n")
319 if not self._options.DryRun():
320 benchmark.change.undo()
321
322 self._write_summary()
323 sys.stderr.write(f"FINISHED BENCHMARK: {benchmark.id}\n")
324
Yu Liucda84242024-01-17 21:30:53 +0000325 def _benchmark_log_dir(self, lunch, benchmark, iteration):
Joe Onorato88ede352023-12-19 02:56:38 +0000326 """Construct the log directory fir a benchmark run."""
327 path = f"{lunch.Combine()}/{benchmark.id}"
328 # Zero pad to the correct length for correct alpha sorting
329 path += ("/%0" + str(len(str(self._options.Iterations()))) + "d") % iteration
330 return path
331
332 def _run_build(self, lunch, build_log_dir, modules):
333 """Builds the modules. Saves interesting log files to log_dir. Raises FatalError
334 if the build fails.
335 """
336 sys.stderr.write(f"STARTING BUILD {modules}\n")
337
338 before_ns = time.perf_counter_ns()
339 if not self._options.DryRun():
340 cmd = [
341 "build/soong/soong_ui.bash",
342 "--build-mode",
343 "--all-modules",
344 f"--dir={self._options.root}",
Joe Onorato7eba1582024-01-03 10:28:41 -0800345 "--skip-metrics-upload",
Joe Onorato88ede352023-12-19 02:56:38 +0000346 ] + modules
347 env = dict(os.environ)
348 env["TARGET_PRODUCT"] = lunch.target_product
349 env["TARGET_RELEASE"] = lunch.target_release
350 env["TARGET_BUILD_VARIANT"] = lunch.target_build_variant
351 returncode = subprocess.call(cmd, env=env)
352 if returncode != 0:
353 report_error(f"Build failed: {' '.join(cmd)}")
354 raise FatalError()
355
356 after_ns = time.perf_counter_ns()
357
358 # TODO: Copy some log files.
359
360 sys.stderr.write(f"FINISHED BUILD {modules}\n")
361
362 return after_ns - before_ns
363
Joe Onorato05434642023-12-20 05:00:04 +0000364 def _dist(self, dist_dir):
Yu Liucda84242024-01-17 21:30:53 +0000365 out_dir = utils.get_out_dir()
366 dest_dir = dist_dir.joinpath("logs")
Joe Onorato05434642023-12-20 05:00:04 +0000367 os.makedirs(dest_dir, exist_ok=True)
368 basenames = [
369 "build.trace.gz",
370 "soong.log",
371 "soong_build_metrics.pb",
372 "soong_metrics",
373 ]
374 for base in basenames:
375 src = out_dir.joinpath(base)
376 if src.exists():
377 sys.stderr.write(f"DIST: copied {src} to {dest_dir}\n")
378 shutil.copy(src, dest_dir)
379
Joe Onorato88ede352023-12-19 02:56:38 +0000380 def _write_summary(self):
381 # Write the results, even if the build failed or we crashed, including
382 # whether we finished all of the benchmarks.
383 data = {
384 "start_time": self._options.Timestamp().isoformat(),
385 "branch": self._options.Branch(),
386 "tag": self._options.Tag(),
387 "benchmarks": [report.ToDict() for report in self._reports],
388 "complete": self._complete,
389 }
390 with open(self._options.LogDir().joinpath("summary.json"), "w", encoding="utf-8") as f:
391 json.dump(data, f, indent=2, sort_keys=True)
392
393
394def benchmark_table(benchmarks):
395 rows = [("ID", "DESCRIPTION", "REBUILD"),]
396 rows += [(benchmark.id, benchmark.title, " ".join(benchmark.modules)) for benchmark in
397 benchmarks]
398 return rows
399
400
401def prepare_log_dir(directory):
402 if os.path.exists(directory):
403 # If it exists and isn't a directory, fail.
404 if not os.path.isdir(directory):
405 report_error(f"Log directory already exists but isn't a directory: {directory}")
406 raise FatalError()
407 # Make sure the directory is empty. Do this rather than deleting it to handle
408 # symlinks cleanly.
409 for filename in os.listdir(directory):
410 entry = os.path.join(directory, filename)
411 if os.path.isdir(entry):
412 shutil.rmtree(entry)
413 else:
414 os.unlink(entry)
415 else:
416 # Create it
417 os.makedirs(directory)
418
419
420class Options():
421 def __init__(self):
422 self._had_error = False
423
424 # Wall time clock when we started
425 self._timestamp = datetime.datetime.now(datetime.timezone.utc)
426
427 # Move to the root of the tree right away. Everything must happen from there.
428 self.root = utils.get_root()
429 if not self.root:
430 report_error("Unable to find root of tree from cwd.")
431 raise FatalError()
432 os.chdir(self.root)
433
434 # Initialize the Benchmarks. Note that this pre-loads all of the files, etc.
435 # Doing all that here forces us to fail fast if one of them can't load a required
436 # file, at the cost of a small startup speed. Don't make this do something slow
437 # like scan the whole tree.
438 self._init_benchmarks()
439
440 # Argument parsing
441 epilog = f"""
442benchmarks:
443{pretty.FormatTable(benchmark_table(self._benchmarks), prefix=" ")}
444"""
445
446 parser = argparse.ArgumentParser(
447 prog="benchmarks",
448 allow_abbrev=False, # Don't let people write unsupportable scripts.
449 formatter_class=argparse.RawDescriptionHelpFormatter,
450 epilog=epilog,
451 description="Run build system performance benchmarks.")
452 self.parser = parser
453
454 parser.add_argument("--log-dir",
455 help="Directory for logs. Default is $TOP/../benchmarks/.")
456 parser.add_argument("--dated-logs", action="store_true",
457 help="Append timestamp to log dir.")
458 parser.add_argument("-n", action="store_true", dest="dry_run",
459 help="Dry run. Don't run the build commands but do everything else.")
460 parser.add_argument("--tag",
461 help="Variant of the run, for when there are multiple perf runs.")
462 parser.add_argument("--lunch", nargs="*",
463 help="Lunch combos to test")
464 parser.add_argument("--iterations", type=int, default=1,
465 help="Number of iterations of each test to run.")
466 parser.add_argument("--branch", type=str,
467 help="Specify branch. Otherwise a guess will be made based on repo.")
468 parser.add_argument("--benchmark", nargs="*", default=[b.id for b in self._benchmarks],
469 metavar="BENCHMARKS",
470 help="Benchmarks to run. Default suite will be run if omitted.")
Joe Onorato60c36ad2024-01-02 07:32:54 +0000471 parser.add_argument("--dist-one", action="store_true",
Joe Onorato05434642023-12-20 05:00:04 +0000472 help="Copy logs and metrics to the given dist dir. Requires that only"
473 + " one benchmark be supplied. Postroll steps will be skipped.")
Joe Onorato88ede352023-12-19 02:56:38 +0000474
475 self._args = parser.parse_args()
476
477 self._branch = self._branch()
478 self._log_dir = self._log_dir()
479 self._lunches = self._lunches()
480
481 # Validate the benchmark ids
482 all_ids = [benchmark.id for benchmark in self._benchmarks]
483 bad_ids = [id for id in self._args.benchmark if id not in all_ids]
484 if bad_ids:
485 for id in bad_ids:
486 self._error(f"Invalid benchmark: {id}")
487
Joe Onorato05434642023-12-20 05:00:04 +0000488 # --dist-one requires that only one benchmark be supplied
Joe Onorato60c36ad2024-01-02 07:32:54 +0000489 if self._args.dist_one and len(self.Benchmarks()) != 1:
Joe Onorato05434642023-12-20 05:00:04 +0000490 self._error("--dist-one requires that exactly one --benchmark.")
491
Joe Onorato88ede352023-12-19 02:56:38 +0000492 if self._had_error:
493 raise FatalError()
494
495 def Timestamp(self):
496 return self._timestamp
497
498 def _branch(self):
499 """Return the branch, either from the command line or by guessing from repo."""
500 if self._args.branch:
501 return self._args.branch
502 try:
503 branch = subprocess.check_output(f"cd {self.root}/.repo/manifests"
504 + " && git rev-parse --abbrev-ref --symbolic-full-name @{u}",
505 shell=True, encoding="utf-8")
506 return branch.strip().split("/")[-1]
507 except subprocess.CalledProcessError as ex:
508 report_error("Can't get branch from .repo dir. Specify --branch argument")
509 report_error(str(ex))
510 raise FatalError()
511
512 def Branch(self):
513 return self._branch
514
515 def _log_dir(self):
516 "The log directory to use, based on the current options"
517 if self._args.log_dir:
518 d = pathlib.Path(self._args.log_dir).resolve().absolute()
519 else:
520 d = self.root.joinpath("..", utils.DEFAULT_REPORT_DIR)
521 if self._args.dated_logs:
522 d = d.joinpath(self._timestamp.strftime('%Y-%m-%d'))
523 d = d.joinpath(self._branch)
524 if self._args.tag:
525 d = d.joinpath(self._args.tag)
526 return d.resolve().absolute()
527
528 def LogDir(self):
529 return self._log_dir
530
531 def Benchmarks(self):
532 return [b for b in self._benchmarks if b.id in self._args.benchmark]
533
534 def Tag(self):
535 return self._args.tag
536
537 def DryRun(self):
538 return self._args.dry_run
539
540 def _lunches(self):
541 def parse_lunch(lunch):
542 parts = lunch.split("-")
543 if len(parts) != 3:
544 raise OptionsError(f"Invalid lunch combo: {lunch}")
545 return Lunch(parts[0], parts[1], parts[2])
546 # If they gave lunch targets on the command line use that
547 if self._args.lunch:
548 result = []
549 # Split into Lunch objects
550 for lunch in self._args.lunch:
551 try:
552 result.append(parse_lunch(lunch))
553 except OptionsError as ex:
554 self._error(ex.message)
555 return result
556 # Use whats in the environment
557 product = os.getenv("TARGET_PRODUCT")
558 release = os.getenv("TARGET_RELEASE")
559 variant = os.getenv("TARGET_BUILD_VARIANT")
560 if (not product) or (not release) or (not variant):
561 # If they didn't give us anything, fail rather than guessing. There's no good
562 # default for AOSP.
563 self._error("No lunch combo specified. Either pass --lunch argument or run lunch.")
564 return []
565 return [Lunch(product, release, variant),]
566
567 def Lunches(self):
568 return self._lunches
569
570 def Iterations(self):
571 return self._args.iterations
572
Joe Onorato05434642023-12-20 05:00:04 +0000573 def DistOne(self):
574 return self._args.dist_one
575
Joe Onorato88ede352023-12-19 02:56:38 +0000576 def _init_benchmarks(self):
577 """Initialize the list of benchmarks."""
578 # Assumes that we've already chdired to the root of the tree.
579 self._benchmarks = [
580 Benchmark(id="full",
Joe Onorato01277d42023-12-20 04:00:12 +0000581 title="Full build",
582 change=Clean(),
583 modules=["droid"],
584 preroll=0,
585 postroll=3,
586 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000587 Benchmark(id="nochange",
Joe Onorato01277d42023-12-20 04:00:12 +0000588 title="No change",
589 change=NoChange(),
590 modules=["droid"],
591 preroll=2,
592 postroll=3,
593 ),
594 Benchmark(id="unreferenced",
595 title="Create unreferenced file",
596 change=Create("bionic/unreferenced.txt"),
597 modules=["droid"],
598 preroll=1,
599 postroll=2,
600 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000601 Benchmark(id="modify_bp",
Joe Onorato01277d42023-12-20 04:00:12 +0000602 title="Modify Android.bp",
603 change=Modify("bionic/libc/Android.bp", Comment("//")),
604 modules=["droid"],
605 preroll=1,
606 postroll=3,
607 ),
608 Benchmark(id="modify_stdio",
609 title="Modify stdio.cpp",
610 change=Modify("bionic/libc/stdio/stdio.cpp", Comment("//")),
611 modules=["libc"],
612 preroll=1,
613 postroll=2,
614 ),
615 Benchmark(id="modify_adbd",
616 title="Modify adbd",
617 change=Modify("packages/modules/adb/daemon/main.cpp", Comment("//")),
618 modules=["adbd"],
619 preroll=1,
620 postroll=2,
621 ),
622 Benchmark(id="services_private_field",
623 title="Add private field to ActivityManagerService.java",
624 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
625 "private"),
626 modules=["services"],
627 preroll=1,
628 postroll=2,
629 ),
630 Benchmark(id="services_public_field",
631 title="Add public field to ActivityManagerService.java",
632 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
633 "/** @hide */ public"),
634 modules=["services"],
635 preroll=1,
636 postroll=2,
637 ),
638 Benchmark(id="services_api",
639 title="Add API to ActivityManagerService.javaa",
640 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
641 "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
642 modules=["services"],
643 preroll=1,
644 postroll=2,
645 ),
646 Benchmark(id="framework_private_field",
647 title="Add private field to Settings.java",
648 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
649 "private"),
650 modules=["framework-minus-apex"],
651 preroll=1,
652 postroll=2,
653 ),
654 Benchmark(id="framework_public_field",
655 title="Add public field to Settings.java",
656 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
657 "/** @hide */ public"),
658 modules=["framework-minus-apex"],
659 preroll=1,
660 postroll=2,
661 ),
662 Benchmark(id="framework_api",
663 title="Add API to Settings.java",
664 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
665 "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
666 modules=["framework-minus-apex"],
667 preroll=1,
668 postroll=2,
669 ),
670 Benchmark(id="modify_framework_resource",
671 title="Modify framework resource",
672 change=Modify("frameworks/base/core/res/res/values/config.xml",
673 lambda: str(uuid.uuid4()),
674 before="</string>"),
675 modules=["framework-minus-apex"],
676 preroll=1,
677 postroll=2,
678 ),
679 Benchmark(id="add_framework_resource",
680 title="Add framework resource",
681 change=Modify("frameworks/base/core/res/res/values/config.xml",
682 lambda: f"<string name=\"BENCHMARK\">{uuid.uuid4()}</string>",
683 before="</resources>"),
684 modules=["framework-minus-apex"],
685 preroll=1,
686 postroll=2,
687 ),
688 Benchmark(id="add_systemui_field",
689 title="Add SystemUI field",
690 change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
691 "public"),
692 modules=["SystemUI"],
693 preroll=1,
694 postroll=2,
695 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000696 ]
697
698 def _error(self, message):
699 report_error(message)
700 self._had_error = True
701
702
703def report_error(message):
704 sys.stderr.write(f"error: {message}\n")
705
706
707def main(argv):
708 try:
709 options = Options()
710 runner = Runner(options)
711 runner.Run()
712 except FatalError:
713 sys.stderr.write(f"FAILED\n")
Yu Liuc6576ad2024-01-16 19:27:45 +0000714 sys.exit(1)
Joe Onorato88ede352023-12-19 02:56:38 +0000715
716
717if __name__ == "__main__":
718 main(sys.argv)