Skip to content

Commit 95a8e97

Browse files
author
OutlyingWest
committed
preserve subprocess output in variables to show later; hint message corrected
1 parent 0d4d863 commit 95a8e97

3 files changed

Lines changed: 42 additions & 18 deletions

File tree

src/scorep_jupyter/kernel.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import time
1111
from enum import Enum
1212
from textwrap import dedent
13-
from typing import IO, AnyStr, Callable
13+
from typing import IO, AnyStr, Callable, List, TextIO
1414

1515
from ipykernel.ipkernel import IPythonKernel
1616

@@ -572,7 +572,6 @@ async def scorep_execute(
572572
KernelErrorCode.PERSISTENCE_LOAD_FAIL,
573573
direction="Score-P -> Jupyter",
574574
optional_hint = get_scorep_process_error_hint()
575-
576575
)
577576
return self.standard_reply()
578577

@@ -665,62 +664,78 @@ def start_reading_scorep_process_streams(
665664
process_busy_spinner = create_busy_spinner(
666665
stdout_lock, spinner_stop_event, is_multicell_final
667666
)
667+
668+
captured_stdout: List[str] = []
669+
captured_stderr: List[str] = [] # Output parameter (return not possible from thread)
668670
t_stderr = threading.Thread(
669671
target=self.read_scorep_stderr,
670-
args=(proc.stderr, stdout_lock, spinner_stop_event),
672+
args=(proc.stderr, stdout_lock, spinner_stop_event, captured_stderr),
671673
)
672674

673675
# Empty cell output, required for interactive output
674676
# e.g. tqdm for-loop progress bar
675677
self.cell_output("\0")
676678

679+
spinner_message = "Done."
680+
677681
try:
678682
process_busy_spinner.start("Process is running...")
679683
t_stderr.start()
680684

681-
self.read_scorep_stdout(
685+
captured_stdout = self.read_scorep_stdout(
682686
proc.stdout, stdout_lock, spinner_stop_event
683687
)
684688

685-
process_busy_spinner.stop("Done.")
686-
687689
except KeyboardInterrupt:
688-
process_busy_spinner.stop("Kernel interrupted.")
690+
spinner_message = "Kernel interrupted."
689691
finally:
690692
t_stderr.join()
693+
process_busy_spinner.stop(spinner_message)
694+
695+
# Handle recorded output (in case if it is suppressed by spinner animation)
696+
self.handle_captured_output(captured_stdout, stream="stdout")
697+
self.handle_captured_output(captured_stderr, stream="stderr")
691698

692699
def read_scorep_stdout(
693700
self,
694701
stdout: IO[AnyStr],
695702
lock: threading.Lock,
696703
spinner_stop_event: threading.Event,
697704
read_chunk_size=64,
698-
):
705+
) -> List[str]:
699706
line_width = 50
700707
clear_line = "\r" + " " * line_width + "\r"
701708

709+
captured_stdout: List[str] = []
710+
702711
def process_stdout_line(line: str):
703712
if spinner_stop_event.is_set():
704713
sys.stdout.write(clear_line)
705714
sys.stdout.flush()
706715
self.cell_output(line)
716+
else:
717+
captured_stdout.append(line)
707718

708719
self.read_scorep_stream(
709720
stdout, lock, process_stdout_line, read_chunk_size
710721
)
722+
return captured_stdout
711723

712724
def read_scorep_stderr(
713725
self,
714726
stderr: IO[AnyStr],
715727
lock: threading.Lock,
716728
spinner_stop_event: threading.Event,
729+
captured_stderr: List[str],
717730
read_chunk_size=64,
718731
):
732+
719733
def process_stderr_line(line: str):
720-
self.log.error(line.strip())
721734
if spinner_stop_event.is_set():
722-
self.cell_output(line)
723-
735+
self.log.error(line.strip())
736+
self.cell_output(line, 'stderr')
737+
else:
738+
captured_stderr.append(line)
724739

725740
self.read_scorep_stream(
726741
stderr, lock, process_stderr_line, read_chunk_size
@@ -752,6 +767,17 @@ def read_scorep_stream(
752767
with lock:
753768
process_line(line)
754769

770+
def handle_captured_output(self, output: List[str], stream: str):
771+
if output:
772+
text_output = "".join(output)
773+
if stream == "stdout":
774+
self.cell_output(text_output, stream=stream)
775+
elif stream == "stderr":
776+
self.cell_output(text_output, stream=stream)
777+
self.log.error(text_output)
778+
else:
779+
self.log.error(f"Undefined stream type: {stream}")
780+
755781
async def do_execute(
756782
self,
757783
code,

src/scorep_jupyter/kernel_messages.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,7 @@ def get_scorep_process_error_hint():
4848
scorep_process_error_hint = ""
4949
if is_spinner_enabled:
5050
scorep_process_error_hint = (
51-
"\nHint: If the animation spinner is active, "
52-
"runtime errors in Score-P cells might be hidden.\n"
53-
"Try disabling the spinner with "
54-
"%env SCOREP_JUPYTER_DISABLE_PROCESSING_ANIMATIONS=1 "
55-
f"and/or check the log: "
56-
f"{LOGGING['handlers']['error_file']['filename']} for details."
51+
"\nHint: full error info saved to log file: "
52+
f"{LOGGING['handlers']['error_file']['filename']}"
5753
)
5854
return scorep_process_error_hint

src/scorep_jupyter/logging_config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from pathlib import Path
55

66

7-
PROJECT_ROOT = Path(__file__).resolve().parent.parent
7+
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
8+
print(f'{Path(__file__).as_uri()=}')
9+
print(f'{PROJECT_ROOT=}')
810
LOGGING_DIR = PROJECT_ROOT / "logs_scorep_jupyter"
911
os.makedirs(LOGGING_DIR, exist_ok=True)
1012

0 commit comments

Comments
 (0)