[go: nahoru, domu]

blob: 543cd9419e1e414ec8ab322ad0b84e5e50f55229 [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 Carye8400642018-06-14 15:43:0230import cyglog_to_orderfile
Benoit Lizea3fe2932017-10-20 10:24:5231import cygprofile_utils
Benoit Lizefefbb27c2018-01-17 13:54:1832import patch_orderfile
Matthew Cary78aae162018-08-10 17:16:3033import phased_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',
272 # We have to build with no symbols if profiling and minimal symbols
273 # otherwise for libchrome.so to fit under the 4 GB limit.
274 # crbug.com/574476
275 'symbol_level=' + ('0' if instrumented else '1'),
276 'target_cpu="' + self._arch + '"',
277 'target_os="android"',
278 'use_goma=' + str(self._use_goma).lower(),
279 'use_order_profiling=' + str(instrumented).lower(),
280 ]
281 if self._goma_dir:
282 args += ['goma_dir="%s"' % self._goma_dir]
Matthew Cary78aae162018-08-10 17:16:30283 if self._system_health_profiling:
284 args += ['devtools_instrumentation_dumping = ' +
285 str(instrumented).lower()]
Benoit Lizea87e5bc2017-11-07 15:12:57286
Benoit Lizea3fe2932017-10-20 10:24:52287 self._step_recorder.RunCommand(
288 ['gn', 'gen', os.path.join(self._out_dir, 'Release'),
289 '--args=' + ' '.join(args)])
290
291 self._step_recorder.RunCommand(
292 ['ninja', '-C', os.path.join(self._out_dir, 'Release'),
293 '-j' + str(self._jobs), '-l' + str(self._max_load), target])
294
295 def CompileChromeApk(self, instrumented, force_relink=False):
296 """Builds a Chrome.apk either with or without order_profiling on.
297
298 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57299 instrumented: (bool) Whether to build an instrumented apk.
Benoit Lizea3fe2932017-10-20 10:24:52300 force_relink: Whether libchromeview.so should be re-created.
301 """
302 if force_relink:
303 self._step_recorder.RunCommand(['rm', '-rf', self.lib_chrome_so])
Matthew Carye1b00062018-09-19 14:27:44304 self.Build(instrumented, self._apk_target)
Benoit Lizea3fe2932017-10-20 10:24:52305
306 def CompileLibchrome(self, instrumented, force_relink=False):
307 """Builds a libchrome.so either with or without order_profiling on.
308
309 Args:
Benoit Lizea87e5bc2017-11-07 15:12:57310 instrumented: (bool) Whether to build an instrumented apk.
311 force_relink: (bool) Whether libchrome.so should be re-created.
Benoit Lizea3fe2932017-10-20 10:24:52312 """
313 if force_relink:
314 self._step_recorder.RunCommand(['rm', '-rf', self.lib_chrome_so])
Matthew Caryd6bfcb72018-09-14 11:27:46315 self.Build(instrumented, self._libchrome_target)
Benoit Lizea3fe2932017-10-20 10:24:52316
317
318class OrderfileUpdater(object):
319 """Handles uploading and committing a new orderfile in the repository.
320
321 Only used for testing or on a bot.
322 """
323
324 _CLOUD_STORAGE_BUCKET_FOR_DEBUG = None
325 _CLOUD_STORAGE_BUCKET = None
326 _UPLOAD_TO_CLOUD_COMMAND = 'upload_to_google_storage.py'
327
328 def __init__(self, repository_root, step_recorder, branch, netrc):
329 """Constructor.
330
331 Args:
332 repository_root: (str) Root of the target repository.
333 step_recorder: (StepRecorder) Step recorder, for logging.
334 branch: (str) Branch to commit to.
335 netrc: (str) Path to the .netrc file to use.
336 """
337 self._repository_root = repository_root
338 self._step_recorder = step_recorder
339 self._branch = branch
340 self._netrc = netrc
341
342 def CommitFileHashes(self, unpatched_orderfile_filename, orderfile_filename):
343 """Commits unpatched and patched orderfiles hashes, if provided.
344
345 Files must have been successfilly uploaded to cloud storage first.
346
347 Args:
348 unpatched_orderfile_filename: (str or None) Unpatched orderfile path.
349 orderfile_filename: (str or None) Orderfile path.
350
351 Raises:
352 NotImplementedError when the commit logic hasn't been overriden.
353 """
354 files_to_commit = []
355 commit_message_lines = ['Update Orderfile.']
356 for filename in [unpatched_orderfile_filename, orderfile_filename]:
357 if not filename:
358 continue
359 (relative_path, sha1) = self._GetHashFilePathAndContents(filename)
360 commit_message_lines.append('Profile: %s: %s' % (
361 os.path.basename(relative_path), sha1))
362 files_to_commit.append(relative_path)
363 if files_to_commit:
364 self._CommitFiles(files_to_commit, commit_message_lines)
365
366 def UploadToCloudStorage(self, filename, use_debug_location):
367 """Uploads a file to cloud storage.
368
369 Args:
370 filename: (str) File to upload.
371 use_debug_location: (bool) Whether to use the debug location.
372 """
373 bucket = (self._CLOUD_STORAGE_BUCKET_FOR_DEBUG if use_debug_location
374 else self._CLOUD_STORAGE_BUCKET)
375 extension = _GetFileExtension(filename)
376 cmd = [self._UPLOAD_TO_CLOUD_COMMAND, '--bucket', bucket]
377 if extension:
378 cmd.extend(['-z', extension])
379 cmd.append(filename)
380 self._step_recorder.RunCommand(cmd)
381 print 'Download: https://sandbox.google.com/storage/%s/%s' % (
382 bucket, _GenerateHash(filename))
383
384 def _GetHashFilePathAndContents(self, filename):
385 """Gets the name and content of the hash file created from uploading the
386 given file.
387
388 Args:
389 filename: (str) The file that was uploaded to cloud storage.
390
391 Returns:
392 A tuple of the hash file name, relative to the reository root, and the
393 content, which should be the sha1 hash of the file
394 ('base_file.sha1', hash)
395 """
396 abs_hash_filename = filename + '.sha1'
397 rel_hash_filename = os.path.relpath(
398 abs_hash_filename, self._repository_root)
399 with open(abs_hash_filename, 'r') as f:
400 return (rel_hash_filename, f.read())
401
402 def _CommitFiles(self, files_to_commit, commit_message_lines):
403 """Commits a list of files, with a given message."""
404 raise NotImplementedError
405
406
407class OrderfileGenerator(object):
408 """A utility for generating a new orderfile for Clank.
409
410 Builds an instrumented binary, profiles a run of the application, and
411 generates an updated orderfile.
412 """
413 _CLANK_REPO = os.path.join(constants.DIR_SOURCE_ROOT, 'clank')
Benoit Lizea3fe2932017-10-20 10:24:52414 _CHECK_ORDERFILE_SCRIPT = os.path.join(
415 constants.DIR_SOURCE_ROOT, 'tools', 'cygprofile', 'check_orderfile.py')
416 _BUILD_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(
417 constants.GetOutDirectory()))) # Normally /path/to/src
418
419 _UNPATCHED_ORDERFILE_FILENAME = os.path.join(
420 _CLANK_REPO, 'orderfiles', 'unpatched_orderfile.%s')
Benoit Lizea3fe2932017-10-20 10:24:52421
422 _PATH_TO_ORDERFILE = os.path.join(_CLANK_REPO, 'orderfiles',
423 'orderfile.%s.out')
424
425 # Previous orderfile_generator debug files would be overwritten.
426 _DIRECTORY_FOR_DEBUG_FILES = '/tmp/orderfile_generator_debug_files'
427
428 def _GetPathToOrderfile(self):
429 """Gets the path to the architecture-specific orderfile."""
430 return self._PATH_TO_ORDERFILE % self._options.arch
431
432 def _GetUnpatchedOrderfileFilename(self):
433 """Gets the path to the architecture-specific unpatched orderfile."""
434 return self._UNPATCHED_ORDERFILE_FILENAME % self._options.arch
435
436 def __init__(self, options, orderfile_updater_class):
437 self._options = options
438
439 self._instrumented_out_dir = os.path.join(
440 self._BUILD_ROOT, self._options.arch + '_instrumented_out')
441 self._uninstrumented_out_dir = os.path.join(
442 self._BUILD_ROOT, self._options.arch + '_uninstrumented_out')
443
444 if options.profile:
Benoit Lizea1b64f82017-12-07 10:12:50445 output_directory = os.path.join(self._instrumented_out_dir, 'Release')
Matthew Caryb8daed942018-06-11 10:58:08446 host_profile_dir = os.path.join(output_directory, 'profile_data')
Benoit Lizea1b64f82017-12-07 10:12:50447 urls = [profile_android_startup.AndroidProfileTool.TEST_URL]
448 use_wpr = True
449 simulate_user = False
Benoit L96466812018-03-06 15:58:37450 urls = options.urls
451 use_wpr = not options.no_wpr
452 simulate_user = options.simulate_user
Benoit Lizea3fe2932017-10-20 10:24:52453 self._profiler = profile_android_startup.AndroidProfileTool(
Matthew Cary453ff1452018-07-18 12:19:35454 output_directory, host_profile_dir, use_wpr, urls, simulate_user,
455 device=options.device)
Matthew Cary69e9e422018-08-10 18:30:06456 if options.pregenerated_profiles:
457 self._profiler.SetPregeneratedProfiles(
458 glob.glob(options.pregenerated_profiles))
459 else:
460 assert not options.pregenerated_profiles, (
461 '--pregenerated-profiles cannot be used with --skip-profile')
462 assert not options.profile_save_dir, (
463 '--profile-save-dir cannot be used with --skip-profile')
Benoit Lizea3fe2932017-10-20 10:24:52464
465 self._output_data = {}
466 self._step_recorder = StepRecorder(options.buildbot)
467 self._compiler = None
468 assert issubclass(orderfile_updater_class, OrderfileUpdater)
469 self._orderfile_updater = orderfile_updater_class(self._CLANK_REPO,
470 self._step_recorder,
471 options.branch,
472 options.netrc)
473 assert os.path.isdir(constants.DIR_SOURCE_ROOT), 'No src directory found'
Benoit Lizefefbb27c2018-01-17 13:54:18474 symbol_extractor.SetArchitecture(options.arch)
Benoit Lizea3fe2932017-10-20 10:24:52475
Benoit Lizea3fe2932017-10-20 10:24:52476 @staticmethod
477 def _RemoveBlanks(src_file, dest_file):
478 """A utility to remove blank lines from a file.
479
480 Args:
481 src_file: The name of the file to remove the blanks from.
482 dest_file: The name of the file to write the output without blanks.
483 """
484 assert src_file != dest_file, 'Source and destination need to be distinct'
485
486 try:
487 src = open(src_file, 'r')
488 dest = open(dest_file, 'w')
489 for line in src:
490 if line and not line.isspace():
491 dest.write(line)
492 finally:
493 src.close()
494 dest.close()
495
496 def _GenerateAndProcessProfile(self):
Matthew Cary78aae162018-08-10 17:16:30497 """Invokes a script to merge the per-thread traces into one file.
498
499 The produced list of offsets is saved in
500 self._GetUnpatchedOrderfileFilename().
501 """
Benoit Lizea3fe2932017-10-20 10:24:52502 self._step_recorder.BeginStep('Generate Profile Data')
503 files = []
Matthew Cary78aae162018-08-10 17:16:30504 logging.getLogger().setLevel(logging.DEBUG)
505 if self._options.system_health_orderfile:
506 files = self._profiler.CollectSystemHealthProfile(
507 self._compiler.chrome_apk)
Matthew Cary69e9e422018-08-10 18:30:06508 self._MaybeSaveProfile(files)
Matthew Cary78aae162018-08-10 17:16:30509 try:
510 self._ProcessPhasedOrderfile(files)
511 except Exception:
512 for f in files:
513 self._SaveForDebugging(f)
Matthew Cary8b1416232018-08-10 19:12:22514 self._SaveForDebugging(self._compiler.lib_chrome_so)
Matthew Cary78aae162018-08-10 17:16:30515 raise
516 finally:
517 self._profiler.Cleanup()
518 else:
519 self._CollectLegacyProfile()
520 logging.getLogger().setLevel(logging.INFO)
521
522 def _ProcessPhasedOrderfile(self, files):
523 """Process the phased orderfiles produced by system health benchmarks.
524
525 The offsets will be placed in _GetUnpatchedOrderfileFilename().
526
527 Args:
528 file: Profile files pulled locally.
529 """
530 self._step_recorder.BeginStep('Process Phased Orderfile')
531 profiles = process_profiles.ProfileManager(files)
532 processor = process_profiles.SymbolOffsetProcessor(
533 self._compiler.lib_chrome_so)
534 phaser = phased_orderfile.PhasedAnalyzer(profiles, processor)
535 if self._options.offsets_for_memory:
536 profile_offsets = phaser.GetOffsetsForMemoryFootprint()
537 else:
538 profile_offsets = phaser.GetOffsetsForStartup()
539 self._output_data['orderfile_size'] = {
540 'startup_kib': processor.OffsetsPrimarySize(
541 profile_offsets.startup) / 1024,
542 'common_kib': processor.OffsetsPrimarySize(
543 profile_offsets.common) / 1024,
544 'interaction_kib': processor.OffsetsPrimarySize(
545 profile_offsets.interaction) / 1024}
546
547 offsets_list = (profile_offsets.startup +
548 profile_offsets.common +
549 profile_offsets.interaction)
Matthew Cary8b1416232018-08-10 19:12:22550 ordered_symbols = processor.GetOrderedSymbols(offsets_list)
551 if not ordered_symbols:
552 raise Exception('Failed to get ordered symbols')
Matthew Cary78aae162018-08-10 17:16:30553 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
Matthew Cary8b1416232018-08-10 19:12:22554 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary78aae162018-08-10 17:16:30555
556 def _CollectLegacyProfile(self):
Matthew Cary2df8d452018-09-19 07:50:20557 files = []
Benoit Lizea3fe2932017-10-20 10:24:52558 try:
Benoit Lizea3fe2932017-10-20 10:24:52559 files = self._profiler.CollectProfile(
560 self._compiler.chrome_apk,
561 constants.PACKAGE_INFO['chrome'])
Matthew Cary69e9e422018-08-10 18:30:06562 self._MaybeSaveProfile(files)
Matthew Caryb8daed942018-06-11 10:58:08563 self._step_recorder.BeginStep('Process profile')
Benoit L96466812018-03-06 15:58:37564 assert os.path.exists(self._compiler.lib_chrome_so)
565 offsets = process_profiles.GetReachedOffsetsFromDumpFiles(
566 files, self._compiler.lib_chrome_so)
567 if not offsets:
568 raise Exception('No profiler offsets found in {}'.format(
569 '\n'.join(files)))
Matthew Cary8b1416232018-08-10 19:12:22570 processor = process_profiles.SymbolOffsetProcessor(
571 self._compiler.lib_chrome_so)
572 ordered_symbols = processor.GetOrderedSymbols(offsets)
573 if not ordered_symbols:
574 raise Exception('No symbol names from offsets found in {}'.format(
575 '\n'.join(files)))
576 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
577 orderfile.write('\n'.join(ordered_symbols))
Matthew Cary0f1f681a2018-01-22 10:40:51578 except Exception:
Benoit Lizea3fe2932017-10-20 10:24:52579 for f in files:
580 self._SaveForDebugging(f)
581 raise
582 finally:
583 self._profiler.Cleanup()
Benoit Lizea3fe2932017-10-20 10:24:52584
Matthew Cary69e9e422018-08-10 18:30:06585 def _MaybeSaveProfile(self, files):
586 if self._options.profile_save_dir:
587 logging.info('Saving profiles to %s', self._options.profile_save_dir)
588 for f in files:
589 shutil.copy(f, self._options.profile_save_dir)
590 logging.info('Saved profile %s', f)
591
Benoit Lizea3fe2932017-10-20 10:24:52592 def _PatchOrderfile(self):
593 """Patches the orderfile using clean version of libchrome.so."""
594 self._step_recorder.BeginStep('Patch Orderfile')
Benoit Lizefefbb27c2018-01-17 13:54:18595 patch_orderfile.GeneratePatchedOrderfile(
596 self._GetUnpatchedOrderfileFilename(), self._compiler.lib_chrome_so,
597 self._GetPathToOrderfile())
Benoit Lizea3fe2932017-10-20 10:24:52598
599 def _VerifySymbolOrder(self):
600 self._step_recorder.BeginStep('Verify Symbol Order')
601 return_code = self._step_recorder.RunCommand(
602 [self._CHECK_ORDERFILE_SCRIPT, self._compiler.lib_chrome_so,
603 self._GetPathToOrderfile(),
604 '--target-arch=' + self._options.arch],
605 constants.DIR_SOURCE_ROOT,
606 raise_on_error=False)
607 if return_code:
608 self._step_recorder.FailStep('Orderfile check returned %d.' % return_code)
609
610 def _RecordHash(self, file_name):
611 """Records the hash of the file into the output_data dictionary."""
612 self._output_data[os.path.basename(file_name) + '.sha1'] = _GenerateHash(
613 file_name)
614
615 def _SaveFileLocally(self, file_name, file_sha1):
616 """Saves the file to a temporary location and prints the sha1sum."""
617 if not os.path.exists(self._DIRECTORY_FOR_DEBUG_FILES):
618 os.makedirs(self._DIRECTORY_FOR_DEBUG_FILES)
619 shutil.copy(file_name, self._DIRECTORY_FOR_DEBUG_FILES)
620 print 'File: %s, saved in: %s, sha1sum: %s' % (
621 file_name, self._DIRECTORY_FOR_DEBUG_FILES, file_sha1)
622
623 def _SaveForDebugging(self, filename):
624 """Uploads the file to cloud storage or saves to a temporary location."""
625 file_sha1 = _GenerateHash(filename)
626 if not self._options.buildbot:
627 self._SaveFileLocally(filename, file_sha1)
628 else:
629 print 'Uploading file for debugging: ' + filename
630 self._orderfile_updater.UploadToCloudStorage(
631 filename, use_debug_location=True)
632
633 def _SaveForDebuggingWithOverwrite(self, file_name):
634 """Uploads and overwrites the file in cloud storage or copies locally.
635
636 Should be used for large binaries like lib_chrome_so.
637
638 Args:
639 file_name: (str) File to upload.
640 """
641 file_sha1 = _GenerateHash(file_name)
642 if not self._options.buildbot:
643 self._SaveFileLocally(file_name, file_sha1)
644 else:
645 print 'Uploading file for debugging: %s, sha1sum: %s' % (
646 file_name, file_sha1)
647 upload_location = '%s/%s' % (
648 self._CLOUD_STORAGE_BUCKET_FOR_DEBUG, os.path.basename(file_name))
649 self._step_recorder.RunCommand([
650 'gsutil.py', 'cp', file_name, 'gs://' + upload_location])
651 print ('Uploaded to: https://sandbox.google.com/storage/' +
652 upload_location)
653
654 def _MaybeArchiveOrderfile(self, filename):
655 """In buildbot configuration, uploads the generated orderfile to
656 Google Cloud Storage.
657
658 Args:
659 filename: (str) Orderfile to upload.
660 """
661 # First compute hashes so that we can download them later if we need to
662 self._step_recorder.BeginStep('Compute hash for ' + filename)
663 self._RecordHash(filename)
664 if self._options.buildbot:
665 self._step_recorder.BeginStep('Archive ' + filename)
666 self._orderfile_updater.UploadToCloudStorage(
667 filename, use_debug_location=False)
668
669 def _GetHashFilePathAndContents(self, base_file):
670 """Gets the name and content of the hash file created from uploading the
671 given file.
672
673 Args:
674 base_file: The file that was uploaded to cloud storage.
675
676 Returns:
677 A tuple of the hash file name, relative to the clank repo path, and the
678 content, which should be the sha1 hash of the file
679 ('base_file.sha1', hash)
680 """
681 abs_file_name = base_file + '.sha1'
682 rel_file_name = os.path.relpath(abs_file_name, self._CLANK_REPO)
683 with open(abs_file_name, 'r') as f:
684 return (rel_file_name, f.read())
685
686 def Generate(self):
687 """Generates and maybe upload an order."""
688 profile_uploaded = False
689 orderfile_uploaded = False
690
Matthew Carye8400642018-06-14 15:43:02691 assert (bool(self._options.profile) ^
692 bool(self._options.manual_symbol_offsets))
Matthew Cary78aae162018-08-10 17:16:30693 if self._options.system_health_orderfile and not self._options.profile:
694 raise AssertionError('--system_health_orderfile must be not be used '
695 'with --skip-profile')
696 if (self._options.manual_symbol_offsets and
697 not self._options.system_health_orderfile):
698 raise AssertionError('--manual-symbol-offsets must be used with '
699 '--system_health_orderfile.')
Matthew Carye8400642018-06-14 15:43:02700
Benoit Lizea3fe2932017-10-20 10:24:52701 if self._options.profile:
702 try:
703 _UnstashOutputDirectory(self._instrumented_out_dir)
704 self._compiler = ClankCompiler(
705 self._instrumented_out_dir,
706 self._step_recorder, self._options.arch, self._options.jobs,
707 self._options.max_load, self._options.use_goma,
Matthew Caryd6bfcb72018-09-14 11:27:46708 self._options.goma_dir, self._options.system_health_orderfile,
709 self._options.monochrome)
Matthew Carya2bea452018-11-13 10:21:07710 if not self._options.pregenerated_profiles:
711 # If there are pregenerated profiles, the instrumented build should
712 # not be changed to avoid invalidating the pregenerated profile
713 # offsets.
714 self._compiler.CompileChromeApk(True)
Benoit Lizea3fe2932017-10-20 10:24:52715 self._GenerateAndProcessProfile()
716 self._MaybeArchiveOrderfile(self._GetUnpatchedOrderfileFilename())
717 profile_uploaded = True
718 finally:
Benoit Lizea3fe2932017-10-20 10:24:52719 _StashOutputDirectory(self._instrumented_out_dir)
Matthew Carye8400642018-06-14 15:43:02720 elif self._options.manual_symbol_offsets:
721 assert self._options.manual_libname
722 assert self._options.manual_objdir
723 with file(self._options.manual_symbol_offsets) as f:
724 symbol_offsets = [int(x) for x in f.xreadlines()]
725 processor = process_profiles.SymbolOffsetProcessor(
726 self._options.manual_libname)
727 generator = cyglog_to_orderfile.OffsetOrderfileGenerator(
728 processor, cyglog_to_orderfile.ObjectFileProcessor(
729 self._options.manual_objdir))
730 ordered_sections = generator.GetOrderedSections(symbol_offsets)
731 if not ordered_sections: # Either None or empty is a problem.
732 raise Exception('Failed to get ordered sections')
733 with open(self._GetUnpatchedOrderfileFilename(), 'w') as orderfile:
734 orderfile.write('\n'.join(ordered_sections))
735
Benoit Lizea3fe2932017-10-20 10:24:52736 if self._options.patch:
737 if self._options.profile:
738 self._RemoveBlanks(self._GetUnpatchedOrderfileFilename(),
739 self._GetPathToOrderfile())
740 try:
741 _UnstashOutputDirectory(self._uninstrumented_out_dir)
742 self._compiler = ClankCompiler(
743 self._uninstrumented_out_dir, self._step_recorder,
744 self._options.arch, self._options.jobs, self._options.max_load,
Matthew Cary78aae162018-08-10 17:16:30745 self._options.use_goma, self._options.goma_dir,
Matthew Caryd6bfcb72018-09-14 11:27:46746 self._options.system_health_orderfile, self._options.monochrome)
Benoit Lizea3fe2932017-10-20 10:24:52747 self._compiler.CompileLibchrome(False)
748 self._PatchOrderfile()
749 # Because identical code folding is a bit different with and without
750 # the orderfile build, we need to re-patch the orderfile with code
751 # folding as close to the final version as possible.
752 self._compiler.CompileLibchrome(False, force_relink=True)
753 self._PatchOrderfile()
754 self._compiler.CompileLibchrome(False, force_relink=True)
755 self._VerifySymbolOrder()
756 self._MaybeArchiveOrderfile(self._GetPathToOrderfile())
757 finally:
758 _StashOutputDirectory(self._uninstrumented_out_dir)
759 orderfile_uploaded = True
760
761 if (self._options.buildbot and self._options.netrc
762 and not self._step_recorder.ErrorRecorded()):
763 unpatched_orderfile_filename = (
764 self._GetUnpatchedOrderfileFilename() if profile_uploaded else None)
765 orderfile_filename = (
Benoit Lized913be82017-10-24 13:38:27766 self._GetPathToOrderfile() if orderfile_uploaded else None)
Benoit Lizea3fe2932017-10-20 10:24:52767 self._orderfile_updater.CommitFileHashes(
768 unpatched_orderfile_filename, orderfile_filename)
769
770 self._step_recorder.EndStep()
771 return not self._step_recorder.ErrorRecorded()
772
773 def GetReportingData(self):
774 """Get a dictionary of reporting data (timings, output hashes)"""
775 self._output_data['timings'] = self._step_recorder.timings
776 return self._output_data
777
778
Benoit Lizea1b64f82017-12-07 10:12:50779def CreateArgumentParser():
780 """Creates and returns the argument parser."""
781 parser = argparse.ArgumentParser()
782 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52783 '--buildbot', action='store_true',
784 help='If true, the script expects to be run on a buildbot')
Benoit Lizea1b64f82017-12-07 10:12:50785 parser.add_argument(
Matthew Cary453ff1452018-07-18 12:19:35786 '--device', default=None, type=str,
787 help='Device serial number on which to run profiling.')
788 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52789 '--verify', action='store_true',
790 help='If true, the script only verifies the current orderfile')
Benoit Lizea1b64f82017-12-07 10:12:50791 parser.add_argument('--target-arch', action='store', dest='arch',
792 default=cygprofile_utils.DetectArchitecture(),
793 choices=['arm', 'arm64', 'x86', 'x86_64', 'x64', 'mips'],
794 help='The target architecture for which to build')
795 parser.add_argument('--output-json', action='store', dest='json_file',
796 help='Location to save stats in json format')
797 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52798 '--skip-profile', action='store_false', dest='profile', default=True,
799 help='Don\'t generate a profile on the device. Only patch from the '
800 'existing profile.')
Benoit Lizea1b64f82017-12-07 10:12:50801 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52802 '--skip-patch', action='store_false', dest='patch', default=True,
803 help='Only generate the raw (unpatched) orderfile, don\'t patch it.')
Benoit Lizea1b64f82017-12-07 10:12:50804 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52805 '--netrc', action='store',
806 help='A custom .netrc file to use for git checkin. Only used on bots.')
Benoit Lizea1b64f82017-12-07 10:12:50807 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52808 '--branch', action='store', default='master',
809 help='When running on buildbot with a netrc, the branch orderfile '
810 'hashes get checked into.')
811 # Note: -j50 was causing issues on the bot.
Benoit Lizea1b64f82017-12-07 10:12:50812 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52813 '-j', '--jobs', action='store', default=20,
814 help='Number of jobs to use for compilation.')
Benoit Lizea1b64f82017-12-07 10:12:50815 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52816 '-l', '--max-load', action='store', default=4, help='Max cpu load.')
Benoit Lizea1b64f82017-12-07 10:12:50817 parser.add_argument('--goma-dir', help='GOMA directory.')
818 parser.add_argument(
Benoit Lizea3fe2932017-10-20 10:24:52819 '--use-goma', action='store_true', help='Enable GOMA.', default=False)
Benoit Lizea1b64f82017-12-07 10:12:50820 parser.add_argument('--adb-path', help='Path to the adb binary.')
Matthew Carye8400642018-06-14 15:43:02821
Matthew Cary03642a2a2018-10-15 09:08:08822 parser.add_argument('--system-health-orderfile', action='store_true',
823 dest='system_health_orderfile', default=False,
Matthew Caryc262f5d2018-09-19 14:36:28824 help=('Create an orderfile based on an about:blank '
825 'startup benchmark instead of system health '
826 'benchmarks.'))
Matthew Caryd6bfcb72018-09-14 11:27:46827 parser.add_argument('--monochrome', action='store_true',
828 help=('Compile and instrument monochrome (for post-N '
829 'devices).'))
Matthew Cary78aae162018-08-10 17:16:30830 parser.add_argument('--offsets-for-memory', action='store_true',
831 help=('Favor memory savings in the orderfile. Used '
832 'with --system-health-orderfile.'),
833 default=False)
834
Matthew Carye8400642018-06-14 15:43:02835 parser.add_argument('--manual-symbol-offsets', default=None, type=str,
836 help=('File of list of ordered symbol offsets generated '
837 'by manual profiling. Must set other --manual* '
838 'flags if this is used, and must --skip-profile.'))
839 parser.add_argument('--manual-libname', default=None, type=str,
840 help=('Library filename corresponding to '
841 '--manual-symbol-offsets.'))
842 parser.add_argument('--manual-objdir', default=None, type=str,
843 help=('Root of object file directory corresponding to '
844 '--manual-symbol-offsets.'))
Matthew Cary69e9e422018-08-10 18:30:06845 parser.add_argument('--pregenerated-profiles', default=None, type=str,
846 help=('Pregenerated profiles to use instead of running '
847 'profile step. Cannot be used with '
848 '--skip-profiles.'))
849 parser.add_argument('--profile-save-dir', default=None, type=str,
850 help=('Directory to save any profiles created. These can '
851 'be used with --pregenerated-profiles. Cannot be '
852 'used with --skip-profiles.'))
Matthew Carye8400642018-06-14 15:43:02853
Benoit Lizea1b64f82017-12-07 10:12:50854 profile_android_startup.AddProfileCollectionArguments(parser)
Benoit Lizea3fe2932017-10-20 10:24:52855 return parser
856
857
858def CreateOrderfile(options, orderfile_updater_class):
859 """Creates an oderfile.
860
861 Args:
862 options: As returned from optparse.OptionParser.parse_args()
863 orderfile_updater_class: (OrderfileUpdater) subclass of OrderfileUpdater.
864
865 Returns:
866 True iff success.
867 """
868 logging.basicConfig(level=logging.INFO)
869 devil_chromium.Initialize(adb_path=options.adb_path)
870
Egor Pasko93e514e2017-10-31 13:32:36871 generator = OrderfileGenerator(options, orderfile_updater_class)
Benoit Lizea3fe2932017-10-20 10:24:52872 try:
873 if options.verify:
Egor Pasko93e514e2017-10-31 13:32:36874 generator._VerifySymbolOrder()
Benoit Lizea3fe2932017-10-20 10:24:52875 else:
Egor Pasko93e514e2017-10-31 13:32:36876 return generator.Generate()
Benoit Lizea3fe2932017-10-20 10:24:52877 finally:
Egor Pasko93e514e2017-10-31 13:32:36878 json_output = json.dumps(generator.GetReportingData(),
Benoit Lizea3fe2932017-10-20 10:24:52879 indent=2) + '\n'
880 if options.json_file:
881 with open(options.json_file, 'w') as f:
882 f.write(json_output)
883 print json_output
884 return False
885
886
Benoit Lizea1b64f82017-12-07 10:12:50887def main():
888 parser = CreateArgumentParser()
889 options = parser.parse_args()
Benoit Lizea3fe2932017-10-20 10:24:52890 return 0 if CreateOrderfile(options, OrderfileUpdater) else 1
891
892
893if __name__ == '__main__':
Benoit Lizea1b64f82017-12-07 10:12:50894 sys.exit(main())