Increases notebook concurrency and linted CI files (#512)

* Fixed private pool issues

* Ran linter

* Added worker timeouts

* Tweaked timeout

* Removed gcloud requirement

* Removed unneeded file
This commit is contained in:
Ivan Cheung
2022-05-18 18:58:54 -04:00
committed by GitHub
parent dea950bcb6
commit 4fca7d98b2
12 changed files with 110 additions and 74 deletions
+26 -27
View File
@@ -1,45 +1,44 @@
from typing import List from typing import List
from resource_cleanup_manager import (
ResourceCleanupManager, from resource_cleanup_manager import (DatasetResourceCleanupManager,
DatasetResourceCleanupManager, EndpointResourceCleanupManager,
EndpointResourceCleanupManager, ModelResourceCleanupManager,
ModelResourceCleanupManager, ResourceCleanupManager)
)
def run_cleanup_managers(managers: List[ResourceCleanupManager], is_dry_run: bool): def run_cleanup_managers(managers: List[ResourceCleanupManager], is_dry_run: bool):
for manager in managers: for manager in managers:
type_name = manager.type_name type_name = manager.type_name
print(f"Fetching {type_name}'s...") print(f"Fetching {type_name}'s...")
resources = manager.list() resources = manager.list()
print(f"Found {len(resources)} {type_name}'s") print(f"Found {len(resources)} {type_name}'s")
for resource in resources: for resource in resources:
if not manager.is_deletable(resource): if not manager.is_deletable(resource):
continue continue
if is_dry_run: if is_dry_run:
resource_name = manager.resource_name(resource) resource_name = manager.resource_name(resource)
print(f"Will delete '{type_name}': {resource_name}") print(f"Will delete '{type_name}': {resource_name}")
else: else:
try: try:
manager.delete(resource) manager.delete(resource)
except Exception as exception: except Exception as exception:
print(exception) print(exception)
print("") print("")
is_dry_run = False is_dry_run = False
if is_dry_run: if is_dry_run:
print("Starting cleanup in dry run mode...") print("Starting cleanup in dry run mode...")
# List of all cleanup managers # List of all cleanup managers
managers = [ managers = [
DatasetResourceCleanupManager(), DatasetResourceCleanupManager(),
EndpointResourceCleanupManager(), EndpointResourceCleanupManager(),
ModelResourceCleanupManager(), ModelResourceCleanupManager(),
] ]
run_cleanup_managers(managers=managers, is_dry_run=is_dry_run) run_cleanup_managers(managers=managers, is_dry_run=is_dry_run)
@@ -1,8 +1,9 @@
import abc import abc
from google.cloud import aiplatform
from typing import Any from typing import Any
from proto.datetime_helpers import DatetimeWithNanoseconds
from google.cloud import aiplatform
from google.cloud.aiplatform import base from google.cloud.aiplatform import base
from proto.datetime_helpers import DatetimeWithNanoseconds
# If a resource was updated within this number of seconds, do not delete. # If a resource was updated within this number of seconds, do not delete.
RESOURCE_UPDATE_BUFFER_IN_SECONDS = 60 * 60 * 8 RESOURCE_UPDATE_BUFFER_IN_SECONDS = 60 * 60 * 8
+10 -1
View File
@@ -17,6 +17,7 @@
import argparse import argparse
import pathlib import pathlib
import execute_changed_notebooks_helper import execute_changed_notebooks_helper
@@ -73,6 +74,13 @@ parser.add_argument(
help="The GCP directory for storing executed notebooks.", help="The GCP directory for storing executed notebooks.",
required=True, required=True,
) )
parser.add_argument(
"--timeout",
type=int,
help="Timeout in seconds",
default=86400,
required=False,
)
parser.add_argument( parser.add_argument(
"--private_pool_id", "--private_pool_id",
type=str, type=str,
@@ -102,6 +110,7 @@ execute_changed_notebooks_helper.process_and_execute_notebooks(
artifacts_bucket=args.artifacts_bucket, artifacts_bucket=args.artifacts_bucket,
variable_project_id=args.variable_project_id, variable_project_id=args.variable_project_id,
variable_region=args.variable_region, variable_region=args.variable_region,
private_pool_id=args.private_pool_id if not "default" else None, private_pool_id=args.private_pool_id,
should_parallelize=args.should_parallelize, should_parallelize=args.should_parallelize,
timeout=args.timeout,
) )
@@ -17,18 +17,22 @@ import concurrent
import dataclasses import dataclasses
import datetime import datetime
import functools import functools
import operator
import os import os
import pathlib import pathlib
import nbformat
import re import re
import subprocess import subprocess
from typing import List, Optional from typing import List, Optional
from tabulate import tabulate
import operator
import execute_notebook_remote import execute_notebook_remote
from utils import util, NotebookProcessors import nbformat
from google.cloud.devtools.cloudbuild_v1.types import BuildOperationMetadata from google.cloud.devtools.cloudbuild_v1.types import BuildOperationMetadata
from ratemate import RateLimit
from tabulate import tabulate
from utils import NotebookProcessors, util
# A buffer so that workers finish before the orchestrating job
WORKER_TIMEOUT_BUFFER_IN_SECONDS: int = 60 * 60
def format_timedelta(delta: datetime.timedelta) -> str: def format_timedelta(delta: datetime.timedelta) -> str:
@@ -103,6 +107,9 @@ def _create_tag(filepath: str) -> str:
return tag return tag
rate_limit = RateLimit(max_count=50, per=60, greedy=True)
def process_and_execute_notebook( def process_and_execute_notebook(
container_uri: str, container_uri: str,
staging_bucket: str, staging_bucket: str,
@@ -110,9 +117,12 @@ def process_and_execute_notebook(
variable_project_id: str, variable_project_id: str,
variable_region: str, variable_region: str,
private_pool_id: Optional[str], private_pool_id: Optional[str],
deadline: datetime,
notebook: str, notebook: str,
should_get_tail_logs: bool = False, should_get_tail_logs: bool = False,
) -> NotebookExecutionResult: ) -> NotebookExecutionResult:
rate_limit.wait() # wait before creating the task
print(f"Running notebook: {notebook}") print(f"Running notebook: {notebook}")
# Create paths # Create paths
@@ -145,14 +155,20 @@ def process_and_execute_notebook(
# Upload the pre-processed code to a GCS bucket # Upload the pre-processed code to a GCS bucket
code_archive_uri = util.archive_code_and_upload(staging_bucket=staging_bucket) code_archive_uri = util.archive_code_and_upload(staging_bucket=staging_bucket)
# Calculate timeout in seconds
timeout_in_seconds = max(
int((deadline - datetime.datetime.now()).total_seconds()), 1
)
operation = execute_notebook_remote.execute_notebook_remote( operation = execute_notebook_remote.execute_notebook_remote(
code_archive_uri=code_archive_uri, code_archive_uri=code_archive_uri,
notebook_uri=notebook, notebook_uri=notebook,
notebook_output_uri=notebook_output_uri, notebook_output_uri=notebook_output_uri,
container_uri=container_uri, container_uri=container_uri,
tag=tag, tag=tag,
region=variable_region,
private_pool_id=private_pool_id, private_pool_id=private_pool_id,
private_pool_region=variable_region,
timeout_in_seconds=timeout_in_seconds,
) )
operation_metadata = BuildOperationMetadata(mapping=operation.metadata) operation_metadata = BuildOperationMetadata(mapping=operation.metadata)
@@ -241,6 +257,7 @@ def process_and_execute_notebooks(
variable_region: str, variable_region: str,
private_pool_id: Optional[str], private_pool_id: Optional[str],
should_parallelize: bool, should_parallelize: bool,
timeout: int,
): ):
""" """
Run the notebooks that exist under the folders defined in the test_paths_file. Run the notebooks that exist under the folders defined in the test_paths_file.
@@ -267,9 +284,16 @@ def process_and_execute_notebooks(
Required. The value for REGION to inject into notebooks. Required. The value for REGION to inject into notebooks.
should_parallelize (bool): should_parallelize (bool):
Required. Should run notebooks in parallel using a thread pool as opposed to in sequence. Required. Should run notebooks in parallel using a thread pool as opposed to in sequence.
timeout (str):
Required. Timeout string according to https://cloud.google.com/build/docs/build-config-file-schema#timeout.
""" """
notebook_execution_results: List[NotebookExecutionResult] = [] notebook_execution_results: List[NotebookExecutionResult] = []
# Calculate deadline
deadline = datetime.datetime.now() + datetime.timedelta(
seconds=max(timeout - WORKER_TIMEOUT_BUFFER_IN_SECONDS, 0)
)
if len(notebooks) > 0: if len(notebooks) > 0:
print(f"Found {len(notebooks)} modified notebooks: {notebooks}") print(f"Found {len(notebooks)} modified notebooks: {notebooks}")
@@ -277,7 +301,9 @@ def process_and_execute_notebooks(
print( print(
"Running notebooks in parallel, so no logs will be displayed. Please wait..." "Running notebooks in parallel, so no logs will be displayed. Please wait..."
) )
with concurrent.futures.ThreadPoolExecutor(max_workers=None) as executor: with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
print(f"Max workers: {executor._max_workers}")
notebook_execution_results = list( notebook_execution_results = list(
executor.map( executor.map(
functools.partial( functools.partial(
@@ -288,6 +314,7 @@ def process_and_execute_notebooks(
variable_project_id, variable_project_id,
variable_region, variable_region,
private_pool_id, private_pool_id,
deadline,
), ),
notebooks, notebooks,
) )
@@ -301,6 +328,7 @@ def process_and_execute_notebooks(
variable_project_id=variable_project_id, variable_project_id=variable_project_id,
variable_region=variable_region, variable_region=variable_region,
private_pool_id=private_pool_id, private_pool_id=private_pool_id,
deadline=deadline,
notebook=notebook, notebook=notebook,
) )
for notebook in notebooks for notebook in notebooks
+1
View File
@@ -16,6 +16,7 @@
"""A CLI to download (optional) and run a single notebook locally""" """A CLI to download (optional) and run a single notebook locally"""
import argparse import argparse
import execute_notebook_helper import execute_notebook_helper
parser = argparse.ArgumentParser(description="Run a single notebook locally.") parser = argparse.ArgumentParser(description="Run a single notebook locally.")
+4 -4
View File
@@ -15,14 +15,14 @@
"""Methods to run a notebook locally""" """Methods to run a notebook locally"""
import sys
import os
import errno import errno
import papermill as pm import os
import shutil import shutil
import sys
from utils import util import papermill as pm
from google.cloud.aiplatform import utils from google.cloud.aiplatform import utils
from utils import util
# This script is used to execute a notebook and write out the output notebook. # This script is used to execute a notebook and write out the output notebook.
+18 -19
View File
@@ -16,22 +16,18 @@
"""Methods to run a notebook on Google Cloud Build""" """Methods to run a notebook on Google Cloud Build"""
from re import sub from re import sub
from typing import Optional
import google.auth
import yaml
from google.api_core import client_options, operation
from google.cloud.aiplatform import utils
from google.cloud.devtools import cloudbuild_v1
from google.cloud.devtools.cloudbuild_v1.types import Source, StorageSource
from google.protobuf import duration_pb2 from google.protobuf import duration_pb2
from yaml.loader import FullLoader from yaml.loader import FullLoader
import google.auth
from google.cloud.devtools import cloudbuild_v1
from google.cloud.devtools.cloudbuild_v1.types import Source, StorageSource
from typing import Optional
import yaml
from google.cloud.aiplatform import utils
from google.api_core import operation, client_options
CLOUD_BUILD_FILEPATH = ".cloud-build/notebook-execution-test-cloudbuild-single.yaml" CLOUD_BUILD_FILEPATH = ".cloud-build/notebook-execution-test-cloudbuild-single.yaml"
TIMEOUT_IN_SECONDS = 86400
SERVICE_BASE_PATH = "cloudbuild.googleapis.com" SERVICE_BASE_PATH = "cloudbuild.googleapis.com"
@@ -40,12 +36,14 @@ def execute_notebook_remote(
notebook_uri: str, notebook_uri: str,
notebook_output_uri: str, notebook_output_uri: str,
container_uri: str, container_uri: str,
region: str,
private_pool_id: Optional[str], private_pool_id: Optional[str],
private_pool_region: Optional[str],
tag: Optional[str], tag: Optional[str],
timeout_in_seconds: Optional[int] = None,
) -> operation.Operation: ) -> operation.Operation:
"""Create and execute a single notebook on Google Cloud Build""" """Create and execute a single notebook on Google Cloud Build"""
# Load build steps from YAML # Load build steps from YAML
cloudbuild_config = yaml.load(open(CLOUD_BUILD_FILEPATH), Loader=FullLoader) cloudbuild_config = yaml.load(open(CLOUD_BUILD_FILEPATH), Loader=FullLoader)
substitutions = { substitutions = {
@@ -57,13 +55,14 @@ def execute_notebook_remote(
build = cloudbuild_v1.Build() build = cloudbuild_v1.Build()
options: Optional[client_options.ClientOptions] = None options: Optional[client_options.ClientOptions] = None
if private_pool_id: if private_pool_id and private_pool_region:
substitutions["_PRIVATE_POOL_NAME"] = private_pool_id # substitutions["_PRIVATE_POOL_NAME"] = private_pool_id
build.options = cloudbuild_config["options"] build.options = cloudbuild_config.get("options")
build.options.pool = {"name": private_pool_id}
# Switch to the regional endpoint of the pool # Switch to the regional endpoint of the pool
options = client_options.ClientOptions( options = client_options.ClientOptions(
api_endpoint=f"{region}-{SERVICE_BASE_PATH}" api_endpoint=f"{private_pool_region}-{SERVICE_BASE_PATH}"
) )
# Authorize the client with Google defaults # Authorize the client with Google defaults
@@ -85,8 +84,8 @@ def execute_notebook_remote(
build.steps = cloudbuild_config["steps"] build.steps = cloudbuild_config["steps"]
build.substitutions = substitutions build.substitutions = substitutions
build.timeout = duration_pb2.Duration(seconds=TIMEOUT_IN_SECONDS) build.timeout = duration_pb2.Duration(seconds=timeout_in_seconds)
build.queue_ttl = duration_pb2.Duration(seconds=TIMEOUT_IN_SECONDS) build.queue_ttl = duration_pb2.Duration(seconds=timeout_in_seconds)
if tag: if tag:
build.tags = [tag] build.tags = [tag]
@@ -25,7 +25,4 @@ steps:
- 'python3 -m pip install -U pip && python3 -m pip freeze && python3 .cloud-build/execute_notebook_cli.py --notebook_source "${_NOTEBOOK_GCS_URI}" --output_file_or_uri "${_NOTEBOOK_OUTPUT_GCS_URI}"' - 'python3 -m pip install -U pip && python3 -m pip freeze && python3 .cloud-build/execute_notebook_cli.py --notebook_source "${_NOTEBOOK_GCS_URI}" --output_file_or_uri "${_NOTEBOOK_OUTPUT_GCS_URI}"'
env: env:
- 'IS_TESTING=1' - 'IS_TESTING=1'
timeout: 86400s timeout: 86400s
options:
pool:
name: ${_PRIVATE_POOL_NAME}
+1 -1
View File
@@ -9,4 +9,4 @@ tabulate
google-cloud-aiplatform google-cloud-aiplatform
google-cloud-storage google-cloud-storage
google-cloud-build google-cloud-build
gcloud ratemate
+4 -2
View File
@@ -13,8 +13,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from nbconvert.preprocessors import Preprocessor
from typing import Dict from typing import Dict
from nbconvert.preprocessors import Preprocessor
from . import UpdateNotebookVariables as update_notebook_variables from . import UpdateNotebookVariables as update_notebook_variables
@@ -60,4 +62,4 @@ class UpdateVariablesPreprocessor(Preprocessor):
executable_cells.append(cell) executable_cells.append(cell)
notebook.cells = executable_cells notebook.cells = executable_cells
return notebook, resources return notebook, resources
@@ -78,4 +78,4 @@ def test_region():
variable_name="REGION", variable_name="REGION",
variable_value="us-central1", variable_value="us-central1",
) )
assert new_content == 'REGION = "us-central1" # @param {type:"string"}' assert new_content == 'REGION = "us-central1" # @param {type:"string"}'
+7 -7
View File
@@ -1,13 +1,13 @@
from datetime import datetime
from typing import Optional
from google.cloud import storage
from google.cloud.aiplatform import utils
from google.auth import credentials as auth_credentials
import os import os
import subprocess import subprocess
import tarfile import tarfile
import uuid import uuid
from datetime import datetime
from typing import Optional
from google.auth import credentials as auth_credentials
from google.cloud import storage
from google.cloud.aiplatform import utils
def download_file(bucket_name: str, blob_name: str, destination_file: str) -> str: def download_file(bucket_name: str, blob_name: str, destination_file: str) -> str:
@@ -57,4 +57,4 @@ def archive_code_and_upload(staging_bucket: str):
print(f"Uploaded source code archive to {source_archived_file_gcs}") print(f"Uploaded source code archive to {source_archived_file_gcs}")
return source_archived_file_gcs return source_archived_file_gcs