diff --git a/DEPS b/DEPS index d097de219d0fb0..b47aeef1e66c5e 100644 --- a/DEPS +++ b/DEPS @@ -1958,6 +1958,76 @@ deps = { 'src/third_party/perfetto': Var('android_git') + '/platform/external/perfetto.git' + '@' + '1f234238909f513ff6c8c5c6ff08723b4795618f', + 'src/base/tracing/test/data': { + 'bucket': 'perfetto', + 'objects': [ + { + 'object_name': 'test_data/chrome_fcp_lcp_navigations.pftrace-ae01d849fbd75a98be1b7ddd5a8873217c377b393a1d5bbd788ed3364f7fefc3', + 'sha256sum': 'ae01d849fbd75a98be1b7ddd5a8873217c377b393a1d5bbd788ed3364f7fefc3', + 'size_bytes': 2398645, + 'generation': 1697714434866488, + 'output_file': 'chrome_fcp_lcp_navigations.pftrace' + }, + { + 'object_name': 'test_data/chrome_input_with_frame_view.pftrace-a93548822e481508c728ccc5da3ad34afcd0aec02ca7a7a4dad84ff340ee5975', + 'sha256sum': 'a93548822e481508c728ccc5da3ad34afcd0aec02ca7a7a4dad84ff340ee5975', + 'size_bytes': 6392331, + 'generation': 1711402389089075, + 'output_file': 'chrome_input_with_frame_view.pftrace' + }, + { + 'object_name': 'test_data/scroll_offsets_trace_2.pftrace-2ddd9f78d91d51e39c72c520bb54fdc9dbf1333ae722e87633fc345159296289', + 'sha256sum': '2ddd9f78d91d51e39c72c520bb54fdc9dbf1333ae722e87633fc345159296289', + 'size_bytes': 1496388, + 'generation': 1712592637141461, + 'output_file': 'scroll_offsets_trace_2.pftrace' + }, + { + 'object_name': 'test_data/top_level_java_choreographer_slices-8001e73b2458e94f65a606bb558a645ba5bca553b57fe416001f6c2175675a8a', + 'sha256sum': '8001e73b2458e94f65a606bb558a645ba5bca553b57fe416001f6c2175675a8a', + 'size_bytes': 5323017, + 'generation': 1671708979893186, + 'output_file': 'top_level_java_choreographer_slices' + }, + { + 'object_name': 'test_data/chrome_page_load_all_categories_not_extended.pftrace.gz-6586e9e2bbc0c996caddb321a0374328654983733e6ffd7f4635ac07db32a493', + 'sha256sum': '6586e9e2bbc0c996caddb321a0374328654983733e6ffd7f4635ac07db32a493', + 'size_bytes': 1277750, + 'generation': 1652442088902445, + 'output_file': 'chrome_page_load_all_categories_not_extended.pftrace.gz' + }, + { + 'object_name': 'test_data/speedometer.perfetto_trace.gz-8a159b354d74a3ca0d38ce9cd071ef47de322db4261ee266bfafe04d70310529', + 'sha256sum': '8a159b354d74a3ca0d38ce9cd071ef47de322db4261ee266bfafe04d70310529', + 'size_bytes': 891088, + 'generation': 1684336047660953, + 'output_file': 'speedometer.perfetto_trace.gz' + }, + { + 'object_name': 'test_data/cpu_powerups_1.pb-70f5511ba0cd6ce1359e3cadb4d1d9301fb6e26be85158e3384b06f41418d386', + 'sha256sum': '70f5511ba0cd6ce1359e3cadb4d1d9301fb6e26be85158e3384b06f41418d386', + 'size_bytes': 2033064, + 'generation': 1669652389509708, + 'output_file': 'cpu_powerups_1.pb' + }, + { + 'object_name': 'test_data/chrome_custom_navigation_trace.gz-ff68279e3cec94076b69259d756eed181a63eaf834d8b956a7f4ba665fabf939', + 'sha256sum': 'ff68279e3cec94076b69259d756eed181a63eaf834d8b956a7f4ba665fabf939', + 'size_bytes': 7572484, + 'generation': 1666713705258900, + 'output_file': 'chrome_custom_navigation_trace.gz' + }, + { + 'object_name': 'test_data/scroll_offsets.pftrace-62101edb5204fec8bea30124f65d4e49bda0808d7b036e95f89445aaad6d8d98', + 'sha256sum': '62101edb5204fec8bea30124f65d4e49bda0808d7b036e95f89445aaad6d8d98', + 'size_bytes': 769741, + 'generation': 1693402148909129, + 'output_file': 'scroll_offsets.pftrace' + } + ], + 'dep_type': 'gcs' + }, + 'src/third_party/perl': { 'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '8ef97ff3b7332e38e61b347a2fbed425a4617151', 'condition': 'checkout_win', @@ -5199,16 +5269,6 @@ hooks = [ ], }, - # Download test data for Perfetto diff tests - { - 'name': 'perfetto_testdata', - 'condition': 'host_os == "linux"', - 'pattern': '\\.sha256', - 'action': [ 'vpython3', - 'src/base/tracing/test/test_data.py', - 'download', - ], - }, # Pull down WPR Archive files { 'name': 'Fetch WPR archive files', diff --git a/PRESUBMIT.py b/PRESUBMIT.py index 47a3c961426dcc..880c2a2536085a 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -3372,6 +3372,61 @@ def CheckForNewDEPSDownloadFromGoogleStorageHooks(input_api, output_api): return [] +def CheckEachPerfettoTestDataFileHasDepsEntry(input_api, output_api): + test_data_filter = lambda f: input_api.FilterSourceFile( + f, files_to_check=[r'^base/tracing/test/data/.*\.sha256']) + if not any(input_api.AffectedFiles(file_filter=test_data_filter)): + return [] + + # Find DEPS entry + deps_entry = [] + for f in input_api.AffectedFiles(include_deletes=False): + if f.LocalPath() == 'DEPS': + new_deps = _ParseDeps('\n'.join(f.NewContents()))['deps'] + deps_entry = new_deps['src/base/tracing/test/data'] + if not deps_entry: + return [output_api.PresubmitError( + 'You must update the DEPS file when you update a ' + '.sha256 file in base/tracing/test/data' + )] + + output = [] + for f in input_api.AffectedFiles(file_filter=test_data_filter): + objects = deps_entry['objects'] + if not f.NewContents(): + # Deleted file so check that DEPS entry removed + sha256_from_file = f.OldContents()[0] + object_entry = next( + (item for item in objects if item["sha256sum"] == sha256_from_file), + None) + if object_entry: + output.append(output_api.PresubmitError( + 'You deleted %s so you must also remove the corresponding DEPS entry.' + % f.LocalPath() + )) + continue + + sha256_from_file = f.NewContents()[0] + object_entry = next( + (item for item in objects if item["sha256sum"] == sha256_from_file), + None) + if not object_entry: + output.append(output_api.PresubmitError( + 'No corresponding DEPS entry found for %s. ' + 'Run `base/tracing/test/test_data.py get_deps --filepath %s` ' + 'to generate the DEPS entry.' + % (f.LocalPath(), f.LocalPath()) + )) + + if output: + output.append(output_api.PresubmitError( + 'The DEPS entry for `src/base/tracing/test/data` in the DEPS file has not been ' + 'updated properly. Run `base/tracing/test/test_data.py get_all_deps` to see what ' + 'the DEPS entry should look like.' + )) + return output + + def CheckAddedDepsHaveTargetApprovals(input_api, output_api): """When a dependency prefixed with + is added to a DEPS file, we want to make sure that the change is reviewed by an OWNER of the diff --git a/PRESUBMIT_test.py b/PRESUBMIT_test.py index 5b88a882ccbb15..865908bf8a6d5b 100755 --- a/PRESUBMIT_test.py +++ b/PRESUBMIT_test.py @@ -225,6 +225,132 @@ def testNotUnitTestMacros(self): MockInputApi(), MockFile('some/path/source.cc', lines)) self.assertEqual(0, len(errors)) + +class CheckEachPerfettoTestDataFileHasDepsEntry(unittest.TestCase): + + def testNewSha256FileNoDEPS(self): + input_api = MockInputApi() + input_api.files = [ + MockFile('base/tracing/test/data/new.pftrace.sha256', []), + ] + results = PRESUBMIT.CheckEachPerfettoTestDataFileHasDepsEntry(input_api, MockOutputApi()) + self.assertEqual( + ('You must update the DEPS file when you update a .sha256 file ' + 'in base/tracing/test/data'), results[0].message) + + def testNewSha256FileSuccess(self): + input_api = MockInputApi() + new_deps = """deps = { + 'src/base/tracing/test/data': { + 'bucket': 'perfetto', + 'objects': [ + { + 'object_name': 'test_data/new.pftrace-a1b2c3f4', + 'sha256sum': 'a1b2c3f4', + 'size_bytes': 1, + 'generation': 1, + 'output_file': 'new.pftrace' + }, + ], + 'dep_type': 'gcs' + }, + }""".splitlines() + input_api.files = [ + MockFile('base/tracing/test/data/new.pftrace.sha256', ['a1b2c3f4']), + MockFile('DEPS', new_deps), + ] + results = PRESUBMIT.CheckEachPerfettoTestDataFileHasDepsEntry(input_api, MockOutputApi()) + self.assertEqual(0, len(results)) + + def testNewSha256FileWrongSha256(self): + input_api = MockInputApi() + new_deps = """deps = { + 'src/base/tracing/test/data': { + 'bucket': 'perfetto', + 'objects': [ + { + 'object_name': 'test_data/new.pftrace-a1b2c3f4', + 'sha256sum': 'wrong_hash', + 'size_bytes': 1, + 'generation': 1, + 'output_file': 'new.pftrace' + }, + ], + 'dep_type': 'gcs' + }, + }""".splitlines() + f = MockFile('base/tracing/test/data/new.pftrace.sha256', ['a1b2c3f4']) + input_api.files = [ + f, + MockFile('DEPS', new_deps), + ] + results = PRESUBMIT.CheckEachPerfettoTestDataFileHasDepsEntry(input_api, MockOutputApi()) + self.assertEqual(( + 'No corresponding DEPS entry found for %s. ' + 'Run `base/tracing/test/test_data.py get_deps --filepath %s` ' + 'to generate the DEPS entry.' % (f.LocalPath(), f.LocalPath())), + results[0].message) + + def testDeleteSha256File(self): + input_api = MockInputApi() + old_deps = """deps = { + 'src/base/tracing/test/data': { + 'bucket': 'perfetto', + 'objects': [ + { + 'object_name': 'test_data/new.pftrace-a1b2c3f4', + 'sha256sum': 'a1b2c3f4', + 'size_bytes': 1, + 'generation': 1, + 'output_file': 'new.pftrace' + }, + ], + 'dep_type': 'gcs' + }, + }""".splitlines() + f = MockFile('base/tracing/test/data/new.pftrace.sha256', [], ['a1b2c3f4'], action='D') + input_api.files = [ + f, + MockFile('DEPS', old_deps), + ] + results = PRESUBMIT.CheckEachPerfettoTestDataFileHasDepsEntry(input_api, MockOutputApi()) + self.assertEqual(( + 'You deleted %s so you must also remove the corresponding DEPS entry.' + % f.LocalPath()), results[0].message) + + def testDeleteSha256Success(self): + input_api = MockInputApi() + new_deps = """deps = { + 'src/base/tracing/test/data': { + 'bucket': 'perfetto', + 'objects': [], + 'dep_type': 'gcs' + }, + }""".splitlines() + old_deps = """deps = { + 'src/base/tracing/test/data': { + 'bucket': 'perfetto', + 'objects': [ + { + 'object_name': 'test_data/new.pftrace-a1b2c3f4', + 'sha256sum': 'a1b2c3f4', + 'size_bytes': 1, + 'generation': 1, + 'output_file': 'new.pftrace' + }, + ], + 'dep_type': 'gcs' + }, + }""".splitlines() + f = MockFile('base/tracing/test/data/new.pftrace.sha256', [], ['a1b2c3f4'], action='D') + input_api.files = [ + f, + MockFile('DEPS', new_deps, old_deps), + ] + results = PRESUBMIT.CheckEachPerfettoTestDataFileHasDepsEntry(input_api, MockOutputApi()) + self.assertEqual(0, len(results)) + + class CheckAddedDepsHaveTestApprovalsTest(unittest.TestCase): def calculate(self, old_include_rules, old_specific_include_rules, diff --git a/base/tracing/test/README b/base/tracing/test/README index e934a3e4446a82..065bef01431db3 100644 --- a/base/tracing/test/README +++ b/base/tracing/test/README @@ -10,6 +10,7 @@ Currently, the diff tests only run on Linux. You can build and run the diff test ``` $ gn gen --args='' out/Linux +$ gclient sync $ autoninja -C out/Linux perfetto_diff_tests $ out/Linux/bin/run_perfetto_diff_tests ``` @@ -22,13 +23,34 @@ Your new diff test should go in `base/tracing/test/trace_processor/diff_tests/ch If you are adding a **new TestSuite**, be sure to add it to `include_index.py` so the runner knows to run this new TestSuite. -If your test requires modifying or adding new test data i.e. a new trace in `base/tracing/test/data`, you will need to upload this to the GCS bucket. These trace files are too large to be checked-in to the codebase so we check-in only `.sha256` files. You can upload any new traces with this script: +### Adding New Test Data +If your test requires modifying or adding new test data i.e. a new trace in `base/tracing/test/data`, you will need to upload this to the GCS bucket. + +``` +$ base/tracing/test/test_data.py upload +``` + +This script will output a deps entry which you will need to add to the [DEPS file](../../../DEPS) (see examples in the `src/base/tracing/test/data` entry). ``` -$ base/tracing/test/test_data.py upload --verbose +{ + "path": { + "dep_type": "gcs", + "bucket": "perfetto", + "objects": [ + { + "object_name": "test_data/file_name-a1b2c3f4", + "sha256sum": "a1b2c3f4", + "size_bytes": 12345, + "generation": 1234567 + } + ] + } +} ``` +You will need to **manually** add this to the deps entry. After adding this entry, running `gclient sync` will download the test files in your local repo. See these [docs](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/gcs_dependencies.md) for the GCS dependency workflow. -This script will upload your file and generate the `.sha256` file in the `base/tracing/test/data/` directory. You can then upload this file with your CL. +Perfetto has it's own way to download GCS objects with the [test_data](../../../third_party/perfetto/tools/test_data) script. This script uses .sha256 files to download. The `test_data.py upload` command will also generate the `file_name-a1b2c3f4.sha256` in `base/tracing/test/data`. You will need to check-in these files in the codebase so they can by rolled to Perfetto, so the tests can work there too. ## Writing TestTraceProcessor Tests diff --git a/base/tracing/test/test_data.py b/base/tracing/test/test_data.py index 7ac4bfa05f0a0c..6e3ea715a57e3a 100755 --- a/base/tracing/test/test_data.py +++ b/base/tracing/test/test_data.py @@ -4,45 +4,161 @@ # found in the LICENSE file. """ -A wrapper script for //third_party/perfetto/tools/test_data. The wrapper -ensures that we upload the correct directory. +A wrapper script for upload_to_google_storage_first_class.py. Usage: -./test_data.py status # Prints the status of new & modified files. -./test_data.py download # To sync remote>local (used by gclient runhooks). -./test_data.py upload # To upload newly created and modified files. -WARNING: the `download` command will overwrite any locally modified files. -If you want to keep locally modified test data, you should upload it before -running `gclient runhooks` otherwise you will lose this data. +# Uploads file and generates .sha256 file +$ ./test_data.py upload data/trace.pftrace + +# Generates deps entry for a single file +$ ./test_data.py get_deps data/trace.pftrace + +# Generate full deps entry for all files in base/tracing/test/data/ +$ ./test_data.py get_all_deps + +The upload command uploads the given file to the gs://perfetto bucket and +generates a .sha256 file in the base/tracing/test/data/ directory, +which is rolled to the Perfetto repository. +The .sha256 file is used by Perfetto to download the files with their +own test_data download script (third_party/perfetto/tools/test_data). + +The script outputs a GCS entry which should be manually added to the +deps dict in /DEPS. See +https://chromium.googlesource.com/chromium/src/+/HEAD/docs/gcs_dependencies.md. + +The files are uploaded as gs://perfetto/test_data/file_name-a1b2c3f4. """ import argparse import os import subprocess import sys +import json +import re -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('cmd', choices=['status', 'download', 'upload']) - parser.add_argument('--verbose', '-v', action='store_true') - args = parser.parse_args() +SRC_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) +TEST_DATA_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data')) +DEPOT_TOOLS_PATH = os.path.abspath(os.path.join(SRC_PATH, 'third_party', 'depot_tools')) +sys.path.append(DEPOT_TOOLS_PATH) +from upload_to_google_storage_first_class import get_sha256sum +from download_from_google_storage import Gsutil, GSUTIL_DEFAULT_PATH - src_root = os.path.abspath(os.path.join(__file__, '..', '..', '..', '..')) - perfetto_dir = os.path.join(src_root, 'third_party', 'perfetto') - tool = os.path.join(perfetto_dir, "tools", "test_data") - test_dir = os.path.join(src_root, 'base', 'tracing', 'test', 'data') - command = ['vpython3', tool, '--dir', test_dir, '--overwrite', args.cmd] - if args.verbose: - command.append('--verbose') +# Write .sha256 file to be rolled into Perfetto. +def write_sha256_file(filepath: str): + sha256sum = get_sha256sum(filepath) + with open(filepath + '.sha256', 'w') as sha_file: + sha_file.write(sha256sum) + return sha256sum + + +# Run `upload_to_google_storage_first_class.py --bucket perfetto `. +def upload_file(filepath: str, dry_run: bool, force: bool): + sha256sum = write_sha256_file(filepath) + + # Perfetto uses 'test_data/file_name-a1b2c3f4' object name format. + object_name = '%s/%s-%s' % ('test_data', os.path.basename(filepath), sha256sum) + + tool = 'upload_to_google_storage_first_class.py' + command = [tool, '--bucket', 'perfetto', '--object-name', object_name, filepath] + if dry_run: + command.append('--dry-run') + if force: + command.append('--force') completed_process = subprocess.run( command, check=False, capture_output=True) - sys.stderr.buffer.write(completed_process.stderr) - sys.stdout.buffer.write(completed_process.stdout) + + if completed_process.returncode == 0: + print('Manually add the deps entry below to the DEPS file. See ' + 'https://chromium.googlesource.com/chromium/src/+/HEAD/docs/gcs_dependencies.md ' + 'for more details. Run `test_data.py get_all_deps` to get the full deps entry.') + sys.stdout.buffer.write(completed_process.stdout) + else: + sys.stderr.buffer.write(completed_process.stderr) + + +# Generate the deps entry for `filepath`, assuming it has been uploaded already. +def generate_deps_entry(filepath: str): + sha256sum = get_sha256sum(filepath) + object_name = '%s/%s-%s' % ('test_data', os.path.basename(filepath), sha256sum) + + # Run `gcloud storage ls -L gs://perfetto/test_data/file_name-a1b2c3f4` to + # get the 'generation' and 'size_bytes' fields for the deps entry + gsutil = Gsutil(GSUTIL_DEFAULT_PATH) + gsutil_args = ['ls', '-L', 'gs://perfetto/%s' % object_name] + code, out, err = gsutil.check_call(*gsutil_args) + if code != 0: + raise Exception(code, err + ' ' + object_name) + generation = int(out.split()[out.split().index('Generation:') + 1]) + size = int(out.split()[out.split().index('Content-Length:') + 1]) + + return { + 'object_name': object_name, + 'sha256sum': sha256sum, + 'size_bytes': size, + 'generation': generation, + 'output_file': os.path.basename(filepath), + } + + +# Generate the full deps entry for Perfetto test data +def generate_all_deps(): + path = os.path.join(SRC_PATH, 'base/tracing/test/data') + objects = [] + for file in os.listdir(path): + if file.endswith('.sha256'): + filepath = os.path.join(path, file)[:-7] + assert os.path.isfile(filepath), 'File does not exist' + object_entry = generate_deps_entry(filepath) + objects.append(object_entry) + return { + 'src/base/tracing/test/data': { + 'bucket': + 'perfetto', + 'objects': objects, + 'dep_type': + 'gcs', + }, + } + + +def main(): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='cmd') + + upload_parser = subparsers.add_parser('upload', help='Upload a file to gs://perfetto') + upload_parser.add_argument('filepath', help='Path to file you want to upload') + upload_parser.add_argument('--dry-run', action='store_true', + help='Check if file already exists on GCS without ' + 'uploading it and output DEP blob.') + upload_parser.add_argument('-f', + '--force', + action='store_true', + help='Force upload even if remote file exists.') + + get_deps_parser = subparsers.add_parser('get_deps', help='Print deps entry for a single file') + get_deps_parser.add_argument('filepath', help='Path to test data file you want the deps entry for.') + + subparsers.add_parser('get_all_deps', help='Print deps entry for all files in `base/tracing/test/data/`') + + args = parser.parse_args() + + if args.cmd == 'get_all_deps': + print(json.dumps(generate_all_deps(), indent=2).replace('"', "'")) + return + + filepath = os.path.abspath(args.filepath) + assert os.path.dirname(filepath) == TEST_DATA_PATH, ('You can only ' + 'upload files in base/tracing/test/data/') + + if args.cmd == 'upload': + upload_file(filepath, args.dry_run, args.force) + elif args.cmd == 'get_deps': + print(json.dumps(generate_deps_entry(filepath), indent=2).replace('"', "'")) if __name__ == '__main__': sys.exit(main()) \ No newline at end of file