From 84005398fbfce42d1853f9660eea819eff8d5565 Mon Sep 17 00:00:00 2001 From: Jim Riggs Date: Wed, 29 Apr 2020 10:34:03 -0500 Subject: [PATCH 1/3] Allow either MySQLdb or PyMySQL and add -s/--socket option - Try to import MySQLdb first and fall back to PyMySQL. - Add -s/--socket option to use UNIX sockets in addition to TCP. - No reason to call get_pa_cursor() with the same arguments every time. --- README.md | 36 ++++++++++++++++++------- proxytop | 78 ++++++++++++++++++++++++++----------------------------- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 6b76dec..6552f6f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,29 @@ Introduction ============ -Proxytop is a self contained real time monitoring tool for ProxySQL, a high performance, -high availability, protocol aware proxy for MySQL and forks (like Percona Server and MariaDB). +Proxytop is a self contained real time monitoring tool for ProxySQL, a high performance, +high availability, protocol aware proxy for MySQL and forks (like Percona Server and MariaDB). ### How to install: +Proxytop works with either MySQL-python (MySQLdb), which is attempted first, or PyMySQL. For +MySQLdb, you may first need to install system Python and MySQL dev packages, followed by +MySQL-python. E.g.: ``` -## You may first need to install system Python and MySQL dev packages -## e.g. "sudo apt install python-dev libmysqlclient-dev" -pip install MySQL-python npyscreen +sudo apt install python-dev libmysqlclient-dev +pip install MySQL-python +``` + +PyMySQL is simply: +``` +pip install PyMySQL +``` + +Finally, install npyscreen (used for the interface) and proxytop itself: +``` +pip install npyscreen wget -P /usr/bin https://raw.githubusercontent.com/sysown/proxytop/master/proxytop +# or curl -o /usr/bin/proxytop https://raw.githubusercontent.com/sysown/proxytop/master/proxytop ``` ### How to run: @@ -22,15 +35,18 @@ $ proxytop The script supports connecting to a non-default confication using command line parameters: ``` -usage: proxytop [-h] [-H HOST] [-P PORT] [-u USER] [-p PASSWORD] [-f DFILE] +usage: proxytop [-h] [-H HOST] [-P PORT] [-s SOCKET] [-u USER] [-p PASSWORD] + [-f DFILE] optional arguments: -h, --help show this help message and exit - -H HOST, --host HOST ProxySQL hostname / IP address (default=127.0.0.1) - -P PORT, --port PORT ProxySQL Admin port (default=6032) - -u USER, --user USER ProxySQL Admin username (default=admin) + -H HOST, --host HOST ProxySQL hostname / IP address + -P PORT, --port PORT ProxySQL Admin port + -s SOCKET, --socket SOCKET + ProxySQL Admin socket + -u USER, --user USER ProxySQL Admin username -p PASSWORD, --password PASSWORD - ProxySQL Admin password (default=admin) + ProxySQL Admin password -f DFILE, --dfile DFILE Use the MySQL defaults file specified (expects a [proxysqladmin] group) diff --git a/proxytop b/proxytop index 4c9ccfb..1aad5a5 100755 --- a/proxytop +++ b/proxytop @@ -3,7 +3,12 @@ import curses import argparse import npyscreen -import MySQLdb + +try: + import MySQLdb as mysql +except ImportError: + import pymysql as mysql + import sys import time @@ -15,41 +20,48 @@ def get_params(): default='127.0.0.1',type=str) parser.add_argument('-P','--port', help='ProxySQL Admin port', default=6032,type=int) + parser.add_argument('-s','--socket', help='ProxySQL Admin socket', + default='',type=str) parser.add_argument('-u','--user', help='ProxySQL Admin username', default='admin',type=str) parser.add_argument('-p','--password', help='ProxySQL Admin password', default='admin',type=str) parser.add_argument('-f','--dfile', help='Use the MySQL defaults file specified (expects a [proxysqladmin] group)', default=None, type=str) - return parser.parse_args() + args = parser.parse_args() + + if args.socket and args.host != '127.0.0.1': + parser.error('only one of -H/--host or -s/--socket should be specified') + + return args ### ProxySQL Admin Access Class class ProxysqlStats(object): def __init__(self, cs): self.cs = cs - self.cursor, self.conn, self.error = self.get_pa_cursor(host=cs.host, port=cs.port, - user=cs.user, password=cs.password, - dfile=cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() - def get_pa_cursor(self, host='127.0.0.1', port=6032, user='admin', password='admin', dfile=None, timeout=60): + def get_pa_cursor(self): "Connects to ProxySQL's Admin module and returns a DictCursor" conn = None cursor = None error = None try: - if dfile is None: - conn = MySQLdb.connect(host=host, - port=port, - user=user, - passwd=password, - connect_timeout=timeout) + if self.cs.dfile is None: + conn = mysql.connect(host=self.cs.host, + port=self.cs.port, + unix_socket=self.cs.socket, + user=self.cs.user, + passwd=self.cs.password, + connect_timeout=60) else: - conn = MySQLdb.connect(host=host, - port=port, - read_default_file=dfile, - read_default_group='proxysqladmin', - connect_timeout=timeout) + conn = mysql.connect(host=self.cs.host, + port=self.cs.port, + unix_socket=self.cs.socket, + read_default_file=self.cs.dfile, + read_default_group='proxysqladmin', + connect_timeout=60) conn.autocommit(True) cursor = conn.cursor() except Exception as e: @@ -83,9 +95,7 @@ class ProxysqlStats(object): else: raise Exception('Hmm... connection seems to have been lost') except Exception as e: - self.cursor, self.conn, self.error = self.get_pa_cursor(host=self.cs.host, port=self.cs.port, - user=self.cs.user, password=self.cs.password, - dfile=self.cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return '- N/A' @@ -112,9 +122,7 @@ class ProxysqlStats(object): else: raise Exception('Hmm... connection seems to have been lost') except Exception as e: - self.cursor, self.conn, self.error = self.get_pa_cursor(host=self.cs.host, port=self.cs.port, - user=self.cs.user, password=self.cs.password, - dfile=self.cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return ['','{}: {}'.format('CRITICAL ERROR', self.error)] @@ -166,9 +174,7 @@ class ProxysqlStats(object): else: raise Exception('Hmm... connection seems to have been lost') except Exception as e: - self.cursor, self.conn, self.error = self.get_pa_cursor(host=self.cs.host, port=self.cs.port, - user=self.cs.user, password=self.cs.password, - dfile=self.cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return ['','{}: {}'.format('CRITICAL ERROR', self.error)] @@ -193,9 +199,7 @@ class ProxysqlStats(object): else: raise Exception('Hmm... connection seems to have been lost') except Exception as e: - self.cursor, self.conn, self.error = self.get_pa_cursor(host=self.cs.host, port=self.cs.port, - user=self.cs.user, password=self.cs.password, - dfile=self.cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return ['','{}: {}'.format('CRITICAL ERROR', self.error)] @@ -224,9 +228,7 @@ class ProxysqlStats(object): else: raise Exception('Hmm... connection seems to have been lost') except Exception as e: - self.cursor, self.conn, self.error = self.get_pa_cursor(host=self.cs.host, port=self.cs.port, - user=self.cs.user, password=self.cs.password, - dfile=self.cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return ['','{}: {}'.format('CRITICAL ERROR', self.error)] @@ -247,9 +249,7 @@ class ProxysqlStats(object): else: raise Exception('Hmm... connection seems to have been lost') except Exception as e: - self.cursor, self.conn, self.error = self.get_pa_cursor(host=self.cs.host, port=self.cs.port, - user=self.cs.user, password=self.cs.password, - dfile=self.cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return ['','{}: {}'.format('CRITICAL ERROR', self.error)] @@ -267,9 +267,7 @@ class ProxysqlStats(object): else: raise Exception('Hmm... connection seems to have been lost') except Exception as e: - self.cursor, self.conn, self.error = self.get_pa_cursor(host=self.cs.host, port=self.cs.port, - user=self.cs.user, password=self.cs.password, - dfile=self.cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return '- N/A' @@ -293,9 +291,7 @@ class ProxysqlStats(object): else: raise Exception('Hmm... connection seems to have been lost') except Exception as e: - self.cursor, self.conn, self.error = self.get_pa_cursor(host=self.cs.host, port=self.cs.port, - user=self.cs.user, password=self.cs.password, - dfile=self.cs.dfile) + self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return '- N/A' From 7fb1452b2f6cea9ccf3e27ce45e99c3a0c0b2c0a Mon Sep 17 00:00:00 2001 From: Jim Riggs Date: Wed, 29 Apr 2020 10:35:47 -0500 Subject: [PATCH 2/3] Move get_params() call from onStart() to __init__() so that argparse can output help and errors --- proxytop | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/proxytop b/proxytop index 1aad5a5..2cc4982 100755 --- a/proxytop +++ b/proxytop @@ -521,10 +521,13 @@ class ExitButton(npyscreen.ButtonPress): class MainApp(npyscreen.NPSAppManaged): keypress_timeout_default = 10 - def onStart(self): + def __init__(self): + super(MainApp, self).__init__() DB_CS = get_params() - title_bar = 'ProxyTop - Realtime ProxySQL Statistics' self.pstats = ProxysqlStats(DB_CS) + + def onStart(self): + title_bar = 'ProxyTop - Realtime ProxySQL Statistics' self.addForm("MAIN", ConnPoolForm, name=title_bar) self.addForm("QSTAT", QueryRulesForm, name=title_bar) self.addForm("GLOB", GlobForm, name=title_bar) From e5b7da3eccdcf9a67e85400d06e0378a07d4d728 Mon Sep 17 00:00:00 2001 From: Jim Riggs Date: Wed, 29 Apr 2020 10:56:50 -0500 Subject: [PATCH 3/3] Use autopep8 and make flake8 happy --- proxytop | 437 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 294 insertions(+), 143 deletions(-) diff --git a/proxytop b/proxytop index 2cc4982..2717aeb 100755 --- a/proxytop +++ b/proxytop @@ -1,6 +1,5 @@ #!/usr/bin/env python -import curses import argparse import npyscreen @@ -12,30 +11,35 @@ except ImportError: import sys import time -### Option parser +# Option parser + def get_params(): parser = argparse.ArgumentParser() - parser.add_argument('-H','--host', help='ProxySQL hostname / IP address', - default='127.0.0.1',type=str) - parser.add_argument('-P','--port', help='ProxySQL Admin port', - default=6032,type=int) - parser.add_argument('-s','--socket', help='ProxySQL Admin socket', - default='',type=str) - parser.add_argument('-u','--user', help='ProxySQL Admin username', - default='admin',type=str) - parser.add_argument('-p','--password', help='ProxySQL Admin password', - default='admin',type=str) - parser.add_argument('-f','--dfile', help='Use the MySQL defaults file specified (expects a [proxysqladmin] group)', + parser.add_argument('-H', '--host', help='ProxySQL hostname / IP address', + default='127.0.0.1', type=str) + parser.add_argument('-P', '--port', help='ProxySQL Admin port', + default=6032, type=int) + parser.add_argument('-s', '--socket', help='ProxySQL Admin socket', + default='', type=str) + parser.add_argument('-u', '--user', help='ProxySQL Admin username', + default='admin', type=str) + parser.add_argument('-p', '--password', help='ProxySQL Admin password', + default='admin', type=str) + parser.add_argument('-f', '--dfile', + help=('Use the MySQL defaults file specified (expects ' + 'a [proxysqladmin] group)'), default=None, type=str) args = parser.parse_args() - if args.socket and args.host != '127.0.0.1': - parser.error('only one of -H/--host or -s/--socket should be specified') + if args.socket and args.host != '127.0.0.1': + parser.error( + 'only one of -H/--host or -s/--socket should be specified') return args -### ProxySQL Admin Access Class +# ProxySQL Admin Access Class + class ProxysqlStats(object): def __init__(self, cs): @@ -83,190 +87,249 @@ class ProxysqlStats(object): "Generates the connection pool header" try: if self.error is None: - self.cursor.execute('SELECT * FROM stats.stats_mysql_global ' \ - 'WHERE variable_name IN ' \ - '("ProxySQL_Uptime","Questions","MySQL_Thread_Workers","Slow_queries")') + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_global ' + 'WHERE variable_name IN ' + '("ProxySQL_Uptime","Questions","MySQL_Thread_Workers",' + '"Slow_queries")') data = self.cursor.fetchall() - title = self.cursor.description fmt_data = '-' for rvalue in data: - fmt_data = ' '.join([fmt_data,'{}: {}'.format(rvalue[0], rvalue[1])]) + fmt_data = ' '.join( + [fmt_data, '{}: {}'.format(rvalue[0], rvalue[1])]) return fmt_data else: raise Exception('Hmm... connection seems to have been lost') - except Exception as e: + except Exception: self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return '- N/A' - def get_cpstats(self, sort = 'ConnOK DESC'): + def get_cpstats(self, sort='ConnOK DESC'): "Generates the connection pool stats" try: if self.error is None: - self.cursor.execute('SELECT * FROM stats.stats_mysql_connection_pool ORDER BY {}'.format(sort)) + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_connection_pool ' + 'ORDER BY {}'.format(sort)) data = self.cursor.fetchall() title = self.cursor.description col_sizes = self.get_col_sizes(title, data) list_data = [''] for row in data: - str_row = '{:<{width}}'.format('{}:{}'.format(row[1],row[2]),width=col_sizes[1]+col_sizes[2]+1) - str_row = '{} hg-{:<{width}}'.format(str_row, row[0],width=col_sizes[0]) - str_row = '{} Conns(U/F/O/E) {:>{width}} / '.format(str_row, row[4],width=col_sizes[4]) - str_row = '{}{:>{width}} / '.format(str_row, row[5],width=col_sizes[5]) - str_row = '{}{:>{width}} / '.format(str_row, row[6],width=col_sizes[6]) - str_row = '{}{:>{width}}'.format(str_row, row[7],width=col_sizes[7]) - str_row = '{} {:>{width}}'.format(str_row, row[3],width=col_sizes[3]) - str_row = '{} LT(us) {:>{width}}'.format(str_row, row[11],width=col_sizes[11]) + str_row = '{:<{width}}'.format('{}:{}'.format( + row[1], row[2]), width=col_sizes[1] + col_sizes[2] + 1) + str_row = '{} hg-{:<{width}}'.format( + str_row, row[0], width=col_sizes[0]) + str_row = '{} Conns(U/F/O/E) {:>{width}} / '.format( + str_row, row[4], width=col_sizes[4]) + str_row = '{}{:>{width}} / '.format( + str_row, row[5], width=col_sizes[5]) + str_row = '{}{:>{width}} / '.format( + str_row, row[6], width=col_sizes[6]) + str_row = '{}{:>{width}}'.format( + str_row, row[7], width=col_sizes[7]) + str_row = '{} {:>{width}}'.format( + str_row, row[3], width=col_sizes[3]) + str_row = '{} LT(us) {:>{width}}'.format( + str_row, row[11], width=col_sizes[11]) list_data.append(str_row) return list_data else: raise Exception('Hmm... connection seems to have been lost') - except Exception as e: + except Exception: self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) - return ['','{}: {}'.format('CRITICAL ERROR', self.error)] + return ['', '{}: {}'.format('CRITICAL ERROR', self.error)] def get_globstats(self): "Generates the global stats" try: if self.error is None: - self.cursor.execute('SELECT * FROM stats.stats_mysql_global WHERE variable_name like ' \ - '"%_Connections_%" or variable_name like "Active_Transactions"') + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_global ' + 'WHERE variable_name LIKE "%_Connections_%" ' + 'OR variable_name LIKE "Active_Transactions"') data = self.cursor.fetchall() title = self.cursor.description col_sizes = self.get_col_sizes(title, data) - list_data = ['','***** Connection Information: *****'] + list_data = ['', '***** Connection Information: *****'] for row in data: - str_row = '{:<{width}}'.format(row[0],width=col_sizes[0]) - str_row = '{}: {:<{width}}'.format(str_row, row[1],width=col_sizes[1]) + str_row = '{:<{width}}'.format(row[0], width=col_sizes[0]) + str_row = '{}: {:<{width}}'.format( + str_row, row[1], width=col_sizes[1]) list_data.append(str_row) - self.cursor.execute('SELECT * FROM stats.stats_mysql_global WHERE variable_name like "Stmt_%"') + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_global ' + 'WHERE variable_name LIKE "Stmt_%"') data = self.cursor.fetchall() title = self.cursor.description col_sizes = self.get_col_sizes(title, data) list_data.append('') list_data.append('***** Statement Information: *****') for row in data: - str_row = '{:<{width}}'.format(row[0],width=col_sizes[0]) - str_row = '{}: {:<{width}}'.format(str_row, row[1],width=col_sizes[1]) + str_row = '{:<{width}}'.format(row[0], width=col_sizes[0]) + str_row = '{}: {:<{width}}'.format( + str_row, row[1], width=col_sizes[1]) list_data.append(str_row) - self.cursor.execute('SELECT * FROM stats.stats_mysql_global WHERE variable_name like "Com__%"') + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_global ' + 'WHERE variable_name LIKE "Com__%"') data = self.cursor.fetchall() title = self.cursor.description col_sizes = self.get_col_sizes(title, data) list_data.append('') list_data.append('***** Command Information: *****') for row in data: - str_row = '{:<{width}}'.format(row[0],width=col_sizes[0]) - str_row = '{}: {:<{width}}'.format(str_row, row[1],width=col_sizes[1]) + str_row = '{:<{width}}'.format(row[0], width=col_sizes[0]) + str_row = '{}: {:<{width}}'.format( + str_row, row[1], width=col_sizes[1]) list_data.append(str_row) - self.cursor.execute('SELECT * FROM stats.stats_mysql_global WHERE variable_name like "Query_Cache_%"') + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_global ' + 'WHERE variable_name LIKE "Query_Cache_%"') data = self.cursor.fetchall() title = self.cursor.description col_sizes = self.get_col_sizes(title, data) list_data.append('') list_data.append('***** Query Cache Information: *****') for row in data: - str_row = '{:<{width}}'.format(row[0],width=col_sizes[0]) - str_row = '{}: {:<{width}}'.format(str_row, row[1],width=col_sizes[1]) + str_row = '{:<{width}}'.format(row[0], width=col_sizes[0]) + str_row = '{}: {:<{width}}'.format( + str_row, row[1], width=col_sizes[1]) list_data.append(str_row) return list_data else: raise Exception('Hmm... connection seems to have been lost') - except Exception as e: + except Exception: self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) - return ['','{}: {}'.format('CRITICAL ERROR', self.error)] + return ['', '{}: {}'.format('CRITICAL ERROR', self.error)] def get_commands(self, sort='Command ASC'): "Generates the command counter stats" try: if self.error is None: - self.cursor.execute('SELECT * FROM stats_mysql_commands_counters WHERE Total_Time_us > 0;') + self.cursor.execute( + 'SELECT * ' + 'FROM stats_mysql_commands_counters ' + 'WHERE Total_Time_us > 0;') data = self.cursor.fetchall() list_data = [] title = self.cursor.description col_sizes = self.get_col_sizes(title, data) if len(data) > 0: for row in data: - str_row = '{:<{width}}'.format(row[0],width=col_sizes[0]) - str_row = '{} Time_us: {:<{width}}'.format(str_row, row[1],width=col_sizes[1]) - str_row = '{} Total_count: {:<{width}}'.format(str_row, row[2],width=col_sizes[2]) + str_row = '{:<{width}}'.format( + row[0], width=col_sizes[0]) + str_row = '{} Time_us: {:<{width}}'.format( + str_row, row[1], width=col_sizes[1]) + str_row = '{} Total_count: {:<{width}}'.format( + str_row, row[2], width=col_sizes[2]) list_data.append(str_row) else: list_data.append("No commands have been executed yet...") return list_data else: raise Exception('Hmm... connection seems to have been lost') - except Exception as e: + except Exception: self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) - return ['','{}: {}'.format('CRITICAL ERROR', self.error)] + return ['', '{}: {}'.format('CRITICAL ERROR', self.error)] def get_proclist(self): "Generates the process list information" try: if self.error is None: - self.cursor.execute('SELECT * FROM stats.stats_mysql_processlist WHERE command != "Sleep"') + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_processlist ' + 'WHERE command != "Sleep"') data = self.cursor.fetchall() list_data = [''] title = self.cursor.description col_sizes = self.get_col_sizes(title, data) if len(data) > 0: for row in data: - str_row = 'th-{:<{width}}'.format(row[0],width=col_sizes[0]) - str_row = '{} hg-{:<{width}}'.format(str_row, row[6],width=col_sizes[6]) - str_row = '{} {:<{width}}'.format(str_row, row[9],width=col_sizes[9]) - str_row = '{} {:<{width}}'.format(str_row, row[11],width=col_sizes[11]) - str_row = '{} {:<{width}}'.format(str_row, row[13],width=col_sizes[13]) + str_row = 'th-{:<{width}}'.format( + row[0], width=col_sizes[0]) + str_row = '{} hg-{:<{width}}'.format( + str_row, row[6], width=col_sizes[6]) + str_row = '{} {:<{width}}'.format( + str_row, row[9], width=col_sizes[9]) + str_row = '{} {:<{width}}'.format( + str_row, row[11], width=col_sizes[11]) + str_row = '{} {:<{width}}'.format( + str_row, row[13], width=col_sizes[13]) list_data.append(str_row) else: - self.cursor.execute('SELECT count(*) FROM stats.stats_mysql_processlist WHERE command == "Sleep"') + self.cursor.execute( + 'SELECT count(*) ' + 'FROM stats.stats_mysql_processlist ' + 'WHERE command = "Sleep"') data = self.cursor.fetchone() - list_data.append("No processes running currently... {} sleeping".format(data[0])) + list_data.append( + "No processes running currently... {} sleeping".format( + data[0])) return list_data else: raise Exception('Hmm... connection seems to have been lost') - except Exception as e: + except Exception: self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) - return ['','{}: {}'.format('CRITICAL ERROR', self.error)] + return ['', '{}: {}'.format('CRITICAL ERROR', self.error)] def get_querystats(self, sort='rule_id ASC'): "Generates the query rules data" try: if self.error is None: - self.cursor.execute('SELECT * FROM stats.stats_mysql_query_rules ORDER BY {}'.format(sort)) + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_query_rules ' + 'ORDER BY {}'.format(sort)) data = self.cursor.fetchall() list_data = [] title = self.cursor.description col_sizes = self.get_col_sizes(title, data) for row in data: - str_row = 'rule_id: {:<{width}}'.format(row[0],width=col_sizes[0]) - str_row = '{} hits: {:<{width}}'.format(str_row, row[1],width=col_sizes[1]) + str_row = 'rule_id: {:<{width}}'.format( + row[0], width=col_sizes[0]) + str_row = '{} hits: {:<{width}}'.format( + str_row, row[1], width=col_sizes[1]) list_data.append(str_row) return list_data else: raise Exception('Hmm... connection seems to have been lost') - except Exception as e: + except Exception: self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) - return ['','{}: {}'.format('CRITICAL ERROR', self.error)] + return ['', '{}: {}'.format('CRITICAL ERROR', self.error)] def get_rule_data(self, rule_id): "Generates detailed query rule information" try: if self.error is None: - self.cursor.execute('SELECT * FROM runtime_mysql_query_rules WHERE rule_id = {}'.format(rule_id)) + self.cursor.execute( + 'SELECT * ' + 'FROM runtime_mysql_query_rules ' + 'WHERE rule_id = {}'.format(rule_id)) rule_data = self.cursor.fetchone() fmt_rule_data = '' for cnt, rvalue in enumerate(rule_data): if rvalue is not None: - fmt_rule_data = '\n'.join([fmt_rule_data,'{:>21}: {}'.format(self.cursor.description[cnt][0], rvalue)]) + fmt_rule_data = '\n'.join( + [fmt_rule_data, '{:>21}: {}'.format( + self.cursor.description[cnt][0], rvalue)]) return fmt_rule_data else: raise Exception('Hmm... connection seems to have been lost') - except Exception as e: + except Exception: self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return '- N/A' @@ -275,34 +338,43 @@ class ProxysqlStats(object): "Generates detailed command counter information" try: if self.error is None: - self.cursor.execute('SELECT * FROM stats.stats_mysql_commands_counters WHERE Command = "{}"'.format(command)) + self.cursor.execute( + 'SELECT * ' + 'FROM stats.stats_mysql_commands_counters ' + 'WHERE Command = "{}"'.format(command)) command_data = self.cursor.fetchone() fmt_command_data = '' previous_interval = '' for cnt, rvalue in enumerate(command_data): if rvalue is not None: - fmt_command_data = '\n'.join([fmt_command_data,'{:>13}: {}' \ - .format(self.cursor.description[cnt][0].replace('cnt_','{} - '.format(previous_interval)), rvalue)]) + fmt_command_data = '\n'.join( + [fmt_command_data, '{:>13}: {}' .format( + self.cursor.description[cnt][0].replace( + 'cnt_', '{} - '.format( + previous_interval)), rvalue)]) if previous_interval == 'Total_Time_us': previous_interval = '0' else: - previous_interval = self.cursor.description[cnt][0].replace('cnt_','') + previous_interval = ( + self.cursor.description[cnt][0].replace( + 'cnt_', '')) return fmt_command_data else: raise Exception('Hmm... connection seems to have been lost') - except Exception as e: + except Exception: self.cursor, self.conn, self.error = self.get_pa_cursor() time.sleep(1) return '- N/A' -### Form sub-classes +# Form sub-classes + -class ConnPoolForm(npyscreen.FormBaseNew): +class ConnPoolForm(npyscreen.FormBaseNew): def set_up_handlers(self): super(ConnPoolForm, self).set_up_handlers() self.handlers.update({"s": self.resort, "+": self.interval_inc, - "-": self.interval_dec}) + "-": self.interval_dec}) def interval_inc(self, input): self.keypress_timeout = self.keypress_timeout + 10 @@ -323,36 +395,56 @@ class ConnPoolForm(npyscreen.FormBaseNew): else: self.sort = 'ConnOK DESC' self.cpt.value = self.parentApp.pstats.get_cpheader() - self.cpw.values = self.parentApp.pstats.get_cpstats(self.sort) - npyscreen.notify_confirm("Now ordered by - {}".format(self.sort), title="Sort order updated:", - form_color='STANDOUT', wrap=True, wide=False, editw=0) + self.cpw.values = self.parentApp.pstats.get_cpstats(self.sort) + npyscreen.notify_confirm( + "Now ordered by - {}".format( + self.sort), + title="Sort order updated:", + form_color='STANDOUT', + wrap=True, + wide=False, + editw=0) self.display() def while_waiting(self): self.cpt.value = self.parentApp.pstats.get_cpheader() - self.cpw.values = self.parentApp.pstats.get_cpstats(self.sort) - self.display() + self.cpw.values = self.parentApp.pstats.get_cpstats(self.sort) + self.display() def create(self): self.sort = 'ConnOK DESC' - self.cpt = self.add(npyscreen.TitleFixedText, name='Connection Pool Statistics:', - value=self.parentApp.pstats.get_cpheader(), editable=False, - use_two_lines=False,max_width=0, max_height=1) - self.cpw = self.add(npyscreen.Pager, name='Connection Pool Statistics:', - values=self.parentApp.pstats.get_cpstats(self.sort), - max_width=0, max_height=-3) - self.connButton = self.add(ConnsButton, name='ConnPool', rely=-3, max_height=1) - self.queryRulesButton = self.add(QueryRulesButton, name='QueryRules', rely=-3, relx=12) - self.globButton = self.add(GlobButton, name='GloStat', rely=-3, relx=24) - self.procButton = self.add(ProcButton, name='ProcList', rely=-3, relx=33) + self.cpt = self.add( + npyscreen.TitleFixedText, + name='Connection Pool Statistics:', + value=self.parentApp.pstats.get_cpheader(), + editable=False, + use_two_lines=False, + max_width=0, + max_height=1) + self.cpw = self.add( + npyscreen.Pager, + name='Connection Pool Statistics:', + values=self.parentApp.pstats.get_cpstats( + self.sort), + max_width=0, + max_height=-3) + self.connButton = self.add( + ConnsButton, name='ConnPool', rely=-3, max_height=1) + self.queryRulesButton = self.add( + QueryRulesButton, name='QueryRules', rely=-3, relx=12) + self.globButton = self.add( + GlobButton, name='GloStat', rely=-3, relx=24) + self.procButton = self.add( + ProcButton, name='ProcList', rely=-3, relx=33) self.comButton = self.add(ComButton, name="ComCount", rely=-3, relx=43) self.exitButton = self.add(ExitButton, name="Exit", rely=-3, relx=53) -class GlobForm(npyscreen.FormBaseNew): + +class GlobForm(npyscreen.FormBaseNew): def set_up_handlers(self): super(GlobForm, self).set_up_handlers() self.handlers.update({"+": self.interval_inc, - "-": self.interval_dec}) + "-": self.interval_dec}) def interval_inc(self, input): self.keypress_timeout = self.keypress_timeout + 10 @@ -361,26 +453,36 @@ class GlobForm(npyscreen.FormBaseNew): if self.keypress_timeout > 10: self.keypress_timeout = self.keypress_timeout - 10 - def while_waiting(self): + def while_waiting(self): self.cpw.values = self.parentApp.pstats.get_globstats() - self.cpw.display() + self.cpw.display() def create(self): - self.cpw = self.add(npyscreen.TitlePager, name='Global Statistics:', begin_entry_at=0, - use_two_lines=True, values=self.parentApp.pstats.get_globstats(), - max_width=0, max_height=-1) - self.connButton = self.add(ConnsButton, name='ConnPool', rely=-3, max_height=1) - self.queryRulesButton = self.add(QueryRulesButton, name='QueryRules', rely=-3, relx=12) - self.globButton = self.add(GlobButton, name='GloStat', rely=-3, relx=24) - self.procButton = self.add(ProcButton, name='ProcList', rely=-3, relx=33) + self.cpw = self.add( + npyscreen.TitlePager, + name='Global Statistics:', + begin_entry_at=0, + use_two_lines=True, + values=self.parentApp.pstats.get_globstats(), + max_width=0, + max_height=-1) + self.connButton = self.add( + ConnsButton, name='ConnPool', rely=-3, max_height=1) + self.queryRulesButton = self.add( + QueryRulesButton, name='QueryRules', rely=-3, relx=12) + self.globButton = self.add( + GlobButton, name='GloStat', rely=-3, relx=24) + self.procButton = self.add( + ProcButton, name='ProcList', rely=-3, relx=33) self.comButton = self.add(ComButton, name="ComCount", rely=-3, relx=43) self.exitButton = self.add(ExitButton, name="Exit", rely=-3, relx=53) + class ProcForm(npyscreen.FormBaseNew): def set_up_handlers(self): super(ProcForm, self).set_up_handlers() self.handlers.update({"+": self.interval_inc, - "-": self.interval_dec}) + "-": self.interval_dec}) def interval_inc(self, input): self.keypress_timeout = self.keypress_timeout + 10 @@ -395,22 +497,32 @@ class ProcForm(npyscreen.FormBaseNew): self.cpw.display() def create(self): - self.cpw = self.add(npyscreen.TitlePager, name='Active Processlist (Minimum interval 5s):', begin_entry_at=0, - use_two_lines=True, values=self.parentApp.pstats.get_proclist(), - max_width=0, max_height=-2) - self.connButton = self.add(ConnsButton, name='ConnPool', rely=-3, max_height=1) - self.queryRulesButton = self.add(QueryRulesButton, name='QueryRules', rely=-3, relx=12) - self.globButton = self.add(GlobButton, name='GloStat', rely=-3, relx=24) - self.procButton = self.add(ProcButton, name='ProcList', rely=-3, relx=33) + self.cpw = self.add( + npyscreen.TitlePager, + name='Active Processlist (Minimum interval 5s):', + begin_entry_at=0, + use_two_lines=True, + values=self.parentApp.pstats.get_proclist(), + max_width=0, + max_height=-2) + self.connButton = self.add( + ConnsButton, name='ConnPool', rely=-3, max_height=1) + self.queryRulesButton = self.add( + QueryRulesButton, name='QueryRules', rely=-3, relx=12) + self.globButton = self.add( + GlobButton, name='GloStat', rely=-3, relx=24) + self.procButton = self.add( + ProcButton, name='ProcList', rely=-3, relx=33) self.comButton = self.add(ComButton, name="ComCount", rely=-3, relx=43) self.exitButton = self.add(ExitButton, name="Exit", rely=-3, relx=53) + class QueryRulesForm(npyscreen.FormBaseNew): def set_up_handlers(self): super(QueryRulesForm, self).set_up_handlers() self.handlers.update({"s": self.resort, "+": self.interval_inc, - "-": self.interval_dec}) + "-": self.interval_dec}) def interval_inc(self, input): self.keypress_timeout = self.keypress_timeout + 10 @@ -425,8 +537,14 @@ class QueryRulesForm(npyscreen.FormBaseNew): else: self.sort = 'rule_id ASC' self.cpw.values = self.parentApp.pstats.get_querystats(self.sort) - npyscreen.notify_confirm("Now ordered by - {}".format(self.sort), title="Sort order updated:", - form_color='STANDOUT', wrap=True, wide=False, editw=0) + npyscreen.notify_confirm( + "Now ordered by - {}".format( + self.sort), + title="Sort order updated:", + form_color='STANDOUT', + wrap=True, + wide=False, + editw=0) self.cpw.display() def while_waiting(self): @@ -435,21 +553,32 @@ class QueryRulesForm(npyscreen.FormBaseNew): def create(self): self.sort = 'rule_id ASC' - self.cpw = self.add(ShowQueryInfo, name='Query Rules Stats:', begin_entry_at=0, - use_two_lines=True, values=self.parentApp.pstats.get_querystats(self.sort), - max_width=0, max_height=-1) - self.connButton = self.add(ConnsButton, name='ConnPool', rely=-3, max_height=1) - self.queryRulesButton = self.add(QueryRulesButton, name='QueryRules', rely=-3, relx=12) - self.globButton = self.add(GlobButton, name='GloStat', rely=-3, relx=24) - self.procButton = self.add(ProcButton, name='ProcList', rely=-3, relx=33) + self.cpw = self.add( + ShowQueryInfo, + name='Query Rules Stats:', + begin_entry_at=0, + use_two_lines=True, + values=self.parentApp.pstats.get_querystats( + self.sort), + max_width=0, + max_height=-1) + self.connButton = self.add( + ConnsButton, name='ConnPool', rely=-3, max_height=1) + self.queryRulesButton = self.add( + QueryRulesButton, name='QueryRules', rely=-3, relx=12) + self.globButton = self.add( + GlobButton, name='GloStat', rely=-3, relx=24) + self.procButton = self.add( + ProcButton, name='ProcList', rely=-3, relx=33) self.comButton = self.add(ComButton, name="ComCount", rely=-3, relx=43) self.exitButton = self.add(ExitButton, name="Exit", rely=-3, relx=53) + class ComForm(npyscreen.FormBaseNew): def set_up_handlers(self): super(ComForm, self).set_up_handlers() self.handlers.update({"+": self.interval_inc, - "-": self.interval_dec}) + "-": self.interval_dec}) def interval_inc(self, input): self.keypress_timeout = self.keypress_timeout + 10 @@ -463,64 +592,87 @@ class ComForm(npyscreen.FormBaseNew): self.cpw.display() def create(self): - self.cpw = self.add(ShowCommandInfo, name='Command Counters:', begin_entry_at=0, - use_two_lines=True, values=self.parentApp.pstats.get_commands(), - max_width=0, max_height=-2) - self.connButton = self.add(ConnsButton, name='ConnPool', rely=-3, max_height=1) - self.queryRulesButton = self.add(QueryRulesButton, name='QueryRules', rely=-3, relx=12) - self.globButton = self.add(GlobButton, name='GloStat', rely=-3, relx=24) - self.procButton = self.add(ProcButton, name='ProcList', rely=-3, relx=33) + self.cpw = self.add( + ShowCommandInfo, + name='Command Counters:', + begin_entry_at=0, + use_two_lines=True, + values=self.parentApp.pstats.get_commands(), + max_width=0, + max_height=-2) + self.connButton = self.add( + ConnsButton, name='ConnPool', rely=-3, max_height=1) + self.queryRulesButton = self.add( + QueryRulesButton, name='QueryRules', rely=-3, relx=12) + self.globButton = self.add( + GlobButton, name='GloStat', rely=-3, relx=24) + self.procButton = self.add( + ProcButton, name='ProcList', rely=-3, relx=33) self.comButton = self.add(ComButton, name="ComCount", rely=-3, relx=43) self.exitButton = self.add(ExitButton, name="Exit", rely=-3, relx=53) -### Popup window sub-classes +# Popup window sub-classes + class ShowCommandInfo(npyscreen.MultiLineAction): def __init__(self, *args, **keywords): super(ShowCommandInfo, self).__init__(*args, **keywords) def actionHighlighted(self, act_on_this, keypress): - npyscreen.notify_confirm(self.parent.parentApp.pstats.get_command_data(act_on_this.split()[0]), - title="Execution Time Distribution:") + npyscreen.notify_confirm( + self.parent.parentApp.pstats.get_command_data( + act_on_this.split()[0]), + title="Execution Time Distribution:") + class ShowQueryInfo(npyscreen.MultiLineAction): def __init__(self, *args, **keywords): super(ShowQueryInfo, self).__init__(*args, **keywords) def actionHighlighted(self, act_on_this, keypress): - npyscreen.notify_confirm(self.parent.parentApp.pstats.get_rule_data(act_on_this.split(':')[1].split()[0]), - title="Query Rule Info:") + npyscreen.notify_confirm( + self.parent.parentApp.pstats.get_rule_data( + act_on_this.split(':')[1].split()[0]), + title="Query Rule Info:") + +# Button sub-classes -### Button sub-classes class ComButton(npyscreen.ButtonPress): def whenPressed(self): self.parent.parentApp.switchForm("COMC") + class QueryRulesButton(npyscreen.ButtonPress): def whenPressed(self): self.parent.parentApp.switchForm("QSTAT") + class ProcButton(npyscreen.ButtonPress): def whenPressed(self): self.parent.parentApp.switchForm("PROC") + class GlobButton(npyscreen.ButtonPress): def whenPressed(self): self.parent.parentApp.switchForm("GLOB") + class ConnsButton(npyscreen.ButtonPress): def whenPressed(self): self.parent.parentApp.switchForm("MAIN") + class ExitButton(npyscreen.ButtonPress): def whenPressed(self): sys.exit(0) -### Main application +# Main application + class MainApp(npyscreen.NPSAppManaged): keypress_timeout_default = 10 + def __init__(self): super(MainApp, self).__init__() DB_CS = get_params() @@ -534,8 +686,7 @@ class MainApp(npyscreen.NPSAppManaged): self.addForm("PROC", ProcForm, name=title_bar) self.addForm("COMC", ComForm, name=title_bar) + if __name__ == '__main__': App = MainApp() App.run() - -