linux-mcp-server 0.1.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- linux_mcp_server/__init__.py +4 -0
- linux_mcp_server/__main__.py +30 -0
- linux_mcp_server/audit.py +331 -0
- linux_mcp_server/connection/__init__.py +0 -0
- linux_mcp_server/connection/ssh.py +338 -0
- linux_mcp_server/logging_config.py +176 -0
- linux_mcp_server/server.py +18 -0
- linux_mcp_server/tools/__init__.py +51 -0
- linux_mcp_server/tools/logs.py +221 -0
- linux_mcp_server/tools/network.py +288 -0
- linux_mcp_server/tools/processes.py +273 -0
- linux_mcp_server/tools/services.py +149 -0
- linux_mcp_server/tools/storage.py +194 -0
- linux_mcp_server/tools/system_info.py +524 -0
- linux_mcp_server/utils/__init__.py +2 -0
- linux_mcp_server/utils/enum.py +16 -0
- linux_mcp_server/utils/format.py +26 -0
- linux_mcp_server/utils/types.py +5 -0
- linux_mcp_server/utils/validation.py +57 -0
- linux_mcp_server-0.1.0a2.dist-info/METADATA +233 -0
- linux_mcp_server-0.1.0a2.dist-info/RECORD +24 -0
- linux_mcp_server-0.1.0a2.dist-info/WHEEL +4 -0
- linux_mcp_server-0.1.0a2.dist-info/entry_points.txt +2 -0
- linux_mcp_server-0.1.0a2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Network diagnostic tools."""
|
|
2
|
+
|
|
3
|
+
import socket
|
|
4
|
+
|
|
5
|
+
import psutil
|
|
6
|
+
|
|
7
|
+
from mcp.types import ToolAnnotations
|
|
8
|
+
|
|
9
|
+
from linux_mcp_server.audit import log_tool_call
|
|
10
|
+
from linux_mcp_server.connection.ssh import execute_command
|
|
11
|
+
from linux_mcp_server.server import mcp
|
|
12
|
+
from linux_mcp_server.utils import format_bytes
|
|
13
|
+
from linux_mcp_server.utils.types import Host
|
|
14
|
+
from linux_mcp_server.utils.types import Username
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mcp.tool(
|
|
18
|
+
title="Get network interfaces",
|
|
19
|
+
description="Get detailed information about network interfaces including address and traffic statistics.",
|
|
20
|
+
annotations=ToolAnnotations(readOnlyHint=True),
|
|
21
|
+
)
|
|
22
|
+
@log_tool_call
|
|
23
|
+
async def get_network_interfaces( # noqa: C901
|
|
24
|
+
host: Host | None = None,
|
|
25
|
+
username: Username | None = None,
|
|
26
|
+
) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Get network interface information.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
if host:
|
|
32
|
+
# Remote execution - use ip command
|
|
33
|
+
info = []
|
|
34
|
+
info.append("=== Network Interfaces ===\n")
|
|
35
|
+
|
|
36
|
+
# Get interface info
|
|
37
|
+
returncode, stdout, _ = await execute_command(
|
|
38
|
+
["ip", "-brief", "address"],
|
|
39
|
+
host=host,
|
|
40
|
+
username=username,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if returncode == 0 and stdout:
|
|
44
|
+
info.append(stdout)
|
|
45
|
+
|
|
46
|
+
# Get detailed interface info
|
|
47
|
+
returncode, stdout, _ = await execute_command(
|
|
48
|
+
["ip", "address"],
|
|
49
|
+
host=host,
|
|
50
|
+
username=username,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if returncode == 0 and stdout:
|
|
54
|
+
info.append("\n=== Detailed Interface Information ===")
|
|
55
|
+
info.append(stdout)
|
|
56
|
+
|
|
57
|
+
# Get network statistics using netstat or ss
|
|
58
|
+
returncode, stdout, _ = await execute_command(
|
|
59
|
+
["cat", "/proc/net/dev"],
|
|
60
|
+
host=host,
|
|
61
|
+
username=username,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if returncode == 0 and stdout:
|
|
65
|
+
info.append("\n=== Network I/O Statistics ===")
|
|
66
|
+
info.append(stdout)
|
|
67
|
+
|
|
68
|
+
return "\n".join(info)
|
|
69
|
+
else:
|
|
70
|
+
# Local execution - use psutil
|
|
71
|
+
info = []
|
|
72
|
+
info.append("=== Network Interfaces ===\n")
|
|
73
|
+
|
|
74
|
+
# Get network interface addresses
|
|
75
|
+
net_if_addrs = psutil.net_if_addrs()
|
|
76
|
+
net_if_stats = psutil.net_if_stats()
|
|
77
|
+
|
|
78
|
+
for interface, addrs in sorted(net_if_addrs.items()):
|
|
79
|
+
info.append(f"\n{interface}:")
|
|
80
|
+
|
|
81
|
+
# Get interface stats
|
|
82
|
+
if interface in net_if_stats:
|
|
83
|
+
stats = net_if_stats[interface]
|
|
84
|
+
status = "UP" if stats.isup else "DOWN"
|
|
85
|
+
info.append(f" Status: {status}")
|
|
86
|
+
info.append(f" Speed: {stats.speed} Mbps")
|
|
87
|
+
info.append(f" MTU: {stats.mtu}")
|
|
88
|
+
|
|
89
|
+
# Get addresses
|
|
90
|
+
for addr in addrs:
|
|
91
|
+
if addr.family == socket.AF_INET:
|
|
92
|
+
info.append(f" IPv4 Address: {addr.address}")
|
|
93
|
+
if addr.netmask:
|
|
94
|
+
info.append(f" Netmask: {addr.netmask}")
|
|
95
|
+
if addr.broadcast:
|
|
96
|
+
info.append(f" Broadcast: {addr.broadcast}")
|
|
97
|
+
elif addr.family == socket.AF_INET6:
|
|
98
|
+
info.append(f" IPv6 Address: {addr.address}")
|
|
99
|
+
if addr.netmask:
|
|
100
|
+
info.append(f" Netmask: {addr.netmask}")
|
|
101
|
+
elif addr.family == psutil.AF_LINK:
|
|
102
|
+
info.append(f" MAC Address: {addr.address}")
|
|
103
|
+
|
|
104
|
+
# Network I/O statistics
|
|
105
|
+
net_io = psutil.net_io_counters()
|
|
106
|
+
info.append("\n\n=== Network I/O Statistics (total) ===")
|
|
107
|
+
info.append(f"Bytes Sent: {format_bytes(net_io.bytes_sent)}")
|
|
108
|
+
info.append(f"Bytes Received: {format_bytes(net_io.bytes_recv)}")
|
|
109
|
+
info.append(f"Packets Sent: {net_io.packets_sent}")
|
|
110
|
+
info.append(f"Packets Received: {net_io.packets_recv}")
|
|
111
|
+
info.append(f"Errors In: {net_io.errin}")
|
|
112
|
+
info.append(f"Errors Out: {net_io.errout}")
|
|
113
|
+
info.append(f"Drops In: {net_io.dropin}")
|
|
114
|
+
info.append(f"Drops Out: {net_io.dropout}")
|
|
115
|
+
|
|
116
|
+
return "\n".join(info)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
return f"Error getting network interface information: {str(e)}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@mcp.tool(
|
|
122
|
+
title="Get network connections",
|
|
123
|
+
description="Get detailed information about active network connections.",
|
|
124
|
+
annotations=ToolAnnotations(readOnlyHint=True),
|
|
125
|
+
)
|
|
126
|
+
@log_tool_call
|
|
127
|
+
async def get_network_connections(
|
|
128
|
+
host: Host | None = None,
|
|
129
|
+
username: Username | None = None,
|
|
130
|
+
) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Get active network connections.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
if host:
|
|
136
|
+
# Remote execution - use ss or netstat command
|
|
137
|
+
# Try ss first (modern tool)
|
|
138
|
+
returncode, stdout, _ = await execute_command(
|
|
139
|
+
["ss", "-tunap"],
|
|
140
|
+
host=host,
|
|
141
|
+
username=username,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if returncode == 0 and stdout:
|
|
145
|
+
info = []
|
|
146
|
+
info.append("=== Active Network Connections ===\n")
|
|
147
|
+
info.append(stdout)
|
|
148
|
+
|
|
149
|
+
# Count connections
|
|
150
|
+
lines = stdout.strip().split("\n")
|
|
151
|
+
info.append(f"\n\nTotal connections: {len(lines) - 1}") # -1 for header
|
|
152
|
+
|
|
153
|
+
return "\n".join(info)
|
|
154
|
+
else:
|
|
155
|
+
# Fallback to netstat
|
|
156
|
+
returncode, stdout, _ = await execute_command(
|
|
157
|
+
["netstat", "-tunap"],
|
|
158
|
+
host=host,
|
|
159
|
+
username=username,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if returncode == 0 and stdout:
|
|
163
|
+
info = []
|
|
164
|
+
info.append("=== Active Network Connections ===\n")
|
|
165
|
+
info.append(stdout)
|
|
166
|
+
return "\n".join(info)
|
|
167
|
+
else:
|
|
168
|
+
return "Error: Neither ss nor netstat command available on remote host"
|
|
169
|
+
else:
|
|
170
|
+
# Local execution - use psutil
|
|
171
|
+
info = []
|
|
172
|
+
info.append("=== Active Network Connections ===\n")
|
|
173
|
+
info.append(f"{'Proto':<8} {'Local Address':<30} {'Remote Address':<30} {'Status':<15} {'PID/Program'}")
|
|
174
|
+
info.append("-" * 110)
|
|
175
|
+
|
|
176
|
+
# Get all network connections
|
|
177
|
+
connections = psutil.net_connections(kind="inet")
|
|
178
|
+
|
|
179
|
+
for conn in connections:
|
|
180
|
+
proto = "TCP" if conn.type == socket.SOCK_STREAM else "UDP"
|
|
181
|
+
|
|
182
|
+
local_addr = f"{conn.laddr.ip}:{conn.laddr.port}" if conn.laddr else "N/A"
|
|
183
|
+
remote_addr = f"{conn.raddr.ip}:{conn.raddr.port}" if conn.raddr else "N/A"
|
|
184
|
+
status = conn.status if conn.status else "N/A"
|
|
185
|
+
|
|
186
|
+
# Try to get process info
|
|
187
|
+
pid_info = str(conn.pid) if conn.pid else "N/A"
|
|
188
|
+
if conn.pid:
|
|
189
|
+
try:
|
|
190
|
+
proc = psutil.Process(conn.pid)
|
|
191
|
+
pid_info = f"{conn.pid}/{proc.name()}"
|
|
192
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
info.append(f"{proto:<8} {local_addr:<30} {remote_addr:<30} {status:<15} {pid_info}")
|
|
196
|
+
|
|
197
|
+
info.append(f"\n\nTotal connections: {len(connections)}")
|
|
198
|
+
|
|
199
|
+
return "\n".join(info)
|
|
200
|
+
except psutil.AccessDenied:
|
|
201
|
+
return "Permission denied. This tool requires elevated privileges to view all network connections."
|
|
202
|
+
except Exception as e:
|
|
203
|
+
return f"Error getting network connections: {str(e)}"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@mcp.tool(
|
|
207
|
+
title="Get listening ports",
|
|
208
|
+
description="Get details on listening port, protocols, and services.",
|
|
209
|
+
annotations=ToolAnnotations(readOnlyHint=True),
|
|
210
|
+
)
|
|
211
|
+
@log_tool_call
|
|
212
|
+
async def get_listening_ports(
|
|
213
|
+
host: Host | None = None,
|
|
214
|
+
username: Username | None = None,
|
|
215
|
+
) -> str:
|
|
216
|
+
"""
|
|
217
|
+
Get listening ports.
|
|
218
|
+
"""
|
|
219
|
+
try:
|
|
220
|
+
if host:
|
|
221
|
+
# Remote execution - use ss or netstat command
|
|
222
|
+
# Try ss first (modern tool)
|
|
223
|
+
returncode, stdout, _ = await execute_command(
|
|
224
|
+
["ss", "-tulnp"],
|
|
225
|
+
host=host,
|
|
226
|
+
username=username,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if returncode == 0 and stdout:
|
|
230
|
+
info = []
|
|
231
|
+
info.append("=== Listening Ports ===\n")
|
|
232
|
+
info.append(stdout)
|
|
233
|
+
|
|
234
|
+
# Count listening ports
|
|
235
|
+
lines = stdout.strip().split("\n")
|
|
236
|
+
info.append(f"\n\nTotal listening ports: {len(lines) - 1}") # -1 for header
|
|
237
|
+
|
|
238
|
+
return "\n".join(info)
|
|
239
|
+
else:
|
|
240
|
+
# Fallback to netstat
|
|
241
|
+
returncode, stdout, _ = await execute_command(
|
|
242
|
+
["netstat", "-tulnp"],
|
|
243
|
+
host=host,
|
|
244
|
+
username=username,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if returncode == 0 and stdout:
|
|
248
|
+
info = []
|
|
249
|
+
info.append("=== Listening Ports ===\n")
|
|
250
|
+
info.append(stdout)
|
|
251
|
+
return "\n".join(info)
|
|
252
|
+
else:
|
|
253
|
+
return "Error: Neither ss nor netstat command available on remote host"
|
|
254
|
+
else:
|
|
255
|
+
# Local execution - use psutil
|
|
256
|
+
info = []
|
|
257
|
+
info.append("=== Listening Ports ===\n")
|
|
258
|
+
info.append(f"{'Proto':<8} {'Local Address':<30} {'Status':<15} {'PID/Program'}")
|
|
259
|
+
info.append("-" * 80)
|
|
260
|
+
|
|
261
|
+
# Get connections in LISTEN state
|
|
262
|
+
connections = psutil.net_connections(kind="inet")
|
|
263
|
+
listening = [c for c in connections if c.status == "LISTEN" or c.type == socket.SOCK_DGRAM]
|
|
264
|
+
|
|
265
|
+
for conn in listening:
|
|
266
|
+
proto = "TCP" if conn.type == socket.SOCK_STREAM else "UDP"
|
|
267
|
+
|
|
268
|
+
local_addr = f"{conn.laddr.ip}:{conn.laddr.port}" if conn.laddr else "N/A"
|
|
269
|
+
status = conn.status if conn.status else "LISTENING"
|
|
270
|
+
|
|
271
|
+
# Try to get process info
|
|
272
|
+
pid_info = str(conn.pid) if conn.pid else "N/A"
|
|
273
|
+
if conn.pid:
|
|
274
|
+
try:
|
|
275
|
+
proc = psutil.Process(conn.pid)
|
|
276
|
+
pid_info = f"{conn.pid}/{proc.name()}"
|
|
277
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
info.append(f"{proto:<8} {local_addr:<30} {status:<15} {pid_info}")
|
|
281
|
+
|
|
282
|
+
info.append(f"\n\nTotal listening ports: {len(listening)}")
|
|
283
|
+
|
|
284
|
+
return "\n".join(info)
|
|
285
|
+
except psutil.AccessDenied:
|
|
286
|
+
return "Permission denied. This tool requires elevated privileges to view all listening ports."
|
|
287
|
+
except Exception as e:
|
|
288
|
+
return f"Error getting listening ports: {str(e)}"
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Process management tools."""
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
import psutil
|
|
8
|
+
|
|
9
|
+
from mcp.types import ToolAnnotations
|
|
10
|
+
|
|
11
|
+
from linux_mcp_server.audit import log_tool_call
|
|
12
|
+
from linux_mcp_server.connection.ssh import execute_command
|
|
13
|
+
from linux_mcp_server.server import mcp
|
|
14
|
+
from linux_mcp_server.utils import format_bytes
|
|
15
|
+
from linux_mcp_server.utils.types import Host
|
|
16
|
+
from linux_mcp_server.utils.types import Username
|
|
17
|
+
from linux_mcp_server.utils.validation import validate_pid
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@mcp.tool(
|
|
21
|
+
title="List processes",
|
|
22
|
+
description="List running processes",
|
|
23
|
+
annotations=ToolAnnotations(readOnlyHint=True),
|
|
24
|
+
)
|
|
25
|
+
@log_tool_call
|
|
26
|
+
async def list_processes(
|
|
27
|
+
host: Host | None = None,
|
|
28
|
+
username: Username | None = None,
|
|
29
|
+
) -> str:
|
|
30
|
+
try:
|
|
31
|
+
if host:
|
|
32
|
+
# Remote execution - use ps command
|
|
33
|
+
returncode, stdout, _ = await execute_command(["ps", "aux", "--sort=-%cpu"], host=host, username=username)
|
|
34
|
+
|
|
35
|
+
if returncode == 0 and stdout:
|
|
36
|
+
info = []
|
|
37
|
+
info.append("=== Running Processes ===\n")
|
|
38
|
+
|
|
39
|
+
lines = stdout.strip().split("\n")
|
|
40
|
+
# Take header and top 100 processes
|
|
41
|
+
if len(lines) > 101:
|
|
42
|
+
info.append("\n".join(lines[:101]))
|
|
43
|
+
info.append(f"\n\nTotal processes: {len(lines) - 1}")
|
|
44
|
+
info.append("Showing: Top 100 by CPU usage")
|
|
45
|
+
else:
|
|
46
|
+
info.append(stdout)
|
|
47
|
+
info.append(f"\n\nTotal processes: {len(lines) - 1}")
|
|
48
|
+
|
|
49
|
+
return "\n".join(info)
|
|
50
|
+
else:
|
|
51
|
+
return "Error executing ps command on remote host"
|
|
52
|
+
else:
|
|
53
|
+
# Local execution - use psutil
|
|
54
|
+
info = []
|
|
55
|
+
info.append("=== Running Processes ===\n")
|
|
56
|
+
info.append(f"{'PID':<8} {'User':<12} {'CPU%':<8} {'Memory%':<10} {'Status':<12} {'Name':<30} {'Command'}")
|
|
57
|
+
info.append("-" * 120)
|
|
58
|
+
|
|
59
|
+
# Get all processes
|
|
60
|
+
processes = []
|
|
61
|
+
for proc in psutil.process_iter(
|
|
62
|
+
["pid", "name", "username", "cpu_percent", "memory_percent", "status", "cmdline"],
|
|
63
|
+
):
|
|
64
|
+
try:
|
|
65
|
+
processes.append(proc.info)
|
|
66
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
# Sort by CPU usage (descending)
|
|
70
|
+
processes.sort(key=lambda x: x.get("cpu_percent", 0) or 0, reverse=True)
|
|
71
|
+
|
|
72
|
+
# Show top processes (limit to reasonable number)
|
|
73
|
+
for proc_info in processes[:100]: # Show top 100 processes
|
|
74
|
+
pid = proc_info.get("pid", "N/A")
|
|
75
|
+
username_val = proc_info.get("username", "N/A")
|
|
76
|
+
if username_val and len(username_val) > 12:
|
|
77
|
+
username_val = username_val[:9] + "..."
|
|
78
|
+
|
|
79
|
+
cpu = proc_info.get("cpu_percent", 0) or 0
|
|
80
|
+
mem = proc_info.get("memory_percent", 0) or 0
|
|
81
|
+
status = proc_info.get("status", "N/A")
|
|
82
|
+
name = proc_info.get("name", "N/A")
|
|
83
|
+
if name and len(name) > 30:
|
|
84
|
+
name = name[:27] + "..."
|
|
85
|
+
|
|
86
|
+
cmdline = proc_info.get("cmdline", [])
|
|
87
|
+
if cmdline:
|
|
88
|
+
cmd = " ".join(cmdline)
|
|
89
|
+
if len(cmd) > 40:
|
|
90
|
+
cmd = cmd[:37] + "..."
|
|
91
|
+
else:
|
|
92
|
+
cmd = name
|
|
93
|
+
|
|
94
|
+
info.append(f"{pid:<8} {username_val:<12} {cpu:<8.1f} {mem:<10.1f} {status:<12} {name:<30} {cmd}")
|
|
95
|
+
|
|
96
|
+
# Add summary
|
|
97
|
+
total_processes = len(list(psutil.process_iter()))
|
|
98
|
+
info.append(f"\n\nTotal processes: {total_processes}")
|
|
99
|
+
info.append("Showing: Top 100 by CPU usage")
|
|
100
|
+
|
|
101
|
+
return "\n".join(info)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
return f"Error listing processes: {str(e)}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@mcp.tool(
|
|
107
|
+
title="Process details",
|
|
108
|
+
description="Get information about a specific process.",
|
|
109
|
+
annotations=ToolAnnotations(readOnlyHint=True),
|
|
110
|
+
)
|
|
111
|
+
@log_tool_call
|
|
112
|
+
async def get_process_info( # noqa: C901
|
|
113
|
+
pid: t.Annotated[int, "Process ID"],
|
|
114
|
+
host: Host | None = None,
|
|
115
|
+
username: Username | None = None,
|
|
116
|
+
) -> str:
|
|
117
|
+
# Validate PID (accepts floats from LLMs)
|
|
118
|
+
validated_pid, error = validate_pid(pid)
|
|
119
|
+
if error:
|
|
120
|
+
return error
|
|
121
|
+
|
|
122
|
+
if validated_pid is None:
|
|
123
|
+
return "Invalid PID"
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
if host:
|
|
127
|
+
# Remote execution - use ps command
|
|
128
|
+
returncode, stdout, _ = await execute_command(
|
|
129
|
+
["ps", "-p", str(validated_pid), "-o", "pid,user,stat,pcpu,pmem,vsz,rss,etime,comm,args"],
|
|
130
|
+
host=host,
|
|
131
|
+
username=username,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if returncode != 0:
|
|
135
|
+
return f"Process with PID {validated_pid} does not exist on remote host."
|
|
136
|
+
|
|
137
|
+
if stdout:
|
|
138
|
+
info = []
|
|
139
|
+
info.append(f"=== Process Information for PID {validated_pid} ===\n")
|
|
140
|
+
info.append(stdout)
|
|
141
|
+
|
|
142
|
+
# Try to get more details with /proc
|
|
143
|
+
returncode, stdout, _ = await execute_command(
|
|
144
|
+
["cat", f"/proc/{validated_pid}/status"],
|
|
145
|
+
host=host,
|
|
146
|
+
username=username,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if returncode == 0 and stdout:
|
|
150
|
+
info.append("\n=== Detailed Status (/proc) ===")
|
|
151
|
+
# Filter to show most relevant fields
|
|
152
|
+
relevant_fields = [
|
|
153
|
+
"Name:",
|
|
154
|
+
"State:",
|
|
155
|
+
"Tgid:",
|
|
156
|
+
"Pid:",
|
|
157
|
+
"PPid:",
|
|
158
|
+
"Threads:",
|
|
159
|
+
"VmPeak:",
|
|
160
|
+
"VmSize:",
|
|
161
|
+
"VmRSS:",
|
|
162
|
+
]
|
|
163
|
+
for line in stdout.split("\n"):
|
|
164
|
+
if any(field in line for field in relevant_fields):
|
|
165
|
+
info.append(line)
|
|
166
|
+
|
|
167
|
+
return "\n".join(info)
|
|
168
|
+
else:
|
|
169
|
+
return f"Process with PID {validated_pid} does not exist on remote host."
|
|
170
|
+
else:
|
|
171
|
+
# Local execution - use psutil
|
|
172
|
+
# Check if process exists
|
|
173
|
+
if not psutil.pid_exists(validated_pid):
|
|
174
|
+
return f"Process with PID {validated_pid} does not exist."
|
|
175
|
+
|
|
176
|
+
proc = psutil.Process(validated_pid)
|
|
177
|
+
info = []
|
|
178
|
+
|
|
179
|
+
info.append(f"=== Process Information for PID {validated_pid} ===\n")
|
|
180
|
+
|
|
181
|
+
# Basic info
|
|
182
|
+
try:
|
|
183
|
+
info.append(f"Name: {proc.name()}")
|
|
184
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
info.append(f"Executable: {proc.exe()}")
|
|
189
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
190
|
+
info.append("Executable: [Access Denied]")
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
info.append(f"Command Line: {' '.join(proc.cmdline())}")
|
|
194
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
info.append(f"Status: {proc.status()}")
|
|
199
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
info.append(f"User: {proc.username()}")
|
|
204
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
# Process IDs
|
|
208
|
+
try:
|
|
209
|
+
info.append(f"\nPID: {proc.pid}")
|
|
210
|
+
info.append(f"Parent PID: {proc.ppid()}")
|
|
211
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
# Resource usage
|
|
215
|
+
try:
|
|
216
|
+
info.append("\n=== Resource Usage ===")
|
|
217
|
+
cpu_percent = proc.cpu_percent(interval=0.1)
|
|
218
|
+
info.append(f"CPU Percent: {cpu_percent}%")
|
|
219
|
+
|
|
220
|
+
mem_info = proc.memory_info()
|
|
221
|
+
info.append(f"Memory RSS: {format_bytes(mem_info.rss)}")
|
|
222
|
+
info.append(f"Memory VMS: {format_bytes(mem_info.vms)}")
|
|
223
|
+
info.append(f"Memory Percent: {proc.memory_percent():.2f}%")
|
|
224
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
225
|
+
info.append("Resource usage: [Access Denied]")
|
|
226
|
+
|
|
227
|
+
# Timing
|
|
228
|
+
try:
|
|
229
|
+
create_time = datetime.fromtimestamp(proc.create_time())
|
|
230
|
+
info.append("\n=== Timing ===")
|
|
231
|
+
info.append(f"Created: {create_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
232
|
+
|
|
233
|
+
cpu_times = proc.cpu_times()
|
|
234
|
+
info.append(f"CPU Time (user): {cpu_times.user:.2f}s")
|
|
235
|
+
info.append(f"CPU Time (system): {cpu_times.system:.2f}s")
|
|
236
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
# Threads
|
|
240
|
+
try:
|
|
241
|
+
num_threads = proc.num_threads()
|
|
242
|
+
info.append(f"\nThreads: {num_threads}")
|
|
243
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
244
|
+
pass
|
|
245
|
+
|
|
246
|
+
# File descriptors
|
|
247
|
+
try:
|
|
248
|
+
num_fds = proc.num_fds()
|
|
249
|
+
info.append(f"Open File Descriptors: {num_fds}")
|
|
250
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, AttributeError):
|
|
251
|
+
pass # Not available on all systems
|
|
252
|
+
|
|
253
|
+
# Connections
|
|
254
|
+
try:
|
|
255
|
+
connections = proc.connections()
|
|
256
|
+
if connections:
|
|
257
|
+
info.append(f"\n=== Network Connections ({len(connections)}) ===")
|
|
258
|
+
for _, conn in enumerate(connections[:10]): # Show first 10
|
|
259
|
+
info.append(
|
|
260
|
+
f" {conn.type.name}: {conn.laddr} -> {conn.raddr if conn.raddr else 'N/A'} [{conn.status}]",
|
|
261
|
+
)
|
|
262
|
+
if len(connections) > 10:
|
|
263
|
+
info.append(f" ... and {len(connections) - 10} more")
|
|
264
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
return "\n".join(info)
|
|
268
|
+
except psutil.NoSuchProcess:
|
|
269
|
+
return f"Process with PID {validated_pid} does not exist."
|
|
270
|
+
except psutil.AccessDenied:
|
|
271
|
+
return f"Access denied to process with PID {validated_pid}. Try running with elevated privileges."
|
|
272
|
+
except Exception as e:
|
|
273
|
+
return f"Error getting process information: {str(e)}"
|