[go: nahoru, domu]

blob: 3291e2a0874c38a6ecc1f3aa1d080af0754a0940 [file] [log] [blame]
Egor Pasko0462e852d2018-03-29 15:52:091#!/usr/bin/env vpython
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 Pasko93e514e2017-10-31 13:32:3613 tools/cygprofile/orderfile_generator_backend.py -l 20 -j 1000 --use-goma \
Benoit Lizea3fe2932017-10-20 10:24:5214 --target-arch=arm
15"""
16
Raul Tambre48f176622019-09-23 10:05:2417from __future__ import print_function
18
Benoit Lizea1b64f82017-12-07 10:12:5019import argparse
Benoit Lizea3fe2932017-10-20 10:24:5220import hashlib
21import json
Matthew Cary69e9e422018-08-10 18:30:0622import glob
Benoit Lizea3fe2932017-10-20 10:24:5223import logging
Benoit Lizea3fe2932017-10-20 10:24:5224import os
25import re
26import shutil
27import subprocess
28import sys
Benoit Lizea87e5bc2017-11-07 15:12:5729import tempfile
Benoit Lizea3fe2932017-10-20 10:24:5230import time
31
Matthew Cary91df9792018-11-30 14:35:1532import cluster
Matthew Carye8400642018-06-14 15:43:0233import cyglog_to_orderfile
Benoit Lizea3fe2932017-10-20 10:24:5234import cygprofile_utils
Benoit Lizefefbb27c2018-01-17 13:54:1835import patch_orderfile
Benoit Lizea87e5bc2017-11-07 15:12:5736import process_profiles
Egor Pasko3bc0b932018-04-03 10:08:2737import profile_android_startup
Benoit Lizefefbb27c2018-01-17 13:54:1838import symbol_extractor
Benoit Lizea3fe2932017-10-20 10:24:5239
Monica Salamaeea2d942019-03-11 12:36:1840_SRC_PATH = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
41sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil'))
42from devil.android import device_utils
43from devil.android.sdk import version_codes
44
Benoit Lizea3fe2932017-10-20 10:24:5245
46_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)),
47 os.pardir, os.pardir)
48sys.path.append(os.path.join(_SRC_PATH, 'build', 'android'))
49import devil_chromium
50from pylib import constants
51
52
53# Needs to happen early for GetBuildType()/GetOutDirectory() to work correctly
54constants.SetBuildType('Release')
55
56
Matthew Cary53f74ba2019-01-24 10:07:1857# Architecture specific GN args. Trying to build an orderfile for an
58# architecture not listed here will eventually throw.
59_ARCH_GN_ARGS = {
60 'arm': [ 'target_cpu = "arm"' ],
61 'arm64': [ 'target_cpu = "arm64"',
62 'android_64bit_browser = true'],
63}
64
Benoit Lizea3fe2932017-10-20 10:24:5265class CommandError(Exception):
66 """Indicates that a dispatched shell command exited with a non-zero status."""
67
68 def __init__(self, value):
69 super(CommandError, self).__init__()
70 self.value = value
71
72 def __str__(self):
73 return repr(self.value)
74
75
76def _GenerateHash(file_path):
77 """Calculates and returns the hash of the file at file_path."""
78 sha1 = hashlib.sha1()
79 with open(file_path, 'rb') as f:
80 while True:
81 # Read in 1mb chunks, so it doesn't all have to be loaded into memory.
82 chunk = f.read(1024 * 1024)
83 if not chunk:
84 break
85 sha1.update(chunk)
86 return sha1.hexdigest()
87
88
89def _GetFileExtension(file_name):
90 """Calculates the file extension from a file name.
91
92 Args:
93 file_name: The source file name.
94 Returns:
95 The part of file_name after the dot (.) or None if the file has no
96 extension.
97 Examples: /home/user/foo.bar -> bar
98 /home/user.name/foo -> None
99 /home/user/.foo -> None
100 /home/user/foo.bar.baz -> baz
101 """
102 file_name_parts = os.path.basename(file_name).split('.')
103 if len(file_name_parts) > 1:
104 return file_name_parts[-1]
105 else:
106 return None
107
108
109def _StashOutputDirectory(buildpath):
110 """Takes the output directory and stashes it in the default output directory.
111
112 This allows it to be used for incremental builds next time (after unstashing)
113 by keeping it in a place that isn't deleted normally, while also ensuring
114 that it is properly clobbered when appropriate.
115
116 This is a dirty hack to deal with the needs of clobbering while also handling
117 incremental builds and the hardcoded relative paths used in some of the
118 project files.
119
120 Args:
121 buildpath: The path where the building happens. If this corresponds to the
122 default output directory, no action is taken.
123 """
124 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
125 constants.GetOutDirectory())):
126 return
127 name = os.path.basename(buildpath)
128 stashpath = os.path.join(constants.GetOutDirectory(), name)
129 if not os.path.exists(buildpath):
130 return
131 if os.path.exists(stashpath):
132 shutil.rmtree(stashpath, ignore_errors=True)
133 shutil.move(buildpath, stashpath)
134
135
136def _UnstashOutputDirectory(buildpath):
137 """Inverse of _StashOutputDirectory.
138
139 Moves the output directory stashed within the default output directory
140 (out/Release) to the position where the builds can actually happen.
141
142 This is a dirty hack to deal with the needs of clobbering while also handling
143 incremental builds and the hardcoded relative paths used in some of the
144 project files.
145
146 Args:
147 buildpath: The path where the building happens. If this corresponds to the
148 default output directory, no action is taken.
149 """
150 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
151 constants.GetOutDirectory())):
152 return
153 name = os.path.basename(buildpath)
154 stashpath = os.path.join(constants.GetOutDirectory(), name)
155 if not os.path.exists(stashpath):
156 return
157 if os.path.exists(buildpath):
158 shutil.rmtree(buildpath, ignore_errors=True)
159 shutil.move(stashpath, buildpath)
160
161
162class StepRecorder(object):
163 """Records steps and timings."""
164
165 def __init__(self, buildbot):
166 self.timings = []
167 self._previous_step = ('', 0.0)
168 self._buildbot = buildbot
169 self._error_recorded = False
170
171 def BeginStep(self, name):
172 """Marks a beginning of the next step in the script.
173
174 On buildbot, this prints a specially formatted name that will show up
175 in the waterfall. Otherwise, just prints the step name.
176
177 Args:
178 name: The name of the step.
179 """
180 self.EndStep()
181 self._previous_step = (name, time.time())
Raul Tambre48f176622019-09-23 10:05:24182 print('Running step: ', name)
Benoit Lizea3fe2932017-10-20 10:24:52183
184 def EndStep(self):
185 """Records successful completion of the current step.
186
187 This is optional if the step is immediately followed by another BeginStep.
188 """
189 if self._previous_step[0]:
190 elapsed = time.time() - self._previous_step[1]
Raul Tambre48f176622019-09-23 10:05:24191 print('Step %s took %f seconds' % (self._previous_step[0], elapsed))
Benoit Lizea3fe2932017-10-20 10:24:52192 self.timings.append((self._previous_step[0], elapsed))
193
194 self._previous_step = ('', 0.0)
195
196 def FailStep(self, message=None):
197 """Marks that a particular step has failed.
198
199 On buildbot, this will mark the current step as failed on the waterfall.
200 Otherwise we will just print an optional failure message.
201
202 Args:
203 message: An optional explanation as to why the step failed.
204 """
Raul Tambre48f176622019-09-23 10:05:24205 print('STEP FAILED!!')
Benoit Lizea3fe2932017-10-20 10:24:52206 if message:
Raul Tambre48f176622019-09-23 10:05:24207 print(message)
Benoit Lizea3fe2932017-10-20 10:24:52208 self._error_recorded = True
209 self.EndStep()
210
211 def ErrorRecorded(self):
212 """True if FailStep has been called."""
213 return self._error_recorded
214
215 def RunCommand(self, cmd, cwd=constants.DIR_SOURCE_ROOT, raise_on_error=True,
216 stdout=None):
217 """Execute a shell command.
218
219 Args:
220 cmd: A list of command strings.
Matthew Cary78aae162018-08-10 17:16:30221 cwd: Directory in which the command should be executed, defaults to build
222 root of script's location if not specified.
Benoit Lizea3fe2932017-10-20 10:24:52223 raise_on_error: If true will raise a CommandError if the call doesn't
224 succeed and mark the step as failed.
225 stdout: A file to redirect stdout for the command to.
226
227 Returns:
228 The process's return code.
229
230 Raises:
231 CommandError: An error executing the specified command.
232 """
Raul Tambre48f176622019-09-23 10:05:24233 print('Executing %s in %s' % (' '.join(cmd), cwd))
Benoit Lizea3fe2932017-10-20 10:24:52234 process = subprocess.Popen(cmd, stdout=stdout, cwd=cwd, env=os.environ)
235 process.wait()
236 if raise_on_error and process.returncode != 0:
237 self.FailStep()
238 raise CommandError('Exception executing command %s' % ' '.join(cmd))
239 return process.returncode
240
241
242class ClankCompiler(object):
243 """Handles compilation of clank."""
244
Christopher Grant073637472019-07-05 13:34:57245 def __init__(self, out_dir, step_recorder, arch, use_goma, goma_dir,
246 system_health_profiling, monochrome, public, orderfile_location):
Benoit Lizea3fe2932017-10-20 10:24:52247 self._out_dir = out_dir
248 self._step_recorder = step_recorder
Benoit Lizea87e5bc2017-11-07 15:12:57249 self._arch = arch
Benoit Lizea3fe2932017-10-20 10:24:52250 self._use_goma = use_goma
Benoit Lizea87e5bc2017-11-07 15:12:57251 self._goma_dir = goma_dir
Matthew Cary78aae162018-08-10 17:16:30252 self._system_health_profiling = system_health_profiling
Stephen Kylef11339f2019-03-25 09:00:47253 self._public = public
254 self._orderfile_location = orderfile_location
Matthew Caryd6bfcb72018-09-14 11:27:46255 if monochrome:
256 self._apk = 'Monochrome.apk'
Matthew Carye1b00062018-09-19 14:27:44257 self._apk_target = 'monochrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46258 self._libname = 'libmonochrome'
Matthew Cary2216fef2019-06-17 13:35:59259 self._libchrome_target = 'libmonochrome'
Matthew Caryd6bfcb72018-09-14 11:27:46260 else:
261 self._apk = 'Chrome.apk'
Matthew Carye1b00062018-09-19 14:27:44262 self._apk_target = 'chrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46263 self._libname = 'libchrome'
264 self._libchrome_target = 'libchrome'
Stephen Kylef11339f2019-03-25 09:00:47265 if public:
266 self._apk = self._apk.replace('.apk', 'Public.apk')
267 self._apk_target = self._apk_target.replace('_apk', '_public_apk')
Matthew Cary78aae162018-08-10 17:16:30268
269 self.obj_dir = os.path.join(self._out_dir, 'Release', 'obj')
Benoit Lizea3fe2932017-10-20 10:24:52270 self.lib_chrome_so = os.path.join(
Matthew Caryd6bfcb72018-09-14 11:27:46271 self._out_dir, 'Release', 'lib.unstripped',
272 '{}.so'.format(self._libname))
273 self.chrome_apk = os.path.join(self._out_dir, 'Release', 'apks', self._apk)
Benoit Lizea3fe2932017-10-20 10:24:52274
Monica Basta99c101f2019-05-21 13:50:05275 def Build(self, instrumented, use_call_graph, target):
Benoit Lizea3fe2932017-10-20 10:24:52276 """Builds the provided ninja target with or without order_profiling on.
277
278 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57279 instrumented: (bool) Whether we want to build an instrumented binary.
Monica Basta99c101f2019-05-21 13:50:05280 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea87e5bc2017-11-07 15:12:57281 target: (str) The name of the ninja target to build.
Benoit Lizea3fe2932017-10-20 10:24:52282 """
283 self._step_recorder.BeginStep('Compile %s' % target)
Monica Basta99c101f2019-05-21 13:50:05284 assert not use_call_graph or instrumented, ('You can not enable call graph '
285 'without instrumentation!')
Benoit Lizea3fe2932017-10-20 10:24:52286
287 # Set the "Release Official" flavor, the parts affecting performance.
288 args = [
Peter Collingbourne1c85d5d22018-08-22 18:06:35289 'enable_resource_whitelist_generation=false',
Stephen Kylef11339f2019-03-25 09:00:47290 'is_chrome_branded=' + str(not self._public).lower(),
Benoit Lizea3fe2932017-10-20 10:24:52291 'is_debug=false',
292 'is_official_build=true',
Benoit Lizea3fe2932017-10-20 10:24:52293 'target_os="android"',
294 'use_goma=' + str(self._use_goma).lower(),
295 'use_order_profiling=' + str(instrumented).lower(),
Monica Basta99c101f2019-05-21 13:50:05296 'use_call_graph=' + str(use_call_graph).lower(),
Benoit Lizea3fe2932017-10-20 10:24:52297 ]
Matthew Cary53f74ba2019-01-24 10:07:18298 args += _ARCH_GN_ARGS[self._arch]
Benoit Lizea3fe2932017-10-20 10:24:52299 if self._goma_dir:
300 args += ['goma_dir="%s"' % self._goma_dir]
Matthew Cary78aae162018-08-10 17:16:30301 if self._system_health_profiling:
302 args += ['devtools_instrumentation_dumping = ' +
303 str(instrumented).lower()]
Benoit Lizea87e5bc2017-11-07 15:12:57304
Stephen Kylef11339f2019-03-25 09:00:47305 if self._public and os.path.exists(self._orderfile_location):
306 # GN needs the orderfile path to be source-absolute.
307 src_abs_orderfile = os.path.relpath(self._orderfile_location,
308 constants.DIR_SOURCE_ROOT)
309 args += ['chrome_orderfile="//{}"'.format(src_abs_orderfile)]
310
Benoit Lizea3fe2932017-10-20 10:24:52311 self._step_recorder.RunCommand(
312 ['gn', 'gen', os.path.join(self._out_dir, 'Release'),
313 '--args=' + ' '.join(args)])
314
315 self._step_recorder.RunCommand(
Christopher Grant073637472019-07-05 13:34:57316 ['autoninja', '-C',
317 os.path.join(self._out_dir, 'Release'), target])
Benoit Lizea3fe2932017-10-20 10:24:52318
Christopher Grant10221762019-07-05 12:10:04319 def ForceRelink(self):
320 """Forces libchrome.so or libmonochrome.so to be re-linked.
321
322 With partitioned libraries enabled, deleting these library files does not
323 guarantee they'll be recreated by the linker (they may simply be
324 re-extracted from a combined library). To be safe, touch a source file
325 instead. See http://crbug.com/972701 for more explanation.
326 """
327 file_to_touch = os.path.join(constants.DIR_SOURCE_ROOT, 'chrome', 'browser',
328 'chrome_browser_main_android.cc')
329 assert os.path.exists(file_to_touch)
330 self._step_recorder.RunCommand(['touch', file_to_touch])
331
Monica Basta99c101f2019-05-21 13:50:05332 def CompileChromeApk(self, instrumented, use_call_graph, force_relink=False):
Benoit Lizea3fe2932017-10-20 10:24:52333 """Builds a Chrome.apk either with or without order_profiling on.
334
335 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57336 instrumented: (bool) Whether to build an instrumented apk.
Monica Basta99c101f2019-05-21 13:50:05337 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea3fe2932017-10-20 10:24:52338 force_relink: Whether libchromeview.so should be re-created.
339 """
340 if force_relink:
Christopher Grant10221762019-07-05 12:10:04341 self.ForceRelink()
Monica Basta99c101f2019-05-21 13:50:05342 self.Build(instrumented, use_call_graph, self._apk_target)
Benoit Lizea3fe2932017-10-20 10:24:52343
Monica Basta99c101f2019-05-21 13:50:05344 def CompileLibchrome(self, instrumented, use_call_graph, force_relink=False):
Benoit Lizea3fe2932017-10-20 10:24:52345 """Builds a libchrome.so either with or without order_profiling on.
346
347 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57348 instrumented: (bool) Whether to build an instrumented apk.
Monica Basta99c101f2019-05-21 13:50:05349 use_call_graph: (bool) Whether to use the call graph instrumentation.
Benoit Lizea87e5bc2017-11-07 15:12:57350 force_relink: (bool) Whether libchrome.so should be re-created.
Benoit Lizea3fe2932017-10-20 10:24:52351 """
352 if force_relink:
Christopher Grant10221762019-07-05 12:10:04353 self.ForceRelink()
Monica Basta99c101f2019-05-21 13:50:05354 self.Build(instrumented, use_call_graph, self._libchrome_target)
Benoit Lizea3fe2932017-10-20 10:24:52355
356
357class OrderfileUpdater(object):
358 """Handles uploading and committing a new orderfile in the repository.
359
360 Only used for testing or on a bot.
361 """
362
363 _CLOUD_STORAGE_BUCKET_FOR_DEBUG = None
364 _CLOUD_STORAGE_BUCKET = None
365 _UPLOAD_TO_CLOUD_COMMAND = 'upload_to_google_storage.py'
366
Egor Pasko7ff04122019-11-25 15:47:18367 def __init__(self, repository_root, step_recorder, branch, netrc=None):
Benoit Lizea3fe2932017-10-20 10:24:52368 """Constructor.
369
370 Args:
371 repository_root: (str) Root of the target repository.
372 step_recorder: (StepRecorder) Step recorder, for logging.
373 branch: (str) Branch to commit to.
Egor Pasko7ff04122019-11-25 15:47:18374 netrc: (str) Obsolete. Specified by code in internal repo. TODO(pasko):
375 remove.
Benoit Lizea3fe2932017-10-20 10:24:52376 """
Egor Pasko7ff04122019-11-25 15:47:18377 # pylint: disable=unused-argument
Benoit Lizea3fe2932017-10-20 10:24:52378 self._repository_root = repository_root
379 self._step_recorder = step_recorder
380 self._branch = branch
Benoit Lizea3fe2932017-10-20 10:24:52381
Matthew Cary86a226e2019-03-19 12:17:44382 def CommitStashedFileHashes(self, files):
383 """Commits unpatched and patched orderfiles hashes if changed.
384
385 The files are committed only if their associated sha1 hash files match, and
386 are modified in git. In normal operations the hash files are changed only
387 when a file is uploaded to cloud storage. If the hash file is not modified
388 in git, the file is skipped.
389
390 Args:
391 files: [str or None] specifies file paths. None items are ignored.
392
393 Raises:
394 Exception if the hash file does not match the file.
395 NotImplementedError when the commit logic hasn't been overridden.
396 """
397 files_to_commit = list(filter(None, files))
398 if files_to_commit:
399 self._CommitStashedFiles(files_to_commit)
400
401 def LegacyCommitFileHashes(self,
402 unpatched_orderfile_filename,
403 orderfile_filename):
Benoit Lizea3fe2932017-10-20 10:24:52404 """Commits unpatched and patched orderfiles hashes, if provided.
405
Matthew Cary86a226e2019-03-19 12:17:44406 DEPRECATED. Left in place during transition.
407
Benoit Lizea3fe2932017-10-20 10:24:52408 Files must have been successfilly uploaded to cloud storage first.
409
410 Args:
411 unpatched_orderfile_filename: (str or None) Unpatched orderfile path.
412 orderfile_filename: (str or None) Orderfile path.
413
414 Raises:
Matthew Cary86a226e2019-03-19 12:17:44415 NotImplementedError when the commit logic hasn't been overridden.
Benoit Lizea3fe2932017-10-20 10:24:52416 """
417 files_to_commit = []
418 commit_message_lines = ['Update Orderfile.']
419 for filename in [unpatched_orderfile_filename, orderfile_filename]:
420 if not filename:
421 continue
422 (relative_path, sha1) = self._GetHashFilePathAndContents(filename)
423 commit_message_lines.append('Profile: %s: %s' % (
424 os.path.basename(relative_path), sha1))
425 files_to_commit.append(relative_path)
426 if files_to_commit:
427 self._CommitFiles(files_to_commit, commit_message_lines)
428
429 def UploadToCloudStorage(self, filename, use_debug_location):
430 """Uploads a file to cloud storage.
431
432 Args:
433 filename: (str) File to upload.
434 use_debug_location: (bool) Whether to use the debug location.
435 """
436 bucket = (self._CLOUD_STORAGE_BUCKET_FOR_DEBUG if use_debug_location
437 else self._CLOUD_STORAGE_BUCKET)
438 extension = _GetFileExtension(filename)
439 cmd = [self._UPLOAD_TO_CLOUD_COMMAND, '--bucket', bucket]
440 if extension:
441 cmd.extend(['-z', extension])
442 cmd.append(filename)
443 self._step_recorder.RunCommand(cmd)
Raul Tambre48f176622019-09-23 10:05:24444 print('Download: https://sandbox.google.com/storage/%s/%s' %
445 (bucket, _GenerateHash(filename)))
Benoit Lizea3fe2932017-10-20 10:24:52446
447 def _GetHashFilePathAndContents(self, filename):
448 """Gets the name and content of the hash file created from uploading the
449 given file.
450
451 Args:
452 filename: (str) The file that was uploaded to cloud storage.
453
454 Returns:
455 A tuple of the hash file name, relative to the reository root, and the
456 content, which should be the sha1 hash of the file
457 ('base_file.sha1', hash)
458 """
459 abs_hash_filename = filename + '.sha1'
460 rel_hash_filename = os.path.relpath(
461 abs_hash_filename, self._repository_root)
462 with open(abs_hash_filename, 'r') as f:
463 return (rel_hash_filename, f.read())
464
465 def _CommitFiles(self, files_to_commit, commit_message_lines):
466 """Commits a list of files, with a given message."""
467 raise NotImplementedError
468
Matthew Cary86a226e2019-03-19 12:17:44469 def _GitStash(self):
470 """Git stash the current clank tree.
471
472 Raises:
473 NotImplementedError when the stash logic hasn't been overridden.
474 """
475 raise NotImplementedError
476
477 def _CommitStashedFiles(self, expected_files_in_stash):
478 """Commits stashed files.
479
480 The local repository is updated and then the files to commit are taken from
481 modified files from the git stash. The modified files should be a subset of
482 |expected_files_in_stash|. If there are unexpected modified files, this
483 function may raise. This is meant to be paired with _GitStash().
484
485 Args:
486 expected_files_in_stash: [str] paths to a possible superset of files
487 expected to be stashed & committed.
488
489 Raises:
490 NotImplementedError when the commit logic hasn't been overridden.
491 """
492 raise NotImplementedError
493
Benoit Lizea3fe2932017-10-20 10:24:52494
Stephen Kylef11339f2019-03-25 09:00:47495class OrderfileNoopUpdater(OrderfileUpdater):
496 def CommitFileHashes(self, unpatched_orderfile_filename, orderfile_filename):
497 # pylint: disable=unused-argument
498 return
499
500 def UploadToCloudStorage(self, filename, use_debug_location):
501 # pylint: disable=unused-argument
502 return
503
504 def _CommitFiles(self, files_to_commit, commit_message_lines):
505 raise NotImplementedError
506
507
Benoit Lizea3fe2932017-10-20 10:24:52508class OrderfileGenerator(object):
509 """A utility for generating a new orderfile for Clank.
510
511 Builds an instrumented binary, profiles a run of the application, and
512 generates an updated orderfile.
513 """
Benoit Lizea3fe2932017-10-20 10:24:52514 _CHECK_ORDERFILE_SCRIPT = os.path.join(
515 constants.DIR_SOURCE_ROOT, 'tools', 'cygprofile', 'check_orderfile.py')
516 _BUILD_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(
517 constants.GetOutDirectory()))) # Normally /path/to/src
518
Benoit Lizea3fe2932017-10-20 10:24:52519 # Previous orderfile_generator debug files would be overwritten.
520 _DIRECTORY_FOR_DEBUG_FILES = '/tmp/orderfile_generator_debug_files'
521
Stephen Kylef11339f2019-03-25 09:00:47522 def _PrepareOrderfilePaths(self):
523 if self._options.public:
524 self._clank_dir = os.path.join(constants.DIR_SOURCE_ROOT,
525 '')
526 if not os.path.exists(os.path.join(self._clank_dir, 'orderfiles')):
527 os.makedirs(os.path.join(self._clank_dir, 'orderfiles'))
528 else:
529 self._clank_dir = os.path.join(constants.DIR_SOURCE_ROOT,
530 'clank')
531
532 self._unpatched_orderfile_filename = os.path.join(
533 self._clank_dir, 'orderfiles', 'unpatched_orderfile.%s')
534 self._path_to_orderfile = os.path.join(
535 self._clank_dir, 'orderfiles', 'orderfile.%s.out')
536
Benoit Lizea3fe2932017-10-20 10:24:52537 def _GetPathToOrderfile(self):
538 """Gets the path to the architecture-specific orderfile."""
Stephen Kylef11339f2019-03-25 09:00:47539 return self._path_to_orderfile % self._options.arch
Benoit Lizea3fe2932017-10-20 10:24:52540
541 def _GetUnpatchedOrderfileFilename(self):
542 """Gets the path to the architecture-specific unpatched orderfile."""
Stephen Kylef11339f2019-03-25 09:00:47543 return self._unpatched_orderfile_filename % self._options.arch
Benoit Lizea3fe2932017-10-20 10:24:52544
Monica Salamaeea2d942019-03-11 12:36:18545 def _SetDevice(self):
546 """ Selects the device to be used by the script.
547
548 Returns:
549 (Device with given serial ID) : if the --device flag is set.
550 (Device running Android[K,L]) : if --use-legacy-chrome-apk flag is set or
551 no device running Android N+ was found.
552 (Device running Android N+) : Otherwise.
553
554 Raises Error:
555 If no device meeting the requirements has been found.
556 """
557 devices = None
558 if self._options.device:
559 devices = [device_utils.DeviceUtils(self._options.device)]
560 else:
561 devices = device_utils.DeviceUtils.HealthyDevices()
562
563 assert devices, 'Expected at least one connected device'
564
565 if self._options.use_legacy_chrome_apk:
566 self._monochrome = False
567 for device in devices:
568 device_version = device.build_version_sdk
569 if (device_version >= version_codes.KITKAT
570 and device_version <= version_codes.LOLLIPOP_MR1):
571 return device
572
573 assert not self._options.use_legacy_chrome_apk, \
574 'No device found running suitable android version for Chrome.apk.'
575
576 preferred_device = None
577 for device in devices:
578 if device.build_version_sdk >= version_codes.NOUGAT:
Monica Salama90b8f5b2019-04-25 11:10:38579 preferred_device = device
580 break
Monica Salamaeea2d942019-03-11 12:36:18581
582 self._monochrome = preferred_device is not None
583
584 return preferred_device if preferred_device else devices[0]
585
586
Benoit Lizea3fe2932017-10-20 10:24:52587 def __init__(self, options, orderfile_updater_class):
588 self._options = options
Benoit Lizea3fe2932017-10-20 10:24:52589 self._instrumented_out_dir = os.path.join(
590 self._BUILD_ROOT, self._options.arch + '_instrumented_out')
Monica Basta99c101f2019-05-21 13:50:05591 if self._options.use_call_graph:
Christopher Grantdfe1bac2019-07-05 13:34:10592 self._instrumented_out_dir += '_call_graph'
Monica Basta99c101f2019-05-21 13:50:05593
Benoit Lizea3fe2932017-10-20 10:24:52594 self._uninstrumented_out_dir = os.path.join(
595 self._BUILD_ROOT, self._options.arch + '_uninstrumented_out')
Monica Salama90b8f5b2019-04-25 11:10:38596 self._no_orderfile_out_dir = os.path.join(
597 self._BUILD_ROOT, self._options.arch + '_no_orderfile_out')
Benoit Lizea3fe2932017-10-20 10:24:52598
Stephen Kylef11339f2019-03-25 09:00:47599 self._PrepareOrderfilePaths()
600
Benoit Lizea3fe2932017-10-20 10:24:52601 if options.profile:
Benoit Lizea1b64f82017-12-07 10:12:50602 output_directory = os.path.join(self._instrumented_out_dir, 'Release')
Matthew Caryb8daed942018-06-11 10:58:08603 host_profile_dir = os.path.join(output_directory, 'profile_data')
Benoit Lizea1b64f82017-12-07 10:12:50604 urls = [profile_android_startup.AndroidProfileTool.TEST_URL]
605 use_wpr = True
606 simulate_user = False
Benoit L96466812018-03-06 15:58:37607 urls = options.urls
608 use_wpr = not options.no_wpr
609 simulate_user = options.simulate_user
Monica Salamaeea2d942019-03-11 12:36:18610 device = self._SetDevice()
Benoit Lizea3fe2932017-10-20 10:24:52611 self._profiler = profile_android_startup.AndroidProfileTool(
Matthew Cary453ff1452018-07-18 12:19:35612 output_directory, host_profile_dir, use_wpr, urls, simulate_user,
Matthew Cary1ea82212019-03-20 12:10:27613 device, debug=self._options.streamline_for_debugging)
Matthew Cary69e9e422018-08-10 18:30:06614 if options.pregenerated_profiles:
615 self._profiler.SetPregeneratedProfiles(
616 glob.glob(options.pregenerated_profiles))
617 else:
618 assert not options.pregenerated_profiles, (
619 '--pregenerated-profiles cannot be used with --skip-profile')
620 assert not options.profile_save_dir, (
621 '--profile-save-dir cannot be used with --skip-profile')
Monica Salamaeea2d942019-03-11 12:36:18622 self._monochrome = not self._options.use_legacy_chrome_apk
Benoit Lizea3fe2932017-10-20 10:24:52623
Matthew Caryf949bba2019-02-04 13:39:23624 # Outlined function handling enabled by default for all architectures.
625 self._order_outlined_functions = not options.noorder_outlined_functions
626
Benoit Lizea3fe2932017-10-20 10:24:52627 self._output_data = {}
628 self._step_recorder = StepRecorder(options.buildbot)
629 self._compiler = None
Stephen Kylef11339f2019-03-25 09:00:47630 if orderfile_updater_class is None:
631 if options.public:
632 orderfile_updater_class = OrderfileNoopUpdater
633 else:
634 orderfile_updater_class = OrderfileUpdater
Benoit Lizea3fe2932017-10-20 10:24:52635 assert issubclass(orderfile_updater_class, OrderfileUpdater)
Stephen Kylef11339f2019-03-25 09:00:47636 self._orderfile_updater = orderfile_updater_class(self._clank_dir,
Benoit Lizea3fe2932017-10-20 10:24:52637 self._step_recorder,
Egor Pasko7ff04122019-11-25 15:47:18638 options.branch)
Benoit Lizea3fe2932017-10-20 10:24:52639 assert os.path.isdir(constants.DIR_SOURCE_ROOT), 'No src directory found'
Benoit Lizefefbb27c2018-01-17 13:54:18640 symbol_extractor.SetArchitecture(options.arch)
Benoit Lizea3fe2932017-10-20 10:24:52641
Benoit Lizea3fe2932017-10-20 10:24:52642 @staticmethod
643 def _RemoveBlanks(src_file, dest_file):
644 """A utility to remove blank lines from a file.
645
646 Args:
647 src_file: The name of the file to remove the blanks from.
648 dest_file: The name of the file to write the output without blanks.
649 """
650 assert src_file != dest_file, 'Source and destination need to be distinct'
651
652 try:
653 src = open(src_file, 'r')
654 dest = open(dest_file, 'w')
655 for line in src:
656 if line and not line.isspace():
657 dest.write(line)
658 finally:
659 src.close()
660 dest.close()
661
662 def _GenerateAndProcessProfile(self):
Matthew Cary78aae162018-08-10 17:16:30663 """Invokes a script to merge the per-thread traces into one file.
664
665 The produced list of offsets is saved in
666 self._GetUnpatchedOrderfileFilename().
667 """
Benoit Lizea3fe2932017-10-20 10:24:52668 self._step_recorder.BeginStep('Generate Profile Data')
669 files = []
Matthew Cary78aae162018-08-10 17:16:30670 logging.getLogger().setLevel(logging.DEBUG)
Christopher Grantdfe1bac2019-07-05 13:34:10671
672 if self._options.profile_save_dir:
673 # The directory must not preexist, to ensure purity of data. Check
674 # before profiling to save time.
675 if os.path.exists(self._options.profile_save_dir):
676 raise Exception('Profile save directory must not pre-exist')
677 os.makedirs(self._options.profile_save_dir)
678
Matthew Cary78aae162018-08-10 17:16:30679 if self._options.system_health_orderfile:
680 files = self._profiler.CollectSystemHealthProfile(
681 self._compiler.chrome_apk)
Matthew Cary69e9e422018-08-10 18:30:06682 self._MaybeSaveProfile(files)
Matthew Cary78aae162018-08-10 17:16:30683 try:
684 self._ProcessPhasedOrderfile(files)
685 except Exception:
686 for f in files:
687 self._SaveForDebugging(f)
Matthew Cary8b1416232018-08-10 19:12:22688 self._SaveForDebugging(self._compiler.lib_chrome_so)
Matthew Cary78aae162018-08-10 17:16:30689 raise
690 finally:
691 self._profiler.Cleanup()
692 else:
693 self._CollectLegacyProfile()
694 logging.getLogger().setLevel(logging.INFO)
695
696 def _ProcessPhasedOrderfile(self, files):
697 """Process the phased orderfiles produced by system health benchmarks.
698
699 The offsets will be placed in _GetUnpatchedOrderfileFilename().
700
701 Args:
702 file: Profile files pulled locally.
703 """
704 self._step_recorder.BeginStep('Process Phased Orderfile')
705 profiles = process_profiles.ProfileManager(files)
706 processor = process_profiles.SymbolOffsetProcessor(
707 self._compiler.lib_chrome_so)
Monica Basta99c101f2019-05-21 13:50:05708 ordered_symbols = cluster.ClusterOffsets(profiles, processor,
709 call_graph=self._options.use_call_graph)
Matthew Cary8b1416232018-08-10 19:12:22710 if not ordered_symbols:
711 raise Exception('Failed to get ordered symbols')
Matthew Caryda9db932019-03-11 15:28:17712 for sym in ordered_symbols:
713 assert not sym.startswith('OUTLINED_FUNCTION_'), (
714 'Outlined function found in instrumented function, very likely '
715 'something has gone very wrong!')
Matthew Cary91df9792018-11-30 14:35:15716 self._output_data['offsets_kib'] = processor.SymbolsSize(
717 ordered_symbols) / 1024
Matthew Cary78aae162018-08-10 17:16:30718 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
Matthew Cary8b1416232018-08-10 19:12:22719 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary78aae162018-08-10 17:16:30720
721 def _CollectLegacyProfile(self):
Matthew Cary2df8d452018-09-19 07:50:20722 files = []
Benoit Lizea3fe2932017-10-20 10:24:52723 try:
Benoit Lizea3fe2932017-10-20 10:24:52724 files = self._profiler.CollectProfile(
725 self._compiler.chrome_apk,
726 constants.PACKAGE_INFO['chrome'])
Matthew Cary69e9e422018-08-10 18:30:06727 self._MaybeSaveProfile(files)
Matthew Caryb8daed942018-06-11 10:58:08728 self._step_recorder.BeginStep('Process profile')
Benoit L96466812018-03-06 15:58:37729 assert os.path.exists(self._compiler.lib_chrome_so)
730 offsets = process_profiles.GetReachedOffsetsFromDumpFiles(
731 files, self._compiler.lib_chrome_so)
732 if not offsets:
733 raise Exception('No profiler offsets found in {}'.format(
734 '\n'.join(files)))
Matthew Cary8b1416232018-08-10 19:12:22735 processor = process_profiles.SymbolOffsetProcessor(
736 self._compiler.lib_chrome_so)
737 ordered_symbols = processor.GetOrderedSymbols(offsets)
738 if not ordered_symbols:
739 raise Exception('No symbol names from offsets found in {}'.format(
740 '\n'.join(files)))
741 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
742 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary0f1f681a2018-01-22 10:40:51743 except Exception:
Benoit Lizea3fe2932017-10-20 10:24:52744 for f in files:
745 self._SaveForDebugging(f)
746 raise
747 finally:
748 self._profiler.Cleanup()
Benoit Lizea3fe2932017-10-20 10:24:52749
Matthew Cary69e9e422018-08-10 18:30:06750 def _MaybeSaveProfile(self, files):
751 if self._options.profile_save_dir:
752 logging.info('Saving profiles to %s', self._options.profile_save_dir)
753 for f in files:
754 shutil.copy(f, self._options.profile_save_dir)
755 logging.info('Saved profile %s', f)
756
Benoit Lizea3fe2932017-10-20 10:24:52757 def _PatchOrderfile(self):
758 """Patches the orderfile using clean version of libchrome.so."""
759 self._step_recorder.BeginStep('Patch Orderfile')
Benoit Lizefefbb27c2018-01-17 13:54:18760 patch_orderfile.GeneratePatchedOrderfile(
761 self._GetUnpatchedOrderfileFilename(), self._compiler.lib_chrome_so,
Matthew Caryf949bba2019-02-04 13:39:23762 self._GetPathToOrderfile(), self._order_outlined_functions)
Benoit Lizea3fe2932017-10-20 10:24:52763
764 def _VerifySymbolOrder(self):
765 self._step_recorder.BeginStep('Verify Symbol Order')
766 return_code = self._step_recorder.RunCommand(
767 [self._CHECK_ORDERFILE_SCRIPT, self._compiler.lib_chrome_so,
768 self._GetPathToOrderfile(),
769 '--target-arch=' + self._options.arch],
770 constants.DIR_SOURCE_ROOT,
771 raise_on_error=False)
772 if return_code:
773 self._step_recorder.FailStep('Orderfile check returned %d.' % return_code)
774
775 def _RecordHash(self, file_name):
776 """Records the hash of the file into the output_data dictionary."""
777 self._output_data[os.path.basename(file_name) + '.sha1'] = _GenerateHash(
778 file_name)
779
780 def _SaveFileLocally(self, file_name, file_sha1):
781 """Saves the file to a temporary location and prints the sha1sum."""
782 if not os.path.exists(self._DIRECTORY_FOR_DEBUG_FILES):
783 os.makedirs(self._DIRECTORY_FOR_DEBUG_FILES)
784 shutil.copy(file_name, self._DIRECTORY_FOR_DEBUG_FILES)
Raul Tambre48f176622019-09-23 10:05:24785 print('File: %s, saved in: %s, sha1sum: %s' %
786 (file_name, self._DIRECTORY_FOR_DEBUG_FILES, file_sha1))
Benoit Lizea3fe2932017-10-20 10:24:52787
788 def _SaveForDebugging(self, filename):
789 """Uploads the file to cloud storage or saves to a temporary location."""
790 file_sha1 = _GenerateHash(filename)
791 if not self._options.buildbot:
792 self._SaveFileLocally(filename, file_sha1)
793 else:
Raul Tambre48f176622019-09-23 10:05:24794 print('Uploading file for debugging: ' + filename)
Benoit Lizea3fe2932017-10-20 10:24:52795 self._orderfile_updater.UploadToCloudStorage(
796 filename, use_debug_location=True)
797
798 def _SaveForDebuggingWithOverwrite(self, file_name):
799 """Uploads and overwrites the file in cloud storage or copies locally.
800
801 Should be used for large binaries like lib_chrome_so.
802
803 Args:
804 file_name: (str) File to upload.
805 """
806 file_sha1 = _GenerateHash(file_name)
807 if not self._options.buildbot:
808 self._SaveFileLocally(file_name, file_sha1)
809 else:
Raul Tambre48f176622019-09-23 10:05:24810 print('Uploading file for debugging: %s, sha1sum: %s' % (file_name,
811 file_sha1))
Benoit Lizea3fe2932017-10-20 10:24:52812 upload_location = '%s/%s' % (
813 self._CLOUD_STORAGE_BUCKET_FOR_DEBUG, os.path.basename(file_name))
814 self._step_recorder.RunCommand([
815 'gsutil.py', 'cp', file_name, 'gs://' + upload_location])
Raul Tambre48f176622019-09-23 10:05:24816 print('Uploaded to: https://sandbox.google.com/storage/' +
817 upload_location)
Benoit Lizea3fe2932017-10-20 10:24:52818
819 def _MaybeArchiveOrderfile(self, filename):
820 """In buildbot configuration, uploads the generated orderfile to
821 Google Cloud Storage.
822
823 Args:
824 filename: (str) Orderfile to upload.
825 """
Matthew Cary91df9792018-11-30 14:35:15826 # First compute hashes so that we can download them later if we need to.
Benoit Lizea3fe2932017-10-20 10:24:52827 self._step_recorder.BeginStep('Compute hash for ' + filename)
828 self._RecordHash(filename)
829 if self._options.buildbot:
830 self._step_recorder.BeginStep('Archive ' + filename)
831 self._orderfile_updater.UploadToCloudStorage(
832 filename, use_debug_location=False)
833
Egor Paskobce64d012018-11-20 12:02:37834 def UploadReadyOrderfiles(self):
835 self._step_recorder.BeginStep('Upload Ready Orderfiles')
836 for file_name in [self._GetUnpatchedOrderfileFilename(),
837 self._GetPathToOrderfile()]:
838 self._orderfile_updater.UploadToCloudStorage(
839 file_name, use_debug_location=False)
840
Monica Salama90b8f5b2019-04-25 11:10:38841 def _NativeCodeMemoryBenchmark(self, apk):
842 """Runs system_health.memory_mobile to assess native code memory footprint.
843
844 Args:
845 apk: (str) Path to the apk.
846
847 Returns:
848 results: ([int]) Values of native code memory footprint in bytes from the
849 benchmark results.
850 """
851 self._step_recorder.BeginStep("Running orderfile.memory_mobile")
852 try:
853 out_dir = tempfile.mkdtemp()
854 self._profiler._RunCommand(['tools/perf/run_benchmark',
855 '--device={}'.format(
856 self._profiler._device.serial),
857 '--browser=exact',
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54858 '--output-format=csv',
Monica Salama90b8f5b2019-04-25 11:10:38859 '--output-dir={}'.format(out_dir),
860 '--reset-results',
861 '--browser-executable={}'.format(apk),
862 'orderfile.memory_mobile'])
863
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54864 out_file_path = os.path.join(out_dir, 'results.csv')
Monica Salama90b8f5b2019-04-25 11:10:38865 if not os.path.exists(out_file_path):
Monica Basta8ec87fd2019-05-13 12:12:35866 raise Exception('Results file not found!')
Monica Salama90b8f5b2019-04-25 11:10:38867
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54868 results = {}
Monica Salama90b8f5b2019-04-25 11:10:38869 with open(out_file_path, 'r') as f:
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54870 reader = csv.DictReader(f)
871 for row in reader:
872 if not row['name'].endswith('NativeCodeResidentMemory'):
Monica Basta8ec87fd2019-05-13 12:12:35873 continue
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54874 # Note: NativeCodeResidentMemory records a single sample from each
875 # story run, so this average (reported as 'avg') is exactly the value
876 # of that one sample. Each story is run multiple times, so this loop
877 # will accumulate into a list all values for all runs of each story.
878 results.setdefault(row['name'], {}).setdefault(
879 row['stories'], []).append(row['avg'])
Monica Basta8ec87fd2019-05-13 12:12:35880
Juan Antonio Navarro Perez5b0867f72019-10-21 15:21:54881 if not results:
882 raise Exception('Could not find relevant results')
883
Monica Basta8ec87fd2019-05-13 12:12:35884 return results
885
886 except Exception as e:
887 return 'Error: ' + str(e)
Monica Salama90b8f5b2019-04-25 11:10:38888
889 finally:
890 shutil.rmtree(out_dir)
891
Monica Salama90b8f5b2019-04-25 11:10:38892
893 def _PerformanceBenchmark(self, apk):
894 """Runs Speedometer2.0 to assess performance.
895
896 Args:
897 apk: (str) Path to the apk.
898
899 Returns:
900 results: ([float]) Speedometer2.0 results samples in milliseconds.
901 """
902 self._step_recorder.BeginStep("Running Speedometer2.0.")
903 try:
904 out_dir = tempfile.mkdtemp()
905 self._profiler._RunCommand(['tools/perf/run_benchmark',
906 '--device={}'.format(
907 self._profiler._device.serial),
908 '--browser=exact',
Monica Basta8ec87fd2019-05-13 12:12:35909 '--output-format=histograms',
Monica Salama90b8f5b2019-04-25 11:10:38910 '--output-dir={}'.format(out_dir),
911 '--reset-results',
912 '--browser-executable={}'.format(apk),
913 'speedometer2'])
914
Monica Basta8ec87fd2019-05-13 12:12:35915 out_file_path = os.path.join(out_dir, 'histograms.json')
Monica Salama90b8f5b2019-04-25 11:10:38916 if not os.path.exists(out_file_path):
Monica Basta8ec87fd2019-05-13 12:12:35917 raise Exception('Results file not found!')
Monica Salama90b8f5b2019-04-25 11:10:38918
919 with open(out_file_path, 'r') as f:
920 results = json.load(f)
921
922 if not results:
Monica Basta8ec87fd2019-05-13 12:12:35923 raise Exception('Results file is empty.')
924
925 for el in results:
926 if 'name' in el and el['name'] == 'Total' and 'sampleValues' in el:
927 return el['sampleValues']
928
929 raise Exception('Unexpected results format.')
930
931 except Exception as e:
932 return 'Error: ' + str(e)
Monica Salama90b8f5b2019-04-25 11:10:38933
934 finally:
935 shutil.rmtree(out_dir)
936
Monica Salama90b8f5b2019-04-25 11:10:38937
938 def RunBenchmark(self, out_directory, no_orderfile=False):
939 """Builds chrome apk and runs performance and memory benchmarks.
940
941 Builds a non-instrumented version of chrome.
942 Installs chrome apk on the device.
943 Runs Speedometer2.0 benchmark to assess performance.
944 Runs system_health.memory_mobile to evaluate memory footprint.
945
946 Args:
947 out_directory: (str) Path to out directory for this build.
948 no_orderfile: (bool) True if chrome to be built without orderfile.
949
950 Returns:
951 benchmark_results: (dict) Results extracted from benchmarks.
952 """
953 try:
954 _UnstashOutputDirectory(out_directory)
Christopher Grant073637472019-07-05 13:34:57955 self._compiler = ClankCompiler(out_directory, self._step_recorder,
956 self._options.arch, self._options.use_goma,
957 self._options.goma_dir,
958 self._options.system_health_orderfile,
959 self._monochrome, self._options.public,
960 self._GetPathToOrderfile())
Monica Salama90b8f5b2019-04-25 11:10:38961
962 if no_orderfile:
963 orderfile_path = self._GetPathToOrderfile()
964 backup_orderfile = orderfile_path + '.backup'
965 shutil.move(orderfile_path, backup_orderfile)
966 open(orderfile_path, 'w').close()
967
968 # Build APK to be installed on the device.
Monica Basta99c101f2019-05-21 13:50:05969 self._compiler.CompileChromeApk(instrumented=False,
970 use_call_graph=False,
971 force_relink=True)
Monica Salama90b8f5b2019-04-25 11:10:38972 benchmark_results = dict()
973 benchmark_results['Speedometer2.0'] = self._PerformanceBenchmark(
974 self._compiler.chrome_apk)
975 benchmark_results['orderfile.memory_mobile'] = (
976 self._NativeCodeMemoryBenchmark(self._compiler.chrome_apk))
Monica Basta8ec87fd2019-05-13 12:12:35977
978 except Exception as e:
979 benchmark_results['Error'] = str(e)
980
Monica Salama90b8f5b2019-04-25 11:10:38981 finally:
982 if no_orderfile and os.path.exists(backup_orderfile):
983 shutil.move(backup_orderfile, orderfile_path)
984 _StashOutputDirectory(out_directory)
985
986 return benchmark_results
987
Benoit Lizea3fe2932017-10-20 10:24:52988 def Generate(self):
989 """Generates and maybe upload an order."""
990 profile_uploaded = False
991 orderfile_uploaded = False
992
Matthew Carye8400642018-06-14 15:43:02993 assert (bool(self._options.profile) ^
994 bool(self._options.manual_symbol_offsets))
Matthew Cary78aae162018-08-10 17:16:30995 if self._options.system_health_orderfile and not self._options.profile:
996 raise AssertionError('--system_health_orderfile must be not be used '
997 'with --skip-profile')
998 if (self._options.manual_symbol_offsets and
999 not self._options.system_health_orderfile):
1000 raise AssertionError('--manual-symbol-offsets must be used with '
1001 '--system_health_orderfile.')
Matthew Carye8400642018-06-14 15:43:021002
Benoit Lizea3fe2932017-10-20 10:24:521003 if self._options.profile:
1004 try:
1005 _UnstashOutputDirectory(self._instrumented_out_dir)
1006 self._compiler = ClankCompiler(
Christopher Grant073637472019-07-05 13:34:571007 self._instrumented_out_dir, self._step_recorder, self._options.arch,
1008 self._options.use_goma, self._options.goma_dir,
1009 self._options.system_health_orderfile, self._monochrome,
1010 self._options.public, self._GetPathToOrderfile())
Matthew Carya2bea452018-11-13 10:21:071011 if not self._options.pregenerated_profiles:
1012 # If there are pregenerated profiles, the instrumented build should
1013 # not be changed to avoid invalidating the pregenerated profile
1014 # offsets.
Monica Basta99c101f2019-05-21 13:50:051015 self._compiler.CompileChromeApk(instrumented=True,
1016 use_call_graph=
1017 self._options.use_call_graph)
Benoit Lizea3fe2932017-10-20 10:24:521018 self._GenerateAndProcessProfile()
1019 self._MaybeArchiveOrderfile(self._GetUnpatchedOrderfileFilename())
1020 profile_uploaded = True
1021 finally:
Benoit Lizea3fe2932017-10-20 10:24:521022 _StashOutputDirectory(self._instrumented_out_dir)
Matthew Carye8400642018-06-14 15:43:021023 elif self._options.manual_symbol_offsets:
1024 assert self._options.manual_libname
1025 assert self._options.manual_objdir
1026 with file(self._options.manual_symbol_offsets) as f:
1027 symbol_offsets = [int(x) for x in f.xreadlines()]
1028 processor = process_profiles.SymbolOffsetProcessor(
Matthew Caryb46ad282018-11-23 14:43:571029 self._compiler.manual_libname)
Matthew Carye8400642018-06-14 15:43:021030 generator = cyglog_to_orderfile.OffsetOrderfileGenerator(
1031 processor, cyglog_to_orderfile.ObjectFileProcessor(
1032 self._options.manual_objdir))
1033 ordered_sections = generator.GetOrderedSections(symbol_offsets)
1034 if not ordered_sections: # Either None or empty is a problem.
1035 raise Exception('Failed to get ordered sections')
1036 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
1037 orderfile.write('\n'.join(ordered_sections))
1038
Benoit Lizea3fe2932017-10-20 10:24:521039 if self._options.patch:
1040 if self._options.profile:
1041 self._RemoveBlanks(self._GetUnpatchedOrderfileFilename(),
1042 self._GetPathToOrderfile())
1043 try:
1044 _UnstashOutputDirectory(self._uninstrumented_out_dir)
1045 self._compiler = ClankCompiler(
1046 self._uninstrumented_out_dir, self._step_recorder,
Christopher Grant073637472019-07-05 13:34:571047 self._options.arch, self._options.use_goma, self._options.goma_dir,
Stephen Kylef11339f2019-03-25 09:00:471048 self._options.system_health_orderfile, self._monochrome,
1049 self._options.public, self._GetPathToOrderfile())
1050
Monica Basta99c101f2019-05-21 13:50:051051 self._compiler.CompileLibchrome(instrumented=False,
1052 use_call_graph=False)
Benoit Lizea3fe2932017-10-20 10:24:521053 self._PatchOrderfile()
1054 # Because identical code folding is a bit different with and without
1055 # the orderfile build, we need to re-patch the orderfile with code
1056 # folding as close to the final version as possible.
Monica Basta99c101f2019-05-21 13:50:051057 self._compiler.CompileLibchrome(instrumented=False,
1058 use_call_graph=False, force_relink=True)
Benoit Lizea3fe2932017-10-20 10:24:521059 self._PatchOrderfile()
Monica Basta99c101f2019-05-21 13:50:051060 self._compiler.CompileLibchrome(instrumented=False,
1061 use_call_graph=False, force_relink=True)
Benoit Lizea3fe2932017-10-20 10:24:521062 self._VerifySymbolOrder()
1063 self._MaybeArchiveOrderfile(self._GetPathToOrderfile())
1064 finally:
1065 _StashOutputDirectory(self._uninstrumented_out_dir)
1066 orderfile_uploaded = True
1067
Monica Salama90b8f5b2019-04-25 11:10:381068 if self._options.benchmark:
1069 self._output_data['orderfile_benchmark_results'] = self.RunBenchmark(
1070 self._uninstrumented_out_dir)
1071 self._output_data['no_orderfile_benchmark_results'] = self.RunBenchmark(
1072 self._no_orderfile_out_dir, no_orderfile=True)
1073
Matthew Caryc5ab92a2019-03-19 15:49:241074 if self._options.new_commit_flow:
Matthew Cary86a226e2019-03-19 12:17:441075 self._orderfile_updater._GitStash()
1076 else:
Egor Pasko7ff04122019-11-25 15:47:181077 if (self._options.buildbot and not self._step_recorder.ErrorRecorded()):
Matthew Cary86a226e2019-03-19 12:17:441078 unpatched_orderfile_filename = (
1079 self._GetUnpatchedOrderfileFilename() if profile_uploaded else None)
1080 orderfile_filename = (
1081 self._GetPathToOrderfile() if orderfile_uploaded else None)
1082 self._orderfile_updater.LegacyCommitFileHashes(
1083 unpatched_orderfile_filename, orderfile_filename)
Benoit Lizea3fe2932017-10-20 10:24:521084 self._step_recorder.EndStep()
1085 return not self._step_recorder.ErrorRecorded()
1086
1087 def GetReportingData(self):
1088 """Get a dictionary of reporting data (timings, output hashes)"""
1089 self._output_data['timings'] = self._step_recorder.timings
1090 return self._output_data
1091
Matthew Cary86a226e2019-03-19 12:17:441092 def CommitStashedOrderfileHashes(self):
1093 """Commit any orderfile hash files in the current checkout.
1094
1095 Only possible if running on the buildbot.
1096
1097 Returns: true on success.
1098 """
Egor Pasko7ff04122019-11-25 15:47:181099 if not self._options.buildbot:
Matthew Cary86a226e2019-03-19 12:17:441100 logging.error('Trying to commit when not running on the buildbot')
1101 return False
1102 self._orderfile_updater._CommitStashedFiles([
1103 filename + '.sha1'
1104 for filename in (self._GetUnpatchedOrderfileFilename(),
1105 self._GetPathToOrderfile())])
1106 return True
1107
Benoit Lizea3fe2932017-10-20 10:24:521108
Benoit Lizea1b64f82017-12-07 10:12:501109def CreateArgumentParser():
1110 """Creates and returns the argument parser."""
1111 parser = argparse.ArgumentParser()
Monica Salama90b8f5b2019-04-25 11:10:381112 parser.add_argument('--no-benchmark', action='store_false', dest='benchmark',
1113 default=True, help='Disables running benchmarks.')
Benoit Lizea1b64f82017-12-07 10:12:501114 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521115 '--buildbot', action='store_true',
1116 help='If true, the script expects to be run on a buildbot')
Benoit Lizea1b64f82017-12-07 10:12:501117 parser.add_argument(
Matthew Cary453ff1452018-07-18 12:19:351118 '--device', default=None, type=str,
1119 help='Device serial number on which to run profiling.')
1120 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521121 '--verify', action='store_true',
1122 help='If true, the script only verifies the current orderfile')
Benoit Lizea1b64f82017-12-07 10:12:501123 parser.add_argument('--target-arch', action='store', dest='arch',
Stephen Martinis71b391b52018-12-04 15:08:041124 default='arm',
Matthew Cary53f74ba2019-01-24 10:07:181125 choices=['arm', 'arm64'],
1126 help='The target architecture for which to build.')
Benoit Lizea1b64f82017-12-07 10:12:501127 parser.add_argument('--output-json', action='store', dest='json_file',
1128 help='Location to save stats in json format')
1129 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521130 '--skip-profile', action='store_false', dest='profile', default=True,
1131 help='Don\'t generate a profile on the device. Only patch from the '
1132 'existing profile.')
Benoit Lizea1b64f82017-12-07 10:12:501133 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521134 '--skip-patch', action='store_false', dest='patch', default=True,
1135 help='Only generate the raw (unpatched) orderfile, don\'t patch it.')
Benoit Lizea1b64f82017-12-07 10:12:501136 parser.add_argument(
Egor Pasko7ff04122019-11-25 15:47:181137 '--netrc', action='store', help='Obsolete. A custom .netrc file.')
Benoit Lizea1b64f82017-12-07 10:12:501138 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521139 '--branch', action='store', default='master',
Egor Pasko7ff04122019-11-25 15:47:181140 help='When running on buildbot, the branch orderfile '
Benoit Lizea3fe2932017-10-20 10:24:521141 'hashes get checked into.')
Christopher Grant073637472019-07-05 13:34:571142 # Obsolete (Autoninja is now used, and this argument is ignored).
Benoit Lizea1b64f82017-12-07 10:12:501143 parser.add_argument(
Christopher Grant073637472019-07-05 13:34:571144 '-j', '--jobs', help='Obsolete. Number of jobs to use for compilation.')
1145 # Obsolete (Autoninja is now used, and this argument is ignored).
1146 parser.add_argument('-l', '--max-load', help='Obsolete. Max cpu load.')
Benoit Lizea1b64f82017-12-07 10:12:501147 parser.add_argument('--goma-dir', help='GOMA directory.')
1148 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:521149 '--use-goma', action='store_true', help='Enable GOMA.', default=False)
Benoit Lizea1b64f82017-12-07 10:12:501150 parser.add_argument('--adb-path', help='Path to the adb binary.')
Matthew Carye8400642018-06-14 15:43:021151
Stephen Kylef11339f2019-03-25 09:00:471152 parser.add_argument('--public', action='store_true',
1153 help='Required if your checkout is non-internal.',
1154 default=False)
Matthew Cary04f41032018-12-10 15:55:271155 parser.add_argument('--nosystem-health-orderfile', action='store_false',
Benoit Lcba421e2019-04-26 15:28:261156 dest='system_health_orderfile', default=True,
Matthew Caryc262f5d2018-09-19 14:36:281157 help=('Create an orderfile based on an about:blank '
1158 'startup benchmark instead of system health '
1159 'benchmarks.'))
Monica Salamaeea2d942019-03-11 12:36:181160 parser.add_argument(
1161 '--use-legacy-chrome-apk', action='store_true', default=False,
1162 help=('Compile and instrument chrome for [L, K] devices.'))
Matthew Cary78aae162018-08-10 17:16:301163
Matthew Carye8400642018-06-14 15:43:021164 parser.add_argument('--manual-symbol-offsets', default=None, type=str,
1165 help=('File of list of ordered symbol offsets generated '
1166 'by manual profiling. Must set other --manual* '
1167 'flags if this is used, and must --skip-profile.'))
1168 parser.add_argument('--manual-libname', default=None, type=str,
1169 help=('Library filename corresponding to '
1170 '--manual-symbol-offsets.'))
1171 parser.add_argument('--manual-objdir', default=None, type=str,
1172 help=('Root of object file directory corresponding to '
1173 '--manual-symbol-offsets.'))
Matthew Caryf949bba2019-02-04 13:39:231174 parser.add_argument('--noorder-outlined-functions', action='store_true',
1175 help='Disable outlined functions in the orderfile.')
Matthew Cary69e9e422018-08-10 18:30:061176 parser.add_argument('--pregenerated-profiles', default=None, type=str,
1177 help=('Pregenerated profiles to use instead of running '
1178 'profile step. Cannot be used with '
1179 '--skip-profiles.'))
1180 parser.add_argument('--profile-save-dir', default=None, type=str,
1181 help=('Directory to save any profiles created. These can '
1182 'be used with --pregenerated-profiles. Cannot be '
1183 'used with --skip-profiles.'))
Egor Paskobce64d012018-11-20 12:02:371184 parser.add_argument('--upload-ready-orderfiles', action='store_true',
1185 help=('Skip orderfile generation and manually upload '
1186 'orderfiles (both patched and unpatched) from '
1187 'their normal location in the tree to the cloud '
1188 'storage. DANGEROUS! USE WITH CARE!'))
Matthew Cary1ea82212019-03-20 12:10:271189 parser.add_argument('--streamline-for-debugging', action='store_true',
1190 help=('Streamline where possible the run for faster '
1191 'iteration while debugging. The orderfile '
1192 'generated will be valid and nontrivial, but '
1193 'may not be based on a representative profile '
1194 'or other such considerations. Use with caution.'))
Matthew Cary86a226e2019-03-19 12:17:441195 parser.add_argument('--commit-hashes', action='store_true',
1196 help=('Commit any orderfile hash files in the current '
1197 'checkout; performs no other action'))
1198 parser.add_argument('--new-commit-flow', action='store_true',
1199 help='Use the new two-step commit flow.')
Monica Basta99c101f2019-05-21 13:50:051200 parser.add_argument('--use-call-graph', action='store_true', default=False,
1201 help='Use call graph instrumentation.')
Benoit Lizea1b64f82017-12-07 10:12:501202 profile_android_startup.AddProfileCollectionArguments(parser)
Benoit Lizea3fe2932017-10-20 10:24:521203 return parser
1204
1205
Stephen Kylef11339f2019-03-25 09:00:471206def CreateOrderfile(options, orderfile_updater_class=None):
Christopher Grantdfe1bac2019-07-05 13:34:101207 """Creates an orderfile.
Benoit Lizea3fe2932017-10-20 10:24:521208
1209 Args:
1210 options: As returned from optparse.OptionParser.parse_args()
1211 orderfile_updater_class: (OrderfileUpdater) subclass of OrderfileUpdater.
Stephen Kylef11339f2019-03-25 09:00:471212 Use to explicitly set an OrderfileUpdater class,
1213 the defaults are OrderfileUpdater, or
1214 OrderfileNoopUpdater if --public is set.
Benoit Lizea3fe2932017-10-20 10:24:521215
1216 Returns:
1217 True iff success.
1218 """
1219 logging.basicConfig(level=logging.INFO)
1220 devil_chromium.Initialize(adb_path=options.adb_path)
1221
Egor Pasko93e514e2017-10-31 13:32:361222 generator = OrderfileGenerator(options, orderfile_updater_class)
Benoit Lizea3fe2932017-10-20 10:24:521223 try:
1224 if options.verify:
Egor Pasko93e514e2017-10-31 13:32:361225 generator._VerifySymbolOrder()
Matthew Cary86a226e2019-03-19 12:17:441226 elif options.commit_hashes:
Matthew Caryc5ab92a2019-03-19 15:49:241227 if not options.new_commit_flow:
Matthew Cary86a226e2019-03-19 12:17:441228 raise Exception('--commit-hashes requries --new-commit-flow')
1229 return generator.CommitStashedOrderfileHashes()
Egor Paskobce64d012018-11-20 12:02:371230 elif options.upload_ready_orderfiles:
1231 return generator.UploadReadyOrderfiles()
Benoit Lizea3fe2932017-10-20 10:24:521232 else:
Egor Pasko93e514e2017-10-31 13:32:361233 return generator.Generate()
Benoit Lizea3fe2932017-10-20 10:24:521234 finally:
Egor Pasko93e514e2017-10-31 13:32:361235 json_output = json.dumps(generator.GetReportingData(),
Benoit Lizea3fe2932017-10-20 10:24:521236 indent=2) + '\n'
1237 if options.json_file:
1238 with open(options.json_file, 'w') as f:
1239 f.write(json_output)
Raul Tambre48f176622019-09-23 10:05:241240 print(json_output)
Benoit Lizea3fe2932017-10-20 10:24:521241 return False
1242
1243
Benoit Lizea1b64f82017-12-07 10:12:501244def main():
1245 parser = CreateArgumentParser()
1246 options = parser.parse_args()
Stephen Kylef11339f2019-03-25 09:00:471247 return 0 if CreateOrderfile(options) else 1
Benoit Lizea3fe2932017-10-20 10:24:521248
1249
1250if __name__ == '__main__':
Benoit Lizea1b64f82017-12-07 10:12:501251 sys.exit(main())