Failed to save the file to the "xx" directory.

Failed to save the file to the "ll" directory.

Failed to save the file to the "mm" directory.

Failed to save the file to the "wp" directory.

403WebShell
403Webshell
Server IP : 66.29.132.124  /  Your IP : 18.224.38.165
Web Server : LiteSpeed
System : Linux business141.web-hosting.com 4.18.0-553.lve.el8.x86_64 #1 SMP Mon May 27 15:27:34 UTC 2024 x86_64
User : wavevlvu ( 1524)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /opt/cloudlinux/venv/lib64/python3.11/site-packages/lvestats/lib/chart/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /opt/cloudlinux/venv/lib64/python3.11/site-packages/lvestats/lib/chart/__init__.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

import argparse
import logging
import datetime
import sys
import os
import subprocess
from bisect import bisect_left
from collections import defaultdict
from typing import Iterable, List

from lxml import etree
from sqlalchemy.exc import SQLAlchemyError

import lvestats
import lvestats.lib.commons.decorators
from clcommon.cpapi.pluginlib import getuser
from lvestats.lib.cloudlinux_statistics import _get_uid_for_select
from lvestats.lib import config, dbengine, lveinfolib, uidconverter
from lvestats.lib.chart.svggraph import SvgChart
from lvestats.lib.chart.rdp import ramerdouglas
from lvestats.lib.commons import dateutil
from lvestats.lib.commons.argparse_utils import period_type2, ParseDatetime
from lvestats.lib.commons.logsetup import setup_logging
from lvestats.lib.chart.polysimplify import VWSimplifier

__author__ = 'shaman'

DEFAULT_SERVER_ID = 'localhost'
IMAGEMAGICK_BINARY = "/opt/alt/alt-ImageMagick/usr/bin/magick"
if not os.path.exists(IMAGEMAGICK_BINARY):
    IMAGEMAGICK_BINARY = "/usr/bin/convert"

format_index = 1


class Renderer(object):
    """
    Renders data to file
    """

    def __init__(self, max_points_on_graph: int = 800, fault_color: str = 'aquamarine'):
        self.log = logging.getLogger('Chart Renderer')
        self.svg_chart = SvgChart()
        self._max_points_on_graph = max_points_on_graph
        self._fault_color = fault_color

    @staticmethod
    def _nop(arg):
        return arg

    @staticmethod
    def _ts_to_str(ts):
        # Example: [0]="5 Feb", [1]="Aug 15, 2:35pm"
        formats = ['%b-%d', '%b-%d %I:%M%p']
        gm = dateutil.unixtimestamp_to_gm_datetime(ts)
        lo = dateutil.gm_to_local(gm)
        return lo.strftime(formats[format_index])

    def set_period_sec(self, period_sec):
        global format_index
        # 8 days = 8 * 24 * 3600 = 691200 sec
        if period_sec >= 691200:
            format_index = 0

    def _optimisation_ramerdouglas(self, points, epsilon_max=2.0, epsilon_min=0):
        epsilon_default = epsilon_max
        points_output = points
        points_len = 0
        for i in range(10):
            points_optimised = ramerdouglas(points, epsilon_default)
            points_len_previous = points_len
            points_len = len(points_optimised)
            if points_len == points_len_previous:
                # return result if changing epsilon not change points number
                return points_output
            if points_len >= self._max_points_on_graph:
                if i == 0:
                    # return result if in first iteration points more than max
                    return points_optimised
                epsilon_min = epsilon_default
            else:
                points_output = points_optimised
                epsilon_max = epsilon_default
            # correcting epsilon
            epsilon_default = (epsilon_max + epsilon_min) / 2.
        return points_output

    def _optimisation_polysimplify(self, points: Iterable[Iterable[float]]) -> List[List[float]]:
        simplifier = VWSimplifier(points)
        result = simplifier.from_number(self._max_points_on_graph)
        return result

    def optimise_points(self, line: Iterable[Iterable[float]]) -> List[List[float]]:
        if len(line) > self._max_points_on_graph:
            line = self._optimisation_ramerdouglas(line, epsilon_max=1.4)
            line = self._optimisation_polysimplify(line)
        return line

    def get_two_closest(self, line, x_value):
        """
        :type x_value: float
        :type line: list
        """
        pos = bisect_left(line, x_value)
        before = line[pos - 1]
        after = line[pos]
        return pos - 1, before, pos, after

    def add_graph(
            self,
            data,
            title,
            legend,
            x_values,
            min_y=None,
            max_y=None,
            x_labels=None,
            generate_n_xlabels=None,
            y_labels=None,
            y_legend_converter=lambda v: v,
            unit=None,
            message=None,
            faults=None):
        """
        :type message: None|str
        :type unit: None|str
        :type y_legend_converter: (float) -> float
        :type max_y: None|float
        :type min_y: None|float
        :type title: str
        :type legend: dict
        :type x_values: list
        :type faults: (str, str, str)
        :type data: defaultdict
        """
        colors, datasets, fault_lines, names = self._add_graph(data, faults, legend, x_values)

        self.svg_chart.add_graph(
            datasets,
            colors,
            title=title,
            minimum_y=min_y,
            maximum_y=max_y,
            x_legend=x_labels,
            x_legend_generate=generate_n_xlabels,
            y_legend=y_labels,
            y_legend_converter=y_legend_converter,
            x_legend_converter=self._ts_to_str,
            names=names,
            unit=unit,
            message=message,
            fault_lines=fault_lines,
            fault_color=self._fault_color)

    def _add_graph(self, data, faults, legend, x_values):
        """
        :type legend: dict
        :type x_values: list
        :type faults: (str, str, str)
        :type data: defaultdict
        :rtype (list, list, list, list)
        """
        datasets = []
        names = []
        colors = []
        datasets_dictionary = {}
        for (key, metainfo) in legend.items():
            try:
                legend_title, color, modifier = metainfo
            except ValueError:
                legend_title, color = metainfo
                modifier = self._nop
            y_values = [modifier(y) for y in data[key.lower()]]
            # x_values = range(0, len(y_values))
            line = list(zip(x_values, y_values))
            line_optimised = self.optimise_points(line)
            datasets.append(line_optimised)
            datasets_dictionary[key.lower()] = line_optimised
            names.append(legend_title)
            colors.append(color)
        fault_lines = self.get_faults_lines(data, datasets_dictionary, faults, x_values)

        # add legend
        if fault_lines:
            names.append('faults')
            colors.append(self._fault_color)
        del datasets_dictionary
        return colors, datasets, fault_lines, names

    def get_faults_lines(self, data, datasets_dictionary, faults, x_values):
        """
        :type x_values: list
        :type faults: (str, str, str)
        :type datasets_dictionary: dict
        :type data: defaultdict
        :rtype list
        """
        fault_lines = []
        if faults is not None:
            fault_name, data_name, limit_name = [x.lower() for x in faults]
            if limit_name in datasets_dictionary and data_name in datasets_dictionary:
                faults_x = [x for x, y in zip(x_values, data[fault_name]) if y > 0]
                average_times, average = list(zip(*datasets_dictionary[data_name]))
                limit_times, limit = list(zip(*datasets_dictionary[limit_name]))
                for fault in faults_x:
                    try:
                        average_dot = self.get_dot(fault, average, average_times)
                        limit_dot = self.get_dot(fault, limit, limit_times)
                        if average_dot[1] < limit_dot[1] and (average_dot, limit_dot) not in fault_lines:
                            fault_lines.append((average_dot, limit_dot))
                    except IndexError:
                        self.log.error("Can't get fault line: %s", str(fault))
        return fault_lines

    def get_dot(self, fault_time, line, line_times):
        """
        :type line_times: list
        :type line: list
        :type fault_time: float
        :rtype (float, float)
        """
        if fault_time >= line_times[-1]:
            return fault_time, line[-1]
        if fault_time <= line_times[0]:
            return fault_time, line[0]
        try:
            dot = (fault_time, line[line_times.index(fault_time)])
        except ValueError:
            before_index, before, after_index, after = self.get_two_closest(line_times, fault_time)
            dot_y = (
                (fault_time - before)
                / (after - before)
                * (line[after_index] - line[before_index])
                + line[before_index]
            )
            dot = (fault_time, dot_y)
        return dot

    def add_common_x_legend(self, x_values, n):
        self.svg_chart._add_x_legend(  # pylint: disable=protected-access
            x_values=x_values,
            number=n,
            x_legend_converter=self._ts_to_str,
        )

    def add_text_box(self, text, font_size=None):
        """
        add empty rectangle with text
        """
        self.svg_chart.add_text_box(text, font_size)

    def render(self):
        return self.svg_chart.dump()


class UserNotFound(Exception):
    def __init__(self, user_name, *args, **kwargs):
        super().__init__(f'User {user_name} not found', *args, **kwargs)


class ChartMain(object):
    def __init__(self, prog_name, prog_desc, cnf):
        self.prog_name = prog_name
        self.prog_desc = prog_desc
        self.log = setup_logging(cnf, prog_name, console_level=logging.ERROR)
        self.cfg = cnf
        # analog of previous parameter but get from config file
        self.is_normalized_user_cpu = config.is_normalized_user_cpu()

    @staticmethod
    def convert_dbdata_to_dict(data, show_columns):
        data_collected = defaultdict(list)
        for row in data:
            row_dict = dict(zip(show_columns, row))
            for (k, v) in row_dict.items():
                data_collected[k].append(float(v or 0))
        return data_collected

    @staticmethod
    def convert_lvedata_to_dict(data):
        # key -- field name, like 'cpu', 'id', 'created', etc.. value -- list of values
        by_key = defaultdict(list)
        for (k, values) in data.items():
            by_key[k] = [float(v or 0) for v in values]
        return by_key

    def make_parser(self):
        current_server_id = self.cfg.get('server_id', DEFAULT_SERVER_ID)
        datetime_now = datetime.datetime.now()
        parser = argparse.ArgumentParser(prog=self.prog_name,
                                         add_help=True,
                                         description=self.prog_desc)

        parser.add_argument('--version', version=lvestats.__version__,
                            help='Version number',
                            dest='version',
                            action='version')
        parser.add_argument('--period',
                            help='Time period\n'
                                 'specify minutes with m,  h - hours, days with d, and values: today, yesterday\n'
                                 '5m - last 5 minutes, 4h - last four hours, 2d - last 2 days, as well as today',
                            type=lambda value: period_type2(value, datetime_now),
                            default=None
                            )

        parser.add_argument('--from',
                            help='Run report from date and time in YYYY-MM-DD HH:MM format\n'
                                 'if not present last 10 minutes are assumed',
                            action=ParseDatetime,
                            nargs='+',
                            dest='ffrom')
        parser.add_argument('--to',
                            help='Run report up to date and time in YYYY-MM-DD HH:MM format\n'
                                 'if not present, reports results up to now',
                            action=ParseDatetime,
                            nargs='+',
                            dest='to')

        parser.add_argument('--server',
                            help='With username or LVE id show only record for that user at given server',
                            dest='server',
                            default=current_server_id)
        parser.add_argument('--output',
                            help='Filename to save chart as, if not present, output will be sent to STDOUT',
                            default='-', dest='output')
        parser.add_argument('--show-all',
                            help='Show all graphs (by default shows graphs for which limits are set)',
                            dest='show_all',
                            action='store_true',
                            default=False)
        parser.add_argument('--style',
                            help='Set graph style',
                            choices=['admin', 'user'],
                            dest='style',
                            default=None)
        parser.add_argument('--format',
                            help='Set graph output format',
                            choices=['svg', 'png'],
                            dest='format',
                            default='png')
        parser.add_argument('--for-reseller',
                            help='Show graph for specific reseller and user (used only with --id key)',
                            type=str,
                            dest='for_reseller',
                            default=None)

        # add opt --dpi, --width, --height for backward compatible with old lvechart
        parser.add_argument('--dpi', help=argparse.SUPPRESS)
        parser.add_argument('--width', help=argparse.SUPPRESS)
        parser.add_argument('--height', help=argparse.SUPPRESS)

        return parser

    def customize_parser(self, parser):
        """
        :type parser: argparse.ArgumentParser
        :rtype : argparse.ArgumentParser
        """
        raise NotImplementedError()

    def get_chart_data(self,
                       engine,
                       from_ts,
                       to_ts,
                       server,
                       user_id,
                       show_all=False):
        """

        Extracts data from database , in form of a tuple:
        (dict, list)
        where dict is

        {
            'cpu': [0, 5, 10 , 95.2, ...] -- values
            'io': [0, 5, 10 , 95.2, ...] -- values
            'foo': ,
        }

        and a list of timestamps for all that collected values:
        2) [1, 2 ,3 ...]



        :param show_all:
        :rtype : tuple
        :type engine: sqlalchemy.engine
        :type from_ts: datetime.datetime
        :type to_ts: datetime.datetime
        :type server: str
        :type user_id: int
        """
        raise NotImplementedError()

    def add_graphs(self, renderer, data_collected, times, lve_version, show_all, is_user=False):
        """
        :rtype : None
        :type renderer: Renderer
        :type data_collected: dict
        :type times: list
        :type lve_version: str
        :type show_all: bool
        :type is_user: bool. True for user, False - admin
        """
        raise NotImplementedError()

    @lvestats.lib.commons.decorators.no_sigpipe
    def main(self, args, debug_engine=None):
        parser = self.make_parser()
        parser = self.customize_parser(parser)
        if debug_engine is not None:
            engine = debug_engine
        else:
            try:
                engine = dbengine.make_db_engine(self.cfg)
            except dbengine.MakeDbException as e:
                sys.stderr.write(str(e) + '\n')
                return 1

        opts = parser.parse_args(list(args))

        if opts.period and any((opts.ffrom, opts.to)):
            print("--period and [--from, --to] are mutually exclusive")
            return 0

        if opts.period:
            f, t = opts.period
        else:
            datetime_now = datetime.datetime.now()
            f = opts.ffrom or datetime_now - datetime.timedelta(minutes=10)
            t = opts.to or datetime_now

        user_id = self._obtain_user_id(engine, opts) if self._has_user_arguments(opts) else os.getuid()
        if getuser() != 'root':
            if user_id and user_id != os.getuid():
                sys.stderr.write('Permission denied\n')
                return 1

        # LVESTATS-97
        # If both params (for_reseller and id or username) are used
        # and user_id not in resellers_users, we return `Permission denied` message
        if opts.for_reseller:
            reseller_name = opts.for_reseller
            reseller_users = _get_uid_for_select(reseller_name, user_id)
            # if reseller_users is just int, than everything is ok
            if isinstance(reseller_users, int):
                pass
            # if not user's id is in reseller's ids
            # neither user is reseller and user's id is reseller's id itself
            elif user_id not in reseller_users:
                error_msg = (
                    f'Permission denied. User with id {user_id} does not belong reseller `{reseller_name}`\n'
                )
                sys.stderr.write(error_msg)
                return 1
        try:
            data_collected, times, period_sec = self.get_chart_data(engine,
                                                                    f,
                                                                    t,
                                                                    opts.server,
                                                                    user_id=user_id)
        except (UserNotFound, SQLAlchemyError) as ex:
            sys.stderr.write(str(ex) + '\n')
            return 1

        rendered_graph = self._render(data_collected, engine, opts, period_sec, times)
        self._output(opts, rendered_graph)
        return 0

    def _render(self, data_collected, engine, opts, period_sec, times):
        show_all = opts.show_all
        try:
            style = opts.style
        except AttributeError:
            style = None
        if style is None:
            style = 'admin'
        renderer = Renderer()
        renderer.set_period_sec(period_sec)
        lve_version = lveinfolib.get_lve_version(engine, opts.server)
        self.add_graphs(renderer, data_collected, times, lve_version, show_all,
                        is_user=style == 'user')
        rendered_graph = renderer.render()
        return rendered_graph

    def _output(self, opts, rendered_graph):
        if opts.format == 'svg' and opts.output == '-':
            # output as SVG
            try:
                root_node = etree.fromstring(rendered_graph.encode('utf8'))
                rendered_graph = etree.tostring(root_node, pretty_print=True)
            except (ImportError, ValueError, UnicodeEncodeError) as e:
                self.log.debug('Can not use pretty print for svg xml formatting; %s', str(e))
        if opts.format == 'png':
            # output as PNG
            cwd = '/var/lve/tmp' if os.getuid() == 0 else None
            with subprocess.Popen(
                [IMAGEMAGICK_BINARY, "-", "png:-"],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                cwd=cwd
            ) as proc:
                rendered_graph, _ = proc.communicate(input=rendered_graph.encode())
        if opts.output == '-':
            if isinstance(rendered_graph, bytes):
                sys.stdout.buffer.flush()  # needed to write directly to buffer
                sys.stdout.buffer.write(rendered_graph)
            else:
                sys.stdout.write(rendered_graph)
        else:
            try:
                if isinstance(rendered_graph, bytes):
                    write_mode = 'wb'
                else:
                    write_mode = 'w'
                with open(opts.output, write_mode) as output:
                    output.write(rendered_graph)
            except IOError:
                self.log.error("Unable to create file: %s", opts.output)

    def _obtain_user_id(self, engine, opts):
        # obtain user id
        try:
            user_id = opts.user_id
        except AttributeError:
            user_id = None
        try:
            user_name = opts.user_name
        except AttributeError:
            user_name = None
        if user_id is None:
            user_id = uidconverter.username_to_uid(
                username=user_name, local_server_id=self.cfg['server_id'],
                server_id=opts.server, db_engine=engine) or -1
        return user_id

    def _has_user_arguments(self, opts):
        return (hasattr(opts, 'user_id') and opts.user_id) or (hasattr(opts, 'user_name') and opts.user_name)

Youez - 2016 - github.com/yon3zu
LinuXploit