ok
Direktori : /opt/cloudlinux/venv/lib64/python3.11/site-packages/lvestats/lib/ |
Current File : //opt/cloudlinux/venv/lib64/python3.11/site-packages/lvestats/lib/lveinfolib.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 from __future__ import division from past.builtins import basestring from builtins import map, zip import copy import datetime import logging import pwd import time import warnings from distutils.util import strtobool from sqlalchemy import Float, Integer from sqlalchemy import exc as sa_exc from sqlalchemy import text from sqlalchemy.sql import and_, or_, not_, select, label, desc, asc from sqlalchemy.sql.expression import func, ColumnElement, case, alias, cast from lvestats.core.plugin import LveStatsPluginTerminated from lvestats.lib.commons.dateutil import (gm_datetime_to_unixtimestamp, round_1m, str_to_timedelta, unixtimestamp_to_gm_datetime, gm_to_local, local_to_gm) from lvestats.lib.commons.sizeutil import ( convert_bytes, convert_powers_of_1000, mempages_to_bytes) from lvestats.orm import history, history_x60, servers, LVE_STATS_2_TABLENAME_PREFIX from lvestats.lib.commons.func import ( skip_user_by_maxuid, get_current_max_lve_id, ) from lvestats.lib.config import ( HIDE_MAX_UID_LVE_PARAMETER, read_config, ) __all__ = ('HistoryShow', 'HistoryShowUnion', 'OutputFormatter', 'get_lve_version') def get_lve_version(dbengine, server_id): sql_query_lve_version = select([servers.lve_version]).where(servers.server_id == server_id) with dbengine.begin() as transaction_: cursor_ = transaction_.execute(sql_query_lve_version) if cursor_.returns_rows: res = cursor_.fetchone() if res is None: return 6 else: return int(res['lve_version']) else: return 6 def servers_info(dbengine): sql_query_lve_version = select([servers.server_id, servers.lve_version]) return dbengine.execute(sql_query_lve_version) def convert_key_to_label(key_): return str.lower(key_) def add_labes_to_column(func_dict): """ Add label (on SQL add ".. as ..") to aggregate_func_dict key of dict convert to lowercase and add as label :param dict func_dict: :return dict: """ func_dict_labeled = dict() for key_, sqlalchemy_func in list(func_dict.items()): if issubclass(sqlalchemy_func.__class__, ColumnElement): func_labeled = label(convert_key_to_label(key_), sqlalchemy_func) else: func_labeled = sqlalchemy_func func_dict_labeled[key_] = func_labeled return func_dict_labeled def convert_to_list(arg): if isinstance(arg, (basestring, int)): return [arg] return arg class OutputFormatter(object): def __init__(self, fields, rows=None, orders=None): self.rows = rows or list() self.fields = list(fields) self._fields_lowered = [_.lower() for _ in fields] # self.fields use for output self._orders = list() self._hidden_fields = set() # self._fields indexes list fields to hide if orders: for field, order in orders: self.add_order(field, order) def get_fields(self): if self._hidden_fields: return [f_ for f_ in self.fields if f_ not in self._hidden_fields] else: return self.fields def set_rows(self, rows): """ Use this method if you used hide_fields number items in row must bee same as in fields after hide """ self.rows = rows def hide_fields(self, h_fields): """ :param tuple|list h_fields: :return: """ self._hidden_fields = self._hidden_fields.union(set(h_fields)) def add_order(self, fields, order): """ :param list|tuples fields: :param order: field to use to order result :return: """ if isinstance(order, str): try: order = getattr(self, order) except AttributeError: raise ValueError('Non such %s order' % order) if not hasattr(order, '__call__'): raise ValueError('input object %s must be callable' % str(order)) self._orders.append(({_.lower() for _ in fields}, order)) def _convert_line(self, row_): """ :param iterable row_: :return: """ row_ = list(row_) row_out = list() for field_, r_ in zip(self._fields_lowered, row_): if field_ in self._hidden_fields: # continue if field must be hide continue # use many orders to one cell for order_fields, order in self._orders: if field_ in order_fields: try: r_ = order(r_) except (TypeError, ValueError, KeyError, IndexError): pass row_out.append(r_) return row_out def __iter__(self): for row_ in self.rows: yield self._convert_line(row_) def __getitem__(self, index): if isinstance(index, slice): return list(map(self._convert_line, self.rows[index.start:index.stop])) return self._convert_line(self.rows[index]) def __len__(self): return len(self.rows) def get_corrected_list(self): return list(self) # build-in orders @staticmethod def strftime(value, format_='%m-%d %H:%M'): return value.strftime(format_) @staticmethod def percentage(value): if value is None: return '-' try: float(value) except ValueError: return value return str(value*100) + '%' @staticmethod def bytes(value): if value is None: return '-' return convert_bytes(value) @staticmethod def powers_of_1000(value): if value is None: return '-' return convert_powers_of_1000(value) @staticmethod def username(value): try: return pwd.getpwuid(int(value)).pw_name except KeyError: return value @staticmethod def datetime(value): """ Convert unix timestamp to datetime (local timezone) """ return datetime.datetime.fromtimestamp(value) def enumerate_duplicate_columns(columns): """ Enumerate if columns name or functions are duplicated Use for force add duplicate columns to select operator :param list|tuple columns: :return list|tuple: enumerated columns """ output = list() output_str = list() # for controlling columns count for c_ in columns: c_str = str(c_) dubl_count = output_str.count(c_str) if dubl_count >= 1: # check whether there is a duplicate; "c_ in columns" for sqlalchemy classes not work # check if column is string or function and get column name if isinstance(c_, basestring): c_name = c_ else: c_name = c_.name # numbering duplicate columns c_name += '_' + str(dubl_count + 1) c_ = label(c_name, c_) # rename column output.append(c_) output_str.append(c_str) return output usage_to_limit_dict = { 'aCPU'.lower(): 'lCPU'.lower(), 'mCPU'.lower(): 'lCPU'.lower(), 'aVMem'.lower(): 'lVMem'.lower(), 'mVMem'.lower(): 'lVMem'.lower(), 'aEP'.lower(): 'lEP'.lower(), 'mEP'.lower(): 'lEP'.lower(), 'aPMem'.lower(): 'lPMem'.lower(), 'mPMem'.lower(): 'lPMem'.lower(), 'aNproc'.lower(): 'lNproc'.lower(), 'mNproc'.lower(): 'lNproc'.lower(), 'aIO'.lower(): 'lIO'.lower(), 'mIO'.lower(): 'lIO'.lower(), 'aIOPS'.lower(): 'lIOPS'.lower(), 'mIOPS'.lower(): 'lIOPS'.lower() } FIELD_TO_TABLE_COLUMN = { 'ID': 'id', 'aCPU': 'cpu', 'aVMem': 'mem', 'aEP': 'mep', 'aPMem': 'memphy', 'aIO': 'io', 'aNproc': 'nproc', 'aIOPS': 'iops', 'lCPU': 'cpu_limit', 'lEP': 'mep_limit', 'lVMem': 'mem_limit', 'lPMem': 'lmemphy', 'lIO': 'io_limit', 'lNproc': 'lnproc', 'lIOPS': 'liops', 'VMemF': 'mem_fault', 'PMemF': 'memphy_fault', 'EPf': 'mep_fault', 'NprocF': 'nproc_fault', 'CPUf': 'cpu_fault', 'IOf': 'io_fault', 'IOPSf': 'iops_fault', 'uCPU': 'cpu', 'uEP': 'mep', 'uVMem': 'mem', 'uPMem': 'memphy', 'uIO': 'io', 'uNproc': 'nproc', 'uIOPS': 'iops', 'mCPU': 'cpu', 'mEP': 'mep', 'mVMem': 'mem', 'mPMem': 'memphy', 'mNproc': 'nproc', 'mIO': 'io', 'mIOPS': 'iops', } FIELD_AVERAGE = ['aCPU', 'aVMem', 'aPMem', 'aEP', 'aNproc', 'aIO', 'aIOPS'] FIELD_LIMIT = ['lCPU', 'lVMem', 'lPMem', 'lEP', 'lNproc', 'lIO', 'lIOPS'] FIELD_FAULT = ['CPUf', 'VMemF', 'PMemF', 'EPf', 'NprocF', 'IOf', 'IOPSf'] FIELD_USAGE = ['uCPU', 'uVMem', 'uPMem', 'uEP', 'uNproc', 'uIO', 'uIOPS'] FIELD_MAX = ['mCPU', 'mVMem', 'mPMem', 'mEP', 'mNproc', 'mIO', 'mIOPS'] KEYS_NORMALIZATION_LOOKUP_TABLE = {'FROM' : 'From', 'TO' : 'To', 'ANYF': 'anyF'} for key in list(FIELD_TO_TABLE_COLUMN.keys()): KEYS_NORMALIZATION_LOOKUP_TABLE[key.upper()] = key def normalize_optional_column_names(names): if names is None: return None return normalize_column_names(names) def normalize_column_names(names): result = [] for name in names: result.append(normalize_column_name(name)) return result def normalize_column_name(name): if name: return KEYS_NORMALIZATION_LOOKUP_TABLE[name.upper()] return None time_unit_orders = [ ('10m', lambda dt: dt - datetime.timedelta(minutes=dt.minute % 10 + 10)), ('1h', lambda dt: dt.replace(minute=0) if dt.minute else dt - datetime.timedelta(hours=1)), # round to hour ('1d', lambda dt: dt.replace(hour=0, minute=0)), # round to day ] def dyn_time_unit_groups(period_from, period_to): period_groups = [round_1m(period_from), round_1m(period_to + datetime.timedelta(minutes=1))] time_unit_list = ['1m'] _from_order_fun = round_1m for time_unit, order_fun in time_unit_orders: from_to_point = order_fun(period_groups[1]) if from_to_point - period_groups[0] >= str_to_timedelta(time_unit): _from_order_fun = order_fun period_groups.insert(1, from_to_point) time_unit_list.append(time_unit) period_groups[0] = _from_order_fun(period_groups[0]) # prepare to output as list of tuples # (<From|datetime>, <To|datetime>, <time-unit|int>) from_to_groups = list() for index, time_unit in enumerate(time_unit_list): from_to_groups.append((period_groups[-index - 2], period_groups[-index - 1], int(str_to_timedelta(time_unit).total_seconds()))) return from_to_groups class HistoryShow(object): def __init__(self, dbengine, period_from, period_to, uid=None, show_columns=None, server_id='localhost', time_unit=None, order_by=None, by_usage=None, by_usage_percentage=0.9, by_fault=None, threshold=1, limit=0, table=None, log=None, time_count=None, show_idle=False): """ Show different statistics from history table :param sqlalchemy.engine.base.Engine dbengine: database engine to use :param datetime.datetime|float|int period_from: start time retrieve data :param datetime.datetime|float|int period_to: end time retrieve data :param int|None|list|tuple uid: filter the output information to the user uid :param tuple|list show_columns: display columns in the order specified. If not, show all supported valid column names: 'aCPU', 'lPMem', 'uIO', 'uEP', 'lEP', 'aVMem', 'PMemF', 'lVMem', 'NprocF', 'anyF', 'aNproc', 'VMemF', 'ID', 'lCPU', 'aIOPS', 'aEP', 'aPMem', 'uPMem', 'lIO', 'lIOPS', 'uCPU', 'lNproc', 'aIO', 'uIOPS', 'EPf', 'uVMem', 'uNproc' :param str server_id: filtering the output for "server id" :param int time_unit: grouping output over an interval of time (in seconds) :param str|None order_by: sorting output by column name (supported by columns) :param str|tuple|list by_usage: filtering are grouped data for the percentage of the use of resources :param float by_usage_percentage: percent for the parameter setting 'by_usage' :param tuple|list|None by_fault: filtering data are grouped for quantity faults (None if it is not filtered) valid names: 'aCPU', 'lPMem', 'uIO', 'uEP', 'lEP', 'aVMem', 'PMemF', 'lVMem', 'NprocF', 'anyF', 'aNproc', 'VMemF', 'ID', 'lCPU', 'aIOPS', 'aEP', 'aPMem', 'uPMem', 'lIO', 'lIOPS', 'uCPU', 'lNproc', 'aIO', 'uIOPS', 'EPf', 'uVMem', 'uNproc' :param threshold: number faults for filtering the data are grouped (used together with by_fault) :param int|None limit: limit on the number of output data (if 0 or None, then the limit is not set) :return generator: returns a list/generator of data with the order set out in the 'show_columns' """ self.dbengine = dbengine self.uid = uid self._is_multi_uids = not isinstance(uid, int) if show_columns is None: show_columns = get_supported_columns(lve_version=get_lve_version(dbengine=dbengine, server_id=server_id)) show_columns.insert(0, 'ID') self.show_columns = normalize_column_names(show_columns) self.server_id = server_id self.time_unit = time_unit self.by_fault = normalize_optional_column_names(by_fault) if order_by: self.order_by = normalize_column_name(order_by) elif self.by_fault and normalize_column_name('anyF') in self.by_fault: self.order_by = normalize_column_name('CPUf') else: self.order_by = self.by_fault and self.by_fault[0] self.by_usage = normalize_optional_column_names(by_usage) self.by_usage_percentage = by_usage_percentage self.threshold = threshold self.limit = limit self.log = log or logging.getLogger('SQL') self.table = table if table is not None else history.__table__ # "or" not supported in this self._table_alive = alias(self.table, 'alive') # alias of main table to using in self join self.period_from = period_from if isinstance(period_from, (int, float)) else gm_datetime_to_unixtimestamp(period_from) self.period_to = period_to if isinstance(period_to, (int, float)) else gm_datetime_to_unixtimestamp(period_to) self.time_count = time_count or self.get_time_count() # correct cpu/100 # we still have to round it, as it seems <[27.333/100] = [0.27332999999999996] - who knows why :( self.result_corrector = OutputFormatter(fields=self.show_columns, orders=[ [FIELD_MAX + FIELD_AVERAGE + FIELD_LIMIT + FIELD_USAGE, lambda x:round(x, 3)], # pylint: disable=round-builtin [['aCPU', 'lCPU', 'mCPU'], lambda item: round(float(item)/100., 5)]]) # pylint: disable=round-builtin self.hide_maxuid_lve = strtobool(read_config().get(HIDE_MAX_UID_LVE_PARAMETER, 'true')) def set_normalised_output(self): # correct data obtained from database # round EP IOPS Nproc output self.result_corrector.add_order( fields=['aEP', 'mEP', 'lEP', 'aNproc', 'mNproc', 'lNproc', 'aIOPS', 'mIOPS', 'lIOPS'], order=lambda x: int(round(x))) # pylint: disable=round-builtin if self.dbengine.url.drivername != "sqlite": self.result_corrector.add_order( fields=['aVMem', 'mVMem', 'lVMem', 'aPMem', 'mPMem', 'lPMem', 'aIO', 'mIO', 'lIO', 'uCPU', 'uEP', 'uVMem', 'uPMem', 'uIO', 'uNproc', 'uIOPS'], order=float) self.result_corrector.add_order( fields=['EPf', 'VMemF', 'CPUf', 'PMemF', 'NprocF', 'IOf', 'IOPSf'], order=int) # convert Mem to bytes self.result_corrector.add_order( fields=['aVMem', 'mVMem', 'lVMem', 'aPMem', 'mPMem', 'lPMem'], order=mempages_to_bytes) def _where_time_period(self, table=None): """ Generate WHERE created BETWEEN xxxxxxxxx AND yyyyyyyy :return: """ # filtering condition by time period if table is None: table = self.table return table.c.created.between(self.period_from, self.period_to) def _where_server_id(self): """ Generate WHERE server_id = 'server_name' :return: """ return self.table.c.server_id == self.server_id def _where_uid(self, uid=-1, table=None): """Generate WHERE id = 'user_uid'""" if table is None: table = self.table if uid == -1: uid = self.uid if uid is None: if self.hide_maxuid_lve: # skip ids in range(MAX_UID, MAX_LVE_ID), because ids > MAX_LVE_ID may contain info # about reseller`s limits return and_(table.c.id > 0, or_(not_(skip_user_by_maxuid(table.c.id)), table.c.id > get_current_max_lve_id())) return table.c.id > 0 elif isinstance(uid, (list, tuple)): if self.dbengine.url.drivername == 'sqlite': # little workaround for sqlite's limit of 999 variables # let's compile query manually return text(table.c.id.in_(list(uid)) .expression.compile(compile_kwargs={"literal_binds": True}) .string) else: # mysql or postgresql do not have such limit return table.c.id.in_(list(uid)) else: return table.c.id == uid def get_time_count(self): """ SELECT count(*) FROM lve_stats2_history WHERE id = 0 AND created BETWEN xxxx AND yyyy server_id = 'localhost' """ where = and_( history.created.between(self.period_from, self.period_to), history.id == 0, history.server_id == self.server_id) query = select([text('count(*)')]).where(where) time_start = time.time() q = str(query.compile(compile_kwargs={"literal_binds": True})).replace('\n', ' ') self.log.debug(q) data = self.dbengine.execute(query) self.log.debug('query time: %s', time.time() - time_start) return data.fetchall()[0][0] def _fun_avg(self, item): """ Generate aggregate function for calculate average for example sum(lve_stats2_history.cpu) / 60 :param item: :return: """ return cast(func.sum(item)/cast(self._fun_time_count(), Float), Float) def _fun_limit(self, item): """ Generate aggregate function for calculate limit if column_limit is zero its mean no limit for example CASE WHEN (min(lve_stats2_history.cpu_limit) > 0) THEN max(lve_stats2_history.cpu_limit) ELSE 0 END :param item: :return: """ return case([(func.min(item) > 0, func.max(item))], else_=0) def _fun_fault(self, item): """ Generate aggregate function for calculate fault for example sum(lve_stats2_history.cpu_fault) :param item: :return: """ return func.sum(item) def _fun_usage(self, item, item_limit): """ Generate aggregate function for calculate resource usage equivalent average/limit for example CASE WHEN ( CASE WHEN (min(lve_stats2_history.cpu_limit) > 0) THEN max(lve_stats2_history.cpu_limit) ELSE 0 END IS NULL) THEN NULL WHEN (CASE WHEN (min(lve_stats2_history.cpu_limit) > 0) THEN max(lve_stats2_history.cpu_limit) ELSE 0 END > 0) THEN (sum(lve_stats2_history.cpu) / 1422) / CASE WHEN (min(lve_stats2_history.cpu_limit) > 0) THEN max(lve_stats2_history.cpu_limit) ELSE 0 END END :param item: :return: """ # noinspection PyComparisonWithNone return case([ # Don't use "is None" here. (self._fun_limit(item_limit) == None, None), # NOQA (self._fun_limit(item_limit) > 0, self._fun_avg(item) / self._fun_limit(item_limit)), ], else_=None) def _fun_max(self, item, item_limit, item_fault): """ Generate aggregate function for calculate maximum resource usage; for backward capability with lve-stats 0.x for example: CASE WHEN (sum(lve_stats2_history.cpu_fault) > 0) THEN max(lve_stats2_history.cpu_limit) ELSE max(lve_stats2_history.cpu) END :param item: :param item_limit: :param item_fault: :return: """ return case([(func.sum(item_fault) > 0, func.max(item_limit))], else_=func.max(item)) def _fun_time_count(self): if self._check_need_join(): return text('count(*)') else: return self.time_count def _fun_time_from(self): if self._is_multi_uids: return self.period_from else: if self._check_need_join(): _table = self._table_alive else: _table = self.table if self.dbengine.url.drivername == 'sqlite': # cast(..., Integer) using for compatibility with lve-stats-2.1-8 database; 'created' saved as float return cast((_table.c.created - self.period_from)/self.time_unit, Integer)*self.time_unit + self.period_from else: return func.floor((_table.c.created - self.period_from)/self.time_unit)*self.time_unit + self.period_from def _fun_time_to(self): # in case of changes here don't forget to check _group_by_query if self._is_multi_uids: return self.period_to else: return self._fun_time_from() + self.time_unit def _fun_user_id(self): if self._is_multi_uids: return label('ID', self.table.c.id) else: return label('ID', text(str(self.uid))) def _aggregate_fun_case(self, item): """ Function for obtain aggregate function (or column name) by column name :param item: 'aCPU', 'aVMem', 'aPMem', 'aEP', 'aNproc', 'aIO', 'aIOPS', 'lCPU', 'lVMem', 'lPMem', 'lEP', 'lNproc', 'lIO', 'lIOPS', 'CPUf', 'VMemF', 'PMemF', 'EPf', 'NprocF', 'IOf', 'IOPSf', 'uCPU', 'uVMem', 'uPMem', 'uEP', 'uNproc', 'uIO', 'uIOPS', 'mCPU', 'mVMem', 'mPMem', 'mEP', 'mNproc', 'mIO', 'mIOPS', 'anyF', 'ID', 'From', 'To' :type item: str :return: """ if item == 'anyF': fun_ = func.sum( self.table.c.mem_fault + self.table.c.memphy_fault + self.table.c.mep_fault + self.table.c.nproc_fault + self.table.c.cpu_fault + self.table.c.io_fault + self.table.c.iops_fault) elif item == 'ID': fun_ = self._fun_user_id() elif item == 'From': fun_ = self._fun_time_from() elif item == 'To': fun_ = self._fun_time_to() else: column_name = FIELD_TO_TABLE_COLUMN[item] table_column = getattr(self.table.c, column_name) if item in FIELD_AVERAGE: fun_ = self._fun_avg(table_column) elif item in FIELD_LIMIT: fun_ = self._fun_limit(table_column) elif item in FIELD_FAULT: fun_ = self._fun_fault(table_column) elif item in FIELD_USAGE: column_name_limit = FIELD_TO_TABLE_COLUMN[FIELD_LIMIT[FIELD_USAGE.index(item)]] table_column_limit = getattr(self.table.c, column_name_limit) fun_ = self._fun_usage(table_column, table_column_limit) elif item in FIELD_MAX: column_name_limit = FIELD_TO_TABLE_COLUMN[FIELD_LIMIT[FIELD_MAX.index(item)]] column_name_fault = FIELD_TO_TABLE_COLUMN[FIELD_FAULT[FIELD_MAX.index(item)]] table_column_limit = getattr(self.table.c, column_name_limit) table_column_fault = getattr(self.table.c, column_name_fault) fun_ = self._fun_max(table_column, table_column_limit, table_column_fault) return fun_ def _group_by_query(self, select_query): by_usage_list = convert_to_list(self.by_usage) by_fault_list = convert_to_list(self.by_fault) if self._is_multi_uids: query = select_query.group_by(self.table.c.id) else: # it is correct because in query result all records having the same _fun_time_from() also have same _fun_time_to() # so .group_by(_fun_time_to()) doesn't create new groups after .group_by(_fun_time_from()) # but we need both this group conditions because mysql throws an error otherwise query = select_query.group_by(self._fun_time_from()).group_by(self._fun_time_to()) if self.order_by: order_by_aggregate_func = self._aggregate_fun_case(self.order_by) query.append_order_by(desc(order_by_aggregate_func)) else: if self._is_multi_uids: query.append_order_by(asc(self.table.c.id)) else: query.append_order_by(self._fun_time_from()) # add filter for having grouped data if by_usage_list: # add filtering by user id filter_fun_list = list() for item in by_usage_list: if item in FIELD_AVERAGE: index = FIELD_AVERAGE.index(item) filter_fun = self.by_usage_percentage <= self._aggregate_fun_case(FIELD_USAGE[index]) else: index = FIELD_MAX.index(item) filter_fun = self.by_usage_percentage*self._aggregate_fun_case(FIELD_LIMIT[index]) <= self._aggregate_fun_case(item) filter_fun_list.append(filter_fun) query.append_having(or_(*filter_fun_list)) if by_fault_list: by_fault_filter = [self.threshold <= self._aggregate_fun_case(funk_key_) for funk_key_ in by_fault_list] query.append_having(or_(*by_fault_filter)) if self.limit != 0 and self.limit is not None: query = query.limit(self.limit) return query def _columns_query(self): """ Generate output columns for SELECT <_columns_query(self)> FROM ... :return: """ columns_agregate_func = list() for column_key in self.show_columns: column_fun = self._aggregate_fun_case(column_key) if isinstance(column_fun, list): columns_agregate_func.extend(column_fun) else: if column_key not in ('From', 'To'): # digest not support label column_fun = label(column_key, column_fun) columns_agregate_func.append(column_fun) # add label return columns_agregate_func def _check_need_time_count(self): columns = {'aCPU', 'uCPU', 'aEP', 'uEP', 'aVMem', 'uVMem', 'aPMem', 'uPMem', 'aNproc', 'uNproc', 'aIO', 'uIO', 'aIOPS', 'uIOPS'} return bool(columns & (set(self.show_columns) | {self.order_by} | set(self.by_usage or set()))) def _check_need_join(self): return self._check_need_time_count() and not self._is_multi_uids def _select_query(self, columns_=None, need_join=False): """ :type need_join: bool """ if columns_ is None: columns_ = self._columns_query() if need_join: where_query = and_(self._where_time_period(table=self._table_alive), self._where_uid(uid=0, table=self._table_alive)) else: where_query = and_(self._where_time_period(), self._where_uid()) if self.server_id: # add filtering by server id where_query = and_(where_query, self._where_server_id()) query = select(columns_).where(where_query) if need_join: _table_joined = self._table_alive.outerjoin( self.table, and_(self._table_alive.c.created == self.table.c.created, self._where_uid(uid=self.uid))) query = query.select_from(_table_joined) return query def main_query(self): columns_ = self._columns_query() query = self._select_query(columns_=columns_, need_join=self._check_need_join()) query = self._group_by_query(query) return query def _min_max_created(self): """ SELECT min(created), max(created) FROM lve_stats2_history WHERE id = <ID> AND created BETWEN xxxx AND yyyy server_id = 'localhost' """ where_query = and_(self._where_time_period(), self._where_uid(), self._where_server_id()) query = select([func.min(self.table.c.created), func.max(self.table.c.created)]).where(where_query) time_start = time.time() q = str(query.compile(compile_kwargs={"literal_binds": True})).replace('\n', ' ') self.log.debug(q) data = self.dbengine.execute(query) self.log.debug('query time: %s', time.time() - time_start) return data.fetchall()[0] def proceed_dyn_time_unit(self): min_created, max_created = self._min_max_created() if max_created is None: # no data return self.result_corrector # we need manipulate with datetime data in local timezone period_from = gm_to_local(unixtimestamp_to_gm_datetime(min_created)) period_to = gm_to_local(unixtimestamp_to_gm_datetime(max_created)) time_unit_groups = dyn_time_unit_groups(period_from, period_to) rows = list() for _from, _to, _time_unit in reversed(time_unit_groups): # create instance copy for modify some attributes self_copy = copy.copy(self) self_copy.period_from = gm_datetime_to_unixtimestamp(local_to_gm(_from)) self_copy.period_to = gm_datetime_to_unixtimestamp(local_to_gm(_to)) - 1 self_copy.time_unit = _time_unit self_copy.limit = 0 rows.extend(self_copy.proceed()) return rows def proceed(self): # check and return some data without run sql query if self.uid == tuple() or self.uid == list(): return list() if self.uid is not None and not isinstance(self.uid, (list, tuple)) and self.uid <= 0: return list() query = self.main_query() time_start = time.time() q = str(query.compile(compile_kwargs={"literal_binds": True})) self.log.debug(q.replace('\n', ' ')) conn = self.dbengine.connect() try: cursor = conn.execute(query) self.log.debug('query time: %s', time.time() - time_start) self.result_corrector.rows = cursor.fetchall() except LveStatsPluginTerminated: conn.close() raise LveStatsPluginTerminated() else: conn.close() return self.result_corrector def proceed_dict(self): return [{item: val for item, val in zip(self.show_columns, items_val)} for items_val in self.proceed()] class _HistoryShowX1(HistoryShow): def __init__(self, *args, **kwargs): HistoryShow.__init__(self, *args, **kwargs) if 'ID' not in self.show_columns: self.show_columns = ['ID'] + self.show_columns self._labels = list() # variable for control duplicated labels def _aggregate_fun_case(self, item): """ :type item: str """ if item == 'anyF': fun_ = [ self.table.c.mem_fault, self.table.c.memphy_fault, self.table.c.mep_fault, self.table.c.nproc_fault, self.table.c.cpu_fault, self.table.c.io_fault, self.table.c.iops_fault] elif item == 'ID': fun_ = label('id', self.table.c.id) elif item == 'From': fun_ = self.period_from elif item == 'To': fun_ = self.period_to else: column_name = FIELD_TO_TABLE_COLUMN[item] table_column = getattr(self.table.c, column_name) if item in (FIELD_AVERAGE + FIELD_LIMIT + FIELD_FAULT): fun_ = label(column_name, table_column) elif item in FIELD_USAGE: column_name_limit = FIELD_TO_TABLE_COLUMN[FIELD_LIMIT[FIELD_USAGE.index(item)]] table_column_limit = label(column_name_limit, getattr(self.table.c, column_name_limit)) fun_ = [table_column, table_column_limit] elif item in FIELD_MAX: column_name_limit = FIELD_TO_TABLE_COLUMN[FIELD_LIMIT[FIELD_MAX.index(item)]] column_name_fault = FIELD_TO_TABLE_COLUMN[FIELD_FAULT[FIELD_MAX.index(item)]] table_column_fault = label(column_name_fault, getattr(self.table.c, column_name_fault)) table_column_limit = label(column_name_limit, getattr(self.table.c, column_name_limit)) table_column_max = label(column_name + '_max', table_column) fun_ = [table_column, table_column_limit, table_column_fault, table_column_max] return fun_ def _columns_query(self): columns_agregate_func = list() show_columns = self.show_columns + (self.by_fault or list()) if self.by_usage: for item in convert_to_list(self.by_usage): if item in FIELD_AVERAGE: index = FIELD_AVERAGE.index(item) show_columns.append(FIELD_USAGE[index]) else: index = FIELD_MAX.index(item) show_columns.extend([FIELD_FAULT[index], item]) if self.order_by: show_columns.append(self.order_by) for column_key in show_columns: column_fun = self._aggregate_fun_case(column_key) if isinstance(column_fun, list): for fun_ in column_fun: if hasattr(fun_, 'name') and fun_.name not in self._labels: # prevent alias duplication columns_agregate_func.append(fun_) self._labels.append(fun_.name) else: if hasattr(column_fun, 'name') and column_fun.name not in self._labels: # prevent alias duplication columns_agregate_func.append(column_fun) self._labels.append(column_fun.name) return columns_agregate_func class _HistoryShowX60(_HistoryShowX1): AGGREGATE_PERIOD = 60*60 def __init__(self, *args, **kwargs): _HistoryShowX1.__init__(self, *args, table=history_x60.__table__, **kwargs) # correct and rewrite time count and period self.period_from, self.period_to = self.get_history_x60_from_to() self.time_count = kwargs.get('time_count') or self.get_time_count() def get_time_count(self): if (self.period_from, self.period_to) == (None, None): return 0 return _HistoryShowX1.get_time_count(self) def get_history_x60_from_to(self): """ calculate present in aggregate table from and to time """ if self.period_to - self.period_from <= self.AGGREGATE_PERIOD: return None, None between_query = self.table.c.created.between(self.period_from + self.AGGREGATE_PERIOD, self.period_to) query = select( [func.min(self.table.c.created), func.max(self.table.c.created)]).where( and_(between_query, self._where_server_id())) time_start = time.time() self.log.debug(str(query.compile(compile_kwargs={"literal_binds": True})).replace('\n', ' ')) result = self.dbengine.execute(query).fetchall()[0] self.log.debug('query time: %s', time.time() - time_start) create_min, create_max = result if create_max is not None: return create_min - self.AGGREGATE_PERIOD + 1, create_max # "+1" for exclude from timestamp else: return result # rewrite average function generating def _aggregate_fun_case(self, item): """ :type item: str """ if item in FIELD_AVERAGE: column_name = FIELD_TO_TABLE_COLUMN[item] table_column = getattr(self.table.c, column_name) return label(column_name, table_column*self.table.c.time) else: return _HistoryShowX1._aggregate_fun_case(self, item) class HistoryShowUnion(HistoryShow): """ Class for retrieve statistics data using two tables """ def __init__(self, *args, **kwargs): HistoryShow.__init__(self, *args, **kwargs) self._alias = LVE_STATS_2_TABLENAME_PREFIX + 'union' kwargs.update(dict(time_count=self.time_count)) self.x60 = _HistoryShowX60(*args, **kwargs) self.x1 = _HistoryShowX1(*args, **kwargs) self._need_union = self.x60.period_to is not None and self._is_multi_uids # detect need union tables if self._need_union: self.table = self._select_union_query() # rewrite '_aggregate_fun_case' for correct calculate maximum def _aggregate_fun_case(self, item): """ :type item: str """ if self._need_union and item in FIELD_MAX: column_name = FIELD_TO_TABLE_COLUMN[item] column_name_limit = FIELD_TO_TABLE_COLUMN[FIELD_LIMIT[FIELD_MAX.index(item)]] column_name_fault = FIELD_TO_TABLE_COLUMN[FIELD_FAULT[FIELD_MAX.index(item)]] column_limit = getattr(self.table.c, column_name_limit) column_fault = getattr(self.table.c, column_name_fault) column_max = getattr(self.table.c, column_name + '_max') fun_ = self._fun_max(column_max, column_limit, column_fault) return fun_ else: return HistoryShow._aggregate_fun_case(self, item) def _select_union_query(self): """ union two tables """ with warnings.catch_warnings(): warnings.simplefilter("ignore", category=sa_exc.SAWarning) union_query = self.x1._select_query().where(not_(self.x1.table.c.created.between(self.x60.period_from, self.x60.period_to))) union_query = union_query.union_all(self.x60._select_query()) union_query = alias(union_query, self._alias) return union_query def _select_query(self, columns_=None, need_join=None): if self._need_union: return select(columns_) else: return HistoryShow._select_query(self, columns_=columns_, need_join=need_join) def get_supported_columns(lve_version=None, mode=None): """ preparation list columns depending of the lve version :type mode: Union[None, str] :type lve_version: Union[None, int] """ columns = list() if mode == 'v1': columns = [ 'aCPU', 'mCPU', 'lCPU', 'aEP', 'mEP', 'lEP', 'aVMem', 'mVMem', 'lVMem', 'VMemF', 'EPf', 'aPMem', 'mPMem', 'lPMem', 'aNproc', 'mNproc', 'lNproc', 'PMemF', 'NprocF', 'aIO', 'mIO', 'lIO'] if lve_version is None or lve_version > 6: columns.extend(['aIOPS', 'mIOPS', 'lIOPS']) elif mode == 'v2': columns = [ 'aCPU', 'lCPU', 'CPUf', 'aEP', 'lEP', 'EPf', 'aVMem', 'lVMem', 'VMemF', 'aPMem', 'lPMem', 'PMemF', 'aNproc', 'lNproc', 'NprocF', 'aIO', 'lIO', 'IOf'] if lve_version is None or lve_version > 6: columns.extend(['aIOPS', 'lIOPS', 'IOPSf']) elif mode is None: # show all columns, v1 and v2 columns = [ 'aCPU', 'uCPU', 'mCPU', 'lCPU', 'CPUf', 'aEP', 'uEP', 'mEP', 'lEP', 'EPf', 'aVMem', 'uVMem', 'mVMem', 'lVMem', 'VMemF', 'aPMem', 'uPMem', 'mPMem', 'lPMem', 'PMemF', 'aNproc', 'uNproc', 'mNproc', 'lNproc', 'NprocF', 'aIO', 'uIO', 'mIO', 'lIO', 'IOf'] if lve_version is None or lve_version > 6: columns.extend(['aIOPS', 'mIOPS', 'uIOPS', 'lIOPS', 'IOPSf']) return columns