diff --git a/.gitignore b/.gitignore index dd4f63d..6315184 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,15 @@ __pycache__/ build logs_scorep_jupyter/ **/*.egg-info + +# PyCharm +.idea/ +.ipynb_checkpoints/ + +# Jupyter Notebook +.ipynb_checkpoints + +# Ignore all result directories inside examples/ at any depth +examples/**/scorep-*/ + + diff --git a/README.md b/README.md index f5b808f..9fdad2a 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,14 @@ For binding to Score-P, the kernel uses the [Score-P Python bindings](https://gi - [Usage](#usage) - [Score-P Instrumentation](#score-p-instrumentation) - [Configuring Score-P in Jupyter](#configuring-score-p-in-jupyter) + - [Vampir Launch Control](#vampir-launch-control) - [Multi-Cell Mode](#multi-cell-mode) - [Write Mode](#write-mode) + - [Logging Configuration](#logging-configuration) - [Presentation of Performance Data](#presentation-of-performance-data) - [Limitations](#limitations) - [Serialization Type Support](#serialization-type-support) - [Overhead](#overhead) - - [Logging Configuration](#logging-configuration) - [Future Work](#future-work) - [Citing](#citing) - [Contact](#contact) @@ -131,7 +132,22 @@ Executes a cell with Score-P, i.e. it calls `python -m scorep ` ![](doc/instrumentation.gif) +### Vampir Launch Control + +To automatically launch **Vampir** after a cell with Score-P instrumentation, use: + +``` +%%enable_vampir_launch_on_scorep_instrumented +``` +This will cause the kernel to open `traces.otf2` in Vampir (if found) after the next instrumented cell. +To disable this behavior again: + +``` +%%disable_vampir_launch +``` + +By default, Vampir launching is disabled. You must enable it explicitly when needed. ## Multi-Cell Mode You can also treat multiple cells as one single cell by using the multi cell mode. Therefore you can mark the cells in the order you wish to execute them. @@ -186,6 +202,23 @@ Stops the marking process and writes the marked cells in a Python script. Additi ![](doc/writemode.gif) +## Logging Configuration +To adjust logging and obtain more detailed output about the behavior of the scorep_jupyter kernel, refer to the `src/logging_config.py` file. +This file contains configuration options for controlling the verbosity, format, and destination of log messages. You can customize it to suit your debugging needs. + +Log files are stored in the following directory: +``` +scorep_jupyter_kernel_python/ +├── logs_scorep_jupyter/ +│ ├── debug.log +│ ├── info.log +└── └── error.log +``` +In some cases, you may want to suppress tqdm messages that are saved to error.log (since tqdm outputs to stderr). This can be done using the following environment variable: +``` +%env TQDM_DISABLE=1 +``` + # Presentation of Performance Data @@ -205,23 +238,6 @@ Similar yields for cloudpickle. Use the `%%marshalling_settings` magic command t When dealing with big data structures, there might be a big runtime overhead at the beginning and the end of a Score-P cell. This is due to additional data saving and loading processes for persistency in the background. However this does not affect the actual user code and the Score-P measurements. -## Logging Configuration -To adjust logging and obtain more detailed output about the behavior of the scorep_jupyter kernel, refer to the `src/logging_config.py` file. -This file contains configuration options for controlling the verbosity, format, and destination of log messages. You can customize it to suit your debugging needs. - -Log files are stored in the following directory: -``` -scorep_jupyter_kernel_python/ -├── logs_scorep_jupyter/ -│ ├── debug.log -│ ├── info.log -└── └── error.log -``` -In some cases, you may want to suppress tqdm messages that are saved to error.log (since tqdm outputs to stderr). This can be done using the following environment variable: -``` -%env TQDM_DISABLE=1 -``` - # Future Work The kernel is still under development. diff --git a/src/scorep_jupyter/kernel.py b/src/scorep_jupyter/kernel.py index 2258b81..6bcd488 100644 --- a/src/scorep_jupyter/kernel.py +++ b/src/scorep_jupyter/kernel.py @@ -100,7 +100,7 @@ def __init__(self, **kwargs): importlib.import_module("scorep") except ModuleNotFoundError: self.scorep_python_available_ = False - + self.launch_vampir_requested = False logging.config.dictConfig(LOGGING) self.log = logging.getLogger("kernel") @@ -634,8 +634,12 @@ async def scorep_execute( f"Instrumentation results can be found in " f"{os.getcwd()}/{scorep_folder}" ) - self.pershelper.postprocess() + + # Optional Vampir launch + if self.launch_vampir_requested and scorep_folder: + self.try_launch_vampir(scorep_folder) + return self.standard_reply() def start_reading_scorep_process_streams( @@ -786,6 +790,38 @@ def handle_captured_output(self, output: List[str], stream: str): else: self.log.error(f"Undefined stream type: {stream}") + def try_launch_vampir(self, scorep_folder: str): + """ + Attempts to find traces.otf2 and launch Vampir on it. + Errors are logged using log_error(). + """ + trace_path = None + for root, dirs, files in os.walk(scorep_folder): + if "traces.otf2" in files: + trace_path = os.path.join(root, "traces.otf2") + break + + if not trace_path or not os.path.isfile(trace_path): + self.log_error( + KernelErrorCode.INSTRUMENTATION_PATH_UNKNOWN, + scorep_folder=scorep_folder, + ) + return + + if shutil.which("vampir") is None: + self.log_error(KernelErrorCode.VAMPIR_NOT_FOUND) + return + + try: + subprocess.run(["pkill", "-f", "vampir"], check=False) + self.cell_output(f"\nLaunching Vampir: {trace_path}") + subprocess.Popen(["vampir", trace_path]) + except Exception as e: + self.log_error( + KernelErrorCode.VAMPIR_LAUNCH_FAILED, + exception=str(e), + ) + async def do_execute( self, code, @@ -861,6 +897,18 @@ async def do_execute( return self.scorep_not_available() or self.abort_writefile() elif code.startswith("%%end_writefile"): return self.scorep_not_available() or self.end_writefile() + + elif code.startswith("%%enable_vampir_launch_on_scorep_instrumented"): + self.launch_vampir_requested = True + if shutil.which("vampir") is None: + self.log_error(KernelErrorCode.VAMPIR_NOT_FOUND) + else: + self.cell_output("Vampir will be launched after next instrumented execution.") + return self.standard_reply() + elif code.startswith("%%disable_vampir_launch"): + self.launch_vampir_requested = False + self.cell_output("Vampir launching disabled.") + return self.standard_reply() elif code.startswith("%%execute_with_scorep"): scorep_missing = self.scorep_not_available() if scorep_missing is None: diff --git a/src/scorep_jupyter/kernel_messages.py b/src/scorep_jupyter/kernel_messages.py index bd5a3fa..d22cc78 100644 --- a/src/scorep_jupyter/kernel_messages.py +++ b/src/scorep_jupyter/kernel_messages.py @@ -1,4 +1,3 @@ -import os from enum import Enum, auto from .logging_config import LOGGING @@ -12,6 +11,8 @@ class KernelErrorCode(Enum): SCOREP_SUBPROCESS_FAIL = auto() SCOREP_NOT_AVAILABLE = auto() SCOREP_PYTHON_NOT_AVAILABLE = auto() + VAMPIR_NOT_FOUND = auto() + VAMPIR_LAUNCH_FAILED = auto() KERNEL_ERROR_MESSAGES = { @@ -38,6 +39,18 @@ class KernelErrorCode(Enum): "[mode: {mode}] Subprocess terminated unexpectedly. " "Persistence not recorded (marshaller: {marshaller})." ), + KernelErrorCode.INSTRUMENTATION_PATH_UNKNOWN: ( + "Instrumentation output directory not found or missing traces.otf2 " + "(looked in: {scorep_folder})" + ), + KernelErrorCode.VAMPIR_NOT_FOUND: ( + 'Vampir binary not found in PATH. Add it to PATH to enable automatic launch' + ' (e.g. in ~/.bashrc: export PATH="/path/to/vampir/bin:$PATH"' + ), + + KernelErrorCode.VAMPIR_LAUNCH_FAILED: ( + "Failed to launch Vampir: {exception}" + ), } diff --git a/src/scorep_jupyter/logging_config.py b/src/scorep_jupyter/logging_config.py index baed6b8..8fb8007 100644 --- a/src/scorep_jupyter/logging_config.py +++ b/src/scorep_jupyter/logging_config.py @@ -5,8 +5,6 @@ PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent -print(f"{Path(__file__).as_uri()=}") -print(f"{PROJECT_ROOT=}") LOGGING_DIR = PROJECT_ROOT / "logs_scorep_jupyter" os.makedirs(LOGGING_DIR, exist_ok=True) diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 0aaab6a..51738d3 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -186,6 +186,8 @@ def test_error_templates_are_formatable(self): "detail": "dummy_detail", "step": "dummy_step", "optional_hint": "dummy_optional_hint", + "scorep_folder": "/fake/path/to/scorep-dir", + "exception": "dummy_exception", } for code, template in KERNEL_ERROR_MESSAGES.items():