Files
oceanbase/script/dooba/dooba
wangzelin.wzl 93a1074b0c patch 4.0
2022-10-24 17:57:12 +08:00

3182 lines
126 KiB
Python
Executable File

#!/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 <jcn@pith.org>
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