mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 09:27:09 +00:00

PR-URL: https://github.com/nodejs/node/pull/54077 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
211 lines
6.0 KiB
Python
Executable File
211 lines
6.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright 2023 the V8 project authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can
|
|
# be found in the LICENSE file.
|
|
"""
|
|
Download PGO profiles for V8 builtins. The version is pulled from V8's version
|
|
file (include/v8-version.h).
|
|
|
|
See argparse documentation for usage details.
|
|
"""
|
|
|
|
import argparse
|
|
import contextlib
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
|
|
FILENAME = os.path.basename(__file__)
|
|
PGO_PROFILE_BUCKET = 'chromium-v8-builtins-pgo'
|
|
PGO_PROFILE_DIR = pathlib.Path(os.path.dirname(__file__)) / 'profiles'
|
|
|
|
V8_DIR = PGO_PROFILE_DIR.parents[2]
|
|
DEPOT_TOOLS_DEFAULT_PATH = os.path.join(V8_DIR, 'third_party', 'depot_tools')
|
|
VERSION_FILE = V8_DIR / 'include' / 'v8-version.h'
|
|
VERSION_RE = r"""#define V8_MAJOR_VERSION (\d+)
|
|
#define V8_MINOR_VERSION (\d+)
|
|
#define V8_BUILD_NUMBER (\d+)
|
|
#define V8_PATCH_LEVEL (\d+)"""
|
|
|
|
|
|
def main(cmd_args=None):
|
|
args = parse_args(cmd_args)
|
|
import_gsutil(args)
|
|
version = retrieve_version(args)
|
|
perform_action(version, args)
|
|
sys.exit(0)
|
|
|
|
|
|
def parse_args(cmd_args):
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
f'Download PGO profiles for V8 builtins generated for the version '
|
|
f'defined in {VERSION_FILE}. If the current checkout has no version '
|
|
f'(i.e. build and patch level are 0 in {VERSION_FILE}), no profiles '
|
|
f'exist and the script returns without errors.'),
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog='\n'.join([
|
|
f'examples:', f' {FILENAME} download',
|
|
f' {FILENAME} validate --bucket=chromium-v8-builtins-pgo-staging',
|
|
f'', f'return codes:',
|
|
f' 0 - profiles successfully downloaded or validated',
|
|
f' 1 - unexpected error, see stdout',
|
|
f' 2 - invalid arguments specified, see {FILENAME} --help',
|
|
f' 3 - invalid path to depot_tools provided'
|
|
f' 4 - gsutil was unable to retrieve data from the bucket'
|
|
]),
|
|
)
|
|
|
|
parser.add_argument(
|
|
'action',
|
|
choices=['download', 'validate'],
|
|
help=(
|
|
'download or validate profiles for the currently checked out version'
|
|
),
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--version',
|
|
help=('download (or validate) profiles for this version (e.g. 11.0.226.0 '
|
|
'or 11.0.226.2), defaults to the version in v8\'s version file'),
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--depot-tools',
|
|
help=('path to depot tools, defaults to V8\'s version in '
|
|
f'{DEPOT_TOOLS_DEFAULT_PATH}.'),
|
|
type=pathlib.Path,
|
|
default=DEPOT_TOOLS_DEFAULT_PATH,
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--force',
|
|
help=('force download, overwriting existing profiles'),
|
|
action='store_true',
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--quiet',
|
|
help=('run silently, still display errors'),
|
|
action='store_true',
|
|
)
|
|
|
|
return parser.parse_args(cmd_args)
|
|
|
|
|
|
def import_gsutil(args):
|
|
abs_depot_tools_path = os.path.abspath(args.depot_tools)
|
|
file = os.path.join(abs_depot_tools_path, 'download_from_google_storage.py')
|
|
if not pathlib.Path(file).is_file():
|
|
print(f'{file} does not exist; check --depot-tools path.', file=sys.stderr)
|
|
sys.exit(3)
|
|
|
|
# Put this path at the beginning of the PATH to give it priority.
|
|
sys.path.insert(0, abs_depot_tools_path)
|
|
globals()['gcs_download'] = __import__('download_from_google_storage')
|
|
|
|
|
|
def retrieve_version(args):
|
|
if args.version:
|
|
return args.version
|
|
|
|
with open(VERSION_FILE) as f:
|
|
version_tuple = re.search(VERSION_RE, f.read()).groups(0)
|
|
|
|
version = '.'.join(version_tuple)
|
|
if version_tuple[2] == version_tuple[3] == '0':
|
|
log(args, f'The version file specifies {version}, which has no profiles.')
|
|
sys.exit(0)
|
|
|
|
return version
|
|
|
|
|
|
def download_profiles(version_path, requested_version, args):
|
|
if args.force:
|
|
return True
|
|
|
|
if not version_path.is_file():
|
|
return True
|
|
|
|
with open(version_path) as version_file:
|
|
profiles_version = version_file.read()
|
|
|
|
if profiles_version != requested_version:
|
|
return True
|
|
|
|
log(args, 'Profiles already downloaded, use --force to overwrite.')
|
|
return False
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def ensure_profiles(version, args):
|
|
version_path = PGO_PROFILE_DIR / 'profiles_version'
|
|
require_profiles = download_profiles(version_path, version, args)
|
|
yield require_profiles
|
|
if require_profiles:
|
|
with open(version_path, 'w') as version_file:
|
|
version_file.write(version)
|
|
|
|
|
|
def perform_action(version, args):
|
|
path = f'{PGO_PROFILE_BUCKET}/by-version/{version}'
|
|
|
|
if args.action == 'download':
|
|
with ensure_profiles(version, args) as require_profiles:
|
|
if not require_profiles:
|
|
return
|
|
|
|
cmd = ['cp', '-R', f'gs://{path}/*.profile', str(PGO_PROFILE_DIR)]
|
|
failure_hint = f'https://storage.googleapis.com/{path} does not exist.'
|
|
call_gsutil(cmd, failure_hint)
|
|
return
|
|
|
|
if args.action == 'validate':
|
|
meta_json = f'{path}/meta.json'
|
|
cmd = ['stat', f'gs://{meta_json}']
|
|
failure_hint = f'https://storage.googleapis.com/{meta_json} does not exist.'
|
|
call_gsutil(cmd, failure_hint)
|
|
return
|
|
|
|
raise AssertionError(f'Invalid action: {args.action}')
|
|
|
|
|
|
def call_gsutil(cmd, failure_hint):
|
|
# Load gsutil from depot tools, and execute command
|
|
gsutil = gcs_download.Gsutil(gcs_download.GSUTIL_DEFAULT_PATH)
|
|
returncode, stdout, stderr = gsutil.check_call(*cmd)
|
|
if returncode != 0:
|
|
print_error(['gsutil', *cmd], returncode, stdout, stderr, failure_hint)
|
|
sys.exit(4)
|
|
|
|
|
|
def print_error(cmd, returncode, stdout, stderr, failure_hint):
|
|
message = [
|
|
'The following command did not succeed:',
|
|
f' $ {" ".join(cmd)}',
|
|
]
|
|
sections = [
|
|
('return code', str(returncode)),
|
|
('stdout', stdout.strip()),
|
|
('stderr', stderr.strip()),
|
|
('hint', failure_hint),
|
|
]
|
|
for label, output in sections:
|
|
if not output:
|
|
continue
|
|
message += [f'{label}:', " " + "\n ".join(output.split("\n"))]
|
|
|
|
print('\n'.join(message), file=sys.stderr)
|
|
|
|
|
|
def log(args, message):
|
|
if args.quiet:
|
|
return
|
|
print(message)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|