PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /opt/sharedrads/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //opt/sharedrads/check_mysql |
#!/opt/imh-python/bin/python3 """MySQL RADS tool - T2S version. This script reads the dbindex file used by cPanel. If it cannot detect databases owners correctly, try running /usr/local/cpanel/bin/setupdbmap""" import argparse import configparser from operator import itemgetter import json import sys from collections import Counter import functools import subprocess from typing import Union import pymysql from pymysql.cursors import Cursor, DictCursor from pymysql.optionfile import Parser as PyMySQLParser import rads from rads.color import red connect = functools.partial( pymysql.connect, read_default_file='/root/.my.cnf', database='INFORMATION_SCHEMA', ) DBINDEX_PATH = '/var/cpanel/databases/dbindex.db.json' def get_dbnames(owners: set[str]) -> set[str]: """Return a dict of {database: owner}""" try: with open(DBINDEX_PATH, encoding='ascii') as handle: dbindex: dict = json.load(handle)['MYSQL'] except (ValueError, OSError, TypeError, KeyError): print( red(f"{DBINDEX_PATH} is unreadable. Try setupdbmap"), file=sys.stderr, ) sys.exit(2) return {k for k, v in dbindex.items() if v in owners} def kill(targets: set[str]): """Kill all MySQL queries for specific cPanel user(s)""" db_names = get_dbnames(targets) with connect() as conn: query_ids = get_query_ids(conn, db_names=db_names) kill_queries(conn, query_ids) def killdb(targets: set[str]): """Kill all MySQL queries for specific database(s)""" with connect() as conn: query_ids = get_query_ids(conn, db_names=targets) kill_queries(conn, query_ids) def killqueries(): """Kill ALL running queries (use with caution. This kills INSERTs!)""" if not rads.prompt_y_n( red( 'Use --killselects instead of --killqueries whenever possible!\n' '--killqueries kills INSERT and UPDATE queries and can cause ' 'data loss.\nProceed with --killqueries?' ) ): print('canceled.') sys.exit(0) with connect() as conn: kill_queries(conn, get_query_ids(conn)) def killselects(): """Kill all running SELECT queries (safer than killqueries, but use with caution)""" with connect() as conn: kill_queries(conn, get_query_ids(conn, selects_only=True)) def count_conns() -> int: num = 0 with subprocess.Popen( ['ss', '-Hxtn'], stdout=subprocess.PIPE, encoding='ascii' ) as proc: for line in proc.stdout: try: _, state, _, _, local, *_ = line.split() except ValueError: continue if state not in ('ESTAB', 'CONNECTED'): continue if not local.endswith(':3306') and 'mysql' not in local: continue num += 1 if rcode := proc.returncode: print(red(f'ss -Hxtn exited with error {rcode}'), file=sys.stderr) return num def get_max_connections() -> int: default = 100 try: parser = PyMySQLParser(strict=False) if not parser.read('/etc/my.cnf'): return default return int(parser.get('mysqld', 'max_connections')) except configparser.Error: return default def sockets() -> None: """Show number of open connections vs max connections""" # Use ss and my.cnf since this is usually used while MySQL is hurting conns = count_conns() max_conns = get_max_connections() print('MySQL Connections:', conns, '/', max_conns) def active() -> None: """Display users with the most currently running queries""" with connect() as conn, conn.cursor() as cur: cur: Cursor cur.execute('SELECT `USER` FROM `PROCESSLIST`') users = cur.fetchall() counts = Counter(map(itemgetter(0), users)) for user, count in sorted(counts.items(), key=itemgetter(1)): print(user, count) def parse_args() -> tuple[str, set[str]]: """Parse commandline arguments""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( 'targets', type=str, nargs='*', default=[], metavar='NAME', help='List of users or DBs used with --kill or --killdb', ) actions = parser.add_mutually_exclusive_group(required=True) for func in (kill, killdb, killqueries, killselects, sockets, active): actions.add_argument( f'--{func.__name__}', action='store_const', const=func.__name__, dest='func', help=func.__doc__, ) args = parser.parse_args() return args.func, set(args.targets) def kill_queries(conn: pymysql.Connection, id_list: list[int]) -> None: """Kill all queries provided in id_list""" print(len(id_list), 'queries to kill') if len(id_list) == 0: return with conn.cursor() as cur: cur: Cursor killed = errors = 0 for query_id in id_list: try: cur.execute('KILL %d' % query_id) cur.fetchall() killed += 1 except pymysql.Error: errors += 1 print(killed, 'successfully killed.', errors, 'failed.') def get_query_ids( conn: pymysql.Connection, db_names: Union[None, set[str]] = None, selects_only: bool = False, ) -> list[int]: """Get query IDs to kill""" with conn.cursor(DictCursor) as cur: cur.execute('SELECT `ID`,`USER`,`DB`,`INFO` from `PROCESSLIST`') procs = cur.fetchall() found_ids = [] skipped = 0 for proc in procs: if db_names and proc['DB'] not in db_names: continue # we are not looking for this database if proc['USER'] in rads.SYS_MYSQL_USERS: skipped += 1 # restricted user continue if selects_only: # we only want SELECTS query = str(proc['INFO']).upper() if proc['INFO'] else '' if not query.lstrip().startswith('SELECT'): continue # this is not a select found_ids.append(proc['ID']) if skipped: print(f'Warning: skipped {skipped} queries from system users') return found_ids def main(): """main: redirect to function based on parse_args result""" func, targets = parse_args() if func == 'kill': return kill(targets) if func == 'killdb': return killdb(targets) if func == 'killqueries': return killqueries() if func == 'killselects': return killselects() if func == 'sockets': return sockets() if func == 'active': return active() raise RuntimeError(f"{func=}") if __name__ == '__main__': try: main() except pymysql.Error as exc: print(red(str(exc)), file=sys.stderr) sys.exit(3)