blob: 9beefc6d2cd5075b167c4bb8589a6b0f08ad78fd [file] [log] [blame]
Egor Pasko0462e852d2018-03-29 15:52:091#!/usr/bin/env vpython
Benoit Lizea3fe293d2017-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 Lizea3fe293d2017-10-20 10:24:5214"""
15
Raul Tambre48f176622019-09-23 10:05:2416from __future__ import print_function
17
Benoit Lizea1b64f82017-12-07 10:12:5018import argparse
Egor Paskoa7ac67a42020-09-09 12:06:5819import csv
Benoit Lizea3fe293d2017-10-20 10:24:5220import hashlib
21import json
Matthew Cary69e9e422018-08-10 18:30:0622import glob
Benoit Lizea3fe293d2017-10-20 10:24:5223import logging
Benoit Lizea3fe293d2017-10-20 10:24:5224import os
Benoit Lizea3fe293d2017-10-20 10:24:5225import shutil
26import subprocess
27import sys
Benoit Lizea87e5bce2017-11-07 15:12:5728import tempfile
Benoit Lizea3fe293d2017-10-20 10:24:5229import time
30
Matthew Cary91df9792018-11-30 14:35:1531import cluster
Matthew Carye8400642018-06-14 15:43:0232import cyglog_to_orderfile
Benoit Lizefefbb27c2018-01-17 13:54:1833import patch_orderfile
Benoit Lizea87e5bce2017-11-07 15:12:5734import process_profiles
Egor Pasko3bc0b932018-04-03 10:08:2735import profile_android_startup
Benoit Lizefefbb27c2018-01-17 13:54:1836import symbol_extractor
Benoit Lizea3fe293d2017-10-20 10:24:5237
Monica Salamaeea2d942019-03-11 12:36:1838_SRC_PATH = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
39sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil'))
40from devil.android import device_utils
41from devil.android.sdk import version_codes
42
Benoit Lizea3fe293d2017-10-20 10:24:5243
44_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)),
45 os.pardir, os.pardir)
46sys.path.append(os.path.join(_SRC_PATH, 'build', 'android'))
47import devil_chromium
48from pylib import constants
49
50
51# Needs to happen early for GetBuildType()/GetOutDirectory() to work correctly
52constants.SetBuildType('Release')
53
54
Matthew Cary53f74ba2019-01-24 10:07:1855# Architecture specific GN args. Trying to build an orderfile for an
56# architecture not listed here will eventually throw.
57_ARCH_GN_ARGS = {
58 'arm': [ 'target_cpu = "arm"' ],
59 'arm64': [ 'target_cpu = "arm64"',
60 'android_64bit_browser = true'],
61}
62
Benoit Lizea3fe293d2017-10-20 10:24:5263class CommandError(Exception):
64 """Indicates that a dispatched shell command exited with a non-zero status."""
65
66 def __init__(self, value):
67 super(CommandError, self).__init__()
68 self.value = value
69
70 def __str__(self):
71 return repr(self.value)
72
73
74def _GenerateHash(file_path):
75 """Calculates and returns the hash of the file at file_path."""
76 sha1 = hashlib.sha1()
77 with open(file_path, 'rb') as f:
78 while True:
79 # Read in 1mb chunks, so it doesn't all have to be loaded into memory.
80 chunk = f.read(1024 * 1024)
81 if not chunk:
82 break
83 sha1.update(chunk)
84 return sha1.hexdigest()
85
86
87def _GetFileExtension(file_name):
88 """Calculates the file extension from a file name.
89
90 Args:
91 file_name: The source file name.
92 Returns:
93 The part of file_name after the dot (.) or None if the file has no
94 extension.
95 Examples: /home/user/foo.bar -> bar
96 /home/user.name/foo -> None
97 /home/user/.foo -> None
98 /home/user/foo.bar.baz -> baz
99 """
100 file_name_parts = os.path.basename(file_name).split('.')
101 if len(file_name_parts) > 1:
102 return file_name_parts[-1]
103 else:
104 return None
105
106
107def _StashOutputDirectory(buildpath):
108 """Takes the output directory and stashes it in the default output directory.
109
110 This allows it to be used for incremental builds next time (after unstashing)
111 by keeping it in a place that isn't deleted normally, while also ensuring
112 that it is properly clobbered when appropriate.
113
114 This is a dirty hack to deal with the needs of clobbering while also handling
115 incremental builds and the hardcoded relative paths used in some of the
116 project files.
117
118 Args:
119 buildpath: The path where the building happens. If this corresponds to the
120 default output directory, no action is taken.
121 """
122 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
123 constants.GetOutDirectory())):
124 return
125 name = os.path.basename(buildpath)
126 stashpath = os.path.join(constants.GetOutDirectory(), name)
127 if not os.path.exists(buildpath):
128 return
129 if os.path.exists(stashpath):
130 shutil.rmtree(stashpath, ignore_errors=True)
131 shutil.move(buildpath, stashpath)
132
133
134def _UnstashOutputDirectory(buildpath):
135 """Inverse of _StashOutputDirectory.
136
137 Moves the output directory stashed within the default output directory
138 (out/Release) to the position where the builds can actually happen.
139
140 This is a dirty hack to deal with the needs of clobbering while also handling
141 incremental builds and the hardcoded relative paths used in some of the
142 project files.
143
144 Args:
145 buildpath: The path where the building happens. If this corresponds to the
146 default output directory, no action is taken.
147 """
148 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
149 constants.GetOutDirectory())):
150 return
151 name = os.path.basename(buildpath)
152 stashpath = os.path.join(constants.GetOutDirectory(), name)
153 if not os.path.exists(stashpath):
154 return
155 if os.path.exists(buildpath):
156 shutil.rmtree(buildpath, ignore_errors=True)
157 shutil.move(stashpath, buildpath)
158
159
160class StepRecorder(object):
161 """Records steps and timings."""
162
163 def __init__(self, buildbot):
164 self.timings = []
165 self._previous_step = ('', 0.0)
166 self._buildbot = buildbot
167 self._error_recorded = False
168
169 def BeginStep(self, name):
170 """Marks a beginning of the next step in the script.
171
172 On buildbot, this prints a specially formatted name that will show up
173 in the waterfall. Otherwise, just prints the step name.
174
175 Args:
176 name: The name of the step.
177 """
178 self.EndStep()
179 self._previous_step = (name, time.time())
Raul Tambre48f176622019-09-23 10:05:24180 print('Running step: ', name)
Benoit Lizea3fe293d2017-10-20 10:24:52181
182 def EndStep(self):
183 """Records successful completion of the current step.
184
185 This is optional if the step is immediately followed by another BeginStep.
186 """
187 if self._previous_step[0]:
188 elapsed = time.time() - self._previous_step[1]
Raul Tambre48f176622019-09-23 10:05:24189 print('Step %s took %f seconds' % (self._previous_step[0], elapsed))
Benoit Lizea3fe293d2017-10-20 10:24:52190 self.timings.append((self._previous_step[0], elapsed))
191
192 self._previous_step = ('', 0.0)
193
194 def FailStep(self, message=None):
195 """Marks that a particular step has failed.
196
197 On buildbot, this will mark the current step as failed on the waterfall.
198 Otherwise we will just print an optional failure message.
199
200 Args:
201 message: An optional explanation as to why the step failed.
202 """
Raul Tambre48f176622019-09-23 10:05:24203 print('STEP FAILED!!')
Benoit Lizea3fe293d2017-10-20 10:24:52204 if message:
Raul Tambre48f176622019-09-23 10:05:24205 print(message)
Benoit Lizea3fe293d2017-10-20 10:24:52206 self._error_recorded = True
207 self.EndStep()
208
209 def ErrorRecorded(self):
210 """True if FailStep has been called."""
211 return self._error_recorded
212
213 def RunCommand(self, cmd, cwd=constants.DIR_SOURCE_ROOT, raise_on_error=True,
214 stdout=None):
215 """Execute a shell command.
216
217 Args:
218 cmd: A list of command strings.
Matthew Cary78aae162018-08-10 17:16:30219 cwd: Directory in which the command should be executed, defaults to build
220 root of script's location if not specified.
Benoit Lizea3fe293d2017-10-20 10:24:52221 raise_on_error: If true will raise a CommandError if the call doesn't
222 succeed and mark the step as failed.
223 stdout: A file to redirect stdout for the command to.
224
225 Returns:
226 The process's return code.
227
228 Raises:
229 CommandError: An error executing the specified command.
230 """
Raul Tambre48f176622019-09-23 10:05:24231 print('Executing %s in %s' % (' '.join(cmd), cwd))
Benoit Lizea3fe293d2017-10-20 10:24:52232 process = subprocess.Popen(cmd, stdout=stdout, cwd=cwd, env=os.environ)
233 process.wait()
234 if raise_on_error and process.returncode != 0:
235 self.FailStep()
236 raise CommandError('Exception executing command %s' % ' '.join(cmd))
237 return process.returncode
238
239
240class ClankCompiler(object):
241 """Handles compilation of clank."""
242
Christopher Grant073637472019-07-05 13:34:57243 def __init__(self, out_dir, step_recorder, arch, use_goma, goma_dir,
244 system_health_profiling, monochrome, public, orderfile_location):
Benoit Lizea3fe293d2017-10-20 10:24:52245 self._out_dir = out_dir
246 self._step_recorder = step_recorder
Benoit Lizea87e5bce2017-11-07 15:12:57247 self._arch = arch
Benoit Lizea3fe293d2017-10-20 10:24:52248 self._use_goma = use_goma
Benoit Lizea87e5bce2017-11-07 15:12:57249 self._goma_dir = goma_dir
Matthew Cary78aae162018-08-10 17:16:30250 self._system_health_profiling = system_health_profiling
Stephen Kylef11339f82019-03-25 09:00:47251 self._public = public
252 self._orderfile_location = orderfile_location
Matthew Caryd6bfcb72018-09-14 11:27:46253 if monochrome:
254 self._apk = 'Monochrome.apk'
Matthew Carye1b00062018-09-19 14:27:44255 self._apk_target = 'monochrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46256 self._libname = 'libmonochrome'
Matthew Cary2216fef2019-06-17 13:35:59257 self._libchrome_target = 'libmonochrome'
Matthew Caryd6bfcb72018-09-14 11:27:46258 else:
259 self._apk = 'Chrome.apk'
Matthew Carye1b00062018-09-19 14:27:44260 self._apk_target = 'chrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46261 self._libname = 'libchrome'
262 self._libchrome_target = 'libchrome'
Stephen Kylef11339f82019-03-25 09:00:47263 if public:
264 self._apk = self._apk.replace('.apk', 'Public.apk')
265 self._apk_target = self._apk_target.replace('_apk', '_public_apk')
Matthew Cary78aae162018-08-10 17:16:30266
267 self.obj_dir = os.path.join(self._out_dir, 'Release', 'obj')
Benoit Lizea3fe293d2017-10-20 10:24:52268 self.lib_chrome_so = os.path.join(
Matthew Caryd6bfcb72018-09-14 11:27:46269 self._out_dir, 'Release', 'lib.unstripped',
270 '{}.so'.format(self._libname))
271 self.chrome_apk = os.path.join(self._out_dir, 'Release', 'apks', self._apk)
Benoit Lizea3fe293d2017-10-20 10:24:52272
Monica Basta99c101fa2019-05-21 13:50:05273 def Build(self, instrumented, use_call_graph, target):
Benoit Lizea3fe293d2017-10-20 10:24:52274 """Builds the provided ninja target with or without order_profiling on.
275
276 Args:
Benoit Lizea87e5bce2017-11-07 15:12:57277 instrumented: (bool) Whether we want to build an instrumented binary.
Monica Basta99c101fa2019-05-21 13:50:05278 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea87e5bce2017-11-07 15:12:57279 target: (str) The name of the ninja target to build.
Benoit Lizea3fe293d2017-10-20 10:24:52280 """
281 self._step_recorder.BeginStep('Compile %s' % target)
Monica Basta99c101fa2019-05-21 13:50:05282 assert not use_call_graph or instrumented, ('You can not enable call graph '
283 'without instrumentation!')
Benoit Lizea3fe293d2017-10-20 10:24:52284
285 # Set the "Release Official" flavor, the parts affecting performance.
286 args = [
Andrew Grieve0e8790c2020-09-03 17:27:32287 'enable_resource_allowlist_generation=false',
Stephen Kylef11339f82019-03-25 09:00:47288 'is_chrome_branded=' + str(not self._public).lower(),
Benoit Lizea3fe293d2017-10-20 10:24:52289 'is_debug=false',
290 'is_official_build=true',
Egor Pasko1d13d532020-01-14 21:25:19291 'symbol_level=1', # to fit 30 GiB RAM on the bot when LLD is running
Benoit Lizea3fe293d2017-10-20 10:24:52292 'target_os="android"',
293 'use_goma=' + str(self._use_goma).lower(),
294 'use_order_profiling=' + str(instrumented).lower(),
Monica Basta99c101fa2019-05-21 13:50:05295 'use_call_graph=' + str(use_call_graph).lower(),
Benoit Lizea3fe293d2017-10-20 10:24:52296 ]
Matthew Cary53f74ba2019-01-24 10:07:18297 args += _ARCH_GN_ARGS[self._arch]
Benoit Lizea3fe293d2017-10-20 10:24:52298 if self._goma_dir:
299 args += ['goma_dir="%s"' % self._goma_dir]
Matthew Cary78aae162018-08-10 17:16:30300 if self._system_health_profiling:
301 args += ['devtools_instrumentation_dumping = ' +
302 str(instrumented).lower()]
Benoit Lizea87e5bce2017-11-07 15:12:57303
Stephen Kylef11339f82019-03-25 09:00:47304 if self._public and os.path.exists(self._orderfile_location):
305 # GN needs the orderfile path to be source-absolute.
306 src_abs_orderfile = os.path.relpath(self._orderfile_location,
307 constants.DIR_SOURCE_ROOT)
308 args += ['chrome_orderfile="//{}"'.format(src_abs_orderfile)]
309
Benoit Lizea3fe293d2017-10-20 10:24:52310 self._step_recorder.RunCommand(
311 ['gn', 'gen', os.path.join(self._out_dir, 'Release'),
312 '--args=' + ' '.join(args)])
313
314 self._step_recorder.RunCommand(
Christopher Grant073637472019-07-05 13:34:57315 ['autoninja', '-C',
316 os.path.join(self._out_dir, 'Release'), target])
Benoit Lizea3fe293d2017-10-20 10:24:52317
Christopher Grant102217692019-07-05 12:10:04318 def ForceRelink(self):
319 """Forces libchrome.so or libmonochrome.so to be re-linked.
320
321 With partitioned libraries enabled, deleting these library files does not
322 guarantee they'll be recreated by the linker (they may simply be
323 re-extracted from a combined library). To be safe, touch a source file
324 instead. See http://crbug.com/972701 for more explanation.
325 """
326 file_to_touch = os.path.join(constants.DIR_SOURCE_ROOT, 'chrome', 'browser',
327 'chrome_browser_main_android.cc')
328 assert os.path.exists(file_to_touch)
329 self._step_recorder.RunCommand(['touch', file_to_touch])
330
Monica Basta99c101fa2019-05-21 13:50:05331 def CompileChromeApk(self, instrumented, use_call_graph, force_relink=False):
Benoit Lizea3fe293d2017-10-20 10:24:52332 """Builds a Chrome.apk either with or without order_profiling on.
333
334 Args:
Benoit Lizea87e5bce2017-11-07 15:12:57335 instrumented: (bool) Whether to build an instrumented apk.
Monica Basta99c101fa2019-05-21 13:50:05336 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea3fe293d2017-10-20 10:24:52337 force_relink: Whether libchromeview.so should be re-created.
338 """
339 if force_relink:
Christopher Grant102217692019-07-05 12:10:04340 self.ForceRelink()
Monica Basta99c101fa2019-05-21 13:50:05341 self.Build(instrumented, use_call_graph, self._apk_target)
Benoit Lizea3fe293d2017-10-20 10:24:52342
Monica Basta99c101fa2019-05-21 13:50:05343 def CompileLibchrome(self, instrumented, use_call_graph, force_relink=False):
Benoit Lizea3fe293d2017-10-20 10:24:52344 """Builds a libchrome.so either with or without order_profiling on.
345
346 Args:
Benoit Lizea87e5bce2017-11-07 15:12:57347 instrumented: (bool) Whether to build an instrumented apk.
Monica Basta99c101fa2019-05-21 13:50:05348 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea87e5bce2017-11-07 15:12:57349 force_relink: (bool) Whether libchrome.so should be re-created.
Benoit Lizea3fe293d2017-10-20 10:24:52350 """
351 if force_relink:
Christopher Grant102217692019-07-05 12:10:04352 self.ForceRelink()
Monica Basta99c101fa2019-05-21 13:50:05353 self.Build(instrumented, use_call_graph, self._libchrome_target)
Benoit Lizea3fe293d2017-10-20 10:24:52354
355
356class OrderfileUpdater(object):
357 """Handles uploading and committing a new orderfile in the repository.
358
359 Only used for testing or on a bot.
360 """
361
362 _CLOUD_STORAGE_BUCKET_FOR_DEBUG = None
363 _CLOUD_STORAGE_BUCKET = None
364 _UPLOAD_TO_CLOUD_COMMAND = 'upload_to_google_storage.py'
365
Egor Pasko0c533c6682019-11-26 21:16:32366 def __init__(self, repository_root, step_recorder):
Benoit Lizea3fe293d2017-10-20 10:24:52367 """Constructor.
368
369 Args:
370 repository_root: (str) Root of the target repository.
371 step_recorder: (StepRecorder) Step recorder, for logging.
Benoit Lizea3fe293d2017-10-20 10:24:52372 """
Benoit Lizea3fe293d2017-10-20 10:24:52373 self._repository_root = repository_root
374 self._step_recorder = step_recorder
Benoit Lizea3fe293d2017-10-20 10:24:52375
Matthew Cary86a226e2019-03-19 12:17:44376 def CommitStashedFileHashes(self, files):
377 """Commits unpatched and patched orderfiles hashes if changed.
378
379 The files are committed only if their associated sha1 hash files match, and
380 are modified in git. In normal operations the hash files are changed only
381 when a file is uploaded to cloud storage. If the hash file is not modified
382 in git, the file is skipped.
383
384 Args:
385 files: [str or None] specifies file paths. None items are ignored.
386
387 Raises:
388 Exception if the hash file does not match the file.
389 NotImplementedError when the commit logic hasn't been overridden.
390 """
391 files_to_commit = list(filter(None, files))
392 if files_to_commit:
393 self._CommitStashedFiles(files_to_commit)
394
Benoit Lizea3fe293d2017-10-20 10:24:52395 def UploadToCloudStorage(self, filename, use_debug_location):
396 """Uploads a file to cloud storage.
397
398 Args:
399 filename: (str) File to upload.
400 use_debug_location: (bool) Whether to use the debug location.
401 """
402 bucket = (self._CLOUD_STORAGE_BUCKET_FOR_DEBUG if use_debug_location
403 else self._CLOUD_STORAGE_BUCKET)
404 extension = _GetFileExtension(filename)
405 cmd = [self._UPLOAD_TO_CLOUD_COMMAND, '--bucket', bucket]
406 if extension:
407 cmd.extend(['-z', extension])
408 cmd.append(filename)
409 self._step_recorder.RunCommand(cmd)
Raul Tambre48f176622019-09-23 10:05:24410 print('Download: https://sandbox.google.com/storage/%s/%s' %
411 (bucket, _GenerateHash(filename)))
Benoit Lizea3fe293d2017-10-20 10:24:52412
413 def _GetHashFilePathAndContents(self, filename):
414 """Gets the name and content of the hash file created from uploading the
415 given file.
416
417 Args:
418 filename: (str) The file that was uploaded to cloud storage.
419
420 Returns:
421 A tuple of the hash file name, relative to the reository root, and the
422 content, which should be the sha1 hash of the file
423 ('base_file.sha1', hash)
424 """
425 abs_hash_filename = filename + '.sha1'
426 rel_hash_filename = os.path.relpath(
427 abs_hash_filename, self._repository_root)
428 with open(abs_hash_filename, 'r') as f:
429 return (rel_hash_filename, f.read())
430
Matthew Cary86a226e2019-03-19 12:17:44431 def _GitStash(self):
432 """Git stash the current clank tree.
433
434 Raises:
435 NotImplementedError when the stash logic hasn't been overridden.
436 """
437 raise NotImplementedError
438
439 def _CommitStashedFiles(self, expected_files_in_stash):
440 """Commits stashed files.
441
442 The local repository is updated and then the files to commit are taken from
443 modified files from the git stash. The modified files should be a subset of
444 |expected_files_in_stash|. If there are unexpected modified files, this
445 function may raise. This is meant to be paired with _GitStash().
446
447 Args:
448 expected_files_in_stash: [str] paths to a possible superset of files
449 expected to be stashed & committed.
450
451 Raises:
452 NotImplementedError when the commit logic hasn't been overridden.
453 """
454 raise NotImplementedError
455
Benoit Lizea3fe293d2017-10-20 10:24:52456
Benoit Lizea3fe293d2017-10-20 10:24:52457class OrderfileGenerator(object):
458 """A utility for generating a new orderfile for Clank.
459
460 Builds an instrumented binary, profiles a run of the application, and
461 generates an updated orderfile.
462 """
Benoit Lizea3fe293d2017-10-20 10:24:52463 _CHECK_ORDERFILE_SCRIPT = os.path.join(
464 constants.DIR_SOURCE_ROOT, 'tools', 'cygprofile', 'check_orderfile.py')
465 _BUILD_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(
466 constants.GetOutDirectory()))) # Normally /path/to/src
467
Benoit Lizea3fe293d2017-10-20 10:24:52468 # Previous orderfile_generator debug files would be overwritten.
469 _DIRECTORY_FOR_DEBUG_FILES = '/tmp/orderfile_generator_debug_files'
470
Stephen Kylef11339f82019-03-25 09:00:47471 def _PrepareOrderfilePaths(self):
472 if self._options.public:
473 self._clank_dir = os.path.join(constants.DIR_SOURCE_ROOT,
474 '')
475 if not os.path.exists(os.path.join(self._clank_dir, 'orderfiles')):
476 os.makedirs(os.path.join(self._clank_dir, 'orderfiles'))
477 else:
478 self._clank_dir = os.path.join(constants.DIR_SOURCE_ROOT,
479 'clank')
480
481 self._unpatched_orderfile_filename = os.path.join(
482 self._clank_dir, 'orderfiles', 'unpatched_orderfile.%s')
483 self._path_to_orderfile = os.path.join(
484 self._clank_dir, 'orderfiles', 'orderfile.%s.out')
485
Benoit Lizea3fe293d2017-10-20 10:24:52486 def _GetPathToOrderfile(self):
487 """Gets the path to the architecture-specific orderfile."""
Stephen Kylef11339f82019-03-25 09:00:47488 return self._path_to_orderfile % self._options.arch
Benoit Lizea3fe293d2017-10-20 10:24:52489
490 def _GetUnpatchedOrderfileFilename(self):
491 """Gets the path to the architecture-specific unpatched orderfile."""
Stephen Kylef11339f82019-03-25 09:00:47492 return self._unpatched_orderfile_filename % self._options.arch
Benoit Lizea3fe293d2017-10-20 10:24:52493
Monica Salamaeea2d942019-03-11 12:36:18494 def _SetDevice(self):
495 """ Selects the device to be used by the script.
496
497 Returns:
498 (Device with given serial ID) : if the --device flag is set.
499 (Device running Android[K,L]) : if --use-legacy-chrome-apk flag is set or
500 no device running Android N+ was found.
501 (Device running Android N+) : Otherwise.
502
503 Raises Error:
504 If no device meeting the requirements has been found.
505 """
506 devices = None
507 if self._options.device:
508 devices = [device_utils.DeviceUtils(self._options.device)]
509 else:
510 devices = device_utils.DeviceUtils.HealthyDevices()
511
512 assert devices, 'Expected at least one connected device'
513
514 if self._options.use_legacy_chrome_apk:
515 self._monochrome = False
516 for device in devices:
517 device_version = device.build_version_sdk
518 if (device_version >= version_codes.KITKAT
519 and device_version <= version_codes.LOLLIPOP_MR1):
520 return device
521
522 assert not self._options.use_legacy_chrome_apk, \
523 'No device found running suitable android version for Chrome.apk.'
524
525 preferred_device = None
526 for device in devices:
527 if device.build_version_sdk >= version_codes.NOUGAT:
Monica Salama90b8f5b2019-04-25 11:10:38528 preferred_device = device
529 break
Monica Salamaeea2d942019-03-11 12:36:18530
531 self._monochrome = preferred_device is not None
532
533 return preferred_device if preferred_device else devices[0]
534
535
Benoit Lizea3fe293d2017-10-20 10:24:52536 def __init__(self, options, orderfile_updater_class):
537 self._options = options
Benoit Lizea3fe293d2017-10-20 10:24:52538 self._instrumented_out_dir = os.path.join(
539 self._BUILD_ROOT, self._options.arch + '_instrumented_out')
Monica Basta99c101fa2019-05-21 13:50:05540 if self._options.use_call_graph:
Christopher Grantdfe1bac2019-07-05 13:34:10541 self._instrumented_out_dir += '_call_graph'
Monica Basta99c101fa2019-05-21 13:50:05542
Benoit Lizea3fe293d2017-10-20 10:24:52543 self._uninstrumented_out_dir = os.path.join(
544 self._BUILD_ROOT, self._options.arch + '_uninstrumented_out')
Monica Salama90b8f5b2019-04-25 11:10:38545 self._no_orderfile_out_dir = os.path.join(
546 self._BUILD_ROOT, self._options.arch + '_no_orderfile_out')
Benoit Lizea3fe293d2017-10-20 10:24:52547
Stephen Kylef11339f82019-03-25 09:00:47548 self._PrepareOrderfilePaths()
549
Benoit Lizea3fe293d2017-10-20 10:24:52550 if options.profile:
Benoit Lizea1b64f82017-12-07 10:12:50551 output_directory = os.path.join(self._instrumented_out_dir, 'Release')
Matthew Caryb8daed942018-06-11 10:58:08552 host_profile_dir = os.path.join(output_directory, 'profile_data')
Benoit Lizea1b64f82017-12-07 10:12:50553 urls = [profile_android_startup.AndroidProfileTool.TEST_URL]
554 use_wpr = True
555 simulate_user = False
Benoit L96466812018-03-06 15:58:37556 urls = options.urls
557 use_wpr = not options.no_wpr
558 simulate_user = options.simulate_user
Monica Salamaeea2d942019-03-11 12:36:18559 device = self._SetDevice()
Benoit Lizea3fe293d2017-10-20 10:24:52560 self._profiler = profile_android_startup.AndroidProfileTool(
Matthew Cary453ff1452018-07-18 12:19:35561 output_directory, host_profile_dir, use_wpr, urls, simulate_user,
Matthew Cary1ea82212019-03-20 12:10:27562 device, debug=self._options.streamline_for_debugging)
Matthew Cary69e9e422018-08-10 18:30:06563 if options.pregenerated_profiles:
564 self._profiler.SetPregeneratedProfiles(
565 glob.glob(options.pregenerated_profiles))
566 else:
567 assert not options.pregenerated_profiles, (
568 '--pregenerated-profiles cannot be used with --skip-profile')
569 assert not options.profile_save_dir, (
570 '--profile-save-dir cannot be used with --skip-profile')
Monica Salamaeea2d942019-03-11 12:36:18571 self._monochrome = not self._options.use_legacy_chrome_apk
Benoit Lizea3fe293d2017-10-20 10:24:52572
Matthew Caryf949bba2019-02-04 13:39:23573 # Outlined function handling enabled by default for all architectures.
574 self._order_outlined_functions = not options.noorder_outlined_functions
575
Benoit Lizea3fe293d2017-10-20 10:24:52576 self._output_data = {}
577 self._step_recorder = StepRecorder(options.buildbot)
578 self._compiler = None
Stephen Kylef11339f82019-03-25 09:00:47579 if orderfile_updater_class is None:
Egor Paskod80bfad2020-09-10 21:53:06580 orderfile_updater_class = OrderfileUpdater
Benoit Lizea3fe293d2017-10-20 10:24:52581 assert issubclass(orderfile_updater_class, OrderfileUpdater)
Stephen Kylef11339f82019-03-25 09:00:47582 self._orderfile_updater = orderfile_updater_class(self._clank_dir,
Egor Pasko0c533c6682019-11-26 21:16:32583 self._step_recorder)
Benoit Lizea3fe293d2017-10-20 10:24:52584 assert os.path.isdir(constants.DIR_SOURCE_ROOT), 'No src directory found'
Benoit Lizefefbb27c2018-01-17 13:54:18585 symbol_extractor.SetArchitecture(options.arch)
Benoit Lizea3fe293d2017-10-20 10:24:52586
Benoit Lizea3fe293d2017-10-20 10:24:52587 @staticmethod
588 def _RemoveBlanks(src_file, dest_file):
589 """A utility to remove blank lines from a file.
590
591 Args:
592 src_file: The name of the file to remove the blanks from.
593 dest_file: The name of the file to write the output without blanks.
594 """
595 assert src_file != dest_file, 'Source and destination need to be distinct'
596
597 try:
598 src = open(src_file, 'r')
599 dest = open(dest_file, 'w')
600 for line in src:
601 if line and not line.isspace():
602 dest.write(line)
603 finally:
604 src.close()
605 dest.close()
606
607 def _GenerateAndProcessProfile(self):
Matthew Cary78aae162018-08-10 17:16:30608 """Invokes a script to merge the per-thread traces into one file.
609
610 The produced list of offsets is saved in
611 self._GetUnpatchedOrderfileFilename().
612 """
Benoit Lizea3fe293d2017-10-20 10:24:52613 self._step_recorder.BeginStep('Generate Profile Data')
614 files = []
Matthew Cary78aae162018-08-10 17:16:30615 logging.getLogger().setLevel(logging.DEBUG)
Christopher Grantdfe1bac2019-07-05 13:34:10616
617 if self._options.profile_save_dir:
618 # The directory must not preexist, to ensure purity of data. Check
619 # before profiling to save time.
620 if os.path.exists(self._options.profile_save_dir):
621 raise Exception('Profile save directory must not pre-exist')
622 os.makedirs(self._options.profile_save_dir)
623
Matthew Cary78aae162018-08-10 17:16:30624 if self._options.system_health_orderfile:
625 files = self._profiler.CollectSystemHealthProfile(
626 self._compiler.chrome_apk)
Matthew Cary69e9e422018-08-10 18:30:06627 self._MaybeSaveProfile(files)
Matthew Cary78aae162018-08-10 17:16:30628 try:
629 self._ProcessPhasedOrderfile(files)
630 except Exception:
631 for f in files:
632 self._SaveForDebugging(f)
Matthew Cary8b1416232018-08-10 19:12:22633 self._SaveForDebugging(self._compiler.lib_chrome_so)
Matthew Cary78aae162018-08-10 17:16:30634 raise
635 finally:
636 self._profiler.Cleanup()
637 else:
638 self._CollectLegacyProfile()
639 logging.getLogger().setLevel(logging.INFO)
640
641 def _ProcessPhasedOrderfile(self, files):
642 """Process the phased orderfiles produced by system health benchmarks.
643
644 The offsets will be placed in _GetUnpatchedOrderfileFilename().
645
646 Args:
647 file: Profile files pulled locally.
648 """
649 self._step_recorder.BeginStep('Process Phased Orderfile')
650 profiles = process_profiles.ProfileManager(files)
651 processor = process_profiles.SymbolOffsetProcessor(
652 self._compiler.lib_chrome_so)
Monica Basta99c101fa2019-05-21 13:50:05653 ordered_symbols = cluster.ClusterOffsets(profiles, processor,
654 call_graph=self._options.use_call_graph)
Matthew Cary8b1416232018-08-10 19:12:22655 if not ordered_symbols:
656 raise Exception('Failed to get ordered symbols')
Matthew Caryda9db932019-03-11 15:28:17657 for sym in ordered_symbols:
658 assert not sym.startswith('OUTLINED_FUNCTION_'), (
659 'Outlined function found in instrumented function, very likely '
660 'something has gone very wrong!')
Matthew Cary91df9792018-11-30 14:35:15661 self._output_data['offsets_kib'] = processor.SymbolsSize(
662 ordered_symbols) / 1024
Matthew Cary78aae162018-08-10 17:16:30663 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
Matthew Cary8b1416232018-08-10 19:12:22664 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary78aae162018-08-10 17:16:30665
666 def _CollectLegacyProfile(self):
Matthew Cary2df8d452018-09-19 07:50:20667 files = []
Benoit Lizea3fe293d2017-10-20 10:24:52668 try:
Benoit Lizea3fe293d2017-10-20 10:24:52669 files = self._profiler.CollectProfile(
670 self._compiler.chrome_apk,
671 constants.PACKAGE_INFO['chrome'])
Matthew Cary69e9e422018-08-10 18:30:06672 self._MaybeSaveProfile(files)
Matthew Caryb8daed942018-06-11 10:58:08673 self._step_recorder.BeginStep('Process profile')
Benoit L96466812018-03-06 15:58:37674 assert os.path.exists(self._compiler.lib_chrome_so)
675 offsets = process_profiles.GetReachedOffsetsFromDumpFiles(
676 files, self._compiler.lib_chrome_so)
677 if not offsets:
678 raise Exception('No profiler offsets found in {}'.format(
679 '\n'.join(files)))
Matthew Cary8b1416232018-08-10 19:12:22680 processor = process_profiles.SymbolOffsetProcessor(
681 self._compiler.lib_chrome_so)
682 ordered_symbols = processor.GetOrderedSymbols(offsets)
683 if not ordered_symbols:
684 raise Exception('No symbol names from offsets found in {}'.format(
685 '\n'.join(files)))
686 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
687 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary0f1f681a2018-01-22 10:40:51688 except Exception:
Benoit Lizea3fe293d2017-10-20 10:24:52689 for f in files:
690 self._SaveForDebugging(f)
691 raise
692 finally:
693 self._profiler.Cleanup()
Benoit Lizea3fe293d2017-10-20 10:24:52694
Matthew Cary69e9e422018-08-10 18:30:06695 def _MaybeSaveProfile(self, files):
696 if self._options.profile_save_dir:
697 logging.info('Saving profiles to %s', self._options.profile_save_dir)
698 for f in files:
699 shutil.copy(f, self._options.profile_save_dir)
700 logging.info('Saved profile %s', f)
701
Benoit Lizea3fe293d2017-10-20 10:24:52702 def _PatchOrderfile(self):
703 """Patches the orderfile using clean version of libchrome.so."""
704 self._step_recorder.BeginStep('Patch Orderfile')
Benoit Lizefefbb27c2018-01-17 13:54:18705 patch_orderfile.GeneratePatchedOrderfile(
706 self._GetUnpatchedOrderfileFilename(), self._compiler.lib_chrome_so,
Matthew Caryf949bba2019-02-04 13:39:23707 self._GetPathToOrderfile(), self._order_outlined_functions)
Benoit Lizea3fe293d2017-10-20 10:24:52708
709 def _VerifySymbolOrder(self):
710 self._step_recorder.BeginStep('Verify Symbol Order')
711 return_code = self._step_recorder.RunCommand(
712 [self._CHECK_ORDERFILE_SCRIPT, self._compiler.lib_chrome_so,
713 self._GetPathToOrderfile(),
714 '--target-arch=' + self._options.arch],
715 constants.DIR_SOURCE_ROOT,
716 raise_on_error=False)
717 if return_code:
718 self._step_recorder.FailStep('Orderfile check returned %d.' % return_code)
719
720 def _RecordHash(self, file_name):
721 """Records the hash of the file into the output_data dictionary."""
722 self._output_data[os.path.basename(file_name) + '.sha1'] = _GenerateHash(
723 file_name)
724
725 def _SaveFileLocally(self, file_name, file_sha1):
726 """Saves the file to a temporary location and prints the sha1sum."""
727 if not os.path.exists(self._DIRECTORY_FOR_DEBUG_FILES):
728 os.makedirs(self._DIRECTORY_FOR_DEBUG_FILES)
729 shutil.copy(file_name, self._DIRECTORY_FOR_DEBUG_FILES)
Raul Tambre48f176622019-09-23 10:05:24730 print('File: %s, saved in: %s, sha1sum: %s' %
731 (file_name, self._DIRECTORY_FOR_DEBUG_FILES, file_sha1))
Benoit Lizea3fe293d2017-10-20 10:24:52732
733 def _SaveForDebugging(self, filename):
734 """Uploads the file to cloud storage or saves to a temporary location."""
735 file_sha1 = _GenerateHash(filename)
736 if not self._options.buildbot:
737 self._SaveFileLocally(filename, file_sha1)
738 else:
Raul Tambre48f176622019-09-23 10:05:24739 print('Uploading file for debugging: ' + filename)
Benoit Lizea3fe293d2017-10-20 10:24:52740 self._orderfile_updater.UploadToCloudStorage(
741 filename, use_debug_location=True)
742
743 def _SaveForDebuggingWithOverwrite(self, file_name):
744 """Uploads and overwrites the file in cloud storage or copies locally.
745
746 Should be used for large binaries like lib_chrome_so.
747
748 Args:
749 file_name: (str) File to upload.
750 """
751 file_sha1 = _GenerateHash(file_name)
752 if not self._options.buildbot:
753 self._SaveFileLocally(file_name, file_sha1)
754 else:
Raul Tambre48f176622019-09-23 10:05:24755 print('Uploading file for debugging: %s, sha1sum: %s' % (file_name,
756 file_sha1))
Benoit Lizea3fe293d2017-10-20 10:24:52757 upload_location = '%s/%s' % (
758 self._CLOUD_STORAGE_BUCKET_FOR_DEBUG, os.path.basename(file_name))
759 self._step_recorder.RunCommand([
760 'gsutil.py', 'cp', file_name, 'gs://' + upload_location])
Raul Tambre48f176622019-09-23 10:05:24761 print('Uploaded to: https://sandbox.google.com/storage/' +
762 upload_location)
Benoit Lizea3fe293d2017-10-20 10:24:52763
764 def _MaybeArchiveOrderfile(self, filename):
765 """In buildbot configuration, uploads the generated orderfile to
766 Google Cloud Storage.
767
768 Args:
769 filename: (str) Orderfile to upload.
770 """
Matthew Cary91df9792018-11-30 14:35:15771 # First compute hashes so that we can download them later if we need to.
Benoit Lizea3fe293d2017-10-20 10:24:52772 self._step_recorder.BeginStep('Compute hash for ' + filename)
773 self._RecordHash(filename)
774 if self._options.buildbot:
775 self._step_recorder.BeginStep('Archive ' + filename)
776 self._orderfile_updater.UploadToCloudStorage(
777 filename, use_debug_location=False)
778
Egor Paskobce64d012018-11-20 12:02:37779 def UploadReadyOrderfiles(self):
780 self._step_recorder.BeginStep('Upload Ready Orderfiles')
781 for file_name in [self._GetUnpatchedOrderfileFilename(),
782 self._GetPathToOrderfile()]:
783 self._orderfile_updater.UploadToCloudStorage(
784 file_name, use_debug_location=False)
785
Monica Salama90b8f5b2019-04-25 11:10:38786 def _NativeCodeMemoryBenchmark(self, apk):
787 """Runs system_health.memory_mobile to assess native code memory footprint.
788
789 Args:
790 apk: (str) Path to the apk.
791
792 Returns:
793 results: ([int]) Values of native code memory footprint in bytes from the
794 benchmark results.
795 """
796 self._step_recorder.BeginStep("Running orderfile.memory_mobile")
797 try:
798 out_dir = tempfile.mkdtemp()
799 self._profiler._RunCommand(['tools/perf/run_benchmark',
800 '--device={}'.format(
801 self._profiler._device.serial),
802 '--browser=exact',
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54803 '--output-format=csv',
Monica Salama90b8f5b2019-04-25 11:10:38804 '--output-dir={}'.format(out_dir),
805 '--reset-results',
806 '--browser-executable={}'.format(apk),
807 'orderfile.memory_mobile'])
808
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54809 out_file_path = os.path.join(out_dir, 'results.csv')
Monica Salama90b8f5b2019-04-25 11:10:38810 if not os.path.exists(out_file_path):
Monica Basta8ec87fd2019-05-13 12:12:35811 raise Exception('Results file not found!')
Monica Salama90b8f5b2019-04-25 11:10:38812
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54813 results = {}
Monica Salama90b8f5b2019-04-25 11:10:38814 with open(out_file_path, 'r') as f:
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54815 reader = csv.DictReader(f)
816 for row in reader:
817 if not row['name'].endswith('NativeCodeResidentMemory'):
Monica Basta8ec87fd2019-05-13 12:12:35818 continue
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54819 # Note: NativeCodeResidentMemory records a single sample from each
820 # story run, so this average (reported as 'avg') is exactly the value
821 # of that one sample. Each story is run multiple times, so this loop
822 # will accumulate into a list all values for all runs of each story.
823 results.setdefault(row['name'], {}).setdefault(
824 row['stories'], []).append(row['avg'])
Monica Basta8ec87fd2019-05-13 12:12:35825
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54826 if not results:
827 raise Exception('Could not find relevant results')
828
Monica Basta8ec87fd2019-05-13 12:12:35829 return results
830
831 except Exception as e:
832 return 'Error: ' + str(e)
Monica Salama90b8f5b2019-04-25 11:10:38833
834 finally:
835 shutil.rmtree(out_dir)
836
Monica Salama90b8f5b2019-04-25 11:10:38837
838 def _PerformanceBenchmark(self, apk):
839 """Runs Speedometer2.0 to assess performance.
840
841 Args:
842 apk: (str) Path to the apk.
843
844 Returns:
845 results: ([float]) Speedometer2.0 results samples in milliseconds.
846 """
847 self._step_recorder.BeginStep("Running Speedometer2.0.")
848 try:
849 out_dir = tempfile.mkdtemp()
850 self._profiler._RunCommand(['tools/perf/run_benchmark',
851 '--device={}'.format(
852 self._profiler._device.serial),
853 '--browser=exact',
Monica Basta8ec87fd2019-05-13 12:12:35854 '--output-format=histograms',
Monica Salama90b8f5b2019-04-25 11:10:38855 '--output-dir={}'.format(out_dir),
856 '--reset-results',
857 '--browser-executable={}'.format(apk),
858 'speedometer2'])
859
Monica Basta8ec87fd2019-05-13 12:12:35860 out_file_path = os.path.join(out_dir, 'histograms.json')
Monica Salama90b8f5b2019-04-25 11:10:38861 if not os.path.exists(out_file_path):
Monica Basta8ec87fd2019-05-13 12:12:35862 raise Exception('Results file not found!')
Monica Salama90b8f5b2019-04-25 11:10:38863
864 with open(out_file_path, 'r') as f:
865 results = json.load(f)
866
867 if not results:
Monica Basta8ec87fd2019-05-13 12:12:35868 raise Exception('Results file is empty.')
869
870 for el in results:
871 if 'name' in el and el['name'] == 'Total' and 'sampleValues' in el:
872 return el['sampleValues']
873
874 raise Exception('Unexpected results format.')
875
876 except Exception as e:
877 return 'Error: ' + str(e)
Monica Salama90b8f5b2019-04-25 11:10:38878
879 finally:
880 shutil.rmtree(out_dir)
881
Monica Salama90b8f5b2019-04-25 11:10:38882
883 def RunBenchmark(self, out_directory, no_orderfile=False):
884 """Builds chrome apk and runs performance and memory benchmarks.
885
886 Builds a non-instrumented version of chrome.
887 Installs chrome apk on the device.
888 Runs Speedometer2.0 benchmark to assess performance.
889 Runs system_health.memory_mobile to evaluate memory footprint.
890
891 Args:
892 out_directory: (str) Path to out directory for this build.
893 no_orderfile: (bool) True if chrome to be built without orderfile.
894
895 Returns:
896 benchmark_results: (dict) Results extracted from benchmarks.
897 """
898 try:
899 _UnstashOutputDirectory(out_directory)
Christopher Grant073637472019-07-05 13:34:57900 self._compiler = ClankCompiler(out_directory, self._step_recorder,
901 self._options.arch, self._options.use_goma,
902 self._options.goma_dir,
903 self._options.system_health_orderfile,
904 self._monochrome, self._options.public,
905 self._GetPathToOrderfile())
Monica Salama90b8f5b2019-04-25 11:10:38906
907 if no_orderfile:
908 orderfile_path = self._GetPathToOrderfile()
909 backup_orderfile = orderfile_path + '.backup'
910 shutil.move(orderfile_path, backup_orderfile)
911 open(orderfile_path, 'w').close()
912
913 # Build APK to be installed on the device.
Monica Basta99c101fa2019-05-21 13:50:05914 self._compiler.CompileChromeApk(instrumented=False,
915 use_call_graph=False,
916 force_relink=True)
Monica Salama90b8f5b2019-04-25 11:10:38917 benchmark_results = dict()
918 benchmark_results['Speedometer2.0'] = self._PerformanceBenchmark(
919 self._compiler.chrome_apk)
920 benchmark_results['orderfile.memory_mobile'] = (
921 self._NativeCodeMemoryBenchmark(self._compiler.chrome_apk))
Monica Basta8ec87fd2019-05-13 12:12:35922
923 except Exception as e:
924 benchmark_results['Error'] = str(e)
925
Monica Salama90b8f5b2019-04-25 11:10:38926 finally:
927 if no_orderfile and os.path.exists(backup_orderfile):
928 shutil.move(backup_orderfile, orderfile_path)
929 _StashOutputDirectory(out_directory)
930
931 return benchmark_results
932
Benoit Lizea3fe293d2017-10-20 10:24:52933 def Generate(self):
934 """Generates and maybe upload an order."""
Matthew Carye8400642018-06-14 15:43:02935 assert (bool(self._options.profile) ^
936 bool(self._options.manual_symbol_offsets))
Matthew Cary78aae162018-08-10 17:16:30937 if self._options.system_health_orderfile and not self._options.profile:
938 raise AssertionError('--system_health_orderfile must be not be used '
939 'with --skip-profile')
940 if (self._options.manual_symbol_offsets and
941 not self._options.system_health_orderfile):
942 raise AssertionError('--manual-symbol-offsets must be used with '
943 '--system_health_orderfile.')
Matthew Carye8400642018-06-14 15:43:02944
Benoit Lizea3fe293d2017-10-20 10:24:52945 if self._options.profile:
946 try:
947 _UnstashOutputDirectory(self._instrumented_out_dir)
948 self._compiler = ClankCompiler(
Christopher Grant073637472019-07-05 13:34:57949 self._instrumented_out_dir, self._step_recorder, self._options.arch,
950 self._options.use_goma, self._options.goma_dir,
951 self._options.system_health_orderfile, self._monochrome,
952 self._options.public, self._GetPathToOrderfile())
Matthew Carya2bea452018-11-13 10:21:07953 if not self._options.pregenerated_profiles:
954 # If there are pregenerated profiles, the instrumented build should
955 # not be changed to avoid invalidating the pregenerated profile
956 # offsets.
Monica Basta99c101fa2019-05-21 13:50:05957 self._compiler.CompileChromeApk(instrumented=True,
958 use_call_graph=
959 self._options.use_call_graph)
Benoit Lizea3fe293d2017-10-20 10:24:52960 self._GenerateAndProcessProfile()
961 self._MaybeArchiveOrderfile(self._GetUnpatchedOrderfileFilename())
Benoit Lizea3fe293d2017-10-20 10:24:52962 finally:
Benoit Lizea3fe293d2017-10-20 10:24:52963 _StashOutputDirectory(self._instrumented_out_dir)
Matthew Carye8400642018-06-14 15:43:02964 elif self._options.manual_symbol_offsets:
965 assert self._options.manual_libname
966 assert self._options.manual_objdir
967 with file(self._options.manual_symbol_offsets) as f:
968 symbol_offsets = [int(x) for x in f.xreadlines()]
969 processor = process_profiles.SymbolOffsetProcessor(
Matthew Caryb46ad28d2018-11-23 14:43:57970 self._compiler.manual_libname)
Matthew Carye8400642018-06-14 15:43:02971 generator = cyglog_to_orderfile.OffsetOrderfileGenerator(
972 processor, cyglog_to_orderfile.ObjectFileProcessor(
973 self._options.manual_objdir))
974 ordered_sections = generator.GetOrderedSections(symbol_offsets)
975 if not ordered_sections: # Either None or empty is a problem.
976 raise Exception('Failed to get ordered sections')
977 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
978 orderfile.write('\n'.join(ordered_sections))
979
Benoit Lizea3fe293d2017-10-20 10:24:52980 if self._options.patch:
981 if self._options.profile:
982 self._RemoveBlanks(self._GetUnpatchedOrderfileFilename(),
983 self._GetPathToOrderfile())
984 try:
985 _UnstashOutputDirectory(self._uninstrumented_out_dir)
986 self._compiler = ClankCompiler(
987 self._uninstrumented_out_dir, self._step_recorder,
Christopher Grant073637472019-07-05 13:34:57988 self._options.arch, self._options.use_goma, self._options.goma_dir,
Stephen Kylef11339f82019-03-25 09:00:47989 self._options.system_health_orderfile, self._monochrome,
990 self._options.public, self._GetPathToOrderfile())
991
Monica Basta99c101fa2019-05-21 13:50:05992 self._compiler.CompileLibchrome(instrumented=False,
993 use_call_graph=False)
Benoit Lizea3fe293d2017-10-20 10:24:52994 self._PatchOrderfile()
995 # Because identical code folding is a bit different with and without
996 # the orderfile build, we need to re-patch the orderfile with code
997 # folding as close to the final version as possible.
Monica Basta99c101fa2019-05-21 13:50:05998 self._compiler.CompileLibchrome(instrumented=False,
999 use_call_graph=False, force_relink=True)
Benoit Lizea3fe293d2017-10-20 10:24:521000 self._PatchOrderfile()
Monica Basta99c101fa2019-05-21 13:50:051001 self._compiler.CompileLibchrome(instrumented=False,
1002 use_call_graph=False, force_relink=True)
Benoit Lizea3fe293d2017-10-20 10:24:521003 self._VerifySymbolOrder()
1004 self._MaybeArchiveOrderfile(self._GetPathToOrderfile())
1005 finally:
1006 _StashOutputDirectory(self._uninstrumented_out_dir)
Benoit Lizea3fe293d2017-10-20 10:24:521007
Monica Salama90b8f5b2019-04-25 11:10:381008 if self._options.benchmark:
1009 self._output_data['orderfile_benchmark_results'] = self.RunBenchmark(
1010 self._uninstrumented_out_dir)
1011 self._output_data['no_orderfile_benchmark_results'] = self.RunBenchmark(
1012 self._no_orderfile_out_dir, no_orderfile=True)
1013
Egor Paskod80bfad2020-09-10 21:53:061014 if self._options.buildbot:
1015 self._orderfile_updater._GitStash()
Benoit Lizea3fe293d2017-10-20 10:24:521016 self._step_recorder.EndStep()
1017 return not self._step_recorder.ErrorRecorded()
1018
1019 def GetReportingData(self):
1020 """Get a dictionary of reporting data (timings, output hashes)"""
1021 self._output_data['timings'] = self._step_recorder.timings
1022 return self._output_data
1023
Matthew Cary86a226e2019-03-19 12:17:441024 def CommitStashedOrderfileHashes(self):
1025 """Commit any orderfile hash files in the current checkout.
1026
1027 Only possible if running on the buildbot.
1028
1029 Returns: true on success.
1030 """
Egor Pasko7ff04122019-11-25 15:47:181031 if not self._options.buildbot:
Matthew Cary86a226e2019-03-19 12:17:441032 logging.error('Trying to commit when not running on the buildbot')
1033 return False
1034 self._orderfile_updater._CommitStashedFiles([
1035 filename + '.sha1'
1036 for filename in (self._GetUnpatchedOrderfileFilename(),
1037 self._GetPathToOrderfile())])
1038 return True
1039
Benoit Lizea3fe293d2017-10-20 10:24:521040
Benoit Lizea1b64f82017-12-07 10:12:501041def CreateArgumentParser():
1042 """Creates and returns the argument parser."""
1043 parser = argparse.ArgumentParser()
Monica Salama90b8f5b2019-04-25 11:10:381044 parser.add_argument('--no-benchmark', action='store_false', dest='benchmark',
1045 default=True, help='Disables running benchmarks.')
Benoit Lizea1b64f82017-12-07 10:12:501046 parser.add_argument(
Benoit Lizea3fe293d2017-10-20 10:24:521047 '--buildbot', action='store_true',
1048 help='If true, the script expects to be run on a buildbot')
Benoit Lizea1b64f82017-12-07 10:12:501049 parser.add_argument(
Matthew Cary453ff1452018-07-18 12:19:351050 '--device', default=None, type=str,
1051 help='Device serial number on which to run profiling.')
1052 parser.add_argument(
Benoit Lizea3fe293d2017-10-20 10:24:521053 '--verify', action='store_true',
1054 help='If true, the script only verifies the current orderfile')
Benoit Lizea1b64f82017-12-07 10:12:501055 parser.add_argument('--target-arch', action='store', dest='arch',
Stephen Martinis71b391b52018-12-04 15:08:041056 default='arm',
Matthew Cary53f74ba2019-01-24 10:07:181057 choices=['arm', 'arm64'],
1058 help='The target architecture for which to build.')
Benoit Lizea1b64f82017-12-07 10:12:501059 parser.add_argument('--output-json', action='store', dest='json_file',
1060 help='Location to save stats in json format')
1061 parser.add_argument(
Benoit Lizea3fe293d2017-10-20 10:24:521062 '--skip-profile', action='store_false', dest='profile', default=True,
1063 help='Don\'t generate a profile on the device. Only patch from the '
1064 'existing profile.')
Benoit Lizea1b64f82017-12-07 10:12:501065 parser.add_argument(
Benoit Lizea3fe293d2017-10-20 10:24:521066 '--skip-patch', action='store_false', dest='patch', default=True,
1067 help='Only generate the raw (unpatched) orderfile, don\'t patch it.')
Benoit Lizea1b64f82017-12-07 10:12:501068 parser.add_argument('--goma-dir', help='GOMA directory.')
1069 parser.add_argument(
Benoit Lizea3fe293d2017-10-20 10:24:521070 '--use-goma', action='store_true', help='Enable GOMA.', default=False)
Benoit Lizea1b64f82017-12-07 10:12:501071 parser.add_argument('--adb-path', help='Path to the adb binary.')
Matthew Carye8400642018-06-14 15:43:021072
Egor Paskod80bfad2020-09-10 21:53:061073 parser.add_argument('--public',
1074 action='store_true',
1075 help='Build non-internal APK and change the orderfile '
1076 'location. Required if your checkout is non-internal.',
Stephen Kylef11339f82019-03-25 09:00:471077 default=False)
Matthew Cary04f41032018-12-10 15:55:271078 parser.add_argument('--nosystem-health-orderfile', action='store_false',
Benoit Lcba421e2019-04-26 15:28:261079 dest='system_health_orderfile', default=True,
Matthew Caryc262f5d2018-09-19 14:36:281080 help=('Create an orderfile based on an about:blank '
1081 'startup benchmark instead of system health '
1082 'benchmarks.'))
Monica Salamaeea2d942019-03-11 12:36:181083 parser.add_argument(
1084 '--use-legacy-chrome-apk', action='store_true', default=False,
1085 help=('Compile and instrument chrome for [L, K] devices.'))
Matthew Carye8400642018-06-14 15:43:021086 parser.add_argument('--manual-symbol-offsets', default=None, type=str,
1087 help=('File of list of ordered symbol offsets generated '
1088 'by manual profiling. Must set other --manual* '
1089 'flags if this is used, and must --skip-profile.'))
1090 parser.add_argument('--manual-libname', default=None, type=str,
1091 help=('Library filename corresponding to '
1092 '--manual-symbol-offsets.'))
1093 parser.add_argument('--manual-objdir', default=None, type=str,
1094 help=('Root of object file directory corresponding to '
1095 '--manual-symbol-offsets.'))
Matthew Caryf949bba2019-02-04 13:39:231096 parser.add_argument('--noorder-outlined-functions', action='store_true',
1097 help='Disable outlined functions in the orderfile.')
Matthew Cary69e9e422018-08-10 18:30:061098 parser.add_argument('--pregenerated-profiles', default=None, type=str,
1099 help=('Pregenerated profiles to use instead of running '
1100 'profile step. Cannot be used with '
1101 '--skip-profiles.'))
1102 parser.add_argument('--profile-save-dir', default=None, type=str,
1103 help=('Directory to save any profiles created. These can '
1104 'be used with --pregenerated-profiles. Cannot be '
1105 'used with --skip-profiles.'))
Egor Paskobce64d012018-11-20 12:02:371106 parser.add_argument('--upload-ready-orderfiles', action='store_true',
1107 help=('Skip orderfile generation and manually upload '
1108 'orderfiles (both patched and unpatched) from '
1109 'their normal location in the tree to the cloud '
1110 'storage. DANGEROUS! USE WITH CARE!'))
Matthew Cary1ea82212019-03-20 12:10:271111 parser.add_argument('--streamline-for-debugging', action='store_true',
1112 help=('Streamline where possible the run for faster '
1113 'iteration while debugging. The orderfile '
1114 'generated will be valid and nontrivial, but '
1115 'may not be based on a representative profile '
1116 'or other such considerations. Use with caution.'))
Matthew Cary86a226e2019-03-19 12:17:441117 parser.add_argument('--commit-hashes', action='store_true',
1118 help=('Commit any orderfile hash files in the current '
1119 'checkout; performs no other action'))
Monica Basta99c101fa2019-05-21 13:50:051120 parser.add_argument('--use-call-graph', action='store_true', default=False,
1121 help='Use call graph instrumentation.')
Benoit Lizea1b64f82017-12-07 10:12:501122 profile_android_startup.AddProfileCollectionArguments(parser)
Benoit Lizea3fe293d2017-10-20 10:24:521123 return parser
1124
1125
Stephen Kylef11339f82019-03-25 09:00:471126def CreateOrderfile(options, orderfile_updater_class=None):
Christopher Grantdfe1bac2019-07-05 13:34:101127 """Creates an orderfile.
Benoit Lizea3fe293d2017-10-20 10:24:521128
1129 Args:
1130 options: As returned from optparse.OptionParser.parse_args()
1131 orderfile_updater_class: (OrderfileUpdater) subclass of OrderfileUpdater.
Benoit Lizea3fe293d2017-10-20 10:24:521132
1133 Returns:
1134 True iff success.
1135 """
1136 logging.basicConfig(level=logging.INFO)
1137 devil_chromium.Initialize(adb_path=options.adb_path)
1138
Egor Pasko93e514e2017-10-31 13:32:361139 generator = OrderfileGenerator(options, orderfile_updater_class)
Benoit Lizea3fe293d2017-10-20 10:24:521140 try:
1141 if options.verify:
Egor Pasko93e514e2017-10-31 13:32:361142 generator._VerifySymbolOrder()
Matthew Cary86a226e2019-03-19 12:17:441143 elif options.commit_hashes:
Matthew Cary86a226e2019-03-19 12:17:441144 return generator.CommitStashedOrderfileHashes()
Egor Paskobce64d012018-11-20 12:02:371145 elif options.upload_ready_orderfiles:
1146 return generator.UploadReadyOrderfiles()
Benoit Lizea3fe293d2017-10-20 10:24:521147 else:
Egor Pasko93e514e2017-10-31 13:32:361148 return generator.Generate()
Benoit Lizea3fe293d2017-10-20 10:24:521149 finally:
Egor Pasko93e514e2017-10-31 13:32:361150 json_output = json.dumps(generator.GetReportingData(),
Benoit Lizea3fe293d2017-10-20 10:24:521151 indent=2) + '\n'
1152 if options.json_file:
1153 with open(options.json_file, 'w') as f:
1154 f.write(json_output)
Raul Tambre48f176622019-09-23 10:05:241155 print(json_output)
Benoit Lizea3fe293d2017-10-20 10:24:521156 return False
1157
1158
Benoit Lizea1b64f82017-12-07 10:12:501159def main():
1160 parser = CreateArgumentParser()
1161 options = parser.parse_args()
Stephen Kylef11339f82019-03-25 09:00:471162 return 0 if CreateOrderfile(options) else 1
Benoit Lizea3fe293d2017-10-20 10:24:521163
1164
1165if __name__ == '__main__':
Benoit Lizea1b64f82017-12-07 10:12:501166 sys.exit(main())