Skip to content

Commit 5cfd235

Browse files
build_manager: do not fetch fuzz_targets when not necessary (#4393)
For non engine fuzzers, we do not need to have the full list of fuzz targets, since those builds are only containing the APP_NAME. For that reason, and when the build is fully unpacked, lazily fetch the fuzzing targets only when requested by the user of the class. This change will tremendously speed up the unpacking step for some of our fuzzers, see https://docs.google.com/document/d/1OepfVcuG2XNXLxgZIXVwgE-uOhX9RO5RfH0TSg_wmPU/. Co-authored-by: jonathanmetzman <[email protected]>
1 parent 73f961f commit 5cfd235

File tree

2 files changed

+50
-43
lines changed

2 files changed

+50
-43
lines changed

src/clusterfuzz/_internal/bot/untrusted_runner/build_setup_host.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,6 @@ def _clear_env():
3131
environment.remove_key('FUZZ_TARGET')
3232

3333

34-
def _handle_response(build, response):
35-
"""Handle build setup response."""
36-
if not response.result:
37-
_clear_env()
38-
return False
39-
40-
_update_env_from_response(response)
41-
42-
if not environment.get_value('APP_PATH'):
43-
fuzzer_directory = environment.get_value('FUZZER_DIR')
44-
if fuzzer_directory:
45-
build_manager.set_environment_vars([fuzzer_directory])
46-
47-
environment.set_value('APP_REVISION', build.revision)
48-
build.fuzz_targets = list(response.fuzz_targets)
49-
50-
return True
51-
52-
5334
def _update_env_from_response(response):
5435
"""Update environment variables from response."""
5536
environment.set_value('APP_PATH', response.app_path)
@@ -64,11 +45,37 @@ def _update_env_from_response(response):
6445
class RemoteRegularBuild(build_manager.RegularBuild):
6546
"""Remote regular build."""
6647

48+
def __init__(self, *args, **kwargs):
49+
super().__init__(*args, **kwargs)
50+
self._fuzz_targets = []
51+
52+
def _handle_response(self, response):
53+
"""Handle build setup response."""
54+
if not response.result:
55+
_clear_env()
56+
return False
57+
58+
_update_env_from_response(response)
59+
60+
if not environment.get_value('APP_PATH'):
61+
fuzzer_directory = environment.get_value('FUZZER_DIR')
62+
if fuzzer_directory:
63+
build_manager.set_environment_vars([fuzzer_directory])
64+
65+
environment.set_value('APP_REVISION', self.revision)
66+
self._fuzz_targets = list(response.fuzz_targets)
67+
68+
return True
69+
6770
def setup(self):
6871
request = untrusted_runner_pb2.SetupRegularBuildRequest( # pylint: disable=no-member
6972
base_build_dir=self.base_build_dir,
7073
revision=self.revision,
7174
build_url=self.build_url,
7275
fuzz_target=self.fuzz_target,
7376
build_prefix=self.build_prefix)
74-
return _handle_response(self, host.stub().SetupRegularBuild(request))
77+
return self._handle_response(host.stub().SetupRegularBuild(request))
78+
79+
@property
80+
def fuzz_targets(self):
81+
return self._fuzz_targets

src/clusterfuzz/_internal/build_management/build_manager.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,9 @@ def __init__(self,
330330
self.build_prefix = build_prefix
331331
self.env_prefix = build_prefix + '_' if build_prefix else ''
332332
# This is used by users of the class to learn the fuzz targets in the build.
333-
self.fuzz_targets = None
333+
self._fuzz_targets = None
334+
self._unpack_everything = environment.get_value(
335+
'UNPACK_ALL_FUZZ_TARGETS_AND_FILES', default_value=False)
334336
# This is used by users of the class to instruct the class which fuzz
335337
# target to unpack.
336338
self.fuzz_target = fuzz_target
@@ -457,15 +459,12 @@ def _download_and_open_build_archive(self, base_build_dir: str,
457459
shell.remove_file(build_local_archive)
458460

459461
def _open_build_archive(self, base_build_dir: str, build_dir: str,
460-
build_url: str, http_build_url: Optional[str],
461-
unpack_everything: Optional[bool]):
462+
build_url: str, http_build_url: Optional[str]):
462463
"""Gets a handle on a build archive for the current build. Depending on the
463464
provided parameters, this function might download the build archive into
464465
the build directory or directly use remote HTTP archive.
465466
466467
Args:
467-
unpack_everything: wether we should unpack the whole archive or try
468-
selective unpacking.
469468
base_build_dir: the base build directory.
470469
build_dir: the current build directory.
471470
build_url: the build URL.
@@ -483,7 +482,8 @@ def _open_build_archive(self, base_build_dir: str, build_dir: str,
483482
allow_unpack_over_http = environment.get_value(
484483
'ALLOW_UNPACK_OVER_HTTP', default_value=False)
485484
can_unzip_over_http = (
486-
allow_unpack_over_http and not unpack_everything and http_build_url and
485+
allow_unpack_over_http and not self._unpack_everything and
486+
http_build_url and
487487
build_archive.unzip_over_http_compatible(http_build_url))
488488

489489
if not can_unzip_over_http:
@@ -503,9 +503,6 @@ def _unpack_build(self,
503503
# Track time taken to unpack builds so that it doesn't silently regress.
504504
start_time = time.time()
505505

506-
unpack_everything = environment.get_value(
507-
'UNPACK_ALL_FUZZ_TARGETS_AND_FILES')
508-
509506
logs.info(f'Unpacking build from {build_url} into {build_dir}.')
510507

511508
# Free up memory.
@@ -520,15 +517,12 @@ def _unpack_build(self,
520517

521518
try:
522519
with self._open_build_archive(base_build_dir, build_dir, build_url,
523-
http_build_url, unpack_everything) as build:
520+
http_build_url) as build:
524521
unpack_start_time = time.time()
525-
unpack_everything = environment.get_value(
526-
'UNPACK_ALL_FUZZ_TARGETS_AND_FILES')
527-
528-
if not unpack_everything:
522+
if not self._unpack_everything:
529523
# We will never unpack the full build so we need to get the targets
530524
# from the build archive.
531-
self.fuzz_targets = list(build.list_fuzz_targets())
525+
self._fuzz_targets = list(build.list_fuzz_targets())
532526
# We only want to unpack a single fuzz target if unpack_everything is
533527
# False.
534528
fuzz_target_to_unpack = self.fuzz_target
@@ -567,9 +561,7 @@ def _unpack_build(self,
567561
logs.error(f'Unable to unpack build archive {build_url}: {e}')
568562
return False
569563

570-
if unpack_everything:
571-
self.fuzz_targets = list(self._get_fuzz_targets_from_dir(build_dir))
572-
else:
564+
if not self._unpack_everything:
573565
# If this is partial build due to selected build files, then mark it as
574566
# such so that it is not re-used.
575567
partial_build_file_path = os.path.join(build_dir, PARTIAL_BUILD_FILE)
@@ -601,6 +593,14 @@ def build_dir(self):
601593
"""The build directory. Usually a subdirectory of base_build_dir."""
602594
raise NotImplementedError
603595

596+
@property
597+
def fuzz_targets(self):
598+
if not self._fuzz_targets and self._unpack_everything:
599+
# we can lazily compute that when unpacking the whole archive, since we
600+
# know all the fuzzers will be in the build directory.
601+
self._fuzz_targets = list(self._get_fuzz_targets_from_dir(self.build_dir))
602+
return self._fuzz_targets
603+
604604
def exists(self):
605605
"""Check if build already exists."""
606606
revision_file = os.path.join(self.build_dir, REVISION_FILE_NAME)
@@ -741,8 +741,8 @@ def setup(self):
741741
# This list will be incomplete because the directory on disk does not have
742742
# all fuzz targets. This is fine. The way fuzz_targets are added to db, it
743743
# does not clobber complete lists.
744-
assert self.fuzz_targets is None
745-
self.fuzz_targets = list(self._get_fuzz_targets_from_dir(self.build_dir))
744+
assert self._fuzz_targets is None
745+
self._fuzz_targets = list(self._get_fuzz_targets_from_dir(self.build_dir))
746746

747747
self._setup_application_path(build_update=build_update)
748748
self._post_setup_success(update_revision=build_update)
@@ -754,7 +754,7 @@ class SplitTargetBuild(RegularBuild):
754754

755755
def setup(self, *args, **kwargs):
756756
result = super().setup(*args, **kwargs)
757-
self.fuzz_targets = list(_split_target_build_list_targets())
757+
self._fuzz_targets = list(_split_target_build_list_targets())
758758
return result
759759

760760

@@ -793,7 +793,7 @@ def setup(self):
793793

794794
# Select a fuzzer, now that a list is available
795795
fuzz_targets = fuchsia.undercoat.list_fuzzers(handle)
796-
self.fuzz_targets = list(fuzz_targets)
796+
self._fuzz_targets = list(fuzz_targets)
797797
return True
798798

799799

@@ -972,7 +972,7 @@ def setup(self):
972972
else:
973973
logs.info('Build already exists.')
974974

975-
self.fuzz_targets = list(self._get_fuzz_targets_from_dir(self.build_dir))
975+
self._fuzz_targets = list(self._get_fuzz_targets_from_dir(self.build_dir))
976976
self._setup_application_path(build_update=build_update)
977977
self._post_setup_success(update_revision=build_update)
978978
return True

0 commit comments

Comments
 (0)