#!/usr/bin/env python # Copyright 2020 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Downloads pgo profiles for optimizing official Chrome. This script has the following responsibilities: 1. Download a requested profile if necessary. 2. Return a path to the current profile to feed to the build system. 3. Removed stale profiles (2 days) to save disk spaces because profiles are large (~1GB) and updated frequently (~4 times a day). """ from __future__ import print_function import argparse import os import sys import time _SRC_ROOT = os.path.abspath( os.path.join(os.path.dirname(__file__), os.path.pardir)) sys.path.append(os.path.join(_SRC_ROOT, 'third_party', 'depot_tools')) import download_from_google_storage sys.path.append(os.path.join(_SRC_ROOT, 'build')) import gn_helpers # Absolute path to the directory that stores pgo related state files, which # specifcies which profile to update and use. _PGO_DIR = os.path.join(_SRC_ROOT, 'chrome', 'build') # Absolute path to the directory that stores pgo profiles. _PGO_PROFILE_DIR = os.path.join(_PGO_DIR, 'pgo_profiles') def _read_profile_name(target): """Read profile name given a target. Args: target(str): The target name, such as win32, mac. Returns: Name of the profile to update and use, such as: chrome-win32-master-67ad3c89d2017131cc9ce664a1580315517550d1.profdata. """ state_file = os.path.join(_PGO_DIR, '%s.pgo.txt' % target) with open(state_file, 'r') as f: profile_name = f.read().strip() return profile_name def _remove_unused_profiles(current_profile_name): """Removes unused profiles, except the current one, to save disk space.""" days = 2 expiration_duration = 60 * 60 * 24 * days for f in os.listdir(_PGO_PROFILE_DIR): if f == current_profile_name: continue p = os.path.join(_PGO_PROFILE_DIR, f) age = time.time() - os.path.getmtime(p) if age > expiration_duration: print('Removing profile %s as it hasn\'t been used in the past %d days' % (p, days)) os.remove(p) def _update(args): """Update profile if necessary according to the state file. Args: args(dict): A dict of cmd arguments, such as target and gs_url_base. Raises: RuntimeError: If failed to download profiles from gcs. """ profile_name = _read_profile_name(args.target) profile_path = os.path.join(_PGO_PROFILE_DIR, profile_name) if os.path.isfile(profile_path): os.utime(profile_path, None) return gsutil = download_from_google_storage.Gsutil( download_from_google_storage.GSUTIL_DEFAULT_PATH) gs_path = 'gs://' + args.gs_url_base.strip('/') + '/' + profile_name code = gsutil.call('cp', gs_path, profile_path) if code != 0: raise RuntimeError('gsutil failed to download "%s"' % gs_path) _remove_unused_profiles(profile_name) def _get_profile_path(args): """Returns an absolute path to the current profile. Args: args(dict): A dict of cmd arguments, such as target and gs_url_base. Raises: RuntimeError: If the current profile is missing. """ profile_path = os.path.join(_PGO_PROFILE_DIR, _read_profile_name(args.target)) if not os.path.isfile(profile_path): raise RuntimeError( 'requested profile "%s" doesn\'t exist, please make sure ' '"checkout_pgo_profiles" is set to True in the "custom_vars" section ' 'of your .gclient file, e.g.: \n' 'solutions = [ \n' ' { \n' ' "name": "src", \n' ' # ... \n' ' "custom_vars": { \n' ' "checkout_pgo_profiles": True, \n' ' }, \n' ' }, \n' '], \n' 'and then run "gclient runhooks" to download it. You can also simply ' 'disable the PGO optimizations by setting |chrome_pgo_phase = 0| in ' 'your GN arguments.'% profile_path) os.utime(profile_path, None) profile_path.rstrip(os.sep) print(gn_helpers.ToGNString(profile_path)) def main(): parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( '--target', required=True, choices=['win32', 'win64', 'mac', 'mac-arm', 'linux'], help='Identifier of a specific target platform + architecture.') subparsers = parser.add_subparsers() parser_update = subparsers.add_parser('update') parser_update.add_argument( '--gs-url-base', required=True, help='The base GS URL to search for the profile.') parser_update.set_defaults(func=_update) parser_get_profile_path = subparsers.add_parser('get_profile_path') parser_get_profile_path.set_defaults(func=_get_profile_path) args = parser.parse_args() return args.func(args) if __name__ == '__main__': sys.exit(main())