blob: 61bf88843163ae8ecfc4bfd6afd167bc203b820e [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()
134 if os.path.exists(out_dir):
135 shutil.rmtree(out_dir)
Joe Onorato88ede352023-12-19 02:56:38 +0000136 return Change(label="Remove out", change=remove_out, undo=lambda: None)
137
138
139def NoChange():
140 """No change to the source tree."""
141 return Change(label="No change", change=lambda: None, undo=lambda: None)
142
143
Joe Onorato01277d42023-12-20 04:00:12 +0000144def Create(filename):
145 "Create an action to create `filename`. The parent directory must exist."
146 def create():
147 with open(filename, "w") as f:
148 pass
149 def delete():
150 os.remove(filename)
151 return Change(
152 label=f"Create {filename}",
153 change=create,
154 undo=delete,
155 )
156
157
Joe Onorato88ede352023-12-19 02:56:38 +0000158def Modify(filename, contents, before=None):
Joe Onorato01277d42023-12-20 04:00:12 +0000159 """Create an action to modify `filename` by appending the result of `contents`
160 before the last instances of `before` in the file.
Joe Onorato88ede352023-12-19 02:56:38 +0000161
162 Raises an error if `before` doesn't appear in the file.
163 """
164 orig = Snapshot(filename)
165 if before:
166 index = orig.contents.rfind(before)
167 if index < 0:
168 report_error(f"{filename}: Unable to find string '{before}' for modify operation.")
169 raise FatalError()
170 else:
171 index = len(orig.contents)
Joe Onorato01277d42023-12-20 04:00:12 +0000172 modified = FileSnapshot(filename, orig.contents[:index] + contents() + orig.contents[index:])
173 if False:
174 print(f"Modify: {filename}")
175 x = orig.contents.replace("\n", "\n ORIG")
176 print(f" ORIG {x}")
177 x = modified.contents.replace("\n", "\n MODIFIED")
178 print(f" MODIFIED {x}")
179
Joe Onorato88ede352023-12-19 02:56:38 +0000180 return Change(
181 label="Modify " + filename,
182 change=lambda: modified.write(),
183 undo=lambda: orig.write()
184 )
185
Joe Onorato01277d42023-12-20 04:00:12 +0000186def AddJavaField(filename, prefix):
187 return Modify(filename,
188 lambda: f"{prefix} static final int BENCHMARK = {random.randint(0, 1000000)};\n",
189 before="}")
190
191
192def Comment(prefix, suffix=""):
193 return lambda: prefix + " " + str(uuid.uuid4()) + suffix
194
Joe Onorato88ede352023-12-19 02:56:38 +0000195
196class BenchmarkReport():
197 "Information about a run of the benchmark"
198
199 lunch: Lunch
200 "lunch combo"
201
202 benchmark: Benchmark
203 "The benchmark object."
204
205 iteration: int
206 "Which iteration of the benchmark"
207
208 log_dir: str
209 "Path the the log directory, relative to the root of the reports directory"
210
211 preroll_duration_ns: [int]
212 "Durations of the in nanoseconds."
213
214 duration_ns: int
215 "Duration of the measured portion of the benchmark in nanoseconds."
216
217 postroll_duration_ns: [int]
218 "Durations of the postrolls in nanoseconds."
219
220 complete: bool
221 "Whether the benchmark made it all the way through the postrolls."
222
223 def __init__(self, lunch, benchmark, iteration, log_dir):
224 self.lunch = lunch
225 self.benchmark = benchmark
226 self.iteration = iteration
227 self.log_dir = log_dir
228 self.preroll_duration_ns = []
229 self.duration_ns = -1
230 self.postroll_duration_ns = []
231 self.complete = False
232
233 def ToDict(self):
234 return {
235 "lunch": self.lunch.ToDict(),
236 "id": self.benchmark.id,
237 "title": self.benchmark.title,
238 "modules": self.benchmark.modules,
239 "change": self.benchmark.change.label,
240 "iteration": self.iteration,
241 "log_dir": self.log_dir,
242 "preroll_duration_ns": self.preroll_duration_ns,
243 "duration_ns": self.duration_ns,
244 "postroll_duration_ns": self.postroll_duration_ns,
245 "complete": self.complete,
246 }
247
248class Runner():
249 """Runs the benchmarks."""
250
251 def __init__(self, options):
252 self._options = options
253 self._reports = []
254 self._complete = False
255
256 def Run(self):
257 """Run all of the user-selected benchmarks."""
258 # Clean out the log dir or create it if necessary
259 prepare_log_dir(self._options.LogDir())
260
261 try:
262 for lunch in self._options.Lunches():
263 print(lunch)
264 for benchmark in self._options.Benchmarks():
265 for iteration in range(self._options.Iterations()):
266 self._run_benchmark(lunch, benchmark, iteration)
267 self._complete = True
268 finally:
269 self._write_summary()
270
271
272 def _run_benchmark(self, lunch, benchmark, iteration):
273 """Run a single benchmark."""
Yu Liucda84242024-01-17 21:30:53 +0000274 benchmark_log_subdir = self._benchmark_log_dir(lunch, benchmark, iteration)
Joe Onorato88ede352023-12-19 02:56:38 +0000275 benchmark_log_dir = self._options.LogDir().joinpath(benchmark_log_subdir)
276
277 sys.stderr.write(f"STARTING BENCHMARK: {benchmark.id}\n")
278 sys.stderr.write(f" lunch: {lunch.Combine()}\n")
279 sys.stderr.write(f" iteration: {iteration}\n")
280 sys.stderr.write(f" benchmark_log_dir: {benchmark_log_dir}\n")
281
282 report = BenchmarkReport(lunch, benchmark, iteration, benchmark_log_subdir)
283 self._reports.append(report)
284
285 # Preroll builds
286 for i in range(benchmark.preroll):
287 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"pre_{i}"), benchmark.modules)
288 report.preroll_duration_ns.append(ns)
289
290 sys.stderr.write(f"PERFORMING CHANGE: {benchmark.change.label}\n")
291 if not self._options.DryRun():
292 benchmark.change.change()
293 try:
294
295 # Measured build
296 ns = self._run_build(lunch, benchmark_log_dir.joinpath("measured"), benchmark.modules)
297 report.duration_ns = ns
298
Joe Onorato05434642023-12-20 05:00:04 +0000299 dist_one = self._options.DistOne()
300 if dist_one:
301 # If we're disting just one benchmark, save the logs and we can stop here.
Yu Liucda84242024-01-17 21:30:53 +0000302 self._dist(utils.get_dist_dir())
Joe Onorato05434642023-12-20 05:00:04 +0000303 else:
304 # Postroll builds
Liz Kammerf67a6e82024-01-25 11:06:40 -0500305 for i in range(benchmark.postroll):
Joe Onorato05434642023-12-20 05:00:04 +0000306 ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"post_{i}"),
307 benchmark.modules)
308 report.postroll_duration_ns.append(ns)
Joe Onorato88ede352023-12-19 02:56:38 +0000309
310 finally:
311 # Always undo, even if we crashed or the build failed and we stopped.
312 sys.stderr.write(f"UNDOING CHANGE: {benchmark.change.label}\n")
313 if not self._options.DryRun():
314 benchmark.change.undo()
315
316 self._write_summary()
317 sys.stderr.write(f"FINISHED BENCHMARK: {benchmark.id}\n")
318
Yu Liucda84242024-01-17 21:30:53 +0000319 def _benchmark_log_dir(self, lunch, benchmark, iteration):
Joe Onorato88ede352023-12-19 02:56:38 +0000320 """Construct the log directory fir a benchmark run."""
321 path = f"{lunch.Combine()}/{benchmark.id}"
322 # Zero pad to the correct length for correct alpha sorting
323 path += ("/%0" + str(len(str(self._options.Iterations()))) + "d") % iteration
324 return path
325
326 def _run_build(self, lunch, build_log_dir, modules):
327 """Builds the modules. Saves interesting log files to log_dir. Raises FatalError
328 if the build fails.
329 """
330 sys.stderr.write(f"STARTING BUILD {modules}\n")
331
332 before_ns = time.perf_counter_ns()
333 if not self._options.DryRun():
334 cmd = [
335 "build/soong/soong_ui.bash",
336 "--build-mode",
337 "--all-modules",
338 f"--dir={self._options.root}",
Joe Onorato7eba1582024-01-03 10:28:41 -0800339 "--skip-metrics-upload",
Joe Onorato88ede352023-12-19 02:56:38 +0000340 ] + modules
341 env = dict(os.environ)
342 env["TARGET_PRODUCT"] = lunch.target_product
343 env["TARGET_RELEASE"] = lunch.target_release
344 env["TARGET_BUILD_VARIANT"] = lunch.target_build_variant
345 returncode = subprocess.call(cmd, env=env)
346 if returncode != 0:
347 report_error(f"Build failed: {' '.join(cmd)}")
348 raise FatalError()
349
350 after_ns = time.perf_counter_ns()
351
352 # TODO: Copy some log files.
353
354 sys.stderr.write(f"FINISHED BUILD {modules}\n")
355
356 return after_ns - before_ns
357
Joe Onorato05434642023-12-20 05:00:04 +0000358 def _dist(self, dist_dir):
Yu Liucda84242024-01-17 21:30:53 +0000359 out_dir = utils.get_out_dir()
360 dest_dir = dist_dir.joinpath("logs")
Joe Onorato05434642023-12-20 05:00:04 +0000361 os.makedirs(dest_dir, exist_ok=True)
362 basenames = [
363 "build.trace.gz",
364 "soong.log",
365 "soong_build_metrics.pb",
366 "soong_metrics",
367 ]
368 for base in basenames:
369 src = out_dir.joinpath(base)
370 if src.exists():
371 sys.stderr.write(f"DIST: copied {src} to {dest_dir}\n")
372 shutil.copy(src, dest_dir)
373
Joe Onorato88ede352023-12-19 02:56:38 +0000374 def _write_summary(self):
375 # Write the results, even if the build failed or we crashed, including
376 # whether we finished all of the benchmarks.
377 data = {
378 "start_time": self._options.Timestamp().isoformat(),
379 "branch": self._options.Branch(),
380 "tag": self._options.Tag(),
381 "benchmarks": [report.ToDict() for report in self._reports],
382 "complete": self._complete,
383 }
384 with open(self._options.LogDir().joinpath("summary.json"), "w", encoding="utf-8") as f:
385 json.dump(data, f, indent=2, sort_keys=True)
386
387
388def benchmark_table(benchmarks):
389 rows = [("ID", "DESCRIPTION", "REBUILD"),]
390 rows += [(benchmark.id, benchmark.title, " ".join(benchmark.modules)) for benchmark in
391 benchmarks]
392 return rows
393
394
395def prepare_log_dir(directory):
396 if os.path.exists(directory):
397 # If it exists and isn't a directory, fail.
398 if not os.path.isdir(directory):
399 report_error(f"Log directory already exists but isn't a directory: {directory}")
400 raise FatalError()
401 # Make sure the directory is empty. Do this rather than deleting it to handle
402 # symlinks cleanly.
403 for filename in os.listdir(directory):
404 entry = os.path.join(directory, filename)
405 if os.path.isdir(entry):
406 shutil.rmtree(entry)
407 else:
408 os.unlink(entry)
409 else:
410 # Create it
411 os.makedirs(directory)
412
413
414class Options():
415 def __init__(self):
416 self._had_error = False
417
418 # Wall time clock when we started
419 self._timestamp = datetime.datetime.now(datetime.timezone.utc)
420
421 # Move to the root of the tree right away. Everything must happen from there.
422 self.root = utils.get_root()
423 if not self.root:
424 report_error("Unable to find root of tree from cwd.")
425 raise FatalError()
426 os.chdir(self.root)
427
428 # Initialize the Benchmarks. Note that this pre-loads all of the files, etc.
429 # Doing all that here forces us to fail fast if one of them can't load a required
430 # file, at the cost of a small startup speed. Don't make this do something slow
431 # like scan the whole tree.
432 self._init_benchmarks()
433
434 # Argument parsing
435 epilog = f"""
436benchmarks:
437{pretty.FormatTable(benchmark_table(self._benchmarks), prefix=" ")}
438"""
439
440 parser = argparse.ArgumentParser(
441 prog="benchmarks",
442 allow_abbrev=False, # Don't let people write unsupportable scripts.
443 formatter_class=argparse.RawDescriptionHelpFormatter,
444 epilog=epilog,
445 description="Run build system performance benchmarks.")
446 self.parser = parser
447
448 parser.add_argument("--log-dir",
449 help="Directory for logs. Default is $TOP/../benchmarks/.")
450 parser.add_argument("--dated-logs", action="store_true",
451 help="Append timestamp to log dir.")
452 parser.add_argument("-n", action="store_true", dest="dry_run",
453 help="Dry run. Don't run the build commands but do everything else.")
454 parser.add_argument("--tag",
455 help="Variant of the run, for when there are multiple perf runs.")
456 parser.add_argument("--lunch", nargs="*",
457 help="Lunch combos to test")
458 parser.add_argument("--iterations", type=int, default=1,
459 help="Number of iterations of each test to run.")
460 parser.add_argument("--branch", type=str,
461 help="Specify branch. Otherwise a guess will be made based on repo.")
462 parser.add_argument("--benchmark", nargs="*", default=[b.id for b in self._benchmarks],
463 metavar="BENCHMARKS",
464 help="Benchmarks to run. Default suite will be run if omitted.")
Joe Onorato60c36ad2024-01-02 07:32:54 +0000465 parser.add_argument("--dist-one", action="store_true",
Joe Onorato05434642023-12-20 05:00:04 +0000466 help="Copy logs and metrics to the given dist dir. Requires that only"
467 + " one benchmark be supplied. Postroll steps will be skipped.")
Joe Onorato88ede352023-12-19 02:56:38 +0000468
469 self._args = parser.parse_args()
470
471 self._branch = self._branch()
472 self._log_dir = self._log_dir()
473 self._lunches = self._lunches()
474
475 # Validate the benchmark ids
476 all_ids = [benchmark.id for benchmark in self._benchmarks]
477 bad_ids = [id for id in self._args.benchmark if id not in all_ids]
478 if bad_ids:
479 for id in bad_ids:
480 self._error(f"Invalid benchmark: {id}")
481
Joe Onorato05434642023-12-20 05:00:04 +0000482 # --dist-one requires that only one benchmark be supplied
Joe Onorato60c36ad2024-01-02 07:32:54 +0000483 if self._args.dist_one and len(self.Benchmarks()) != 1:
Joe Onorato05434642023-12-20 05:00:04 +0000484 self._error("--dist-one requires that exactly one --benchmark.")
485
Joe Onorato88ede352023-12-19 02:56:38 +0000486 if self._had_error:
487 raise FatalError()
488
489 def Timestamp(self):
490 return self._timestamp
491
492 def _branch(self):
493 """Return the branch, either from the command line or by guessing from repo."""
494 if self._args.branch:
495 return self._args.branch
496 try:
497 branch = subprocess.check_output(f"cd {self.root}/.repo/manifests"
498 + " && git rev-parse --abbrev-ref --symbolic-full-name @{u}",
499 shell=True, encoding="utf-8")
500 return branch.strip().split("/")[-1]
501 except subprocess.CalledProcessError as ex:
502 report_error("Can't get branch from .repo dir. Specify --branch argument")
503 report_error(str(ex))
504 raise FatalError()
505
506 def Branch(self):
507 return self._branch
508
509 def _log_dir(self):
510 "The log directory to use, based on the current options"
511 if self._args.log_dir:
512 d = pathlib.Path(self._args.log_dir).resolve().absolute()
513 else:
514 d = self.root.joinpath("..", utils.DEFAULT_REPORT_DIR)
515 if self._args.dated_logs:
516 d = d.joinpath(self._timestamp.strftime('%Y-%m-%d'))
517 d = d.joinpath(self._branch)
518 if self._args.tag:
519 d = d.joinpath(self._args.tag)
520 return d.resolve().absolute()
521
522 def LogDir(self):
523 return self._log_dir
524
525 def Benchmarks(self):
526 return [b for b in self._benchmarks if b.id in self._args.benchmark]
527
528 def Tag(self):
529 return self._args.tag
530
531 def DryRun(self):
532 return self._args.dry_run
533
534 def _lunches(self):
535 def parse_lunch(lunch):
536 parts = lunch.split("-")
537 if len(parts) != 3:
538 raise OptionsError(f"Invalid lunch combo: {lunch}")
539 return Lunch(parts[0], parts[1], parts[2])
540 # If they gave lunch targets on the command line use that
541 if self._args.lunch:
542 result = []
543 # Split into Lunch objects
544 for lunch in self._args.lunch:
545 try:
546 result.append(parse_lunch(lunch))
547 except OptionsError as ex:
548 self._error(ex.message)
549 return result
550 # Use whats in the environment
551 product = os.getenv("TARGET_PRODUCT")
552 release = os.getenv("TARGET_RELEASE")
553 variant = os.getenv("TARGET_BUILD_VARIANT")
554 if (not product) or (not release) or (not variant):
555 # If they didn't give us anything, fail rather than guessing. There's no good
556 # default for AOSP.
557 self._error("No lunch combo specified. Either pass --lunch argument or run lunch.")
558 return []
559 return [Lunch(product, release, variant),]
560
561 def Lunches(self):
562 return self._lunches
563
564 def Iterations(self):
565 return self._args.iterations
566
Joe Onorato05434642023-12-20 05:00:04 +0000567 def DistOne(self):
568 return self._args.dist_one
569
Joe Onorato88ede352023-12-19 02:56:38 +0000570 def _init_benchmarks(self):
571 """Initialize the list of benchmarks."""
572 # Assumes that we've already chdired to the root of the tree.
573 self._benchmarks = [
574 Benchmark(id="full",
Joe Onorato01277d42023-12-20 04:00:12 +0000575 title="Full build",
576 change=Clean(),
577 modules=["droid"],
578 preroll=0,
579 postroll=3,
580 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000581 Benchmark(id="nochange",
Joe Onorato01277d42023-12-20 04:00:12 +0000582 title="No change",
583 change=NoChange(),
584 modules=["droid"],
585 preroll=2,
586 postroll=3,
587 ),
588 Benchmark(id="unreferenced",
589 title="Create unreferenced file",
590 change=Create("bionic/unreferenced.txt"),
591 modules=["droid"],
592 preroll=1,
593 postroll=2,
594 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000595 Benchmark(id="modify_bp",
Joe Onorato01277d42023-12-20 04:00:12 +0000596 title="Modify Android.bp",
597 change=Modify("bionic/libc/Android.bp", Comment("//")),
598 modules=["droid"],
599 preroll=1,
600 postroll=3,
601 ),
602 Benchmark(id="modify_stdio",
603 title="Modify stdio.cpp",
604 change=Modify("bionic/libc/stdio/stdio.cpp", Comment("//")),
605 modules=["libc"],
606 preroll=1,
607 postroll=2,
608 ),
609 Benchmark(id="modify_adbd",
610 title="Modify adbd",
611 change=Modify("packages/modules/adb/daemon/main.cpp", Comment("//")),
612 modules=["adbd"],
613 preroll=1,
614 postroll=2,
615 ),
616 Benchmark(id="services_private_field",
617 title="Add private field to ActivityManagerService.java",
618 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
619 "private"),
620 modules=["services"],
621 preroll=1,
622 postroll=2,
623 ),
624 Benchmark(id="services_public_field",
625 title="Add public field to ActivityManagerService.java",
626 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
627 "/** @hide */ public"),
628 modules=["services"],
629 preroll=1,
630 postroll=2,
631 ),
632 Benchmark(id="services_api",
633 title="Add API to ActivityManagerService.javaa",
634 change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
635 "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
636 modules=["services"],
637 preroll=1,
638 postroll=2,
639 ),
640 Benchmark(id="framework_private_field",
641 title="Add private field to Settings.java",
642 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
643 "private"),
644 modules=["framework-minus-apex"],
645 preroll=1,
646 postroll=2,
647 ),
648 Benchmark(id="framework_public_field",
649 title="Add public field to Settings.java",
650 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
651 "/** @hide */ public"),
652 modules=["framework-minus-apex"],
653 preroll=1,
654 postroll=2,
655 ),
656 Benchmark(id="framework_api",
657 title="Add API to Settings.java",
658 change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
659 "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
660 modules=["framework-minus-apex"],
661 preroll=1,
662 postroll=2,
663 ),
664 Benchmark(id="modify_framework_resource",
665 title="Modify framework resource",
666 change=Modify("frameworks/base/core/res/res/values/config.xml",
667 lambda: str(uuid.uuid4()),
668 before="</string>"),
669 modules=["framework-minus-apex"],
670 preroll=1,
671 postroll=2,
672 ),
673 Benchmark(id="add_framework_resource",
674 title="Add framework resource",
675 change=Modify("frameworks/base/core/res/res/values/config.xml",
676 lambda: f"<string name=\"BENCHMARK\">{uuid.uuid4()}</string>",
677 before="</resources>"),
678 modules=["framework-minus-apex"],
679 preroll=1,
680 postroll=2,
681 ),
682 Benchmark(id="add_systemui_field",
683 title="Add SystemUI field",
684 change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
685 "public"),
686 modules=["SystemUI"],
687 preroll=1,
688 postroll=2,
689 ),
Joe Onorato88ede352023-12-19 02:56:38 +0000690 ]
691
692 def _error(self, message):
693 report_error(message)
694 self._had_error = True
695
696
697def report_error(message):
698 sys.stderr.write(f"error: {message}\n")
699
700
701def main(argv):
702 try:
703 options = Options()
704 runner = Runner(options)
705 runner.Run()
706 except FatalError:
707 sys.stderr.write(f"FAILED\n")
Yu Liuc6576ad2024-01-16 19:27:45 +0000708 sys.exit(1)
Joe Onorato88ede352023-12-19 02:56:38 +0000709
710
711if __name__ == "__main__":
712 main(sys.argv)