Skip to content

Commit 8a2ed11

Browse files
refactor: list_instances (#4329)
* feat(cli): refactor list_instances for improved process validation and instance retrieval * feat(cli): refactor list_instances to utilize core functionality for instance retrieval * fix(cli): optimize process iteration by adding info attribute for better performance * chore: adding changelog file 4329.miscellaneous.md [dependabot-skip] * fix(cli): handle AccessDenied exception in get_ansys_process_from_port function * test(cli): add comprehensive tests for get_ansys_process_from_port function * refactor: move get_ansys_process_from_port function to core module --------- Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent cd57b8b commit 8a2ed11

File tree

5 files changed

+409
-46
lines changed

5 files changed

+409
-46
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
List_instances

src/ansys/mapdl/core/cli/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@
2020
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
# SOFTWARE.
2222

23-
from ansys.mapdl.core import _HAS_CLICK
23+
try:
24+
import click # noqa: F401
25+
26+
_HAS_CLICK = True
27+
except ImportError: # pragma: no cover
28+
_HAS_CLICK = False
2429

2530
if _HAS_CLICK:
2631
###################################
2732
# PyMAPDL CLI
28-
import click
2933

3034
@click.group(invoke_without_command=True)
3135
@click.pass_context
@@ -38,9 +42,9 @@ def main(ctx: click.Context):
3842
from ansys.mapdl.core.cli.stop import stop as stop_cmd
3943

4044
main.add_command(convert_cmd, name="convert")
45+
main.add_command(list_instances, name="list")
4146
main.add_command(start_cmd, name="start")
4247
main.add_command(stop_cmd, name="stop")
43-
main.add_command(list_instances, name="list")
4448

4549

4650
else:

src/ansys/mapdl/core/cli/core.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
"""
24+
Minimal core functionality for CLI operations.
25+
This module avoids importing heavy dependencies like pandas, numpy, etc.
26+
"""
27+
28+
from typing import Any, Dict, List
29+
30+
import psutil
31+
32+
33+
def is_valid_ansys_process_name(name: str) -> bool:
34+
"""Check if process name indicates ANSYS/MAPDL"""
35+
return ("ansys" in name.lower()) or ("mapdl" in name.lower())
36+
37+
38+
def is_alive_status(status) -> bool:
39+
"""Check if process status indicates alive"""
40+
return status in [
41+
psutil.STATUS_RUNNING,
42+
psutil.STATUS_IDLE,
43+
psutil.STATUS_SLEEPING,
44+
]
45+
46+
47+
def get_mapdl_instances() -> List[Dict[str, Any]]:
48+
"""Get list of MAPDL instances with minimal data"""
49+
instances = []
50+
51+
for proc in psutil.process_iter(attrs=["name"]):
52+
name = proc.info["name"]
53+
if not is_valid_ansys_process_name(name):
54+
continue
55+
56+
try:
57+
status = proc.status()
58+
if not is_alive_status(status):
59+
continue
60+
61+
cmdline = proc.cmdline()
62+
if "-grpc" not in cmdline:
63+
continue
64+
65+
# Get port from cmdline
66+
port = None
67+
try:
68+
ind_grpc = cmdline.index("-port")
69+
port = int(cmdline[ind_grpc + 1])
70+
except (ValueError, IndexError):
71+
continue
72+
73+
children = proc.children(recursive=True)
74+
is_instance = len(children) >= 2
75+
76+
cwd = proc.cwd()
77+
instances.append(
78+
{
79+
"name": name,
80+
"status": status,
81+
"port": port,
82+
"pid": proc.pid,
83+
"cmdline": cmdline,
84+
"is_instance": is_instance,
85+
"cwd": cwd,
86+
}
87+
)
88+
89+
except (psutil.NoSuchProcess, psutil.ZombieProcess, psutil.AccessDenied):
90+
continue
91+
92+
return instances
93+
94+
95+
def get_ansys_process_from_port(port: int):
96+
import socket
97+
98+
import psutil
99+
100+
from ansys.mapdl.core.cli.core import is_alive_status, is_valid_ansys_process_name
101+
102+
# Filter by name first
103+
potential_procs = []
104+
for proc in psutil.process_iter(attrs=["name"]):
105+
name = proc.info["name"]
106+
if is_valid_ansys_process_name(name):
107+
potential_procs.append(proc)
108+
109+
for proc in potential_procs:
110+
try:
111+
status = proc.status()
112+
if not is_alive_status(status):
113+
continue
114+
cmdline = proc.cmdline()
115+
if "-grpc" not in cmdline:
116+
continue
117+
# Check if listening on the port
118+
connections = proc.connections()
119+
for conn in connections:
120+
if (
121+
conn.status == "LISTEN"
122+
and conn.family == socket.AF_INET
123+
and conn.laddr[1] == port
124+
):
125+
return proc
126+
except (psutil.NoSuchProcess, psutil.ZombieProcess, psutil.AccessDenied):
127+
continue

src/ansys/mapdl/core/cli/list_instances.py

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -66,46 +66,16 @@
6666
help="Print running location info.",
6767
)
6868
def list_instances(instances, long, cmd, location) -> None:
69-
import psutil
69+
return _list_instances(instances, long, cmd, location)
70+
71+
72+
def _list_instances(instances, long, cmd, location):
7073
from tabulate import tabulate
7174

75+
from ansys.mapdl.core.cli.core import get_mapdl_instances
76+
7277
# Assuming all ansys processes have -grpc flag
73-
mapdl_instances = []
74-
75-
def is_grpc_based(proc):
76-
cmdline = proc.cmdline()
77-
return "-grpc" in cmdline
78-
79-
def get_port(proc):
80-
cmdline = proc.cmdline()
81-
ind_grpc = cmdline.index("-port")
82-
return cmdline[ind_grpc + 1]
83-
84-
def is_valid_process(proc):
85-
valid_status = proc.status() in [
86-
psutil.STATUS_RUNNING,
87-
psutil.STATUS_IDLE,
88-
psutil.STATUS_SLEEPING,
89-
]
90-
valid_ansys_process = ("ansys" in proc.name().lower()) or (
91-
"mapdl" in proc.name().lower()
92-
)
93-
return valid_status and valid_ansys_process and is_grpc_based(proc)
94-
95-
for proc in psutil.process_iter():
96-
# Check if the process is running and not suspended
97-
try:
98-
if is_valid_process(proc):
99-
# Checking the number of children we infer if the process is the main process,
100-
# or one of the main process thread.
101-
if len(proc.children(recursive=True)) < 2:
102-
proc.ansys_instance = False
103-
else:
104-
proc.ansys_instance = True
105-
mapdl_instances.append(proc)
106-
107-
except (psutil.NoSuchProcess, psutil.ZombieProcess) as e:
108-
continue
78+
mapdl_instances = get_mapdl_instances()
10979

11080
# printing
11181
if long:
@@ -124,23 +94,23 @@ def is_valid_process(proc):
12494

12595
table = []
12696
for each_p in mapdl_instances:
127-
if instances and not each_p.ansys_instance:
97+
if instances and not each_p.get("is_instance", False):
12898
# Skip child processes if only printing instances
12999
continue
130100

131101
proc_line = []
132-
proc_line.append(each_p.name())
102+
proc_line.append(each_p["name"])
133103

134104
if not instances:
135-
proc_line.append(each_p.ansys_instance)
105+
proc_line.append(each_p.get("is_instance", False))
136106

137-
proc_line.extend([each_p.status(), get_port(each_p), each_p.pid])
107+
proc_line.extend([each_p["status"], each_p["port"], each_p["pid"]])
138108

139109
if cmd:
140-
proc_line.append(" ".join(each_p.cmdline()))
110+
proc_line.append(" ".join(each_p["cmdline"]))
141111

142112
if location:
143-
proc_line.append(each_p.cwd())
113+
proc_line.append(each_p["cwd"])
144114

145115
table.append(proc_line)
146116

0 commit comments

Comments
 (0)