Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / cbuildbot / afdo.py
1 # Copyright 2014 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Module containing the various utilities to build Chrome with AFDO.
6
7 For a description of AFDO see gcc.gnu.org/wiki/AutoFDO.
8 """
9
10 import datetime
11 import os
12 import re
13
14 from chromite.cbuildbot import failures_lib
15 from chromite.cbuildbot import constants
16 from chromite.lib import cros_build_lib
17 from chromite.lib import git
18 from chromite.lib import gs
19 from chromite.lib import osutils
20 from chromite.lib import timeout_util
21
22
23 # AFDO-specific constants.
24 # Chrome URL where AFDO data is stored.
25 AFDO_PROD_URL = 'gs://chromeos-prebuilt/afdo-job/canonicals/'
26 AFDO_TEST_URL = '%s/afdo-job/canonicals/' % constants.TRASH_BUCKET
27 AFDO_BASE_URL = AFDO_PROD_URL
28 AFDO_CHROOT_ROOT = os.path.join('%(build_root)s', constants.DEFAULT_CHROOT_DIR)
29 AFDO_LOCAL_DIR = os.path.join('%(root)s', 'tmp')
30 AFDO_BUILDROOT_LOCAL = AFDO_LOCAL_DIR % {'root': AFDO_CHROOT_ROOT}
31 CHROME_ARCH_VERSION = '%(package)s-%(arch)s-%(version)s'
32 CHROME_PERF_AFDO_FILE = '%s.perf.data' % CHROME_ARCH_VERSION
33 CHROME_PERF_AFDO_URL = '%s%s.bz2' % (AFDO_BASE_URL, CHROME_PERF_AFDO_FILE)
34 CHROME_AFDO_FILE = '%s.afdo' % CHROME_ARCH_VERSION
35 CHROME_AFDO_URL = '%s%s.bz2' % (AFDO_BASE_URL, CHROME_AFDO_FILE)
36 CHROME_ARCH_RELEASE = '%(package)s-%(arch)s-%(release)s'
37 LATEST_CHROME_AFDO_FILE = 'latest-%s.afdo' % CHROME_ARCH_RELEASE
38 LATEST_CHROME_AFDO_URL = AFDO_BASE_URL + LATEST_CHROME_AFDO_FILE
39 CHROME_DEBUG_BIN = os.path.join('%(root)s',
40                                 'build/%(board)s/usr/lib/debug',
41                                 'opt/google/chrome/chrome.debug')
42 CHROME_DEBUG_BIN_URL = '%s%s.debug.bz2' % (AFDO_BASE_URL, CHROME_ARCH_VERSION)
43
44 AFDO_GENERATE_GCOV_TOOL = '/usr/bin/create_gcov'
45
46 # regex to find AFDO file for specific architecture within the ebuild file.
47 CHROME_EBUILD_AFDO_EXP = r'^(?P<bef>AFDO_FILE\["%s"\]=")(?P<name>.*)(?P<aft>")'
48 # and corresponding replacement string.
49 CHROME_EBUILD_AFDO_REPL = r'\g<bef>%s\g<aft>'
50
51 # How old can the AFDO data be? (in days).
52 AFDO_ALLOWED_STALE = 7
53
54 # TODO(llozano): Figure out exact list of boards/architectures that
55 # can generate 'perf' suitable for AFDO (with LBR events).
56 # Set of boards that can generate the AFDO profile (can generate 'perf'
57 # data with LBR events).
58 AFDO_DATA_GENERATORS = ('lumpy',)
59 # Set of boards that are allowed to update the Chrome ebuild with the
60 # appropriate AFDO file name.
61 # TODO(llozano): is it possible that the canary build for "falco" be
62 # executed before the PFQ for lumpy?. Is there a problem with this?
63 AFDO_EBUILD_UPDATERS = ('lumpy', 'daisy', 'x86-alex')
64
65 # For a given architecture, which architecture is used to generate
66 # the AFDO profile. Some architectures are not able to generate their
67 # own profile.
68 AFDO_ARCH_GENERATORS = {'amd64': 'amd64',
69                         'arm': 'amd64',
70                         'x86': 'amd64'}
71
72 AFDO_ALERT_RECIPIENTS = ['chromeos-toolchain@google.com']
73
74
75 class MissingAFDOData(failures_lib.StepFailure):
76   """Exception thrown when necessary AFDO data is missing."""
77
78
79 class MissingAFDOMarkers(failures_lib.StepFailure):
80   """Exception thrown when necessary ebuild markers for AFDO are missing."""
81
82
83 def CompressAFDOFile(to_compress, buildroot):
84   """Compress file used by AFDO process.
85
86   Args:
87     to_compress: File to compress.
88     buildroot: buildroot where to store the compressed data.
89
90   Returns:
91     Name of the compressed data file.
92   """
93   local_dir = AFDO_BUILDROOT_LOCAL % {'build_root': buildroot}
94   dest = os.path.join(local_dir, os.path.basename(to_compress)) + '.bz2'
95   cros_build_lib.CompressFile(to_compress, dest)
96   return dest
97
98
99 def UncompressAFDOFile(to_decompress, buildroot):
100   """Decompress file used by AFDO process.
101
102   Args:
103     to_decompress: File to decompress.
104     buildroot: buildroot where to store the decompressed data.
105   """
106   local_dir = AFDO_BUILDROOT_LOCAL % {'build_root': buildroot}
107   basename = os.path.basename(to_decompress)
108   dest_basename = basename.rsplit('.', 1)[0]
109   dest = os.path.join(local_dir, dest_basename)
110   cros_build_lib.UncompressFile(to_decompress, dest)
111   return dest
112
113
114 def GSUploadIfNotPresent(gs_context, src, dest):
115   """Upload a file to GS only if the file does not exist.
116
117   Will not generate an error if the file already exist in GS. It will
118   only emit a warning.
119
120   I could use GSContext.Copy(src,dest,version=0) here but it does not seem
121   to work for large files. Using GSContext.Exists(dest) instead. See
122   crbug.com/395858.
123
124   Args:
125     gs_context: GS context instance.
126     src: File to copy.
127     dest: Destination location.
128
129   Returns:
130     True if file was uploaded. False otherwise.
131   """
132   if gs_context.Exists(dest):
133     cros_build_lib.Warning('File %s already in GS', dest)
134     return False
135   else:
136     gs_context.Copy(src, dest, acl='public-read')
137     return True
138
139
140 def CheckAFDOPerfData(arch, cpv, buildroot, gs_context):
141   """Check whether AFDO perf data exists for the given architecture.
142
143   Check if 'perf' data file for this architecture and release is available
144   in GS. If so, copy it into a temp directory in the buildroot.
145
146   Args:
147     arch: architecture we're going to build Chrome for.
148     cpv: The portage_utilities.CPV object for chromeos-chrome.
149     buildroot: buildroot where AFDO data should be stored.
150     gs_context: GS context to retrieve data.
151
152   Returns:
153     True if AFDO perf data is available. False otherwise.
154   """
155   # The file name of the perf data is based only in the chrome version.
156   # The test case that produces it does not know anything about the
157   # revision number.
158   # TODO(llozano): perf data filename should include the revision number.
159   version_number = cpv.version_no_rev.split('_')[0]
160   chrome_spec = {'package': cpv.package,
161                  'arch': arch,
162                  'version': version_number}
163   url = CHROME_PERF_AFDO_URL % chrome_spec
164   if not gs_context.Exists(url):
165     cros_build_lib.Info('Could not find AFDO perf data')
166     return False
167   dest_dir = AFDO_BUILDROOT_LOCAL % {'build_root': buildroot}
168   dest_path = os.path.join(dest_dir, url.rsplit('/', 1)[1])
169   gs_context.Copy(url, dest_path)
170
171   UncompressAFDOFile(dest_path, buildroot)
172   cros_build_lib.Info('Found and retrieved AFDO perf data')
173   return True
174
175
176 def WaitForAFDOPerfData(cpv, arch, buildroot, gs_context,
177                         timeout=constants.AFDO_GENERATE_TIMEOUT):
178   """Wait for AFDO perf data to show up (with an appropriate timeout).
179
180   Args:
181     arch: architecture we're going to build Chrome for.
182     cpv: CPV object for Chrome.
183     buildroot: buildroot where AFDO data should be stored.
184     gs_context: GS context to retrieve data.
185     timeout: How long to wait total, in seconds.
186
187   Returns:
188     True if found the AFDO perf data before the timeout expired.
189     False otherwise.
190   """
191   try:
192     timeout_util.WaitForReturnTrue(
193         CheckAFDOPerfData,
194         func_args=(arch, cpv, buildroot, gs_context),
195         timeout=timeout, period=constants.SLEEP_TIMEOUT)
196   except timeout_util.TimeoutError:
197     return False
198   return True
199
200
201 def PatchChromeEbuildAFDOFile(ebuild_file, arch_profiles):
202   """Patch the Chrome ebuild with the dictionary of {arch: afdo_file} pairs.
203
204   Args:
205     ebuild_file: path of the ebuild file within the chroot.
206     arch_profiles: {arch: afdo_file} pairs to put into the ebuild.
207   """
208   original_ebuild = cros_build_lib.FromChrootPath(ebuild_file)
209   modified_ebuild = '%s.new' % original_ebuild
210
211   arch_patterns = {}
212   arch_repls = {}
213   arch_markers = {}
214   for arch in arch_profiles.keys():
215     arch_patterns[arch] = re.compile(CHROME_EBUILD_AFDO_EXP % arch)
216     arch_repls[arch] = CHROME_EBUILD_AFDO_REPL % arch_profiles[arch]
217     arch_markers[arch] = False
218
219   with open(original_ebuild, 'r') as original:
220     with open(modified_ebuild, 'w') as modified:
221       for line in original:
222         for arch in arch_profiles.keys():
223           matched = arch_patterns[arch].match(line)
224           if matched:
225             arch_markers[arch] = True
226             modified.write(arch_patterns[arch].sub(arch_repls[arch], line))
227             break
228         else: # line without markers, just copy it.
229           modified.write(line)
230
231   for arch, found in arch_markers.iteritems():
232     if not found:
233       raise MissingAFDOMarkers('Chrome ebuild file does not have appropriate '
234                                'AFDO markers for arch %s' % arch)
235
236   os.rename(modified_ebuild, original_ebuild)
237
238
239 def UpdateChromeEbuildAFDOFile(board, arch_profiles):
240   """Update chrome ebuild with the dictionary of {arch: afdo_file} pairs.
241
242   Modifies the Chrome ebuild to set the appropriate AFDO file for each
243   given architecture. Regenerates the associated Manifest file and
244   commits the new ebuild and Manifest.
245
246   Args:
247     board: board we are building Chrome for.
248     arch_profiles: {arch: afdo_file} pairs to put into the ebuild.
249   """
250   # Find the Chrome ebuild file.
251   equery_prog = 'equery'
252   ebuild_prog = 'ebuild'
253   if board:
254     equery_prog += '-%s' % board
255     ebuild_prog += '-%s' % board
256
257   equery_cmd = [equery_prog, 'w', 'chromeos-chrome']
258   ebuild_file = cros_build_lib.RunCommand(equery_cmd,
259                                           enter_chroot=True,
260                                           redirect_stdout=True).output.rstrip()
261
262   # Patch the ebuild file with the names of the available afdo_files.
263   PatchChromeEbuildAFDOFile(ebuild_file, arch_profiles)
264
265   # Also patch the 9999 ebuild. This is necessary because the uprev
266   # process starts from the 9999 ebuild file and then compares to the
267   # current version to see if the uprev is really necessary. We dont
268   # want the names of the available afdo_files to show as differences.
269   # It also allows developers to do USE=afdo_use when using the 9999
270   # ebuild.
271   ebuild_9999 = os.path.join(os.path.dirname(ebuild_file),
272                              'chromeos-chrome-9999.ebuild')
273   PatchChromeEbuildAFDOFile(ebuild_9999, arch_profiles)
274
275   # Regenerate the Manifest file.
276   ebuild_gs_dir = None
277   # If using the GS test location, pass this location to the
278   # chrome ebuild.
279   if AFDO_BASE_URL == AFDO_TEST_URL:
280     ebuild_gs_dir = {'AFDO_GS_DIRECTORY': AFDO_TEST_URL}
281   gen_manifest_cmd = [ebuild_prog,  ebuild_file, 'manifest', '--force']
282   cros_build_lib.RunCommand(gen_manifest_cmd, enter_chroot=True,
283                             extra_env=ebuild_gs_dir, print_cmd=True)
284
285   ebuild_dir = cros_build_lib.FromChrootPath(os.path.dirname(ebuild_file))
286   git.RunGit(ebuild_dir, ['add', 'Manifest'])
287
288   # Check if anything changed compared to the previous version.
289   mod_files = ['Manifest', os.path.basename(ebuild_file),
290                os.path.basename(ebuild_9999)]
291   modifications = git.RunGit(ebuild_dir,
292                              ['status', '--porcelain', '--'] + mod_files,
293                              capture_output=True, print_cmd=True).output
294   if not modifications:
295     cros_build_lib.Info('AFDO info for the Chrome ebuild did not change. '
296                         'Nothing to commit')
297     return
298
299   # If there are changes to ebuild or Manifest, commit them.
300   commit_msg = ('"Set {arch: afdo_file} pairs %s and updated Manifest"'
301                 % arch_profiles)
302   git.RunGit(ebuild_dir,
303              ['commit', '-m', commit_msg, '--'] + mod_files,
304              print_cmd=True)
305
306
307 def VerifyLatestAFDOFile(afdo_release_spec, buildroot, gs_context):
308   """Verify that the latest AFDO profile for a release is suitable.
309
310   Find the latest AFDO profile file for a particular release and check
311   that it is not too stale. The latest AFDO profile name for a release
312   can be found in a file in GS under the name
313   latest-chrome-<arch>-<release>.afdo.
314
315   Args:
316     afdo_release_spec: architecture and release to find the latest AFDO
317         profile for.
318     buildroot: buildroot where AFDO data should be stored.
319     gs_context: GS context to retrieve data.
320
321   Returns:
322     The name of the AFDO profile file if a suitable one was found.
323     None otherwise.
324   """
325   latest_afdo_url = LATEST_CHROME_AFDO_URL % afdo_release_spec
326
327   # Check if latest-chrome-<arch>-<release>.afdo exists.
328   latest_detail = None
329   if gs_context.Exists(latest_afdo_url):
330     latest_detail = gs_context.LSWithDetails(latest_afdo_url)
331   if not latest_detail:
332     cros_build_lib.Info('Could not find latest AFDO info file %s' %
333                         latest_afdo_url)
334     return None
335
336   # Verify the AFDO profile file is not too stale.
337   mod_date = latest_detail[0][2]
338   curr_date = datetime.datetime.now()
339   allowed_stale_days = datetime.timedelta(days=AFDO_ALLOWED_STALE)
340   if (curr_date - mod_date) > allowed_stale_days:
341     cros_build_lib.Info('Found latest AFDO info file %s but it is too old' %
342                         latest_afdo_url)
343     return None
344
345   # Then get the name of the latest valid AFDO profile file.
346   local_dir = AFDO_BUILDROOT_LOCAL % {'build_root': buildroot }
347   latest_afdo_file = LATEST_CHROME_AFDO_FILE % afdo_release_spec
348   latest_afdo_path = os.path.join(local_dir, latest_afdo_file)
349   gs_context.Copy(latest_afdo_url, latest_afdo_path)
350
351   return osutils.ReadFile(latest_afdo_path).strip()
352
353
354 def GetLatestAFDOFile(cpv, arch, buildroot, gs_context):
355   """Try to find the latest suitable AFDO profile file.
356
357   Try to find the latest AFDO profile generated for current release
358   and architecture. If there is none, check the previous release (mostly
359   in case we have just branched).
360
361   Args:
362     cpv: cpv object for Chrome.
363     arch: architecture for which we are looking for AFDO profile.
364     buildroot: buildroot where AFDO data should be stored.
365     gs_context: GS context to retrieve data.
366
367   Returns:
368     Name of latest suitable AFDO profile file if one is found.
369     None otherwise.
370   """
371   generator_arch = AFDO_ARCH_GENERATORS[arch]
372   version_number = cpv.version
373   current_release = version_number.split('.')[0]
374   afdo_release_spec = {'package': cpv.package,
375                        'arch': generator_arch,
376                        'release': current_release}
377   afdo_file = VerifyLatestAFDOFile(afdo_release_spec, buildroot, gs_context)
378   if afdo_file:
379     return afdo_file
380
381   # Could not find suitable AFDO file for the current release.
382   # Let's see if there is one from the previous release.
383   previous_release = str(int(current_release) - 1)
384   prev_release_spec = {'package': cpv.package,
385                        'arch': generator_arch,
386                        'release': previous_release}
387   return VerifyLatestAFDOFile(prev_release_spec, buildroot, gs_context)
388
389
390 def GenerateAFDOData(cpv, arch, board, buildroot, gs_context):
391   """Generate AFDO profile data from 'perf' data.
392
393   Given the 'perf' profile, generate an AFDO profile using create_gcov.
394   It also creates a latest-chrome-<arch>-<release>.afdo file pointing
395   to the generated AFDO profile.
396   Uploads the generated data to GS for retrieval by the chrome ebuild
397   file when doing an 'afdo_use' build.
398   It is possible the generated data has previously been uploaded to GS
399   in which case this routine will not upload the data again. Uploading
400   again may cause verication failures for the ebuild file referencing
401   the previous contents of the data.
402
403   Args:
404     cpv: cpv object for Chrome.
405     arch: architecture for which we are looking for AFDO profile.
406     board: board we are building for.
407     buildroot: buildroot where AFDO data should be stored.
408     gs_context: GS context to retrieve/store data.
409
410   Returns:
411     Name of the AFDO profile file generated if successful.
412   """
413   CHROME_UNSTRIPPED_NAME = 'chrome.unstripped'
414
415   version_number = cpv.version
416   afdo_spec = {'package': cpv.package,
417                'arch': arch,
418                'version': version_number}
419   chroot_root = AFDO_CHROOT_ROOT % {'build_root': buildroot }
420   local_dir = AFDO_LOCAL_DIR % {'root': chroot_root }
421   in_chroot_local_dir = AFDO_LOCAL_DIR % {'root': '' }
422
423   # Upload compressed chrome debug binary to GS for triaging purposes.
424   # TODO(llozano): This simplifies things in case of need of triaging
425   # problems but is it really necessary?
426   debug_bin = CHROME_DEBUG_BIN % {'root': chroot_root,
427                                   'board': board }
428   comp_debug_bin_path = CompressAFDOFile(debug_bin, buildroot)
429   GSUploadIfNotPresent(gs_context, comp_debug_bin_path,
430                        CHROME_DEBUG_BIN_URL % afdo_spec)
431
432   # create_gcov demands the name of the profiled binary exactly matches
433   # the name of the unstripped binary or it is named 'chrome.unstripped'.
434   # So create a symbolic link with the appropriate name.
435   local_debug_sym = os.path.join(local_dir, CHROME_UNSTRIPPED_NAME)
436   in_chroot_debug_bin = CHROME_DEBUG_BIN % {'root': '', 'board': board }
437   osutils.SafeUnlink(local_debug_sym)
438   os.symlink(in_chroot_debug_bin, local_debug_sym)
439
440   # Call create_gcov tool to generated AFDO profile from 'perf' profile
441   # and upload it to GS. Need to call from within chroot since this tool
442   # was built inside chroot.
443   debug_sym = os.path.join(in_chroot_local_dir, CHROME_UNSTRIPPED_NAME)
444   # The name of the 'perf' file is based only on the version of chrome. The
445   # revision number is not included.
446   afdo_spec_no_rev = {'package': cpv.package,
447                       'arch': arch,
448                       'version': cpv.version_no_rev.split('_')[0]}
449   perf_afdo_file = CHROME_PERF_AFDO_FILE % afdo_spec_no_rev
450   perf_afdo_path = os.path.join(in_chroot_local_dir, perf_afdo_file)
451   afdo_file = CHROME_AFDO_FILE % afdo_spec
452   afdo_path = os.path.join(in_chroot_local_dir, afdo_file)
453   afdo_cmd = [AFDO_GENERATE_GCOV_TOOL,
454               '--binary=%s' % debug_sym,
455               '--profile=%s' % perf_afdo_path,
456               '--gcov=%s' % afdo_path]
457   cros_build_lib.RunCommand(afdo_cmd, enter_chroot=True, capture_output=True,
458                             print_cmd=True)
459
460   afdo_local_path = os.path.join(local_dir, afdo_file)
461   comp_afdo_path = CompressAFDOFile(afdo_local_path, buildroot)
462   uploaded_afdo_file = GSUploadIfNotPresent(gs_context, comp_afdo_path,
463                                             CHROME_AFDO_URL % afdo_spec)
464
465   if uploaded_afdo_file:
466     # Create latest-chrome-<arch>-<release>.afdo pointing to the name
467     # of the AFDO profile file and upload to GS.
468     current_release = version_number.split('.')[0]
469     afdo_release_spec = {'package': cpv.package,
470                          'arch': arch,
471                          'release': current_release}
472     latest_afdo_file = LATEST_CHROME_AFDO_FILE % afdo_release_spec
473     latest_afdo_path = os.path.join(local_dir, latest_afdo_file)
474     osutils.WriteFile(latest_afdo_path, afdo_file)
475     gs_context.Copy(latest_afdo_path,
476                     LATEST_CHROME_AFDO_URL % afdo_release_spec,
477                     acl='public-read')
478
479   return afdo_file
480
481
482 def CanGenerateAFDOData(board):
483   """Does this board has the capability of generating its own AFDO data?."""
484   return board in AFDO_DATA_GENERATORS
485
486
487 def CanUpdateEbuildAFDOData(board):
488   """Is this board one of the designated to update the chrome ebuild?."""
489   return board in AFDO_EBUILD_UPDATERS
490
491
492 def GenerateOrFindAFDOData(cpv, arch, board, buildroot):
493   """Generate or find the appropriate AFDO profile for the given architecture.
494
495   For the architectures that can generate a 'perf' tool profile, wait for
496   it to be generated by the autotest AFDO_generate and generate an AFDO
497   profile for it. In the generation of the 'perf' profile failed, use
498   a previously generated AFDO profile that is not too stale.
499   For the architectures that cannot generate a 'perf' tool profile, use
500   the AFDO profile from another architecture (e.g.: ARM can use a profile
501   from AMD64).
502   Once we have an adequate AFDO profile, put this information in the
503   chrome ebuild.
504
505   Args:
506     cpv: cpv object for Chrome.
507     arch: architecture for which we are looking for AFDO profile.
508     board: board we are building for.
509     buildroot: buildroot where AFDO data should be stored.
510   """
511   if not CanUpdateEbuildAFDOData(board):
512     return
513
514   gs_context = gs.GSContext()
515   afdo_file = None
516   if CanGenerateAFDOData(board):
517     # Generation of AFDO could fail for different reasons. Try to be
518     # resilient about this and in case of failure just find an
519     # older AFDO profile.
520     try:
521       if WaitForAFDOPerfData(cpv, arch, buildroot, gs_context):
522         afdo_file = GenerateAFDOData(cpv, arch, board, buildroot, gs_context)
523         assert afdo_file
524         cros_build_lib.Info('Generated %s AFDO profile %s',
525                             arch, afdo_file)
526     # Will let system-exiting exceptions through.
527     except Exception:
528       cros_build_lib.PrintBuildbotStepWarnings()
529       cros_build_lib.Warning('AFDO profile generation failed with exception ',
530                              exc_info=True)
531   if not afdo_file:
532     cros_build_lib.Info('Trying to find previous appropriate AFDO profile')
533     afdo_file = GetLatestAFDOFile(cpv, arch, buildroot, gs_context)
534     if afdo_file:
535       cros_build_lib.Info('Found previous %s AFDO profile %s',
536                           arch, afdo_file)
537   if not afdo_file:
538     raise MissingAFDOData('Could not generate or find appropriate AFDO profile')
539
540   # We found an AFDO profile. Lets put the info in the chrome ebuild.
541   UpdateChromeEbuildAFDOFile(board, {arch: afdo_file})