����JFIF��x�x����'
| Server IP : 78.140.185.180 / Your IP : 216.73.216.28 Web Server : LiteSpeed System : Linux cpanel13.v.fozzy.com 4.18.0-513.11.1.lve.el8.x86_64 #1 SMP Thu Jan 18 16:21:02 UTC 2024 x86_64 User : builderbox ( 1072) PHP Version : 7.3.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /proc/3357889/root/opt/cloudlinux/venv/lib/python3.11/site-packages/cl_plus/collectors/ |
Upload File : |
# coding=utf-8
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2020 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT
#
import time
import warnings
import subprocess
from typing import Optional, Tuple
try:
import pymysql
except ImportError:
pymysql = None
from clcommon.cpapi import db_access
from clcommon.cpapi.cpapiexceptions import NoDBAccessData, NotSupported
from clcommon import mysql_lib
from .collector_base import CollectorBase
from cl_plus.daemon.daemon_control import _SYSTEMCTL_BIN
# enough to collect only several times per minute, cause we rely on DB counter
MYSQL_COLLECTION_INTERVAL = 30
def _total_queries_num_request(db, query_str):
"""
Execs passed query and returns value
"""
with warnings.catch_warnings():
warnings.filterwarnings('ignore', category=pymysql.Warning)
data = db.execute_query(query_str)
return data[0][1]
class MySQLCollector(CollectorBase):
class DBAccess:
def __init__(self, login: Optional[str] = None, password: Optional[str] = None):
self.login = login
self.password = password
self.expire_time = time.time() + 3600
def __eq__(self, other):
return self.login == other.login and self.password == other.password
def __bool__(self):
return bool(self.login and self.password and not self.is_expired())
def is_expired(self) -> bool:
return time.time() > self.expire_time
def __init__(self, _logger):
super(MySQLCollector, self).__init__(_logger)
self._is_mysql_error = False
self.collection_interval = MYSQL_COLLECTION_INTERVAL
self.access = self.DBAccess()
self.previous_num, self.current_num = None, None
self._aggregation_times = 0
def init(self):
"""
Initialize MySQL collector
:return: None
"""
self._aggregated_data = 0
self._logger.info("MySQL collector init")
self._logger.info('MySQL collector initial values: previous: %s, current: %s',
str(self.previous_num),
str(self.current_num))
def _get_total_queries_num(self, dblogin: str, dbpass: str):
"""
Gets total queries number via Queries variable in DB
it is a total counter of queries made to DB
it is used be such monitoring system as Nagios and mysqladmin returns it
https://github.com/nagios-plugins/nagios-plugins/blob/master/plugins/check_mysql.c
"""
if not pymysql:
return "No MySQL client libraries installed", 0
max_retries = 3
retry_delay = 5 # seconds
for attempt in range(max_retries):
try:
connector = mysql_lib.MySQLConnector(host='localhost',
user=dblogin,
passwd=dbpass,
db='mysql',
use_unicode=True,
charset='utf8')
with connector.connect() as db:
total = _total_queries_num_request(db, 'show global status where Variable_name = \'Questions\';')
self._aggregation_times += 1
# Log success if we had previous errors
if attempt > 0:
self._logger.info("MySQL collector: connection succeeded on attempt %d", attempt + 1)
return 'OK', int(total)
except Exception as e:
# Check if it's a connection error that we should retry
is_connection_error = (hasattr(e, 'args') and len(e.args) > 0 and
isinstance(e.args[0], int) and e.args[0] in (2002, 2003))
if attempt < max_retries - 1 and is_connection_error:
self._logger.info("MySQL collector: connection attempt %d/%d failed, retrying in %d seconds: %s",
attempt + 1, max_retries, retry_delay, str(e))
time.sleep(retry_delay)
continue
else:
# Last attempt failed - try to restart cl_plus_sender.service
if is_connection_error:
self._logger.warning("MySQL collector: all %d connection attempts failed, attempting to restart cl_plus_sender.service", max_retries)
if self._restart_cl_plus_sender_service():
# Give service time to start
time.sleep(10)
# Try one more time after service restart
try:
connector = mysql_lib.MySQLConnector(host='localhost',
user=dblogin,
passwd=dbpass,
db='mysql',
use_unicode=True,
charset='utf8')
with connector.connect() as db:
total = _total_queries_num_request(db, 'show global status where Variable_name = \'Questions\';')
self._aggregation_times += 1
self._logger.info("MySQL collector: connection succeeded after cl_plus_sender.service restart")
return 'OK', int(total)
except Exception as retry_e:
self._logger.error("MySQL collector: connection failed even after cl_plus_sender.service restart: %s", str(retry_e))
return str(retry_e), 0
else:
self._logger.error("MySQL collector: failed to restart cl_plus_sender.service")
return str(e), 0
return "Max retries exceeded", 0
def _restart_cl_plus_sender_service(self):
"""
Restart cl_plus_sender.service using systemctl, but only if it's enabled and not active
:return: True if successful, False otherwise
"""
try:
# Check if service is enabled
check_enabled = subprocess.run([_SYSTEMCTL_BIN, 'is-enabled', 'cl_plus_sender.service'],
capture_output=True, text=True, timeout=10,
env={'PATH': '/usr/bin:/bin'}) # Restrict PATH
if check_enabled.returncode != 0 or check_enabled.stdout.strip() == 'disabled':
self._logger.info("MySQL collector: cl_plus_sender.service is disabled, skipping restart")
return False
# Service is enabled, proceed with restart
self._logger.info("MySQL collector: cl_plus_sender.service is enabled, attempting restart")
result = subprocess.run([_SYSTEMCTL_BIN, 'restart', 'cl_plus_sender.service'],
capture_output=True, text=True, timeout=30,
env={'PATH': '/usr/bin:/bin'}) # Restrict PATH
if result.returncode == 0:
self._logger.info("MySQL collector: successfully restarted cl_plus_sender.service")
return True
else:
self._logger.error("MySQL collector: failed to restart cl_plus_sender.service: %s", result.stderr)
return False
except subprocess.TimeoutExpired:
self._logger.error("MySQL collector: timeout while checking/restarting cl_plus_sender.service")
return False
except Exception as e:
self._logger.error("MySQL collector: error checking/restarting cl_plus_sender.service: %s", str(e))
return False
def _get_db_access(self) -> Tuple[str, str]:
"""
Get DB access data from cpapi function. Logs error
:return: tuple (db_root_login, db_password)
None, None if error
"""
if not self.access:
try:
access = db_access() # {'pass': str, 'login': str}
self.access = self.DBAccess(access['login'], access['pass'])
except (NoDBAccessData, NotSupported, KeyError, TypeError) as e:
# Can't retrieve data
if not self._is_mysql_error:
self._logger.warn("MySQL collector: can't obtain MySQL DB access: %s", str(e))
self._is_mysql_error = True
return self.access.login, self.access.password
def aggregate_new_data(self):
"""
Retrieve and aggregate new data
:return None
"""
self._get_db_access()
if not self.access: # access data still is not set
return
# New data present - aggregate
message, total_queries = self._get_total_queries_num(self.access.login, self.access.password)
if message == 'OK':
self.previous_num = self.current_num
self.current_num = total_queries
# no previous value to compare during 1st iteration after collector is started
if self.previous_num is not None and self.current_num is not None:
# if mysql counter was re-set
if self.current_num < self.previous_num:
self._logger.info('MySQL collector: QUERIES counter was re-set in database, '
're-set collectors previous counter as well')
self.previous_num = 0
self._aggregated_data += self.current_num - self.previous_num
self._is_mysql_error = False
return
# Retrieve data error
if not self._is_mysql_error:
self._logger.error("MySQL collector can't obtain MySQL queries number: %s", message)
self._is_mysql_error = True
def get_averages(self):
"""
Get collector's averages data
:return: dict:
{ "mysql_queries_num": 16}
or None if can't get data
"""
mysql_queries_num = max(0, self._aggregated_data - self._aggregation_times)
self._aggregated_data = 0
self._aggregation_times = 0
return {"mysql_queries_num": mysql_queries_num}