File: C:/Program Files/MySQL/MySQL Workbench 8.0/modules/wb_admin_connections.py
# Copyright (c) 2009, 2020, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation. The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have included with MySQL.
# 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, version 2.0, 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
from mforms import newTreeView, newButton, newBox, newSelector, newCheckBox, newLabel, Utilities
import mforms
import grt
from workbench.db_utils import escape_sql_string, QueryError
from functools import partial
from wb_common import dprint_ex
from wb_admin_utils import weakcb, WbAdminTabBase, WbAdminValidationBase, WbAdminValidationConnection
import json
from workbench.log import log_error
class WBThreadStack(mforms.Form):
enable_debug_info = False
def __init__(self, ctrl_be, thread_id):
mforms.Form.__init__(self, mforms.Form.main_form())
self.set_title("Thread Stack for %d" % thread_id)
self.ctrl_be = ctrl_be
vbox = mforms.newBox(False)
vbox.set_padding(20)
vbox.set_spacing(18)
self.thread_id = thread_id
splitter = mforms.newSplitter(True, False)
self.tree = mforms.newTreeView(mforms.TreeDefault)
self.tree.add_column(mforms.IntegerColumnType, "Event Id", 50, False)
self.tree.add_column(mforms.StringColumnType, "Event info", 200, False)
self.tree.add_column(mforms.StringColumnType, "Type", 100, False)
self.tree.add_column(mforms.StringColumnType, "Timer wait [\xC2\xB5s]", 80, False)
if self.enable_debug_info:
self.tree.add_column(mforms.StringColumnType, "Source", 200, False)
self.tree.end_columns()
self.tree.set_size(400, -1)
self.tree.add_changed_callback(self.event_selected)
splitter.add(self.tree, 500);
l = mforms.newLabel("Wait info")
l.set_style(mforms.BoldStyle)
tbox = newBox(False)
lbox = newBox(False)
lbox.set_spacing(5)
lbox.set_padding(5)
lbox.add(l, False, False)
tbox.add(lbox, False, False)
self.text = mforms.newTextBox(mforms.VerticalScrollBar)
self.text.set_read_only(True)
self.text.set_size(150, -1)
tbox.add(self.text, True, True)
splitter.add(tbox, 150)
vbox.add(splitter, True, True)
self.set_content(vbox)
bbox = newBox(True)
bbox.set_spacing(8)
self.ok = newButton()
self.ok.set_text("Close")
self.ok.add_clicked_callback(self.close_form)
bbox.add_end(self.ok, False, True)
vbox.add_end(bbox, False, True)
self.set_size(800, 600)
self.center()
def close_form(self):
self.close()
def event_selected(self):
node = self.tree.get_selected_node()
if node:
tag = node.get_tag()
if tag:
self.text.set_value(tag)
else:
self.text.clear()
def load_data(self):
data = None
try:
result = self.ctrl_be.exec_query("SELECT sys.ps_thread_stack(%d, %s)" % (self.thread_id, "TRUE" if self.enable_debug_info else "FALSE"))
if result is not None:
if result.nextRow():
data = result.stringByIndex(1)
if data:
data = data.replace("\0", "") # workaround for server bug
return self.parse_data(json.loads(data))
except Exception as e:
import traceback
#open("/tmp/data.js", "w+").write(data)
log_error("Exception during sys.ps_thread_stack(%d, %s):\n%s\n" % (self.thread_id, "TRUE" if self.enable_debug_info else "FALSE", traceback.format_exc()))
mforms.Utilities.show_error("Error Getting Thread Stack", "The thread stack for thread %d can't be loaded, please check if your sys schema is properly installed and available.\n%s" % (self.thread_id, e), "OK", "", "")
return None
return False
def parse_data(self, datatree):
treecache = {}
if datatree.get('events') == None or len(datatree['events']) == 0:
return False
for item in datatree['events']:
node = None
if int(item['nesting_event_id']) == 0:
node = self.tree.add_node()
treecache['event_%d' % int(item['event_id'])] = node
else: #Check if there is already node, if not, we create one
if treecache.get('event_%d' % int(item['nesting_event_id'])):
parent = treecache.get('event_%d' % int(item['nesting_event_id']))
node = parent.add_child()
treecache['event_%d' % int(item['event_id'])] = node
else:
continue
node.set_string(0, item['event_id'])
node.set_string(1, item['event_info'].strip())
node.set_string(2, item['event_type'].strip())
if 'timer_wait' in item:
node.set_string(3, str(item['timer_wait']))
pnode = node.get_parent()
if pnode:
pnode_twait = 0.0
try:
pnode_twait = float(pnode.get_string(3))
except:
#Parent node can be empty
pass
pnode.set_string(3, str(item['timer_wait'] + pnode_twait))
if self.enable_debug_info:
node.set_string(4, item['source'].strip())
if item['wait_info'] and item['wait_info'].strip() != item['source'].strip():
node.set_tag(item['wait_info'])
#Set the textbox text as the first node
if self.tree.count():
tag = self.tree.node_at_row(0).get_tag()
if tag:
self.text.set_value(tag)
return True
def run(self):
r = self.load_data()
if not r:
if r is not None:
mforms.Utilities.show_error("Error Getting Thread Stack", "Thread stack is not available for thread %d. Please enable Performance Schema instrumentation (Statement and Stage instrumentations and respective History consumers)." % self.thread_id, "OK", "", "")
self.close()
else:
self.show()
class ConnectionDetailsPanel(mforms.Table):
def __init__(self, owner):
mforms.Table.__init__(self)
self.owner = owner
self.set_padding(8)
self.set_row_spacing(4)
self.set_column_spacing(4)
self.set_column_count(2)
self.set_row_count(16)
self.labels = {}
self.make_line("Processlist Id:", "PROCESSLIST_ID", "Process List")
self.make_line("Thread Id:", "THREAD_ID", "Thread Identifier")
self.make_line("Name:", "NAME", "Name")
self.make_line("Type:", "TYPE", "Type")
self.make_line("User:", "PROCESSLIST_USER", "Process List User")
self.make_line("Host:", "PROCESSLIST_HOST", "Process List Host")
self.make_line("Schema:", "PROCESSLIST_DB", "Process List Database")
self.make_line("Command:", "PROCESSLIST_COMMAND", "Process List Command")
self.make_line("Time:", "PROCESSLIST_TIME", "Process List Time")
self.make_line("State:", "PROCESSLIST_STATE", "Process List State")
self.make_line("Role:", "ROLE", "Role")
self.make_line("Instrumented:", "INSTRUMENTED", "Instrumented")
self.make_line("Parent Thread Id:", "PARENT_THREAD_ID", "Parent Thread Identified")
l = mforms.newLabel("Info:")
l.set_name("Info")
l.set_style(mforms.BoldStyle)
self.add(l, 0, 1, 13, 14, mforms.HFillFlag|mforms.HExpandFlag)
self.info = mforms.newCodeEditor()
self.info.set_features(mforms.FeatureGutter, False)
self.info.set_features(mforms.FeatureReadOnly, True)
self.info.set_features(mforms.FeatureWrapText, True)
self.info.set_language(mforms.LanguageMySQL56)
self.info.set_size(0, 50)
self.add(self.info, 0, 2, 14, 15, mforms.HFillFlag|mforms.VFillFlag|mforms.HExpandFlag|mforms.VExpandFlag)
if self.owner.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 7, 2):
self.explain = mforms.newButton()
self.explain.set_enabled(False)
self.explain.add_clicked_callback(self.owner.explain_selected)
self.explain.set_text("Explain for Connection")
self.add(self.explain, 1, 2, 15, 16, mforms.HFillFlag)
else:
self.explain = None
def make_line(self, caption, name, accessibilityName):
i = len(self.labels)
l = mforms.newLabel(caption)
l.set_name(accessibilityName)
l.set_text_align(mforms.MiddleLeft)
l.set_style(mforms.BoldStyle)
self.add(l, 0, 1, i, i+1, mforms.HFillFlag|mforms.HExpandFlag)
l = mforms.newLabel("")
self.add(l, 1, 2, i, i+1, mforms.HFillFlag|mforms.HExpandFlag)
self.labels[name] = l
def update(self, node):
self.labels["PROCESSLIST_ID"].set_text("%s" % node.get_long(0) if node else "")
self.labels["PROCESSLIST_USER"].set_text(node.get_string(1) if node else "")
self.labels["PROCESSLIST_HOST"].set_text(node.get_string(2) if node else "")
self.labels["PROCESSLIST_DB"].set_text(node.get_string(3) if node else "")
self.labels["PROCESSLIST_COMMAND"].set_text(node.get_string(4) if node else "")
self.labels["PROCESSLIST_TIME"].set_text("%s" % node.get_long(5) if node else "")
self.labels["PROCESSLIST_STATE"].set_text(node.get_string(6) if node else "")
self.labels["THREAD_ID"].set_text("%s" % node.get_long(7) if node else "")
self.labels["TYPE"].set_text(node.get_string(8) if node else "")
self.labels["NAME"].set_text(node.get_string(9) if node else "")
self.labels["PARENT_THREAD_ID"].set_text("%s" % node.get_long(10) if node else "")
self.labels["INSTRUMENTED"].set_text(node.get_string(11) if node else "")
self.info.set_features(mforms.FeatureReadOnly, False)
self.info.set_value(node.get_tag() if node else "")
self.info.set_features(mforms.FeatureReadOnly, True)
if self.explain:
self.explain.set_enabled(True if node and node.get_tag() else False)
class WbAdminValidationPermissions(WbAdminValidationBase):
def __init__(self, ctrl_be):
super().__init__()
self._ctrl_be = ctrl_be
def validate(self):
try:
self._ctrl_be.exec_query("SELECT COUNT(*) FROM performance_schema.threads")
except QueryError as e:
import traceback
log_error("QueryError in Admin for Client Connections:\n%s\n\n%s\n" % (e, traceback.format_exc()))
if e.error == 1142:
self.set_error_message("The account you are currently using does not have sufficient privileges to view the client connections.")
else:
self.set_error_message("There was a problem opening the Client Connections. Please check the error log for more details.")
return False
except Exception as e:
import traceback
log_error("Exception in Admin for Client Connections:\n%s\n\n%s\n" % (e, traceback.format_exc()))
self.set_error_message("There was a problem opening the Client Connections. Please check the error log for more details.")
return False
return True
class WbAdminConnections(WbAdminTabBase):
#ui_created = False
serial = 0
@classmethod
def wba_register(cls, admin_context):
admin_context.register_page(cls, "Management", "Client Connections", "Client Connections", False)
@classmethod
def identifier(cls):
return "admin_connections"
def __init__(self, ctrl_be, instance_info, main_view):
WbAdminTabBase.__init__(self, ctrl_be, instance_info, main_view)
self.add_validation(WbAdminValidationConnection(ctrl_be))
self.add_validation(WbAdminValidationPermissions(ctrl_be))
self.set_standard_header("title_connections.png", self.instance_info.name, "Client Connections")
self.set_managed()
self.set_release_on_add()
self._new_processlist = self.check_if_ps_available()
self._refresh_timeout = None
self.warning = None
if self.new_processlist():
self.columns = [("PROCESSLIST_ID", mforms.LongIntegerColumnType, "Id", 50),
("PROCESSLIST_USER", mforms.StringColumnType, "User", 80),
("PROCESSLIST_HOST", mforms.StringColumnType, "Host", 120),
("PROCESSLIST_DB", mforms.StringColumnType, "DB", 100),
("PROCESSLIST_COMMAND", mforms.StringColumnType, "Command", 80),
("PROCESSLIST_TIME", mforms.LongIntegerColumnType, "Time", 60),
("PROCESSLIST_STATE", mforms.StringColumnType, "State", 80),
("THREAD_ID", mforms.LongIntegerColumnType, "Thread Id", 50),
("TYPE", mforms.StringColumnType, "Type", 80),
("NAME", mforms.StringColumnType, "Name", 80),
("PARENT_THREAD_ID", mforms.LongIntegerColumnType, "Parent Thread", 50),
("INSTRUMENTED", mforms.StringColumnType, "Instrumented", 80),
("PROCESSLIST_INFO", mforms.StringColumnType, "Info", 200),
]
self.long_int_columns = [0, 5, 7, 10]
self.info_column = 12
if self.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 6, 6):
self.columns.append(("ATTR_VALUE", mforms.StringColumnType, "Program", 150))
get_path = mforms.App.get().get_resource_path
self.icon_for_object_type = {
"GLOBAL" : "",
"SCHEMA" : get_path("db.Schema.16x16.png"),
"TABLE" : get_path("db.Table.16x16.png"),
"FUNCTION" : get_path("db.Role.16x16.png"),
"PROCEDURE" : get_path("db.Role.16x16.png"),
"TRIGGER" : get_path("db.Trigger.16x16.png"),
"EVENT" : get_path("GrtObject.16x16.png"),
"COMMIT" : get_path("db.Column.16x16.png")
}
else:
self.columns = [("Id", mforms.LongIntegerColumnType, "Id", 50),
("User", mforms.StringColumnType, "User", 80),
("Host", mforms.StringColumnType, "Host", 120),
("DB", mforms.StringColumnType, "DB", 100),
("Command", mforms.StringColumnType, "Command", 80),
("Time", mforms.LongIntegerColumnType, "Time", 60),
("State", mforms.StringColumnType, "State", 80),
("Info", mforms.StringColumnType, "Info", 80),
]
self.long_int_columns = [0, 5]
self.info_column = 7
def create_ui(self):
dprint_ex(4, "Enter")
uiBox = mforms.newBox(False)
if self.new_processlist():
widths = grt.root.wb.state.get("wb.admin:ConnectionListColumnWidthsPS", None)
else:
widths = grt.root.wb.state.get("wb.admin:ConnectionListColumnWidths", None)
if widths:
column_widths = [int(i) for i in widths.split(",")]
else:
column_widths = None
self.connection_box = mforms.newBox(True)
self.connection_box.set_spacing(8)
self.connection_list = newTreeView(mforms.TreeDefault|mforms.TreeFlatList|mforms.TreeAltRowColors)
self.connection_list.set_selection_mode(mforms.TreeSelectMultiple)
self.connection_list.add_column_resized_callback(self.column_resized)
self.connection_list.set_name("Connection List")
for i, (field, type, caption, width) in enumerate(self.columns):
if column_widths and i < len(column_widths):
width = column_widths[i]
self.connection_list.add_column(type, caption, width, False)
self.connection_list.end_columns()
self.connection_list.set_allow_sorting(True)
self.connection_list.add_changed_callback(weakcb(self, "connection_selected"))
self.connection_box.add(self.connection_list, True, True)
info_table = mforms.newTable()
info_table.set_row_count(2)
info_table.set_column_count(5)
info_table.set_row_spacing(4)
info_table.set_column_spacing(20)
info_table.set_name("Counters")
info_table.add(self.create_labeled_info("Threads Connected:", "Threads Connected", "lbl_Threads_connected"), 0, 1, 0, 1, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Threads Running:", "Threads Running", "lbl_Threads_running"), 1, 2, 0, 1, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Threads Created:", "Threads Created", "lbl_Threads_created"), 2, 3, 0, 1, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Threads Cached:", "Threads Cached", "lbl_Threads_cached"), 3, 4, 0, 1, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Rejected (over limit):", "Rejected", "lbl_Connection_errors_max_connections"), 4, 5, 0, 1, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Total Connections:", "Total Connections", "lbl_Connections"), 0, 1, 1, 2, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Connection Limit:", "Connection Limit", "lbl_max_connections"), 1, 2, 1, 2, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Aborted Clients:", "Aborted Clients", "lbl_Aborted_clients"), 2, 3, 1, 2, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Aborted Connections:", "Aborted Connections", "lbl_Aborted_connects"), 3, 4, 1, 2, mforms.HFillFlag | mforms.VFillFlag)
info_table.add(self.create_labeled_info("Errors:", "Errors", "lbl_errors", "tooltip_errors"), 4, 5, 1, 2, mforms.HFillFlag | mforms.VFillFlag)
self.info_table = info_table
uiBox.add(info_table, False, True)
uiBox.add(self.connection_box, True, True)
box = newBox(True)
self.button_box = box
self.button_box.set_spacing(12)
refresh_button = newButton()
refresh_button.set_text("Refresh")
self.button_box.add_end(refresh_button, False, True)
refresh_button.add_clicked_callback(weakcb(self, "refresh"))
self.kill_button = newButton()
self.kill_button.set_text("Kill Connection(s)")
self.kill_button.set_name("Kill Connections")
self.button_box.add_end(self.kill_button, False, True)
self.kill_button.add_clicked_callback(weakcb(self, "kill_connection"))
self.killq_button = newButton()
self.killq_button.set_text("Kill Query(s)")
self.killq_button.set_name("Kill Queries")
self.button_box.add_end(self.killq_button, False, True)
self.killq_button.add_clicked_callback(weakcb(self, "kill_query"))
refresh_label = newLabel("Refresh Rate:")
self.button_box.add(refresh_label, False, True)
self._menu = mforms.newContextMenu()
self._menu.add_will_show_callback(self.menu_will_show)
self.connection_list.set_context_menu(self._menu)
self.refresh_values = [0.5, 1, 2, 3, 4, 5, 10, 15, 30]
self.refresh_values_size = len(self.refresh_values)
self.refresh_selector = newSelector()
self.refresh_selector.set_size(100,-1)
for s in self.refresh_values:
self.refresh_selector.add_item(str(s) + " seconds")
self.refresh_selector.add_item("Don't Refresh")
refresh_rate_index = grt.root.wb.options.options.get('Administrator:refresh_connections_rate_index', 9)
self.refresh_selector.set_selected(refresh_rate_index)
self.update_refresh_rate()
self.refresh_selector.add_changed_callback(weakcb(self, "update_refresh_rate"))
self.button_box.add(self.refresh_selector, False, True)
self.check_box = newBox(True)
self.check_box.set_spacing(12)
self.hide_sleep_connections = newCheckBox()
self.hide_sleep_connections.set_text('Hide sleeping connections')
self.hide_sleep_connections.set_name('Hide sleeping connections')
self.hide_sleep_connections.add_clicked_callback(self.refresh)
self.hide_sleep_connections.set_tooltip('Remove connections in the Sleeping state from the connection list.')
self.check_box.add(self.hide_sleep_connections, False, True)
self.mdl_locks_page = None
self._showing_extras = False
if self.new_processlist():
self.hide_background_threads = newCheckBox()
self.hide_background_threads.set_active(True)
self.hide_background_threads.set_text('Hide background threads')
self.hide_background_threads.set_name('Hide background threads')
self.hide_background_threads.set_tooltip('Remove background threads (internal server threads) from the connection list.')
self.hide_background_threads.add_clicked_callback(self.refresh)
self.check_box.add(self.hide_background_threads, False, True)
self.truncate_info = newCheckBox()
self.truncate_info.set_active(True)
self.truncate_info.set_text('Don\'t load full thread info')
self.truncate_info.set_name('Do Not Load Thread Info')
self.truncate_info.set_tooltip('Toggle whether to load the entire query information for all connections or just the first 255 characters.\nEnabling this can have a large impact in busy servers or server executing large INSERTs.')
self.truncate_info.add_clicked_callback(self.refresh)
self.check_box.add(self.truncate_info, False, True)
# tab with some extra info, only available if PS exists
self.extra_info_tab = mforms.newTabView(mforms.TabViewSystemStandard)
self.extra_info_tab.set_size(350, -1)
self.extra_info_tab.add_tab_changed_callback(self.extra_tab_changed)
self.extra_info_tab.set_name("Details Sidebar")
self.connection_details_scrollarea = mforms.newScrollPanel()
self.connection_details = ConnectionDetailsPanel(self)
self.connection_details_scrollarea.add(self.connection_details)
self.details_page = self.extra_info_tab.add_page(self.connection_details_scrollarea, "Details")
self.mdl_list_box = None
if self.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 7, 3):
self.mdl_list_box_scrollarea = mforms.newScrollPanel()
self.mdl_list_box = mforms.newBox(False)
self.mdl_list_box_scrollarea.add(self.mdl_list_box)
self.mdl_label = mforms.newLabel('Metadata locks (MDL) protect concurrent access to\nobject metadata (not table row/data locks)')
self.mdl_label.set_name("Metadata Info")
self.mdl_list_box.add(self.mdl_label, False, True)
label = mforms.newLabel("\nGranted Locks (and threads waiting on them)")
label.set_name("Granted Locks Info")
label.set_style(mforms.BoldStyle)
self.mdl_list_box.add(label, False, True)
label = mforms.newLabel("Locks this connection currently owns and\nconnections that are waiting for them.")
label.set_name("Lock Connection Info")
label.set_style(mforms.SmallHelpTextStyle)
self.mdl_list_box.add(label, False, True)
self.mdl_list_held = mforms.newTreeView(mforms.TreeAltRowColors)
self.mdl_list_held.add_column(mforms.IconStringColumnType, "Object", 130, False)
self.mdl_list_held.add_column(mforms.StringColumnType, "Type", 100, False)
self.mdl_list_held.add_column(mforms.StringColumnType, "Duration", 100, False)
self.mdl_list_held.end_columns()
self.mdl_list_held.set_size(0, 100)
self.mdl_list_box.add(self.mdl_list_held, True, True)
label = mforms.newLabel("\nPending Locks")
label.set_style(mforms.BoldStyle)
self.mdl_list_box.add(label, False, True)
hbox = mforms.newBox(True)
hbox.set_spacing(4)
self.mdl_blocked_icon = mforms.newImageBox()
self.mdl_blocked_icon.set_image(mforms.App.get().get_resource_path("message_warning.png"))
hbox.add(self.mdl_blocked_icon, False, True)
self.mdl_waiting_label = mforms.newLabel("Locks this connection is currently waiting for.")
self.mdl_waiting_label.set_name("Lock Connection Info")
hbox.add(self.mdl_waiting_label, True, True)
self.mdl_list_box.add(hbox, False, True)
self.mdl_locks_page = self.extra_info_tab.add_page(self.mdl_list_box_scrollarea, "Locks")
if self.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 6, 0):
self.attributes_list = mforms.newTreeView(mforms.TreeFlatList|mforms.TreeAltRowColors)
self.attributes_list.add_column(mforms.StringColumnType, "Attribute", 150, False)
self.attributes_list.add_column(mforms.StringColumnType, "Value", 200, False)
self.attributes_list.end_columns()
self.attributes_page = self.extra_info_tab.add_page(self.attributes_list, "Attributes")
self.connection_box.add(self.extra_info_tab, False, True)
self.extra_info_tab.show(False)
self.show_extras = newButton()
self.show_extras.set_text('Show Details')
self.show_extras.add_clicked_callback(self.toggle_extras)
self.check_box.add_end(self.show_extras, False, True)
self.check_box.set_padding(0, 5, 0, 5)
footerBox = mforms.newBox(False)
footerBox.add_end(self.check_box, False, True)
footerBox.add_end(self.button_box, False, True)
self.set_footer(footerBox)
self.resume_layout()
self.connection_selected()
self.relayout()
dprint_ex(4, "Leave")
# Call refresh to load list at least once
self.refresh()
return uiBox
def shutdown(self):
if self._refresh_timeout:
Utilities.cancel_timeout(self._refresh_timeout)
self._refresh_timeout = None
def create_labeled_info(self, lbl_txt, acc_name, lbl_name, tooltip_name = None):
lbox = newBox(True)
lbox.set_spacing(5)
l = mforms.newLabel(lbl_txt)
l.set_name(acc_name)
lbox.add(l, False, True)
setattr(self, lbl_name, mforms.newLabel(""))
l.set_style(mforms.BoldStyle)
lbox.add(getattr(self, lbl_name), False, True)
if tooltip_name != None:
i = mforms.newImageBox()
i.set_image(mforms.App.get().get_resource_path("mini_notice.png"))
i.set_tooltip("")
i.set_name(tooltip_name)
lbox.add(i, False, True)
setattr(self, tooltip_name, i)
return lbox
def load_info_panel_data(self):
vars = ['Threads_connected', 'Threads_running', 'Threads_created', 'Threads_cached', 'Connections', 'Aborted_clients', 'Aborted_connects']
errors = []
if self.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 6, 5):
vars.append('Connection_errors_max_connections')
errors = ['Connection_errors_accept', 'Connection_errors_internal', 'Connection_mac_connections', 'Connection_per_addr', 'Connection_errors_select', 'Connection_errors_tcpwrap']
error_tooltip = []
error_count = 0
result = self.ctrl_be.exec_query("SHOW GLOBAL STATUS")
if result is not None:
while result.nextRow():
if result.stringByIndex(1) in vars:
obj = getattr(self, "lbl_%s" % result.stringByIndex(1), None)
if obj != None:
obj.set_text(result.stringByIndex(2))
if result.stringByIndex(1) in errors:
error_count += int(result.stringByIndex(2))
error_tooltip.append("%s: %d" % (result.stringByIndex(1), int(result.stringByIndex(2))))
if error_tooltip:
self.lbl_errors.show(True)
self.tooltip_errors.set_tooltip("\n".join(error_tooltip))
self.lbl_errors.set_text("%d" % error_count)
else:
self.lbl_errors.show(False)
self.lbl_max_connections.set_text(self.ctrl_be.server_variables['max_connections'])
def menu_will_show(self, item):
if item is None:
self._menu.remove_all()
selected_conn = self.connection_list.get_selection()
if selected_conn:
user_thread = self.new_processlist() == False
if not user_thread:
user_thread = len([sel for sel in selected_conn if not sel.get_string(8).startswith('BACKGROUND')]) > 0
item = self._menu.add_item_with_title("Copy", self.copy_selected, "Copy Selected", "copy_selected")
item = self._menu.add_item_with_title("Copy Info", self.copy_selected_info, "Copy Selected Info", "copy_selected_info")
item = self._menu.add_item_with_title("Show in Editor", self.edit_selected, "Edit Selected", "edit_selected")
item.set_enabled(user_thread)
if self.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 7):
self._menu.add_separator()
item = self._menu.add_item_with_title("Explain for Connection", self.explain_selected, "Explain", "explain")
if len(selected_conn) != 1:
item.set_enabled(False)
else:
info = selected_conn[0].get_string(self.info_column)
if not info or info == "NULL":
item.set_enabled(False)
if self.new_processlist():
item = self._menu.add_item_with_title("View Thread Stack", self.view_thread_stack, "View Thread Stack", "view_thread_stack")
if len(selected_conn) != 1:
item.set_enabled(False)
instr_caption = "Enable Instrumentation for Thread"
start_with_yes = [sel.get_string(11).startswith('YES') for sel in selected_conn]
if all(start_with_yes):
instr_caption = "Disable Instrumentation for Thread"
item = self._menu.add_item_with_title(instr_caption, self.enable_disable_instrumentation, "Enable Instrumentation", "enable_disable_instrumentation")
if any(start_with_yes) and not all(start_with_yes):
item.set_enabled(False)
self._menu.add_separator()
item = self._menu.add_item_with_title("Kill Query(s)", self.kill_query, "Kill Query", "kill_query")
item.set_enabled(user_thread)
item = self._menu.add_item_with_title("Kill Connection(s)", self.kill_connection, "Kill Connection", "kill_connection")
item.set_enabled(user_thread)
self._menu.add_separator()
self._menu.add_item_with_title("Refresh", self.refresh, "Refresh", "refresh")
def column_resized(self, col):
widths = []
for c in range(self.connection_list.get_column_count()):
widths.append(self.connection_list.get_column_width(c))
widths = ",".join([str(w) for w in widths])
if self.new_processlist():
grt.root.wb.state["wb.admin:ConnectionListColumnWidthsPS"] = widths
else:
grt.root.wb.state["wb.admin:ConnectionListColumnWidths"] = widths
def check_if_ps_available(self):
if self.ctrl_be.target_version and self.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 6):
result = self.ctrl_be.exec_query("select @@performance_schema")
if result:
if result.nextRow():
if result.intByIndex(1) == 1:
return True
return False
def check_if_mdl_available(self):
if self.ctrl_be.target_version and self.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 7, 3):
result = self.ctrl_be.exec_query("select count(*) from performance_schema.setup_instruments where name = 'wait/lock/metadata/sql/mdl' and enabled='YES'")
if result:
if result.nextRow():
if result.intByIndex(1) == 1:
return True
return False
def new_processlist(self):
return self._new_processlist
def connection_selected(self):
dprint_ex(4, "Enter")
sel = self.connection_list.get_selection()
if not sel:
self.kill_button.set_enabled(False)
self.killq_button.set_enabled(False)
else:
self.kill_button.set_enabled(True)
self.killq_button.set_enabled(True)
if self._showing_extras:
if sel and len(sel) == 1:
self.connection_details.update(sel[0])
else:
self.connection_details.update(None)
tab = self.extra_info_tab.get_active_tab()
if tab == self.mdl_locks_page:
self.refresh_mdl_list()
elif tab == self.attributes_page:
self.refresh_attr_list()
dprint_ex(4, "Leave")
def refresh_attr_list(self):
self.attributes_list.clear()
try:
nodes = self.connection_list.get_selection()
if nodes and len(nodes) == 1:
connid = nodes[0].get_long(0)
result = self.ctrl_be.exec_query("SELECT * FROM performance_schema.session_connect_attrs WHERE processlist_id = %s ORDER BY ORDINAL_POSITION" % connid)
while result and result.nextRow():
node = self.attributes_list.add_node()
node.set_string(0, result.stringByName("ATTR_NAME"))
node.set_string(1, result.stringByName("ATTR_VALUE"))
except Exception as e:
import traceback
log_error("Error looking up attribute information: %s\n" % traceback.format_exc())
mforms.Utilities.show_error("Lookup Connection Attributes", "Error looking up connection attributes: %s" % e, "OK", "", "")
def refresh_mdl_list(self):
self.mdl_list_held.clear()
self.mdl_blocked_icon.show(False)
waiting_label_text = "This connection is not waiting for any locks."
try:
nodes = self.connection_list.get_selection()
if nodes and len(nodes) == 1:
thread_id = nodes[0].get_long(7)
result = self.ctrl_be.exec_query("SELECT * FROM performance_schema.metadata_locks WHERE owner_thread_id = %s" % thread_id)
if result is not None:
while result.nextRow():
lock_status = result.stringByName("LOCK_STATUS")
if lock_status == "PENDING":
otype = result.stringByName("OBJECT_TYPE")
oschema = result.stringByName("OBJECT_SCHEMA")
oname = result.stringByName("OBJECT_NAME")
obj_name = [oschema, oname]
if otype == "GLOBAL":
obj_name = "<global>"
else:
obj_name = ".".join([o for o in obj_name if o is not None])
self.mdl_blocked_icon.show(True)
sub_expr = "OBJECT_TYPE = '%s'" % otype
if oschema:
sub_expr += " AND OBJECT_SCHEMA = '%s'" % escape_sql_string(oschema)
if oname:
sub_expr += " AND OBJECT_NAME = '%s'" % escape_sql_string(oname)
lock_type = result.stringByName("LOCK_TYPE")
lock_duration = result.stringByName("LOCK_DURATION")
subresult = self.ctrl_be.exec_query("""SELECT *
FROM performance_schema.metadata_locks
WHERE %s AND LOCK_STATUS = 'GRANTED'""" % sub_expr)
owners = []
while subresult and subresult.nextRow():
owners.append(subresult.intByName("OWNER_THREAD_ID"))
owner_list = ", ".join([str(i) for i in owners])
if len(owners) == 1:
waiting_label_text = "The connection is waiting for a lock on\n%s %s,\nheld by thread %s." % (otype.lower(), obj_name, owner_list)
else:
waiting_label_text = "The connection is waiting for a lock on\n%s %s,\nheld by threads %s" % (otype.lower(), obj_name, owner_list)
waiting_label_text += "\nType: %s\nDuration: %s" % (lock_type, lock_duration)
elif lock_status == "GRANTED":
node = self.mdl_list_held.add_node()
otype = result.stringByName("OBJECT_TYPE")
oschema = result.stringByName("OBJECT_SCHEMA")
oname = result.stringByName("OBJECT_NAME")
obj_name = [oschema, oname]
if otype == "GLOBAL":
node.set_string(0, "<global>")
else:
node.set_string(0, ".".join([o for o in obj_name if o is not None]))
node.set_icon_path(0, self.icon_for_object_type.get(otype))
sub_expr = "OBJECT_TYPE = '%s'" % otype
if oschema:
sub_expr += " AND OBJECT_SCHEMA = '%s'" % escape_sql_string(oschema)
if oname:
sub_expr += " AND OBJECT_NAME = '%s'" % escape_sql_string(oname)
node.set_string(1, result.stringByName("LOCK_TYPE"))
node.set_string(2, result.stringByName("LOCK_DURATION"))
subresult = self.ctrl_be.exec_query("""SELECT OWNER_THREAD_ID, LOCK_TYPE, LOCK_DURATION
FROM performance_schema.metadata_locks
WHERE %s AND LOCK_STATUS = 'PENDING'""" % sub_expr)
while subresult and subresult.nextRow():
subnode = node.add_child()
subnode.set_string(0, "thread %s" % subresult.intByName("OWNER_THREAD_ID"))
subnode.set_string(1, subresult.stringByName("LOCK_TYPE"))
subnode.set_string(2, subresult.stringByName("LOCK_DURATION"))
else:
waiting_label_text = ""
except Exception as e:
import traceback
log_error("Error looking up metadata lock information: %s\n" % traceback.format_exc())
mforms.Utilities.show_error("Lookup Metadata Locks", "Error looking up metadata lock information: %s" % e, "OK", "", "")
self.mdl_waiting_label.set_text(waiting_label_text)
def get_process_list(self):
if self.new_processlist():
return self.get_process_list_new()
else:
return self.get_process_list_old()
def get_process_list_new(self):
cols = []
for field, type, caption, width in self.columns:
if field == "PROCESSLIST_USER":
cols.append("IF (NAME = 'thread/sql/event_scheduler','event_scheduler',t.PROCESSLIST_USER) PROCESSLIST_USER")
elif field == "INFO" and self.truncate_info.get_active():
cols.append("SUBSTR(t.INFO, 0, 255) INFO")
else:
if field == "ATTR_VALUE":
cols.append("a."+field)
else:
cols.append("t."+field)
if self.ctrl_be.target_version.is_supported_mysql_version_at_least(5, 6, 6):
JOIN = " LEFT OUTER JOIN performance_schema.session_connect_attrs a ON t.processlist_id = a.processlist_id AND (a.attr_name IS NULL OR a.attr_name = 'program_name')"
else:
JOIN = ""
if self.hide_background_threads.get_active():
result = self.ctrl_be.exec_query("SELECT %s FROM performance_schema.threads t %s WHERE t.TYPE <> 'BACKGROUND'" % (",".join(cols), JOIN))
else:
result = self.ctrl_be.exec_query("SELECT %s FROM performance_schema.threads t %s WHERE 1=1" % (",".join(cols), JOIN))
if result is not None:
result_rows = []
while result.nextRow():
row = []
for i, (field, type, caption, width) in enumerate(self.columns):
value = result.stringByName(field)
row.append(value)
result_rows.append(row)
return result_rows
return None
def get_process_list_old(self):
result = self.ctrl_be.exec_query("SHOW FULL PROCESSLIST")
if result is not None:
result_rows = []
while result.nextRow():
row = []
for field, type, caption, width in self.columns:
value = result.stringByName(field)
row.append(value)
result_rows.append(row)
return result_rows
return None
def update_refresh_rate(self):
index = int(self.refresh_selector.get_selected_index())
grt.root.wb.options.options['Administrator:refresh_connections_rate_index'] = index
self.serial += 1
if self._refresh_timeout:
Utilities.cancel_timeout(self._refresh_timeout)
self._refresh_timeout = None
if (index < self.refresh_values_size):
self._refresh_timeout = Utilities.add_timeout(self.refresh_values[index], partial(self.refresh, my_serial = self.serial))
def enable_disable_instrumentation(self):
selected_conn = self.connection_list.get_selection()
if not selected_conn:
return
instr_state = 'YES'
if selected_conn[0].get_string(11).startswith('YES'):
instr_state = 'No'
for sel in selected_conn:
connid = sel.get_long(7)
try:
self.ctrl_be.exec_sql("UPDATE performance_schema.threads SET instrumented = '%s' WHERE thread_id = %d LIMIT 1" % (instr_state, connid))
except Exception as e:
log_error("Error enabling thread instrumentation: %s\n" % e)
mforms.Utilities.show_error("Toggle Thread Instrumentation", "Error setting instrumentation for thread %d: %s" % (connid, e), "OK", "", "")
break
self.refresh()
def extra_tab_changed(self):
self.connection_selected()
def enable_mdl_instrumentation(self):
try:
self.ctrl_be.exec_sql("UPDATE performance_schema.setup_instruments SET enabled='YES' WHERE name = 'wait/lock/metadata/sql/mdl'")
except Exception as e:
log_error("Error enabling MDL instrumentation: %s\n" % e)
mforms.Utilities.show_error("Enable MDL Instrumentation", "Error enabling performance_schema MDL instrumentation.\n%s" % e, "OK", "", "")
return
self._mdl_enabled = self.check_if_mdl_available()
if self._mdl_enabled:
self.mdl_list_box.remove(self.mdl_enable_button_sep)
self.mdl_list_box.remove(self.mdl_enable_button)
self.mdl_enable_button_sep = None
self.mdl_enable_button = None
else:
log_error("MDL instrumentation enabled, but it's still disabled!?\n")
_mdl_enabled = None
def toggle_extras(self):
self._showing_extras = not self._showing_extras
if self._showing_extras:
if self._mdl_enabled is None:
self._mdl_enabled = self.check_if_mdl_available()
if not self._mdl_enabled and self.mdl_list_box:
self.mdl_enable_button_sep = mforms.newLabel("\n\nMDL instrumentation is currently disabled.\nClick [Enable Instrumentation] to enable it.")
self.mdl_list_box.add(self.mdl_enable_button_sep, False, True)
self.mdl_enable_button = mforms.newButton()
self.mdl_enable_button.add_clicked_callback(self.enable_mdl_instrumentation)
self.mdl_enable_button.set_text("Enable Instrumentation")
self.mdl_list_box.add(self.mdl_enable_button, False, True)
self.extra_info_tab.show(True)
self.connection_selected()
self.show_extras.set_text("Hide Details")
else:
self.extra_info_tab.show(False)
self.show_extras.set_text("Show Details")
def view_thread_stack(self):
sel = self.connection_list.get_selected_node()
if not sel:
return
view = WBThreadStack(self.ctrl_be, sel.get_long(7))
view.run()
def copy_selected_info(self):
selected_conn = self.connection_list.get_selection()
if not selected_conn:
return
info = ', '.join([sel.get_tag() for sel in selected_conn if sel.get_tag()])
if len(info) > 250 and self.new_processlist() and self.truncate_info.get_active():
info += " /* statement may be truncated */"
mforms.Utilities.set_clipboard_text(info)
def _node_text(self, sel):
text = []
text.append("-- Connection Id: %s\n" % sel.get_long(0))
text.append("-- User: %s\n" % sel.get_string(1))
text.append("-- Host: %s\n" % sel.get_string(2))
text.append("-- DB: %s\n" % sel.get_string(3))
text.append("-- Command: %s\n" % sel.get_string(4))
text.append("-- Time: %s\n" % sel.get_long(5))
text.append("-- State: %s\n" % sel.get_string(6))
info = sel.get_tag()
if len(info) > 250 and self.new_processlist() and self.truncate_info.get_active():
info += " /* statement may be truncated */"
text.append(info)
return "".join(text)
def copy_selected(self):
selected_conn = self.connection_list.get_selection()
if not selected_conn:
return
mforms.Utilities.set_clipboard_text('\n'.join([self._node_text(sel) for sel in selected_conn]))
def edit_selected(self):
selected_conn = self.connection_list.get_selection()
if not selected_conn:
return
editor = self.main_view.editor.addQueryEditor()
editor.replaceContents('\n'.join([self._node_text(sel) for sel in selected_conn]))
return editor
def explain_selected(self):
sel = self.connection_list.get_selected_node()
if not sel:
return
editor = self.edit_selected()
grt.modules.SQLIDEQueryAnalysis.visualExplainForConnection(editor, str(sel.get_long(0)), sel.get_string(self.info_column))
def refresh(self, query_result = None, my_serial = 0):
if not self.page_active():
dprint_ex(2, "Leave. Page is inactive")
return True
if not self.ctrl_be.is_sql_connected():
dprint_ex(2, "Leave. SQL connection is offline")
self._refresh_timeout = None
return False
self.load_info_panel_data()
if self.new_processlist():
id_column = 7
else:
id_column = 0
node = self.connection_list.get_selected_node()
if node:
old_selected = node.get_long(id_column)
else:
old_selected = None
old_selected_node = None
if query_result is None:
query_result = self.get_process_list()
if query_result is not None:
self.connection_list.freeze_refresh()
self.connection_list.clear()
no_sleep_connections = self.hide_sleep_connections.get_active()
no_bg_threads = False
if self.new_processlist():
no_bg_threads = self.hide_background_threads.get_active()
try:
for row in query_result:
if no_sleep_connections and str(row[4]).startswith('Sleep'):
continue
if no_bg_threads and str(row[8]).startswith('BACKGROUND'):
continue
r = self.connection_list.add_node()
for c, field in enumerate(row):
if c in self.long_int_columns:
try:
field = int(field)
except Exception:
field = 0
r.set_long(c, field)
if field == old_selected and id_column == c:
old_selected_node = r
elif c == self.info_column:
# truncate Info column to 255 chars for display, since they can be REALLY long
# which causes GDI trouble in Windows... so just store the full info in the tag
if field is not None:
r.set_string(c, field[:255])
else:
r.set_string(c, "NULL")
r.set_tag(field or "")
else:
field = str(field)
r.set_string(c, field)
finally:
self.connection_list.thaw_refresh()
if old_selected_node:
self.connection_list.select_node(old_selected_node)
self.connection_selected()
cont = (my_serial == self.serial)
if not cont:
self._refresh_timeout = None
return cont
def kill_connection(self):
if not self.ctrl_be.is_sql_connected():
return
selections = self.connection_list.get_selection()
if not selections:
return
for sel in selections:
connid = sel.get_long(0)
if self.new_processlist() and sel.get_string(8).startswith('BACKGROUND'):
mforms.Utilities.show_error("Error Killing Connection", "Thread %s cannot be killed" % connid, "OK", "", "")
return
try:
self.ctrl_be.exec_sql("KILL CONNECTION %s"%connid)
except Exception as e:
mforms.Utilities.show_error("Error Killing Connection", "%s" % e, "OK", "", "")
break
self.refresh()
def kill_query(self):
if not self.ctrl_be.is_sql_connected():
return
selections = self.connection_list.get_selection()
if not selections:
return
for sel in selections:
connid = sel.get_long(0)
if self.new_processlist() and sel.get_string(8).startswith('BACKGROUND'):
mforms.Utilities.show_error("Error Killing Connection", "Thread %s cannot be killed" % connid, "OK", "", "")
return
try:
self.ctrl_be.exec_sql("KILL QUERY %s"%connid)
except Exception as e:
mforms.Utilities.show_error("Error Killing Connection", "Error executing KILL QUERY on thread %d: %s" % (connid, e), "OK", "", "")
break
self.refresh()