[go: nahoru, domu]

blob: f03621e72cc5bea50efd074eb04fc585b78e430c [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):
65 super(CommandError, self).__init__()
66 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]
101 else:
102 return None
103
104
105def _StashOutputDirectory(buildpath):
106 """Takes the output directory and stashes it in the default output directory.
107
108 This allows it to be used for incremental builds next time (after unstashing)
109 by keeping it in a place that isn't deleted normally, while also ensuring
110 that it is properly clobbered when appropriate.
111
112 This is a dirty hack to deal with the needs of clobbering while also handling
113 incremental builds and the hardcoded relative paths used in some of the
114 project files.
115
116 Args:
117 buildpath: The path where the building happens. If this corresponds to the
118 default output directory, no action is taken.
119 """
120 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
121 constants.GetOutDirectory())):
122 return
123 name = os.path.basename(buildpath)
124 stashpath = os.path.join(constants.GetOutDirectory(), name)
125 if not os.path.exists(buildpath):
126 return
127 if os.path.exists(stashpath):
128 shutil.rmtree(stashpath, ignore_errors=True)
129 shutil.move(buildpath, stashpath)
130
131
132def _UnstashOutputDirectory(buildpath):
133 """Inverse of _StashOutputDirectory.
134
135 Moves the output directory stashed within the default output directory
136 (out/Release) to the position where the builds can actually happen.
137
138 This is a dirty hack to deal with the needs of clobbering while also handling
139 incremental builds and the hardcoded relative paths used in some of the
140 project files.
141
142 Args:
143 buildpath: The path where the building happens. If this corresponds to the
144 default output directory, no action is taken.
145 """
146 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
147 constants.GetOutDirectory())):
148 return
149 name = os.path.basename(buildpath)
150 stashpath = os.path.join(constants.GetOutDirectory(), name)
151 if not os.path.exists(stashpath):
152 return
153 if os.path.exists(buildpath):
154 shutil.rmtree(buildpath, ignore_errors=True)
155 shutil.move(stashpath, buildpath)
156
157
158class StepRecorder(object):
159 """Records steps and timings."""
160
161 def __init__(self, buildbot):
162 self.timings = []
163 self._previous_step = ('', 0.0)
164 self._buildbot = buildbot
165 self._error_recorded = False
166
167 def BeginStep(self, name):
168 """Marks a beginning of the next step in the script.
169
170 On buildbot, this prints a specially formatted name that will show up
171 in the waterfall. Otherwise, just prints the step name.
172
173 Args:
174 name: The name of the step.
175 """
176 self.EndStep()
177 self._previous_step = (name, time.time())
Raul Tambre48f176622019-09-23 10:05:24178 print('Running step: ', name)
Benoit Lizea3fe2932017-10-20 10:24:52179
180 def EndStep(self):
181 """Records successful completion of the current step.
182
183 This is optional if the step is immediately followed by another BeginStep.
184 """
185 if self._previous_step[0]:
186 elapsed = time.time() - self._previous_step[1]
Raul Tambre48f176622019-09-23 10:05:24187 print('Step %s took %f seconds' % (self._previous_step[0], elapsed))
Benoit Lizea3fe2932017-10-20 10:24:52188 self.timings.append((self._previous_step[0], elapsed))
189
190 self._previous_step = ('', 0.0)
191
192 def FailStep(self, message=None):
193 """Marks that a particular step has failed.
194
195 On buildbot, this will mark the current step as failed on the waterfall.
196 Otherwise we will just print an optional failure message.
197
198 Args:
199 message: An optional explanation as to why the step failed.
200 """
Raul Tambre48f176622019-09-23 10:05:24201 print('STEP FAILED!!')
Benoit Lizea3fe2932017-10-20 10:24:52202 if message:
Raul Tambre48f176622019-09-23 10:05:24203 print(message)
Benoit Lizea3fe2932017-10-20 10:24:52204 self._error_recorded = True
205 self.EndStep()
206
207 def ErrorRecorded(self):
208 """True if FailStep has been called."""
209 return self._error_recorded
210
211 def RunCommand(self, cmd, cwd=constants.DIR_SOURCE_ROOT, raise_on_error=True,
212 stdout=None):
213 """Execute a shell command.
214
215 Args:
216 cmd: A list of command strings.
Matthew Cary78aae162018-08-10 17:16:30217 cwd: Directory in which the command should be executed, defaults to build
218 root of script's location if not specified.
Benoit Lizea3fe2932017-10-20 10:24:52219 raise_on_error: If true will raise a CommandError if the call doesn't
220 succeed and mark the step as failed.
221 stdout: A file to redirect stdout for the command to.
222
223 Returns:
224 The process's return code.
225
226 Raises:
227 CommandError: An error executing the specified command.
228 """
Raul Tambre48f176622019-09-23 10:05:24229 print('Executing %s in %s' % (' '.join(cmd), cwd))
Benoit Lizea3fe2932017-10-20 10:24:52230 process = subprocess.Popen(cmd, stdout=stdout, cwd=cwd, env=os.environ)
231 process.wait()
232 if raise_on_error and process.returncode != 0:
233 self.FailStep()
234 raise CommandError('Exception executing command %s' % ' '.join(cmd))
235 return process.returncode
236
237
238class ClankCompiler(object):
239 """Handles compilation of clank."""
240
Christopher Grant073637472019-07-05 13:34:57241 def __init__(self, out_dir, step_recorder, arch, use_goma, goma_dir,
242 system_health_profiling, monochrome, public, orderfile_location):
Benoit Lizea3fe2932017-10-20 10:24:52243 self._out_dir = out_dir
244 self._step_recorder = step_recorder
Benoit Lizea87e5bc2017-11-07 15:12:57245 self._arch = arch
Benoit Lizea3fe2932017-10-20 10:24:52246 self._use_goma = use_goma
Benoit Lizea87e5bc2017-11-07 15:12:57247 self._goma_dir = goma_dir
Matthew Cary78aae162018-08-10 17:16:30248 self._system_health_profiling = system_health_profiling
Stephen Kylef11339f2019-03-25 09:00:47249 self._public = public
250 self._orderfile_location = orderfile_location
Matthew Caryd6bfcb72018-09-14 11:27:46251 if monochrome:
252 self._apk = 'Monochrome.apk'
Matthew Carye1b00062018-09-19 14:27:44253 self._apk_target = 'monochrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46254 self._libname = 'libmonochrome'
Matthew Cary2216fef2019-06-17 13:35:59255 self._libchrome_target = 'libmonochrome'
Matthew Caryd6bfcb72018-09-14 11:27:46256 else:
257 self._apk = 'Chrome.apk'
Matthew Carye1b00062018-09-19 14:27:44258 self._apk_target = 'chrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46259 self._libname = 'libchrome'
260 self._libchrome_target = 'libchrome'
Stephen Kylef11339f2019-03-25 09:00:47261 if public:
262 self._apk = self._apk.replace('.apk', 'Public.apk')
263 self._apk_target = self._apk_target.replace('_apk', '_public_apk')
Matthew Cary78aae162018-08-10 17:16:30264
265 self.obj_dir = os.path.join(self._out_dir, 'Release', 'obj')
Benoit Lizea3fe2932017-10-20 10:24:52266 self.lib_chrome_so = os.path.join(
Matthew Caryd6bfcb72018-09-14 11:27:46267 self._out_dir, 'Release', 'lib.unstripped',
268 '{}.so'.format(self._libname))
269 self.chrome_apk = os.path.join(self._out_dir, 'Release', 'apks', self._apk)
Benoit Lizea3fe2932017-10-20 10:24:52270
Monica Basta99c101f2019-05-21 13:50:05271 def Build(self, instrumented, use_call_graph, target):
Benoit Lizea3fe2932017-10-20 10:24:52272 """Builds the provided ninja target with or without order_profiling on.
273
274 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57275 instrumented: (bool) Whether we want to build an instrumented binary.
Monica Basta99c101f2019-05-21 13:50:05276 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea87e5bc2017-11-07 15:12:57277 target: (str) The name of the ninja target to build.
Benoit Lizea3fe2932017-10-20 10:24:52278 """
279 self._step_recorder.BeginStep('Compile %s' % target)
Monica Basta99c101f2019-05-21 13:50:05280 assert not use_call_graph or instrumented, ('You can not enable call graph '
281 'without instrumentation!')
Benoit Lizea3fe2932017-10-20 10:24:52282
283 # Set the "Release Official" flavor, the parts affecting performance.
284 args = [
Andrew Grieve0e8790c2020-09-03 17:27:32285 'enable_resource_allowlist_generation=false',
Stephen Kylef11339f2019-03-25 09:00:47286 'is_chrome_branded=' + str(not self._public).lower(),
Benoit Lizea3fe2932017-10-20 10:24:52287 'is_debug=false',
288 'is_official_build=true',
Egor Pasko1d13d532020-01-14 21:25:19289 'symbol_level=1', # to fit 30 GiB RAM on the bot when LLD is running
Benoit Lizea3fe2932017-10-20 10:24:52290 'target_os="android"',
291 'use_goma=' + str(self._use_goma).lower(),
292 'use_order_profiling=' + str(instrumented).lower(),
Monica Basta99c101f2019-05-21 13:50:05293 'use_call_graph=' + str(use_call_graph).lower(),
Benoit Lizea3fe2932017-10-20 10:24:52294 ]
Matthew Cary53f74ba2019-01-24 10:07:18295 args += _ARCH_GN_ARGS[self._arch]
Benoit Lizea3fe2932017-10-20 10:24:52296 if self._goma_dir:
297 args += ['goma_dir="%s"' % self._goma_dir]
Matthew Cary78aae162018-08-10 17:16:30298 if self._system_health_profiling:
299 args += ['devtools_instrumentation_dumping = ' +
300 str(instrumented).lower()]
Benoit Lizea87e5bc2017-11-07 15:12:57301
Stephen Kylef11339f2019-03-25 09:00:47302 if self._public and os.path.exists(self._orderfile_location):
303 # GN needs the orderfile path to be source-absolute.
304 src_abs_orderfile = os.path.relpath(self._orderfile_location,
305 constants.DIR_SOURCE_ROOT)
306 args += ['chrome_orderfile="//{}"'.format(src_abs_orderfile)]
307
Benoit Lizea3fe2932017-10-20 10:24:52308 self._step_recorder.RunCommand(
309 ['gn', 'gen', os.path.join(self._out_dir, 'Release'),
310 '--args=' + ' '.join(args)])
311
312 self._step_recorder.RunCommand(
Christopher Grant073637472019-07-05 13:34:57313 ['autoninja', '-C',
314 os.path.join(self._out_dir, 'Release'), target])
Benoit Lizea3fe2932017-10-20 10:24:52315
Christopher Grant10221762019-07-05 12:10:04316 def ForceRelink(self):
317 """Forces libchrome.so or libmonochrome.so to be re-linked.
318
319 With partitioned libraries enabled, deleting these library files does not
320 guarantee they'll be recreated by the linker (they may simply be
321 re-extracted from a combined library). To be safe, touch a source file
322 instead. See http://crbug.com/972701 for more explanation.
323 """
324 file_to_touch = os.path.join(constants.DIR_SOURCE_ROOT, 'chrome', 'browser',
325 'chrome_browser_main_android.cc')
326 assert os.path.exists(file_to_touch)
327 self._step_recorder.RunCommand(['touch', file_to_touch])
328
Monica Basta99c101f2019-05-21 13:50:05329 def CompileChromeApk(self, instrumented, use_call_graph, force_relink=False):
Benoit Lizea3fe2932017-10-20 10:24:52330 """Builds a Chrome.apk either with or without order_profiling on.
331
332 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57333 instrumented: (bool) Whether to build an instrumented apk.
Monica Basta99c101f2019-05-21 13:50:05334 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea3fe2932017-10-20 10:24:52335 force_relink: Whether libchromeview.so should be re-created.
336 """
337 if force_relink:
Christopher Grant10221762019-07-05 12:10:04338 self.ForceRelink()
Monica Basta99c101f2019-05-21 13:50:05339 self.Build(instrumented, use_call_graph, self._apk_target)
Benoit Lizea3fe2932017-10-20 10:24:52340
Monica Basta99c101f2019-05-21 13:50:05341 def CompileLibchrome(self, instrumented, use_call_graph, force_relink=False):
Benoit Lizea3fe2932017-10-20 10:24:52342 """Builds a libchrome.so either with or without order_profiling on.
343
344 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57345 instrumented: (bool) Whether to build an instrumented apk.
Monica Basta99c101f2019-05-21 13:50:05346 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea87e5bc2017-11-07 15:12:57347 force_relink: (bool) Whether libchrome.so should be re-created.
Benoit Lizea3fe2932017-10-20 10:24:52348 """
349 if force_relink:
Christopher Grant10221762019-07-05 12:10:04350 self.ForceRelink()
Monica Basta99c101f2019-05-21 13:50:05351 self.Build(instrumented, use_call_graph, self._libchrome_target)
Benoit Lizea3fe2932017-10-20 10:24:52352
353
354class OrderfileUpdater(object):
355 """Handles uploading and committing a new orderfile in the repository.
356
357 Only used for testing or on a bot.
358 """
359
360 _CLOUD_STORAGE_BUCKET_FOR_DEBUG = None
361 _CLOUD_STORAGE_BUCKET = None
362 _UPLOAD_TO_CLOUD_COMMAND = 'upload_to_google_storage.py'
363
Egor Pasko0c533c6682019-11-26 21:16:32364 def __init__(self, repository_root, step_recorder):
Benoit Lizea3fe2932017-10-20 10:24:52365 """Constructor.
366
367 Args:
368 repository_root: (str) Root of the target repository.
369 step_recorder: (StepRecorder) Step recorder, for logging.
Benoit Lizea3fe2932017-10-20 10:24:52370 """
371 self._repository_root = repository_root
372 self._step_recorder = step_recorder
Benoit Lizea3fe2932017-10-20 10:24:52373
Matthew Cary86a226e2019-03-19 12:17:44374 def CommitStashedFileHashes(self, files):
375 """Commits unpatched and patched orderfiles hashes if changed.
376
377 The files are committed only if their associated sha1 hash files match, and
378 are modified in git. In normal operations the hash files are changed only
379 when a file is uploaded to cloud storage. If the hash file is not modified
380 in git, the file is skipped.
381
382 Args:
383 files: [str or None] specifies file paths. None items are ignored.
384
385 Raises:
386 Exception if the hash file does not match the file.
387 NotImplementedError when the commit logic hasn't been overridden.
388 """
Ari Chivukulaa52f8ba2021-08-10 22:30:39389 files_to_commit = [_f for _f in files if _f]
Matthew Cary86a226e2019-03-19 12:17:44390 if files_to_commit:
391 self._CommitStashedFiles(files_to_commit)
392
Benoit Lizea3fe2932017-10-20 10:24:52393 def UploadToCloudStorage(self, filename, use_debug_location):
394 """Uploads a file to cloud storage.
395
396 Args:
397 filename: (str) File to upload.
398 use_debug_location: (bool) Whether to use the debug location.
399 """
400 bucket = (self._CLOUD_STORAGE_BUCKET_FOR_DEBUG if use_debug_location
401 else self._CLOUD_STORAGE_BUCKET)
402 extension = _GetFileExtension(filename)
403 cmd = [self._UPLOAD_TO_CLOUD_COMMAND, '--bucket', bucket]
404 if extension:
405 cmd.extend(['-z', extension])
406 cmd.append(filename)
407 self._step_recorder.RunCommand(cmd)
Raul Tambre48f176622019-09-23 10:05:24408 print('Download: https://sandbox.google.com/storage/%s/%s' %
409 (bucket, _GenerateHash(filename)))
Benoit Lizea3fe2932017-10-20 10:24:52410
411 def _GetHashFilePathAndContents(self, filename):
412 """Gets the name and content of the hash file created from uploading the
413 given file.
414
415 Args:
416 filename: (str) The file that was uploaded to cloud storage.
417
418 Returns:
419 A tuple of the hash file name, relative to the reository root, and the
420 content, which should be the sha1 hash of the file
421 ('base_file.sha1', hash)
422 """
423 abs_hash_filename = filename + '.sha1'
424 rel_hash_filename = os.path.relpath(
425 abs_hash_filename, self._repository_root)
426 with open(abs_hash_filename, 'r') as f:
427 return (rel_hash_filename, f.read())
428
Matthew Cary86a226e2019-03-19 12:17:44429 def _GitStash(self):
430 """Git stash the current clank tree.
431
432 Raises:
433 NotImplementedError when the stash logic hasn't been overridden.
434 """
435 raise NotImplementedError
436
437 def _CommitStashedFiles(self, expected_files_in_stash):
438 """Commits stashed files.
439
440 The local repository is updated and then the files to commit are taken from
441 modified files from the git stash. The modified files should be a subset of
442 |expected_files_in_stash|. If there are unexpected modified files, this
443 function may raise. This is meant to be paired with _GitStash().
444
445 Args:
446 expected_files_in_stash: [str] paths to a possible superset of files
447 expected to be stashed & committed.
448
449 Raises:
450 NotImplementedError when the commit logic hasn't been overridden.
451 """
452 raise NotImplementedError
453
Benoit Lizea3fe2932017-10-20 10:24:52454
455class OrderfileGenerator(object):
456 """A utility for generating a new orderfile for Clank.
457
458 Builds an instrumented binary, profiles a run of the application, and
459 generates an updated orderfile.
460 """
Benoit Lizea3fe2932017-10-20 10:24:52461 _CHECK_ORDERFILE_SCRIPT = os.path.join(
462 constants.DIR_SOURCE_ROOT, 'tools', 'cygprofile', 'check_orderfile.py')
463 _BUILD_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(
464 constants.GetOutDirectory()))) # Normally /path/to/src
465
Benoit Lizea3fe2932017-10-20 10:24:52466 # Previous orderfile_generator debug files would be overwritten.
467 _DIRECTORY_FOR_DEBUG_FILES = '/tmp/orderfile_generator_debug_files'
468
Stephen Kylef11339f2019-03-25 09:00:47469 def _PrepareOrderfilePaths(self):
470 if self._options.public:
471 self._clank_dir = os.path.join(constants.DIR_SOURCE_ROOT,
472 '')
473 if not os.path.exists(os.path.join(self._clank_dir, 'orderfiles')):
474 os.makedirs(os.path.join(self._clank_dir, 'orderfiles'))
475 else:
476 self._clank_dir = os.path.join(constants.DIR_SOURCE_ROOT,
477 'clank')
478
479 self._unpatched_orderfile_filename = os.path.join(
480 self._clank_dir, 'orderfiles', 'unpatched_orderfile.%s')
481 self._path_to_orderfile = os.path.join(
482 self._clank_dir, 'orderfiles', 'orderfile.%s.out')
483
Benoit Lizea3fe2932017-10-20 10:24:52484 def _GetPathToOrderfile(self):
485 """Gets the path to the architecture-specific orderfile."""
Benoit Lize6208ddc2021-08-30 12:26:29486 # Build GN files use the ".arm" orderfile irrespective of the actual
487 # architecture. Fake it, otherwise the orderfile we generate here is not
488 # going to be picked up by builds.
489 orderfile_fake_arch = 'arm'
490 return self._path_to_orderfile % orderfile_fake_arch
Benoit Lizea3fe2932017-10-20 10:24:52491
492 def _GetUnpatchedOrderfileFilename(self):
493 """Gets the path to the architecture-specific unpatched orderfile."""
Stephen Kylef11339f2019-03-25 09:00:47494 return self._unpatched_orderfile_filename % self._options.arch
Benoit Lizea3fe2932017-10-20 10:24:52495
Monica Salamaeea2d942019-03-11 12:36:18496 def _SetDevice(self):
497 """ Selects the device to be used by the script.
498
499 Returns:
500 (Device with given serial ID) : if the --device flag is set.
501 (Device running Android[K,L]) : if --use-legacy-chrome-apk flag is set or
502 no device running Android N+ was found.
503 (Device running Android N+) : Otherwise.
504
505 Raises Error:
506 If no device meeting the requirements has been found.
507 """
508 devices = None
509 if self._options.device:
510 devices = [device_utils.DeviceUtils(self._options.device)]
511 else:
512 devices = device_utils.DeviceUtils.HealthyDevices()
513
514 assert devices, 'Expected at least one connected device'
515
516 if self._options.use_legacy_chrome_apk:
517 self._monochrome = False
518 for device in devices:
519 device_version = device.build_version_sdk
520 if (device_version >= version_codes.KITKAT
521 and device_version <= version_codes.LOLLIPOP_MR1):
522 return device
523
524 assert not self._options.use_legacy_chrome_apk, \
525 'No device found running suitable android version for Chrome.apk.'
526
527 preferred_device = None
528 for device in devices:
529 if device.build_version_sdk >= version_codes.NOUGAT:
Monica Salama90b8f5b2019-04-25 11:10:38530 preferred_device = device
531 break
Monica Salamaeea2d942019-03-11 12:36:18532
533 self._monochrome = preferred_device is not None
534
535 return preferred_device if preferred_device else devices[0]
536
537
Benoit Lizea3fe2932017-10-20 10:24:52538 def __init__(self, options, orderfile_updater_class):
539 self._options = options
Benoit Lizea3fe2932017-10-20 10:24:52540 self._instrumented_out_dir = os.path.join(
541 self._BUILD_ROOT, self._options.arch + '_instrumented_out')
Monica Basta99c101f2019-05-21 13:50:05542 if self._options.use_call_graph:
Christopher Grantdfe1bac2019-07-05 13:34:10543 self._instrumented_out_dir += '_call_graph'
Monica Basta99c101f2019-05-21 13:50:05544
Benoit Lizea3fe2932017-10-20 10:24:52545 self._uninstrumented_out_dir = os.path.join(
546 self._BUILD_ROOT, self._options.arch + '_uninstrumented_out')
Monica Salama90b8f5b2019-04-25 11:10:38547 self._no_orderfile_out_dir = os.path.join(
548 self._BUILD_ROOT, self._options.arch + '_no_orderfile_out')
Benoit Lizea3fe2932017-10-20 10:24:52549
Stephen Kylef11339f2019-03-25 09:00:47550 self._PrepareOrderfilePaths()
551
Benoit Lizea3fe2932017-10-20 10:24:52552 if options.profile:
Benoit Lizea1b64f82017-12-07 10:12:50553 output_directory = os.path.join(self._instrumented_out_dir, 'Release')
Matthew Caryb8daed942018-06-11 10:58:08554 host_profile_dir = os.path.join(output_directory, 'profile_data')
Benoit Lizea1b64f82017-12-07 10:12:50555 urls = [profile_android_startup.AndroidProfileTool.TEST_URL]
556 use_wpr = True
557 simulate_user = False
Benoit L96466812018-03-06 15:58:37558 urls = options.urls
559 use_wpr = not options.no_wpr
560 simulate_user = options.simulate_user
Monica Salamaeea2d942019-03-11 12:36:18561 device = self._SetDevice()
Benoit Lizea3fe2932017-10-20 10:24:52562 self._profiler = profile_android_startup.AndroidProfileTool(
Matthew Cary453ff1452018-07-18 12:19:35563 output_directory, host_profile_dir, use_wpr, urls, simulate_user,
Matthew Cary1ea82212019-03-20 12:10:27564 device, debug=self._options.streamline_for_debugging)
Matthew Cary69e9e422018-08-10 18:30:06565 if options.pregenerated_profiles:
566 self._profiler.SetPregeneratedProfiles(
567 glob.glob(options.pregenerated_profiles))
568 else:
569 assert not options.pregenerated_profiles, (
570 '--pregenerated-profiles cannot be used with --skip-profile')
571 assert not options.profile_save_dir, (
572 '--profile-save-dir cannot be used with --skip-profile')
Monica Salamaeea2d942019-03-11 12:36:18573 self._monochrome = not self._options.use_legacy_chrome_apk
Benoit Lizea3fe2932017-10-20 10:24:52574
Matthew Caryf949bba2019-02-04 13:39:23575 # Outlined function handling enabled by default for all architectures.
576 self._order_outlined_functions = not options.noorder_outlined_functions
577
Benoit Lizea3fe2932017-10-20 10:24:52578 self._output_data = {}
579 self._step_recorder = StepRecorder(options.buildbot)
580 self._compiler = None
Stephen Kylef11339f2019-03-25 09:00:47581 if orderfile_updater_class is None:
Egor Paskod80bfad2020-09-10 21:53:06582 orderfile_updater_class = OrderfileUpdater
Benoit Lizea3fe2932017-10-20 10:24:52583 assert issubclass(orderfile_updater_class, OrderfileUpdater)
Stephen Kylef11339f2019-03-25 09:00:47584 self._orderfile_updater = orderfile_updater_class(self._clank_dir,
Egor Pasko0c533c6682019-11-26 21:16:32585 self._step_recorder)
Benoit Lizea3fe2932017-10-20 10:24:52586 assert os.path.isdir(constants.DIR_SOURCE_ROOT), 'No src directory found'
587
Benoit Lizea3fe2932017-10-20 10:24:52588 @staticmethod
589 def _RemoveBlanks(src_file, dest_file):
590 """A utility to remove blank lines from a file.
591
592 Args:
593 src_file: The name of the file to remove the blanks from.
594 dest_file: The name of the file to write the output without blanks.
595 """
596 assert src_file != dest_file, 'Source and destination need to be distinct'
597
598 try:
599 src = open(src_file, 'r')
600 dest = open(dest_file, 'w')
601 for line in src:
602 if line and not line.isspace():
603 dest.write(line)
604 finally:
605 src.close()
606 dest.close()
607
608 def _GenerateAndProcessProfile(self):
Matthew Cary78aae162018-08-10 17:16:30609 """Invokes a script to merge the per-thread traces into one file.
610
611 The produced list of offsets is saved in
612 self._GetUnpatchedOrderfileFilename().
613 """
Benoit Lizea3fe2932017-10-20 10:24:52614 self._step_recorder.BeginStep('Generate Profile Data')
615 files = []
Matthew Cary78aae162018-08-10 17:16:30616 logging.getLogger().setLevel(logging.DEBUG)
Christopher Grantdfe1bac2019-07-05 13:34:10617
618 if self._options.profile_save_dir:
619 # The directory must not preexist, to ensure purity of data. Check
620 # before profiling to save time.
621 if os.path.exists(self._options.profile_save_dir):
622 raise Exception('Profile save directory must not pre-exist')
623 os.makedirs(self._options.profile_save_dir)
624
Matthew Cary78aae162018-08-10 17:16:30625 if self._options.system_health_orderfile:
626 files = self._profiler.CollectSystemHealthProfile(
627 self._compiler.chrome_apk)
Matthew Cary69e9e422018-08-10 18:30:06628 self._MaybeSaveProfile(files)
Matthew Cary78aae162018-08-10 17:16:30629 try:
630 self._ProcessPhasedOrderfile(files)
631 except Exception:
632 for f in files:
633 self._SaveForDebugging(f)
Matthew Cary8b1416232018-08-10 19:12:22634 self._SaveForDebugging(self._compiler.lib_chrome_so)
Matthew Cary78aae162018-08-10 17:16:30635 raise
636 finally:
637 self._profiler.Cleanup()
638 else:
639 self._CollectLegacyProfile()
640 logging.getLogger().setLevel(logging.INFO)
641
642 def _ProcessPhasedOrderfile(self, files):
643 """Process the phased orderfiles produced by system health benchmarks.
644
645 The offsets will be placed in _GetUnpatchedOrderfileFilename().
646
647 Args:
648 file: Profile files pulled locally.
649 """
650 self._step_recorder.BeginStep('Process Phased Orderfile')
651 profiles = process_profiles.ProfileManager(files)
652 processor = process_profiles.SymbolOffsetProcessor(
653 self._compiler.lib_chrome_so)
Monica Basta99c101f2019-05-21 13:50:05654 ordered_symbols = cluster.ClusterOffsets(profiles, processor,
655 call_graph=self._options.use_call_graph)
Matthew Cary8b1416232018-08-10 19:12:22656 if not ordered_symbols:
657 raise Exception('Failed to get ordered symbols')
Matthew Caryda9db932019-03-11 15:28:17658 for sym in ordered_symbols:
659 assert not sym.startswith('OUTLINED_FUNCTION_'), (
660 'Outlined function found in instrumented function, very likely '
661 'something has gone very wrong!')
Matthew Cary91df9792018-11-30 14:35:15662 self._output_data['offsets_kib'] = processor.SymbolsSize(
663 ordered_symbols) / 1024
Matthew Cary78aae162018-08-10 17:16:30664 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
Matthew Cary8b1416232018-08-10 19:12:22665 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary78aae162018-08-10 17:16:30666
667 def _CollectLegacyProfile(self):
Matthew Cary2df8d452018-09-19 07:50:20668 files = []
Benoit Lizea3fe2932017-10-20 10:24:52669 try:
Benoit Lizea3fe2932017-10-20 10:24:52670 files = self._profiler.CollectProfile(
671 self._compiler.chrome_apk,
672 constants.PACKAGE_INFO['chrome'])
Matthew Cary69e9e422018-08-10 18:30:06673 self._MaybeSaveProfile(files)
Matthew Caryb8daed942018-06-11 10:58:08674 self._step_recorder.BeginStep('Process profile')
Benoit L96466812018-03-06 15:58:37675 assert os.path.exists(self._compiler.lib_chrome_so)
676 offsets = process_profiles.GetReachedOffsetsFromDumpFiles(
677 files, self._compiler.lib_chrome_so)
678 if not offsets:
679 raise Exception('No profiler offsets found in {}'.format(
680 '\n'.join(files)))
Matthew Cary8b1416232018-08-10 19:12:22681 processor = process_profiles.SymbolOffsetProcessor(
682 self._compiler.lib_chrome_so)
683 ordered_symbols = processor.GetOrderedSymbols(offsets)
684 if not ordered_symbols:
685 raise Exception('No symbol names from offsets found in {}'.format(
686 '\n'.join(files)))
687 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
688 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary0f1f681a2018-01-22 10:40:51689 except Exception:
Benoit Lizea3fe2932017-10-20 10:24:52690 for f in files:
691 self._SaveForDebugging(f)
692 raise
693 finally:
694 self._profiler.Cleanup()
Benoit Lizea3fe2932017-10-20 10:24:52695
Matthew Cary69e9e422018-08-10 18:30:06696 def _MaybeSaveProfile(self, files):
697 if self._options.profile_save_dir:
698 logging.info('Saving profiles to %s', self._options.profile_save_dir)
699 for f in files:
700 shutil.copy(f, self._options.profile_save_dir)
701 logging.info('Saved profile %s', f)
702
Benoit Lizea3fe2932017-10-20 10:24:52703 def _PatchOrderfile(self):
704 """Patches the orderfile using clean version of libchrome.so."""
705 self._step_recorder.BeginStep('Patch Orderfile')
Benoit Lizefefbb27c2018-01-17 13:54:18706 patch_orderfile.GeneratePatchedOrderfile(
707 self._GetUnpatchedOrderfileFilename(), self._compiler.lib_chrome_so,
Matthew Caryf949bba2019-02-04 13:39:23708 self._GetPathToOrderfile(), self._order_outlined_functions)
Benoit Lizea3fe2932017-10-20 10:24:52709
710 def _VerifySymbolOrder(self):
711 self._step_recorder.BeginStep('Verify Symbol Order')
Andrew Grievec45bb952021-11-02 18:33:27712 return_code = self._step_recorder.RunCommand([
713 self._CHECK_ORDERFILE_SCRIPT, self._compiler.lib_chrome_so,
714 self._GetPathToOrderfile()
715 ],
716 constants.DIR_SOURCE_ROOT,
717 raise_on_error=False)
Benoit Lizea3fe2932017-10-20 10:24:52718 if return_code:
719 self._step_recorder.FailStep('Orderfile check returned %d.' % return_code)
720
721 def _RecordHash(self, file_name):
722 """Records the hash of the file into the output_data dictionary."""
723 self._output_data[os.path.basename(file_name) + '.sha1'] = _GenerateHash(
724 file_name)
725
726 def _SaveFileLocally(self, file_name, file_sha1):
727 """Saves the file to a temporary location and prints the sha1sum."""
728 if not os.path.exists(self._DIRECTORY_FOR_DEBUG_FILES):
729 os.makedirs(self._DIRECTORY_FOR_DEBUG_FILES)
730 shutil.copy(file_name, self._DIRECTORY_FOR_DEBUG_FILES)
Raul Tambre48f176622019-09-23 10:05:24731 print('File: %s, saved in: %s, sha1sum: %s' %
732 (file_name, self._DIRECTORY_FOR_DEBUG_FILES, file_sha1))
Benoit Lizea3fe2932017-10-20 10:24:52733
734 def _SaveForDebugging(self, filename):
735 """Uploads the file to cloud storage or saves to a temporary location."""
736 file_sha1 = _GenerateHash(filename)
737 if not self._options.buildbot:
738 self._SaveFileLocally(filename, file_sha1)
739 else:
Raul Tambre48f176622019-09-23 10:05:24740 print('Uploading file for debugging: ' + filename)
Benoit Lizea3fe2932017-10-20 10:24:52741 self._orderfile_updater.UploadToCloudStorage(
742 filename, use_debug_location=True)
743
744 def _SaveForDebuggingWithOverwrite(self, file_name):
745 """Uploads and overwrites the file in cloud storage or copies locally.
746
747 Should be used for large binaries like lib_chrome_so.
748
749 Args:
750 file_name: (str) File to upload.
751 """
752 file_sha1 = _GenerateHash(file_name)
753 if not self._options.buildbot:
754 self._SaveFileLocally(file_name, file_sha1)
755 else:
Raul Tambre48f176622019-09-23 10:05:24756 print('Uploading file for debugging: %s, sha1sum: %s' % (file_name,
757 file_sha1))
Benoit Lizea3fe2932017-10-20 10:24:52758 upload_location = '%s/%s' % (
759 self._CLOUD_STORAGE_BUCKET_FOR_DEBUG, os.path.basename(file_name))
760 self._step_recorder.RunCommand([
761 'gsutil.py', 'cp', file_name, 'gs://' + upload_location])
Raul Tambre48f176622019-09-23 10:05:24762 print('Uploaded to: https://sandbox.google.com/storage/' +
763 upload_location)
Benoit Lizea3fe2932017-10-20 10:24:52764
765 def _MaybeArchiveOrderfile(self, filename):
766 """In buildbot configuration, uploads the generated orderfile to
767 Google Cloud Storage.
768
769 Args:
770 filename: (str) Orderfile to upload.
771 """
Matthew Cary91df9792018-11-30 14:35:15772 # First compute hashes so that we can download them later if we need to.
Benoit Lizea3fe2932017-10-20 10:24:52773 self._step_recorder.BeginStep('Compute hash for ' + filename)
774 self._RecordHash(filename)
775 if self._options.buildbot:
776 self._step_recorder.BeginStep('Archive ' + filename)
777 self._orderfile_updater.UploadToCloudStorage(
778 filename, use_debug_location=False)
779
Egor Paskobce64d012018-11-20 12:02:37780 def UploadReadyOrderfiles(self):
781 self._step_recorder.BeginStep('Upload Ready Orderfiles')
782 for file_name in [self._GetUnpatchedOrderfileFilename(),
783 self._GetPathToOrderfile()]:
784 self._orderfile_updater.UploadToCloudStorage(
785 file_name, use_debug_location=False)
786
Monica Salama90b8f5b2019-04-25 11:10:38787 def _NativeCodeMemoryBenchmark(self, apk):
788 """Runs system_health.memory_mobile to assess native code memory footprint.
789
790 Args:
791 apk: (str) Path to the apk.
792
793 Returns:
794 results: ([int]) Values of native code memory footprint in bytes from the
795 benchmark results.
796 """
797 self._step_recorder.BeginStep("Running orderfile.memory_mobile")
798 try:
799 out_dir = tempfile.mkdtemp()
800 self._profiler._RunCommand(['tools/perf/run_benchmark',
801 '--device={}'.format(
802 self._profiler._device.serial),
803 '--browser=exact',
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54804 '--output-format=csv',
Monica Salama90b8f5b2019-04-25 11:10:38805 '--output-dir={}'.format(out_dir),
806 '--reset-results',
807 '--browser-executable={}'.format(apk),
808 'orderfile.memory_mobile'])
809
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54810 out_file_path = os.path.join(out_dir, 'results.csv')
Monica Salama90b8f5b2019-04-25 11:10:38811 if not os.path.exists(out_file_path):
Monica Basta8ec87fd2019-05-13 12:12:35812 raise Exception('Results file not found!')
Monica Salama90b8f5b2019-04-25 11:10:38813
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54814 results = {}
Monica Salama90b8f5b2019-04-25 11:10:38815 with open(out_file_path, 'r') as f:
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54816 reader = csv.DictReader(f)
817 for row in reader:
818 if not row['name'].endswith('NativeCodeResidentMemory'):
Monica Basta8ec87fd2019-05-13 12:12:35819 continue
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54820 # Note: NativeCodeResidentMemory records a single sample from each
821 # story run, so this average (reported as 'avg') is exactly the value
822 # of that one sample. Each story is run multiple times, so this loop
823 # will accumulate into a list all values for all runs of each story.
824 results.setdefault(row['name'], {}).setdefault(
825 row['stories'], []).append(row['avg'])
Monica Basta8ec87fd2019-05-13 12:12:35826
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54827 if not results:
828 raise Exception('Could not find relevant results')
829
Monica Basta8ec87fd2019-05-13 12:12:35830 return results
831
832 except Exception as e:
833 return 'Error: ' + str(e)
Monica Salama90b8f5b2019-04-25 11:10:38834
835 finally:
836 shutil.rmtree(out_dir)
837
Monica Salama90b8f5b2019-04-25 11:10:38838
839 def _PerformanceBenchmark(self, apk):
840 """Runs Speedometer2.0 to assess performance.
841
842 Args:
843 apk: (str) Path to the apk.
844
845 Returns:
846 results: ([float]) Speedometer2.0 results samples in milliseconds.
847 """
848 self._step_recorder.BeginStep("Running Speedometer2.0.")
849 try:
850 out_dir = tempfile.mkdtemp()
851 self._profiler._RunCommand(['tools/perf/run_benchmark',
852 '--device={}'.format(
853 self._profiler._device.serial),
854 '--browser=exact',
Monica Basta8ec87fd2019-05-13 12:12:35855 '--output-format=histograms',
Monica Salama90b8f5b2019-04-25 11:10:38856 '--output-dir={}'.format(out_dir),
857 '--reset-results',
858 '--browser-executable={}'.format(apk),
859 'speedometer2'])
860
Monica Basta8ec87fd2019-05-13 12:12:35861 out_file_path = os.path.join(out_dir, 'histograms.json')
Monica Salama90b8f5b2019-04-25 11:10:38862 if not os.path.exists(out_file_path):
Monica Basta8ec87fd2019-05-13 12:12:35863 raise Exception('Results file not found!')
Monica Salama90b8f5b2019-04-25 11:10:38864
865 with open(out_file_path, 'r') as f:
866 results = json.load(f)
867
868 if not results:
Monica Basta8ec87fd2019-05-13 12:12:35869 raise Exception('Results file is empty.')
870
871 for el in results:
872 if 'name' in el and el['name'] == 'Total' and 'sampleValues' in el:
873 return el['sampleValues']
874
875 raise Exception('Unexpected results format.')
876
877 except Exception as e:
878 return 'Error: ' + str(e)
Monica Salama90b8f5b2019-04-25 11:10:38879
880 finally:
881 shutil.rmtree(out_dir)
882
Monica Salama90b8f5b2019-04-25 11:10:38883
884 def RunBenchmark(self, out_directory, no_orderfile=False):
885 """Builds chrome apk and runs performance and memory benchmarks.
886
887 Builds a non-instrumented version of chrome.
888 Installs chrome apk on the device.
889 Runs Speedometer2.0 benchmark to assess performance.
890 Runs system_health.memory_mobile to evaluate memory footprint.
891
892 Args:
893 out_directory: (str) Path to out directory for this build.
894 no_orderfile: (bool) True if chrome to be built without orderfile.
895
896 Returns:
897 benchmark_results: (dict) Results extracted from benchmarks.
898 """
Benoit Lize74e82b32021-08-26 14:22:01899 benchmark_results = {}
Monica Salama90b8f5b2019-04-25 11:10:38900 try:
901 _UnstashOutputDirectory(out_directory)
Christopher Grant073637472019-07-05 13:34:57902 self._compiler = ClankCompiler(out_directory, self._step_recorder,
903 self._options.arch, self._options.use_goma,
904 self._options.goma_dir,
905 self._options.system_health_orderfile,
906 self._monochrome, self._options.public,
907 self._GetPathToOrderfile())
Monica Salama90b8f5b2019-04-25 11:10:38908
909 if no_orderfile:
910 orderfile_path = self._GetPathToOrderfile()
911 backup_orderfile = orderfile_path + '.backup'
912 shutil.move(orderfile_path, backup_orderfile)
913 open(orderfile_path, 'w').close()
914
915 # Build APK to be installed on the device.
Monica Basta99c101f2019-05-21 13:50:05916 self._compiler.CompileChromeApk(instrumented=False,
917 use_call_graph=False,
918 force_relink=True)
Monica Salama90b8f5b2019-04-25 11:10:38919 benchmark_results['Speedometer2.0'] = self._PerformanceBenchmark(
920 self._compiler.chrome_apk)
921 benchmark_results['orderfile.memory_mobile'] = (
922 self._NativeCodeMemoryBenchmark(self._compiler.chrome_apk))
Monica Basta8ec87fd2019-05-13 12:12:35923
924 except Exception as e:
925 benchmark_results['Error'] = str(e)
926
Monica Salama90b8f5b2019-04-25 11:10:38927 finally:
928 if no_orderfile and os.path.exists(backup_orderfile):
929 shutil.move(backup_orderfile, orderfile_path)
930 _StashOutputDirectory(out_directory)
931
932 return benchmark_results
933
Benoit Lizea3fe2932017-10-20 10:24:52934 def Generate(self):
935 """Generates and maybe upload an order."""
Matthew Carye8400642018-06-14 15:43:02936 assert (bool(self._options.profile) ^
937 bool(self._options.manual_symbol_offsets))
Matthew Cary78aae162018-08-10 17:16:30938 if self._options.system_health_orderfile and not self._options.profile:
939 raise AssertionError('--system_health_orderfile must be not be used '
940 'with --skip-profile')
941 if (self._options.manual_symbol_offsets and
942 not self._options.system_health_orderfile):
943 raise AssertionError('--manual-symbol-offsets must be used with '
944 '--system_health_orderfile.')
Matthew Carye8400642018-06-14 15:43:02945
Benoit Lizea3fe2932017-10-20 10:24:52946 if self._options.profile:
947 try:
948 _UnstashOutputDirectory(self._instrumented_out_dir)
949 self._compiler = ClankCompiler(
Christopher Grant073637472019-07-05 13:34:57950 self._instrumented_out_dir, self._step_recorder, self._options.arch,
951 self._options.use_goma, self._options.goma_dir,
952 self._options.system_health_orderfile, self._monochrome,
953 self._options.public, self._GetPathToOrderfile())
Matthew Carya2bea452018-11-13 10:21:07954 if not self._options.pregenerated_profiles:
955 # If there are pregenerated profiles, the instrumented build should
956 # not be changed to avoid invalidating the pregenerated profile
957 # offsets.
Monica Basta99c101f2019-05-21 13:50:05958 self._compiler.CompileChromeApk(instrumented=True,
959 use_call_graph=
960 self._options.use_call_graph)
Benoit Lizea3fe2932017-10-20 10:24:52961 self._GenerateAndProcessProfile()
962 self._MaybeArchiveOrderfile(self._GetUnpatchedOrderfileFilename())
Benoit Lizea3fe2932017-10-20 10:24:52963 finally:
Benoit Lizea3fe2932017-10-20 10:24:52964 _StashOutputDirectory(self._instrumented_out_dir)
Matthew Carye8400642018-06-14 15:43:02965 elif self._options.manual_symbol_offsets:
966 assert self._options.manual_libname
967 assert self._options.manual_objdir
Ari Chivukulaa52f8ba2021-08-10 22:30:39968 with open(self._options.manual_symbol_offsets) as f:
969 symbol_offsets = [int(x) for x in f]
Matthew Carye8400642018-06-14 15:43:02970 processor = process_profiles.SymbolOffsetProcessor(
Matthew Caryb46ad282018-11-23 14:43:57971 self._compiler.manual_libname)
Matthew Carye8400642018-06-14 15:43:02972 generator = cyglog_to_orderfile.OffsetOrderfileGenerator(
973 processor, cyglog_to_orderfile.ObjectFileProcessor(
974 self._options.manual_objdir))
975 ordered_sections = generator.GetOrderedSections(symbol_offsets)
976 if not ordered_sections: # Either None or empty is a problem.
977 raise Exception('Failed to get ordered sections')
978 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
979 orderfile.write('\n'.join(ordered_sections))
980
Benoit Lizea3fe2932017-10-20 10:24:52981 if self._options.patch:
982 if self._options.profile:
983 self._RemoveBlanks(self._GetUnpatchedOrderfileFilename(),
984 self._GetPathToOrderfile())
985 try:
986 _UnstashOutputDirectory(self._uninstrumented_out_dir)
987 self._compiler = ClankCompiler(
988 self._uninstrumented_out_dir, self._step_recorder,
Christopher Grant073637472019-07-05 13:34:57989 self._options.arch, self._options.use_goma, self._options.goma_dir,
Stephen Kylef11339f2019-03-25 09:00:47990 self._options.system_health_orderfile, self._monochrome,
991 self._options.public, self._GetPathToOrderfile())
992
Monica Basta99c101f2019-05-21 13:50:05993 self._compiler.CompileLibchrome(instrumented=False,
994 use_call_graph=False)
Benoit Lizea3fe2932017-10-20 10:24:52995 self._PatchOrderfile()
996 # Because identical code folding is a bit different with and without
997 # the orderfile build, we need to re-patch the orderfile with code
998 # folding as close to the final version as possible.
Monica Basta99c101f2019-05-21 13:50:05999 self._compiler.CompileLibchrome(instrumented=False,
1000 use_call_graph=False, force_relink=True)
Benoit Lizea3fe2932017-10-20 10:24:521001 self._PatchOrderfile()
Monica Basta99c101f2019-05-21 13:50:051002 self._compiler.CompileLibchrome(instrumented=False,
1003 use_call_graph=False, force_relink=True)
Benoit Lizea3fe2932017-10-20 10:24:521004 self._VerifySymbolOrder()
1005 self._MaybeArchiveOrderfile(self._GetPathToOrderfile())
1006 finally:
1007 _StashOutputDirectory(self._uninstrumented_out_dir)
Benoit Lizea3fe2932017-10-20 10:24:521008
Monica Salama90b8f5b2019-04-25 11:10:381009 if self._options.benchmark:
1010 self._output_data['orderfile_benchmark_results'] = self.RunBenchmark(
1011 self._uninstrumented_out_dir)
1012 self._output_data['no_orderfile_benchmark_results'] = self.RunBenchmark(
1013 self._no_orderfile_out_dir, no_orderfile=True)
1014
Egor Paskod80bfad2020-09-10 21:53:061015 if self._options.buildbot:
1016 self._orderfile_updater._GitStash()
Benoit Lizea3fe2932017-10-20 10:24:521017 self._step_recorder.EndStep()
1018 return not self._step_recorder.ErrorRecorded()
1019
1020 def GetReportingData(self):
1021 """Get a dictionary of reporting data (timings, output hashes)"""
1022 self._output_data['timings'] = self._step_recorder.timings
1023 return self._output_data
1024
Matthew Cary86a226e2019-03-19 12:17:441025 def CommitStashedOrderfileHashes(self):
1026 """Commit any orderfile hash files in the current checkout.
1027
1028 Only possible if running on the buildbot.
1029
1030 Returns: true on success.
1031 """
Egor Pasko7ff04122019-11-25 15:47:181032 if not self._options.buildbot:
Matthew Cary86a226e2019-03-19 12:17:441033 logging.error('Trying to commit when not running on the buildbot')
1034 return False
1035 self._orderfile_updater._CommitStashedFiles([
1036 filename + '.sha1'
1037 for filename in (self._GetUnpatchedOrderfileFilename(),
1038 self._GetPathToOrderfile())])
1039 return True
1040
Benoit Lizea3fe2932017-10-20 10:24:521041
Benoit Lizea1b64f82017-12-07 10:12:501042def CreateArgumentParser():
1043 """Creates and returns the argument parser."""
1044 parser = argparse.ArgumentParser()
Monica Salama90b8f5b2019-04-25 11:10:381045 parser.add_argument('--no-benchmark', action='store_false', dest='benchmark',
1046 default=True, help='Disables running benchmarks.')
Benoit Lizea1b64f82017-12-07 10:12:501047 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521048 '--buildbot', action='store_true',
1049 help='If true, the script expects to be run on a buildbot')
Benoit Lizea1b64f82017-12-07 10:12:501050 parser.add_argument(
Matthew Cary453ff1452018-07-18 12:19:351051 '--device', default=None, type=str,
1052 help='Device serial number on which to run profiling.')
1053 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521054 '--verify', action='store_true',
1055 help='If true, the script only verifies the current orderfile')
Benoit Lizef5581722021-08-26 09:48:201056 parser.add_argument('--target-arch',
1057 action='store',
1058 dest='arch',
Stephen Martinis71b391b52018-12-04 15:08:041059 default='arm',
Benoit Lizef5581722021-08-26 09:48:201060 choices=list(_ARCH_GN_ARGS.keys()),
Matthew Cary53f74ba2019-01-24 10:07:181061 help='The target architecture for which to build.')
Benoit Lizea1b64f82017-12-07 10:12:501062 parser.add_argument('--output-json', action='store', dest='json_file',
1063 help='Location to save stats in json format')
1064 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521065 '--skip-profile', action='store_false', dest='profile', default=True,
1066 help='Don\'t generate a profile on the device. Only patch from the '
1067 'existing profile.')
Benoit Lizea1b64f82017-12-07 10:12:501068 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521069 '--skip-patch', action='store_false', dest='patch', default=True,
1070 help='Only generate the raw (unpatched) orderfile, don\'t patch it.')
Benoit Lizea1b64f82017-12-07 10:12:501071 parser.add_argument('--goma-dir', help='GOMA directory.')
1072 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521073 '--use-goma', action='store_true', help='Enable GOMA.', default=False)
Benoit Lizea1b64f82017-12-07 10:12:501074 parser.add_argument('--adb-path', help='Path to the adb binary.')
Matthew Carye8400642018-06-14 15:43:021075
Egor Paskod80bfad2020-09-10 21:53:061076 parser.add_argument('--public',
1077 action='store_true',
1078 help='Build non-internal APK and change the orderfile '
1079 'location. Required if your checkout is non-internal.',
Stephen Kylef11339f2019-03-25 09:00:471080 default=False)
Matthew Cary04f41032018-12-10 15:55:271081 parser.add_argument('--nosystem-health-orderfile', action='store_false',
Benoit Lcba421e2019-04-26 15:28:261082 dest='system_health_orderfile', default=True,
Matthew Caryc262f5d2018-09-19 14:36:281083 help=('Create an orderfile based on an about:blank '
1084 'startup benchmark instead of system health '
1085 'benchmarks.'))
Monica Salamaeea2d942019-03-11 12:36:181086 parser.add_argument(
1087 '--use-legacy-chrome-apk', action='store_true', default=False,
1088 help=('Compile and instrument chrome for [L, K] devices.'))
Matthew Carye8400642018-06-14 15:43:021089 parser.add_argument('--manual-symbol-offsets', default=None, type=str,
1090 help=('File of list of ordered symbol offsets generated '
1091 'by manual profiling. Must set other --manual* '
1092 'flags if this is used, and must --skip-profile.'))
1093 parser.add_argument('--manual-libname', default=None, type=str,
1094 help=('Library filename corresponding to '
1095 '--manual-symbol-offsets.'))
1096 parser.add_argument('--manual-objdir', default=None, type=str,
1097 help=('Root of object file directory corresponding to '
1098 '--manual-symbol-offsets.'))
Matthew Caryf949bba2019-02-04 13:39:231099 parser.add_argument('--noorder-outlined-functions', action='store_true',
1100 help='Disable outlined functions in the orderfile.')
Matthew Cary69e9e422018-08-10 18:30:061101 parser.add_argument('--pregenerated-profiles', default=None, type=str,
1102 help=('Pregenerated profiles to use instead of running '
1103 'profile step. Cannot be used with '
1104 '--skip-profiles.'))
1105 parser.add_argument('--profile-save-dir', default=None, type=str,
1106 help=('Directory to save any profiles created. These can '
1107 'be used with --pregenerated-profiles. Cannot be '
1108 'used with --skip-profiles.'))
Egor Paskobce64d012018-11-20 12:02:371109 parser.add_argument('--upload-ready-orderfiles', action='store_true',
1110 help=('Skip orderfile generation and manually upload '
1111 'orderfiles (both patched and unpatched) from '
1112 'their normal location in the tree to the cloud '
1113 'storage. DANGEROUS! USE WITH CARE!'))
Matthew Cary1ea82212019-03-20 12:10:271114 parser.add_argument('--streamline-for-debugging', action='store_true',
1115 help=('Streamline where possible the run for faster '
1116 'iteration while debugging. The orderfile '
1117 'generated will be valid and nontrivial, but '
1118 'may not be based on a representative profile '
1119 'or other such considerations. Use with caution.'))
Matthew Cary86a226e2019-03-19 12:17:441120 parser.add_argument('--commit-hashes', action='store_true',
1121 help=('Commit any orderfile hash files in the current '
1122 'checkout; performs no other action'))
Monica Basta99c101f2019-05-21 13:50:051123 parser.add_argument('--use-call-graph', action='store_true', default=False,
1124 help='Use call graph instrumentation.')
Benoit Lizea1b64f82017-12-07 10:12:501125 profile_android_startup.AddProfileCollectionArguments(parser)
Benoit Lizea3fe2932017-10-20 10:24:521126 return parser
1127
1128
Stephen Kylef11339f2019-03-25 09:00:471129def CreateOrderfile(options, orderfile_updater_class=None):
Christopher Grantdfe1bac2019-07-05 13:34:101130 """Creates an orderfile.
Benoit Lizea3fe2932017-10-20 10:24:521131
1132 Args:
1133 options: As returned from optparse.OptionParser.parse_args()
1134 orderfile_updater_class: (OrderfileUpdater) subclass of OrderfileUpdater.
1135
1136 Returns:
1137 True iff success.
1138 """
1139 logging.basicConfig(level=logging.INFO)
1140 devil_chromium.Initialize(adb_path=options.adb_path)
1141
Benoit Lize6208ddc2021-08-30 12:26:291142 # Since we generate a ".arm" orderfile irrespective of the architecture (see
1143 # comment in _GetPathToOrderfile()), make sure that we don't commit it.
1144 if options.arch != 'arm':
1145 assert not options.buildbot, (
1146 'ARM is the only supported architecture on bots')
1147 assert not options.upload_ready_orderfiles, (
1148 'ARM is the only supported architecture on bots')
1149
Egor Pasko93e514e2017-10-31 13:32:361150 generator = OrderfileGenerator(options, orderfile_updater_class)
Benoit Lizea3fe2932017-10-20 10:24:521151 try:
1152 if options.verify:
Egor Pasko93e514e2017-10-31 13:32:361153 generator._VerifySymbolOrder()
Matthew Cary86a226e2019-03-19 12:17:441154 elif options.commit_hashes:
Matthew Cary86a226e2019-03-19 12:17:441155 return generator.CommitStashedOrderfileHashes()
Egor Paskobce64d012018-11-20 12:02:371156 elif options.upload_ready_orderfiles:
1157 return generator.UploadReadyOrderfiles()
Benoit Lizea3fe2932017-10-20 10:24:521158 else:
Egor Pasko93e514e2017-10-31 13:32:361159 return generator.Generate()
Benoit Lizea3fe2932017-10-20 10:24:521160 finally:
Egor Pasko93e514e2017-10-31 13:32:361161 json_output = json.dumps(generator.GetReportingData(),
Benoit Lizea3fe2932017-10-20 10:24:521162 indent=2) + '\n'
1163 if options.json_file:
1164 with open(options.json_file, 'w') as f:
1165 f.write(json_output)
Raul Tambre48f176622019-09-23 10:05:241166 print(json_output)
Benoit Lizea3fe2932017-10-20 10:24:521167 return False
1168
1169
Benoit Lizea1b64f82017-12-07 10:12:501170def main():
1171 parser = CreateArgumentParser()
1172 options = parser.parse_args()
Stephen Kylef11339f2019-03-25 09:00:471173 return 0 if CreateOrderfile(options) else 1
Benoit Lizea3fe2932017-10-20 10:24:521174
1175
1176if __name__ == '__main__':
Benoit Lizea1b64f82017-12-07 10:12:501177 sys.exit(main())