PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /opt/maint/bin/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //opt/maint/bin/userstats.py |
#!/opt/imh-python/bin/python3 # post CloudLinux user stats to grafana from argparse import ArgumentParser import subprocess import json import requests import traceback from socket import gethostname from datetime import datetime from pathlib import Path from time import time maint_dir = Path("/opt/maint/etc") GLOBAL_IDS = {} def run_script(command, capture_output=True, check_exit_code=True): proc = subprocess.run(command, capture_output=capture_output) out = proc.stdout if proc.returncode != 0 and check_exit_code: raise Exception( f"Command returned exit code {proc.returncode}" f": '{command}'" ) return out.decode("utf-8") class StatsAPI: def __init__(self, conf): self.url = conf["host"] self.key = conf["key"] def post_stats(self, stats, period): server = gethostname() data = dict(server=gethostname(), period=period, stats=stats) response = requests.post( f"{self.url}/v1/userstats/?server={server.split('.')[0]}", json=data, headers={"Authorization": f"{self.key}"}, ) return response def fatal_error(msg): print(f"Error: {msg}") msg = f"{datetime.now()}: {msg}" err_file = maint_dir / "userstats.err" footer = ( "Remove this file if you've reviewed the error message" "or fixed the issue." ) err_file.write_text(f"{msg}\n\n{footer}") exit(1) def load_conf(): conf = maint_dir / "userstats.cfg" if not conf.exists(): fatal_error(f"Configuration file {conf} not found.") return json.loads(conf.read_text()) def parse_args(): parser = ArgumentParser(description="Export user usage statistics") parser.add_argument( "--check", action="store_true", ) return parser.parse_args() def check(): err_file = maint_dir / "userstats.err" status = run_script(["systemctl", "status", "lvestats"]) if "Active: active" not in status: print("CRITICAL: lvestats is not running.") exit(2) # CRITICAL if err_file.exists(): now = datetime.now().astimezone().timestamp() delta = now - err_file.stat().st_mtime if delta < 60 * 15: # 5 minutes print( f"CRITICAL: Userstats failfile tripped. Error in: {err_file} " "When the issue is fixed - the command runs normally - remove the file" ) exit(2) # CRITICAL else: print( f"WARNING: {err_file} exists and is {int(delta/60)} minutes old." ) exit(1) # WARNING if "ERROR" in status: print("WARNING: lvestats service encountered an error.") exit(1) # WARNING print("OK.") exit(0) def detect_period(average_period: int, touch_threshold=False): """ Use touch file to detect period touch_threshold determines if we should touch the file regardless of average_period. True means we only touch it if delta is > average_period. """ touch_file = Path(f"/run/clstats-touch-{average_period}") if not touch_file.exists(): touch_file.touch() return average_period else: mtime = int(touch_file.stat().st_mtime) now = int(time()) time_delta = now - mtime minutes = int(round(time_delta / 60)) if not touch_threshold or minutes >= average_period: touch_file.touch() return max(average_period, minutes) else: return minutes def get_stats(period): stats = [ "/sbin/cloudlinux-statistics", "--period", period, "--json", ] usage_stats = run_script(stats) usage_data = json.loads(usage_stats) rows = [] userdata = usage_data["users"] for user_details in userdata: usage = user_details["usage"] limits = user_details["limits"] faults = user_details["faults"] username = user_details["username"] domain = user_details["domain"] reseller = user_details["reseller"] row = {} def convert_int(stat): return min(int(round(stat["lve"])), 2147483647) usage_convert_table = { "cpu": lambda x: min(int(round(x["lve"])), 65535), "ep": lambda x: min(int(x["lve"]), 65535), "io": lambda x: min(int(x["lve"] / 1024 / 1024), 65535), # mb "iops": lambda x: min(int(x["lve"]), 65535), "pmem": lambda x: min(int(x["lve"] / 1024 / 1024), 65535), # mb "vmem": lambda x: min(int(x["lve"] / 1024 / 1024), 65535), # mb "nproc": lambda x: min(int(x["lve"]), 65535), } row["usage"] = {} for key, value in usage.items(): converted = usage_convert_table[key](value) key_name = key if key in ["io", "pmem", "vmem"]: key_name = f"{key}_m" row["usage"][key_name] = converted row["limits"] = {} for key, value in limits.items(): row["limits"][key] = convert_int(value) row["faults"] = {} for key, value in faults.items(): row["faults"][key] = convert_int(value) row["domain"] = domain row["reseller"] = reseller row["username"] = username rows.append(row) return rows def main(): args = parse_args() if args.check: check() if not Path("/sbin/cloudlinux-statistics").exists(): fatal_error( "cloudLinux-statistics not found. " "This tool only works on CloudLinux systems." ) conf = load_conf() try: api = StatsAPI(conf["api"]) period = detect_period(1) rows = get_stats(f"{period}m") if rows: post1 = api.post_stats(rows, 1) if post1.status_code != 200: fatal_error( f"Failed to post user stats to Grafana: {post1.status_code}\n{post1.text}" ) except Exception as e: stack_trace = traceback.format_exc() fatal_error(f"An error occurred: {str(e)}\n{stack_trace}") if __name__ == "__main__": main()