From 8fdd567347ab0f3973d3f987e55da7a8644d1a82 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Thu, 26 Oct 2023 17:27:54 +0200 Subject: [PATCH] Separate ZODB connection information into new ZODB Connections view (#1178) * - Separate ZODB connection information into new ZODB Connections view * - move comment and remove auto-refresh * table facelift * - fall back to PATH_INFO if REQUEST_URI is missing or empty * - Move the cache detail links to the individual database pages. - Fix the auto refresh functionality on the Reference Count page. * DebugInfo: monotonized table style * - fix cache_detail pages to show data for the selected database only * set table-spacing back to standard Ref: https://github.com/zopefoundation/Zope/pull/1178#issuecomment-1780912346 --------- Co-authored-by: drfho --- CHANGES.rst | 6 ++ src/App/ApplicationManager.py | 31 +++++---- src/App/DavLockManager.py | 4 +- src/App/ZODBConnectionDebugger.py | 90 +++++++++++++++++++++++++++ src/App/dtml/dbMain.dtml | 13 +++- src/App/dtml/debug.dtml | 63 +++++++------------ src/App/dtml/zodbConnections.dtml | 51 +++++++++++++++ src/zmi/styles/resources/zmi_base.css | 12 ++++ 8 files changed, 214 insertions(+), 56 deletions(-) create mode 100644 src/App/ZODBConnectionDebugger.py create mode 100644 src/App/dtml/zodbConnections.dtml diff --git a/CHANGES.rst b/CHANGES.rst index 78aaf1f907..0a3cd08e0d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,12 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst 5.8.7 (unreleased) ------------------ +- Separate ZODB connection information into new ZODB Connections view. + +- Move the cache detail links to the individual database pages. + +- Fix the auto refresh functionality on the Reference Count page + - Update the Ace editor in the ZMI. - Restrict access to static ZMI resources. diff --git a/src/App/ApplicationManager.py b/src/App/ApplicationManager.py index 07a2e75026..077b15be58 100644 --- a/src/App/ApplicationManager.py +++ b/src/App/ApplicationManager.py @@ -28,6 +28,7 @@ from App.special_dtml import DTMLFile from App.Undo import UndoSupport from App.version_txt import version_txt +from App.ZODBConnectionDebugger import ZODBConnectionDebugger from DateTime.DateTime import DateTime from OFS.Traversable import Traversable from Persistence import Persistent @@ -63,7 +64,9 @@ class DatabaseChooser(Tabs, Traversable, Implicit): {'label': 'Databases', 'action': 'manage_main'}, {'label': 'Configuration', 'action': '../Configuration/manage_main'}, {'label': 'DAV Locks', 'action': '../DavLocks/manage_main'}, - {'label': 'Debug Information', 'action': '../DebugInfo/manage_main'}, + {'label': 'Reference Counts', 'action': '../DebugInfo/manage_main'}, + {'label': 'ZODB Connections', + 'action': '../ZODBConnections/manage_main'}, ) MANAGE_TABS_NO_BANNER = True @@ -108,7 +111,9 @@ class ConfigurationViewer(Tabs, Traversable, Implicit): {'label': 'Databases', 'action': '../Database/manage_main'}, {'label': 'Configuration', 'action': 'manage_main'}, {'label': 'DAV Locks', 'action': '../DavLocks/manage_main'}, - {'label': 'Debug Information', 'action': '../DebugInfo/manage_main'}, + {'label': 'Reference Counts', 'action': '../DebugInfo/manage_main'}, + {'label': 'ZODB Connections', + 'action': '../ZODBConnections/manage_main'}, ) MANAGE_TABS_NO_BANNER = True @@ -150,7 +155,7 @@ class DebugManager(Tabs, Traversable, Implicit): manage = manage_main = manage_workspace = DTMLFile('dtml/debug', globals()) manage_main._setName('manage_main') id = 'DebugInfo' - name = title = 'Debug Information' + name = title = 'Reference Counts' meta_type = name zmi_icon = 'fas fa-bug' @@ -159,7 +164,9 @@ class DebugManager(Tabs, Traversable, Implicit): {'label': 'Databases', 'action': '../Database/manage_main'}, {'label': 'Configuration', 'action': '../Configuration/manage_main'}, {'label': 'DAV Locks', 'action': '../DavLocks/manage_main'}, - {'label': 'Debug Information', 'action': 'manage_main'}, + {'label': 'Reference Counts', 'action': 'manage_main'}, + {'label': 'ZODB Connections', + 'action': '../ZODBConnections/manage_main'}, ) def refcount(self, n=None, t=(type(Implicit), type(object))): @@ -224,10 +231,6 @@ def rcdeltas(self): 'rc': n[1][0], } for n in rd] - def dbconnections(self): - import Zope2 # for data - return Zope2.DB.connectionDebugInfo() - def manage_getSysPath(self): return list(sys.path) @@ -235,8 +238,7 @@ def manage_getSysPath(self): InitializeClass(DebugManager) -class ApplicationManager(CacheManager, - Persistent, +class ApplicationManager(Persistent, Tabs, Traversable, Implicit): @@ -255,6 +257,7 @@ class ApplicationManager(CacheManager, Configuration = ConfigurationViewer() DavLocks = DavLockManager() DebugInfo = DebugManager() + ZODBConnections = ZODBConnectionDebugger() manage = manage_main = DTMLFile('dtml/cpContents', globals()) manage_main._setName('manage_main') @@ -263,7 +266,8 @@ class ApplicationManager(CacheManager, {'label': 'Databases', 'action': 'Database/manage_main'}, {'label': 'Configuration', 'action': 'Configuration/manage_main'}, {'label': 'DAV Locks', 'action': 'DavLocks/manage_main'}, - {'label': 'Debug Information', 'action': 'DebugInfo/manage_main'}, + {'label': 'Reference Counts', 'action': 'DebugInfo/manage_main'}, + {'label': 'ZODB Connections', 'action': 'ZODBConnections/manage_main'}, ) MANAGE_TABS_NO_BANNER = True @@ -311,7 +315,7 @@ def getCLIENT_HOME(self): return getConfiguration().clienthome -class AltDatabaseManager(Traversable, UndoSupport): +class AltDatabaseManager(CacheManager, Traversable, UndoSupport): """ Database management DBTab-style """ id = 'DatabaseManagement' @@ -333,6 +337,9 @@ def _getDB(self): def cache_length(self): return self._getDB().cacheSize() + def cache_active_and_inactive_count(self): + return sum([x['size'] for x in self._getDB().cacheDetailSize()]) + def cache_length_bytes(self): return self._getDB().getCacheSizeBytes() diff --git a/src/App/DavLockManager.py b/src/App/DavLockManager.py index 2e4622da94..28962f7cae 100644 --- a/src/App/DavLockManager.py +++ b/src/App/DavLockManager.py @@ -39,7 +39,9 @@ class DavLockManager(Item, Implicit): {'label': 'Databases', 'action': '../Database/manage_main'}, {'label': 'Configuration', 'action': '../Configuration/manage_main'}, {'label': 'DAV Locks', 'action': 'manage_main'}, - {'label': 'Debug Information', 'action': '../DebugInfo/manage_main'}, + {'label': 'Reference Counts', 'action': '../DebugInfo/manage_main'}, + {'label': 'ZODB Connections', + 'action': '../ZODBConnections/manage_main'}, ) @security.protected(webdav_manage_locks) diff --git a/src/App/ZODBConnectionDebugger.py b/src/App/ZODBConnectionDebugger.py new file mode 100644 index 0000000000..9167d42c32 --- /dev/null +++ b/src/App/ZODBConnectionDebugger.py @@ -0,0 +1,90 @@ +############################################################################## +# +# Copyright (c) 2023 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +import pprint +import time +from operator import itemgetter + +from AccessControl.class_init import InitializeClass +from AccessControl.SecurityInfo import ClassSecurityInfo +from Acquisition import Implicit +from App.special_dtml import DTMLFile +from OFS.SimpleItem import Item + + +class ZODBConnectionDebugger(Item, Implicit): + id = 'ZODBConnectionDebugger' + name = title = 'ZODB Connections' + meta_type = 'ZODB Connection Debugger' + zmi_icon = 'fas fa-bug' + + security = ClassSecurityInfo() + + manage_zodb_conns = manage_main = manage = manage_workspace = DTMLFile( + 'dtml/zodbConnections', globals()) + manage_zodb_conns._setName('manage_zodb_conns') + manage_options = ( + {'label': 'Control Panel', 'action': '../manage_main'}, + {'label': 'Databases', 'action': '../Database/manage_main'}, + {'label': 'Configuration', 'action': '../Configuration/manage_main'}, + {'label': 'DAV Locks', 'action': '../DavLocks/manage_main'}, + {'label': 'Reference Counts', 'action': '../DebugInfo/manage_main'}, + {'label': 'ZODB Connections', 'action': 'manage_main'}, + ) + + def dbconnections(self): + import Zope2 # for data + + result = [] + now = time.time() + + def get_info(connection): + # `result`, `time` and `before` are lexically inherited. + request_info = {} + request_info_formatted = '' + debug_info_formatted = '' + opened = connection.opened + debug_info = connection.getDebugInfo() or {} + + if debug_info: + debug_info_formatted = pprint.pformat(debug_info) + if len(debug_info) == 2: + request_info = debug_info[0] + request_info.update(debug_info[1]) + request_info_formatted = pprint.pformat(request_info) + + if opened is not None: + # output UTC time with the standard Z time zone indicator + open_since = "{}".format( + time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(opened))) + open_for = "{:.2f}s".format(now - opened) + else: + open_since = '(closed)' + open_for = '' + + result.append({ + 'open_since': open_since, + 'open_for': open_for, + 'info': debug_info, + 'info_formatted': debug_info_formatted, + 'request_info': request_info, + 'request_formatted': request_info_formatted, + 'before': connection.before, + 'cache_size': len(connection._cache), + }) + + Zope2.DB._connectionMap(get_info) + return sorted(result, key=itemgetter('open_since')) + + +InitializeClass(ZODBConnectionDebugger) diff --git a/src/App/dtml/dbMain.dtml b/src/App/dtml/dbMain.dtml index 8b480209e7..2fbf311b9b 100644 --- a/src/App/dtml/dbMain.dtml +++ b/src/App/dtml/dbMain.dtml @@ -91,11 +91,22 @@ Total &dtml-cache_length; -   + &dtml-cache_active_and_inactive_count; +

+ + + Cache detail + + + + Cache extreme detail + +

+
- + @@ -14,11 +14,11 @@

- Reference count and database connection information + Python reference count information

Top 100 reference counts

- @@ -28,14 +28,14 @@

Changes since last refresh

- +
- - - - + + + + @@ -49,51 +49,30 @@ -

- - - Cache detail - - - - Cache extreme detail - -

-
- + + - - - - + + +
+ + + +
-

ZODB database connections

-
ClassDeltaClassDelta
- - - - - - - - - - -
OpenedInfo
&dtml-opened;&dtml-info;
-
diff --git a/src/App/dtml/zodbConnections.dtml b/src/App/dtml/zodbConnections.dtml new file mode 100644 index 0000000000..9e7ef6aead --- /dev/null +++ b/src/App/dtml/zodbConnections.dtml @@ -0,0 +1,51 @@ + + + + + +
+ +

+ This page shows ZODB database connections and their current state. The + Duration column tells you how long an open database connection has been + held by the request shown in the Info column next to it. You can use this + view to diagnose long-running requests. +

+ +

ZODB database connections

+ + + + + + + + + + + + + +
Opened atDurationInfo
&dtml-open_since; + +
+ + + + +
+ + + No request information available + +
+
+
+ + - +
+
+ +
+ + diff --git a/src/zmi/styles/resources/zmi_base.css b/src/zmi/styles/resources/zmi_base.css index 855de730b0..fbef5d6dc6 100644 --- a/src/zmi/styles/resources/zmi_base.css +++ b/src/zmi/styles/resources/zmi_base.css @@ -969,6 +969,18 @@ header.navbar select.form-control-sm, box-sizing: border-box; } +.zmi-connection_info { + display: none; +} + +#request_uri { + margin-bottom: 0; +} + +#conn_info { + margin-top: 1rem; +} + /* EXAMPLE: IMPLANT YOUR LOGO */ /* .zmi-manage_menu {