#!/usr/bin/env python2 # -*- mode: python;coding: utf-8 -*- # dooba --- # # Filename: dooba # Description: `dooba' is a easy tools monitoring oceanbase cluster for # oceanbase admins. It's based on python curses library, and is a # powerful tool for watching oceanbase cluster status with # straightfoward vision. # Created: 2013-07-23 21:05:08 (+0800) # Version: 2.1.3 # Last-Updated: 2018-06-07T16:57:32+0800 # By: Shi Yudi # Update #: 7247 # # Change Log: # 2018-06-06 Shi Yudi # Last-Updated: 2018-06-06T19:14:36+0800 #7237 (Shi Yudi) # 1. 修复Gallery页面切租户信息不显示的问题 # # 2018-01-16 Shi Yudi # Last-Updated: 2018-01-16T20:25:30+0800 #7112 (Shi Yudi) # 1. 适配1.x分支内部表格式 # 2. 去除0.5中的ConfigHelper,1.x只能显式指定ip等信息登录 # # 2014-11-20 Shi Yudi # Last-Updated: 2014-11-20T15:26:41+0800 #6946 (Shi Yudi) # 1. show only servers in current cluster in machine stat page # 2. a little change for machine widget # # 2014-05-14 Shi Yudi # Last-Updated: 2014-05-14T16:15:26+0800 #6913 (Shi Yudi) # 1. skip to select cluster if only one cluster # # 2014-04-24 Shi Yudi # Last-Updated: 2014-04-24T18:57:09+0800 #6515 (Shi Yudi) # 1. support ob 0.5 updateserver columns # # 2014-04-22 Shi Yudi # Last-Updated: 2014-04-22T19:57:35+0800 #6288 (Shi Yudi) # 1. add column filter for ups,ms,cs page # # 2014-04-01 Shi Yudi # Last-Updated: 2014-04-01T13:41:45+0800 #5844 (Shi Yudi) # 1. fix oceanbase 0.5 gather stats mixed by all cluster # # 2014-02-27 Yudi Shi # Last-Updated: Thu Feb 27 20:01:13 2014 (+0800) #5777 (Yudi Shi) # 1. add fail sql exec count in gallery page # # 2013-12-31 Shi Yudi # Last-Updated: Tue Dec 31 13:10:39 2013 (+0800) #5770 (Shi Yudi) # 1. add error msg if geometry is not enough # 2. fix some msg output # # 2013-12-11 Shi Yudi # Last-Updated: Wed Dec 11 14:51:43 2013 (+0800) #5745 (Shi Yudi) # 1. fix password decrypt bug (fill 8 width encoding str) # # 2013-10-25 Shi Yudi # Last-Updated: Tue Dec 3 16:01:23 2013 (+0800) #5739 (Shi Yudi) # 1. add time to widget frame # # 2013-10-25 Shi Yudi # Last-Updated: 2013-10-25 20:28:17 (+0800) #5647 (Shi Yudi) # 1. add http api support # 2. add obssh to login server # 3. using obconfig password # # 2013-09-04 Shi Yudi # Last-Updated: 2013-09-04 10:52:02 (+0800) #4786 (Shi Yudi) # 1. add machine stat # 2. fix many bugs # 3. add ssh, mysql login # 4. add delete and restore widgets # # 2013-08-20 Shi Yudi # Last-Updated: 2013-08-20 15:29:45 (+0800) #2012 (Shi Yudi) # 1. support instant statistics monitor for ups/cs/ms # 2. keyboard response for switch between widget # 3. rewrite help page # # 2013-08-16 Shi Yudi # Last-Updated: 2013-08-16 16:07:32 (+0800) #1052 (Shi Yudi) # 1. add header column widget descripe each server # 2. add instant stat list (only for ms now) # 3. add mergeserver header info # # 2013-08-14 Shi Yudi # Last-Updated: 2013-08-14 16:11:40 (+0800) #576 (Shi Yudi) # 1. add MessageBox for dooba, key 'p' for test # 2. add selective mode, TAB for swtich between widgets # 3. add supermode option, just a husk right now # # 2013-08-07 Shi Yudi # Last-Updated: 2013-08-07 10:38:33 (+0800) #479 (Shi Yudi) # 1. fix term evironment setting bug # 2. fix getting appname bug # 3. refact main method # # 2013-08-06 Shi Yudi # Last-Updated: 2013-08-06 20:47:35 (+0800) #443 (Shi Yudi) # 1. OceanBase alive checker before running # 2. colorfull widgets # 3. fix some promptions # 4. change header widget and status widget style with horizontal line # 5. remove page border # # 2013-08-06 Shi Yudi # Last-Updated: 2013-08-06 16:46:39 (+0800) #347 (Shi Yudi) # 1. add chunkserver, mergeserver, updateserver info widgets # 2. fix non-lexical closures problem # 3. dynamic helper widget promption # 4. change some pages' index # # 2013-07-31 Shi Yudi # Last-Updated: 2013-07-31 19:11:19 (+0800) #14 (Shi Yudi) # 1. redesign helper bar and status bar # 2. add more pages for dooba # 3. beauty python coding style # # 2013-07-23 Shi Yudi # 1. header with app name, and other mocks. # 2. sql rt, sql count, cs rt, ups rt are added to screen. # # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 3, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth # Floor, Boston, MA 02110-1301, USA. # # # Code: from collections import deque from datetime import datetime, timedelta, date from errno import * from getopt import getopt, GetoptError from locale import setlocale, LC_ALL from os import environ, read, setsid from pprint import pformat from random import shuffle from subprocess import Popen, PIPE, STDOUT, call from telnetlib import Telnet from threading import Thread from time import sleep, strftime from urllib2 import urlopen, URLError import BaseHTTPServer import atexit import bz2 import curses import curses.textpad import itertools import json import math import os import select import signal import socket import struct import sys import tempfile import textwrap import types import traceback import time class Global: MAX_LINES = 100 WIDGET_HEIGHT = 7 DEFAULT_USER = 'root' DEFAULT_PASS = 'admin' class Options(object): host = '127.0.0.1' port = 2828 user = None password = "" database = "oceanbase" supermode = False interval = 1 dataid = None using_ip_port = False env = 'unknown' machine_interval = 5 degradation = False show_win_file = None daemon = False http = False daemon_action = 'start' # HTTP server relating http_port = 33244 tenant_id = None tenant = None debug = False def __str__(self): result = {} for k in dir(self): v = getattr(self, k) if k.find('__') == 0: continue result[k] = v return pformat(result) # auxiliary functions class ColumnFactory(object): def __init__(self, svr, ip): self.__svr = svr self.__ip = ip def count(self, name, sname, obname, enable=False): DEBUG(self.count, "name,sname,obname", ",".join([str(name), str(sname), str(obname)])) if type(obname) == str: pass elif type(obname) == list: obname = [name for name in obname] else: raise Exception("unsupport type %s" % type(obname)) DEBUG(self.count, "name,sname,obname", ",".join([str(name), str(sname), str(obname)])) return self.count0(name, sname, obname, enable) def count0(self, name, sname, obname, enable=False): DEBUG(self.count0, "name,sname,obname", ",".join([str(name), str(sname), str(obname)])) DEBUG(self.count0, "self.__svr|self.__ip", "|".join([self.__svr, self.__ip])) svr = self.__svr ip = self.__ip def calc_func(stat,ip=ip): def try_get(d, k, b): try: if d.has_key(k): return d[k] else: return d[b] except Exception as e: raise e try: if type(obname) == str: return try_get(stat, svr, oceanbase.get_current_tenant())[ip][obname] elif type(obname) == list: return sum([try_get(stat, svr, oceanbase.get_current_tenant())[ip][name] for name in obname]) else: raise Exception("unsupport type %s" % type(obname)) except KeyError as e: # DEBUG(calc_func, "exception", e) pass return Column(name, calc_func, 7, True, enable=enable, sname=sname) def time(self, name, sname, obnamet, obnamec=None, enable=False): if obnamec is None: obnamec = obnamet return self.time0(name, sname, obnamet, obnamec, enable) def time0(self, name, sname, obnamet, obnamec=None, enable=False): svr = self.__svr ip = self.__ip def calc_func(stat,ip=ip): def try_get(d, k, b): if d.has_key(k): return d[k] else: return d[b] try: if type(obnamec) == str: total_count = try_get(stat, svr, oceanbase.get_current_tenant())[ip][obnamec] elif type(obnamec) == list: total_count = sum([try_get(stat, svr, oceanbase.get_current_tenant())[ip][name] for name in obnamec]) DEBUG(calc_func, "name", obnamet) DEBUG(calc_func, "ip", ip) DEBUG(calc_func, "svr", svr) DEBUG(calc_func, "enable", enable) DEBUG(calc_func, "tenant", oceanbase.get_current_tenant()) DEBUG(calc_func, "try_get(stat, svr, oceanbase.get_current_tenant())[ip][obnamet]", try_get(stat, svr, oceanbase.get_current_tenant())[ip][obnamet]) DEBUG(calc_func, "float(total_count)", float(total_count)) DEBUG(calc_func, "try_get(stat, svr, oceanbase.get_current_tenant())[ip][obnamet] / 1000 / float(total_count)", try_get(stat, svr, oceanbase.get_current_tenant())[ip][obnamet] / 1000 / float(total_count or 1) ) return try_get(stat, svr, oceanbase.get_current_tenant())[ip][obnamet] / 1000 / float(total_count or 1) except Exception as e: # DEBUG(calc_func, "exception", e) pass return Column(name, calc_func, 7, enable=enable, sname=sname) def cache(self, name, sname, obname): svr = self.__svr ip = self.__ip return Column(name, lambda stat,ip=ip: stat[svr][ip][obname+"_cache_hit"] / float(stat[svr][ip][obname+"_cache_hit"] + stat[svr][ip][obname+"_cache_miss"] or 1), 7, enable=False, sname=sname) def mem_str(mem_int, bit=False): mem_int = int(mem_int) if mem_int < 1024: return str(mem_int) + (bit and 'b' or '') mem_int = float(mem_int) mem_int /= 1024 if mem_int < 1024: return "%.2fK" % mem_int + (bit and 'b' or '') mem_int /= 1024 if mem_int < 1024: return "%.2fM" % mem_int + (bit and 'b' or '') mem_int /= 1024 if mem_int < 1024: return "%.2fG" % mem_int + (bit and 'b' or '') return "UNKNOW" def count_str(count_int, kilo=True): return str(count_int) def percent_str(percent_int): return str(round(float(percent_int) * 100, 1)) + "%" class Cowsay(object): '''Copyright 2011 Jesse Chan-Norris https://github.com/jcn/cowsay-py/blob/master/cowsay.py''' def __init__(self, str, length=40): self.__result = self.build_bubble(str, length) + self.build_cow() def __str__(self): return self.__result def build_cow(self): return """ \ ^__^ \ (oo)\_______ (__)\ )\/\\ ||----w | || || """ def build_bubble(self, str, length=40): bubble = [] lines = self.normalize_text(str, length) bordersize = len(lines[0]) bubble.append(" " + "_" * bordersize) for index, line in enumerate(lines): border = self.get_border(lines, index) bubble.append("%s %s %s" % (border[0], line, border[1])) bubble.append(" " + "-" * bordersize) return "\n".join(bubble) def normalize_text(self, str, length): lines = textwrap.wrap(str, length) maxlen = len(max(lines, key=len)) return [ line.ljust(maxlen) for line in lines ] def get_border(self, lines, index): if len(lines) < 2: return [ "<", ">" ] elif index == 0: return [ "/", "\\" ] elif index == len(lines) - 1: return [ "\\", "/" ] else: return [ "|", "|" ] class Daemon: """ A generic daemon class. Usage: subclass the Daemon class and override the run() method """ def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile def daemonize(self): """ do the UNIX double-fork magic, see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177) http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 """ try: pid = os.fork() if pid > 0: # exit first parent sys.exit(0) except OSError, e: sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # decouple from parent environment os.chdir("/") os.setsid() os.umask(0) # do second fork try: pid = os.fork() if pid > 0: # exit from second parent sys.exit(0) except OSError, e: sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() si = file(self.stdin, 'r') so = file(self.stdout, 'a+') se = file(self.stderr, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # write pidfile atexit.register(self.delpid) pid = str(os.getpid()) file(self.pidfile,'w+').write("%s\n" % pid) def delpid(self): os.remove(self.pidfile) def start(self): """ Start the daemon """ # Check for a pidfile to see if the daemon already runs try: pf = file(self.pidfile,'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if pid: message = "pidfile %s already exist. Daemon already running?\n" sys.stderr.write(message % self.pidfile) sys.exit(1) # Start the daemon self.daemonize() self.run() def stop(self): """ Stop the daemon """ # Get the pid from the pidfile try: pf = file(self.pidfile,'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if not pid: message = "pidfile %s does not exist. Daemon not running?\n" sys.stderr.write(message % self.pidfile) return # not an error in a restart # Try killing the daemon process try: while 1: os.kill(pid, signal.SIGTERM) sleep(0.1) except OSError, err: err = str(err) if err.find("No such process") > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print str(err) sys.exit(1) def restart(self): """ Restart the daemon """ self.stop() self.start() def run(self): """ You should override this method when you subclass Daemon. It will be called after the process has been daemonized by start() or restart(). """ class OceanBase(object): instant_key_list = [ # mergeserver 'ms_memory_limit', 'ms_memory_total', 'ms_memory_parser', 'ms_memory_transformer', 'ms_memory_ps_plan', 'ms_memory_rpc_request', 'ms_memory_sql_array', 'ms_memory_expression', 'ms_memory_row_store', 'ms_memory_session', 'ps_count', # chunkserver 'serving_version', 'old_ver_tablets_num', 'old_ver_merged_tablets_num', 'new_ver_tablets_num', 'new_ver_tablets_num', 'memory_used_default', 'memory_used_network', 'memory_used_thread_buffer', 'memory_used_tablet', 'memory_used_bi_cache', 'memory_used_block_cache', 'memory_used_bi_cache_unserving', 'memory_used_block_cache_unserving', 'memory_used_join_cache', 'memory_used_sstable_row_cache', 'memory_used_merge_buffer', 'memory_used_merge_split_buffer', # updateserver 'memory_total', 'memory_limit', 'memtable_total', 'memtable_used', 'total_rows', 'active_memtable_limit', 'active_memtable_total', 'active_memtable_used', 'active_total_rows', 'frozen_memtable_limit', 'frozen_memtable_total', 'frozen_memtable_used', 'frozen_total_rows', 'low_prio_queued_count', 'normal_prio_queued_count', 'high_prio_queued_count', 'hotspot_queued_count', # machine stat 'load1', 'load5', 'load15', 'MemTotal', 'MemFree', # mock 'timestamp' ] app_info = {'username':None, 'password':None} def __init__(self, dataid=None): self.__q = {} self.__stop = True self.__machine_stat = {} self.__host = Options.host self.__port = Options.port self.update_dataid(dataid) self.__cur_cluster_svrs = None self.__cur_tenant_id = 1 self.__cur_tenant_name = 'sys' self.tenant = [] def update_dataid(self, dataid): if dataid: self.app_info = ObConfigHelper().get_app_info(dataid) def dosql(self, sql, host=None, port=None, database=None): if host is None: host = self.__host if port is None: port = self.__port if host is None: host = Options.host if port is None: port = Options.port if database is None: database = Options.database username = Options.user or self.app_info['username'] or Global.DEFAULT_USER password = Options.password if password: mysql = "mysql --connect_timeout=5 -s -N -h%s -P%d -u%s -p%s %s" % (host, port, username, password, database) else: mysql = "mysql --connect_timeout=5 -s -N -h%s -P%d -u%s %s" % (host, port, username, database) cmd = "%s -e \"%s\"" % (mysql, sql) DEBUG(self.dosql, "", cmd) p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) output = p.communicate()[0] err = p.wait() if err: raise Exception('popen Fail', cmd) return output def show_sql_result(self, sql, host=None, port=None, database=None): if host is None: host = self.__host if port is None: port = self.__port if host is None: host = Options.host if port is None: port = Options.port if database is None: database = Options.database username = Options.user or self.app_info['username'] or Global.DEFAULT_USER password = Options.password if password: mysql = "mysql --table --connect_timeout=5 --pager=less --column-names -h%s -P%d -u%s -p%s %s" % (host, port, username, password, database) else: mysql = "mysql --table --connect_timeout=5 --pager=less --column-names -h%s -P%d -u%s %s" % (host, port, username, database) cmd = "%s -e \"%s\" | less" % (mysql, sql) curses.endwin() call("clear") os.system(cmd) curses.doupdate() def mysql(self, host=None, port=None, database=None): if host is None: host = self.__host if port is None: port = self.__port if host is None: host = Options.host if port is None: port = Options.port if database is None: database = Options.database username = Options.user or self.app_info['username'] or Global.DEFAULT_USER password = Options.password if password: cmd = "mysql --connect_timeout=5 -h%s -P%d -u%s -p%s" % (host, port, username, password) else: cmd = "mysql --connect_timeout=5 -h%s -P%d -u%s" % (host, port, username) DEBUG(self.mysql, "mysql", cmd) environ['MYSQL_PS1'] = "(\u@\h) [%s]> " % self.app call('clear') try: call(cmd, shell=True) except KeyboardInterrupt: pass call('clear') def ssh(self, host): cmd = "ssh -o StrictHostKeyChecking=no %s" % host call('clear') try: call(cmd, shell=True) except KeyboardInterrupt: pass call('clear') def test_alive(self, fatal=True, do_false=None, do_true=None, host=None, port=None): if host is None: host = Options.host if port is None: port = Options.port try: oceanbase.dosql('select 1', host=host, port=port) if do_true is not None: do_true("Check oceanbase alive successfully! [%s:%d]" % (host, port)) except Exception: if do_false is not None: do_false("Can't connect oceanbase, plz check options!\n" "Options: [IP:%s] [PORT:%d] [USER:%s] [PASS:%s]" % (host, port, Options.user, Options.password)) if fatal: exit(1) else: return False return True def __check_schema(self): sql = """desc gv\\$sysstat""" res = self.dosql(sql) if "CON_ID" in res.split("\n")[0]: return """ select current_time(), con_id, svr_ip, svr_port, name, value from gv\$sysstat where con_id = %s""" else: return """ select current_time(), tenant_id, svr_ip, svr_port, stat_name, value from gv\$sysstat where tenant_id = %s""" def __get_all_stat(self): sql = self.__check_schema() % (str(self.get_current_tenant())) res = self.dosql(sql) r = dict() time = '' if not self.__using_server_time(): time = str(datetime.now()) for one in res.split("\n")[:-1]: a = one.split("\t") if self.__using_server_time(): time = a[0] now = datetime.strptime(time, '%Y-%m-%d %H:%M:%S.%f') tnt = a[1] ip = a[2] port = a[3] name = a[4] value = int(a[5]) ipport = ip + ":" + port a = [time, tnt, ip, port, name, value] DEBUG(self.__get_all_stat, "stuffs", a) if tnt not in r: r[tnt] = dict() if ipport not in r[tnt]: r[tnt][ipport] = dict() r[tnt][ipport][name] = value r['time'] = now r['timestamp'] = now return r def sub_stat(self, now, prev): r = self.__sub_stat(now, prev) return r def __sub_stat(self, now, prev): r = {} for k in now.keys(): if k in self.instant_key_list: r[k] = now[k] elif type(now[k]) == dict: r.update({k: self.__sub_stat(now[k], prev[k])}) elif type(now[k]) == int: r[k] = now[k] - prev[k] elif type(now[k]) == float: r[k] = now[k] - prev[k] elif type(now[k]) == type(datetime.min): r[k] = now[k] - prev[k] else: #print type(now[k]) pass return r def __update_oceanbase_stat_runner(self): q = self.__q prev = self.__get_all_stat() while not self.__stop: sleep(Options.interval) if self.__stop: break try: cur = self.__get_all_stat() except Exception: continue if str(oceanbase.get_current_tenant()) not in cur: # tid has changed before result returned, ignore it. prev = self.__get_all_stat() continue try: q.append(self.__sub_stat(cur, prev)) if (len(q) > Global.MAX_LINES): q.popleft() except KeyError: pass prev = cur def __do_ssh(self, host, cmd): BUFFER_SIZE = 1 << 16 ssh = Popen(['ssh', '-o', 'PreferredAuthentications=publickey', '-o', 'StrictHostKeyChecking=no', host, cmd], stdout=PIPE, stderr=PIPE, preexec_fn=setsid, shell=False) output = ssh.communicate()[0] err = ssh.wait() if err: raise Exception('popen Fail', "%s: %s" % (host, cmd)) return output def __get_machine_stat(self, ip): cmd = ("cat <(date +%s%N) <(cat /proc/stat | head -1 | cut -d' ' -f 3-9) " "/proc/loadavg <(cat /proc/meminfo) <(echo END_MEM) " "<(cat /proc/net/dev | sed -n 3~1p) <(echo END_NET) " "<(cat /proc/diskstats | grep ' 8 ') <(echo END_DISK)") try: output = self.__do_ssh(ip, cmd) except Exception: return {} lines = output.split('\n') res = {} res['time'] = float(lines[0]) / 1000 # ms lines = lines[1:] cpuinfo = lines[0] columns = cpuinfo.split(" ") res['user'] = int(columns[0]) res['nice'] = int(columns[1]) res['sys'] = int(columns[2]) res['idle'] = int(columns[3]) res['iowait'] = int(columns[4]) res['irq'] = int(columns[5]) res['softirq'] = int(columns[6]) res['total'] = 0 for c in columns: res['total'] += int(c) lines = lines[1:] loadavg = lines[0] columns = loadavg.split(" ") res['load1'] = columns[0] res['load5'] = columns[1] res['load15'] = columns[2] lines = lines[1:] # mem for line in lines: if 'END_MEM' == line: break kv = line.split() k = kv[0][:-1] v = kv[1] res[k] = int(v) * 1024 idx = lines.index('END_MEM') lines = lines[idx + 1:] # net res['net'] = {} for line in lines: if 'END_NET' == line: break colon = line.find(':') assert colon > 0, line name = line[:colon].strip() res['net'][name] = {} fields = line[colon+1:].strip().split() res['net'][name]['bytes_recv'] = int(fields[0]) res['net'][name]['packets_recv'] = int(fields[1]) res['net'][name]['errin'] = int(fields[2]) res['net'][name]['dropin'] = int(fields[3]) res['net'][name]['bytes_sent'] = int(fields[8]) res['net'][name]['packets_sent'] = int(fields[9]) res['net'][name]['errout'] = int(fields[10]) res['net'][name]['dropout'] = int(fields[11]) idx = lines.index('END_NET') lines = lines[idx + 1:] res['disk'] = {} SECTOR_SIZE = 512 for line in lines: # http://www.mjmwired.net/kernel/Documentation/iostats.txt if 'END_DISK' == line: break _, _, name, reads, _, rbytes, rtime, writes, _, wbytes, wtime = line.split()[:11] res['disk'][name] = {} res['disk'][name]['rbytes'] = int(rbytes) * SECTOR_SIZE res['disk'][name]['wbytes'] = int(wbytes) * SECTOR_SIZE res['disk'][name]['reads'] = int(reads) res['disk'][name]['writes'] = int(writes) res['disk'][name]['rtime'] = int(rtime) res['disk'][name]['wtime'] = int(wtime) return res idx = lines.index('END_DISK') lines = lines[idx + 1:] def __update_server_stat_runner(self): prev = {} for ip in self.ip_list: prev[ip] = self.__get_machine_stat(ip) while not self.__stop: sleep(Options.machine_interval) shuffle(self.ip_list) for ip in self.ip_list: if self.__stop: break cur = self.__get_machine_stat(ip) try: result = self.__sub_stat(cur, prev[ip]) self.__machine_stat.update({ip: result}) except KeyError: pass prev[ip] = cur def __update_version(self): self.version = "Unknown" res = self.dosql("show variables like 'version_comment'") for one in res.split("\n")[:-1]: a = one.split("\t") self.version = a[1] self.app = oceanbase.dosql("select value from __all_virtual_sys_parameter_stat where name='cluster' limit 1")[:-1] def __using_server_time(self): if self.version.find('0.4.1') >= 0: return False elif self.version.find('0.4.2') >= 0: return True return False def start(self): self.__stop = False self.__q = deque([]) self.__th = Thread(target=self.__update_oceanbase_stat_runner, args=()) self.__th.daemon = True self.__th.start() if Options.env == 'online': self.__update_ip_list() self.__machine_stat = {} self.__svr_th = Thread(target=self.__update_server_stat_runner, args=()) self.__svr_th.daemon = True self.__svr_th.start() def dump_queue(self): print self.__q def now(self): return self.__q[-1] def stat_count(self): return len(self.__q) def latest(self, num=1): return list(self.__q)[-num:] def machine_stat(self): return self.__machine_stat def update_tenant_info(self): DEBUG(self.update_tenant_info, "update_tenant_info", "start update_tenant_info") class Tenant: def __init__(self, tid, name, zone_list, selected): self.tenant_id = tid self.tenant_name = name self.zone_list = zone_list self.selected = selected self.svr_list = {"observer":[]} res = self.dosql("select tenant_id, tenant_name, zone_list from __all_tenant") self.tenant = [] flg = True for line in res.split("\n")[0:-1]: tnt = line.split("\t") self.tenant.append(Tenant(tnt[0], tnt[1], tnt[2], flg)) flg = False DEBUG(self.update_tenant_info, "self.tenant", self.tenant) svrs = self.dosql("select svr_ip, svr_port, id, zone, inner_port, with_rootserver, status from __all_server") for line in svrs.rstrip("\n").split("\n"): svr = line.split("\t") for tnt in self.tenant: tnt.svr_list["observer"].append({"ip":svr[0], "port":svr[1], "role":svr[5]}) self.__update_version() #self.__update_ip_list() self.__update_cur_cluster_info() self.__update_ip_list() self.__update_sample() def check_lms(self, say): if Options.dataid is not None: lms_list = self.app_info['lms_list'] if not lms_list or len(lms_list) <= 0: say('Get lms list fail, plz check' + ' [ dataid = %s, lms_list = %s ]' % (Options.dataid, lms_list)) else: for lms in lms_list: say('checking lms [%s:%s]' % lms) if oceanbase.test_alive(host=lms[0], port=lms[1], fatal=False, do_false=say, do_true=say): self.__host = lms[0] self.__port = lms[1] #oceanbase.update_cluster_info() oceanbase.update_tenant_info() return True return False def __update_sample(self): self.sample = self.__get_all_stat() self.sample['time'] = timedelta() def __update_ip_list(self): ip_list = [] ip_map = {} for clu in self.tenant: svr_list = clu.svr_list for name in ["observer"]: ip_list += [svr["ip"] for svr in svr_list[name]] for ip in [svr["ip"] for svr in svr_list[name]]: if ip in ip_map: ip_map[ip].append(name) else: ip_map[ip] = [name] self.ip_list = list(set(ip_list)) self.ip_map = ip_map # def __update_ip_list(self): # ip_list = [] # ip_map = {} # for clu in self.cluster: # svr_list = clu.svr_list # for name in ('chunkserver', 'mergeserver', 'updateserver', 'rootserver'): # ip_list += [svr['ip'] for svr in svr_list[name]] # for ip in [svr['ip'] for svr in svr_list[name]]: # if ip in ip_map: # ip_map[ip].append(name) # else: # ip_map[ip] = [name] # self.ip_list = list(set(ip_list)) # self.ip_map = ip_map def __update_cur_cluster_info(self): for tnt in self.tenant: self.__cur_tenant_id = tnt.tenant_id self.__cur_cluster_svrs = tnt.svr_list return self.__cur_cluster_svrs # def __update_cur_cluster_info(self): # ip = self.__host # port = self.__port # for clu in self.cluster: # for ms in clu.svr_list['mergeserver']: # if ms['ip'] == ip and ms['port'] == port: # if Options.dataid: # self.__host, self.__port = clu.vip, clu.port # self.__cur_cluster_svrs = clu.svr_list # self.__cur_cluster_id = clu.id # return self.__cur_cluster_svrs # if clu.vip == ip and clu.port == port: # if Options.dataid: # self.__host, self.__port = clu.vip, clu.port # self.__cur_cluster_svrs = clu.svr_list # self.__cur_cluster_id = clu.id # return self.__cur_cluster_svrs # return {} def update_lms(self): svrs = oceanbase.dosql("select svr_ip, svr_port from __all_server where with_rootserver = 1 limit 1") for line in svrs.rstrip("\n").split("\n"): svr = line.split("\t") ip = svr[0] try: port = int(svr[1]) self.__host = svr[0] self.__port = svr[1] except ValueError: return # def update_lms(self): # svrs = oceanbase.dosql("select svr_ip,svr_port from (select * from __all_server_stat) t where svr_type='updateserver' limit 1") # for line in svrs.rstrip("\n").split("\n"): # svr = line.split("\t") # ip = svr[0] # try: # port = int(svr[1]) # except ValueError: # no lms # return # for clu in self.cluster: # for ups in clu.svr_list['updateserver']: # if ups['ip'] == ip and ups['port'] == port: # self.__host = clu.vip # self.__port = clu.port # return def __set_current_tenant(self, tname, tid): self.__cur_tenant_name = tname self.__cur_tenant_id = tid def get_current_tenant(self): return self.__cur_tenant_id def get_current_tenant_name(self): return self.__cur_tenant_name def get_tenant_svr(self, tid=None): for tnt in self.tenant: DEBUG(self.get_tenant_svr, "cur_tenant_id.selected", str(tnt.tenant_id) + "|" + str(tnt.selected)) if True == tnt.selected: return tnt.svr_list def find_svr_list(self): if not self.__cur_cluster_svrs: self.__update_cur_cluster_info() return self.__cur_cluster_svrs def stop(self): self.__stop = True def switch_tenant(self, tid): tname = "sys" for tnt in self.tenant: if str(tid) == str(tnt.tenant_id) or tid == tnt.tenant_name: tname = tnt.tenant_name tid = tnt.tenant_id tnt.selected = True self.__q.clear() else: tnt.selected = False DEBUG(self.switch_tenant, "self.tenant.selected", [" ".join([tnt.tenant_id, str(tnt.selected)]) for tnt in self.tenant]) self.__update_cur_cluster_info() self.__update_sample() self.__set_current_tenant(tname, tid) class Page(object): def __init__(self, parent, layout, y, x, height, width): self.__parent = parent self.__layout = layout self.__widgets = [] self.__win = parent.derwin(height, width, y, x) self.__y = y self.__x = x self.__height = height self.__width = width self.border() self.__win.nodelay(1) self.__win.timeout(0) self.__win.keypad(1) self.move(y, x) self.resize(height, width) self.__cur_select = 0 self.__shown_widget_num = 0 def add_widget(self, widget): if 0 == len(self.__widgets): widget.select(True) self.__widgets.append(widget) self.__rearrange() def update_widgets(self): pass def __rearrange(self): undeleted_widgets = filter(lambda w: False == w.delete(), self.__widgets) self.__layout.rearrange(0, 0, self.__height, self.__width, undeleted_widgets) def rearrange(self): self.__rearrange() def update(self): self.update_widgets() def clear_widgets(self): self.__widgets = [] def resize(self, height, width): self.__height = height self.__width = width self.__win.resize(height, width) self.__rearrange() def move(self, y, x): self.__x = x self.__y = y self.__win.mvderwin(y, x) self.__rearrange() def __reset_widgets(self): if len(self.__widgets) <= 0: return shown_widgets = self.shown_widgets() map(lambda w: w.select(False), self.__widgets) map(lambda w: w.delete(False), self.__widgets) self.__cur_select = 0 self.__widgets[self.__cur_select].select(True) self.__rearrange() def __delete_current_widget(self): shown_widgets = self.shown_widgets() if len(shown_widgets) <= 0: return shown_widgets[self.__cur_select].select(False) shown_widgets[self.__cur_select].delete(True) self.__cur_select -= 1 self.select_next() self.__rearrange() def border(self): pass def process_key(self, ch): if ch == ord('d'): self.__delete_current_widget() elif ch == ord('R'): self.__reset_widgets() elif ch == ord('m'): curses.endwin() oceanbase.mysql() curses.doupdate() elif ch == ord('j'): curses.endwin() w = self.selected_widget() oceanbase.ssh(w.host()) curses.doupdate() def redraw(self): self.erase() self.__layout.redraw(self.shown_widgets()) def getch(self): return self.__win.getch() def erase(self): self.__win.erase() def win(self): return self.__win def title(self): return 'Untitled' def select_next(self): shown_widgets = self.shown_widgets() if len(shown_widgets) <= 0: return shown_widgets[self.__cur_select].select(False) self.__cur_select = (self.__cur_select + 1) % len(shown_widgets) shown_widgets[self.__cur_select].select(True) def parent(self): return self.__parent def shown_widgets(self): '''Actually shown widgets that is all widgets except for 1. deleted widgets and, 2. couldn\'t display widgets as no space for them. ''' return filter(lambda w: w.show() and False == w.delete(), self.__widgets) def valid_widgets(self): '''All widgets excpet for the deleted widgets.''' return filter(lambda w: False == w.delete(), self.__widgets) def all_widgets(self): return self.__widgets def selected_widget(self): ''' may has no selected widget ''' if len(self.shown_widgets()) > self.__cur_select: return self.shown_widgets()[self.__cur_select] return None def select_columns(self): if len(self.all_widgets()) > 0: all_widgets = [hc_widget.column_widget() for hc_widget in self.all_widgets()] columns = all_widgets[0].valid_columns() columns_ret = ColumnCheckBox('Select Columns', columns, self.__parent).run() for widget in all_widgets: for idx in range(0, len(columns)): widget.valid_columns()[idx].enable(columns[idx].enable(enable=True)) widget.update() [hc_widget.resize() for hc_widget in self.all_widgets()] self.rearrange() class Layout(object): def __init__(self): pass def redraw(self, widgets): for widget in widgets: try: if widget.show(): widget.redraw() except Exception: pass def __calc_widget_height(self, height, width, widgets): max_min_height = max([ widget.min_height() for widget in widgets ]) for widget in widgets: widget.min_height(max_min_height) wwidths = [ widget.min_width() for widget in widgets ] cur_width = 0 nline = 1 for wwidth in wwidths: if cur_width + wwidth <= width: cur_width += wwidth else: cur_width = wwidth nline += 1 widget_height = height / nline return max(widget_height, max_min_height) def rearrange(self, y, x, height, width, widgets): if height <= 0 or width <= 0 or len(widgets) <= 0: return 0 widget_height = self.__calc_widget_height(height, width, widgets) for widget in widgets: widget.show(False) cur_y = 0 cur_x = 0 for index,widget in enumerate(widgets): if cur_x + widget.min_width() > width: cur_y += widget_height cur_x = 0 try: widget.move(0, 0) widget.resize(widget_height, widget.min_width()) widget.move(cur_y + y, cur_x + x) widget.show(True) except curses.error: return index cur_x += widget.min_width() return len(widgets) class Widget(object): def __init__(self, min_height, min_width, parent, use_win=False): if use_win: self.__win = parent else: self.__win = parent.derwin(min_height, min_width, 0, 0) self.__min_height = min_height self.__min_width = min_width self.__height, self.__width = self.__win.getmaxyx() self.__y, self.__x = self.__win.getmaxyx() self.__select = False self.__show = False self.__deleted = False def resize(self, height, width): self.__height = height self.__width = width maxh, maxw = self.__win.getmaxyx() if (width > maxw): MessageBox(self.__win, "TERM is too small!").run(anykey=True) exit(1) self.__win.resize(height, width) def move(self, y, x): self.__win.mvderwin(y, x) self.__y = y self.__x = x def mvwin(self, y, x): self.__win.mvwin(y, x) self.__y = y self.__x = x def min_height(self, height=None): if height: self.__min_height = height return self.__min_height def min_width(self, width=None): if width: self.__min_width = width return self.__min_width def geometry(self): return self.__y, self.__x, self.__height, self.__width def height(self): return self.__height def width(self): return self.__width def redraw(self): pass def refresh(self): self.__win.refresh() def erase(self): self.__win.erase() def win(self): return self.__win def select(self, select = None): if select is not None: self.__select = select return self.__select def win(self): return self.__win def show(self, show=None): if show is not None: self.__show = show return self.__show def update(self): pass def delete(self, delete=None): if delete is not None: self.__deleted = delete return self.__deleted class Column(object): def __init__(self, name, filter, width, duration=False, enable=True, sname=None): self.__name = name self.__filter = filter self.__width = width self.__duration = duration self.__enable = enable self.__sname = sname or name self.__valid = True DEBUG(self.__init__, "self.__name, self.__sname", ",".join([self.__name, self.__sname])) def __str__(self): return self.name() + " (" + self.sname() + ")" def __repr__(self): return self.name() def __eq__(self, obj): return isinstance(obj, Column) and self.name() == obj.name() def name(self): return self.__name def sname(self): return self.__sname def header(self): return self.__sname.center(self.__width).upper() def value(self, stat): def seconds(td): return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6; d = self.__filter(stat) if type(d) == type(0.0): div = (seconds(stat["timestamp"]) or 1.0) if self.__duration else 1.0 v = d / div if (v > 100): v = int(v) return str(v).center(self.__width)[:self.__width] return ("%.2f" % v).center(self.__width)[:self.__width] elif type(d) == type(0): div = (seconds(stat['time']) or 1.0) if self.__duration else 1.0 v = int(d / div) if (self.__sname == 'ni' or self.__sname == 'no'): return mem_str(v).center(self.__width)[:self.__width] return str(v).center(self.__width)[:self.__width] elif type(d) == type(''): return d.center(self.__width)[:self.__width] def enable(self, enable=None): if enable is not None: self.__enable = enable return self.__enable def valid(self, valid=None): if valid is not None: self.__valid = valid return self.__valid class MachineStatWidget(Widget): def __init__(self, name, parent, border=True): self.__name = name self.__border = border width = 48 Widget.__init__(self, 20, width, parent) def host(self): return self.__name.split(':')[0] def type_str(self): return self.__name.split(':')[1] def redraw(self): if self.__border: if self.select(): self.win().attron(curses.color_pair(1) | curses.A_BOLD) self.win().box() self.win().attroff(curses.color_pair(1) | curses.A_BOLD) else: self.win().box() self.win().addstr(0, 2, " " + self.__name + " ", curses.color_pair(3)) try: stat = oceanbase.machine_stat()[self.host()] except KeyError: return if self.__border: # cpu info self.win().addstr(1, 2, "%-7s%4s%%" % ("CPU", "100"), curses.color_pair(7) | curses.A_BOLD) # self.win().attron(curses.color_pair(8)) self.win().addstr(1, 17, "%-7s%4.1f%%" % ("nice:", float(stat['nice']) / stat['total'] * 100)) self.win().addstr(2, 2, "%-7s%4.1f%%" % ("user:", float(stat['user']) / stat['total'] * 100)) self.win().addstr(2, 17, "%-7s%4.1f%%" % ("iowait:", float(stat['iowait']) / stat['total'] * 100)) self.win().addstr(3, 2, "%-7s%4.1f%%" % ("sys:", float(stat['sys']) / stat['total'] * 100)) self.win().addstr(3, 17, "%-7s%4.1f%%" % ("irq:", float(stat['irq']) / stat['total'] * 100)) self.win().addstr(4, 2, "%-7s%4.1f%%" % ("idle:", float(stat['idle']) / stat['total'] * 100)) self.win().addstr(4, 17, "%-7s%4.1f%%" % ("sirq:", float(stat['softirq']) / stat['total'] * 100)) # self.win().attroff(curses.color_pair(8)) # load self.win().addstr(1, 32, "%-7s%6s" % ("Load", "x-core"), curses.color_pair(7) | curses.A_BOLD) # self.win().attron(curses.color_pair(8)) self.win().addstr(2, 32, "%-7s%6s" % ("1 min:", stat['load1'])) self.win().addstr(3, 32, "%-7s%6s" % ("5 min:", stat['load5'])) self.win().addstr(4, 32, "%-7s%6s" % ("15 min:", stat['load15'])) # self.win().attroff(curses.color_pair(8)) # mem info self.win().addstr(6, 2, "%-6s%7s" % ("Mem", mem_str(stat['MemTotal'])), curses.color_pair(7) | curses.A_BOLD) self.win().addstr(7, 2, "%-6s%7s" % ("used:", mem_str(stat['MemTotal'] - stat['MemFree']))) self.win().addstr(8, 2, "%-6s%7s" % ("free:", mem_str(stat['MemFree']))) # net info self.win().addstr(6, 17, "%-9s%9s%9s" % ("Network", 'Rx/s', 'Tx/s'), curses.color_pair(7) | curses.A_BOLD) for idx,item in enumerate(stat['net'].items()): if idx > 5: break self.win().addstr(7 + idx, 17, "%-9s%9s%9s" % (item[0], mem_str(item[1]['bytes_recv'] * pow(10, 6) / float(stat['time']), bit=True), mem_str(item[1]['bytes_sent'] * pow(10, 6) / float(stat['time']), bit=True))) # disk io info self.win().addstr(14, 2, "%-5s%6s%9s" % ("Disk I/O", 'In/s', 'Out/s'), curses.color_pair(7) | curses.A_BOLD) idx = 15 for item in stat['disk'].items(): if idx >= self.height() - 1: break if item[1]['rbytes'] <= 0 and item[1]['wbytes'] <= 0: continue self.win().addstr(idx, 2, "%-5s%9s%9s" % (item[0], mem_str(item[1]['rbytes'] * pow(10, 6) / float(stat['time'])), mem_str(item[1]['wbytes'] * pow(10, 6) / float(stat['time'])))) idx += 1 class ColumnWidget(Widget): def __init__(self, name, columns, parent, border=True): self.__check_column_valid(columns) self.__name = name self.__columns = columns self.__border = border enabled_columns = filter(lambda c: c.enable(), self.valid_columns()) width = len(" ".join([c.header() for c in enabled_columns])) + 2 # 2 padding width = max(width, 18) if border: width += 2 Widget.__init__(self, Global.WIDGET_HEIGHT, width, parent) def __check_column_valid(self, columns): for c in columns: try: c.value(oceanbase.sample) except KeyError: c.valid(False) def valid_columns(self): return filter(lambda c: c.valid(), self.__columns) def update(self): enabled_columns = filter(lambda c: c.enable(), self.valid_columns()) width = len(" ".join([c.header() for c in enabled_columns])) + 2 # 2 border + 2 padding if self.__border: width += 2 self.min_width(width) def redraw(self): def stat_count(): return oceanbase.stat_count() def latest(num): return oceanbase.latest(num) enabled_columns = filter(lambda c: c.enable(), self.valid_columns()) if self.__border: if self.select(): self.win().attron(curses.color_pair(1) | curses.A_BOLD) self.win().box() self.win().attroff(curses.color_pair(1) | curses.A_BOLD) else: self.win().box() if self.__name: self.win().addstr(0, 2, " " + self.__name + " ", curses.color_pair(3)) print_lines = min(stat_count(), self.height() - 3) self.win().addstr(1, 1, " " + " ".join([ c.header() for c in enabled_columns ]) + " ", curses.color_pair(2)) border_offset = 1 else: print_lines = min(stat_count(), self.height() - 1) self.win().addstr(0, 0, " " + " ".join([ c.header() for c in enabled_columns ]) + " ", curses.color_pair(2)) border_offset = 0 latest = latest(print_lines) li = [] for i in range(0, len(latest)): item = [] for c in enabled_columns: item.append(str(c.value(latest[i]))) li.append(" " + " ".join(item)) for i in range(0, len(li)): self.win().addstr(i + 1 + border_offset, border_offset, li[i]) class HeaderColumnWidget(Widget): def __init__(self, name, columns, parent, padding=0, get_header=None): self.__name = name self.__padding = padding self.__get_header = get_header Widget.__init__(self, Global.WIDGET_HEIGHT, 0, parent) self.__column_widget = ColumnWidget(None, columns, self.win(), border=False) self.min_width(self.__column_widget.min_width() + 2) self.resize(self.min_height(), self.min_width()) def redraw(self): if self.select(): self.win().attron(curses.color_pair(1)) self.win().box() self.win().attroff(curses.color_pair(1)) else: self.win().box() nline = 1 if self.__get_header and oceanbase.stat_count() > 0: header = self.__get_header(oceanbase.now()) string = "" for item in header.items(): item_str = ": ".join([item[0], str(item[1])]) if (len(string) + len(item_str) + 2 > self.min_width() - 2): string = string[2:] self.win().addstr(nline, 1, string.center(self.min_width() - 2)) string = "" nline += 1 string += " " + item_str if string: string = string[2:] self.win().addstr(nline, 1, string.center(self.min_width() - 2)) self.__set_padding(nline) else: # fix ob 0.4.1 wired bug self.__set_padding(0) self.__column_widget.redraw() if self.__name: self.win().addstr(0, 2, " " + self.__name + " ", curses.color_pair(3)) time = strftime(" %Y-%m-%d %T ") self.win().addstr(0, self.width() - len(time) - 2, time, curses.color_pair(3)) def resize(self, height=None, width=None): if height is not None and width is not None: Widget.resize(self, height, width) maxy, maxx = self.win().getmaxyx() self.__column_widget.resize(maxy - self.__padding - 2, maxx - 2) if height is None: height = self.__column_widget.min_height() + self.__padding + 2 self.resize(height, width) if width is None: width = self.__column_widget.min_width() + 2 self.min_width(width) self.resize(height, width) def move(self, y, x): self.win().mvderwin(y, x) self.__column_widget.win().mvderwin(self.__padding + 1, 1) def __set_padding(self, padding): self.min_height(padding + 2 + 2) maxy, maxx = self.win().getmaxyx() self.__padding = padding self.__column_widget.move(0, 0) self.__column_widget.resize(maxy - self.__padding - 2, maxx - 2) self.__column_widget.move(self.__padding + 1, 1) def update(self): self.__column_widget.update() def host(self): return self.__name.split(":")[0] def sql_port(self): return self.__name.split(":")[1] def column_widget(self): return self.__column_widget class StatusWidget(Widget): def __init__(self, parent): self.__parent = parent maxy, maxx = parent.getmaxyx() Widget.__init__(self, 2, maxx, parent) self.resize(2, maxx) self.move(maxy - 2, 0) self.win().bkgd(curses.color_pair(1)) def redraw(self): now = strftime("%Y-%m-%d %T") maxy, maxx = self.win().getmaxyx() tps = self.__get_tps() qps = self.__get_qps() svr_list = oceanbase.find_svr_list()["observer"][0] statstr = "HOST: %s:%d " % (Options.host, Options.port) statstr += "%s<%s> TPS: %-6d QPS: %-6d " % (oceanbase.app, oceanbase.get_current_tenant_name(), tps, qps) statstr += "SERVERS: %d" % (len(svr_list)) try: self.win().addstr(1, maxx - len(now) - 2, now) self.win().addstr(1, 2, statstr) self.win().hline(0, 0, curses.ACS_HLINE, 1024) except curses.error: pass def __seconds(self, td): return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6; def __get_tps(self): tid = str(oceanbase.get_current_tenant()) if oceanbase.stat_count() > 1: last = oceanbase.now() insert = sum([item["sql insert count"] for item in last[tid].values()]) replace = sum([item["sql replace count"] for item in last[tid].values()]) delete = sum([item["sql delete count"] for item in last[tid].values()]) update = sum([item["sql update count"] for item in last[tid].values()]) tps = float(insert + replace + delete + update) / self.__seconds(last['time']) return int(tps) else: return 0 def __get_qps(self): tid = str(oceanbase.get_current_tenant()) if oceanbase.stat_count() > 1: last = oceanbase.now() total = float(sum([item["sql select count"] for item in last[tid].values()])) qps = total / self.__seconds(last['time']) return int(qps) else: return 0 class HeaderWidget(Widget): def __init__(self, parent): maxy, maxx = parent.getmaxyx() Widget.__init__(self, 2, maxx, parent) self.__page = None self.win().bkgd(curses.color_pair(1)) def redraw(self): self.erase() if self.__page is None: try: self.win().addstr(0, 2, '1:Help 2:Gallery 3:SQL(ObServer)', curses.A_BOLD) self.win().hline(1, 0, curses.ACS_HLINE, 1024, curses.A_BOLD) except curses.error: pass else: maxy, maxx = self.win().getmaxyx() shown_num = len(self.__page.shown_widgets()) valid_num = len(self.__page.valid_widgets()) all_num = len(self.__page.all_widgets()) shown_str = 'Shown: %d / Valid: %d / Total: %d' % (shown_num, valid_num, all_num) try: self.win().addstr(0, 2, self.__page.title(), curses.A_BOLD) self.win().addstr(0, maxx - len(shown_str) - 4, shown_str, curses.A_BOLD) self.win().hline(1, 0, curses.ACS_HLINE, 1024, curses.A_BOLD) except curses.error: pass def switch_page(self, page): self.__page = page class MessageBox(object): def __init__(self, parent, msg): self.msg = msg self.parent = parent maxy, maxx = parent.getmaxyx() width = min(maxx - 20, len(msg)) + 10 height = 5 y = (maxy - height) / 2 x = (maxx - width) / 2 self.win = parent.derwin(height, width, y, x) self.win.erase() self.win.attron(curses.color_pair(1)) self.win.box() self.win.attroff(curses.color_pair(1)) self.win.addstr(2, 4, self.msg, curses.color_pair(0)) def run(self, anykey=False): res = True while (1): c = self.win.getch() if anykey: break elif c == ord('y'): res = True break elif c == ord('n'): res = False break elif c == ord('q'): res = False break self.win.erase() self.win.refresh() return res class PopPad(Widget): def __init__(self, name, items, parent, **args): self.__offset_x = 0 self.__offset_y = 0 self.__name = name maxy, maxx = parent.getmaxyx() width = maxx - 100 height = 10 Widget.__init__(self, height, width, parent) self.resize(height, width) y = (maxy - height - self.__offset_y) / 2 x = (maxx - width - self.__offset_x) / 2 self.mvwin(self.__offset_y + y, self.__offset_x + x) self.move(self.__offset_y + y, self.__offset_x + x) def __do_key(self): ch = self.win().getch() stopFlg = False if ch in (ord("q"), ord("Q")): stopFlg = True def __redraw(self): self.win().erase() self.win().attron(curses.color_pair(1)) self.win().box() self.win().attroff(curses.color_pair(1)) self.win().addstr(0, 2, ' %s ' % self.__name, curses.color_pair(3)) def run(self): self.__redraw() while(True): if self.__do_key(): self.win().erase() self.win().refresh() return self.__redraw() self.win().refresh() class SelectionBox(Widget): __padding_left = 5 __padding_top = 0 __offset_y = 0 __offset_x = 0 __chr_list = 'abcdefghimorstuvwxyzABCDEFGHIMORSTUVWXYZ0123456789' __hot_key = False def __init__(self, name, items, parent, **args): self.__index = 0 self.__name = name self.items = items self.parent = parent self.__start_idx = 0 self.__stop_idx = 0 s = list(self.__chr_list) shuffle(s) self.__chr_list = ''.join(s) self.__offset_y = args.get('offset_y', self.__offset_y) self.__offset_x = args.get('offset_x', self.__offset_x) self.__hot_key = args.get('hot_key', self.__hot_key) maxy, maxx = parent.getmaxyx() width = max(len(name) + 6, max([len(it) + 5 for it in items])) height = min(len(items) + 2, maxy - self.__offset_y) Widget.__init__(self, height, width, parent) self.resize(height, width) y = (maxy - height - self.__offset_y) / 2 x = (maxx - width - self.__offset_x) / 2 self.mvwin(self.__offset_y + y, self.__offset_x + x) self.move(self.__offset_y + y, self.__offset_x + x) self.win().nodelay(1) self.win().timeout(0) self.win().keypad(1) self.__stop_idx = self.__start_idx + self.height() - 2 def __do_key(self): ch = self.win().getch() stop = False if self.__fm is not None and ch != -1: self.__fm() self.__fm = None if ch in [ ord('j'), ord('\t'), ord('n'), ord('J'), ord('N'), curses.KEY_DOWN ]: if self.__index == len(self.items) - 1: self.__start_idx = 0 self.__stop_idx = self.__start_idx + self.height() - 2 self.__index = 0 elif self.__index == self.__stop_idx - 1: self.__start_idx = self.__start_idx + 1 self.__stop_idx = self.__stop_idx + 1 self.__index = self.__index + 1 else: self.__index = self.__index + 1 elif ch in [ ord('k'), ord('K'), ord('p'), ord('P'), curses.KEY_UP ]: if self.__index == 0: self.__start_idx = max(0, len(self.items) - self.height() + 2) self.__stop_idx = self.__start_idx + self.height() - 2 self.__index = len(self.items) - 1 elif self.__index == self.__start_idx: self.__start_idx = self.__start_idx - 1 self.__stop_idx = self.__stop_idx - 1 self.__index = self.__index - 1 else: self.__index = self.__index - 1 elif ch in [ ord('\n'), ord(' ') ]: stop = True elif ch in [ ord('q'), ord('Q') ]: self.__index = -1 stop = True elif not self.__hot_key: pass elif ch in [ ord(c) for c in self.__chr_list ]: self.__index = [ ord(c) for c in self.__chr_list ].index(ch) + self.__start_idx stop = True return stop def __redraw(self): self.win().erase() self.win().attron(curses.color_pair(1)) self.win().box() self.win().attroff(curses.color_pair(1)) self.win().addstr(0, 2, ' %s ' % self.__name, curses.color_pair(3)) for idx, item in enumerate(self.items): if idx < self.__start_idx or idx >= self.__stop_idx: continue if not self.__hot_key: line = item.center(self.width() - 2) elif idx - self.__start_idx < len(self.__chr_list): pref = ' %s)' % self.__chr_list[idx - self.__start_idx] line = pref + item.center(self.width() - 5) else: pref = ' ' line = pref + item.center(self.width() - 5) flag = 0 if idx == self.__index: flag = curses.A_BOLD | curses.color_pair(5) self.win().addstr(idx - self.__start_idx + self.__padding_top + 1, 1, item, flag) def run(self, first_movement=None): self.__fm = first_movement res = True while (1): if self.__do_key(): self.win().erase() self.win().refresh() return self.__index self.__redraw() self.win().refresh() class ColumnCheckBox(Widget): __padding_left = 5 __padding_top = 0 __margin_top = 4 __margin_bottom = 4 __offset_y = 0 __offset_x = 0 def __init__(self, name, items, parent, **args): self.__index = 0 self.__name = name self.items = items self.parent = parent self.__start_idx = 0 self.__stop_idx = 0 self.__margin_top = args.get('marging_top', self.__margin_top) self.__margin_bottom = args.get('marging_bottom', self.__margin_bottom) self.__offset_y = args.get('offset_y', self.__offset_y) self.__offset_x = args.get('offset_x', self.__offset_x) maxy, maxx = parent.getmaxyx() width = max(len(name) + 6, max([len(str(it)) + 9 for it in items])) height = min(len(items) + 2, maxy - self.__offset_y - self.__margin_top - self.__margin_bottom) Widget.__init__(self, height, width, parent) self.resize(height, width) y = (maxy - height - self.__offset_y) / 2 x = (maxx - width - self.__offset_x) / 2 self.mvwin(self.__offset_y + y, self.__offset_x + x) self.move(self.__offset_y + y, self.__offset_x + x) self.win().nodelay(1) self.win().timeout(0) self.win().keypad(1) self.__stop_idx = self.__start_idx + self.height() - 2 def __do_key(self): ch = self.win().getch() stop = False if self.__fm is not None and ch != -1: self.__fm() self.__fm = None if ch in [ ord('j'), ord('\t'), ord('n'), ord('J'), ord('N'), curses.KEY_DOWN ]: if self.__index == len(self.items) - 1: self.__start_idx = 0 self.__stop_idx = self.__start_idx + self.height() - 2 self.__index = 0 elif self.__index == self.__stop_idx - 1: self.__start_idx = self.__start_idx + 1 self.__stop_idx = self.__stop_idx + 1 self.__index = self.__index + 1 else: self.__index = self.__index + 1 elif ch in [ ord('k'), ord('K'), ord('p'), ord('P'), curses.KEY_UP ]: if self.__index == 0: self.__start_idx = max(0, len(self.items) - self.height() + 2) self.__stop_idx = self.__start_idx + self.height() - 2 self.__index = len(self.items) - 1 elif self.__index == self.__start_idx: self.__start_idx = self.__start_idx - 1 self.__stop_idx = self.__stop_idx - 1 self.__index = self.__index - 1 else: self.__index = self.__index - 1 elif ch in [ ord(' ') ]: self.items[self.__index].enable(not self.items[self.__index].enable()) elif ch in [ ord('\n') ]: stop = True elif ch in [ ord('q'), ord('Q') ]: self.__index = -1 stop = True return stop def __redraw(self): self.win().erase() self.win().attron(curses.color_pair(1)) self.win().box() self.win().attroff(curses.color_pair(1)) self.win().addstr(0, 2, ' %s ' % self.__name, curses.color_pair(3)) for idx, item in enumerate(self.items): if idx < self.__start_idx or idx >= self.__stop_idx: continue pref = ' [%s]' % (item.enable() and 'X' or ' ') line = pref + str(item).center(self.width() - 6) flag = 0 if idx == self.__index: flag = curses.A_BOLD | curses.color_pair(5) self.win().addstr(idx - self.__start_idx + self.__padding_top + 1, 1, line, flag) def run(self, first_movement=None): self.__fm = first_movement res = True while (1): if self.__do_key(): self.win().erase() self.win().refresh() return self.__index self.__redraw() self.win().refresh() if self.__index == -1: return [] else: return self.items class InputBox(Widget): def __init__(self, parent, prompt="Password", password=False, width=30): self.__index = 0 self.__name = "name" self.__offset_x = 0 self.__offset_y = 0 self.__password = password height = 3 win = curses.newwin(0, 0, 0, 0) Widget.__init__(self, height, width, win, True) self.resize(height, width) maxy, maxx = parent.getmaxyx() y = (maxy - height - self.__offset_y) / 2 x = (maxx - width - self.__offset_x) / 2 self.mvwin(y, x) win.box() win.refresh() self.__prompt = prompt + ": " win.addstr(1, 1, self.__prompt) win.move(1, 1 + len(self.__prompt)) self.__textbox = curses.textpad.Textbox(win) self.__result = "" def validator(self, ch): y, x = self.win().getyx() if ch == 127: new_x = max(x - 1, len(self.__prompt) + 1) self.win().delch(y, new_x) self.win().insch(' ') self.win().move(y, new_x) self.__result = self.__result[:-1] return 0 elif ch == 7 or ch == 10: # submit return 7 elif x == self.width() - 2: return 0 elif ch < 256: self.__result += chr(ch) if self.__password: return ord('*') else: return ch def run(self): curses.noecho() curses.curs_set(1) self.__textbox.edit(self.validator) curses.curs_set(0) return self.__result class GalleryPage(Page): def __add_widgets(self): tidf = lambda: str(oceanbase.get_current_tenant()) DEBUG(self.__add_widgets, "cur_tenant_id", tidf()) time_widget = ColumnWidget("TIME-TENANT", [ Column("timestamp", lambda stat: stat["timestamp"].strftime("%H:%m:%S"), 10, True), Column("tenant", lambda stat: tidf(), 10, True ), ], self.win()) sql_count_widget = ColumnWidget("SQL COUNT", [ Column("sel", lambda stat: sum([item["sql select count"] for item in stat[tidf()].values()]), 6, True), Column("ins", lambda stat: sum([ item["sql insert count"] for item in stat[tidf()].values() ]), 6, True), Column("rep", lambda stat: sum([ item["sql replace count"] for item in stat[tidf()].values() ]), 6, True), Column("del", lambda stat: sum([ item["sql delete count"] for item in stat[tidf()].values() ]), 6, True), Column("upd", lambda stat: sum([ item["sql update count"] for item in stat[tidf()].values() ]), 6, True), Column("cmt", lambda stat: sum([ item["trans commit count"] for item in stat[tidf()].values() ]), 6, True), Column("fail", lambda stat: sum([ item["sql fail exec count"] + item["sql fail query count"] for item in stat[tidf()].values() ]), 6, True) ], self.win()) sql_rt_widget = ColumnWidget("SQL RT", [ Column("select", lambda stat: (sum([ item["sql select time"] for item in stat[tidf()].values() ]) / float(sum([ item["sql select count"] for item in stat[tidf()].values() ]) or 1) / 1000), 8), Column("insert", lambda stat: (sum([ item["sql insert time"] for item in stat[tidf()].values() ]) / float(sum([ item["sql insert count"] for item in stat[tidf()].values() ]) or 1) / 1000), 8), Column("update", lambda stat: (sum([ item["sql update time"] for item in stat[tidf()].values() ]) / float(sum([ item["sql update count"] for item in stat[tidf()].values() ]) or 1) / 1000), 8), Column("replace", lambda stat: (sum([ item["sql replace time"] for item in stat[tidf()].values() ]) / float(sum([ item["sql replace count"] for item in stat[tidf()].values() ]) or 1) / 1000), 8), Column("delete", lambda stat: (sum([ item["sql delete time"] for item in stat[tidf()].values() ]) / float(sum([ item["sql delete count"] for item in stat[tidf()].values() ]) or 1) / 1000), 8), ], self.win()) net_rt_widget = ColumnWidget("NET RT", [ Column("net", lambda stat: (sum([ item["rpc net delay"] for item in stat[tidf()].values() ]) / float(sum([ item["rpc packet in"] for item in stat[tidf()].values() ]) or 1) / 1000), 8), Column("frame", lambda stat: (sum([ item["rpc net frame delay"] for item in stat[tidf()].values() ]) / float(sum([ item["rpc packet in"] for item in stat[tidf()].values() ]) or 1) / 1000), 8), ], self.win()) memory_widget = ColumnWidget("MEMORY", [ Column("active", lambda stat: mem_str(sum([item["active memstore used"] for item in stat[tidf()].values()])), 8), Column("total", lambda stat: mem_str(sum([item["total memstore used"] for item in stat[tidf()].values()])), 8), Column("freeze", lambda stat: mem_str(sum([item["major freeze limit"] for item in stat[tidf()].values()])), 8), Column("limit", lambda stat: mem_str(sum([item["memstore limit"] for item in stat[tidf()].values()])), 8) ], self.win()) tableapi_widget = ColumnWidget("TABLE API ROWS/SEC", [ Column("get", lambda stat: (sum([ item["multi retrieve rows"] for item in stat[tidf()].values() ]) + sum([ item["single retrieve execute count"] for item in stat[tidf()].values() ])), 6, True), Column("put", lambda stat: (sum([ item["multi insert_or_update rows"] for item in stat[tidf()].values() ]) + sum([ item["single insert_or_update execute count"] for item in stat[tidf()].values() ])), 6, True), Column("delete", lambda stat: (sum([ item["multi delete rows"] for item in stat[tidf()].values() ]) + sum([ item["single delete execute count"] for item in stat[tidf()].values() ])), 6, True), Column("insert", lambda stat: (sum([ item["multi insert rows"] for item in stat[tidf()].values() ]) + sum([ item["single insert execute count"] for item in stat[tidf()].values() ])), 6, True), Column("update", lambda stat: (sum([ item["multi update rows"] for item in stat[tidf()].values() ]) + sum([ item["single update execute count"] for item in stat[tidf()].values() ])), 6, True), Column("replace", lambda stat: (sum([ item["multi replace rows"] for item in stat[tidf()].values() ]) + sum([ item["single replace execute count"] for item in stat[tidf()].values() ])), 6, True), ], self.win()) self.add_widget(time_widget) self.add_widget(sql_count_widget) self.add_widget(sql_rt_widget) self.add_widget(net_rt_widget) self.add_widget(memory_widget) self.add_widget(tableapi_widget) def __init__(self, y, x, h, w, parent): Page.__init__(self, parent, Layout(), y, x, h, w) try: self.__add_widgets() except curses.error: pass def title(self): return "Gallery" def process_key(self, ch): if ch == ord('j'): pass else: Page.process_key(self, ch) DEV = file("/dev/null", "w") def DEBUG(*args): global DEV if Options.debug: print >> DEV, "%s in [%s] %s %s" % (time.asctime(), args[0].func_name, args[1], args[2]) class SQLPage(Page): def update_widgets(self): def observer_info(stat, ip): def try_add(name, key, wrapper): try: result[name] = wrapper(stat[oceanbase.get_current_tenant()][ip][key]) except Exception as e: # DEBUG(try_add, "exception", e) pass def try_add2(name, key1, key2, wrapper): try: result[name] = wrapper(stat[oceanbase.get_current_tenant()][ip][key1] / (stat[oceanbase.get_current_tenant()][ip][key1] + stat[oceanbase.get_current_tenant()][ip][key2]) or 1) DEBUG(try_add2, "wrapper", (stat[oceanbase.get_current_tenant()][ip][key1] + stat[oceanbase.get_current_tenant()][ip][key2])) DEBUG(try_add2, "wrapper", stat[oceanbase.get_current_tenant()][ip][key1]) except Exception as e: DEBUG(try_add2, "exception", e) pass result = dict() try_add("active sessions", "active sessions", count_str) try_add2("R-cache hit", "row cache hit", "row cache miss", percent_str) try_add2("L-cache hit", "location cache hit", "location cache miss", percent_str) try_add2("B-cache hit", "block cache hit", "block cache miss", percent_str) try_add2("BI-cache hit", "block index cache hit", "block index cache miss", percent_str) try_add2("BloomFilter cache hit", "bloom filter cache hit", "bloom filter cache miss", percent_str) return result svr = "observer" self.clear_widgets() DEBUG(self.update_widgets, "tenant.svr_list", oceanbase.get_tenant_svr()) for ms in oceanbase.get_tenant_svr()[svr]: ip = ms['ip'] port = ms['port'] DEBUG(observer_info, "oceanbase.get_current_tenant()", oceanbase.get_current_tenant()) ipport = ip + ":" + port f = ColumnFactory(svr, ipport) DEBUG(self.update_widgets, "f", f) widget = HeaderColumnWidget( '%s:%d' % (ip,int(port)), SQLPage.generate_columns(f), self.win(), get_header=lambda stat,ip=ip: observer_info(stat,ipport)) self.add_widget(widget) def __init__(self, y, x, h, w, parent): Page.__init__(self, parent, Layout(), y, x, h, w) try: self.update_widgets() except curses.error: pass def select_columns(self): if len(self.all_widgets()) > 0: all_widgets = [hc_widget.column_widget() for hc_widget in self.all_widgets()] columns = all_widgets[0].valid_columns() i = ColumnCheckBox("Select Columns", columns, self.parent()).run() for w in all_widgets: for idx in range(0, len(columns)): w.valid_columns()[idx].enable(columns[idx].enable()) w.update() [hc_widget.resize() for hc_widget in self.all_widgets()] self.rearrange() def enable_column_group(self, items): # enable table api if len(self.all_widgets()) > 0: all_widgets = [hc_widget.column_widget() for hc_widget in self.all_widgets()] columns = all_widgets[0].valid_columns() DEBUG(self.enable_column_group, "column count", len(columns)) enable_columns = [] for idx in range(0, len(columns)): enable_columns.append(0) for idx in items: enable_columns[idx] = 1 DEBUG(self.enable_column_group, "enable idx", idx) for w in all_widgets: for idx in range(0, len(columns)): w.valid_columns()[idx].enable(enable_columns[idx]==1) w.update() [hc_widget.resize() for hc_widget in self.all_widgets()] self.rearrange() def process_key(self, ch): if (len(self.shown_widgets())) > 0: w = self.selected_widget() else: return if ch == ord('m'): curses.endwin() oceanbase.mysql() curses.doupdate() elif ch == ord('O') or ch == ord('o'): like_str = InputBox(self.win(), prompt="LIKE STR").run() oceanbase.show_sql_result("select name,value from __all_virtual_sys_parameter_stat where svr_ip='%s' and svr_type='mergeserver' and name like '%s' order by name" % (w.host(), like_str)) elif Options.env == 'online' and ch == ord('l'): cmd = "ssh -t %s 'less oceanbase/log/mergeserver.log'" % w.host() curses.endwin() os.system(cmd) curses.doupdate() elif ch == ord("="): self.select_columns() elif ch == ord('A') or ch == ord('a'): # table api QPS self.enable_column_group([16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38]) elif ch == ord('B') or ch == ord('b'): # table api RT self.enable_column_group([17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39]) elif ch == ord('S') or ch == ord('s'): # SQL self.enable_column_group(range(13)) else: Page.process_key(self, ch) def title(self): return "ServerPage" @staticmethod def generate_columns(f): enable_sql_columns = True enable_table_api_columns = False enable_table_api_rt_columns = False return [ #default(SQL) f.count("Sql Select Count", "ssc", "sql select count", enable=enable_sql_columns), f.time("Sql Select Time", "ssrt", "sql select time", "sql select count", enable=enable_sql_columns), f.count("Sql Update Count", "suc", "sql update count", enable=enable_sql_columns), f.time("Sql Update Time", "surt", "sql update time", "sql update count", enable=enable_sql_columns), f.count("Sql Insert Count", "sic", "sql insert count", enable=enable_sql_columns), f.time("Sql Insert Time", "sirt", "sql insert time", "sql insert count", enable=enable_sql_columns), f.count("Sql Delete Count", "sdc", "sql delete count", enable=enable_sql_columns), f.time("Sql Delete Time", "sdrt", "sql delete time", "sql delete count", enable=enable_sql_columns), f.count("Trans Commit Count", "tcc", "trans commit count", enable=enable_sql_columns), f.time("Trans Commit Time", "tcrt", "trans commit time", "trans commit count", enable=enable_sql_columns), f.count("Sql Execute Local Count", "selc", "sql local count", enable=enable_sql_columns), f.count("Sql Execute Remote Count", "serc", "sql remote count", enable=enable_sql_columns), f.count("Sql Execute Distributed Count", "sedc", "sql distributed count", enable=enable_sql_columns), f.count("Inner SQL Connection Execute Count", "iscec", "inner sql connection execute count"), f.time("Inner SQL Connection Execute Time", "iscet", "inner sql connection execute time", "inner sql connection execute count"), # Table API f.count("Table API table api login count", "login", "table api login count"), f.count("Table API single retrieve execute count", "get", "single retrieve execute count", enable=enable_table_api_columns), f.time("Table API single retrieve execute time", "g-rt", "single retrieve execute time", "single retrieve execute count", enable=enable_table_api_rt_columns), f.count("Table API single insert execute count", "insert", "single insert execute count", enable=enable_table_api_columns), f.time("Table API single insert execute time", "i-rt", "single insert execute time", "single insert execute count", enable=enable_table_api_rt_columns), f.count("Table API single delete execute count", "delete", "single delete execute count", enable=enable_table_api_columns), f.time("Table API single delete execute time", "d-rt", "single delete execute time", "single delete execute count", enable=enable_table_api_rt_columns), f.count("Table API single update execute count", "update", "single update execute count", enable=enable_table_api_columns), f.time("Table API single update execute time", "u-rt", "single update execute time", "single update execute count", enable=enable_table_api_rt_columns), f.count("Table API single insert_or_update execute count", "put", "single insert_or_update execute count", enable=enable_table_api_columns), f.time("Table API single insert_or_update execute time", "p-rt", "single insert_or_update execute time", "single insert_or_update execute count", enable=enable_table_api_rt_columns), f.count("Table API single replace execute count", "replace", "single replace execute count", enable=enable_table_api_columns), f.time("Table API single replace execute time", "r-rt", "single replace execute time", "single replace execute count", enable=enable_table_api_rt_columns), f.count("Table API multi retrieve execute count", "mget", "multi retrieve execute count", enable=enable_table_api_columns), f.time("Table API multi retrieve execute time", "mg-rt", "multi retrieve execute time", "multi retrieve execute count", enable=enable_table_api_rt_columns), f.count("Table API multi insert execute count", "minsert", "multi insert execute count", enable=enable_table_api_columns), f.time("Table API multi insert execute time", "mi-rt", "multi insert execute time", "multi insert execute count", enable=enable_table_api_rt_columns), f.count("Table API multi delete execute count", "mdelete", "multi delete execute count", enable=enable_table_api_columns), f.time("Table API multi delete execute time", "md-rt", "multi delete execute time", "multi delete execute count", enable=enable_table_api_rt_columns), f.count("Table API multi update execute count", "mupdate", "multi update execute count", enable=enable_table_api_columns), f.time("Table API multi update execute time", "mu-rt", "multi update execute time", "multi update execute count", enable=enable_table_api_rt_columns), f.count("Table API multi insert_or_update execute count", "mput", "multi insert_or_update execute count", enable=enable_table_api_columns), f.time("Table API multi insert_or_update execute time", "mp-rt", "multi insert_or_update execute time", "multi insert_or_update execute count", enable=enable_table_api_rt_columns), f.count("Table API multi replace execute count", "mreplace", "multi replace execute count", enable=enable_table_api_columns), f.time("Table API multi replace execute time", "mr-rt", "multi replace execute time", "multi replace execute count", enable=enable_table_api_rt_columns), f.count("Table API batch retrieve execute count", "bget", "batch retrieve execute count"), f.time("Table API batch retrieve execute time", "bg-rt", "batch retrieve execute time", "batch retrieve execute count"), f.count("Table API batch hybrid execute count", "bhybrid", "batch hybrid execute count", enable=enable_table_api_columns), f.time("Table API batch hybrid execute time", "bh-rt", "batch hybrid execute time", "batch hybrid execute count", enable=enable_table_api_rt_columns), f.count("Table API multi retrieve rows", "rget", "multi retrieve rows"), f.time("Table API multi retrieve execute time/row", "rg-rt", "multi retrieve execute time", "multi retrieve rows"), f.count("Table API multi insert rows", "rinsert", "multi insert rows"), f.time("Table API multi insert execute time/row", "ri-rt", "multi insert execute time", "multi insert rows"), f.count("Table API multi delete rows", "rdelete", "multi delete rows"), f.time("Table API multi delete execute time/row", "rd-rt", "multi delete execute time", "multi delete rows"), f.count("Table API multi update rows", "rupdate", "multi update rows"), f.time("Table API multi update execute time/row", "ru-rt", "multi update execute time", "multi update rows"), f.count("Table API multi insert_or_update rows", "rput", "multi insert_or_update rows"), f.time("Table API multi insert_or_update execute time", "rp-rt", "multi insert_or_update execute time", "multi insert_or_update rows"), f.count("Table API multi replace rows", "rreplace", "multi replace rows"), f.time("Table API multi replace execute time/row", "rr-rt", "multi replace execute time", "multi replace rows"), f.count("Table API batch retrieve rows", "rbget", "batch retrieve rows"), f.time("Table API batch retrieve execute time/row", "rbg-rt", "batch retrieve execute time", "batch retrieve rows"), f.count("Table API batch hybrid rows", "rbhybrid", "batch hybrid insert_or_update rows"), f.time("Table API batch hybrid execute time/row", "rbh-rt", "batch hybrid execute time", "batch hybrid insert_or_update rows"), #inner packet f.count("RPC Packet In", "rpci", "rpc packet in"), f.count("RPC Packet In Bytes", "rpci(B)", "rpc packet in bytes"), f.count("RPC Packet Out", "rpco", "rpc packet out"), f.count("RPC Packet Out Bytes", "rpco(B)", "rpc packet out bytes"), f.count("RPC Deliver Fail", "rpc fail", "rpc deliver fail"), f.time("RPC Net Delay", "rpc delay", "rpc net delay"), f.count("RPC Net Frame Delay", "rpc frame delay", "rpc net frame delay"), #outer packet f.count("MySQL Packet In", "mpci", "mysql packet in"), f.count("MySQL Packet In Bytes", "mpci(B)", "mysql packet in bytes"), f.count("MySQL Packet Out", "mpco", "mysql packet out"), f.count("MySQL Packet Out Bytes", "mpco(B)", "mysql packet out bytes"), f.count("MySQL Deliver Fail", "mdf", "mysql deliver fail"), #request queue f.count("Request Enqueue Count", "enqueue", "request enqueue count"), f.count("Request Dequeue Count", "dequeue", "request dequeue count"), f.time("Request Queue Time", "QT", "request queue time"), #transmition f.time("Trans Commit Log Time", "tclt", "trans commit log time"), f.count("Trans Commit Log Sync Count", "tclsc(sync)", "trans commit log sync count"), f.count("Trans Commit LOg Submit Count", "tclsc(submit)", "trans commit log submit count"), f.count("Trans System Trans Count", "tstc", "trans system trans count"), f.count("Trans User Trans Count", "tutc", "trans user trans count"), f.count("Trans Start Count", "tsc", "trans start count"), f.count("Trans Total Used Time", "ttut", "trans total used time"), f.count("Trans Commit Count", "tcc", "trans commit count"), f.time("Trans Commit Time", "tct", "trans commit time"), f.count("Trans Rollback Count", "trc", "trans rollback count"), f.time("Trans Rollback Time", "trt", "trans rollback time"), f.count("Trans Timeout Count", "ttc", "trans timeout count"), f.count("Trans Single Partition Count", "tspc", "trans single partition count"), #cache #FIXME f.count("Row Cache Hit", "rch", "row cache hit"), f.count("Row Cache Miss", "rcm", "row cache miss"), f.count("Block Index Cache Hit", "bich", "block index cache hit"), f.count("Block Index Cache Miss", "bicm", "block index cache miss"), f.count("Bloom Filter Cache Hit", "bfch", "bloom filter cache hit"), f.count("Bloom Filter Cache Miss", "bfcm", "bloom fileter cache miss"), f.count("Bloom Filter Filts", "bff", "bloom filter filts"), f.count("Bloom Filter Passes", "bfp", "bloom filter passes"), f.count("Block Cache Hit", "bch", "block cache hit"), f.count("Block Cache Miss", "bcm", "block cache miss"), f.count("Location Cache Hit", "lch", "location cache hit"), f.count("Location Cache Miss", "lcm", "location cache miss"), #resources f.count("Active Sessions", "as", "active sessions"), f.count("IO Read Count", "iorc", "io read count"), f.count("IO Read Delay", "iord", "io read delay"), f.count("IO Read Bytes", "ior(B)", "io read bytes"), f.count("IO Write Count", "iowc", "io write count"), f.count("IO Write Delay", "iowd", "io write delay"), f.count("IO Write Bytes", "iow(B)", "io write delay"), f.count("Memstore Scan Count", "msc", "memstore scan count"), f.count("Memstore Scan Succ Count", "mssc", "memstore scan succ count"), f.count("Memstore Scan Fail Count", "msfc", "memstore scan fail count"), f.count("Memstore Get Count", "mgc", "memstore get count"), f.count("Memstore Get Succ Count", "mgsc", "memstore get succ count"), f.count("Memstore Get Fail Count", "mgfc", "memstore get fail count"), f.count("Memstore Apply Count", "mac", "memstore apply count"), f.count("Memstore Apply Succ Count", "masc", "memstore apply succ count"), f.count("Memstore Apply Fail Count", "mafc", "memstore apply fail count"), f.count("Memstore Row Count", "mrc", "memstore row count"), f.time("Memstore Get Time", "mgt", "memstore get time"), f.time("Memstore Scan Time", "mst", "memstore scan time"), f.time("Memstore Apply Time", "mat", "memstore apply time"), f.count("Memstore Read Lock Succ Count", "mrlsc", "memstore read lock succ count"), f.count("Memstore Read Lock Fail Count", "mrlfc", "memstore read lock fail count"), f.count("Memstore Write Lock Succ Count", "mwlsc", "memstore write lock succ count"), f.count("Memstore Write Lock Fail Count", "mwlfc", "memstore write lock fail count"), f.time("Memstore Wait Write Lock Time", "mwwlt", "memstore wait write lock time"), f.time("Memstore Wait Read Lock Time", "mwrlt", "memstore wait read lock time"), f.count("IO Read Micro Index Count", "iormic", "io read micro index count"), f.count("IO Read Micro Index Bytes", "iormib", "io read micro index bytes"), f.count("IO Prefetch Micro Block Count", "iopmbc", "io prefetch micro block count"), f.count("IO Prefetch Micro Block Bytes", "iopmbb", "io prefetch micro block bytes"), f.count("IO Read Uncompress Micro Block Count", "iorumbc", "io read uncompress micro block count"), f.count("IO Read Uncompress Micro Block Bytes", "iorumbb", "io read uncompress micro block bytes"), f.count("Active Memstore Used", "amu", "active memstore used"), f.count("Total Memstore Used", "tmu", "total memstore used"), f.count("Major Freeze Trigger", "mft", "major freeze trigger"), f.count("Memstore Limit", "ml", "memstore limit"), f.count("Min Memory Size", "mms(min)", "min memory size"), f.count("Max Memory Size", "mms(max)", "max memory size"), f.count("Memory Usage", "mu", "memory usage"), f.count("Min CPUS", "mc(min)", "min cpus"), f.count("Max CPUS", "mc(max)", "max cpus"), #meta f.count("Refresh Schema Count", "rsc", "refresh schema count"), f.time("Refresh Schema Time", "rst", "refresh schema time"), f.count("Partition Table Operator Get Count", "ptogc", "partition table operator get count"), f.time("Partition Table Operator Get Time", "ptogt", "partition table operator get time"), #clog f.count("Submitted To Sliding Window Log Count", "sswlc", "submitted to sliding window log count"), f.count("Submitted To Sliding Window Log Size", "sswls", "submitted to sliding window log size"), f.count("Index Log Flushed Count", "ilfc", "index log flushed count"), f.count("Index Log Flushed Clog Size", "ilfcs", "index log flushed clog size"), f.count("Clog Flushed Count", "cfc", "clog flushed count"), f.count("Clog Flushed Size", "cfs", "clog flushed size"), f.count("Clog Read Count", "crc(read)", "clog read count"), f.count("Clog Read Size", "crs", "clog read size"), f.count("Clog Disk Read Size", "cdrs", "clog disk read size"), f.count("Clog Disk Read Count", "cdrc", "clog disk read count"), f.time("Clog Disk Read Time", "cdrt", "clog disk read time"), f.count("Clog Fetch Log Size", "cfls", "clog fetch log size"), f.count("Clog Fetch Log Count", "cflc", "clog fetch log size"), f.count("Clog Fetch Log By Location Size", "cflbls", "clog fetch log by location size"), f.count("Clog Fetch Log By Location Count", "cflblc", "clog fetch log by location count"), f.count("Clog Read Request Succ Size", "crrss", "clog read request succ size"), f.count("Clog Read Request Succ Count", "crrsc", "clog read request succ count"), f.count("Clog Read Request Fail Count", "crrfc", "clog read request fail count"), f.time("Clog Confirm Delay Time", "ccdt", "clog confirm delay time"), f.count("Clog Flush Task Generate Count", "cftgc", "clog flush task generate count"), f.count("Clog Flush Task Release Count", "cftrc", "clog flush task release count"), f.count("Clog RPC Delay Time", "crdt", "clog rpc delay time"), f.count("Clog RPC Count", "crc(rpc)", "clog rpc count"), f.count("Clog Non KV Cache Hit Count", "cnkchc", "clog non kv cache hit count"), f.time("Clog RPC Request Handle Time", "crrht", "clog rpc request handle time"), f.count("Clog RPC Request Count", "crrc", "clog rpc request count"), f.count("Clog Cache Hit Count", "cchc", "clog cache hit count")] class MachineStatPage(Page): def update_widgets(self): pass def title(self): return "Machine Stat" def __init__(self, y, x, h, w, parent): Page.__init__(self, parent, Layout(), y, x, h, w) class HelpPage(Page): def __init__(self, y, x, h, w, parent): Page.__init__(self, parent, Layout(), y, x, h, w) self.win().bkgd(curses.color_pair(4)) def redraw(self): nline = [0] # hack mutating outer variables for python2 def addline(x, line, attr=0): self.win().addstr(nline[0], x, line, attr) nline[0] += 1 def addkeys(keys): for key_item in keys: if 0 == len(key_item): string = "" else: string = " %-14s: %s" % key_item addline(4, string) nline[0] += 1 def addgroup(group_name, keys): addline(4, group_name, curses.color_pair(4) | curses.A_BOLD) addline(2, "----------------------------------------------", curses.color_pair(4) | curses.A_BOLD) addkeys(keys) try: Page.redraw(self) ob_keys = [('c', 'Switch between tenants')] addgroup("Global Keys - OceanBase", ob_keys) widget_keys = [('Tab','Select next widget'), ('m', 'Connect to oceanbase using mysql client'), ('j', 'ssh to selected host')] addgroup("Global Keys - Widget", widget_keys) pages_keys = [('1 F1', 'Help page'), ('2 F2', 'Gallery page'), ('3 F3', 'Observer page'), ('d', 'Delete selected widget'), ('R', 'Restore deleted widgets'), ('=', 'Filter Columns for ObServer Page'), ('a A', 'Show columns for Table API QPS'), ('b B', 'Show columns for Table API RT'), ('s S', 'Show columns for SQL QPS and RT'), ] addgroup("Global Keys - Page", pages_keys) test_keys = [('p', 'Messsage box tooltips')] addgroup("Global Keys - Test", test_keys) select_keys = [('DOWN TAB J N', 'Next item'), ('UP K P', 'Previous item'), ('SPC ENTER', 'Select current item'), ('Q q', 'Quit selection box')] addgroup("Global Keys - Selection Box", select_keys) system_keys = [('q', 'quit dooba')] addgroup("Global Keys - System", system_keys) support = [ ('Author', 'oceanbase'), ('Mail', ''), () ] addgroup("Support", support) except curses.error: pass def title(self): return 'Help' class BlankPage(Page): def __init__(self, y, x, h, w, parent): Page.__init__(self, parent, Layout(), y, x, h, w) def title(self): return 'Blank Page' class Dooba(object): def build_oceanbase(self): return ''' ___ ____ / _ \ ___ ___ __ _ _ __ | __ ) __ _ ___ ___ | | | |/ __/ _ \/ _` | \'_ \| _ \ / _` / __|/ _ \\ | |_| | (_| __/ (_| | | | | |_) | (_| \__ \ __/ \___/ \___\___|\__,_|_| |_|____/ \__,_|___/\___|''' def build_dooba(self): return ''' _ _ __| | ___ ___ | |__ __ _ / _` |/ _ \ / _ \| \'_ \ / _` | | (_| | (_) | (_) | |_) | (_| | \__,_|\___/ \___/|_.__/ \__,_|''' def __init_curses(self, win): self.stdscr = win self.stdscr.keypad(1) self.stdscr.nodelay(1) self.stdscr.timeout(0) self.maxy, self.maxx = self.stdscr.getmaxyx() curses.curs_set(0) curses.noecho() self.__term = curses.termname() self.__init_colors() def __init_colors(self): if curses.has_colors(): curses.use_default_colors() curses.init_pair(1, curses.COLOR_RED, -1) # header widget and status widget curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_GREEN) # column header curses.init_pair(3, curses.COLOR_MAGENTA, -1) # widget title curses.init_pair(4, curses.COLOR_YELLOW, -1) # help page color curses.init_pair(5, curses.COLOR_RED, curses.COLOR_CYAN) curses.init_pair(6, curses.COLOR_GREEN, -1) # column header curses.init_pair(7, curses.COLOR_WHITE, -1) # machine stat header # curses.init_pair(8, curses.COLOR_GRAY, -1) # machine stat text def __resize_term(self): self.maxy, self.maxx = self.stdscr.getmaxyx() try: self.help_w.move(0, 0) self.help_w.mvwin(0, 0) self.help_w.resize(2, self.maxx) self.stat_w.move(self.maxy - 2, 0) self.stat_w.resize(2, self.maxx) for page in self.__all_page: page.resize(self.maxy - 4, self.maxx) except curses.error: pass self.stdscr.erase() def __do_key(self): ch = self.__all_page[self.__page_index].getch() if ch == ord('q'): curses.endwin() return True elif ch >= ord('0') and ch <= ord('9'): index = ch - ord('0') if index > 0 and index < len(self.__all_page): self.__page_index = ch - ord('0') self.help_w.switch_page(self.__all_page[self.__page_index]) elif ch >= curses.KEY_F1 and ch <= curses.KEY_F9: self.__page_index = ch - curses.KEY_F1 self.help_w.switch_page(self.__all_page[self.__page_index]) elif ch == ord('\t'): self.__all_page[self.__page_index].select_next() elif ch == ord('w'): pagename = self.__all_page[self.__page_index].title() appname = oceanbase.app filename = "%s_%s.dooba.win.bz2" % (appname, pagename) tmpf = tempfile.TemporaryFile() self.stdscr.putwin(tmpf); tmpf.seek(0) f = bz2.BZ2File(filename, "w") f.write(tmpf.read()) f.close() tmpf.close() MessageBox(self.stdscr, "[ INFO ] save screen to %s done!" % filename).run(anykey=True) elif ch == ord('p'): MessageBox(self.stdscr, "[TEST]What's up? (- -)#").run() elif ch == ord('z'): items = [('check1 12344', False), ('check2 1', True), ('check3 124328432431', False)] CheckBox('Test check box', items, self.stdscr).run() # elif ch == ord('i'): # result = InputBox(self.stdscr).run() # MessageBox(self.stdscr, "[TEST] %s" % result).run(anykey=True) elif ch == ord('C'): dataid_bk = Options.dataid self.stdscr.erase() self.__show_logo() while True: if self.__select_dataid() < 0: Options.dataid = dataid_bk break elif self.__update_lms() and self.__select_cluster(): break if dataid_bk != Options.dataid: oceanbase.stop() oceanbase.start() for page in self.__all_page: page.update() elif ch == ord('c'): self.__select_cluster() elif ch == ord("t"): PopPad("Abbreviation", [], self.stdscr).run() self.__all_page[self.__page_index].process_key(ch) def __run(self): while (1): self.stdscr.erase() self.help_w.redraw() self.__all_page[self.__page_index].redraw() self.stat_w.redraw() if (curses.is_term_resized(self.maxy, self.maxx)): self.__resize_term() if self.__do_key(): break self.stdscr.refresh() sleep(0.05) def __select_cluster(self): if len(oceanbase.tenant) <= 1: idx = 0 else: DEBUG(self.__select_cluster, "tnt.selected", [tnt.selected for tnt in oceanbase.tenant]) idx = SelectionBox("Select Tenant", [ "[%s] %s " % ("*" if tnt.selected else " ", tnt.tenant_name) for tnt in oceanbase.tenant], self.stdscr).run() if True == oceanbase.tenant[idx].selected: pass else: tid = oceanbase.tenant[idx].tenant_id tname = oceanbase.tenant[idx].tenant_name oceanbase.switch_tenant(tid) DEBUG(self.__select_cluster, "global oceanbase", oceanbase) DEBUG(self.__select_cluster, "oceanbase.tenant", oceanbase.tenant[idx]) DEBUG(self.__select_cluster, "oceanbase.tenant[idx]", oceanbase.tenant[idx].selected) DEBUG(self.__select_cluster, "oceanbase.get_current_tenant()", oceanbase.get_current_tenant()) for page in self.__all_page: page.cur_tenant_id = tid page.update() DEBUG(self.__select_cluster, "page.cur_tenant_id", [" ".join([str(page.title()), page.cur_tenant_id]) for page in self.__all_page]) def __show_logo(self): self.stdscr.hline(7, 0, curses.ACS_HLINE, 1024, curses.A_BOLD | curses.color_pair(1)) self.stdscr.refresh() oceanbase_width = max([len(line) for line in self.build_oceanbase().split('\n')]) oceanbase_height = len(self.build_oceanbase().split('\n')) ob_win = curses.newwin(oceanbase_height + 1, oceanbase_width + 1, 0, 10) ob_win.addstr(self.build_oceanbase(), curses.A_BOLD | curses.color_pair(1)) ob_win.refresh() dooba_width = max([len(line) for line in self.build_dooba().split('\n')]) dooba_height = len(self.build_dooba().split('\n')) dooba_win = curses.newwin(dooba_height + 1, dooba_width + 1, 0, self.maxx - dooba_width - 10) dooba_win.addstr(self.build_dooba(), curses.A_BOLD | curses.color_pair(6)) dooba_win.refresh() def __cowsay(self, saying): cowsay = str(Cowsay(saying)) cowsay_width = max([len(line) for line in cowsay.split('\n')]) cowsay_height = len(cowsay.split('\n')) try: cowsay_win = curses.newwin(cowsay_height + 1, cowsay_width + 1, self.maxy - cowsay_height - 2, self.maxx - cowsay_width - 10) cowsay_win.addstr(cowsay, curses.color_pair(3)) cowsay_win.refresh() self.__cowsay_win = cowsay_win except curses.error: pass def __search_app(self, search_text, dataid_list): ret = [] for group in dataid_list: for dataid in dataid_list[group]: if search_text in dataid: ret.append(dataid) if not ret: ret.append("Could not Find App Name Contains: %s" % search_text) return ret def __select_dataid(self): def clear_cowsay(): try: self.__cowsay_win.erase() self.__cowsay_win.refresh() except AttributeError: pass dataid_list = ObConfigHelper().get_dataid_list() if len(dataid_list) == 0: MessageBox(self.stdcsr, "Can't fetch dataid list, please check your environment").run(anykey=True) curses.endwin() exit(1) idx = SelectionBox("Select OceanBase App Groups", dataid_list, self.stdscr, offset_y=8, hot_key=False).run(first_movement=clear_cowsay) tmp_k= "" if idx == -2: search_text = InputBox(self.stdscr, prompt="App Search").run() search_res = self.__search_app(search_text, dataid_list) idx = SelectionBox("Searching Results", search_res, self.stdscr, offset_y=8, hot_key=True).run(first_movement=clear_cowsay) Options.dataid = search_res[idx] oceanbase.update_dataid(Options.dataid) else: for seq, k in zip(range(len(dataid_list)), dataid_list): if seq == idx: tmp_k = k break idx = SelectionBox("Select OceanBase Data ID", dataid_list[tmp_k], self.stdscr, offset_y=8, hot_key=True).run(first_movement=clear_cowsay) if idx >= 0 and tmp_k in dataid_list.keys(): Options.dataid = dataid_list[tmp_k][idx] oceanbase.update_dataid(Options.dataid) return idx # def __select_dataid(self): # def clear_cowsay(): # try: # self.__cowsay_win.erase() # self.__cowsay_win.refresh() # except AttributeError: # pass # dataid_list = ObConfigHelper().get_dataid_list() # if len(dataid_list) == 0: # MessageBox(self.stdscr, "Can't fetch dataid list, plz check your environment!").run(anykey=True) # curses.endwin() # exit(1) # idx = SelectionBox('Select OceanBase dataID', # dataid_list, self.stdscr, offset_y=8, hot_key=True).run(first_movement=clear_cowsay) # if idx >= 0: # Options.dataid = dataid_list[idx] # oceanbase.update_dataid(Options.dataid) # return idx def __update_lms(self): return oceanbase.check_lms(self.__cowsay) def __obssh(self, dataid): #oceanbase.update_cluster_info() oceanbase.update_tenant_info() clusters = oceanbase.cluster ipmap = {} for clu in clusters: for svr in clu.svr_list: for s in clu.svr_list[svr]: ip = s['ip'] if ip not in ipmap: ipmap[ip] = {} if "server" not in ipmap[ip]: ipmap[ip]["server"] = [] ipmap[ip]["id"] = clu.id ipmap[ip]["role"] = clu.role ipmap[ip]["server"].append({'type': svr, 'role': s['role']}) def get_sign(ipmap): name_map = {'rootserver':'R', 'updateserver':'U', 'mergeserver':'M', 'chunkserver':'C'} sign = '' for svr in ipmap["server"]: s = name_map[svr['type']] if svr['role'] == 2: s = s.lower() sign += s sign = '<%d:%s>' % (ipmap["id"], ipmap["role"] == 1 and 'MASTR' or 'SLAVE') + ('%4s' % sign).replace(' ', '_') return sign def sort_cmp(l, r): cmp0 = cmp(l[1], r[1]) cmp1 = cmp(l[0], r[0]) if cmp0 != 0: return cmp0 else: return cmp1 iplist = sorted([(ip, get_sign(ipmap[ip])) for ip in ipmap.keys()], cmp=sort_cmp) idx = SelectionBox("select server (%s)" % dataid, [" %-16s | %14s " % (info[0], info[1]) for info in iplist], self.stdscr, hot_key=True).run() if idx >= 0: curses.endwin() oceanbase.ssh(iplist[idx][0]) curses.doupdate() return True return False def __run_obssh(self): if Options.dataid is None: while True: if self.__select_dataid() < 0: curses.endwin() exit(0) if not self.__update_lms(): continue while self.__obssh(Options.dataid): pass else: if not self.__update_lms(): exit(1) while self.__obssh(Options.dataid): pass else: exit(0) def __run_show_win_file(self): self.stdscr.erase() bzfh = bz2.BZ2File(Options.show_win_file) fh = tempfile.TemporaryFile() fh.write(bzfh.read()) fh.seek(0) bzfh.close() self.stdscr = curses.getwin(fh) self.stdscr.nodelay(0) self.stdscr.notimeout(1) fh.close() ch = self.stdscr.getch() curses.endwin() exit(0) def __init__(self, win): self.__all_page = [] self.__page_index = 2 self.__init_curses(win) minx = 80 if self.maxx < minx or self.maxy < 20: MessageBox(self.stdscr, "[ ERROR ] %dx%d is tooooo smalllll! %dx20 is at least..." % (self.maxx, self.maxy, minx)).run(anykey=True) return self.__select_cluster() oceanbase.update_tenant_info() oceanbase.start() self.__show_logo() #if Options.show_win_file: # self.__run_show_win_file() #elif Options.degradation: # self.__run_obssh() #elif Options.using_ip_port: # #oceanbase.update_cluster_info() # oceanbase.update_tenant_info() # self.__select_cluster() # oceanbase.start() #else: # if Options.dataid is None: # while True: # if self.__select_dataid() < 0: # curses.endwin() # exit(0) # if self.__update_lms() and self.__select_cluster(): # break # elif not self.__update_lms(): # curses.endwin() # print ("ERROR: update lms failed for [%s]" % Options.dataid) # exit(1) # elif not self.__select_cluster(): # exit(0) # oceanbase.start() self.stat_w = StatusWidget(self.stdscr) self.help_w = HeaderWidget(self.stdscr) self.__all_page.append(BlankPage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 0 self.__all_page.append(HelpPage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 1 self.__all_page.append(GalleryPage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 2 self.__all_page.append(SQLPage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 3 # self.__all_page.append(HistoryPage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 4 # self.__all_page.append(BianquePage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 5 # self.__all_page.append(MachineStatPage(2, 0, self.maxy-4, self.maxx, self.stdscr)) # 6 # self.__all_page.append(BlankPage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 7 # self.__all_page.append(BlankPage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 8 # self.__all_page.append(BlankPage(2, 0, self.maxy - 4, self.maxx, self.stdscr)) # 9 self.__run() class DoobaMain(object): '''NAME dooba - A curses powerful tool for OceanBase admin, more than a monitor SYNOPSIS dooba [OPTIONS] OPTIONS · --host=HOST, -h HOST Connect to OceanBase on the given host. · --port=PORT, -P PORT The TCP/IP port to use for connecting to OceanBase server. · --user=USER, -u USER The user to use for connecting to OceanBase server. · --password=PASSWORD, -p PASSWORD The password to use for connecting to OceanBase server. EXAMPLES ./dooba -h127.0.0.1 -P2800 -uroot ''' def __usage(self): print('Usage: dooba [-h|--host=HOST] [-P|--port=PORT] [-u|--user=USER] [-p|--password=PASSWORD] [--tenant=TENANT]') #print('Usage: dooba --show dooba_win_file') def __set_env(self): setlocale(LC_ALL, "en_US.UTF-8") environ['TERM'] = 'xterm' def __parse_options(self): try: opts, args = getopt(sys.argv[1:], '?dh:i:I:p:P:su:D', ['debug', 'help', 'host=', 'interval=', 'port=', 'password=', 'supermode', 'user=', 'dataid=', 'online', 'offline', 'machine-interval=', 'degradation', 'show=', 'daemon', 'http', 'start', 'stop', 'restart', 'http-port=', 'tenant=', 'tenant_id=']) except GetoptError as err: print str(err) # will print something like "option -a not recognized" self.__usage() exit(2) for o, v in opts: if o in ('-?', '--help'): print self.__doc__ exit(1) if o in ('-d', '--debug'): Options.debug = True elif o in ('-h', '--host'): Options.host = v Options.using_ip_port = True elif o in ('-P', '--port'): Options.port = int(v) Options.using_ip_port = True elif o in ('-u', '--user'): Options.user = v elif o in ('-p', '--password'): Options.password = v elif o in ('-s', '--supermode'): Options.supermode = True elif o in ('-i', '--interval'): Options.interval = float(v) elif o in ('-I', '--machine-interval'): Options.machine_interval = float(v) elif o in ('--dataid'): Options.dataid = v elif o in ('--online'): Options.env = 'online' elif o in ('--offline'): Options.env = 'offline' elif o in ('-D', '--degradation'): Options.degradation = True elif o in ('--show'): Options.show_win_file = v elif o in ('--http'): Options.http = True elif o in ('--start'): Options.daemon_action = 'start' elif o in ('--stop'): Options.daemon_action = 'stop' elif o in ('--restart'): Options.daemon_action = 'restart' elif o in ('--daemon'): Options.daemon = True elif o in ('--http-port'): Options.http_port = int(v) elif o in ('--tenant'): Options.tenant = v elif o in ('--tenant_id'): Options.tenant_id = int(v) else: assert False, 'unhandled option [%s]' & o return args def __ignore_signal(self): def signal_handler(signal, frame): pass signal.signal(signal.SIGINT, signal_handler) def __myprint(self, info): print info print self.__usage() def __init__(self): global oceanbase self.__set_env() if "print-config" in self.__parse_options(): print "".join(ObConfigHelper().get_config(Options.dataid)) ObConfigHelper().get_dataid_list() exit(0) if not Options.show_win_file: oceanbase = OceanBase(Options.dataid) if Options.using_ip_port: oceanbase.test_alive(do_false=self.__myprint) if Options.http: pid_file = '/tmp/dooba.%d.pid' % Options.http_port if Options.daemon: if Options.daemon_action == 'start': HTTPServerDaemon(pid_file).start() elif Options.daemon_action == 'stop': HTTPServerDaemon(pid_file).stop() elif Options.daemon_action == 'restart': HTTPServerDaemon(pid_file).restart() else: try: HTTPServerDaemon(pid_file).run() except KeyboardInterrupt: pass else: try: curses.wrapper(Dooba) except KeyboardInterrupt: pass class HTTPServerDaemon(Daemon): def run(self): #oceanbase.update_cluster_info() oceanbase.update_tenant_info() if (Options.tenant_id is not None): oceanbase.switch_tenant(Options.tenant_id) elif (Options.tenant is not None): oceanbase.switch_tenant(Options.tenant) oceanbase.start() server = BaseHTTPServer.HTTPServer(('', Options.http_port), WebRequestHandler) server.serve_forever() class JSONDateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (date, datetime)): return obj.isoformat() elif isinstance(obj, (timedelta)): return 'NULL' else: return json.JSONEncoder.default(self, obj) class WebRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): self.wfile.write('HTTP/1.1 200 OK\nContent-Type: text/html\n\n') result = {} latest = oceanbase.now() for svr in oceanbase.get_tenant_svr()['observer']: ip = svr['ip'] port = svr['port'] ipport = ip + ":" + port f = ColumnFactory('observer', ipport) columns = SQLPage.generate_columns(f) result["%s:%s"%(ip,port)] = {} for c in columns: v = c.value(latest) if type(v) == str: v = v.strip() result["%s:%s"%(ip,port)][c.name()] = v self.wfile.write(json.dumps(result, cls=JSONDateTimeEncoder)) def main(): DoobaMain() if __name__ == "__main__": DoobaMain() # # dooba ends here