ok
Direktori : /opt/cloudlinux/venv/lib/python3.11/site-packages/lvestats/lib/info/ |
Current File : //opt/cloudlinux/venv/lib/python3.11/site-packages/lvestats/lib/info/cloudlinux_top.py |
# coding=utf-8 # # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT from __future__ import absolute_import import json import logging import time from distutils.util import strtobool from clcommon.lib import MySQLGovException, MySQLGovernor, GovernorStatus from clcommon import cpapi, FormattedException from clcommon.cpapi import NotSupported from lvestats.lib.commons.logsetup import setup_logging from typing import Callable # pylint: disable=unused-import import lvestats.lib.commons.decorators from lvestats.lib import dbengine from lvestats.lib.commons import func from lvestats.lib.commons import server_status from lvestats.lib.commons.users_manager import g_usersManager from lvestats.lib.commons.func import ( get_users_for_reseller, skip_user_by_maxuid ) from clcommon.cpapi.pluginlib import getuser from lvestats.lib.parsers.cloudlinux_top_argparse import cloudlinux_top_parser from lvestats.lib.uidconverter import uid_to_username from lvestats.lib.config import HIDE_MAX_UID_LVE_PARAMETER NOT_AVAILABLE = "N/A" class CloudLinuxTop(object): def __init__(self, cnf, engine=None): self._log = setup_logging( config=cnf, caller_name="CloudLinuxTop", file_level=logging.WARNING, console_level=logging.FATAL) self.fname = '/var/lve/cloudlinux_top.json' self.fname_db = '/var/lve/cloudlinux_dbtop.json' self.touch_fname = '/var/lve/governor.ts' self.server_id = cnf.get('server_id', 'localhost') self.hide_maxuid_lve = strtobool(cnf.get(HIDE_MAX_UID_LVE_PARAMETER, 'true')) self.engine_error = None if engine is not None: self.engine = engine else: try: self.engine = dbengine.make_db_engine(cnf) except dbengine.MakeDbException as e: self.engine_error = e self.mysql_governor = MySQLGovernor() self.governor_status, self.governor_error = \ self.mysql_governor.get_governor_status() self.governor_mode = func.get_governor_mode(self.mysql_governor) self._admins = self._get_admins() if getuser() == 'root' else set() @staticmethod def _get_admins(): try: return cpapi.admins() except (NotSupported, AttributeError): return set() def _get_username_by_id(self, uid): return uid_to_username( uid=uid, local_server_id=self.server_id, server_id=self.server_id, db_engine=self.engine ) def _get_order_key(self, order, cl_data): # type: (str, dict) -> Callable[dict] """returns key function for sorted""" def mysql_key(value): def key(k): username = self._get_username_by_id(k["id"]) if username not in cl_data["dbgov_data"]: return 0 else: dbgov_stats = cl_data["dbgov_data"][username] return dbgov_stats[value] return key order_key_map = { "cpu": lambda k: k["usage"]["cpu"]["all"], "io": lambda k: k["usage"]["io"]["all"], "mysql_cpu": mysql_key("cpu"), "mysql_io": mysql_key("io"), "iops": lambda k: k["usage"].get("iops", 0), "ep": lambda k: k["usage"]["ep"], "nproc": lambda k: k["usage"].get("pno", 0), "pmem": lambda k: k["usage"].get("mem", 0), } return order_key_map[order] def _load_from_json_file(self, fname, default=None): """ Try load and parse json data from file; return <default> if loading or parsing wrong :param fname: path to parsed file :param None|dict default: default data if loaded file is wrong :return None| dict: return loaded data """ try: with open(fname) as json_file: cl_data = json.load(json_file) except (IOError, OSError): cl_data = default return cl_data def _touch_dbtop(self): """ Trying touch the file to signal new data """ try: func.touch(self.touch_fname) except (IOError, OSError): self._log.error("An error occurred while touching file %s. " "Database statistics may be not available", self.touch_fname) def _load_data(self): """ Load and join data from separate files :return dict: loaded data """ cl_data = self._load_from_json_file(self.fname, {"users": [], "resellers": []}) cl_data.update(self._load_from_json_file(self.fname_db, {"dbgov_data": {}})) return cl_data def _get_data(self, username_filter=None, domain_filter=None, limit=None, show_mysql=True, order="cpu", reseller=None): """ :type order: str :type show_mysql: bool :type limit: int|None :type domain_filter: str|None :type username_filter: str|None :param None|str reseller: get information about given reseller :rtype: tuple[list, list] """ users_result = [] resellers_result = [] users_list = [] current_user_name = getuser() current_user_is_admin = current_user_name == 'root' if current_user_is_admin or reseller: self._touch_dbtop() # touch file as signal to save dbgov data to file g_usersManager.build_users_cache(reseller) if reseller: users_list = get_users_for_reseller(reseller or getuser()) # show reseller information too else: users_list = [current_user_name] cl_data = self._load_data() cl_users_data_ordered = sorted(cl_data["users"], key=self._get_order_key(order, cl_data), reverse=True) for user in cl_users_data_ordered: # skip users with maxuid lve ids due to some client`s complaints (details: LVES-929) if self.hide_maxuid_lve and skip_user_by_maxuid(user['id']): continue username = self._get_username_by_id(user["id"]) or NOT_AVAILABLE # users must not be skipped if current user is root and when --for-reseller is not passed should_skip_users = not current_user_is_admin or reseller is not None if should_skip_users and username not in users_list or username in self._admins: continue if username == NOT_AVAILABLE: user["domain"] = NOT_AVAILABLE user["reseller"] = NOT_AVAILABLE else: user["reseller"] = g_usersManager.get_reseller(username, raise_exc=False) or NOT_AVAILABLE user["domain"] = g_usersManager.get_domain(username, raise_exc=bool(domain_filter)) or NOT_AVAILABLE if (domain_filter and (user["domain"] == NOT_AVAILABLE or domain_filter not in user["domain"])) or \ (username_filter and (username == NOT_AVAILABLE or username_filter not in username)): continue user["username"] = username if show_mysql and self.governor_status == GovernorStatus.ENABLED: self._add_mysql_data(user, cl_data, username) users_result.append(user) if limit is not None and 0 < limit <= len(users_result): break cl_resellers_data_ordered = \ sorted(cl_data.get("resellers", []), key=self._get_order_key(order, cl_data), reverse=True) if current_user_is_admin: for reseller_info in cl_resellers_data_ordered: username = self._get_username_by_id(reseller_info['id']) # get only reseller that we need (if needed) if reseller and username != (reseller or getuser()): continue if username is None: username = NOT_AVAILABLE reseller_info["name"] = username resellers_result.append(reseller_info) return users_result, resellers_result def _add_mysql_data(self, user, cl_data, username): """ add mysql statistics to the user dict :type cl_data: dict :type user: dict :type username: str """ dbgov_stats = cl_data["dbgov_data"].get(username, {"cpu": 0, "io": 0, "cause_of_restrict": "-", "time_of_restrict": 0}) if self.governor_mode != "all": try: mysql_limits = self.mysql_governor.get_limits_by_user(username) except MySQLGovException as e: self.governor_status = GovernorStatus.ERROR self.governor_error = e else: user["limit"]["cpu"]["mysql"] = mysql_limits[0] user["limit"]["io"]["mysql"] = mysql_limits[1] * 1024 # in bytes user["usage"]["cpu"]["mysql"] = dbgov_stats["cpu"] user["usage"]["io"]["mysql"] = dbgov_stats["io"] else: user["limit"]["cpu"]["mysql"] = "-" user["limit"]["io"]["mysql"] = "-" user["usage"]["cpu"]["mysql"] = min(dbgov_stats["cpu"], user["usage"]["cpu"]["all"]) user["usage"]["io"]["mysql"] = min(dbgov_stats["io"], user["usage"]["io"]["all"]) if dbgov_stats["cause_of_restrict"] != "-": user["restricted"] = True user["restriction"] = { "time": dbgov_stats["time_of_restrict"], "reason": "Violated the {0} limit".format(dbgov_stats["cause_of_restrict"])} else: user["restricted"] = False @staticmethod def to_json(value): """ :type value: dict :rtype: str """ return json.dumps(value, indent=4, sort_keys=True) @lvestats.lib.commons.decorators.no_sigpipe def main(self, *args): """ :rtype: (str, int) """ parser = cloudlinux_top_parser() result = {"timestamp": time.time()} try: namespace = parser.parse_args(args) if not namespace.json: return "Use --json option, other modes currently unsupported", 1 except ValueError as e: result["result"] = e.args[0] result["context"] = {} return self.to_json(result), 0 if getuser() != 'root' and namespace.for_reseller is not None and namespace.for_reseller != getuser(): error_msg = 'Permission denied. Reseller can view ' \ 'information only about himself. ' \ 'Option --for-reseller={} is forbidden.'.format(namespace.for_reseller) result['result'] = error_msg result["context"] = {} return self.to_json(result), 1 lvestats_status = server_status.check_server_status() if lvestats_status != server_status.RUNNING: result["result"] = server_status.status_description[lvestats_status] elif self.engine_error is not None: result["result"] = self.engine_error.message # pylint: disable=exception-message-attribute result["context"] = self.engine_error.context else: try: users, resellers = self._get_data( username_filter=namespace.username, domain_filter=namespace.domain, limit=namespace.max, show_mysql=not namespace.hide_mysql, order=namespace.order_by, reseller=namespace.for_reseller) except IOError as e: result["result"] = str(e) result["context"] = {} except FormattedException as e: result["result"] = e.message result["context"] = e.context else: result.update({ "result": "success", "users": users, "resellers": resellers, "mySqlGov": self.governor_status, "mySqlGovMode": self.governor_mode}) # broken governor is a non-fatal problem # and lve statistics should works even in that case # so let's threat "error" status as "warning" # and show "warning" message in web ui # anyway, at the moment lvemanager ignores "errors" field if self.governor_status == GovernorStatus.ERROR: result["warning"] = self.governor_error.message # pylint: disable=exception-message-attribute try: result["context"] = self.governor_error.context except AttributeError: result["context"] = {} return self.to_json(result), 0