#!/opt/imh-python/bin/python3
# post CloudLinux user stats to grafana
from argparse import ArgumentParser
from socket import gethostname
from datetime import datetime
maint_dir = Path("/opt/maint/etc")
def run_script(command, capture_output=True, check_exit_code=True):
proc = subprocess.run(command, capture_output=capture_output)
if proc.returncode != 0 and check_exit_code:
f"Command returned exit code {proc.returncode}" f": '{command}'"
return out.decode("utf-8")
def __init__(self, conf):
def post_stats(self, stats, period):
data = dict(server=gethostname(), period=period, stats=stats)
response = requests.post(
f"{self.url}/v1/userstats/?server={server.split('.')[0]}",
headers={"Authorization": f"{self.key}"},
msg = f"{datetime.now()}: {msg}"
err_file = maint_dir / "userstats.err"
"Remove this file if you've reviewed the error message"
err_file.write_text(f"{msg}\n\n{footer}")
conf = maint_dir / "userstats.cfg"
fatal_error(f"Configuration file {conf} not found.")
return json.loads(conf.read_text())
parser = ArgumentParser(description="Export user usage statistics")
return parser.parse_args()
err_file = maint_dir / "userstats.err"
status = run_script(["systemctl", "status", "lvestats"])
if "Active: active" not in status:
print("CRITICAL: lvestats is not running.")
now = datetime.now().astimezone().timestamp()
delta = now - err_file.stat().st_mtime
if delta < 60 * 15: # 5 minutes
f"CRITICAL: Userstats failfile tripped. Error in: {err_file} "
"When the issue is fixed - the command runs normally - remove the file"
f"WARNING: {err_file} exists and is {int(delta/60)} minutes old."
print("WARNING: lvestats service encountered an error.")
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():
mtime = int(touch_file.stat().st_mtime)
minutes = int(round(time_delta / 60))
if not touch_threshold or minutes >= average_period:
return max(average_period, minutes)
"/sbin/cloudlinux-statistics",
usage_stats = run_script(stats)
usage_data = json.loads(usage_stats)
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"]
return min(int(round(stat["lve"])), 2147483647)
"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),
for key, value in usage.items():
converted = usage_convert_table[key](value)
if key in ["io", "pmem", "vmem"]:
row["usage"][key_name] = converted
for key, value in limits.items():
row["limits"][key] = convert_int(value)
for key, value in faults.items():
row["faults"][key] = convert_int(value)
row["reseller"] = reseller
row["username"] = username
if not Path("/sbin/cloudlinux-statistics").exists():
"cloudLinux-statistics not found. "
"This tool only works on CloudLinux systems."
api = StatsAPI(conf["api"])
period = detect_period(1)
rows = get_stats(f"{period}m")
post1 = api.post_stats(rows, 1)
if post1.status_code != 200:
f"Failed to post user stats to Grafana: {post1.status_code}\n{post1.text}"
stack_trace = traceback.format_exc()
fatal_error(f"An error occurred: {str(e)}\n{stack_trace}")
if __name__ == "__main__":