[go: nahoru, domu]

blob: 3dc26cb8c8eda17de27006292e3e656cea6e5efc [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
Benoit Lizea1b64f82017-12-07 10:12:5017import argparse
Benoit Lizea3fe2932017-10-20 10:24:5218import hashlib
19import json
Matthew Cary69e9e422018-08-10 18:30:0620import glob
Benoit Lizea3fe2932017-10-20 10:24:5221import logging
Benoit Lizea3fe2932017-10-20 10:24:5222import os
23import re
24import shutil
25import subprocess
26import sys
Benoit Lizea87e5bc2017-11-07 15:12:5727import tempfile
Benoit Lizea3fe2932017-10-20 10:24:5228import time
29
Matthew Cary91df9792018-11-30 14:35:1530import cluster
Matthew Carye8400642018-06-14 15:43:0231import cyglog_to_orderfile
Benoit Lizea3fe2932017-10-20 10:24:5232import cygprofile_utils
Benoit Lizefefbb27c2018-01-17 13:54:1833import patch_orderfile
Benoit Lizea87e5bc2017-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 Lizea3fe2932017-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 Lizea3fe2932017-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 Lizea3fe2932017-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())
180 print 'Running step: ', name
181
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]
189 print 'Step %s took %f seconds' % (self._previous_step[0], elapsed)
190 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 """
203 print 'STEP FAILED!!'
204 if message:
205 print message
206 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 Lizea3fe2932017-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 """
231 print 'Executing %s in %s' % (' '.join(cmd), cwd)
232 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
243 def __init__(self, out_dir, step_recorder, arch, jobs, max_load, use_goma,
Matthew Caryd6bfcb72018-09-14 11:27:46244 goma_dir, system_health_profiling, monochrome):
Benoit Lizea3fe2932017-10-20 10:24:52245 self._out_dir = out_dir
246 self._step_recorder = step_recorder
Benoit Lizea87e5bc2017-11-07 15:12:57247 self._arch = arch
248 self._jobs = jobs
249 self._max_load = max_load
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
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'
257 self._libchrome_target = 'monochrome'
258 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'
Matthew Cary78aae162018-08-10 17:16:30263
264 self.obj_dir = os.path.join(self._out_dir, 'Release', 'obj')
Benoit Lizea3fe2932017-10-20 10:24:52265 self.lib_chrome_so = os.path.join(
Matthew Caryd6bfcb72018-09-14 11:27:46266 self._out_dir, 'Release', 'lib.unstripped',
267 '{}.so'.format(self._libname))
268 self.chrome_apk = os.path.join(self._out_dir, 'Release', 'apks', self._apk)
Benoit Lizea3fe2932017-10-20 10:24:52269
270 def Build(self, instrumented, target):
271 """Builds the provided ninja target with or without order_profiling on.
272
273 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57274 instrumented: (bool) Whether we want to build an instrumented binary.
275 target: (str) The name of the ninja target to build.
Benoit Lizea3fe2932017-10-20 10:24:52276 """
277 self._step_recorder.BeginStep('Compile %s' % target)
278
279 # Set the "Release Official" flavor, the parts affecting performance.
280 args = [
Peter Collingbourne1c85d5d22018-08-22 18:06:35281 'enable_resource_whitelist_generation=false',
Benoit Lizea3fe2932017-10-20 10:24:52282 'is_chrome_branded=true',
283 'is_debug=false',
284 'is_official_build=true',
Benoit Lizea3fe2932017-10-20 10:24:52285 'target_os="android"',
286 'use_goma=' + str(self._use_goma).lower(),
287 'use_order_profiling=' + str(instrumented).lower(),
288 ]
Matthew Cary53f74ba2019-01-24 10:07:18289 args += _ARCH_GN_ARGS[self._arch]
Benoit Lizea3fe2932017-10-20 10:24:52290 if self._goma_dir:
291 args += ['goma_dir="%s"' % self._goma_dir]
Matthew Cary78aae162018-08-10 17:16:30292 if self._system_health_profiling:
293 args += ['devtools_instrumentation_dumping = ' +
294 str(instrumented).lower()]
Benoit Lizea87e5bc2017-11-07 15:12:57295
Benoit Lizea3fe2932017-10-20 10:24:52296 self._step_recorder.RunCommand(
297 ['gn', 'gen', os.path.join(self._out_dir, 'Release'),
298 '--args=' + ' '.join(args)])
299
300 self._step_recorder.RunCommand(
301 ['ninja', '-C', os.path.join(self._out_dir, 'Release'),
302 '-j' + str(self._jobs), '-l' + str(self._max_load), target])
303
304 def CompileChromeApk(self, instrumented, force_relink=False):
305 """Builds a Chrome.apk either with or without order_profiling on.
306
307 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57308 instrumented: (bool) Whether to build an instrumented apk.
Benoit Lizea3fe2932017-10-20 10:24:52309 force_relink: Whether libchromeview.so should be re-created.
310 """
311 if force_relink:
312 self._step_recorder.RunCommand(['rm', '-rf', self.lib_chrome_so])
Matthew Carye1b00062018-09-19 14:27:44313 self.Build(instrumented, self._apk_target)
Benoit Lizea3fe2932017-10-20 10:24:52314
315 def CompileLibchrome(self, instrumented, force_relink=False):
316 """Builds a libchrome.so either with or without order_profiling on.
317
318 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57319 instrumented: (bool) Whether to build an instrumented apk.
320 force_relink: (bool) Whether libchrome.so should be re-created.
Benoit Lizea3fe2932017-10-20 10:24:52321 """
322 if force_relink:
323 self._step_recorder.RunCommand(['rm', '-rf', self.lib_chrome_so])
Matthew Caryd6bfcb72018-09-14 11:27:46324 self.Build(instrumented, self._libchrome_target)
Benoit Lizea3fe2932017-10-20 10:24:52325
326
327class OrderfileUpdater(object):
328 """Handles uploading and committing a new orderfile in the repository.
329
330 Only used for testing or on a bot.
331 """
332
333 _CLOUD_STORAGE_BUCKET_FOR_DEBUG = None
334 _CLOUD_STORAGE_BUCKET = None
335 _UPLOAD_TO_CLOUD_COMMAND = 'upload_to_google_storage.py'
336
337 def __init__(self, repository_root, step_recorder, branch, netrc):
338 """Constructor.
339
340 Args:
341 repository_root: (str) Root of the target repository.
342 step_recorder: (StepRecorder) Step recorder, for logging.
343 branch: (str) Branch to commit to.
344 netrc: (str) Path to the .netrc file to use.
345 """
346 self._repository_root = repository_root
347 self._step_recorder = step_recorder
348 self._branch = branch
349 self._netrc = netrc
350
351 def CommitFileHashes(self, unpatched_orderfile_filename, orderfile_filename):
352 """Commits unpatched and patched orderfiles hashes, if provided.
353
354 Files must have been successfilly uploaded to cloud storage first.
355
356 Args:
357 unpatched_orderfile_filename: (str or None) Unpatched orderfile path.
358 orderfile_filename: (str or None) Orderfile path.
359
360 Raises:
361 NotImplementedError when the commit logic hasn't been overriden.
362 """
363 files_to_commit = []
364 commit_message_lines = ['Update Orderfile.']
365 for filename in [unpatched_orderfile_filename, orderfile_filename]:
366 if not filename:
367 continue
368 (relative_path, sha1) = self._GetHashFilePathAndContents(filename)
369 commit_message_lines.append('Profile: %s: %s' % (
370 os.path.basename(relative_path), sha1))
371 files_to_commit.append(relative_path)
372 if files_to_commit:
373 self._CommitFiles(files_to_commit, commit_message_lines)
374
375 def UploadToCloudStorage(self, filename, use_debug_location):
376 """Uploads a file to cloud storage.
377
378 Args:
379 filename: (str) File to upload.
380 use_debug_location: (bool) Whether to use the debug location.
381 """
382 bucket = (self._CLOUD_STORAGE_BUCKET_FOR_DEBUG if use_debug_location
383 else self._CLOUD_STORAGE_BUCKET)
384 extension = _GetFileExtension(filename)
385 cmd = [self._UPLOAD_TO_CLOUD_COMMAND, '--bucket', bucket]
386 if extension:
387 cmd.extend(['-z', extension])
388 cmd.append(filename)
389 self._step_recorder.RunCommand(cmd)
390 print 'Download: https://sandbox.google.com/storage/%s/%s' % (
391 bucket, _GenerateHash(filename))
392
393 def _GetHashFilePathAndContents(self, filename):
394 """Gets the name and content of the hash file created from uploading the
395 given file.
396
397 Args:
398 filename: (str) The file that was uploaded to cloud storage.
399
400 Returns:
401 A tuple of the hash file name, relative to the reository root, and the
402 content, which should be the sha1 hash of the file
403 ('base_file.sha1', hash)
404 """
405 abs_hash_filename = filename + '.sha1'
406 rel_hash_filename = os.path.relpath(
407 abs_hash_filename, self._repository_root)
408 with open(abs_hash_filename, 'r') as f:
409 return (rel_hash_filename, f.read())
410
411 def _CommitFiles(self, files_to_commit, commit_message_lines):
412 """Commits a list of files, with a given message."""
413 raise NotImplementedError
414
415
416class OrderfileGenerator(object):
417 """A utility for generating a new orderfile for Clank.
418
419 Builds an instrumented binary, profiles a run of the application, and
420 generates an updated orderfile.
421 """
422 _CLANK_REPO = os.path.join(constants.DIR_SOURCE_ROOT, 'clank')
Benoit Lizea3fe2932017-10-20 10:24:52423 _CHECK_ORDERFILE_SCRIPT = os.path.join(
424 constants.DIR_SOURCE_ROOT, 'tools', 'cygprofile', 'check_orderfile.py')
425 _BUILD_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(
426 constants.GetOutDirectory()))) # Normally /path/to/src
427
428 _UNPATCHED_ORDERFILE_FILENAME = os.path.join(
429 _CLANK_REPO, 'orderfiles', 'unpatched_orderfile.%s')
Benoit Lizea3fe2932017-10-20 10:24:52430
431 _PATH_TO_ORDERFILE = os.path.join(_CLANK_REPO, 'orderfiles',
432 'orderfile.%s.out')
433
434 # Previous orderfile_generator debug files would be overwritten.
435 _DIRECTORY_FOR_DEBUG_FILES = '/tmp/orderfile_generator_debug_files'
436
437 def _GetPathToOrderfile(self):
438 """Gets the path to the architecture-specific orderfile."""
439 return self._PATH_TO_ORDERFILE % self._options.arch
440
441 def _GetUnpatchedOrderfileFilename(self):
442 """Gets the path to the architecture-specific unpatched orderfile."""
443 return self._UNPATCHED_ORDERFILE_FILENAME % self._options.arch
444
Monica Salamaeea2d942019-03-11 12:36:18445 def _SetDevice(self):
446 """ Selects the device to be used by the script.
447
448 Returns:
449 (Device with given serial ID) : if the --device flag is set.
450 (Device running Android[K,L]) : if --use-legacy-chrome-apk flag is set or
451 no device running Android N+ was found.
452 (Device running Android N+) : Otherwise.
453
454 Raises Error:
455 If no device meeting the requirements has been found.
456 """
457 devices = None
458 if self._options.device:
459 devices = [device_utils.DeviceUtils(self._options.device)]
460 else:
461 devices = device_utils.DeviceUtils.HealthyDevices()
462
463 assert devices, 'Expected at least one connected device'
464
465 if self._options.use_legacy_chrome_apk:
466 self._monochrome = False
467 for device in devices:
468 device_version = device.build_version_sdk
469 if (device_version >= version_codes.KITKAT
470 and device_version <= version_codes.LOLLIPOP_MR1):
471 return device
472
473 assert not self._options.use_legacy_chrome_apk, \
474 'No device found running suitable android version for Chrome.apk.'
475
476 preferred_device = None
477 for device in devices:
478 if device.build_version_sdk >= version_codes.NOUGAT:
479 preferred_device = device
480 break
481
482 self._monochrome = preferred_device is not None
483
484 return preferred_device if preferred_device else devices[0]
485
486
Benoit Lizea3fe2932017-10-20 10:24:52487 def __init__(self, options, orderfile_updater_class):
488 self._options = options
489
490 self._instrumented_out_dir = os.path.join(
491 self._BUILD_ROOT, self._options.arch + '_instrumented_out')
492 self._uninstrumented_out_dir = os.path.join(
493 self._BUILD_ROOT, self._options.arch + '_uninstrumented_out')
494
495 if options.profile:
Benoit Lizea1b64f82017-12-07 10:12:50496 output_directory = os.path.join(self._instrumented_out_dir, 'Release')
Matthew Caryb8daed942018-06-11 10:58:08497 host_profile_dir = os.path.join(output_directory, 'profile_data')
Benoit Lizea1b64f82017-12-07 10:12:50498 urls = [profile_android_startup.AndroidProfileTool.TEST_URL]
499 use_wpr = True
500 simulate_user = False
Benoit L96466812018-03-06 15:58:37501 urls = options.urls
502 use_wpr = not options.no_wpr
503 simulate_user = options.simulate_user
Monica Salamaeea2d942019-03-11 12:36:18504 device = self._SetDevice()
Benoit Lizea3fe2932017-10-20 10:24:52505 self._profiler = profile_android_startup.AndroidProfileTool(
Matthew Cary453ff1452018-07-18 12:19:35506 output_directory, host_profile_dir, use_wpr, urls, simulate_user,
Monica Salamaeea2d942019-03-11 12:36:18507 device=device)
Matthew Cary69e9e422018-08-10 18:30:06508 if options.pregenerated_profiles:
509 self._profiler.SetPregeneratedProfiles(
510 glob.glob(options.pregenerated_profiles))
511 else:
512 assert not options.pregenerated_profiles, (
513 '--pregenerated-profiles cannot be used with --skip-profile')
514 assert not options.profile_save_dir, (
515 '--profile-save-dir cannot be used with --skip-profile')
Monica Salamaeea2d942019-03-11 12:36:18516 self._monochrome = not self._options.use_legacy_chrome_apk
Benoit Lizea3fe2932017-10-20 10:24:52517
Matthew Caryf949bba2019-02-04 13:39:23518 # Outlined function handling enabled by default for all architectures.
519 self._order_outlined_functions = not options.noorder_outlined_functions
520
Benoit Lizea3fe2932017-10-20 10:24:52521 self._output_data = {}
522 self._step_recorder = StepRecorder(options.buildbot)
523 self._compiler = None
524 assert issubclass(orderfile_updater_class, OrderfileUpdater)
525 self._orderfile_updater = orderfile_updater_class(self._CLANK_REPO,
526 self._step_recorder,
527 options.branch,
528 options.netrc)
529 assert os.path.isdir(constants.DIR_SOURCE_ROOT), 'No src directory found'
Benoit Lizefefbb27c2018-01-17 13:54:18530 symbol_extractor.SetArchitecture(options.arch)
Benoit Lizea3fe2932017-10-20 10:24:52531
Benoit Lizea3fe2932017-10-20 10:24:52532 @staticmethod
533 def _RemoveBlanks(src_file, dest_file):
534 """A utility to remove blank lines from a file.
535
536 Args:
537 src_file: The name of the file to remove the blanks from.
538 dest_file: The name of the file to write the output without blanks.
539 """
540 assert src_file != dest_file, 'Source and destination need to be distinct'
541
542 try:
543 src = open(src_file, 'r')
544 dest = open(dest_file, 'w')
545 for line in src:
546 if line and not line.isspace():
547 dest.write(line)
548 finally:
549 src.close()
550 dest.close()
551
552 def _GenerateAndProcessProfile(self):
Matthew Cary78aae162018-08-10 17:16:30553 """Invokes a script to merge the per-thread traces into one file.
554
555 The produced list of offsets is saved in
556 self._GetUnpatchedOrderfileFilename().
557 """
Benoit Lizea3fe2932017-10-20 10:24:52558 self._step_recorder.BeginStep('Generate Profile Data')
559 files = []
Matthew Cary78aae162018-08-10 17:16:30560 logging.getLogger().setLevel(logging.DEBUG)
561 if self._options.system_health_orderfile:
562 files = self._profiler.CollectSystemHealthProfile(
563 self._compiler.chrome_apk)
Matthew Cary69e9e422018-08-10 18:30:06564 self._MaybeSaveProfile(files)
Matthew Cary78aae162018-08-10 17:16:30565 try:
566 self._ProcessPhasedOrderfile(files)
567 except Exception:
568 for f in files:
569 self._SaveForDebugging(f)
Matthew Cary8b1416232018-08-10 19:12:22570 self._SaveForDebugging(self._compiler.lib_chrome_so)
Matthew Cary78aae162018-08-10 17:16:30571 raise
572 finally:
573 self._profiler.Cleanup()
574 else:
575 self._CollectLegacyProfile()
576 logging.getLogger().setLevel(logging.INFO)
577
578 def _ProcessPhasedOrderfile(self, files):
579 """Process the phased orderfiles produced by system health benchmarks.
580
581 The offsets will be placed in _GetUnpatchedOrderfileFilename().
582
583 Args:
584 file: Profile files pulled locally.
585 """
586 self._step_recorder.BeginStep('Process Phased Orderfile')
587 profiles = process_profiles.ProfileManager(files)
588 processor = process_profiles.SymbolOffsetProcessor(
589 self._compiler.lib_chrome_so)
Matthew Caryda9db932019-03-11 15:28:17590 ordered_symbols = cluster.ClusterOffsets(profiles, processor)
Matthew Cary8b1416232018-08-10 19:12:22591 if not ordered_symbols:
592 raise Exception('Failed to get ordered symbols')
Matthew Caryda9db932019-03-11 15:28:17593 for sym in ordered_symbols:
594 assert not sym.startswith('OUTLINED_FUNCTION_'), (
595 'Outlined function found in instrumented function, very likely '
596 'something has gone very wrong!')
Matthew Cary91df9792018-11-30 14:35:15597 self._output_data['offsets_kib'] = processor.SymbolsSize(
598 ordered_symbols) / 1024
Matthew Cary78aae162018-08-10 17:16:30599 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
Matthew Cary8b1416232018-08-10 19:12:22600 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary78aae162018-08-10 17:16:30601
602 def _CollectLegacyProfile(self):
Matthew Cary2df8d452018-09-19 07:50:20603 files = []
Benoit Lizea3fe2932017-10-20 10:24:52604 try:
Benoit Lizea3fe2932017-10-20 10:24:52605 files = self._profiler.CollectProfile(
606 self._compiler.chrome_apk,
607 constants.PACKAGE_INFO['chrome'])
Matthew Cary69e9e422018-08-10 18:30:06608 self._MaybeSaveProfile(files)
Matthew Caryb8daed942018-06-11 10:58:08609 self._step_recorder.BeginStep('Process profile')
Benoit L96466812018-03-06 15:58:37610 assert os.path.exists(self._compiler.lib_chrome_so)
611 offsets = process_profiles.GetReachedOffsetsFromDumpFiles(
612 files, self._compiler.lib_chrome_so)
613 if not offsets:
614 raise Exception('No profiler offsets found in {}'.format(
615 '\n'.join(files)))
Matthew Cary8b1416232018-08-10 19:12:22616 processor = process_profiles.SymbolOffsetProcessor(
617 self._compiler.lib_chrome_so)
618 ordered_symbols = processor.GetOrderedSymbols(offsets)
619 if not ordered_symbols:
620 raise Exception('No symbol names from offsets found in {}'.format(
621 '\n'.join(files)))
622 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
623 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary0f1f681a2018-01-22 10:40:51624 except Exception:
Benoit Lizea3fe2932017-10-20 10:24:52625 for f in files:
626 self._SaveForDebugging(f)
627 raise
628 finally:
629 self._profiler.Cleanup()
Benoit Lizea3fe2932017-10-20 10:24:52630
Matthew Cary69e9e422018-08-10 18:30:06631 def _MaybeSaveProfile(self, files):
632 if self._options.profile_save_dir:
633 logging.info('Saving profiles to %s', self._options.profile_save_dir)
634 for f in files:
635 shutil.copy(f, self._options.profile_save_dir)
636 logging.info('Saved profile %s', f)
637
Benoit Lizea3fe2932017-10-20 10:24:52638 def _PatchOrderfile(self):
639 """Patches the orderfile using clean version of libchrome.so."""
640 self._step_recorder.BeginStep('Patch Orderfile')
Benoit Lizefefbb27c2018-01-17 13:54:18641 patch_orderfile.GeneratePatchedOrderfile(
642 self._GetUnpatchedOrderfileFilename(), self._compiler.lib_chrome_so,
Matthew Caryf949bba2019-02-04 13:39:23643 self._GetPathToOrderfile(), self._order_outlined_functions)
Benoit Lizea3fe2932017-10-20 10:24:52644
645 def _VerifySymbolOrder(self):
646 self._step_recorder.BeginStep('Verify Symbol Order')
647 return_code = self._step_recorder.RunCommand(
648 [self._CHECK_ORDERFILE_SCRIPT, self._compiler.lib_chrome_so,
649 self._GetPathToOrderfile(),
650 '--target-arch=' + self._options.arch],
651 constants.DIR_SOURCE_ROOT,
652 raise_on_error=False)
653 if return_code:
654 self._step_recorder.FailStep('Orderfile check returned %d.' % return_code)
655
656 def _RecordHash(self, file_name):
657 """Records the hash of the file into the output_data dictionary."""
658 self._output_data[os.path.basename(file_name) + '.sha1'] = _GenerateHash(
659 file_name)
660
661 def _SaveFileLocally(self, file_name, file_sha1):
662 """Saves the file to a temporary location and prints the sha1sum."""
663 if not os.path.exists(self._DIRECTORY_FOR_DEBUG_FILES):
664 os.makedirs(self._DIRECTORY_FOR_DEBUG_FILES)
665 shutil.copy(file_name, self._DIRECTORY_FOR_DEBUG_FILES)
666 print 'File: %s, saved in: %s, sha1sum: %s' % (
667 file_name, self._DIRECTORY_FOR_DEBUG_FILES, file_sha1)
668
669 def _SaveForDebugging(self, filename):
670 """Uploads the file to cloud storage or saves to a temporary location."""
671 file_sha1 = _GenerateHash(filename)
672 if not self._options.buildbot:
673 self._SaveFileLocally(filename, file_sha1)
674 else:
675 print 'Uploading file for debugging: ' + filename
676 self._orderfile_updater.UploadToCloudStorage(
677 filename, use_debug_location=True)
678
679 def _SaveForDebuggingWithOverwrite(self, file_name):
680 """Uploads and overwrites the file in cloud storage or copies locally.
681
682 Should be used for large binaries like lib_chrome_so.
683
684 Args:
685 file_name: (str) File to upload.
686 """
687 file_sha1 = _GenerateHash(file_name)
688 if not self._options.buildbot:
689 self._SaveFileLocally(file_name, file_sha1)
690 else:
691 print 'Uploading file for debugging: %s, sha1sum: %s' % (
692 file_name, file_sha1)
693 upload_location = '%s/%s' % (
694 self._CLOUD_STORAGE_BUCKET_FOR_DEBUG, os.path.basename(file_name))
695 self._step_recorder.RunCommand([
696 'gsutil.py', 'cp', file_name, 'gs://' + upload_location])
697 print ('Uploaded to: https://sandbox.google.com/storage/' +
698 upload_location)
699
700 def _MaybeArchiveOrderfile(self, filename):
701 """In buildbot configuration, uploads the generated orderfile to
702 Google Cloud Storage.
703
704 Args:
705 filename: (str) Orderfile to upload.
706 """
Matthew Cary91df9792018-11-30 14:35:15707 # First compute hashes so that we can download them later if we need to.
Benoit Lizea3fe2932017-10-20 10:24:52708 self._step_recorder.BeginStep('Compute hash for ' + filename)
709 self._RecordHash(filename)
710 if self._options.buildbot:
711 self._step_recorder.BeginStep('Archive ' + filename)
712 self._orderfile_updater.UploadToCloudStorage(
713 filename, use_debug_location=False)
714
Egor Paskobce64d012018-11-20 12:02:37715 def UploadReadyOrderfiles(self):
716 self._step_recorder.BeginStep('Upload Ready Orderfiles')
717 for file_name in [self._GetUnpatchedOrderfileFilename(),
718 self._GetPathToOrderfile()]:
719 self._orderfile_updater.UploadToCloudStorage(
720 file_name, use_debug_location=False)
721
Benoit Lizea3fe2932017-10-20 10:24:52722 def _GetHashFilePathAndContents(self, base_file):
723 """Gets the name and content of the hash file created from uploading the
724 given file.
725
726 Args:
727 base_file: The file that was uploaded to cloud storage.
728
729 Returns:
730 A tuple of the hash file name, relative to the clank repo path, and the
731 content, which should be the sha1 hash of the file
732 ('base_file.sha1', hash)
733 """
734 abs_file_name = base_file + '.sha1'
735 rel_file_name = os.path.relpath(abs_file_name, self._CLANK_REPO)
736 with open(abs_file_name, 'r') as f:
737 return (rel_file_name, f.read())
738
739 def Generate(self):
740 """Generates and maybe upload an order."""
741 profile_uploaded = False
742 orderfile_uploaded = False
743
Matthew Carye8400642018-06-14 15:43:02744 assert (bool(self._options.profile) ^
745 bool(self._options.manual_symbol_offsets))
Matthew Cary78aae162018-08-10 17:16:30746 if self._options.system_health_orderfile and not self._options.profile:
747 raise AssertionError('--system_health_orderfile must be not be used '
748 'with --skip-profile')
749 if (self._options.manual_symbol_offsets and
750 not self._options.system_health_orderfile):
751 raise AssertionError('--manual-symbol-offsets must be used with '
752 '--system_health_orderfile.')
Matthew Carye8400642018-06-14 15:43:02753
Benoit Lizea3fe2932017-10-20 10:24:52754 if self._options.profile:
755 try:
756 _UnstashOutputDirectory(self._instrumented_out_dir)
757 self._compiler = ClankCompiler(
758 self._instrumented_out_dir,
759 self._step_recorder, self._options.arch, self._options.jobs,
760 self._options.max_load, self._options.use_goma,
Matthew Caryd6bfcb72018-09-14 11:27:46761 self._options.goma_dir, self._options.system_health_orderfile,
Monica Salamaeea2d942019-03-11 12:36:18762 self._monochrome)
Matthew Carya2bea452018-11-13 10:21:07763 if not self._options.pregenerated_profiles:
764 # If there are pregenerated profiles, the instrumented build should
765 # not be changed to avoid invalidating the pregenerated profile
766 # offsets.
767 self._compiler.CompileChromeApk(True)
Benoit Lizea3fe2932017-10-20 10:24:52768 self._GenerateAndProcessProfile()
769 self._MaybeArchiveOrderfile(self._GetUnpatchedOrderfileFilename())
770 profile_uploaded = True
771 finally:
Benoit Lizea3fe2932017-10-20 10:24:52772 _StashOutputDirectory(self._instrumented_out_dir)
Matthew Carye8400642018-06-14 15:43:02773 elif self._options.manual_symbol_offsets:
774 assert self._options.manual_libname
775 assert self._options.manual_objdir
776 with file(self._options.manual_symbol_offsets) as f:
777 symbol_offsets = [int(x) for x in f.xreadlines()]
778 processor = process_profiles.SymbolOffsetProcessor(
Matthew Caryb46ad282018-11-23 14:43:57779 self._compiler.manual_libname)
Matthew Carye8400642018-06-14 15:43:02780 generator = cyglog_to_orderfile.OffsetOrderfileGenerator(
781 processor, cyglog_to_orderfile.ObjectFileProcessor(
782 self._options.manual_objdir))
783 ordered_sections = generator.GetOrderedSections(symbol_offsets)
784 if not ordered_sections: # Either None or empty is a problem.
785 raise Exception('Failed to get ordered sections')
786 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
787 orderfile.write('\n'.join(ordered_sections))
788
Benoit Lizea3fe2932017-10-20 10:24:52789 if self._options.patch:
790 if self._options.profile:
791 self._RemoveBlanks(self._GetUnpatchedOrderfileFilename(),
792 self._GetPathToOrderfile())
793 try:
794 _UnstashOutputDirectory(self._uninstrumented_out_dir)
795 self._compiler = ClankCompiler(
796 self._uninstrumented_out_dir, self._step_recorder,
797 self._options.arch, self._options.jobs, self._options.max_load,
Matthew Cary78aae162018-08-10 17:16:30798 self._options.use_goma, self._options.goma_dir,
Monica Salamaeea2d942019-03-11 12:36:18799 self._options.system_health_orderfile, self._monochrome)
Benoit Lizea3fe2932017-10-20 10:24:52800 self._compiler.CompileLibchrome(False)
801 self._PatchOrderfile()
802 # Because identical code folding is a bit different with and without
803 # the orderfile build, we need to re-patch the orderfile with code
804 # folding as close to the final version as possible.
805 self._compiler.CompileLibchrome(False, force_relink=True)
806 self._PatchOrderfile()
807 self._compiler.CompileLibchrome(False, force_relink=True)
808 self._VerifySymbolOrder()
809 self._MaybeArchiveOrderfile(self._GetPathToOrderfile())
810 finally:
811 _StashOutputDirectory(self._uninstrumented_out_dir)
812 orderfile_uploaded = True
813
814 if (self._options.buildbot and self._options.netrc
815 and not self._step_recorder.ErrorRecorded()):
816 unpatched_orderfile_filename = (
817 self._GetUnpatchedOrderfileFilename() if profile_uploaded else None)
818 orderfile_filename = (
Benoit Lized913be82017-10-24 13:38:27819 self._GetPathToOrderfile() if orderfile_uploaded else None)
Benoit Lizea3fe2932017-10-20 10:24:52820 self._orderfile_updater.CommitFileHashes(
821 unpatched_orderfile_filename, orderfile_filename)
822
823 self._step_recorder.EndStep()
824 return not self._step_recorder.ErrorRecorded()
825
826 def GetReportingData(self):
827 """Get a dictionary of reporting data (timings, output hashes)"""
828 self._output_data['timings'] = self._step_recorder.timings
829 return self._output_data
830
831
Benoit Lizea1b64f82017-12-07 10:12:50832def CreateArgumentParser():
833 """Creates and returns the argument parser."""
834 parser = argparse.ArgumentParser()
835 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52836 '--buildbot', action='store_true',
837 help='If true, the script expects to be run on a buildbot')
Benoit Lizea1b64f82017-12-07 10:12:50838 parser.add_argument(
Matthew Cary453ff1452018-07-18 12:19:35839 '--device', default=None, type=str,
840 help='Device serial number on which to run profiling.')
841 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52842 '--verify', action='store_true',
843 help='If true, the script only verifies the current orderfile')
Benoit Lizea1b64f82017-12-07 10:12:50844 parser.add_argument('--target-arch', action='store', dest='arch',
Stephen Martinis71b391b52018-12-04 15:08:04845 default='arm',
Matthew Cary53f74ba2019-01-24 10:07:18846 choices=['arm', 'arm64'],
847 help='The target architecture for which to build.')
Benoit Lizea1b64f82017-12-07 10:12:50848 parser.add_argument('--output-json', action='store', dest='json_file',
849 help='Location to save stats in json format')
850 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52851 '--skip-profile', action='store_false', dest='profile', default=True,
852 help='Don\'t generate a profile on the device. Only patch from the '
853 'existing profile.')
Benoit Lizea1b64f82017-12-07 10:12:50854 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52855 '--skip-patch', action='store_false', dest='patch', default=True,
856 help='Only generate the raw (unpatched) orderfile, don\'t patch it.')
Benoit Lizea1b64f82017-12-07 10:12:50857 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52858 '--netrc', action='store',
859 help='A custom .netrc file to use for git checkin. Only used on bots.')
Benoit Lizea1b64f82017-12-07 10:12:50860 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52861 '--branch', action='store', default='master',
862 help='When running on buildbot with a netrc, the branch orderfile '
863 'hashes get checked into.')
864 # Note: -j50 was causing issues on the bot.
Benoit Lizea1b64f82017-12-07 10:12:50865 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52866 '-j', '--jobs', action='store', default=20,
867 help='Number of jobs to use for compilation.')
Benoit Lizea1b64f82017-12-07 10:12:50868 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52869 '-l', '--max-load', action='store', default=4, help='Max cpu load.')
Benoit Lizea1b64f82017-12-07 10:12:50870 parser.add_argument('--goma-dir', help='GOMA directory.')
871 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52872 '--use-goma', action='store_true', help='Enable GOMA.', default=False)
Benoit Lizea1b64f82017-12-07 10:12:50873 parser.add_argument('--adb-path', help='Path to the adb binary.')
Matthew Carye8400642018-06-14 15:43:02874
Matthew Cary04f41032018-12-10 15:55:27875 parser.add_argument('--nosystem-health-orderfile', action='store_false',
876 dest='system_health_orderfile', default=True,
Matthew Caryc262f5d2018-09-19 14:36:28877 help=('Create an orderfile based on an about:blank '
878 'startup benchmark instead of system health '
879 'benchmarks.'))
Monica Salamaeea2d942019-03-11 12:36:18880 parser.add_argument(
881 '--use-legacy-chrome-apk', action='store_true', default=False,
882 help=('Compile and instrument chrome for [L, K] devices.'))
Matthew Cary78aae162018-08-10 17:16:30883
Matthew Carye8400642018-06-14 15:43:02884 parser.add_argument('--manual-symbol-offsets', default=None, type=str,
885 help=('File of list of ordered symbol offsets generated '
886 'by manual profiling. Must set other --manual* '
887 'flags if this is used, and must --skip-profile.'))
888 parser.add_argument('--manual-libname', default=None, type=str,
889 help=('Library filename corresponding to '
890 '--manual-symbol-offsets.'))
891 parser.add_argument('--manual-objdir', default=None, type=str,
892 help=('Root of object file directory corresponding to '
893 '--manual-symbol-offsets.'))
Matthew Caryf949bba2019-02-04 13:39:23894 parser.add_argument('--noorder-outlined-functions', action='store_true',
895 help='Disable outlined functions in the orderfile.')
Matthew Cary69e9e422018-08-10 18:30:06896 parser.add_argument('--pregenerated-profiles', default=None, type=str,
897 help=('Pregenerated profiles to use instead of running '
898 'profile step. Cannot be used with '
899 '--skip-profiles.'))
900 parser.add_argument('--profile-save-dir', default=None, type=str,
901 help=('Directory to save any profiles created. These can '
902 'be used with --pregenerated-profiles. Cannot be '
903 'used with --skip-profiles.'))
Egor Paskobce64d012018-11-20 12:02:37904 parser.add_argument('--upload-ready-orderfiles', action='store_true',
905 help=('Skip orderfile generation and manually upload '
906 'orderfiles (both patched and unpatched) from '
907 'their normal location in the tree to the cloud '
908 'storage. DANGEROUS! USE WITH CARE!'))
Matthew Carye8400642018-06-14 15:43:02909
Benoit Lizea1b64f82017-12-07 10:12:50910 profile_android_startup.AddProfileCollectionArguments(parser)
Benoit Lizea3fe2932017-10-20 10:24:52911 return parser
912
913
914def CreateOrderfile(options, orderfile_updater_class):
915 """Creates an oderfile.
916
917 Args:
918 options: As returned from optparse.OptionParser.parse_args()
919 orderfile_updater_class: (OrderfileUpdater) subclass of OrderfileUpdater.
920
921 Returns:
922 True iff success.
923 """
924 logging.basicConfig(level=logging.INFO)
925 devil_chromium.Initialize(adb_path=options.adb_path)
926
Egor Pasko93e514e2017-10-31 13:32:36927 generator = OrderfileGenerator(options, orderfile_updater_class)
Benoit Lizea3fe2932017-10-20 10:24:52928 try:
929 if options.verify:
Egor Pasko93e514e2017-10-31 13:32:36930 generator._VerifySymbolOrder()
Egor Paskobce64d012018-11-20 12:02:37931 elif options.upload_ready_orderfiles:
932 return generator.UploadReadyOrderfiles()
Benoit Lizea3fe2932017-10-20 10:24:52933 else:
Egor Pasko93e514e2017-10-31 13:32:36934 return generator.Generate()
Benoit Lizea3fe2932017-10-20 10:24:52935 finally:
Egor Pasko93e514e2017-10-31 13:32:36936 json_output = json.dumps(generator.GetReportingData(),
Benoit Lizea3fe2932017-10-20 10:24:52937 indent=2) + '\n'
938 if options.json_file:
939 with open(options.json_file, 'w') as f:
940 f.write(json_output)
941 print json_output
942 return False
943
944
Benoit Lizea1b64f82017-12-07 10:12:50945def main():
946 parser = CreateArgumentParser()
947 options = parser.parse_args()
Benoit Lizea3fe2932017-10-20 10:24:52948 return 0 if CreateOrderfile(options, OrderfileUpdater) else 1
949
950
951if __name__ == '__main__':
Benoit Lizea1b64f82017-12-07 10:12:50952 sys.exit(main())