Mini Shell

Direktori : /opt/sharedrads/
Upload File :
Current File : //opt/sharedrads/check_lve

#!/opt/imh-python/bin/python3
"""Generate a chart of resource usage as reported by lveinfo. This script can
also generate a resource usage chart for user review."""
from rads import prompt_y_n, is_cpuser
from rads.color import red, green
from collections import OrderedDict
from prettytable import PrettyTable
from pwd import getpwuid, getpwnam
from grp import getgrnam
import subprocess
import argparse
import json
import sys
import os

USERCOUNT = 15


def parse_args():
    """Get command line arguments"""
    parser = argparse.ArgumentParser(description=__doc__)
    # fmt: off
    group = parser.add_mutually_exclusive_group(required=True)
    # mutually exclusive arguments
    group.add_argument(
        "-c", "--cpu", action="store_true", dest="CPU",
        help="Show and sort by CPU usage",
    )
    group.add_argument(
        "-m", "--memory", action="store_true", dest="Mem",
        help="Show and sort by memory (physical and virtual) usage",
    )
    group.add_argument(
        "-p", "--procs", action="store_true", dest="Proc",
        help="Show and sort by processes",
    )
    group.add_argument(
        "-i", "--io", action="store_true", dest="IO",
        help="Show and sort by IO and IO Operations",
    )
    group.add_argument(
        "-f", "--faults", action="store_true", dest="Fault",
        help="Show and sort by cpu, proc, mem, and IO faults.",
    )
    group.add_argument(
        "--chart", action="store_true", dest="Chart",
        help="Generate a chart for the given user in --location",
    )
    # additional arguments (location and user)
    parser.add_argument(
        "-l", "--location", action="store", dest="Location", nargs="?",
        help="Designate the location for the chart image generated "
        "e.g. /home/userna5/lvechart.9-30.png",
    )
    parser.add_argument(
        "-u", "--user", action="store", dest="User", nargs="?",
        help="User for chart generation",
    )
    parser.add_argument(
        "-t", "--time", action="store", dest="Time", nargs="?", default="5m",
        help="time period; specify minutes (m), hours (h), days (d), today, "
        "yesterday; 5m - last 5 minutes, 4h - last four hours, 2d - last "
        "2 days including today",
    )
    # fmt: on
    # get the args
    results = parser.parse_args()
    # if a location is provided, we probably meant to --chart
    if results.Location and not results.Chart:
        print("Assuming --chart")
        results.Chart = True
    # if --chart but no --user or --location
    if results.Chart and (not results.User or not results.Location):
        print(red("--chart requires a user and a location."))
        sys.exit(1)
    # check to make sure User is an actual cPanel user
    if results.User and not is_cpuser(results.User):
        print(red(f"{results.User} is not a valid cPanel user."))
        sys.exit(1)
    # make sure the path to --location exists
    if results.Location and not os.path.exists(
        os.path.dirname(results.Location)
    ):
        print(
            red(os.path.dirname(results.Location)),
            red("is not a valid path. Does it exist?"),
        )
        sys.exit(1)
    # get the owner of the --location dir
    if results.Location:
        owner = getpwuid(
            os.stat(os.path.dirname(results.Location)).st_uid
        ).pw_name
        # is the --location owned by --user? If not, verify this is okay
        if results.User and results.User != owner:
            # The user and location owner don't match. Prompt to continue
            if not prompt_y_n(
                f"{owner} is the owner of {results.Location} but you entered "
                f"--user {results.User}. Proceed?"
            ):
                print("Exiting.")
                sys.exit(1)
    return results


def generate_chart(user, location):
    """Generate the LVE chart in the provided location
    lvechart --period=5d --user=$USER --output=$LOCATION"""
    userarg = f"--user={user}"
    locationarg = f"--output={location}"
    try:
        # run lvechart --period=5d --user=$user --output=$location
        subprocess.call(["lvechart", "--period=5d", userarg, locationarg])
        os.chown(location, getpwnam(user).pw_uid, getgrnam(user).gr_gid)
        print(green(f"Chart successfully generated for {user} at {location}"))
    except subprocess.CalledProcessError as error:
        print(red(error.output))


def lveinfo(sortmethod, time, columns):
    """Run lve info with the provided arguments
    lveinfo -d --period=5m -o $sort --show-columns $columns"""
    try:
        # fmt: off
        data = subprocess.check_output([
            "lveinfo", "-d", f"--period={time}", "--json", "-o", sortmethod,
            "--show-columns", columns,
        ])
        # fmt: on
        return data
    except subprocess.CalledProcessError as error:
        print(red(error.output))
        sys.exit(1)


def tableify(table, data):
    """Format given data into a pretty table"""
    for row in data:
        table.add_row([key[1] for key in row.items()])
    print(table)


def sizeof_fmt(num, suffix='B'):
    for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
        if abs(num) < 1024.0:
            return f"{num:3.1f}{unit}{suffix}"
        num /= 1024.0
    return "{:.1f}{}{}".format(num, 'Yi', suffix)


def main():
    """Main program logic. Build the arguments for lveinfo, collect the json
    output, rearrange and reformat as needed, and send to prettytable for
    actual output."""
    table = PrettyTable()
    args = parse_args()
    data = None
    if args.Chart:
        # skip everything else, we're making a chart for the user
        generate_chart(args.User, args.Location)
    # collect the json of the desired resource check
    elif args.CPU:
        print(f"Printing {args.Time} CPU usage statistics...")
        resources = ["ID", "uCPU", "lCPU", "CPUf"]
        sortmethod = "aCPU"
        table.field_names = [
            "User",
            "AVG used",
            "Core Limit",
            "Limit Reached #",
        ]
    elif args.Mem:
        print(f"Printing {args.Time} Memory usage statistics...")
        resources = ["ID", "aPMem", "lPMem", "aVMem", "lVMem", "PMemF", "VMemF"]
        sortmethod = "aPMem"
        table.field_names = [
            "User",
            "AVG PMem Used",
            "PMem Limit",
            "AVG VMem Used",
            "VMem Limit",
            "Limit Reached #",
        ]
    elif args.Proc:
        print(f"Printing {args.Time} process counts...")
        resources = ["ID", "aEP", "lEP", "EPf", "aNproc", "lNproc", "NprocF"]
        sortmethod = "aNproc"
        table.field_names = [
            "User",
            "Entry Procs",
            "EP Limit",
            "EP Limit Reached #",
            "Total Procs",
            "Proc Limit",
            "Limit Reached #",
        ]
    elif args.IO:
        print(f"Printing {args.Time} IO usage statistics")
        resources = [
            "ID",
            "aIO",
            "uIO",
            "lIO",
            "IOf",
            "aIOPS",
            "lIOPS",
            "IOPSf",
        ]
        sortmethod = "aIO"
        table.field_names = [
            "User",
            "I/O",
            "%",
            "I/O Limit",
            "I/O Limit Reached #",
            "I/O Ops.",
            "I/O Ops. Limit",
            "Ops. Limit Reached #",
        ]
    elif args.Fault:
        print(f"Printing usage limits hit in the time period {args.Time}")
        resources = [
            "ID",
            "CPUf",
            "EPf",
            "NprocF",
            "VMemF",
            "PMemF",
            "IOf",
            "IOPSf",
        ]
        sortmethod = "any_faults"
        table.field_names = [
            "User",
            "CPU",
            "Entry Procs",
            "Total Procs",
            "Memory",
            "I/O",
            "I/O Ops.",
        ]
    if not args.Chart:
        # if we're doing anything but generating a chart, run lveinfo
        data = lveinfo(sortmethod, args.Time, ",".join(resources))
    if data:
        # load lveinfo's json output into a dict
        data = json.loads(data)
        tablelist = []
        iteration = 0
        for user in data["data"]:
            if iteration == USERCOUNT:
                break
            # the info from lve isn't ordered. Put it in our desired order
            # based on the resources list
            userinfo = OrderedDict()
            mems = {"aPMem", "lPMem", "aVMem", "lVMem"}
            for resource in resources:
                # some special formatting e.g. sizes and percentages
                if resource in ("uCPU", "uIO"):
                    user[resource] = f"{user[resource]:.0%}"
                if resource == "lCPU":
                    user[resource] = user[resource] / 100.0
                if resource in mems and isinstance(user[resource], int):
                    user[resource] = sizeof_fmt(user[resource])
                if resource in ["aIO", "lIO"]:
                    user[resource] = sizeof_fmt(user[resource] * 1024)
                if "MemF" in resource:
                    # Virtual and Physical Memory faults can be combined
                    if "Memory" in user:
                        userinfo["Memory"] += user[resource]
                    else:
                        userinfo["Memory"] = user[resource]
                else:
                    userinfo[resource] = user[resource]
            tablelist.append(userinfo)
            iteration += 1
        # send the collected data to pretty table
        tableify(table, tablelist)


if __name__ == "__main__":
    main()

Zerion Mini Shell 1.0