From f4b2988dec8a2ce66c229c2934d437f129be2332 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach Date: Fri, 10 Jul 2026 20:30:17 +0000 Subject: [PATCH] Clean up Jupyter Nsight integration --- brev/entrypoint-jupyter-user.bash | 4 +- brev/entrypoint-jupyter.bash | 38 +++++++- brev/jupyter-generate-plugin-settings.bash | 59 ------------ brev/jupyter-generate-settings.bash | 23 +++++ brev/restart-compose-services.py | 94 +++++++++++++++++++ .../brev/docker-compose.yml | 1 - .../accelerated-python/brev/requirements.txt | 1 - tutorials/cuda-cpp/brev/docker-compose.yml | 1 - tutorials/cuda-cpp/brev/requirements.txt | 1 - tutorials/cuda-tile/brev/docker-compose.yml | 1 - tutorials/cuda-tile/brev/requirements.txt | 1 - .../brev/docker-compose.yml | 1 - .../brev/requirements.txt | 1 - .../gpu-deployment-from-scratch.md | 1 - .../nvmath-python/brev/docker-compose.yml | 1 - tutorials/nvmath-python/brev/requirements.txt | 1 - tutorials/stdpar/brev/docker-compose.yml | 1 - tutorials/stdpar/brev/requirements.txt | 1 - tutorials/warp/brev/docker-compose.yml | 1 - 19 files changed, 155 insertions(+), 77 deletions(-) delete mode 100755 brev/jupyter-generate-plugin-settings.bash create mode 100755 brev/jupyter-generate-settings.bash create mode 100755 brev/restart-compose-services.py diff --git a/brev/entrypoint-jupyter-user.bash b/brev/entrypoint-jupyter-user.bash index 5c78eac6..3c25981a 100755 --- a/brev/entrypoint-jupyter-user.bash +++ b/brev/entrypoint-jupyter-user.bash @@ -6,8 +6,8 @@ set -euo pipefail export HOME="${ACH_TARGET_HOME}" -# Generate Jupyter plugin settings -/accelerated-computing-hub/brev/jupyter-generate-plugin-settings.bash +# Generate JupyterLab user settings +/accelerated-computing-hub/brev/jupyter-generate-settings.bash mkdir -p /accelerated-computing-hub/logs diff --git a/brev/entrypoint-jupyter.bash b/brev/entrypoint-jupyter.bash index 2ba3ec67..3ebc0b4e 100755 --- a/brev/entrypoint-jupyter.bash +++ b/brev/entrypoint-jupyter.bash @@ -4,5 +4,39 @@ set -euo pipefail -# Switch to user and run user-level entrypoint -exec gosu "${ACH_TARGET_USER}" /accelerated-computing-hub/brev/entrypoint-jupyter-user.bash "$@" +# Keep this root wrapper alive so a clean Jupyter shutdown can restart its +# sibling Nsight services through the Docker socket. External container stop +# signals skip the sibling restarts. +TERMINATING=0 +JUPYTER_PID="" + +# shellcheck disable=SC2317 # Invoked indirectly by the signal traps below. +terminate_jupyter() { + TERMINATING=1 + if [ -n "${JUPYTER_PID}" ] && kill -0 "${JUPYTER_PID}" 2>/dev/null; then + kill -TERM "${JUPYTER_PID}" + fi +} + +trap terminate_jupyter TERM INT + +gosu "${ACH_TARGET_USER}" /accelerated-computing-hub/brev/entrypoint-jupyter-user.bash "$@" & +JUPYTER_PID=$! + +set +e +wait "${JUPYTER_PID}" +JUPYTER_STATUS=$? +if [ "${TERMINATING}" -eq 1 ] && kill -0 "${JUPYTER_PID}" 2>/dev/null; then + wait "${JUPYTER_PID}" + JUPYTER_STATUS=$? +fi +set -e + +if [ "${TERMINATING}" -eq 0 ] && [ "${JUPYTER_STATUS}" -eq 0 ] && [ -n "${ACH_PORT_FORWARDS:-}" ]; then + echo "Jupyter exited cleanly; restarting Nsight services." + if ! python3 /accelerated-computing-hub/brev/restart-compose-services.py nsys ncu; then + echo "Error: Failed to restart one or more Nsight services." >&2 + fi +fi + +exit "${JUPYTER_STATUS}" diff --git a/brev/jupyter-generate-plugin-settings.bash b/brev/jupyter-generate-plugin-settings.bash deleted file mode 100755 index f5999499..00000000 --- a/brev/jupyter-generate-plugin-settings.bash +++ /dev/null @@ -1,59 +0,0 @@ -#! /bin/bash - -set -eu - -JUPYTER_HOST="jupyter0-${BREV_ENV_ID:-local}.brevlab.com" -NSYS_HTTP_URL="https://nsys0-${BREV_ENV_ID:-local}.brevlab.com" - -# Theme -mkdir -p "${HOME}/.jupyter/lab/user-settings/@jupyterlab/apputils-extension" -cat << EOF > "${HOME}/.jupyter/lab/user-settings/@jupyterlab/apputils-extension/themes.jupyterlab-settings" -{ - "theme": "JupyterLab Dark", - "adaptive-theme": true, - "preferred-light-theme": "JupyterLab Light", - "preferred-dark-theme": "JupyterLab Dark", - "theme-scrollbars": false -} -EOF - -# Load TURN credentials generated by the base service -TURN_CREDENTIALS_FILE="/accelerated-computing-hub/.turn-credentials" -if [ -f "${TURN_CREDENTIALS_FILE}" ]; then - source "${TURN_CREDENTIALS_FILE}" -else - echo "Warning: TURN credentials file not found at ${TURN_CREDENTIALS_FILE}" >&2 -fi - -# Nsight JupyterLab Plugin -mkdir -p "${HOME}/.jupyter/lab/user-settings/jupyterlab-nvidia-nsight" - -TURN_SETTINGS="" -if [ -n "${TURN_USERNAME+x}" ] && [ -n "${TURN_PASSWORD+x}" ]; then - TURN_SETTINGS=$(cat < "${HOME}/.jupyter/lab/user-settings/jupyterlab-nvidia-nsight/plugin.jupyterlab-settings" -{ - "ui": { - "enabled": true, - "suppressServerAddressWarning": true, - "host": "${JUPYTER_HOST}", - "dockerHost": "${JUPYTER_HOST}", - "defaultStreamerAddress": "${NSYS_HTTP_URL}"${TURN_SETTINGS} - } -} -EOF - -# Execution timing -mkdir -p "${HOME}/.jupyter/lab/user-settings/@jupyterlab/notebook-extension" -cat << EOF > "${HOME}/.jupyter/lab/user-settings/@jupyterlab/notebook-extension/tracker.jupyterlab-settings" -{ - "recordTiming": true -} -EOF diff --git a/brev/jupyter-generate-settings.bash b/brev/jupyter-generate-settings.bash new file mode 100755 index 00000000..02912f98 --- /dev/null +++ b/brev/jupyter-generate-settings.bash @@ -0,0 +1,23 @@ +#! /bin/bash + +set -eu + +# Theme +mkdir -p "${HOME}/.jupyter/lab/user-settings/@jupyterlab/apputils-extension" +cat << EOF > "${HOME}/.jupyter/lab/user-settings/@jupyterlab/apputils-extension/themes.jupyterlab-settings" +{ + "theme": "JupyterLab Dark", + "adaptive-theme": true, + "preferred-light-theme": "JupyterLab Light", + "preferred-dark-theme": "JupyterLab Dark", + "theme-scrollbars": false +} +EOF + +# Execution timing +mkdir -p "${HOME}/.jupyter/lab/user-settings/@jupyterlab/notebook-extension" +cat << EOF > "${HOME}/.jupyter/lab/user-settings/@jupyterlab/notebook-extension/tracker.jupyterlab-settings" +{ + "recordTiming": true +} +EOF diff --git a/brev/restart-compose-services.py b/brev/restart-compose-services.py new file mode 100755 index 00000000..792fe847 --- /dev/null +++ b/brev/restart-compose-services.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Restart sibling Docker Compose services through the Docker Engine socket.""" + +import http.client +import json +import os +import socket +import sys +import urllib.parse + + +DOCKER_SOCKET = "/var/run/docker.sock" + + +class UnixHTTPConnection(http.client.HTTPConnection): + def __init__(self, socket_path): + super().__init__("localhost") + self.socket_path = socket_path + + def connect(self): + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.connect(self.socket_path) + + +def docker_request(method, path): + connection = UnixHTTPConnection(DOCKER_SOCKET) + try: + connection.request(method, path) + response = connection.getresponse() + body = response.read() + finally: + connection.close() + + if response.status >= 300: + detail = body.decode("utf-8", errors="replace") + raise RuntimeError( + f"Docker API {method} {path} returned {response.status}: {detail}" + ) + return body + + +def main(): + services = sys.argv[1:] + if not services: + print(f"Usage: {sys.argv[0]} [ ...]", file=sys.stderr) + return 2 + if not os.path.exists(DOCKER_SOCKET): + print(f"Error: Docker socket not found at {DOCKER_SOCKET}", file=sys.stderr) + return 1 + + container_name = socket.gethostname() + container_path = urllib.parse.quote(container_name, safe="") + container = json.loads( + docker_request("GET", f"/containers/{container_path}/json") + ) + labels = container.get("Config", {}).get("Labels", {}) or {} + project = labels.get("com.docker.compose.project") + if not project: + print( + "Error: Current container has no com.docker.compose.project label", + file=sys.stderr, + ) + return 1 + + failed = False + for service in services: + filters = json.dumps( + { + "label": [ + f"com.docker.compose.project={project}", + f"com.docker.compose.service={service}", + ] + } + ) + query = urllib.parse.urlencode({"all": "1", "filters": filters}) + containers = json.loads(docker_request("GET", f"/containers/json?{query}")) + if not containers: + print( + f"Error: No {service!r} container found in Compose project {project!r}", + file=sys.stderr, + ) + failed = True + continue + + for sibling in containers: + container_id = sibling["Id"] + docker_request("POST", f"/containers/{container_id}/restart?t=10") + print(f"Restarted Compose service {project}/{service}") + + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tutorials/accelerated-python/brev/docker-compose.yml b/tutorials/accelerated-python/brev/docker-compose.yml index f46134ac..b9a17139 100644 --- a/tutorials/accelerated-python/brev/docker-compose.yml +++ b/tutorials/accelerated-python/brev/docker-compose.yml @@ -19,7 +19,6 @@ x-config: count: all capabilities: [gpu] common-env: &common-env - BREV_ENV_ID: ${BREV_ENV_ID:-} ACH_TUTORIAL: *tutorial-name ACH_RUN_TESTS: ${ACH_RUN_TESTS:-} ACH_TEST_ARGS: ${ACH_TEST_ARGS:-} diff --git a/tutorials/accelerated-python/brev/requirements.txt b/tutorials/accelerated-python/brev/requirements.txt index 44f7c95f..6a735f36 100644 --- a/tutorials/accelerated-python/brev/requirements.txt +++ b/tutorials/accelerated-python/brev/requirements.txt @@ -13,7 +13,6 @@ jupyter-dark-detect == 0.1.0 jupyter == 1.1.1 jupyter-server-proxy == 4.4.0 jupyterlab == 4.5.7 -jupyterlab-nvidia-nsight == 1.0.0 jupyterlab-execute-time == 3.3.0 # CUDA diff --git a/tutorials/cuda-cpp/brev/docker-compose.yml b/tutorials/cuda-cpp/brev/docker-compose.yml index d31dacd6..55ea4b24 100644 --- a/tutorials/cuda-cpp/brev/docker-compose.yml +++ b/tutorials/cuda-cpp/brev/docker-compose.yml @@ -18,7 +18,6 @@ x-config: count: all capabilities: [gpu] common-env: &common-env - BREV_ENV_ID: ${BREV_ENV_ID:-} ACH_TUTORIAL: *tutorial-name ACH_RUN_TESTS: ${ACH_RUN_TESTS:-} ACH_TEST_ARGS: ${ACH_TEST_ARGS:-} diff --git a/tutorials/cuda-cpp/brev/requirements.txt b/tutorials/cuda-cpp/brev/requirements.txt index c2ffd9b2..0b08b0c2 100644 --- a/tutorials/cuda-cpp/brev/requirements.txt +++ b/tutorials/cuda-cpp/brev/requirements.txt @@ -7,7 +7,6 @@ jupyter-dark-detect == 0.1.0 # Jupyter jupyter jupyter-server-proxy -jupyterlab-nvidia-nsight jupyterlab-execute-time # NVIDIA devtools diff --git a/tutorials/cuda-tile/brev/docker-compose.yml b/tutorials/cuda-tile/brev/docker-compose.yml index aafc747e..cc765a65 100644 --- a/tutorials/cuda-tile/brev/docker-compose.yml +++ b/tutorials/cuda-tile/brev/docker-compose.yml @@ -19,7 +19,6 @@ x-config: count: all capabilities: [gpu] common-env: &common-env - BREV_ENV_ID: ${BREV_ENV_ID:-} ACH_TUTORIAL: *tutorial-name ACH_RUN_TESTS: ${ACH_RUN_TESTS:-} ACH_TEST_ARGS: ${ACH_TEST_ARGS:-} diff --git a/tutorials/cuda-tile/brev/requirements.txt b/tutorials/cuda-tile/brev/requirements.txt index 775475ea..6db125f7 100644 --- a/tutorials/cuda-tile/brev/requirements.txt +++ b/tutorials/cuda-tile/brev/requirements.txt @@ -13,7 +13,6 @@ jupyter-dark-detect == 0.1.0 jupyter jupyter-server-proxy jupyterlab -jupyterlab-nvidia-nsight jupyterlab-execute-time # CUDA diff --git a/tutorials/floating-point-emulation/brev/docker-compose.yml b/tutorials/floating-point-emulation/brev/docker-compose.yml index 8574267f..f1c0c703 100644 --- a/tutorials/floating-point-emulation/brev/docker-compose.yml +++ b/tutorials/floating-point-emulation/brev/docker-compose.yml @@ -25,7 +25,6 @@ x-config: - accelerated-computing-hub:/accelerated-computing-hub - /var/run/docker.sock:/var/run/docker.sock environment: - BREV_ENV_ID: ${BREV_ENV_ID:-} ACH_TUTORIAL: *tutorial-name ACH_RUN_TESTS: ${ACH_RUN_TESTS:-} ACH_USER: ${ACH_USER:-ach} diff --git a/tutorials/floating-point-emulation/brev/requirements.txt b/tutorials/floating-point-emulation/brev/requirements.txt index cc6009f5..fd85fe66 100644 --- a/tutorials/floating-point-emulation/brev/requirements.txt +++ b/tutorials/floating-point-emulation/brev/requirements.txt @@ -15,7 +15,6 @@ matplotlib # Jupyter jupyterlab -jupyterlab-nvidia-nsight jupyterlab-execute-time ipywidgets ipykernel diff --git a/tutorials/gpu-deployment/gpu-deployment-from-scratch.md b/tutorials/gpu-deployment/gpu-deployment-from-scratch.md index f22c8003..83732f91 100644 --- a/tutorials/gpu-deployment/gpu-deployment-from-scratch.md +++ b/tutorials/gpu-deployment/gpu-deployment-from-scratch.md @@ -501,7 +501,6 @@ To be able to visualize the file, we can download it an use [nsight-systems](htt Further reading: - [Nsight Documentation](https://developer.nvidia.com/nsight-systems/get-started) -- [Jupyter Lab Nsight extension](https://pypi.org/project/jupyterlab-nvidia-nsight/) - [Towards Data Science community guide](https://medium.com/data-science/profiling-cuda-using-nsight-systems-a-numba-example-fc65003f8c52) diff --git a/tutorials/nvmath-python/brev/docker-compose.yml b/tutorials/nvmath-python/brev/docker-compose.yml index f996db88..58479e8c 100644 --- a/tutorials/nvmath-python/brev/docker-compose.yml +++ b/tutorials/nvmath-python/brev/docker-compose.yml @@ -19,7 +19,6 @@ x-config: count: all capabilities: [gpu] common-env: &common-env - BREV_ENV_ID: ${BREV_ENV_ID:-} ACH_TUTORIAL: *tutorial-name ACH_RUN_TESTS: ${ACH_RUN_TESTS:-} ACH_TEST_ARGS: ${ACH_TEST_ARGS:-} diff --git a/tutorials/nvmath-python/brev/requirements.txt b/tutorials/nvmath-python/brev/requirements.txt index a59d68fd..82327032 100644 --- a/tutorials/nvmath-python/brev/requirements.txt +++ b/tutorials/nvmath-python/brev/requirements.txt @@ -22,7 +22,6 @@ jupyter-dark-detect == 0.1.0 # Jupyter jupyter-server-proxy jupyterlab -jupyterlab-nvidia-nsight jupyterlab-execute-time ipywidgets ipykernel diff --git a/tutorials/stdpar/brev/docker-compose.yml b/tutorials/stdpar/brev/docker-compose.yml index ae619d12..78b6babd 100644 --- a/tutorials/stdpar/brev/docker-compose.yml +++ b/tutorials/stdpar/brev/docker-compose.yml @@ -19,7 +19,6 @@ x-config: count: all capabilities: [gpu] common-env: &common-env - BREV_ENV_ID: ${BREV_ENV_ID:-} ACH_TUTORIAL: *tutorial-name ACH_RUN_TESTS: ${ACH_RUN_TESTS:-} ACH_TEST_ARGS: ${ACH_TEST_ARGS:-} diff --git a/tutorials/stdpar/brev/requirements.txt b/tutorials/stdpar/brev/requirements.txt index 65e05bf5..b6d93ece 100644 --- a/tutorials/stdpar/brev/requirements.txt +++ b/tutorials/stdpar/brev/requirements.txt @@ -18,7 +18,6 @@ conan # Jupyter jupyter jupyter-server-proxy -jupyterlab-nvidia-nsight jupyterlab-execute-time # NVIDIA devtools diff --git a/tutorials/warp/brev/docker-compose.yml b/tutorials/warp/brev/docker-compose.yml index 99b3e4ad..1d2afdac 100644 --- a/tutorials/warp/brev/docker-compose.yml +++ b/tutorials/warp/brev/docker-compose.yml @@ -19,7 +19,6 @@ x-config: count: all capabilities: [gpu] common-env: &common-env - BREV_ENV_ID: ${BREV_ENV_ID:-} ACH_TUTORIAL: *tutorial-name ACH_RUN_TESTS: ${ACH_RUN_TESTS:-} ACH_TEST_ARGS: ${ACH_TEST_ARGS:-}