[go: nahoru, domu]

blob: 8cd367d4c9de3929f6861a6ed298152f023f1ec9 [file] [log] [blame]
Ari Chivukulaa52f8ba2021-08-10 22:30:391#!/usr/bin/env vpython3
Benoit Lizea3fe2932017-10-20 10:24:522# Copyright (c) 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6""" A utility to generate an up-to-date orderfile.
7
8The orderfile is used by the linker to order text sections such that the
9sections are placed consecutively in the order specified. This allows us
10to page in less code during start-up.
11
12Example usage:
Egor Paskoa7ac67a42020-09-09 12:06:5813 tools/cygprofile/orderfile_generator_backend.py --use-goma --target-arch=arm
Benoit Lizea3fe2932017-10-20 10:24:5214"""
15
Raul Tambre48f176622019-09-23 10:05:2416
Benoit Lizea1b64f82017-12-07 10:12:5017import argparse
Egor Paskoa7ac67a42020-09-09 12:06:5818import csv
Benoit Lizea3fe2932017-10-20 10:24:5219import hashlib
20import json
Matthew Cary69e9e422018-08-10 18:30:0621import glob
Benoit Lizea3fe2932017-10-20 10:24:5222import logging
Benoit Lizea3fe2932017-10-20 10:24:5223import os
Benoit Lizea3fe2932017-10-20 10:24:5224import shutil
25import subprocess
26import sys
Benoit Lizea87e5bc2017-11-07 15:12:5727import tempfile
Benoit Lizea3fe2932017-10-20 10:24:5228import time
29
Matthew Cary91df9792018-11-30 14:35:1530import cluster
Matthew Carye8400642018-06-14 15:43:0231import cyglog_to_orderfile
Benoit Lizefefbb27c2018-01-17 13:54:1832import patch_orderfile
Benoit Lizea87e5bc2017-11-07 15:12:5733import process_profiles
Egor Pasko3bc0b932018-04-03 10:08:2734import profile_android_startup
Benoit Lizea3fe2932017-10-20 10:24:5235
Monica Salamaeea2d942019-03-11 12:36:1836_SRC_PATH = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
37sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil'))
38from devil.android import device_utils
39from devil.android.sdk import version_codes
40
Benoit Lizea3fe2932017-10-20 10:24:5241
42_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)),
43 os.pardir, os.pardir)
44sys.path.append(os.path.join(_SRC_PATH, 'build', 'android'))
45import devil_chromium
46from pylib import constants
47
48
49# Needs to happen early for GetBuildType()/GetOutDirectory() to work correctly
50constants.SetBuildType('Release')
51
52
Matthew Cary53f74ba2019-01-24 10:07:1853# Architecture specific GN args. Trying to build an orderfile for an
54# architecture not listed here will eventually throw.
55_ARCH_GN_ARGS = {
Benoit Lizef5581722021-08-26 09:48:2056 'arm': ['target_cpu = "arm"'],
57 'arm64': ['target_cpu = "arm64"', 'android_64bit_browser = true'],
58 'x86': ['target_cpu = "x86"'],
Matthew Cary53f74ba2019-01-24 10:07:1859}
60
Benoit Lizea3fe2932017-10-20 10:24:5261class CommandError(Exception):
62 """Indicates that a dispatched shell command exited with a non-zero status."""
63
64 def __init__(self, value):
Jesse McKennac0b694b72022-06-17 17:46:1465 super().__init__()
Benoit Lizea3fe2932017-10-20 10:24:5266 self.value = value
67
68 def __str__(self):
69 return repr(self.value)
70
71
72def _GenerateHash(file_path):
73 """Calculates and returns the hash of the file at file_path."""
74 sha1 = hashlib.sha1()
75 with open(file_path, 'rb') as f:
76 while True:
77 # Read in 1mb chunks, so it doesn't all have to be loaded into memory.
78 chunk = f.read(1024 * 1024)
79 if not chunk:
80 break
81 sha1.update(chunk)
82 return sha1.hexdigest()
83
84
85def _GetFileExtension(file_name):
86 """Calculates the file extension from a file name.
87
88 Args:
89 file_name: The source file name.
90 Returns:
91 The part of file_name after the dot (.) or None if the file has no
92 extension.
93 Examples: /home/user/foo.bar -> bar
94 /home/user.name/foo -> None
95 /home/user/.foo -> None
96 /home/user/foo.bar.baz -> baz
97 """
98 file_name_parts = os.path.basename(file_name).split('.')
99 if len(file_name_parts) > 1:
100 return file_name_parts[-1]
Jesse McKennac0b694b72022-06-17 17:46:14101 return None
Benoit Lizea3fe2932017-10-20 10:24:52102
103
104def _StashOutputDirectory(buildpath):
105 """Takes the output directory and stashes it in the default output directory.
106
107 This allows it to be used for incremental builds next time (after unstashing)
108 by keeping it in a place that isn't deleted normally, while also ensuring
109 that it is properly clobbered when appropriate.
110
111 This is a dirty hack to deal with the needs of clobbering while also handling
112 incremental builds and the hardcoded relative paths used in some of the
113 project files.
114
115 Args:
116 buildpath: The path where the building happens. If this corresponds to the
117 default output directory, no action is taken.
118 """
119 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
120 constants.GetOutDirectory())):
121 return
122 name = os.path.basename(buildpath)
123 stashpath = os.path.join(constants.GetOutDirectory(), name)
124 if not os.path.exists(buildpath):
125 return
126 if os.path.exists(stashpath):
127 shutil.rmtree(stashpath, ignore_errors=True)
128 shutil.move(buildpath, stashpath)
129
130
131def _UnstashOutputDirectory(buildpath):
132 """Inverse of _StashOutputDirectory.
133
134 Moves the output directory stashed within the default output directory
135 (out/Release) to the position where the builds can actually happen.
136
137 This is a dirty hack to deal with the needs of clobbering while also handling
138 incremental builds and the hardcoded relative paths used in some of the
139 project files.
140
141 Args:
142 buildpath: The path where the building happens. If this corresponds to the
143 default output directory, no action is taken.
144 """
145 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
146 constants.GetOutDirectory())):
147 return
148 name = os.path.basename(buildpath)
149 stashpath = os.path.join(constants.GetOutDirectory(), name)
150 if not os.path.exists(stashpath):
151 return
152 if os.path.exists(buildpath):
153 shutil.rmtree(buildpath, ignore_errors=True)
154 shutil.move(stashpath, buildpath)
155
156
Jesse McKennac0b694b72022-06-17 17:46:14157class StepRecorder:
Benoit Lizea3fe2932017-10-20 10:24:52158 """Records steps and timings."""
159
160 def __init__(self, buildbot):
161 self.timings = []
162 self._previous_step = ('', 0.0)
163 self._buildbot = buildbot
164 self._error_recorded = False
165
166 def BeginStep(self, name):
167 """Marks a beginning of the next step in the script.
168
169 On buildbot, this prints a specially formatted name that will show up
170 in the waterfall. Otherwise, just prints the step name.
171
172 Args:
173 name: The name of the step.
174 """
175 self.EndStep()
176 self._previous_step = (name, time.time())
Raul Tambre48f176622019-09-23 10:05:24177 print('Running step: ', name)
Benoit Lizea3fe2932017-10-20 10:24:52178
179 def EndStep(self):
180 """Records successful completion of the current step.
181
182 This is optional if the step is immediately followed by another BeginStep.
183 """
184 if self._previous_step[0]:
185 elapsed = time.time() - self._previous_step[1]
Raul Tambre48f176622019-09-23 10:05:24186 print('Step %s took %f seconds' % (self._previous_step[0], elapsed))
Benoit Lizea3fe2932017-10-20 10:24:52187 self.timings.append((self._previous_step[0], elapsed))
188
189 self._previous_step = ('', 0.0)
190
191 def FailStep(self, message=None):
192 """Marks that a particular step has failed.
193
194 On buildbot, this will mark the current step as failed on the waterfall.
195 Otherwise we will just print an optional failure message.
196
197 Args:
198 message: An optional explanation as to why the step failed.
199 """
Raul Tambre48f176622019-09-23 10:05:24200 print('STEP FAILED!!')
Benoit Lizea3fe2932017-10-20 10:24:52201 if message:
Raul Tambre48f176622019-09-23 10:05:24202 print(message)
Benoit Lizea3fe2932017-10-20 10:24:52203 self._error_recorded = True
204 self.EndStep()
205
206 def ErrorRecorded(self):
207 """True if FailStep has been called."""
208 return self._error_recorded
209
210 def RunCommand(self, cmd, cwd=constants.DIR_SOURCE_ROOT, raise_on_error=True,
211 stdout=None):
212 """Execute a shell command.
213
214 Args:
215 cmd: A list of command strings.
Matthew Cary78aae162018-08-10 17:16:30216 cwd: Directory in which the command should be executed, defaults to build
217 root of script's location if not specified.
Benoit Lizea3fe2932017-10-20 10:24:52218 raise_on_error: If true will raise a CommandError if the call doesn't
219 succeed and mark the step as failed.
220 stdout: A file to redirect stdout for the command to.
221
222 Returns:
223 The process's return code.
224
225 Raises:
226 CommandError: An error executing the specified command.
227 """
Raul Tambre48f176622019-09-23 10:05:24228 print('Executing %s in %s' % (' '.join(cmd), cwd))
Benoit Lizea3fe2932017-10-20 10:24:52229 process = subprocess.Popen(cmd, stdout=stdout, cwd=cwd, env=os.environ)
230 process.wait()
231 if raise_on_error and process.returncode != 0:
232 self.FailStep()
233 raise CommandError('Exception executing command %s' % ' '.join(cmd))
234 return process.returncode
235
236
Jesse McKennac0b694b72022-06-17 17:46:14237class ClankCompiler:
Benoit Lizea3fe2932017-10-20 10:24:52238 """Handles compilation of clank."""
239
Christopher Grant073637472019-07-05 13:34:57240 def __init__(self, out_dir, step_recorder, arch, use_goma, goma_dir,
241 system_health_profiling, monochrome, public, orderfile_location):
Benoit Lizea3fe2932017-10-20 10:24:52242 self._out_dir = out_dir
243 self._step_recorder = step_recorder
Benoit Lizea87e5bc2017-11-07 15:12:57244 self._arch = arch
Benoit Lizea3fe2932017-10-20 10:24:52245 self._use_goma = use_goma
Benoit Lizea87e5bc2017-11-07 15:12:57246 self._goma_dir = goma_dir
Matthew Cary78aae162018-08-10 17:16:30247 self._system_health_profiling = system_health_profiling
Stephen Kylef11339f2019-03-25 09:00:47248 self._public = public
249 self._orderfile_location = orderfile_location
Matthew Caryd6bfcb72018-09-14 11:27:46250 if monochrome:
251 self._apk = 'Monochrome.apk'
Matthew Carye1b00062018-09-19 14:27:44252 self._apk_target = 'monochrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46253 self._libname = 'libmonochrome'
Matthew Cary2216fef2019-06-17 13:35:59254 self._libchrome_target = 'libmonochrome'
Matthew Caryd6bfcb72018-09-14 11:27:46255 else:
256 self._apk = 'Chrome.apk'
Matthew Carye1b00062018-09-19 14:27:44257 self._apk_target = 'chrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46258 self._libname = 'libchrome'
259 self._libchrome_target = 'libchrome'
Stephen Kylef11339f2019-03-25 09:00:47260 if public:
261 self._apk = self._apk.replace('.apk', 'Public.apk')
262 self._apk_target = self._apk_target.replace('_apk', '_public_apk')
Matthew Cary78aae162018-08-10 17:16:30263
264 self.obj_dir = os.path.join(self._out_dir, 'Release', 'obj')
Benoit Lizea3fe2932017-10-20 10:24:52265 self.lib_chrome_so = os.path.join(
Matthew Caryd6bfcb72018-09-14 11:27:46266 self._out_dir, 'Release', 'lib.unstripped',
267 '{}.so'.format(self._libname))
268 self.chrome_apk = os.path.join(self._out_dir, 'Release', 'apks', self._apk)
Benoit Lizea3fe2932017-10-20 10:24:52269
Monica Basta99c101f2019-05-21 13:50:05270 def Build(self, instrumented, use_call_graph, target):
Benoit Lizea3fe2932017-10-20 10:24:52271 """Builds the provided ninja target with or without order_profiling on.
272
273 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57274 instrumented: (bool) Whether we want to build an instrumented binary.
Monica Basta99c101f2019-05-21 13:50:05275 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea87e5bc2017-11-07 15:12:57276 target: (str) The name of the ninja target to build.
Benoit Lizea3fe2932017-10-20 10:24:52277 """
278 self._step_recorder.BeginStep('Compile %s' % target)
Monica Basta99c101f2019-05-21 13:50:05279 assert not use_call_graph or instrumented, ('You can not enable call graph '
280 'without instrumentation!')
Benoit Lizea3fe2932017-10-20 10:24:52281
282 # Set the "Release Official" flavor, the parts affecting performance.
283 args = [
Andrew Grieve0e8790c2020-09-03 17:27:32284 'enable_resource_allowlist_generation=false',
Stephen Kylef11339f2019-03-25 09:00:47285 'is_chrome_branded=' + str(not self._public).lower(),
Benoit Lizea3fe2932017-10-20 10:24:52286 'is_debug=false',
287 'is_official_build=true',
Egor Pasko1d13d532020-01-14 21:25:19288 'symbol_level=1', # to fit 30 GiB RAM on the bot when LLD is running
Benoit Lizea3fe2932017-10-20 10:24:52289 'target_os="android"',
290 'use_goma=' + str(self._use_goma).lower(),
291 'use_order_profiling=' + str(instrumented).lower(),
Monica Basta99c101f2019-05-21 13:50:05292 'use_call_graph=' + str(use_call_graph).lower(),
Benoit Lizea3fe2932017-10-20 10:24:52293 ]
Matthew Cary53f74ba2019-01-24 10:07:18294 args += _ARCH_GN_ARGS[self._arch]
Benoit Lizea3fe2932017-10-20 10:24:52295 if self._goma_dir:
296 args += ['goma_dir="%s"' % self._goma_dir]
Matthew Cary78aae162018-08-10 17:16:30297 if self._system_health_profiling:
298 args += ['devtools_instrumentation_dumping = ' +
299 str(instrumented).lower()]
Benoit Lizea87e5bc2017-11-07 15:12:57300
Stephen Kylef11339f2019-03-25 09:00:47301 if self._public and os.path.exists(self._orderfile_location):
302 # GN needs the orderfile path to be source-absolute.
303 src_abs_orderfile = os.path.relpath(self._orderfile_location,
304 constants.DIR_SOURCE_ROOT)
305 args += ['chrome_orderfile="//{}"'.format(src_abs_orderfile)]
306
Benoit Lizea3fe2932017-10-20 10:24:52307 self._step_recorder.RunCommand(
308 ['gn', 'gen', os.path.join(self._out_dir, 'Release'),
309 '--args=' + ' '.join(args)])
310
311 self._step_recorder.RunCommand(
Christopher Grant073637472019-07-05 13:34:57312 ['autoninja', '-C',
313 os.path.join(self._out_dir, 'Release'), target])
Benoit Lizea3fe2932017-10-20 10:24:52314
Christopher Grant10221762019-07-05 12:10:04315 def ForceRelink(self):
316 """Forces libchrome.so or libmonochrome.so to be re-linked.
317
318 With partitioned libraries enabled, deleting these library files does not
319 guarantee they'll be recreated by the linker (they may simply be
320 re-extracted from a combined library). To be safe, touch a source file
321 instead. See http://crbug.com/972701 for more explanation.
322 """
323 file_to_touch = os.path.join(constants.DIR_SOURCE_ROOT, 'chrome', 'browser',
324 'chrome_browser_main_android.cc')
325 assert os.path.exists(file_to_touch)
326 self._step_recorder.RunCommand(['touch', file_to_touch])
327
Monica Basta99c101f2019-05-21 13:50:05328 def CompileChromeApk(self, instrumented, use_call_graph, force_relink=False):
Benoit Lizea3fe2932017-10-20 10:24:52329 """Builds a Chrome.apk either with or without order_profiling on.
330
331 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57332 instrumented: (bool) Whether to build an instrumented apk.
Monica Basta99c101f2019-05-21 13:50:05333 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea3fe2932017-10-20 10:24:52334 force_relink: Whether libchromeview.so should be re-created.
335 """
336 if force_relink:
Christopher Grant10221762019-07-05 12:10:04337 self.ForceRelink()
Monica Basta99c101f2019-05-21 13:50:05338 self.Build(instrumented, use_call_graph, self._apk_target)
Benoit Lizea3fe2932017-10-20 10:24:52339
Monica Basta99c101f2019-05-21 13:50:05340 def CompileLibchrome(self, instrumented, use_call_graph, force_relink=False):
Benoit Lizea3fe2932017-10-20 10:24:52341 """Builds a libchrome.so either with or without order_profiling on.
342
343 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57344 instrumented: (bool) Whether to build an instrumented apk.
Monica Basta99c101f2019-05-21 13:50:05345 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea87e5bc2017-11-07 15:12:57346 force_relink: (bool) Whether libchrome.so should be re-created.
Benoit Lizea3fe2932017-10-20 10:24:52347 """
348 if force_relink:
Christopher Grant10221762019-07-05 12:10:04349 self.ForceRelink()
Monica Basta99c101f2019-05-21 13:50:05350 self.Build(instrumented, use_call_graph, self._libchrome_target)
Benoit Lizea3fe2932017-10-20 10:24:52351
352
Jesse McKennac0b694b72022-06-17 17:46:14353class OrderfileUpdater:
Benoit Lizea3fe2932017-10-20 10:24:52354 """Handles uploading and committing a new orderfile in the repository.
355
356 Only used for testing or on a bot.
357 """
358
359 _CLOUD_STORAGE_BUCKET_FOR_DEBUG = None
360 _CLOUD_STORAGE_BUCKET = None
361 _UPLOAD_TO_CLOUD_COMMAND = 'upload_to_google_storage.py'
362
Egor Pasko0c533c6682019-11-26 21:16:32363 def __init__(self, repository_root, step_recorder):
Benoit Lizea3fe2932017-10-20 10:24:52364 """Constructor.
365
366 Args:
367 repository_root: (str) Root of the target repository.
368 step_recorder: (StepRecorder) Step recorder, for logging.
Benoit Lizea3fe2932017-10-20 10:24:52369 """
370 self._repository_root = repository_root
371 self._step_recorder = step_recorder
Benoit Lizea3fe2932017-10-20 10:24:52372
Matthew Cary86a226e2019-03-19 12:17:44373 def CommitStashedFileHashes(self, files):
374 """Commits unpatched and patched orderfiles hashes if changed.
375
376 The files are committed only if their associated sha1 hash files match, and
377 are modified in git. In normal operations the hash files are changed only
378 when a file is uploaded to cloud storage. If the hash file is not modified
379 in git, the file is skipped.
380
381 Args:
382 files: [str or None] specifies file paths. None items are ignored.
383
384 Raises:
385 Exception if the hash file does not match the file.
386 NotImplementedError when the commit logic hasn't been overridden.
387 """
Ari Chivukulaa52f8ba2021-08-10 22:30:39388 files_to_commit = [_f for _f in files if _f]
Matthew Cary86a226e2019-03-19 12:17:44389 if files_to_commit:
390 self._CommitStashedFiles(files_to_commit)
391
Benoit Lizea3fe2932017-10-20 10:24:52392 def UploadToCloudStorage(self, filename, use_debug_location):
393 """Uploads a file to cloud storage.
394
395 Args:
396 filename: (str) File to upload.
397 use_debug_location: (bool) Whether to use the debug location.
398 """
399 bucket = (self._CLOUD_STORAGE_BUCKET_FOR_DEBUG if use_debug_location
400 else self._CLOUD_STORAGE_BUCKET)
401 extension = _GetFileExtension(filename)
402 cmd = [self._UPLOAD_TO_CLOUD_COMMAND, '--bucket', bucket]
403 if extension:
404 cmd.extend(['-z', extension])
405 cmd.append(filename)
406 self._step_recorder.RunCommand(cmd)
Raul Tambre48f176622019-09-23 10:05:24407 print('Download: https://sandbox.google.com/storage/%s/%s' %
408 (bucket, _GenerateHash(filename)))
Benoit Lizea3fe2932017-10-20 10:24:52409
410 def _GetHashFilePathAndContents(self, filename):
411 """Gets the name and content of the hash file created from uploading the
412 given file.
413
414 Args:
415 filename: (str) The file that was uploaded to cloud storage.
416
417 Returns:
418 A tuple of the hash file name, relative to the reository root, and the
419 content, which should be the sha1 hash of the file
420 ('base_file.sha1', hash)
421 """
422 abs_hash_filename = filename + '.sha1'
423 rel_hash_filename = os.path.relpath(
424 abs_hash_filename, self._repository_root)
425 with open(abs_hash_filename, 'r') as f:
426 return (rel_hash_filename, f.read())
427
Matthew Cary86a226e2019-03-19 12:17:44428 def _GitStash(self):
429 """Git stash the current clank tree.
430
431 Raises:
432 NotImplementedError when the stash logic hasn't been overridden.
433 """
434 raise NotImplementedError
435
436 def _CommitStashedFiles(self, expected_files_in_stash):
437 """Commits stashed files.
438
439 The local repository is updated and then the files to commit are taken from
440 modified files from the git stash. The modified files should be a subset of
441 |expected_files_in_stash|. If there are unexpected modified files, this
442 function may raise. This is meant to be paired with _GitStash().
443
444 Args:
445 expected_files_in_stash: [str] paths to a possible superset of files
446 expected to be stashed & committed.
447
448 Raises:
449 NotImplementedError when the commit logic hasn't been overridden.
450 """
451 raise NotImplementedError
452
Benoit Lizea3fe2932017-10-20 10:24:52453
Jesse McKennac0b694b72022-06-17 17:46:14454class OrderfileGenerator:
Benoit Lizea3fe2932017-10-20 10:24:52455 """A utility for generating a new orderfile for Clank.
456
457 Builds an instrumented binary, profiles a run of the application, and
458 generates an updated orderfile.
459 """
Benoit Lizea3fe2932017-10-20 10:24:52460 _CHECK_ORDERFILE_SCRIPT = os.path.join(
461 constants.DIR_SOURCE_ROOT, 'tools', 'cygprofile', 'check_orderfile.py')
462 _BUILD_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(
463 constants.GetOutDirectory()))) # Normally /path/to/src
464
Benoit Lizea3fe2932017-10-20 10:24:52465 # Previous orderfile_generator debug files would be overwritten.
466 _DIRECTORY_FOR_DEBUG_FILES = '/tmp/orderfile_generator_debug_files'
467
Jesse McKennac0b694b72022-06-17 17:46:14468 _CLOUD_STORAGE_BUCKET_FOR_DEBUG = None
469
Stephen Kylef11339f2019-03-25 09:00:47470 def _PrepareOrderfilePaths(self):
471 if self._options.public:
472 self._clank_dir = os.path.join(constants.DIR_SOURCE_ROOT,
473 '')
474 if not os.path.exists(os.path.join(self._clank_dir, 'orderfiles')):
475 os.makedirs(os.path.join(self._clank_dir, 'orderfiles'))
476 else:
477 self._clank_dir = os.path.join(constants.DIR_SOURCE_ROOT,
478 'clank')
479
480 self._unpatched_orderfile_filename = os.path.join(
481 self._clank_dir, 'orderfiles', 'unpatched_orderfile.%s')
482 self._path_to_orderfile = os.path.join(
483 self._clank_dir, 'orderfiles', 'orderfile.%s.out')
484
Benoit Lizea3fe2932017-10-20 10:24:52485 def _GetPathToOrderfile(self):
486 """Gets the path to the architecture-specific orderfile."""
Benoit Lize6208ddc2021-08-30 12:26:29487 # Build GN files use the ".arm" orderfile irrespective of the actual
488 # architecture. Fake it, otherwise the orderfile we generate here is not
489 # going to be picked up by builds.
490 orderfile_fake_arch = 'arm'
491 return self._path_to_orderfile % orderfile_fake_arch
Benoit Lizea3fe2932017-10-20 10:24:52492
493 def _GetUnpatchedOrderfileFilename(self):
494 """Gets the path to the architecture-specific unpatched orderfile."""
Stephen Kylef11339f2019-03-25 09:00:47495 return self._unpatched_orderfile_filename % self._options.arch
Benoit Lizea3fe2932017-10-20 10:24:52496
Monica Salamaeea2d942019-03-11 12:36:18497 def _SetDevice(self):
498 """ Selects the device to be used by the script.
499
500 Returns:
501 (Device with given serial ID) : if the --device flag is set.
502 (Device running Android[K,L]) : if --use-legacy-chrome-apk flag is set or
503 no device running Android N+ was found.
504 (Device running Android N+) : Otherwise.
505
506 Raises Error:
507 If no device meeting the requirements has been found.
508 """
509 devices = None
510 if self._options.device:
511 devices = [device_utils.DeviceUtils(self._options.device)]
512 else:
513 devices = device_utils.DeviceUtils.HealthyDevices()
514
515 assert devices, 'Expected at least one connected device'
516
517 if self._options.use_legacy_chrome_apk:
518 self._monochrome = False
519 for device in devices:
520 device_version = device.build_version_sdk
Jesse McKennac0b694b72022-06-17 17:46:14521 if (version_codes.KITKAT <= device_version <=
522 version_codes.LOLLIPOP_MR1):
Monica Salamaeea2d942019-03-11 12:36:18523 return device
524
525 assert not self._options.use_legacy_chrome_apk, \
526 'No device found running suitable android version for Chrome.apk.'
527
528 preferred_device = None
529 for device in devices:
530 if device.build_version_sdk >= version_codes.NOUGAT:
Monica Salama90b8f5b2019-04-25 11:10:38531 preferred_device = device
532 break
Monica Salamaeea2d942019-03-11 12:36:18533
534 self._monochrome = preferred_device is not None
535
536 return preferred_device if preferred_device else devices[0]
537
538
Benoit Lizea3fe2932017-10-20 10:24:52539 def __init__(self, options, orderfile_updater_class):
540 self._options = options
Benoit Lizea3fe2932017-10-20 10:24:52541 self._instrumented_out_dir = os.path.join(
542 self._BUILD_ROOT, self._options.arch + '_instrumented_out')
Monica Basta99c101f2019-05-21 13:50:05543 if self._options.use_call_graph:
Christopher Grantdfe1bac2019-07-05 13:34:10544 self._instrumented_out_dir += '_call_graph'
Monica Basta99c101f2019-05-21 13:50:05545
Benoit Lizea3fe2932017-10-20 10:24:52546 self._uninstrumented_out_dir = os.path.join(
547 self._BUILD_ROOT, self._options.arch + '_uninstrumented_out')
Monica Salama90b8f5b2019-04-25 11:10:38548 self._no_orderfile_out_dir = os.path.join(
549 self._BUILD_ROOT, self._options.arch + '_no_orderfile_out')
Benoit Lizea3fe2932017-10-20 10:24:52550
Stephen Kylef11339f2019-03-25 09:00:47551 self._PrepareOrderfilePaths()
552
Benoit Lizea3fe2932017-10-20 10:24:52553 if options.profile:
Benoit Lizea1b64f82017-12-07 10:12:50554 output_directory = os.path.join(self._instrumented_out_dir, 'Release')
Matthew Caryb8daed942018-06-11 10:58:08555 host_profile_dir = os.path.join(output_directory, 'profile_data')
Benoit Lizea1b64f82017-12-07 10:12:50556 urls = [profile_android_startup.AndroidProfileTool.TEST_URL]
557 use_wpr = True
558 simulate_user = False
Benoit L96466812018-03-06 15:58:37559 urls = options.urls
560 use_wpr = not options.no_wpr
561 simulate_user = options.simulate_user
Monica Salamaeea2d942019-03-11 12:36:18562 device = self._SetDevice()
Benoit Lizea3fe2932017-10-20 10:24:52563 self._profiler = profile_android_startup.AndroidProfileTool(
Matthew Cary453ff1452018-07-18 12:19:35564 output_directory, host_profile_dir, use_wpr, urls, simulate_user,
Matthew Cary1ea82212019-03-20 12:10:27565 device, debug=self._options.streamline_for_debugging)
Matthew Cary69e9e422018-08-10 18:30:06566 if options.pregenerated_profiles:
567 self._profiler.SetPregeneratedProfiles(
568 glob.glob(options.pregenerated_profiles))
569 else:
570 assert not options.pregenerated_profiles, (
571 '--pregenerated-profiles cannot be used with --skip-profile')
572 assert not options.profile_save_dir, (
573 '--profile-save-dir cannot be used with --skip-profile')
Monica Salamaeea2d942019-03-11 12:36:18574 self._monochrome = not self._options.use_legacy_chrome_apk
Benoit Lizea3fe2932017-10-20 10:24:52575
Matthew Caryf949bba2019-02-04 13:39:23576 # Outlined function handling enabled by default for all architectures.
577 self._order_outlined_functions = not options.noorder_outlined_functions
578
Benoit Lizea3fe2932017-10-20 10:24:52579 self._output_data = {}
580 self._step_recorder = StepRecorder(options.buildbot)
581 self._compiler = None
Stephen Kylef11339f2019-03-25 09:00:47582 if orderfile_updater_class is None:
Egor Paskod80bfad2020-09-10 21:53:06583 orderfile_updater_class = OrderfileUpdater
Benoit Lizea3fe2932017-10-20 10:24:52584 assert issubclass(orderfile_updater_class, OrderfileUpdater)
Stephen Kylef11339f2019-03-25 09:00:47585 self._orderfile_updater = orderfile_updater_class(self._clank_dir,
Egor Pasko0c533c6682019-11-26 21:16:32586 self._step_recorder)
Benoit Lizea3fe2932017-10-20 10:24:52587 assert os.path.isdir(constants.DIR_SOURCE_ROOT), 'No src directory found'
588
Benoit Lizea3fe2932017-10-20 10:24:52589 @staticmethod
590 def _RemoveBlanks(src_file, dest_file):
591 """A utility to remove blank lines from a file.
592
593 Args:
594 src_file: The name of the file to remove the blanks from.
595 dest_file: The name of the file to write the output without blanks.
596 """
597 assert src_file != dest_file, 'Source and destination need to be distinct'
598
599 try:
600 src = open(src_file, 'r')
601 dest = open(dest_file, 'w')
602 for line in src:
603 if line and not line.isspace():
604 dest.write(line)
605 finally:
606 src.close()
607 dest.close()
608
609 def _GenerateAndProcessProfile(self):
Matthew Cary78aae162018-08-10 17:16:30610 """Invokes a script to merge the per-thread traces into one file.
611
612 The produced list of offsets is saved in
613 self._GetUnpatchedOrderfileFilename().
614 """
Benoit Lizea3fe2932017-10-20 10:24:52615 self._step_recorder.BeginStep('Generate Profile Data')
616 files = []
Matthew Cary78aae162018-08-10 17:16:30617 logging.getLogger().setLevel(logging.DEBUG)
Christopher Grantdfe1bac2019-07-05 13:34:10618
619 if self._options.profile_save_dir:
620 # The directory must not preexist, to ensure purity of data. Check
621 # before profiling to save time.
622 if os.path.exists(self._options.profile_save_dir):
623 raise Exception('Profile save directory must not pre-exist')
624 os.makedirs(self._options.profile_save_dir)
625
Matthew Cary78aae162018-08-10 17:16:30626 if self._options.system_health_orderfile:
627 files = self._profiler.CollectSystemHealthProfile(
628 self._compiler.chrome_apk)
Matthew Cary69e9e422018-08-10 18:30:06629 self._MaybeSaveProfile(files)
Matthew Cary78aae162018-08-10 17:16:30630 try:
631 self._ProcessPhasedOrderfile(files)
632 except Exception:
633 for f in files:
634 self._SaveForDebugging(f)
Matthew Cary8b1416232018-08-10 19:12:22635 self._SaveForDebugging(self._compiler.lib_chrome_so)
Matthew Cary78aae162018-08-10 17:16:30636 raise
637 finally:
638 self._profiler.Cleanup()
639 else:
640 self._CollectLegacyProfile()
641 logging.getLogger().setLevel(logging.INFO)
642
643 def _ProcessPhasedOrderfile(self, files):
644 """Process the phased orderfiles produced by system health benchmarks.
645
646 The offsets will be placed in _GetUnpatchedOrderfileFilename().
647
648 Args:
649 file: Profile files pulled locally.
650 """
651 self._step_recorder.BeginStep('Process Phased Orderfile')
652 profiles = process_profiles.ProfileManager(files)
653 processor = process_profiles.SymbolOffsetProcessor(
654 self._compiler.lib_chrome_so)
Monica Basta99c101f2019-05-21 13:50:05655 ordered_symbols = cluster.ClusterOffsets(profiles, processor,
656 call_graph=self._options.use_call_graph)
Matthew Cary8b1416232018-08-10 19:12:22657 if not ordered_symbols:
658 raise Exception('Failed to get ordered symbols')
Matthew Caryda9db932019-03-11 15:28:17659 for sym in ordered_symbols:
660 assert not sym.startswith('OUTLINED_FUNCTION_'), (
661 'Outlined function found in instrumented function, very likely '
662 'something has gone very wrong!')
Matthew Cary91df9792018-11-30 14:35:15663 self._output_data['offsets_kib'] = processor.SymbolsSize(
664 ordered_symbols) / 1024
Matthew Cary78aae162018-08-10 17:16:30665 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
Matthew Cary8b1416232018-08-10 19:12:22666 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary78aae162018-08-10 17:16:30667
668 def _CollectLegacyProfile(self):
Matthew Cary2df8d452018-09-19 07:50:20669 files = []
Benoit Lizea3fe2932017-10-20 10:24:52670 try:
Benoit Lizea3fe2932017-10-20 10:24:52671 files = self._profiler.CollectProfile(
672 self._compiler.chrome_apk,
673 constants.PACKAGE_INFO['chrome'])
Matthew Cary69e9e422018-08-10 18:30:06674 self._MaybeSaveProfile(files)
Matthew Caryb8daed942018-06-11 10:58:08675 self._step_recorder.BeginStep('Process profile')
Benoit L96466812018-03-06 15:58:37676 assert os.path.exists(self._compiler.lib_chrome_so)
677 offsets = process_profiles.GetReachedOffsetsFromDumpFiles(
678 files, self._compiler.lib_chrome_so)
679 if not offsets:
680 raise Exception('No profiler offsets found in {}'.format(
681 '\n'.join(files)))
Matthew Cary8b1416232018-08-10 19:12:22682 processor = process_profiles.SymbolOffsetProcessor(
683 self._compiler.lib_chrome_so)
684 ordered_symbols = processor.GetOrderedSymbols(offsets)
685 if not ordered_symbols:
686 raise Exception('No symbol names from offsets found in {}'.format(
687 '\n'.join(files)))
688 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
689 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary0f1f681a2018-01-22 10:40:51690 except Exception:
Benoit Lizea3fe2932017-10-20 10:24:52691 for f in files:
692 self._SaveForDebugging(f)
693 raise
694 finally:
695 self._profiler.Cleanup()
Benoit Lizea3fe2932017-10-20 10:24:52696
Matthew Cary69e9e422018-08-10 18:30:06697 def _MaybeSaveProfile(self, files):
698 if self._options.profile_save_dir:
699 logging.info('Saving profiles to %s', self._options.profile_save_dir)
700 for f in files:
701 shutil.copy(f, self._options.profile_save_dir)
702 logging.info('Saved profile %s', f)
703
Benoit Lizea3fe2932017-10-20 10:24:52704 def _PatchOrderfile(self):
705 """Patches the orderfile using clean version of libchrome.so."""
706 self._step_recorder.BeginStep('Patch Orderfile')
Benoit Lizefefbb27c2018-01-17 13:54:18707 patch_orderfile.GeneratePatchedOrderfile(
708 self._GetUnpatchedOrderfileFilename(), self._compiler.lib_chrome_so,
Matthew Caryf949bba2019-02-04 13:39:23709 self._GetPathToOrderfile(), self._order_outlined_functions)
Benoit Lizea3fe2932017-10-20 10:24:52710
711 def _VerifySymbolOrder(self):
712 self._step_recorder.BeginStep('Verify Symbol Order')
Andrew Grievec45bb952021-11-02 18:33:27713 return_code = self._step_recorder.RunCommand([
714 self._CHECK_ORDERFILE_SCRIPT, self._compiler.lib_chrome_so,
715 self._GetPathToOrderfile()
716 ],
717 constants.DIR_SOURCE_ROOT,
718 raise_on_error=False)
Benoit Lizea3fe2932017-10-20 10:24:52719 if return_code:
720 self._step_recorder.FailStep('Orderfile check returned %d.' % return_code)
721
722 def _RecordHash(self, file_name):
723 """Records the hash of the file into the output_data dictionary."""
724 self._output_data[os.path.basename(file_name) + '.sha1'] = _GenerateHash(
725 file_name)
726
727 def _SaveFileLocally(self, file_name, file_sha1):
728 """Saves the file to a temporary location and prints the sha1sum."""
729 if not os.path.exists(self._DIRECTORY_FOR_DEBUG_FILES):
730 os.makedirs(self._DIRECTORY_FOR_DEBUG_FILES)
731 shutil.copy(file_name, self._DIRECTORY_FOR_DEBUG_FILES)
Raul Tambre48f176622019-09-23 10:05:24732 print('File: %s, saved in: %s, sha1sum: %s' %
733 (file_name, self._DIRECTORY_FOR_DEBUG_FILES, file_sha1))
Benoit Lizea3fe2932017-10-20 10:24:52734
735 def _SaveForDebugging(self, filename):
736 """Uploads the file to cloud storage or saves to a temporary location."""
737 file_sha1 = _GenerateHash(filename)
738 if not self._options.buildbot:
739 self._SaveFileLocally(filename, file_sha1)
740 else:
Raul Tambre48f176622019-09-23 10:05:24741 print('Uploading file for debugging: ' + filename)
Benoit Lizea3fe2932017-10-20 10:24:52742 self._orderfile_updater.UploadToCloudStorage(
743 filename, use_debug_location=True)
744
745 def _SaveForDebuggingWithOverwrite(self, file_name):
746 """Uploads and overwrites the file in cloud storage or copies locally.
747
748 Should be used for large binaries like lib_chrome_so.
749
750 Args:
751 file_name: (str) File to upload.
752 """
753 file_sha1 = _GenerateHash(file_name)
754 if not self._options.buildbot:
755 self._SaveFileLocally(file_name, file_sha1)
756 else:
Raul Tambre48f176622019-09-23 10:05:24757 print('Uploading file for debugging: %s, sha1sum: %s' % (file_name,
758 file_sha1))
Benoit Lizea3fe2932017-10-20 10:24:52759 upload_location = '%s/%s' % (
760 self._CLOUD_STORAGE_BUCKET_FOR_DEBUG, os.path.basename(file_name))
761 self._step_recorder.RunCommand([
762 'gsutil.py', 'cp', file_name, 'gs://' + upload_location])
Raul Tambre48f176622019-09-23 10:05:24763 print('Uploaded to: https://sandbox.google.com/storage/' +
764 upload_location)
Benoit Lizea3fe2932017-10-20 10:24:52765
766 def _MaybeArchiveOrderfile(self, filename):
767 """In buildbot configuration, uploads the generated orderfile to
768 Google Cloud Storage.
769
770 Args:
771 filename: (str) Orderfile to upload.
772 """
Matthew Cary91df9792018-11-30 14:35:15773 # First compute hashes so that we can download them later if we need to.
Benoit Lizea3fe2932017-10-20 10:24:52774 self._step_recorder.BeginStep('Compute hash for ' + filename)
775 self._RecordHash(filename)
776 if self._options.buildbot:
777 self._step_recorder.BeginStep('Archive ' + filename)
778 self._orderfile_updater.UploadToCloudStorage(
779 filename, use_debug_location=False)
780
Egor Paskobce64d012018-11-20 12:02:37781 def UploadReadyOrderfiles(self):
782 self._step_recorder.BeginStep('Upload Ready Orderfiles')
783 for file_name in [self._GetUnpatchedOrderfileFilename(),
784 self._GetPathToOrderfile()]:
785 self._orderfile_updater.UploadToCloudStorage(
786 file_name, use_debug_location=False)
787
Monica Salama90b8f5b2019-04-25 11:10:38788 def _NativeCodeMemoryBenchmark(self, apk):
789 """Runs system_health.memory_mobile to assess native code memory footprint.
790
791 Args:
792 apk: (str) Path to the apk.
793
794 Returns:
795 results: ([int]) Values of native code memory footprint in bytes from the
796 benchmark results.
797 """
798 self._step_recorder.BeginStep("Running orderfile.memory_mobile")
799 try:
800 out_dir = tempfile.mkdtemp()
801 self._profiler._RunCommand(['tools/perf/run_benchmark',
802 '--device={}'.format(
803 self._profiler._device.serial),
804 '--browser=exact',
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54805 '--output-format=csv',
Monica Salama90b8f5b2019-04-25 11:10:38806 '--output-dir={}'.format(out_dir),
807 '--reset-results',
808 '--browser-executable={}'.format(apk),
809 'orderfile.memory_mobile'])
810
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54811 out_file_path = os.path.join(out_dir, 'results.csv')
Monica Salama90b8f5b2019-04-25 11:10:38812 if not os.path.exists(out_file_path):
Monica Basta8ec87fd2019-05-13 12:12:35813 raise Exception('Results file not found!')
Monica Salama90b8f5b2019-04-25 11:10:38814
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54815 results = {}
Monica Salama90b8f5b2019-04-25 11:10:38816 with open(out_file_path, 'r') as f:
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54817 reader = csv.DictReader(f)
818 for row in reader:
819 if not row['name'].endswith('NativeCodeResidentMemory'):
Monica Basta8ec87fd2019-05-13 12:12:35820 continue
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54821 # Note: NativeCodeResidentMemory records a single sample from each
822 # story run, so this average (reported as 'avg') is exactly the value
823 # of that one sample. Each story is run multiple times, so this loop
824 # will accumulate into a list all values for all runs of each story.
825 results.setdefault(row['name'], {}).setdefault(
826 row['stories'], []).append(row['avg'])
Monica Basta8ec87fd2019-05-13 12:12:35827
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54828 if not results:
829 raise Exception('Could not find relevant results')
830
Monica Basta8ec87fd2019-05-13 12:12:35831 return results
832
833 except Exception as e:
834 return 'Error: ' + str(e)
Monica Salama90b8f5b2019-04-25 11:10:38835
836 finally:
837 shutil.rmtree(out_dir)
838
Monica Salama90b8f5b2019-04-25 11:10:38839
840 def _PerformanceBenchmark(self, apk):
841 """Runs Speedometer2.0 to assess performance.
842
843 Args:
844 apk: (str) Path to the apk.
845
846 Returns:
847 results: ([float]) Speedometer2.0 results samples in milliseconds.
848 """
849 self._step_recorder.BeginStep("Running Speedometer2.0.")
850 try:
851 out_dir = tempfile.mkdtemp()
852 self._profiler._RunCommand(['tools/perf/run_benchmark',
853 '--device={}'.format(
854 self._profiler._device.serial),
855 '--browser=exact',
Monica Basta8ec87fd2019-05-13 12:12:35856 '--output-format=histograms',
Monica Salama90b8f5b2019-04-25 11:10:38857 '--output-dir={}'.format(out_dir),
858 '--reset-results',
859 '--browser-executable={}'.format(apk),
860 'speedometer2'])
861
Monica Basta8ec87fd2019-05-13 12:12:35862 out_file_path = os.path.join(out_dir, 'histograms.json')
Monica Salama90b8f5b2019-04-25 11:10:38863 if not os.path.exists(out_file_path):
Monica Basta8ec87fd2019-05-13 12:12:35864 raise Exception('Results file not found!')
Monica Salama90b8f5b2019-04-25 11:10:38865
866 with open(out_file_path, 'r') as f:
867 results = json.load(f)
868
869 if not results:
Monica Basta8ec87fd2019-05-13 12:12:35870 raise Exception('Results file is empty.')
871
872 for el in results:
873 if 'name' in el and el['name'] == 'Total' and 'sampleValues' in el:
874 return el['sampleValues']
875
876 raise Exception('Unexpected results format.')
877
878 except Exception as e:
879 return 'Error: ' + str(e)
Monica Salama90b8f5b2019-04-25 11:10:38880
881 finally:
882 shutil.rmtree(out_dir)
883
Monica Salama90b8f5b2019-04-25 11:10:38884
885 def RunBenchmark(self, out_directory, no_orderfile=False):
886 """Builds chrome apk and runs performance and memory benchmarks.
887
888 Builds a non-instrumented version of chrome.
889 Installs chrome apk on the device.
890 Runs Speedometer2.0 benchmark to assess performance.
891 Runs system_health.memory_mobile to evaluate memory footprint.
892
893 Args:
894 out_directory: (str) Path to out directory for this build.
895 no_orderfile: (bool) True if chrome to be built without orderfile.
896
897 Returns:
898 benchmark_results: (dict) Results extracted from benchmarks.
899 """
Benoit Lize74e82b32021-08-26 14:22:01900 benchmark_results = {}
Monica Salama90b8f5b2019-04-25 11:10:38901 try:
902 _UnstashOutputDirectory(out_directory)
Christopher Grant073637472019-07-05 13:34:57903 self._compiler = ClankCompiler(out_directory, self._step_recorder,
904 self._options.arch, self._options.use_goma,
905 self._options.goma_dir,
906 self._options.system_health_orderfile,
907 self._monochrome, self._options.public,
908 self._GetPathToOrderfile())
Monica Salama90b8f5b2019-04-25 11:10:38909
910 if no_orderfile:
911 orderfile_path = self._GetPathToOrderfile()
912 backup_orderfile = orderfile_path + '.backup'
913 shutil.move(orderfile_path, backup_orderfile)
914 open(orderfile_path, 'w').close()
915
916 # Build APK to be installed on the device.
Monica Basta99c101f2019-05-21 13:50:05917 self._compiler.CompileChromeApk(instrumented=False,
918 use_call_graph=False,
919 force_relink=True)
Monica Salama90b8f5b2019-04-25 11:10:38920 benchmark_results['Speedometer2.0'] = self._PerformanceBenchmark(
921 self._compiler.chrome_apk)
922 benchmark_results['orderfile.memory_mobile'] = (
923 self._NativeCodeMemoryBenchmark(self._compiler.chrome_apk))
Monica Basta8ec87fd2019-05-13 12:12:35924
925 except Exception as e:
926 benchmark_results['Error'] = str(e)
927
Monica Salama90b8f5b2019-04-25 11:10:38928 finally:
929 if no_orderfile and os.path.exists(backup_orderfile):
930 shutil.move(backup_orderfile, orderfile_path)
931 _StashOutputDirectory(out_directory)
932
933 return benchmark_results
934
Benoit Lizea3fe2932017-10-20 10:24:52935 def Generate(self):
936 """Generates and maybe upload an order."""
Matthew Carye8400642018-06-14 15:43:02937 assert (bool(self._options.profile) ^
938 bool(self._options.manual_symbol_offsets))
Matthew Cary78aae162018-08-10 17:16:30939 if self._options.system_health_orderfile and not self._options.profile:
940 raise AssertionError('--system_health_orderfile must be not be used '
941 'with --skip-profile')
942 if (self._options.manual_symbol_offsets and
943 not self._options.system_health_orderfile):
944 raise AssertionError('--manual-symbol-offsets must be used with '
945 '--system_health_orderfile.')
Matthew Carye8400642018-06-14 15:43:02946
Benoit Lizea3fe2932017-10-20 10:24:52947 if self._options.profile:
948 try:
949 _UnstashOutputDirectory(self._instrumented_out_dir)
950 self._compiler = ClankCompiler(
Christopher Grant073637472019-07-05 13:34:57951 self._instrumented_out_dir, self._step_recorder, self._options.arch,
952 self._options.use_goma, self._options.goma_dir,
953 self._options.system_health_orderfile, self._monochrome,
954 self._options.public, self._GetPathToOrderfile())
Matthew Carya2bea452018-11-13 10:21:07955 if not self._options.pregenerated_profiles:
956 # If there are pregenerated profiles, the instrumented build should
957 # not be changed to avoid invalidating the pregenerated profile
958 # offsets.
Monica Basta99c101f2019-05-21 13:50:05959 self._compiler.CompileChromeApk(instrumented=True,
960 use_call_graph=
961 self._options.use_call_graph)
Benoit Lizea3fe2932017-10-20 10:24:52962 self._GenerateAndProcessProfile()
963 self._MaybeArchiveOrderfile(self._GetUnpatchedOrderfileFilename())
Benoit Lizea3fe2932017-10-20 10:24:52964 finally:
Benoit Lizea3fe2932017-10-20 10:24:52965 _StashOutputDirectory(self._instrumented_out_dir)
Matthew Carye8400642018-06-14 15:43:02966 elif self._options.manual_symbol_offsets:
967 assert self._options.manual_libname
968 assert self._options.manual_objdir
Ari Chivukulaa52f8ba2021-08-10 22:30:39969 with open(self._options.manual_symbol_offsets) as f:
970 symbol_offsets = [int(x) for x in f]
Matthew Carye8400642018-06-14 15:43:02971 processor = process_profiles.SymbolOffsetProcessor(
Jesse McKennac0b694b72022-06-17 17:46:14972 self._options.manual_libname)
Matthew Carye8400642018-06-14 15:43:02973 generator = cyglog_to_orderfile.OffsetOrderfileGenerator(
974 processor, cyglog_to_orderfile.ObjectFileProcessor(
975 self._options.manual_objdir))
976 ordered_sections = generator.GetOrderedSections(symbol_offsets)
977 if not ordered_sections: # Either None or empty is a problem.
978 raise Exception('Failed to get ordered sections')
979 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
980 orderfile.write('\n'.join(ordered_sections))
981
Benoit Lizea3fe2932017-10-20 10:24:52982 if self._options.patch:
983 if self._options.profile:
984 self._RemoveBlanks(self._GetUnpatchedOrderfileFilename(),
985 self._GetPathToOrderfile())
986 try:
987 _UnstashOutputDirectory(self._uninstrumented_out_dir)
988 self._compiler = ClankCompiler(
989 self._uninstrumented_out_dir, self._step_recorder,
Christopher Grant073637472019-07-05 13:34:57990 self._options.arch, self._options.use_goma, self._options.goma_dir,
Stephen Kylef11339f2019-03-25 09:00:47991 self._options.system_health_orderfile, self._monochrome,
992 self._options.public, self._GetPathToOrderfile())
993
Monica Basta99c101f2019-05-21 13:50:05994 self._compiler.CompileLibchrome(instrumented=False,
995 use_call_graph=False)
Benoit Lizea3fe2932017-10-20 10:24:52996 self._PatchOrderfile()
997 # Because identical code folding is a bit different with and without
998 # the orderfile build, we need to re-patch the orderfile with code
999 # folding as close to the final version as possible.
Monica Basta99c101f2019-05-21 13:50:051000 self._compiler.CompileLibchrome(instrumented=False,
1001 use_call_graph=False, force_relink=True)
Benoit Lizea3fe2932017-10-20 10:24:521002 self._PatchOrderfile()
Monica Basta99c101f2019-05-21 13:50:051003 self._compiler.CompileLibchrome(instrumented=False,
1004 use_call_graph=False, force_relink=True)
Benoit Lizea3fe2932017-10-20 10:24:521005 self._VerifySymbolOrder()
1006 self._MaybeArchiveOrderfile(self._GetPathToOrderfile())
1007 finally:
1008 _StashOutputDirectory(self._uninstrumented_out_dir)
Benoit Lizea3fe2932017-10-20 10:24:521009
Monica Salama90b8f5b2019-04-25 11:10:381010 if self._options.benchmark:
1011 self._output_data['orderfile_benchmark_results'] = self.RunBenchmark(
1012 self._uninstrumented_out_dir)
1013 self._output_data['no_orderfile_benchmark_results'] = self.RunBenchmark(
1014 self._no_orderfile_out_dir, no_orderfile=True)
1015
Egor Paskod80bfad2020-09-10 21:53:061016 if self._options.buildbot:
1017 self._orderfile_updater._GitStash()
Benoit Lizea3fe2932017-10-20 10:24:521018 self._step_recorder.EndStep()
1019 return not self._step_recorder.ErrorRecorded()
1020
1021 def GetReportingData(self):
1022 """Get a dictionary of reporting data (timings, output hashes)"""
1023 self._output_data['timings'] = self._step_recorder.timings
1024 return self._output_data
1025
Matthew Cary86a226e2019-03-19 12:17:441026 def CommitStashedOrderfileHashes(self):
1027 """Commit any orderfile hash files in the current checkout.
1028
1029 Only possible if running on the buildbot.
1030
1031 Returns: true on success.
1032 """
Egor Pasko7ff04122019-11-25 15:47:181033 if not self._options.buildbot:
Matthew Cary86a226e2019-03-19 12:17:441034 logging.error('Trying to commit when not running on the buildbot')
1035 return False
1036 self._orderfile_updater._CommitStashedFiles([
1037 filename + '.sha1'
1038 for filename in (self._GetUnpatchedOrderfileFilename(),
1039 self._GetPathToOrderfile())])
1040 return True
1041
Benoit Lizea3fe2932017-10-20 10:24:521042
Benoit Lizea1b64f82017-12-07 10:12:501043def CreateArgumentParser():
1044 """Creates and returns the argument parser."""
1045 parser = argparse.ArgumentParser()
Monica Salama90b8f5b2019-04-25 11:10:381046 parser.add_argument('--no-benchmark', action='store_false', dest='benchmark',
1047 default=True, help='Disables running benchmarks.')
Benoit Lizea1b64f82017-12-07 10:12:501048 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521049 '--buildbot', action='store_true',
1050 help='If true, the script expects to be run on a buildbot')
Benoit Lizea1b64f82017-12-07 10:12:501051 parser.add_argument(
Matthew Cary453ff1452018-07-18 12:19:351052 '--device', default=None, type=str,
1053 help='Device serial number on which to run profiling.')
1054 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521055 '--verify', action='store_true',
1056 help='If true, the script only verifies the current orderfile')
Benoit Lizef5581722021-08-26 09:48:201057 parser.add_argument('--target-arch',
1058 action='store',
1059 dest='arch',
Stephen Martinis71b391b52018-12-04 15:08:041060 default='arm',
Benoit Lizef5581722021-08-26 09:48:201061 choices=list(_ARCH_GN_ARGS.keys()),
Matthew Cary53f74ba2019-01-24 10:07:181062 help='The target architecture for which to build.')
Benoit Lizea1b64f82017-12-07 10:12:501063 parser.add_argument('--output-json', action='store', dest='json_file',
1064 help='Location to save stats in json format')
1065 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521066 '--skip-profile', action='store_false', dest='profile', default=True,
1067 help='Don\'t generate a profile on the device. Only patch from the '
1068 'existing profile.')
Benoit Lizea1b64f82017-12-07 10:12:501069 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521070 '--skip-patch', action='store_false', dest='patch', default=True,
1071 help='Only generate the raw (unpatched) orderfile, don\'t patch it.')
Benoit Lizea1b64f82017-12-07 10:12:501072 parser.add_argument('--goma-dir', help='GOMA directory.')
1073 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521074 '--use-goma', action='store_true', help='Enable GOMA.', default=False)
Benoit Lizea1b64f82017-12-07 10:12:501075 parser.add_argument('--adb-path', help='Path to the adb binary.')
Matthew Carye8400642018-06-14 15:43:021076
Egor Paskod80bfad2020-09-10 21:53:061077 parser.add_argument('--public',
1078 action='store_true',
1079 help='Build non-internal APK and change the orderfile '
1080 'location. Required if your checkout is non-internal.',
Stephen Kylef11339f2019-03-25 09:00:471081 default=False)
Matthew Cary04f41032018-12-10 15:55:271082 parser.add_argument('--nosystem-health-orderfile', action='store_false',
Benoit Lcba421e2019-04-26 15:28:261083 dest='system_health_orderfile', default=True,
Matthew Caryc262f5d2018-09-19 14:36:281084 help=('Create an orderfile based on an about:blank '
1085 'startup benchmark instead of system health '
1086 'benchmarks.'))
Monica Salamaeea2d942019-03-11 12:36:181087 parser.add_argument(
1088 '--use-legacy-chrome-apk', action='store_true', default=False,
1089 help=('Compile and instrument chrome for [L, K] devices.'))
Matthew Carye8400642018-06-14 15:43:021090 parser.add_argument('--manual-symbol-offsets', default=None, type=str,
1091 help=('File of list of ordered symbol offsets generated '
1092 'by manual profiling. Must set other --manual* '
1093 'flags if this is used, and must --skip-profile.'))
1094 parser.add_argument('--manual-libname', default=None, type=str,
1095 help=('Library filename corresponding to '
1096 '--manual-symbol-offsets.'))
1097 parser.add_argument('--manual-objdir', default=None, type=str,
1098 help=('Root of object file directory corresponding to '
1099 '--manual-symbol-offsets.'))
Matthew Caryf949bba2019-02-04 13:39:231100 parser.add_argument('--noorder-outlined-functions', action='store_true',
1101 help='Disable outlined functions in the orderfile.')
Matthew Cary69e9e422018-08-10 18:30:061102 parser.add_argument('--pregenerated-profiles', default=None, type=str,
1103 help=('Pregenerated profiles to use instead of running '
1104 'profile step. Cannot be used with '
1105 '--skip-profiles.'))
1106 parser.add_argument('--profile-save-dir', default=None, type=str,
1107 help=('Directory to save any profiles created. These can '
1108 'be used with --pregenerated-profiles. Cannot be '
1109 'used with --skip-profiles.'))
Egor Paskobce64d012018-11-20 12:02:371110 parser.add_argument('--upload-ready-orderfiles', action='store_true',
1111 help=('Skip orderfile generation and manually upload '
1112 'orderfiles (both patched and unpatched) from '
1113 'their normal location in the tree to the cloud '
1114 'storage. DANGEROUS! USE WITH CARE!'))
Matthew Cary1ea82212019-03-20 12:10:271115 parser.add_argument('--streamline-for-debugging', action='store_true',
1116 help=('Streamline where possible the run for faster '
1117 'iteration while debugging. The orderfile '
1118 'generated will be valid and nontrivial, but '
1119 'may not be based on a representative profile '
1120 'or other such considerations. Use with caution.'))
Matthew Cary86a226e2019-03-19 12:17:441121 parser.add_argument('--commit-hashes', action='store_true',
1122 help=('Commit any orderfile hash files in the current '
1123 'checkout; performs no other action'))
Monica Basta99c101f2019-05-21 13:50:051124 parser.add_argument('--use-call-graph', action='store_true', default=False,
1125 help='Use call graph instrumentation.')
Benoit Lizea1b64f82017-12-07 10:12:501126 profile_android_startup.AddProfileCollectionArguments(parser)
Benoit Lizea3fe2932017-10-20 10:24:521127 return parser
1128
1129
Stephen Kylef11339f2019-03-25 09:00:471130def CreateOrderfile(options, orderfile_updater_class=None):
Christopher Grantdfe1bac2019-07-05 13:34:101131 """Creates an orderfile.
Benoit Lizea3fe2932017-10-20 10:24:521132
1133 Args:
1134 options: As returned from optparse.OptionParser.parse_args()
1135 orderfile_updater_class: (OrderfileUpdater) subclass of OrderfileUpdater.
1136
1137 Returns:
1138 True iff success.
1139 """
1140 logging.basicConfig(level=logging.INFO)
1141 devil_chromium.Initialize(adb_path=options.adb_path)
1142
Benoit Lize6208ddc2021-08-30 12:26:291143 # Since we generate a ".arm" orderfile irrespective of the architecture (see
1144 # comment in _GetPathToOrderfile()), make sure that we don't commit it.
1145 if options.arch != 'arm':
1146 assert not options.buildbot, (
1147 'ARM is the only supported architecture on bots')
1148 assert not options.upload_ready_orderfiles, (
1149 'ARM is the only supported architecture on bots')
1150
Egor Pasko93e514e2017-10-31 13:32:361151 generator = OrderfileGenerator(options, orderfile_updater_class)
Benoit Lizea3fe2932017-10-20 10:24:521152 try:
1153 if options.verify:
Egor Pasko93e514e2017-10-31 13:32:361154 generator._VerifySymbolOrder()
Matthew Cary86a226e2019-03-19 12:17:441155 elif options.commit_hashes:
Matthew Cary86a226e2019-03-19 12:17:441156 return generator.CommitStashedOrderfileHashes()
Egor Paskobce64d012018-11-20 12:02:371157 elif options.upload_ready_orderfiles:
1158 return generator.UploadReadyOrderfiles()
Benoit Lizea3fe2932017-10-20 10:24:521159 else:
Egor Pasko93e514e2017-10-31 13:32:361160 return generator.Generate()
Benoit Lizea3fe2932017-10-20 10:24:521161 finally:
Egor Pasko93e514e2017-10-31 13:32:361162 json_output = json.dumps(generator.GetReportingData(),
Benoit Lizea3fe2932017-10-20 10:24:521163 indent=2) + '\n'
1164 if options.json_file:
1165 with open(options.json_file, 'w') as f:
1166 f.write(json_output)
Raul Tambre48f176622019-09-23 10:05:241167 print(json_output)
Benoit Lizea3fe2932017-10-20 10:24:521168 return False
1169
1170
Benoit Lizea1b64f82017-12-07 10:12:501171def main():
1172 parser = CreateArgumentParser()
1173 options = parser.parse_args()
Stephen Kylef11339f2019-03-25 09:00:471174 return 0 if CreateOrderfile(options) else 1
Benoit Lizea3fe2932017-10-20 10:24:521175
1176
1177if __name__ == '__main__':
Benoit Lizea1b64f82017-12-07 10:12:501178 sys.exit(main())