Mini Shell
#!/opt/imh-python/bin/python3
"""Prints a CPU Usage Report in HTML"""
__author__ = 'chases'
import gzip
from typing import Union
from datetime import date
from collections import defaultdict
import subprocess
import time
import sys
from pathlib import Path
import argparse
from prettytable import PrettyTable
import rads
ONE_DAY = 86400
def parse_args() -> str:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'-u',
'--user',
action='store',
help='Define your User',
required=True,
)
args = parser.parse_args()
if not rads.is_cpuser(args.user):
sys.exit("Not a valid user.")
return args.user
def parse_hourly_averages(
cpuser: str, today: str
) -> tuple[dict[str, Union[float, None]], float]:
now = time.time()
gz_files: list[Path] = []
for entry in Path('/var/log/sa/rads', today).iterdir():
if not entry.name.endswith('avg.gz'):
continue
if entry.name in ('99-avg.gz', '00-avg.gz'):
continue
if now - entry.stat().st_mtime <= ONE_DAY:
gz_files.append(entry)
gz_files.sort(key=lambda x: x.name)
total_cp = 0.0
hour_vs_cp = {}
prev_hourly_cp = 0.0
for gz_file in gz_files:
hour = gz_file.name.split('-')[0]
hour_vs_cp[hour] = None
# userna5 8389 0.10% 180.12re 0.03% 96.80cp 0.59% 0avio 20654k
for line in gzip.open(gz_file):
line = str(line, 'ascii', 'ignore')
if line.startswith(f'{cpuser} '):
# get cp usage for this hour (including hours prior to it)
hourly_cp = float(line.split()[5].replace("cp", ''))
# calc cp usage for only this hour
this_hour_cp = hourly_cp - prev_hourly_cp
total_cp += this_hour_cp
hour_vs_cp[hour] = this_hour_cp
prev_hourly_cp = hourly_cp
break
return hour_vs_cp, total_cp
def get_biggest_process(
hour_vs_cp: dict[str, Union[float, None]], cpuser: str, today: str
) -> tuple[dict[str, str], dict[str, str]]:
log_dir = Path('/var/log/sa/rads', today)
top_one = {}
top_summ = {}
for hour, cp_usage in hour_vs_cp.items():
if not cp_usage:
continue
highest_cp = 0.0
highest_one = ''
procs = defaultdict(float)
with subprocess.Popen(
['zgrep', f'^{cpuser} ', str(log_dir / f"{hour}.gz")],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
encoding='ascii',
) as proc:
for line in proc.stdout:
line: str
if '*' in line:
continue
# 'userna5 1.18 cpu 17904k mem 0 io php-cgi \n'
cols = line.split()
name = ' '.join(cols[7:])
cpu = float(cols[1])
# if cpu comes out as 0.00 because it exited so fast, give it
# 0.005s worth of cpu so it registers
procs[name] += cpu or 0.005
if cpu > highest_cp:
highest_cp = cpu
highest_one = name
top_one[hour] = highest_one
if procs:
top_summ[hour] = sorted(procs.items(), key=lambda x: x[1])[-1][0]
else:
top_summ[hour] = ''
return top_one, top_summ
def sa_cm(cpuser: str) -> Union[float, None]:
with subprocess.Popen(
['/usr/sbin/sa', '-cm'],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
encoding='ascii',
) as proc:
for line in proc.stdout:
if line.startswith(f'{cpuser} '):
# 'userna5 1 0.00% 0.00re 0.00% 0.00cp 0.00% 0avio 1092k'
return float(line.split()[5].replace('cp', ''))
return None
def print_pretty(
hour_cp: dict[str, Union[float, None]],
daily_total: float,
top_one: dict[str, str],
top_summ: dict[str, str],
cpuser: str,
):
tbl = PrettyTable(
["Time", "Percent Usage", "Most CPU Used", "Longest-Running Process"]
)
usage_today = sa_cm(cpuser)
for hour in sorted(hour_cp):
span = f'{int(hour) - 3}:00 - {hour}:00'
if hour_cp[hour]:
percent = f"{hour_cp[hour]/usage_today*100:.2f}"
tbl.add_row([span, percent, top_summ[hour], top_one[hour]])
else:
tbl.add_row([span, 'None', 'None', 'None'])
if usage_today - daily_total > 0:
recent_usage = f"{(usage_today - daily_total) / usage_today * 100:.2f}"
tbl.add_row(['Recent', recent_usage, '', ''])
else:
tbl.add_row(['Recent', 'None', 'None', 'Unable to determine'])
print(tbl.get_html_string(attributes={"class": "proc_body"}))
def main():
"""Prints a CPU Usage Report in HTML"""
today = date.today().strftime('%b/%d')
cpuser = parse_args()
hourly_cp, daily_total = parse_hourly_averages(cpuser, today)
top_one, top_summ = get_biggest_process(hourly_cp, cpuser, today)
print_pretty(hourly_cp, daily_total, top_one, top_summ, cpuser)
if __name__ == "__main__":
main()
Zerion Mini Shell 1.0