diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index f09e15d225..d8f10e35c1 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -14,11 +14,13 @@ body: id: new-bug attributes: label: Before submitting the issue - description: Please, make sure the following conditions are met + description: Ensure that the following conditions are met: options: - - label: I have searched among the existing issues + - label: I have visited the [Troubleshooting section](https://mapdl.docs.pyansys.com/troubleshoot/index.html). required: true - - label: I am using a Python virtual environment + - label: I have searched among the existing issues. + required: true + - label: I am using a Python virtual environment. required: true - label: I have a fully updated virtual environment (i.e. ``pip install --upgrade --upgrade-strategy eager ansys-mapdl-core``) required: true @@ -134,4 +136,28 @@ body: validations: - required: true \ No newline at end of file + required: true + + - type: textarea + id: logger_log + attributes: + label: Logger output file + description: | + Attach the logger output file. For more information on how to set the logger and + attach its output file, see the [Troubleshooting section] + (https://mapdl.docs.pyansys.com/troubleshoot/index.html). + + value: | +
+ Show the logger output file. + + + ```text + + # PASTE HERE THE CONTENT OF THE LOGGER OUTPUT FILE. + + ``` +
+ + validations: + required: false \ No newline at end of file diff --git a/doc/source/troubleshoot/index.rst b/doc/source/troubleshoot/index.rst index ca677272d0..7453f931da 100644 --- a/doc/source/troubleshoot/index.rst +++ b/doc/source/troubleshoot/index.rst @@ -15,6 +15,33 @@ some of the most common problems and frequently asked questions are posted here. faq +Debug in PyMAPDL +---------------- + +If you are having trouble with PyMAPDL, you can examine the content +of the output file to help to identify any issue. + +You can set the logger output file to be ``mylog.log`` by +running the following commands in a Python terminal or at the beginning of your +script: + +.. code:: python + + from ansys.mapdl.core import LOG + LOG.setLevel("DEBUG") + LOG.log_to_file("mylog.log") + + from ansys.mapdl.core import launch_mapdl + + mapdl = launch_mapdl(loglevel="DEBUG") + +You can attach this file to a bug report in the PyMAPDL GitHub repository for further investigation. +If you are not able to identify the issue, you can open a discussion on the +`PyMAPDL Discussions page `_. +If you believe you have found a bug, open an issue on the +`PyMAPDL Issues page `_. + + More help needed? ----------------- diff --git a/src/ansys/mapdl/core/__init__.py b/src/ansys/mapdl/core/__init__.py index 30c20e2c01..e420c7be8c 100644 --- a/src/ansys/mapdl/core/__init__.py +++ b/src/ansys/mapdl/core/__init__.py @@ -11,6 +11,9 @@ _LOCAL_PORTS = [] +LINUX_DEFAULT_DIRS = [["/", "usr", "ansys_inc"], ["/", "ansys_inc"]] +LINUX_DEFAULT_DIRS = [os.path.join(*each) for each in LINUX_DEFAULT_DIRS] + # Per contract with Sphinx-Gallery, this method must be available at top level try: from pyvista.utilities.sphinx_gallery import _get_sg_image_scraper diff --git a/src/ansys/mapdl/core/errors.py b/src/ansys/mapdl/core/errors.py index 0e383f330a..1d71c52eaa 100644 --- a/src/ansys/mapdl/core/errors.py +++ b/src/ansys/mapdl/core/errors.py @@ -89,8 +89,15 @@ def __init__(self, msg=""): RuntimeError.__init__(self, msg) +class MapdlConnectionError(RuntimeError): + """Provides the error when connecting to the MAPDL instance fails.""" + + def __init__(self, msg=""): + RuntimeError.__init__(self, msg) + + class LicenseServerConnectionError(MapdlDidNotStart): - """Error when the license server is not available.""" + """Provides the error when the license server is not available.""" def __init__(self, msg=""): MapdlDidNotStart.__init__(self, msg) diff --git a/src/ansys/mapdl/core/launcher.py b/src/ansys/mapdl/core/launcher.py index fe311161db..c5cc91e7c6 100644 --- a/src/ansys/mapdl/core/launcher.py +++ b/src/ansys/mapdl/core/launcher.py @@ -21,7 +21,7 @@ import appdirs from ansys.mapdl import core as pymapdl -from ansys.mapdl.core import LOG +from ansys.mapdl.core import LINUX_DEFAULT_DIRS, LOG from ansys.mapdl.core._version import SUPPORTED_ANSYS_VERSIONS from ansys.mapdl.core.errors import LockFileException, MapdlDidNotStart, VersionError from ansys.mapdl.core.licensing import ALLOWABLE_LICENSES, LicenseChecker @@ -42,6 +42,7 @@ if not os.path.isdir(SETTINGS_DIR): try: os.makedirs(SETTINGS_DIR) + LOG.debug(f"Created settings directory: {SETTINGS_DIR}") except: warnings.warn( "Unable to create settings directory.\n" @@ -51,6 +52,7 @@ CONFIG_FILE = os.path.join(SETTINGS_DIR, "config.txt") ALLOWABLE_MODES = ["corba", "console", "grpc"] + LOCALHOST = "127.0.0.1" MAPDL_DEFAULT_PORT = 50052 @@ -401,14 +403,17 @@ def launch_grpc( >>> mapdl = launch_mapdl(exec_file, additional_switches='-smp') """ + LOG.debug("Starting 'launch_mapdl'.") # disable all MAPDL pop-up errors: os.environ["ANS_CMD_NODIAG"] = "TRUE" # use temporary directory if run_location is unspecified if run_location is None: run_location = create_temp_dir() + LOG.debug(f"Using temporary directory for MAPDL run location: {run_location}") elif not os.path.isdir(run_location): os.mkdir(run_location) + LOG.debug(f"Creating directory for MAPDL run location: {run_location}") if not os.access(run_location, os.W_OK): raise IOError('Unable to write to ``run_location`` "%s"' % run_location) @@ -424,21 +429,26 @@ def launch_grpc( if port is None: if not pymapdl._LOCAL_PORTS: port = MAPDL_DEFAULT_PORT + LOG.debug(f"Using default port: {port}") else: port = max(pymapdl._LOCAL_PORTS) + 1 + LOG.debug(f"Using next available port: {port}") while port_in_use(port) or port in pymapdl._LOCAL_PORTS: port += 1 + LOG.debug(f"Port in use. Incrementing port number. port={port}") pymapdl._LOCAL_PORTS.append(port) # setting ip for the grpc server if ip != LOCALHOST: # Default local ip is 127.0.0.1 create_ip_file(ip, run_location) + LOG.debug(f"Writing ip ({ip}) in 'mylocal.ip' file.") cpu_sw = "-np %d" % nproc if ram: ram_sw = "-m %d" % int(1024 * ram) + LOG.debug(f"Setting RAM: {ram_sw}") else: ram_sw = "" @@ -455,6 +465,7 @@ def launch_grpc( if os.path.isfile(filename): try: os.remove(filename) + LOG.debug(f"Removing temporary error file: {filename}") except: raise IOError( f"Unable to remove {filename}. There might be " @@ -467,6 +478,7 @@ def launch_grpc( tmp_inp = ".__tmp__.inp" with open(os.path.join(run_location, tmp_inp), "w") as f: f.write("FINISH\r\n") + LOG.debug(f"Writing temporary input file: {tmp_inp} with 'FINISH' command.") # must start in batch mode on windows to hide APDL window command_parm = [ @@ -500,6 +512,8 @@ def launch_grpc( ) command = " ".join(command_parm) + LOG.debug(f"Starting MAPDL with command: {command}") + env_vars = update_env_vars(add_env_vars, replace_env_vars) LOG.info(f"Running in {ip}:{port} the following command: '{command}'") @@ -517,6 +531,7 @@ def launch_grpc( stderr=subprocess.DEVNULL, env=env_vars, ) + LOG.debug("MAPDL started in background.") # watch for the creation of temporary files at the run_directory. # This lets us know that the MAPDL process has at least started @@ -528,7 +543,7 @@ def launch_grpc( files = os.listdir(run_location) has_ans = any([filename for filename in files if ".err" in filename]) if has_ans: - LOG.info("MAPDL session successfully started (Error file found)") + LOG.debug("MAPDL session successfully started (Error file found)") break time.sleep(sleep_time) @@ -614,7 +629,14 @@ def get_start_instance(start_instance_default=True): f'Invalid value "{val}" for PYMAPDL_START_INSTANCE\n' 'PYMAPDL_START_INSTANCE should be either "TRUE" or "FALSE"' ) + LOG.debug( + f"PYMAPDL_START_INSTANCE is set to {os.environ['PYMAPDL_START_INSTANCE']}" + ) return os.environ["PYMAPDL_START_INSTANCE"].lower() == "true" + + LOG.debug( + f"PYMAPDL_START_INSTANCE is unset, using default value {start_instance_default}" + ) return start_instance_default @@ -670,6 +692,9 @@ def _get_available_base_ansys(): } if installed_versions: + LOG.debug( + f"Found the following installed Ansys versions: {installed_versions}" + ) return installed_versions else: # pragma: no cover LOG.debug( @@ -682,7 +707,7 @@ def _get_available_base_ansys(): ) return {} elif os.name == "posix": - for path in ["/usr/ansys_inc", "/ansys_inc"]: + for path in LINUX_DEFAULT_DIRS: if os.path.isdir(path): base_path = path else: # pragma: no cover @@ -962,6 +987,7 @@ def warn_uncommon_executable_path(exe_loc): # pragma: no cover def check_lock_file(path, jobname, override): + LOG.debug("Checking for lock file") # Check for lock file lockfile = os.path.join(path, jobname + ".lock") if os.path.isfile(lockfile): @@ -975,6 +1001,7 @@ def check_lock_file(path, jobname, override): else: try: os.remove(lockfile) + LOG.debug("Removed lock file") except PermissionError: raise LockFileException( "Unable to remove lock file. " @@ -1410,6 +1437,9 @@ def launch_mapdl( if ip is None: ip = os.environ.get("PYMAPDL_IP", LOCALHOST) else: # pragma: no cover + LOG.debug( + "Because ``PYMAPDL_IP is not None, an attempt is made to connect to a remote session. ('START_INSTANCE' is set to False.`)" + ) start_instance = False ip = socket.gethostbyname(ip) # Converting ip or hostname to ip @@ -1418,6 +1448,7 @@ def launch_mapdl( if port is None: port = int(os.environ.get("PYMAPDL_PORT", MAPDL_DEFAULT_PORT)) check_valid_port(port) + LOG.debug(f"Using default port {port}") # Start MAPDL with PyPIM if the environment is configured for it # and the user did not pass a directive on how to launch it. @@ -1427,14 +1458,25 @@ def launch_mapdl( # connect to an existing instance if enabled if start_instance is None: + if "PYMAPDL_START_INSTANCE" in os.environ: + LOG.debug("Using PYMAPDL_START_INSTANCE environment variable.") + + if os.environ.get("PYMAPDL_START_INSTANCE") and os.environ.get( + "PYMAPDL_START_INSTANCE" + ).lower() not in ["true", "false"]: + raise ValueError("PYMAPDL_START_INSTANCE must be either True or False.") + start_instance = check_valid_start_instance( os.environ.get("PYMAPDL_START_INSTANCE", True) ) + LOG.debug("Using 'start_instance' equal to %s", start_instance) + # special handling when building the gallery outside of CI. This # creates an instance of mapdl the first time if PYMAPDL start instance # is False. if pymapdl.BUILDING_GALLERY: # pragma: no cover + LOG.debug("Building gallery.") # launch an instance of pymapdl if it does not already exist and # we're allowed to start instances if start_instance and GALLERY_INSTANCE[0] is None: @@ -1474,6 +1516,7 @@ def launch_mapdl( return mapdl if not start_instance: + LOG.debug("Connecting to an existing instance of MAPDL at %s:%s", ip, port) if clear_on_connect is None: # pragma: no cover clear_on_connect = False @@ -1490,6 +1533,7 @@ def launch_mapdl( # verify executable if exec_file is None: + LOG.debug("Using default executable.") # Load cached path exec_file = get_ansys_path() if exec_file is None: @@ -1507,11 +1551,13 @@ def launch_mapdl( # verify run location if run_location is None: + LOG.debug("Using default run location.") temp_dir = tempfile.gettempdir() run_location = os.path.join(temp_dir, "ansys_%s" % random_string(10)) if not os.path.isdir(run_location): try: os.mkdir(run_location) + LOG.debug("Created run location at %s", run_location) except: raise RuntimeError( "Unable to create the temporary working " @@ -1525,9 +1571,13 @@ def launch_mapdl( LOG.info("`run_location` set. Disabling the removal of temporary files.") remove_temp_dir_on_exit = False + LOG.debug("Using run location at %s", run_location) + # verify no lock file and the mode is valid check_lock_file(run_location, jobname, override) + mode = check_mode(mode, _version_from_path(exec_file)) + LOG.debug("Using mode %s", mode) # Setting SMP by default if student version is used. additional_switches = _force_smp_student_version(additional_switches, exec_file) @@ -1538,6 +1588,7 @@ def launch_mapdl( ) additional_switches = _check_license_argument(license_type, additional_switches) + LOG.debug(f"Using additional switches {additional_switches}.") start_parm = { "exec_file": exec_file, @@ -1555,14 +1606,18 @@ def launch_mapdl( start_parm["override"] = override start_parm["timeout"] = start_timeout + LOG.debug(f"Using start parameters {start_parm}") + # Check the license server if license_server_check: # configure timeout to be 90% of the wait time of the startup # time for Ansys. + LOG.debug("Checking license server.") lic_check = LicenseChecker(timeout=start_timeout * 0.9) lic_check.start() try: + LOG.debug("Starting MAPDL") if mode == "console": from ansys.mapdl.core.mapdl_console import MapdlConsole @@ -1610,12 +1665,14 @@ def launch_mapdl( # Failed to launch for some reason. Check if failure was due # to the license check if license_server_check: + LOG.debug("Checking license server.") lic_check.check() raise exception # Stopping license checker if license_server_check: + LOG.debug("Stopping license server check.") lic_check.is_connected = True return mapdl @@ -1716,6 +1773,7 @@ def update_env_vars(add_env_vars, replace_env_vars): ) add_env_vars.update(os.environ) + LOG.debug(f"Updating environment variables with: {add_env_vars}") return add_env_vars elif replace_env_vars: @@ -1723,7 +1781,7 @@ def update_env_vars(add_env_vars, replace_env_vars): raise TypeError( "The variable 'replace_env_vars' should be a dict with env vars." ) - + LOG.debug(f"Replacing environment variables with: {replace_env_vars}") return replace_env_vars diff --git a/src/ansys/mapdl/core/mapdl_grpc.py b/src/ansys/mapdl/core/mapdl_grpc.py index afa8602d4d..d5bf581f01 100755 --- a/src/ansys/mapdl/core/mapdl_grpc.py +++ b/src/ansys/mapdl/core/mapdl_grpc.py @@ -53,7 +53,12 @@ DEFAULT_FILE_CHUNK_SIZE, parse_chunks, ) -from ansys.mapdl.core.errors import MapdlExitedError, MapdlRuntimeError, protect_grpc +from ansys.mapdl.core.errors import ( + MapdlConnectionError, + MapdlExitedError, + MapdlRuntimeError, + protect_grpc, +) from ansys.mapdl.core.mapdl import _MapdlCore from ansys.mapdl.core.mapdl_types import MapdlInt from ansys.mapdl.core.misc import ( @@ -361,8 +366,10 @@ def __init__( self._pids = [] if channel is None: + self._log.debug("Creating channel to %s:%s", ip, port) self._channel = self._create_channel(ip, port) else: + self._log.debug("Using provided channel") self._channel = channel # connect and validate to the channel @@ -428,8 +435,8 @@ def _multi_connect(self, n_attempts=5, timeout=15, set_no_abort=True): ) if not connected: - raise IOError( - f"Unable to connect to MAPDL gRPC instance at {self._channel_str}" + raise MapdlConnectionError( + f"Unable to connect to MAPDL gRPC instance at {self._channel_str}." ) else: self._exited = False diff --git a/src/ansys/mapdl/core/misc.py b/src/ansys/mapdl/core/misc.py index c887dc255f..bd599486d3 100644 --- a/src/ansys/mapdl/core/misc.py +++ b/src/ansys/mapdl/core/misc.py @@ -18,7 +18,7 @@ import numpy as np from ansys.mapdl import core as pymapdl -from ansys.mapdl.core import _HAS_PYVISTA, LOG +from ansys.mapdl.core import _HAS_PYVISTA, LINUX_DEFAULT_DIRS, LOG try: import ansys.tools.report as pyansys_report @@ -82,21 +82,54 @@ def check_valid_routine(routine): return True +def get_linux_default_ansys_bin(rver): + """Find the MAPDL executable file using standard Linux installation paths, + + Raises: + FileNotFoundError: When no binary is found. + + Returns: + str: Path to MAPDL executable. + """ + for each_path in LINUX_DEFAULT_DIRS: + for each_file in [f"ansys{rver}", "mapdl"]: + + ans_root = os.getenv(f"AWP_ROOT{rver}", each_path) + mapdlbin = os.path.join(ans_root, f"v{rver}", "ansys", "bin", each_file) + + # rare case where the versioned binary doesn't exist + if os.path.isfile(mapdlbin): + LOG.debug(f"Found ANSYS binary at {mapdlbin}") + return mapdlbin + else: + LOG.debug(f"NOT found Ansys binary at {mapdlbin}") + + # We could not find a binary, returning a default one + return os.path.join( + LINUX_DEFAULT_DIRS[0], f"v{rver}", "ansys", "bin", f"ansys{rver}" + ) + + +def get_windows_default_ansys_bin(rver): + """Find the MAPDL executable using standard Windows installation paths""" + program_files = os.getenv("PROGRAMFILES", os.path.join("c:\\", "Program Files")) + ans_root = os.getenv( + f"AWP_ROOT{rver}", os.path.join(program_files, "ANSYS Inc", f"v{rver}") + ) + return os.path.join(ans_root, "ansys", "bin", "winx64", f"ANSYS{rver}.exe") + + def get_ansys_bin(rver): """Identify the ansys executable based on the release version (e.g. "201")""" - if os.name == "nt": # pragma: no cover - program_files = os.getenv("PROGRAMFILES", os.path.join("c:\\", "Program Files")) - ans_root = os.getenv( - f"AWP_ROOT{rver}", os.path.join(program_files, "ANSYS Inc", f"v{rver}") + if os.getenv(f"AWP_ROOT{rver}") is not None: + LOG.debug( + f"Found 'AWP_ROOT{rver}' environment variable and it has value {os.getenv(f'AWP_ROOT{rver}')}" ) - mapdlbin = os.path.join(ans_root, "ansys", "bin", "winx64", f"ANSYS{rver}.exe") - else: - ans_root = os.getenv(f"AWP_ROOT{rver}", os.path.join("/", "usr", "ansys_inc")) - mapdlbin = os.path.join(ans_root, f"v{rver}", "ansys", "bin", f"ansys{rver}") - # rare case where the versioned binary doesn't exist - if not os.path.isfile(mapdlbin): - mapdlbin = os.path.join(ans_root, f"v{rver}", "ansys", "bin", "mapdl") + if os.name == "nt": # pragma: no cover + mapdlbin = get_windows_default_ansys_bin(rver) + else: + mapdlbin = get_linux_default_ansys_bin(rver) return mapdlbin