# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. # # 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; version 2 of the # License. # # 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; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA import platform import socket import threading import time import subprocess import os from mforms import App from workbench.db_utils import QueryError, MySQLError import wb_admin_ssh from wb_server_management import wbaOS from wb_common import Users from grt import log_info, log_error, log_warning, log_debug, log_debug2, log_debug3 _this_file = os.path.basename(__file__) try: import _subprocess except ImportError: pass #=============================================================================== class DataSource(object): def __init__(self, name, mon_be, widget): self.widget = None self.label_cb = None # method to create description self.calc_cb = None self.name = name self.mon_be = mon_be self.ctrl_be = mon_be.get_ctrl_be() if widget is not None: self.widget = widget[0] self.label_cb = widget[1] self.calc_cb = widget[2] def poll(self): pass #=============================================================================== class ShellDataSource(DataSource): cmds = None def __init__(self, name, cmds, mon_be, widget): DataSource.__init__(self, name, mon_be, widget) self.cmds = cmds def poll(self): results = [] result = self.ctrl_be.execute_filtered_command(self.cmds) if result[1] == 0: result = result[0].strip(" \r\t\n,:.") if result != "": try: result = float(result.replace(',','.')) except ValueError, e: print "Error! Shell source ", self.cmds, "returned wrong value. Expected int or float but got", result result = 0 if self.widget is not None: if self.calc_cb is not None: result = self.calc_cb(result) self.widget.set_value(result) if self.label_cb is not None: lbl = self.label_cb(result) self.ctrl_be.uitask(self.widget.set_description, lbl) #=============================================================================== class Source(object): def __init__(self, name, ctrl_be, widget, variables, calc): self.name = name self.ctrl_be = ctrl_be self.widget = widget[0] self.label_cb = widget[1] self.variables = variables self.calc = calc self.vars_set = 0 self.values = [0] * len(variables) self.vars_len = len(variables) def set_var(self, i, value): self.values[i] = value self.vars_set += 1 def calculate(self): result = None if self.vars_len <= self.vars_set: self.vars_set = 0 args = tuple(self.values) if self.calc is not None: result = self.calc(args) else: result = args[0] if result is not None and self.widget is not None: try: result = float(result) except ValueError, e: print("Error! Calculation returned returned wrong value. Expected int or float but got", result) result = 0.0 self.widget.set_value(result) if self.label_cb is not None: lbl = self.label_cb(result) self.ctrl_be.uitask(self.widget.set_description, lbl) #=============================================================================== class SqlDataSource(DataSource): stopped = 0 started = 1 skip_poll = 2 polling = 3 def __init__(self, sql, mon_be): DataSource.__init__(self, "sql_source", mon_be, None) self.sql = sql self.mon_be = mon_be self.sources = {} self.rev_sources = {} self.dbconn = None self.ctrl_be.add_me_for_event("server_started", self) self.ctrl_be.add_me_for_event("server_stopped", self) self.ctrl_be.add_me_for_event("shutdown", self) self.poll_control = self.stopped log_debug2('%s:%s.__init__()' % (_this_file, self.__class__.__name__), 'SqlDataSource created. poll_control = %i\n' % self.poll_control) def server_started_event(self): self.poll_control = self.started if self.dbconn: self.dbconn.disconnect() self.dbconn = None self.dbconn = self.ctrl_be.get_new_sql_connection(True) self.poll_control = self.polling log_debug2('%s:%s.server_started_event()' % (_this_file, self.__class__.__name__), 'SqlDataSource server started. poll_control = %i\n' % self.poll_control) def server_stopped_event(self): self.poll_control = self.skip_poll if self.dbconn: self.dbconn.disconnect() self.dbconn = None self.poll_control = self.stopped log_debug2('%s:%s.server_stopped_event()' % (_this_file, self.__class__.__name__), 'SqlDataSource server stopped. poll_control = %i\n' % self.poll_control) def shutdown_event(self): self.poll_control = self.skip_poll if self.dbconn: self.dbconn.disconnect() self.dbconn = None self.poll_control = self.stopped log_debug2('%s:%s.shutdown_event()' % (_this_file, self.__class__.__name__), 'SqlDataSource shutdown. poll_control = %i\n' % self.poll_control) def add_source(self, name, definition, widget): # Source is a dict which contains names of status variables, formula to calc # result, min and max values. Formula is a method(lambda) which gets params as # specified in the list of status_variables. For example: # add_source({'query' : ('Key_reads', 'Key_read_reqs'), 'calc' : lambda x: x[0] - Key_reads, x[1] - Key_read_reqs}) status_variables = definition['query'] # We need to store data we need, like names of status variables, calc function, and temporary data from status_variables # As data from show status is not parsed as a whole but row by row, we have to gather all status variables which are involved # in calc before calling calc. src = Source(name, self.mon_be.get_ctrl_be(), widget, status_variables, definition['calc']) self.sources[name] = src # Add reverse index of all status variables. Each status variable name may refer several # calc formulas, and each formula may have several variables. This rev index helps to find formulas # where status variable is used. Each entry in this reverse index contain reference to source tuple (see # above, where source tuple is added to self.sources) and also the entry has index of variable in formula for i, status_variable_name in enumerate(src.variables): rev_src = None if status_variable_name in self.rev_sources: rev_src = self.rev_sources[status_variable_name] else: rev_src = [] self.rev_sources[status_variable_name] = rev_src rev_src.append((src, i)) if 'min' in definition and 'max' in definition: widget[0].set_value_range(definition['min'], definition['max']) def poll(self): log_debug3('%s:%s.poll()' % (_this_file, self.__class__.__name__), 'SqlDataSource poll. poll_control = %i\n' % self.poll_control) if self.poll_control == self.polling and self.dbconn: result = None try: result = self.dbconn.executeQuery(self.sql) #ctrl_be from DataSource except QueryError, e: result = None if e.is_connection_error(): self.dbconn.disconnect() if e.is_error_recoverable(): self.dbconn = self.ctrl_be.get_new_sql_connection(False) if result is not None: while result.nextRow(): name = result.stringByName("Variable_name") if name in self.rev_sources: rev_src = self.rev_sources[name] value = float(result.stringByName("Value")) # rev_src contains list of sources and index of current variable in the sources for (src, i) in rev_src: src.set_var(i, value) res = src.calculate() #=============================================================================== class WBAWinRemoteStats(object): #----------------------------------------------------------------------------- def __init__(self, ctrl_be, server_profile, running, cpu_widget, mem_widget): self.ctrl_be = ctrl_be self.ssh = None self.cpu = 0 self.mem = 0 self.mtx = threading.Lock() self.running = running self.cpu_widget = cpu_widget self.mem_widget = mem_widget self.settings = server_profile self.remote_admin_enabled = self.settings.uses_ssh if not self.remote_admin_enabled: return self.ctrl_be.add_me_for_event("shutdown", self) #upload script. Get local name, open ftp session and upload to the directory # where mysql.ini is. self.script = None self.ssh = ctrl_be.open_ssh_session_for_monitoring() (dirpath, code) = self.ssh.exec_cmd("cmd /C echo %USERPROFILE%") # %APPDATA% is n/a for LocalService # which is a user sshd can be run dirpath = dirpath.strip(" \r\t\n") if code == 0 and dirpath is not None and dirpath != "%USERPROFILE%": script_path = App.get().get_resource_path("mysql_system_status_rmt.vbs") filename = "\"" + dirpath + "\\mysql_system_status_rmt.vbs\"" log_debug('%s:%s.__init__()' % (_this_file, self.__class__.__name__), 'Script local path is "%s". Will be uploaded to "%s"\n' % (script_path, filename) ) if script_path is not None and script_path != "": #print "Uploading file to ", filename try: f = open(script_path) self.ssh.exec_cmd("cmd /C echo. > " + filename) maxsize = 1800 cmd = "" for line in f: line = line.strip("\r\n") tline = line.strip(" \t") if len(tline) > 0: if tline[0] != "'": if len(cmd) > maxsize: self.ssh.exec_cmd("cmd /C " + cmd.strip(" &")) self.ssh.exec_cmd("cmd /C echo " + line + " >> " + filename) cmd = "" else: cmd += "echo " + line + " >> " + filename cmd += " && " if len(cmd) > 0: self.ssh.exec_cmd("cmd /C " + cmd.strip(" &")) cmd = "" self.script = "cscript //NoLogo " + filename + " /DoStdIn" #run ssh in a thread log_debug2('%s:%s.__init__()' % (_this_file, self.__class__.__name__), 'About to run "%s"\n' % self.script) self.chan = None self.out = "" self.read_thread = threading.Thread(target=self.ssh.exec_cmd, args=(self.script, Users.CURRENT, None, self.reader, 1, self.save_channel)) self.read_thread.setDaemon(True) self.read_thread.start() except IOError, e: self.ssh.close() self.ssh = None raise e else: print "Can't find a place to upload script dirpath='%s'"%dirpath #----------------------------------------------------------------------------- def shutdown_event(self): if self.ssh: self.running[0] = False try: if self.read_thread: self.read_thread.join() except: pass self.ssh.close() self.ssh = None #----------------------------------------------------------------------------- def save_channel(self, chan): self.chan = chan #----------------------------------------------------------------------------- def poll(self): value = self.get_mem() if value is not None: self.mem_widget[0].set_value(value) self.ctrl_be.uitask(self.mem_widget[0].set_description, "Mem: " + str(int(value)) + "%") value = self.get_cpu() if value is not None: self.cpu_widget[0].set_value(value / 100) self.ctrl_be.uitask(self.cpu_widget[0].set_description, "CPU: " + str(int(value)) + "%") #----------------------------------------------------------------------------- def parse_cpu(self, text): text = text.strip(" \r\t\n") value = 0.0 try: value = float(text) except ValueError: value = 0.0 try: self.mtx.acquire() self.cpu = value finally: self.mtx.release() #----------------------------------------------------------------------------- def parse_mem(self, text): text = text.strip(" \r\t\n") value = None try: value = float(text) except ValueError: value = None if value is not None: self.mtx.acquire() try: self.mem = value finally: self.mtx.release() log_debug3('%s:%s.parse_mem()' % (_this_file, self.__class__.__name__), 'Got mem stat value from remote script - "%r"\n' % value) #----------------------------------------------------------------------------- def reader(self, ssh_session): what = None out = "" timeouts = 12 while self.running[0]: # running is passed in list via "refernce" try: ch = ssh_session.recv(1) timeouts = 12 if ch == "C": what = self.parse_cpu elif ch == "M": what = self.parse_mem elif ch == "\r" or ch == "\n": if what is not None: what(out) what = None out = "" elif ch in "0123456789. ": out += ch elif ch == ",": out += "." else: what = None out = "" except socket.timeout: timeouts -= 1 if timeouts <= 0: ssh_session.close() raise Exception("Can't read from remote Windows script") log_debug('%s:%s.reader()' % (_this_file, self.__class__.__name__), 'Leaving monitor thread which polls remote windows\n') #----------------------------------------------------------------------------- def get_cpu(self): ret = "" self.mtx.acquire() ret = self.cpu self.mtx.release() return ret #----------------------------------------------------------------------------- def get_mem(self): ret = "" self.mtx.acquire() ret = self.mem self.mtx.release() return ret #=============================================================================== # Should have been derived from DataSource, but that is meaningless # This class starts thread which reads data from cscript in Windows # Also the class mimics DataSource. class WBAWinLocalStats(object): def __init__(self, ctrl_be, server_profile, running, cpu_widget, mem_widget): self.mtx = threading.Lock() self.cpu = 0 self.mem = 0 self.running = running self.cpu_widget = cpu_widget self.mem_widget = mem_widget self.ctrl_be = ctrl_be script_path = App.get().get_resource_path("mysql_system_status.vbs") self.read_thread = threading.Thread(target=self.read_from_script) self.read_thread.setDaemon(True) self.ctrl_be.add_me_for_event("shutdown", self) su = subprocess.STARTUPINFO() su.dwFlags |= _subprocess.STARTF_USESHOWWINDOW su.wShowWindow = _subprocess.SW_HIDE self.ch = subprocess.Popen("cscript //NoLogo " + script_path + " /DoStdIn", stdin = subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=su) self.read_thread.start() #----------------------------------------------------------------------------- def shutdown_event(self): if self.ch: try: self.ch.stdin.write("exit\r\n") self.ch.stdin.flush() except: pass #----------------------------------------------------------------------------- def poll(self): if self.running[0] is False: self.ch.stdin.write("exit\r\n") self.ch.stdin.flush() return self.ch.stdin.write("cpu\r\n") self.ch.stdin.flush() self.ch.stdin.write("mem\r\n") self.ch.stdin.flush() value = self.get_mem() try: value = float(value) except ValueError: log_debug2('%s:%s.poll()' % (_this_file, self.__class__.__name__), 'Can not convert mem value "%r" to float\n' % value) value = None if value is not None: self.mem_widget[0].set_value(value) self.ctrl_be.uitask(self.mem_widget[0].set_description, "Mem: " + str(int(value)) + "%") value = self.get_cpu() try: value = float(value) except ValueError: value = None if value is not None: self.cpu_widget[0].set_value(value/100) self.ctrl_be.uitask(self.cpu_widget[0].set_description ,"CPU: " + str(int(value)) + "%") #----------------------------------------------------------------------------- def read_from_script(self): line = "" while self.running[0]: c = self.ch.stdout.read(1) if c == ",": c = "." if c == "": break line += c if c == "\n": line = line.strip(" \r\t\n%") if len(line) == 0: print "Read empty string from script" # TODO: Probably need another way of informing. TBD else: if line[0] == 'C': self.mtx.acquire() self.cpu = line[2:] self.mtx.release() elif line[0] == 'M': self.mtx.acquire() self.mem = line[2:] self.mtx.release() line = "" log_debug2('%s:%s.read_from_script()' % (_this_file, self.__class__.__name__), 'WinLocalStats: line-"%s", cpu-"%s", mem-"%s"\n' % (line, self.cpu, self.mem) ) #----------------------------------------------------------------------------- def get_cpu(self): ret = "" self.mtx.acquire() ret = self.cpu self.mtx.release() return ret #----------------------------------------------------------------------------- def get_mem(self): ret = "" self.mtx.acquire() ret = self.mem self.mtx.release() return ret #=============================================================================== class WMIStats(object): def __init__(self, ctrl_be, server_profile, cpu_widget, mem_widget): if not hasattr(ctrl_be.server_control, 'wmi'): raise Exception("Current profile has no WMI enabled") # TODO Should be better message self.ctrl_be = ctrl_be self.server_profile = server_profile self.cpu_widget = cpu_widget self.mem_widget = mem_widget self.cpu_mon_id = None self.total_mem = None self.wmi = ctrl_be.server_control.wmi def query(self, session, attr, query): value = None res = self.wmi.wmiQuery(session, query) if len(res) > 0: if hasattr(res[0], attr): value = getattr(res[0], attr) try: value = int(value) except: print "Wmi query: can't cast '%s' to int" % str(value) value = 1 else: print "Wmi query: expected '%s' result attribute, got:" % attr print res value = 1 return value def poll(self): wmi_session = self.ctrl_be.server_control.wmi_session_id_for_current_thread # Calc CPU value = self.query(wmi_session, 'PercentProcessorTime', "SELECT PercentProcessorTime FROM Win32_PerfFormattedData_PerfOS_Processor WHERE Name = '_Total'") if value is not None: self.cpu_widget[0].set_value(value/100.0) self.ctrl_be.uitask(self.cpu_widget[0].set_description ,"CPU: " + str(int(value)) + "%") # Calc Memory if self.total_mem is None: self.total_mem = float(self.query(wmi_session, 'TotalVisibleMemorySize', "Select TotalVisibleMemorySize FROM Win32_OperatingSystem")) value = self.query(wmi_session, 'AvailableKBytes', "Select AvailableKBytes FROM Win32_PerfFormattedData_PerfOS_Memory") if value is not None: mem_perc = round(100 - ((value/self.total_mem) * 100), 1) self.mem_widget[0].set_value(mem_perc) self.ctrl_be.uitask(self.mem_widget[0].set_description ,"Mem: " + str(int(mem_perc)) + "%") #=============================================================================== class WBAdminMonitorBE(object): def __init__(self, interval, server_profile, ctrl_be, widgets, sql): self.ctrl_be = ctrl_be self.sources = [] # List of sources self.running = [True] self.commands = {} self.poll_thread = None self.interval = interval self.ctrl_be.add_me_for_event("server_started", self) self.ctrl_be.add_me_for_event("server_stopped", self) if server_profile.target_os != wbaOS.windows: if server_profile.is_local or server_profile.remote_admin_enabled: # First read all script commands that were specified for the given instance. script = server_profile.sys_script lines = script.split("\n") for line in lines: line = line.strip(" \r\t\n") idx = line.find("=") if idx > 0: name = line[:idx].strip(" \t") cmds = line[idx+1:].strip(" \t") self.commands[name] = cmds widget = None if name in widgets: widget = widgets[name] cmd = ShellDataSource(name, cmds, self, widget) self.sources.append(cmd) else: print "WBAMonBE: Data sources were not added. Profile set to non-local or remote admin is disabled." else: if server_profile.is_local: # or server_profile.uses_wmi: # Force WMI for local boxes self.wmimon = WMIStats(ctrl_be, server_profile, widgets['get_cpu_info'], widgets['get_mem_info']) self.sources.append(self.wmimon) #Next, if we are reading from Windows targets replace sources to use a special script. # Create special source for windows. This stats will open a script that will output values of mem # and cpu usage upon request. The data obtained from the script is passed directly to widgets #stats = WBAWinLocalStats(ctrl_be, server_profile, self.running, widgets['get_cpu_info'], widgets['get_mem_info']) #self.sources.append(stats) else: if server_profile.remote_admin_enabled and server_profile.connect_method == 'ssh': stats = WBAWinRemoteStats(ctrl_be, server_profile, self.running, widgets['get_cpu_info'], widgets['get_mem_info']) self.sources.append(stats) sql_sources = SqlDataSource('show global status', self) for name, query in sql.iteritems(): widget = None if name in widgets: widget = widgets[name] sql_sources.add_source(name, query, widget) self.sources.append(sql_sources) self.poll_thread = None def __del__(self): self.running[0] = False try: self.poll_thread.join() except: pass self.sources = [] def run(self, name): result = None if name in self.commands: result, rc = self.ctrl_be.execute_filtered_command(self.commands[name]) else: pass # Try to fallback return result def get_ctrl_be(self): return self.ctrl_be def server_started_event(self): log_debug3('%s:%s.server_started_event()' % (_this_file, self.__class__.__name__), 'Enter\n') # This is needed to ensure an existing polling thread is finished # Before creating the new one if self.poll_thread: self.running[0] = False self.poll_thread.join() self.poll_thread = None self.running[0] = True self.poll_thread = threading.Thread(target = self.poll_sources) self.poll_thread.start() log_debug3('%s:%s.server_started_event()' % (_this_file, self.__class__.__name__), 'Leave\n') def server_stopped_event(self): log_debug3('%s:%s.server_stopped_event()' % (_this_file, self.__class__.__name__), 'Enter\n') self.running[0] = False self.poll_thread = None log_debug3('%s:%s.server_stopped_event()' % (_this_file, self.__class__.__name__), 'Leave\n') def poll_sources(self): while self.running[0] and self.ctrl_be.running: #sleep here for cmd in self.sources: cmd.poll() time.sleep(self.interval) log_debug('%s:%s.poll_sources()' % (_this_file, self.__class__.__name__), 'Exiting monitor thread...\n')