[go: nahoru, domu]

blob: 54d5a34d13697a1bc31d2c06118c60cc77e17105 [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
38
39_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)),
40 os.pardir, os.pardir)
41sys.path.append(os.path.join(_SRC_PATH, 'build', 'android'))
42import devil_chromium
43from pylib import constants
44
45
46# Needs to happen early for GetBuildType()/GetOutDirectory() to work correctly
47constants.SetBuildType('Release')
48
49
50class CommandError(Exception):
51 """Indicates that a dispatched shell command exited with a non-zero status."""
52
53 def __init__(self, value):
54 super(CommandError, self).__init__()
55 self.value = value
56
57 def __str__(self):
58 return repr(self.value)
59
60
61def _GenerateHash(file_path):
62 """Calculates and returns the hash of the file at file_path."""
63 sha1 = hashlib.sha1()
64 with open(file_path, 'rb') as f:
65 while True:
66 # Read in 1mb chunks, so it doesn't all have to be loaded into memory.
67 chunk = f.read(1024 * 1024)
68 if not chunk:
69 break
70 sha1.update(chunk)
71 return sha1.hexdigest()
72
73
74def _GetFileExtension(file_name):
75 """Calculates the file extension from a file name.
76
77 Args:
78 file_name: The source file name.
79 Returns:
80 The part of file_name after the dot (.) or None if the file has no
81 extension.
82 Examples: /home/user/foo.bar -> bar
83 /home/user.name/foo -> None
84 /home/user/.foo -> None
85 /home/user/foo.bar.baz -> baz
86 """
87 file_name_parts = os.path.basename(file_name).split('.')
88 if len(file_name_parts) > 1:
89 return file_name_parts[-1]
90 else:
91 return None
92
93
94def _StashOutputDirectory(buildpath):
95 """Takes the output directory and stashes it in the default output directory.
96
97 This allows it to be used for incremental builds next time (after unstashing)
98 by keeping it in a place that isn't deleted normally, while also ensuring
99 that it is properly clobbered when appropriate.
100
101 This is a dirty hack to deal with the needs of clobbering while also handling
102 incremental builds and the hardcoded relative paths used in some of the
103 project files.
104
105 Args:
106 buildpath: The path where the building happens. If this corresponds to the
107 default output directory, no action is taken.
108 """
109 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
110 constants.GetOutDirectory())):
111 return
112 name = os.path.basename(buildpath)
113 stashpath = os.path.join(constants.GetOutDirectory(), name)
114 if not os.path.exists(buildpath):
115 return
116 if os.path.exists(stashpath):
117 shutil.rmtree(stashpath, ignore_errors=True)
118 shutil.move(buildpath, stashpath)
119
120
121def _UnstashOutputDirectory(buildpath):
122 """Inverse of _StashOutputDirectory.
123
124 Moves the output directory stashed within the default output directory
125 (out/Release) to the position where the builds can actually happen.
126
127 This is a dirty hack to deal with the needs of clobbering while also handling
128 incremental builds and the hardcoded relative paths used in some of the
129 project files.
130
131 Args:
132 buildpath: The path where the building happens. If this corresponds to the
133 default output directory, no action is taken.
134 """
135 if os.path.abspath(buildpath) == os.path.abspath(os.path.dirname(
136 constants.GetOutDirectory())):
137 return
138 name = os.path.basename(buildpath)
139 stashpath = os.path.join(constants.GetOutDirectory(), name)
140 if not os.path.exists(stashpath):
141 return
142 if os.path.exists(buildpath):
143 shutil.rmtree(buildpath, ignore_errors=True)
144 shutil.move(stashpath, buildpath)
145
146
147class StepRecorder(object):
148 """Records steps and timings."""
149
150 def __init__(self, buildbot):
151 self.timings = []
152 self._previous_step = ('', 0.0)
153 self._buildbot = buildbot
154 self._error_recorded = False
155
156 def BeginStep(self, name):
157 """Marks a beginning of the next step in the script.
158
159 On buildbot, this prints a specially formatted name that will show up
160 in the waterfall. Otherwise, just prints the step name.
161
162 Args:
163 name: The name of the step.
164 """
165 self.EndStep()
166 self._previous_step = (name, time.time())
167 print 'Running step: ', name
168
169 def EndStep(self):
170 """Records successful completion of the current step.
171
172 This is optional if the step is immediately followed by another BeginStep.
173 """
174 if self._previous_step[0]:
175 elapsed = time.time() - self._previous_step[1]
176 print 'Step %s took %f seconds' % (self._previous_step[0], elapsed)
177 self.timings.append((self._previous_step[0], elapsed))
178
179 self._previous_step = ('', 0.0)
180
181 def FailStep(self, message=None):
182 """Marks that a particular step has failed.
183
184 On buildbot, this will mark the current step as failed on the waterfall.
185 Otherwise we will just print an optional failure message.
186
187 Args:
188 message: An optional explanation as to why the step failed.
189 """
190 print 'STEP FAILED!!'
191 if message:
192 print message
193 self._error_recorded = True
194 self.EndStep()
195
196 def ErrorRecorded(self):
197 """True if FailStep has been called."""
198 return self._error_recorded
199
200 def RunCommand(self, cmd, cwd=constants.DIR_SOURCE_ROOT, raise_on_error=True,
201 stdout=None):
202 """Execute a shell command.
203
204 Args:
205 cmd: A list of command strings.
Matthew Cary78aae162018-08-10 17:16:30206 cwd: Directory in which the command should be executed, defaults to build
207 root of script's location if not specified.
Benoit Lizea3fe2932017-10-20 10:24:52208 raise_on_error: If true will raise a CommandError if the call doesn't
209 succeed and mark the step as failed.
210 stdout: A file to redirect stdout for the command to.
211
212 Returns:
213 The process's return code.
214
215 Raises:
216 CommandError: An error executing the specified command.
217 """
218 print 'Executing %s in %s' % (' '.join(cmd), cwd)
219 process = subprocess.Popen(cmd, stdout=stdout, cwd=cwd, env=os.environ)
220 process.wait()
221 if raise_on_error and process.returncode != 0:
222 self.FailStep()
223 raise CommandError('Exception executing command %s' % ' '.join(cmd))
224 return process.returncode
225
226
227class ClankCompiler(object):
228 """Handles compilation of clank."""
229
230 def __init__(self, out_dir, step_recorder, arch, jobs, max_load, use_goma,
Matthew Caryd6bfcb72018-09-14 11:27:46231 goma_dir, system_health_profiling, monochrome):
Benoit Lizea3fe2932017-10-20 10:24:52232 self._out_dir = out_dir
233 self._step_recorder = step_recorder
Benoit Lizea87e5bc2017-11-07 15:12:57234 self._arch = arch
235 self._jobs = jobs
236 self._max_load = max_load
Benoit Lizea3fe2932017-10-20 10:24:52237 self._use_goma = use_goma
Benoit Lizea87e5bc2017-11-07 15:12:57238 self._goma_dir = goma_dir
Matthew Cary78aae162018-08-10 17:16:30239 self._system_health_profiling = system_health_profiling
Matthew Caryd6bfcb72018-09-14 11:27:46240 if monochrome:
241 self._apk = 'Monochrome.apk'
Matthew Carye1b00062018-09-19 14:27:44242 self._apk_target = 'monochrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46243 self._libname = 'libmonochrome'
244 self._libchrome_target = 'monochrome'
245 else:
246 self._apk = 'Chrome.apk'
Matthew Carye1b00062018-09-19 14:27:44247 self._apk_target = 'chrome_apk'
Matthew Caryd6bfcb72018-09-14 11:27:46248 self._libname = 'libchrome'
249 self._libchrome_target = 'libchrome'
Matthew Cary78aae162018-08-10 17:16:30250
251 self.obj_dir = os.path.join(self._out_dir, 'Release', 'obj')
Benoit Lizea3fe2932017-10-20 10:24:52252 self.lib_chrome_so = os.path.join(
Matthew Caryd6bfcb72018-09-14 11:27:46253 self._out_dir, 'Release', 'lib.unstripped',
254 '{}.so'.format(self._libname))
255 self.chrome_apk = os.path.join(self._out_dir, 'Release', 'apks', self._apk)
Benoit Lizea3fe2932017-10-20 10:24:52256
257 def Build(self, instrumented, target):
258 """Builds the provided ninja target with or without order_profiling on.
259
260 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57261 instrumented: (bool) Whether we want to build an instrumented binary.
262 target: (str) The name of the ninja target to build.
Benoit Lizea3fe2932017-10-20 10:24:52263 """
264 self._step_recorder.BeginStep('Compile %s' % target)
265
266 # Set the "Release Official" flavor, the parts affecting performance.
267 args = [
Peter Collingbourne1c85d5d22018-08-22 18:06:35268 'enable_resource_whitelist_generation=false',
Benoit Lizea3fe2932017-10-20 10:24:52269 'is_chrome_branded=true',
270 'is_debug=false',
271 'is_official_build=true',
Benoit Lizea3fe2932017-10-20 10:24:52272 'target_cpu="' + self._arch + '"',
273 'target_os="android"',
274 'use_goma=' + str(self._use_goma).lower(),
275 'use_order_profiling=' + str(instrumented).lower(),
276 ]
277 if self._goma_dir:
278 args += ['goma_dir="%s"' % self._goma_dir]
Matthew Cary78aae162018-08-10 17:16:30279 if self._system_health_profiling:
280 args += ['devtools_instrumentation_dumping = ' +
281 str(instrumented).lower()]
Benoit Lizea87e5bc2017-11-07 15:12:57282
Benoit Lizea3fe2932017-10-20 10:24:52283 self._step_recorder.RunCommand(
284 ['gn', 'gen', os.path.join(self._out_dir, 'Release'),
285 '--args=' + ' '.join(args)])
286
287 self._step_recorder.RunCommand(
288 ['ninja', '-C', os.path.join(self._out_dir, 'Release'),
289 '-j' + str(self._jobs), '-l' + str(self._max_load), target])
290
291 def CompileChromeApk(self, instrumented, force_relink=False):
292 """Builds a Chrome.apk either with or without order_profiling on.
293
294 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57295 instrumented: (bool) Whether to build an instrumented apk.
Benoit Lizea3fe2932017-10-20 10:24:52296 force_relink: Whether libchromeview.so should be re-created.
297 """
298 if force_relink:
299 self._step_recorder.RunCommand(['rm', '-rf', self.lib_chrome_so])
Matthew Carye1b00062018-09-19 14:27:44300 self.Build(instrumented, self._apk_target)
Benoit Lizea3fe2932017-10-20 10:24:52301
302 def CompileLibchrome(self, instrumented, force_relink=False):
303 """Builds a libchrome.so either with or without order_profiling on.
304
305 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57306 instrumented: (bool) Whether to build an instrumented apk.
307 force_relink: (bool) Whether libchrome.so should be re-created.
Benoit Lizea3fe2932017-10-20 10:24:52308 """
309 if force_relink:
310 self._step_recorder.RunCommand(['rm', '-rf', self.lib_chrome_so])
Matthew Caryd6bfcb72018-09-14 11:27:46311 self.Build(instrumented, self._libchrome_target)
Benoit Lizea3fe2932017-10-20 10:24:52312
313
314class OrderfileUpdater(object):
315 """Handles uploading and committing a new orderfile in the repository.
316
317 Only used for testing or on a bot.
318 """
319
320 _CLOUD_STORAGE_BUCKET_FOR_DEBUG = None
321 _CLOUD_STORAGE_BUCKET = None
322 _UPLOAD_TO_CLOUD_COMMAND = 'upload_to_google_storage.py'
323
324 def __init__(self, repository_root, step_recorder, branch, netrc):
325 """Constructor.
326
327 Args:
328 repository_root: (str) Root of the target repository.
329 step_recorder: (StepRecorder) Step recorder, for logging.
330 branch: (str) Branch to commit to.
331 netrc: (str) Path to the .netrc file to use.
332 """
333 self._repository_root = repository_root
334 self._step_recorder = step_recorder
335 self._branch = branch
336 self._netrc = netrc
337
338 def CommitFileHashes(self, unpatched_orderfile_filename, orderfile_filename):
339 """Commits unpatched and patched orderfiles hashes, if provided.
340
341 Files must have been successfilly uploaded to cloud storage first.
342
343 Args:
344 unpatched_orderfile_filename: (str or None) Unpatched orderfile path.
345 orderfile_filename: (str or None) Orderfile path.
346
347 Raises:
348 NotImplementedError when the commit logic hasn't been overriden.
349 """
350 files_to_commit = []
351 commit_message_lines = ['Update Orderfile.']
352 for filename in [unpatched_orderfile_filename, orderfile_filename]:
353 if not filename:
354 continue
355 (relative_path, sha1) = self._GetHashFilePathAndContents(filename)
356 commit_message_lines.append('Profile: %s: %s' % (
357 os.path.basename(relative_path), sha1))
358 files_to_commit.append(relative_path)
359 if files_to_commit:
360 self._CommitFiles(files_to_commit, commit_message_lines)
361
362 def UploadToCloudStorage(self, filename, use_debug_location):
363 """Uploads a file to cloud storage.
364
365 Args:
366 filename: (str) File to upload.
367 use_debug_location: (bool) Whether to use the debug location.
368 """
369 bucket = (self._CLOUD_STORAGE_BUCKET_FOR_DEBUG if use_debug_location
370 else self._CLOUD_STORAGE_BUCKET)
371 extension = _GetFileExtension(filename)
372 cmd = [self._UPLOAD_TO_CLOUD_COMMAND, '--bucket', bucket]
373 if extension:
374 cmd.extend(['-z', extension])
375 cmd.append(filename)
376 self._step_recorder.RunCommand(cmd)
377 print 'Download: https://sandbox.google.com/storage/%s/%s' % (
378 bucket, _GenerateHash(filename))
379
380 def _GetHashFilePathAndContents(self, filename):
381 """Gets the name and content of the hash file created from uploading the
382 given file.
383
384 Args:
385 filename: (str) The file that was uploaded to cloud storage.
386
387 Returns:
388 A tuple of the hash file name, relative to the reository root, and the
389 content, which should be the sha1 hash of the file
390 ('base_file.sha1', hash)
391 """
392 abs_hash_filename = filename + '.sha1'
393 rel_hash_filename = os.path.relpath(
394 abs_hash_filename, self._repository_root)
395 with open(abs_hash_filename, 'r') as f:
396 return (rel_hash_filename, f.read())
397
398 def _CommitFiles(self, files_to_commit, commit_message_lines):
399 """Commits a list of files, with a given message."""
400 raise NotImplementedError
401
402
403class OrderfileGenerator(object):
404 """A utility for generating a new orderfile for Clank.
405
406 Builds an instrumented binary, profiles a run of the application, and
407 generates an updated orderfile.
408 """
409 _CLANK_REPO = os.path.join(constants.DIR_SOURCE_ROOT, 'clank')
Benoit Lizea3fe2932017-10-20 10:24:52410 _CHECK_ORDERFILE_SCRIPT = os.path.join(
411 constants.DIR_SOURCE_ROOT, 'tools', 'cygprofile', 'check_orderfile.py')
412 _BUILD_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(
413 constants.GetOutDirectory()))) # Normally /path/to/src
414
415 _UNPATCHED_ORDERFILE_FILENAME = os.path.join(
416 _CLANK_REPO, 'orderfiles', 'unpatched_orderfile.%s')
Benoit Lizea3fe2932017-10-20 10:24:52417
418 _PATH_TO_ORDERFILE = os.path.join(_CLANK_REPO, 'orderfiles',
419 'orderfile.%s.out')
420
421 # Previous orderfile_generator debug files would be overwritten.
422 _DIRECTORY_FOR_DEBUG_FILES = '/tmp/orderfile_generator_debug_files'
423
424 def _GetPathToOrderfile(self):
425 """Gets the path to the architecture-specific orderfile."""
426 return self._PATH_TO_ORDERFILE % self._options.arch
427
428 def _GetUnpatchedOrderfileFilename(self):
429 """Gets the path to the architecture-specific unpatched orderfile."""
430 return self._UNPATCHED_ORDERFILE_FILENAME % self._options.arch
431
432 def __init__(self, options, orderfile_updater_class):
433 self._options = options
434
435 self._instrumented_out_dir = os.path.join(
436 self._BUILD_ROOT, self._options.arch + '_instrumented_out')
437 self._uninstrumented_out_dir = os.path.join(
438 self._BUILD_ROOT, self._options.arch + '_uninstrumented_out')
439
440 if options.profile:
Benoit Lizea1b64f82017-12-07 10:12:50441 output_directory = os.path.join(self._instrumented_out_dir, 'Release')
Matthew Caryb8daed942018-06-11 10:58:08442 host_profile_dir = os.path.join(output_directory, 'profile_data')
Benoit Lizea1b64f82017-12-07 10:12:50443 urls = [profile_android_startup.AndroidProfileTool.TEST_URL]
444 use_wpr = True
445 simulate_user = False
Benoit L96466812018-03-06 15:58:37446 urls = options.urls
447 use_wpr = not options.no_wpr
448 simulate_user = options.simulate_user
Benoit Lizea3fe2932017-10-20 10:24:52449 self._profiler = profile_android_startup.AndroidProfileTool(
Matthew Cary453ff1452018-07-18 12:19:35450 output_directory, host_profile_dir, use_wpr, urls, simulate_user,
451 device=options.device)
Matthew Cary69e9e422018-08-10 18:30:06452 if options.pregenerated_profiles:
453 self._profiler.SetPregeneratedProfiles(
454 glob.glob(options.pregenerated_profiles))
455 else:
456 assert not options.pregenerated_profiles, (
457 '--pregenerated-profiles cannot be used with --skip-profile')
458 assert not options.profile_save_dir, (
459 '--profile-save-dir cannot be used with --skip-profile')
Benoit Lizea3fe2932017-10-20 10:24:52460
461 self._output_data = {}
462 self._step_recorder = StepRecorder(options.buildbot)
463 self._compiler = None
464 assert issubclass(orderfile_updater_class, OrderfileUpdater)
465 self._orderfile_updater = orderfile_updater_class(self._CLANK_REPO,
466 self._step_recorder,
467 options.branch,
468 options.netrc)
469 assert os.path.isdir(constants.DIR_SOURCE_ROOT), 'No src directory found'
Benoit Lizefefbb27c2018-01-17 13:54:18470 symbol_extractor.SetArchitecture(options.arch)
Benoit Lizea3fe2932017-10-20 10:24:52471
Benoit Lizea3fe2932017-10-20 10:24:52472 @staticmethod
473 def _RemoveBlanks(src_file, dest_file):
474 """A utility to remove blank lines from a file.
475
476 Args:
477 src_file: The name of the file to remove the blanks from.
478 dest_file: The name of the file to write the output without blanks.
479 """
480 assert src_file != dest_file, 'Source and destination need to be distinct'
481
482 try:
483 src = open(src_file, 'r')
484 dest = open(dest_file, 'w')
485 for line in src:
486 if line and not line.isspace():
487 dest.write(line)
488 finally:
489 src.close()
490 dest.close()
491
492 def _GenerateAndProcessProfile(self):
Matthew Cary78aae162018-08-10 17:16:30493 """Invokes a script to merge the per-thread traces into one file.
494
495 The produced list of offsets is saved in
496 self._GetUnpatchedOrderfileFilename().
497 """
Benoit Lizea3fe2932017-10-20 10:24:52498 self._step_recorder.BeginStep('Generate Profile Data')
499 files = []
Matthew Cary78aae162018-08-10 17:16:30500 logging.getLogger().setLevel(logging.DEBUG)
501 if self._options.system_health_orderfile:
502 files = self._profiler.CollectSystemHealthProfile(
503 self._compiler.chrome_apk)
Matthew Cary69e9e422018-08-10 18:30:06504 self._MaybeSaveProfile(files)
Matthew Cary78aae162018-08-10 17:16:30505 try:
506 self._ProcessPhasedOrderfile(files)
507 except Exception:
508 for f in files:
509 self._SaveForDebugging(f)
Matthew Cary8b1416232018-08-10 19:12:22510 self._SaveForDebugging(self._compiler.lib_chrome_so)
Matthew Cary78aae162018-08-10 17:16:30511 raise
512 finally:
513 self._profiler.Cleanup()
514 else:
515 self._CollectLegacyProfile()
516 logging.getLogger().setLevel(logging.INFO)
517
518 def _ProcessPhasedOrderfile(self, files):
519 """Process the phased orderfiles produced by system health benchmarks.
520
521 The offsets will be placed in _GetUnpatchedOrderfileFilename().
522
523 Args:
524 file: Profile files pulled locally.
525 """
526 self._step_recorder.BeginStep('Process Phased Orderfile')
527 profiles = process_profiles.ProfileManager(files)
528 processor = process_profiles.SymbolOffsetProcessor(
529 self._compiler.lib_chrome_so)
Matthew Cary91df9792018-11-30 14:35:15530 ordered_symbols= cluster.ClusterOffsets(profiles, processor)
Matthew Cary8b1416232018-08-10 19:12:22531 if not ordered_symbols:
532 raise Exception('Failed to get ordered symbols')
Matthew Cary91df9792018-11-30 14:35:15533 self._output_data['offsets_kib'] = processor.SymbolsSize(
534 ordered_symbols) / 1024
Matthew Cary78aae162018-08-10 17:16:30535 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
Matthew Cary8b1416232018-08-10 19:12:22536 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary78aae162018-08-10 17:16:30537
538 def _CollectLegacyProfile(self):
Matthew Cary2df8d452018-09-19 07:50:20539 files = []
Benoit Lizea3fe2932017-10-20 10:24:52540 try:
Benoit Lizea3fe2932017-10-20 10:24:52541 files = self._profiler.CollectProfile(
542 self._compiler.chrome_apk,
543 constants.PACKAGE_INFO['chrome'])
Matthew Cary69e9e422018-08-10 18:30:06544 self._MaybeSaveProfile(files)
Matthew Caryb8daed942018-06-11 10:58:08545 self._step_recorder.BeginStep('Process profile')
Benoit L96466812018-03-06 15:58:37546 assert os.path.exists(self._compiler.lib_chrome_so)
547 offsets = process_profiles.GetReachedOffsetsFromDumpFiles(
548 files, self._compiler.lib_chrome_so)
549 if not offsets:
550 raise Exception('No profiler offsets found in {}'.format(
551 '\n'.join(files)))
Matthew Cary8b1416232018-08-10 19:12:22552 processor = process_profiles.SymbolOffsetProcessor(
553 self._compiler.lib_chrome_so)
554 ordered_symbols = processor.GetOrderedSymbols(offsets)
555 if not ordered_symbols:
556 raise Exception('No symbol names from offsets found in {}'.format(
557 '\n'.join(files)))
558 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
559 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary0f1f681a2018-01-22 10:40:51560 except Exception:
Benoit Lizea3fe2932017-10-20 10:24:52561 for f in files:
562 self._SaveForDebugging(f)
563 raise
564 finally:
565 self._profiler.Cleanup()
Benoit Lizea3fe2932017-10-20 10:24:52566
Matthew Cary69e9e422018-08-10 18:30:06567 def _MaybeSaveProfile(self, files):
568 if self._options.profile_save_dir:
569 logging.info('Saving profiles to %s', self._options.profile_save_dir)
570 for f in files:
571 shutil.copy(f, self._options.profile_save_dir)
572 logging.info('Saved profile %s', f)
573
Benoit Lizea3fe2932017-10-20 10:24:52574 def _PatchOrderfile(self):
575 """Patches the orderfile using clean version of libchrome.so."""
576 self._step_recorder.BeginStep('Patch Orderfile')
Benoit Lizefefbb27c2018-01-17 13:54:18577 patch_orderfile.GeneratePatchedOrderfile(
578 self._GetUnpatchedOrderfileFilename(), self._compiler.lib_chrome_so,
579 self._GetPathToOrderfile())
Benoit Lizea3fe2932017-10-20 10:24:52580
581 def _VerifySymbolOrder(self):
582 self._step_recorder.BeginStep('Verify Symbol Order')
583 return_code = self._step_recorder.RunCommand(
584 [self._CHECK_ORDERFILE_SCRIPT, self._compiler.lib_chrome_so,
585 self._GetPathToOrderfile(),
586 '--target-arch=' + self._options.arch],
587 constants.DIR_SOURCE_ROOT,
588 raise_on_error=False)
589 if return_code:
590 self._step_recorder.FailStep('Orderfile check returned %d.' % return_code)
591
592 def _RecordHash(self, file_name):
593 """Records the hash of the file into the output_data dictionary."""
594 self._output_data[os.path.basename(file_name) + '.sha1'] = _GenerateHash(
595 file_name)
596
597 def _SaveFileLocally(self, file_name, file_sha1):
598 """Saves the file to a temporary location and prints the sha1sum."""
599 if not os.path.exists(self._DIRECTORY_FOR_DEBUG_FILES):
600 os.makedirs(self._DIRECTORY_FOR_DEBUG_FILES)
601 shutil.copy(file_name, self._DIRECTORY_FOR_DEBUG_FILES)
602 print 'File: %s, saved in: %s, sha1sum: %s' % (
603 file_name, self._DIRECTORY_FOR_DEBUG_FILES, file_sha1)
604
605 def _SaveForDebugging(self, filename):
606 """Uploads the file to cloud storage or saves to a temporary location."""
607 file_sha1 = _GenerateHash(filename)
608 if not self._options.buildbot:
609 self._SaveFileLocally(filename, file_sha1)
610 else:
611 print 'Uploading file for debugging: ' + filename
612 self._orderfile_updater.UploadToCloudStorage(
613 filename, use_debug_location=True)
614
615 def _SaveForDebuggingWithOverwrite(self, file_name):
616 """Uploads and overwrites the file in cloud storage or copies locally.
617
618 Should be used for large binaries like lib_chrome_so.
619
620 Args:
621 file_name: (str) File to upload.
622 """
623 file_sha1 = _GenerateHash(file_name)
624 if not self._options.buildbot:
625 self._SaveFileLocally(file_name, file_sha1)
626 else:
627 print 'Uploading file for debugging: %s, sha1sum: %s' % (
628 file_name, file_sha1)
629 upload_location = '%s/%s' % (
630 self._CLOUD_STORAGE_BUCKET_FOR_DEBUG, os.path.basename(file_name))
631 self._step_recorder.RunCommand([
632 'gsutil.py', 'cp', file_name, 'gs://' + upload_location])
633 print ('Uploaded to: https://sandbox.google.com/storage/' +
634 upload_location)
635
636 def _MaybeArchiveOrderfile(self, filename):
637 """In buildbot configuration, uploads the generated orderfile to
638 Google Cloud Storage.
639
640 Args:
641 filename: (str) Orderfile to upload.
642 """
Matthew Cary91df9792018-11-30 14:35:15643 # First compute hashes so that we can download them later if we need to.
Benoit Lizea3fe2932017-10-20 10:24:52644 self._step_recorder.BeginStep('Compute hash for ' + filename)
645 self._RecordHash(filename)
646 if self._options.buildbot:
647 self._step_recorder.BeginStep('Archive ' + filename)
648 self._orderfile_updater.UploadToCloudStorage(
649 filename, use_debug_location=False)
650
Egor Paskobce64d012018-11-20 12:02:37651 def UploadReadyOrderfiles(self):
652 self._step_recorder.BeginStep('Upload Ready Orderfiles')
653 for file_name in [self._GetUnpatchedOrderfileFilename(),
654 self._GetPathToOrderfile()]:
655 self._orderfile_updater.UploadToCloudStorage(
656 file_name, use_debug_location=False)
657
Benoit Lizea3fe2932017-10-20 10:24:52658 def _GetHashFilePathAndContents(self, base_file):
659 """Gets the name and content of the hash file created from uploading the
660 given file.
661
662 Args:
663 base_file: The file that was uploaded to cloud storage.
664
665 Returns:
666 A tuple of the hash file name, relative to the clank repo path, and the
667 content, which should be the sha1 hash of the file
668 ('base_file.sha1', hash)
669 """
670 abs_file_name = base_file + '.sha1'
671 rel_file_name = os.path.relpath(abs_file_name, self._CLANK_REPO)
672 with open(abs_file_name, 'r') as f:
673 return (rel_file_name, f.read())
674
675 def Generate(self):
676 """Generates and maybe upload an order."""
677 profile_uploaded = False
678 orderfile_uploaded = False
679
Matthew Carye8400642018-06-14 15:43:02680 assert (bool(self._options.profile) ^
681 bool(self._options.manual_symbol_offsets))
Matthew Cary78aae162018-08-10 17:16:30682 if self._options.system_health_orderfile and not self._options.profile:
683 raise AssertionError('--system_health_orderfile must be not be used '
684 'with --skip-profile')
685 if (self._options.manual_symbol_offsets and
686 not self._options.system_health_orderfile):
687 raise AssertionError('--manual-symbol-offsets must be used with '
688 '--system_health_orderfile.')
Matthew Carye8400642018-06-14 15:43:02689
Benoit Lizea3fe2932017-10-20 10:24:52690 if self._options.profile:
691 try:
692 _UnstashOutputDirectory(self._instrumented_out_dir)
693 self._compiler = ClankCompiler(
694 self._instrumented_out_dir,
695 self._step_recorder, self._options.arch, self._options.jobs,
696 self._options.max_load, self._options.use_goma,
Matthew Caryd6bfcb72018-09-14 11:27:46697 self._options.goma_dir, self._options.system_health_orderfile,
698 self._options.monochrome)
Matthew Carya2bea452018-11-13 10:21:07699 if not self._options.pregenerated_profiles:
700 # If there are pregenerated profiles, the instrumented build should
701 # not be changed to avoid invalidating the pregenerated profile
702 # offsets.
703 self._compiler.CompileChromeApk(True)
Benoit Lizea3fe2932017-10-20 10:24:52704 self._GenerateAndProcessProfile()
705 self._MaybeArchiveOrderfile(self._GetUnpatchedOrderfileFilename())
706 profile_uploaded = True
707 finally:
Benoit Lizea3fe2932017-10-20 10:24:52708 _StashOutputDirectory(self._instrumented_out_dir)
Matthew Carye8400642018-06-14 15:43:02709 elif self._options.manual_symbol_offsets:
710 assert self._options.manual_libname
711 assert self._options.manual_objdir
712 with file(self._options.manual_symbol_offsets) as f:
713 symbol_offsets = [int(x) for x in f.xreadlines()]
714 processor = process_profiles.SymbolOffsetProcessor(
Matthew Caryb46ad282018-11-23 14:43:57715 self._compiler.manual_libname)
Matthew Carye8400642018-06-14 15:43:02716 generator = cyglog_to_orderfile.OffsetOrderfileGenerator(
717 processor, cyglog_to_orderfile.ObjectFileProcessor(
718 self._options.manual_objdir))
719 ordered_sections = generator.GetOrderedSections(symbol_offsets)
720 if not ordered_sections: # Either None or empty is a problem.
721 raise Exception('Failed to get ordered sections')
722 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
723 orderfile.write('\n'.join(ordered_sections))
724
Benoit Lizea3fe2932017-10-20 10:24:52725 if self._options.patch:
726 if self._options.profile:
727 self._RemoveBlanks(self._GetUnpatchedOrderfileFilename(),
728 self._GetPathToOrderfile())
729 try:
730 _UnstashOutputDirectory(self._uninstrumented_out_dir)
731 self._compiler = ClankCompiler(
732 self._uninstrumented_out_dir, self._step_recorder,
733 self._options.arch, self._options.jobs, self._options.max_load,
Matthew Cary78aae162018-08-10 17:16:30734 self._options.use_goma, self._options.goma_dir,
Matthew Caryd6bfcb72018-09-14 11:27:46735 self._options.system_health_orderfile, self._options.monochrome)
Benoit Lizea3fe2932017-10-20 10:24:52736 self._compiler.CompileLibchrome(False)
737 self._PatchOrderfile()
738 # Because identical code folding is a bit different with and without
739 # the orderfile build, we need to re-patch the orderfile with code
740 # folding as close to the final version as possible.
741 self._compiler.CompileLibchrome(False, force_relink=True)
742 self._PatchOrderfile()
743 self._compiler.CompileLibchrome(False, force_relink=True)
744 self._VerifySymbolOrder()
745 self._MaybeArchiveOrderfile(self._GetPathToOrderfile())
746 finally:
747 _StashOutputDirectory(self._uninstrumented_out_dir)
748 orderfile_uploaded = True
749
750 if (self._options.buildbot and self._options.netrc
751 and not self._step_recorder.ErrorRecorded()):
752 unpatched_orderfile_filename = (
753 self._GetUnpatchedOrderfileFilename() if profile_uploaded else None)
754 orderfile_filename = (
Benoit Lized913be82017-10-24 13:38:27755 self._GetPathToOrderfile() if orderfile_uploaded else None)
Benoit Lizea3fe2932017-10-20 10:24:52756 self._orderfile_updater.CommitFileHashes(
757 unpatched_orderfile_filename, orderfile_filename)
758
759 self._step_recorder.EndStep()
760 return not self._step_recorder.ErrorRecorded()
761
762 def GetReportingData(self):
763 """Get a dictionary of reporting data (timings, output hashes)"""
764 self._output_data['timings'] = self._step_recorder.timings
765 return self._output_data
766
767
Benoit Lizea1b64f82017-12-07 10:12:50768def CreateArgumentParser():
769 """Creates and returns the argument parser."""
770 parser = argparse.ArgumentParser()
771 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52772 '--buildbot', action='store_true',
773 help='If true, the script expects to be run on a buildbot')
Benoit Lizea1b64f82017-12-07 10:12:50774 parser.add_argument(
Matthew Cary453ff1452018-07-18 12:19:35775 '--device', default=None, type=str,
776 help='Device serial number on which to run profiling.')
777 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52778 '--verify', action='store_true',
779 help='If true, the script only verifies the current orderfile')
Benoit Lizea1b64f82017-12-07 10:12:50780 parser.add_argument('--target-arch', action='store', dest='arch',
781 default=cygprofile_utils.DetectArchitecture(),
782 choices=['arm', 'arm64', 'x86', 'x86_64', 'x64', 'mips'],
783 help='The target architecture for which to build')
784 parser.add_argument('--output-json', action='store', dest='json_file',
785 help='Location to save stats in json format')
786 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52787 '--skip-profile', action='store_false', dest='profile', default=True,
788 help='Don\'t generate a profile on the device. Only patch from the '
789 'existing profile.')
Benoit Lizea1b64f82017-12-07 10:12:50790 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52791 '--skip-patch', action='store_false', dest='patch', default=True,
792 help='Only generate the raw (unpatched) orderfile, don\'t patch it.')
Benoit Lizea1b64f82017-12-07 10:12:50793 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52794 '--netrc', action='store',
795 help='A custom .netrc file to use for git checkin. Only used on bots.')
Benoit Lizea1b64f82017-12-07 10:12:50796 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52797 '--branch', action='store', default='master',
798 help='When running on buildbot with a netrc, the branch orderfile '
799 'hashes get checked into.')
800 # Note: -j50 was causing issues on the bot.
Benoit Lizea1b64f82017-12-07 10:12:50801 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52802 '-j', '--jobs', action='store', default=20,
803 help='Number of jobs to use for compilation.')
Benoit Lizea1b64f82017-12-07 10:12:50804 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52805 '-l', '--max-load', action='store', default=4, help='Max cpu load.')
Benoit Lizea1b64f82017-12-07 10:12:50806 parser.add_argument('--goma-dir', help='GOMA directory.')
807 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52808 '--use-goma', action='store_true', help='Enable GOMA.', default=False)
Benoit Lizea1b64f82017-12-07 10:12:50809 parser.add_argument('--adb-path', help='Path to the adb binary.')
Matthew Carye8400642018-06-14 15:43:02810
Matthew Cary03642a2a2018-10-15 09:08:08811 parser.add_argument('--system-health-orderfile', action='store_true',
812 dest='system_health_orderfile', default=False,
Matthew Caryc262f5d2018-09-19 14:36:28813 help=('Create an orderfile based on an about:blank '
814 'startup benchmark instead of system health '
815 'benchmarks.'))
Matthew Caryd6bfcb72018-09-14 11:27:46816 parser.add_argument('--monochrome', action='store_true',
817 help=('Compile and instrument monochrome (for post-N '
818 'devices).'))
Matthew Cary78aae162018-08-10 17:16:30819
Matthew Carye8400642018-06-14 15:43:02820 parser.add_argument('--manual-symbol-offsets', default=None, type=str,
821 help=('File of list of ordered symbol offsets generated '
822 'by manual profiling. Must set other --manual* '
823 'flags if this is used, and must --skip-profile.'))
824 parser.add_argument('--manual-libname', default=None, type=str,
825 help=('Library filename corresponding to '
826 '--manual-symbol-offsets.'))
827 parser.add_argument('--manual-objdir', default=None, type=str,
828 help=('Root of object file directory corresponding to '
829 '--manual-symbol-offsets.'))
Matthew Cary69e9e422018-08-10 18:30:06830 parser.add_argument('--pregenerated-profiles', default=None, type=str,
831 help=('Pregenerated profiles to use instead of running '
832 'profile step. Cannot be used with '
833 '--skip-profiles.'))
834 parser.add_argument('--profile-save-dir', default=None, type=str,
835 help=('Directory to save any profiles created. These can '
836 'be used with --pregenerated-profiles. Cannot be '
837 'used with --skip-profiles.'))
Egor Paskobce64d012018-11-20 12:02:37838 parser.add_argument('--upload-ready-orderfiles', action='store_true',
839 help=('Skip orderfile generation and manually upload '
840 'orderfiles (both patched and unpatched) from '
841 'their normal location in the tree to the cloud '
842 'storage. DANGEROUS! USE WITH CARE!'))
Matthew Carye8400642018-06-14 15:43:02843
Benoit Lizea1b64f82017-12-07 10:12:50844 profile_android_startup.AddProfileCollectionArguments(parser)
Benoit Lizea3fe2932017-10-20 10:24:52845 return parser
846
847
848def CreateOrderfile(options, orderfile_updater_class):
849 """Creates an oderfile.
850
851 Args:
852 options: As returned from optparse.OptionParser.parse_args()
853 orderfile_updater_class: (OrderfileUpdater) subclass of OrderfileUpdater.
854
855 Returns:
856 True iff success.
857 """
858 logging.basicConfig(level=logging.INFO)
859 devil_chromium.Initialize(adb_path=options.adb_path)
860
Egor Pasko93e514e2017-10-31 13:32:36861 generator = OrderfileGenerator(options, orderfile_updater_class)
Benoit Lizea3fe2932017-10-20 10:24:52862 try:
863 if options.verify:
Egor Pasko93e514e2017-10-31 13:32:36864 generator._VerifySymbolOrder()
Egor Paskobce64d012018-11-20 12:02:37865 elif options.upload_ready_orderfiles:
866 return generator.UploadReadyOrderfiles()
Benoit Lizea3fe2932017-10-20 10:24:52867 else:
Egor Pasko93e514e2017-10-31 13:32:36868 return generator.Generate()
Benoit Lizea3fe2932017-10-20 10:24:52869 finally:
Egor Pasko93e514e2017-10-31 13:32:36870 json_output = json.dumps(generator.GetReportingData(),
Benoit Lizea3fe2932017-10-20 10:24:52871 indent=2) + '\n'
872 if options.json_file:
873 with open(options.json_file, 'w') as f:
874 f.write(json_output)
875 print json_output
876 return False
877
878
Benoit Lizea1b64f82017-12-07 10:12:50879def main():
880 parser = CreateArgumentParser()
881 options = parser.parse_args()
Benoit Lizea3fe2932017-10-20 10:24:52882 return 0 if CreateOrderfile(options, OrderfileUpdater) else 1
883
884
885if __name__ == '__main__':
Benoit Lizea1b64f82017-12-07 10:12:50886 sys.exit(main())