Mini Shell
#!/opt/imh-python/bin/python3
"""Displays dcpumon info for one user as an HTML table"""
import argparse
import subprocess
import os
import re
import time
import pty
import errno
import select
from prettytable import PrettyTable
import rads
def parse_args():
"""Parse sys.argv"""
parser = argparse.ArgumentParser(description=__doc__)
# --user is only for backwards compat
parser.add_argument(
'-u', '--user', action='store_true', dest='u', help=argparse.SUPPRESS
)
parser.add_argument('user', help='cPanel username')
args = parser.parse_args()
if not rads.is_cpuser(args.user):
print(rads.color.red(f"Not a valid cPanel user: {args.user}"))
return args.user
def dcpumon() -> str:
"""Run dcpumonview {24h ago} {now} and return stdout as a str"""
# master_fd, slave_fd = pty.openpty()
start = str(int(time.time()) - 86400) # 24h ago
stop = str(int(time.time())) # now
cmd = ['/usr/local/cpanel/bin/dcpumonview', start, stop]
# dcpumonview prints HTML if it thinks it's not attached to a TTY
stdout = tty_capture(cmd)
return str(stdout, 'utf-8', 'ignore')
def tty_capture(cmd: list[str]) -> bytes:
"""Run a subprocess with PTYs attached"""
# https://stackoverflow.com/a/52954716
# https://stackoverflow.com/a/70166246
m_out, s_out = pty.openpty()
m_err, s_err = pty.openpty()
m_in, s_in = pty.openpty()
with subprocess.Popen(
cmd,
bufsize=1,
stdin=s_in,
stdout=s_out,
stderr=s_err,
close_fds=True,
env={"LINES": "40", "COLUMNS": "200", "TERM": "xterm-256color"},
) as proc:
for ready_fd in [s_out, s_err, s_in]:
os.close(ready_fd)
timeout = 0.04
readable = [m_out, m_err]
result = {m_out: b'', m_err: b''}
try:
while readable:
ready, _, _ = select.select(readable, [], [], timeout)
for ready_fd in ready:
try:
data = os.read(ready_fd, 512)
except OSError as e:
if e.errno != errno.EIO:
raise
readable.remove(ready_fd)
else:
if not data: # EOF
readable.remove(ready_fd)
result[ready_fd] += data
finally:
for ready_fd in [m_out, m_err, m_in]:
os.close(ready_fd)
if proc.poll() is None:
proc.kill()
proc.wait()
# stderr is result[m_err], but we don't use that in this script
return result[m_out]
def check_dcpumon(user: str):
"""Filter dcpumon output for one user and output as an html table"""
hitfound = False
tbl_data = False
tbl = PrettyTable([f"Top Process for {user}"])
tbl.align[f"Top Process for {user}"] = 'l'
user_re = re.compile(r'\|' + re.escape(user) + r'[_\s]*\|')
# |userna5_|example.com |0.00|0.00|0.0 |
# | Top Process | 0.1 | dovecot/imap |
# |userna6 |example2.com |0.00|0.00|0.0 |
for line in dcpumon().splitlines():
if user_re.match(line):
hitfound = True
continue
if not hitfound:
continue
if line.startswith('| Top'):
cmd = line.strip(' |').split('|')[-1].strip(' |')
tbl_data = True
tbl.add_row([cmd])
else:
hitfound = False
if tbl_data:
print(tbl.get_html_string(attributes={"class": "proc_body"}))
def main():
"""Displays dcpumon info for one user as an HTML table"""
user = parse_args()
check_dcpumon(user)
if __name__ == "__main__":
main()
Zerion Mini Shell 1.0