Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: redirect MAPDL console output to a file #3596

Merged
merged 10 commits into from
Dec 13, 2024
1 change: 1 addition & 0 deletions doc/changelog.d/3596.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feat: redirect MAPDL console output to a file
49 changes: 39 additions & 10 deletions src/ansys/mapdl/core/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import subprocess # nosec B404
import threading
import time
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union
import warnings

import psutil
Expand Down Expand Up @@ -115,6 +115,7 @@
"license_type",
"log_apdl",
"loglevel",
"mapdl_output",
"mode",
"nproc",
"override",
Expand Down Expand Up @@ -437,6 +438,7 @@
run_location: str = None,
env_vars: Optional[Dict[str, str]] = None,
launch_on_hpc: bool = False,
mapdl_output: Optional[str] = None,
) -> subprocess.Popen:
"""Start MAPDL locally in gRPC mode.

Expand All @@ -456,6 +458,9 @@
If running on an HPC, this needs to be :class:`True` to avoid the
temporary file creation on Windows.

mapdl_output : str, optional
Whether redirect MAPDL console output (stdout and stderr) to a file.

Returns
-------
subprocess.Popen
Expand Down Expand Up @@ -487,6 +492,13 @@
"\n============"
)

if mapdl_output:
stdout = open(str(mapdl_output), "wb", 0)
stderr = subprocess.STDOUT

Check warning on line 497 in src/ansys/mapdl/core/launcher.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/launcher.py#L496-L497

Added lines #L496 - L497 were not covered by tests
else:
stdout = subprocess.PIPE
stderr = subprocess.PIPE

if os.name == "nt":
# getting tmp file name
if not launch_on_hpc:
Expand All @@ -505,8 +517,8 @@
shell=shell, # sbatch does not work without shell.
cwd=run_location,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=stdout,
stderr=stderr,
env_vars=env_vars,
)

Expand Down Expand Up @@ -554,7 +566,7 @@

if os.name == "posix" and not ON_WSL:
LOG.debug("Checking if gRPC server is alive.")
_check_server_is_alive(stdout_queue, run_location, timeout)
_check_server_is_alive(stdout_queue, timeout)

Check warning on line 569 in src/ansys/mapdl/core/launcher.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/launcher.py#L569

Added line #L569 was not covered by tests

except MapdlDidNotStart as e: # pragma: no cover
msg = (
Expand Down Expand Up @@ -596,7 +608,11 @@
raise MapdlDidNotStart(msg)


def _check_server_is_alive(stdout_queue, run_location, timeout):
def _check_server_is_alive(stdout_queue, timeout):
if not stdout_queue:
LOG.debug("No STDOUT queue. Not checking MAPDL this way.")
return

t0 = time.time()
empty_attemps = 3
empty_i = 0
Expand Down Expand Up @@ -629,6 +645,9 @@


def _get_std_output(std_queue, timeout=1):
if not std_queue:
return [None]

lines = []
reach_empty = False
t0 = time.time()
Expand All @@ -642,10 +661,15 @@
return lines


def _create_queue_for_std(std):
def _create_queue_for_std(
std: subprocess.PIPE,
) -> Tuple[Optional[Queue[str]], Optional[threading.Thread]]:
"""Create a queue and thread objects for a given PIPE std"""
if not std:
LOG.debug("No STDOUT. Not checking MAPDL this way.")
return None, None

def enqueue_output(out, queue):
def enqueue_output(out: subprocess.PIPE, queue: Queue[str]) -> None:

Check warning on line 672 in src/ansys/mapdl/core/launcher.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/launcher.py#L672

Added line #L672 was not covered by tests
try:
for line in iter(out.readline, b""):
queue.put(line)
Expand All @@ -655,16 +679,16 @@
# ValueError: PyMemoryView_FromBuffer(): info -> buf must not be NULL
pass

q = Queue()
t = threading.Thread(target=enqueue_output, args=(std, q))
q: Queue[str] = Queue()
t: threading.Thread = threading.Thread(target=enqueue_output, args=(std, q))

Check warning on line 683 in src/ansys/mapdl/core/launcher.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/mapdl/core/launcher.py#L682-L683

Added lines #L682 - L683 were not covered by tests
t.daemon = True # thread dies with the program
t.start()

return q, t


def launch_remote_mapdl(
version: str = None,
version: Optional[str] = None,
cleanup_on_exit: bool = True,
) -> MapdlGrpc:
"""Start MAPDL remotely using the product instance management API.
Expand Down Expand Up @@ -1020,6 +1044,7 @@
version: Optional[Union[int, str]] = None,
running_on_hpc: bool = True,
launch_on_hpc: bool = False,
mapdl_output: Optional[str] = None,
**kwargs: Dict[str, Any],
) -> Union[MapdlGrpc, "MapdlConsole"]:
"""Start MAPDL locally.
Expand Down Expand Up @@ -1205,6 +1230,9 @@
to specify the scheduler arguments as a string or as a dictionary.
For more information, see :ref:`ref_hpc_slurm`.

mapdl_output : str, optional
Redirect the MAPDL console output to a given file.

kwargs : dict, Optional
These keyword arguments are interface-specific or for
development purposes. For more information, see Notes.
Expand Down Expand Up @@ -1575,6 +1603,7 @@
run_location=args["run_location"],
env_vars=env_vars,
launch_on_hpc=args.get("launch_on_hpc"),
mapdl_output=args.get("mapdl_output"),
)

if args["launch_on_hpc"]:
Expand Down
4 changes: 2 additions & 2 deletions src/ansys/mapdl/core/mapdl_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ def _read_stds(self):
_get_std_output, # Avoid circular import error
)

if self._mapdl_process is None:
if self._mapdl_process is None or not self._mapdl_process.stdout:
return

self._log.debug("Reading stdout")
Expand Down Expand Up @@ -2701,7 +2701,7 @@ def _download_as_raw(self, target_name: str) -> str:
@property
def is_alive(self) -> bool:
"""True when there is an active connect to the gRPC server"""
if self.channel_state not in ["IDLE", "READY"]:
if self.channel_state not in ["IDLE", "READY", None]:
self._log.debug(
"MAPDL instance is not alive because the channel is not 'IDLE' o 'READY'."
)
Expand Down
57 changes: 57 additions & 0 deletions tests/test_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -1947,3 +1947,60 @@ def raising():
@patch("ansys.mapdl.core.launcher.check_valid_ansys", raising)
def test_check_has_mapdl_failed():
assert check_has_mapdl() is False


@requires("local")
@patch("ansys.mapdl.core.launcher._is_ubuntu", lambda *args, **kwargs: True)
@patch("ansys.mapdl.core.launcher.check_mapdl_launch", lambda *args, **kwargs: None)
def test_mapdl_output_pass_arg(tmpdir):
def submitter(*args, **kwargs):
from _io import FileIO

# Checking we are passing the arguments
assert isinstance(kwargs["stdout"], FileIO)
assert kwargs["stderr"] is subprocess.STDOUT

return

with patch("ansys.mapdl.core.launcher.submitter", submitter) as mck_sub:
mapdl_output = os.path.join(tmpdir, "apdl.txt")
args = launch_mapdl(just_launch=True, mapdl_output=mapdl_output)

assert isinstance(args, list)


@requires("local")
@requires("nostudent")
def test_mapdl_output(tmpdir):
mapdl_output = os.path.join(tmpdir, "apdl.txt")
mapdl = launch_mapdl(mapdl_output=mapdl_output, port=50058)

assert os.path.exists(mapdl_output)

mapdl.prep7()
mapdl.exit(force=True)

with open(mapdl_output, "r") as fid:
content = fid.read()

assert "Beta activation of the GRPC server." in content
assert "### START GRPC SERVER ###" in content
assert "Server listening on" in content


def test_check_server_is_alive_no_queue():
from ansys.mapdl.core.launcher import _check_server_is_alive

assert _check_server_is_alive(None, 30) is None


def test_get_std_output_no_queue():
from ansys.mapdl.core.launcher import _get_std_output

assert _get_std_output(None, 30) == [None]


def test_create_queue_for_std_no_queue():
from ansys.mapdl.core.launcher import _create_queue_for_std

assert _create_queue_for_std(None) == (None, None)
Loading