• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Zope / 6653239671

26 Oct 2023 11:08AM UTC coverage: 82.184% (-0.05%) from 82.237%
6653239671

Pull #1178

github

dataflake
Merge branch 'dataflake/better_zmi_debugging' of github.com:zopefoundation/Zope into dataflake/better_zmi_debugging
Pull Request #1178: Separate ZODB connection information into new ZODB Connections view

4368 of 6983 branches covered (0.0%)

Branch coverage included in aggregate %.

49 of 49 new or added lines in 2 files covered. (100.0%)

27571 of 31880 relevant lines covered (86.48%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.83
/src/App/ApplicationManager.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE.
11
#
12
##############################################################################
13

14
import os
1✔
15
import sys
1✔
16
import time
1✔
17
import types
1✔
18
from _thread import get_ident
1✔
19
from urllib import parse
1✔
20

21
from AccessControl.class_init import InitializeClass
1✔
22
from AccessControl.requestmethod import requestmethod
1✔
23
from Acquisition import Implicit
1✔
24
from App.CacheManager import CacheManager
1✔
25
from App.config import getConfiguration
1✔
26
from App.DavLockManager import DavLockManager
1✔
27
from App.Management import Tabs
1✔
28
from App.special_dtml import DTMLFile
1✔
29
from App.Undo import UndoSupport
1✔
30
from App.version_txt import version_txt
1✔
31
from App.ZODBConnectionDebugger import ZODBConnectionDebugger
1✔
32
from DateTime.DateTime import DateTime
1✔
33
from OFS.Traversable import Traversable
1✔
34
from Persistence import Persistent
1✔
35
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
1✔
36

37

38
class FakeConnection:
1✔
39
    # Supports the methods of Connection that CacheManager needs
40

41
    def __init__(self, db, parent_jar):
1✔
42
        self._db = db
1✔
43

44
    def db(self):
1✔
45
        return self._db
1✔
46

47

48
class DatabaseChooser(Tabs, Traversable, Implicit):
1✔
49
    """ Choose which database to view
50
    """
51

52
    __allow_access_to_unprotected_subobjects__ = 1
1✔
53

54
    id = 'Database'
1✔
55
    name = title = 'Database Management'
1✔
56
    meta_type = 'Database Management'
1✔
57

58
    manage_main = manage_workspace = PageTemplateFile('www/chooseDatabase.pt',
1✔
59
                                                      globals())
60
    manage_main.__name__ = 'manage_main'
1✔
61
    manage_main._need__name__ = 0
1✔
62
    manage_options = (
1✔
63
        {'label': 'Control Panel', 'action': '../manage_main'},
64
        {'label': 'Databases', 'action': 'manage_main'},
65
        {'label': 'Configuration', 'action': '../Configuration/manage_main'},
66
        {'label': 'DAV Locks', 'action': '../DavLocks/manage_main'},
67
        {'label': 'Reference Counts', 'action': '../DebugInfo/manage_main'},
68
        {'label': 'ZODB Connections',
69
         'action': '../ZODBConnections/manage_main'},
70
    )
71
    MANAGE_TABS_NO_BANNER = True
1✔
72

73
    def getDatabaseNames(self, quote=False):
1✔
74
        configuration = getConfiguration()
1✔
75
        names = configuration.dbtab.listDatabaseNames()
1✔
76
        names.sort()
1✔
77
        if quote:
1✔
78
            return [(name, parse.quote(name)) for name in names]
1✔
79
        return names
1✔
80

81
    def __getitem__(self, name):
1✔
82
        configuration = getConfiguration()
1✔
83
        db = configuration.dbtab.getDatabase(name=name)
1✔
84
        m = AltDatabaseManager()
1✔
85
        m.id = name
1✔
86
        m._p_jar = FakeConnection(db, self.getPhysicalRoot()._p_jar)
1✔
87
        return m.__of__(self)
1✔
88

89
    def __bobo_traverse__(self, request, name):
1✔
90
        configuration = getConfiguration()
1✔
91
        if configuration.dbtab.hasDatabase(name):
1✔
92
            return self[name]
1✔
93
        return getattr(self, name)
1✔
94

95

96
InitializeClass(DatabaseChooser)
1✔
97

98

99
class ConfigurationViewer(Tabs, Traversable, Implicit):
1✔
100
    """ Provides information about the running configuration
101
    """
102
    manage = manage_main = manage_workspace = DTMLFile('dtml/cpConfiguration',
1✔
103
                                                       globals())
104
    manage_main._setName('manage_main')
1✔
105
    id = 'Configuration'
1✔
106
    name = title = 'Configuration Viewer'
1✔
107
    meta_type = name
1✔
108
    zmi_icon = 'fa fa-cog'
1✔
109
    manage_options = (
1✔
110
        {'label': 'Control Panel', 'action': '../manage_main'},
111
        {'label': 'Databases', 'action': '../Database/manage_main'},
112
        {'label': 'Configuration', 'action': 'manage_main'},
113
        {'label': 'DAV Locks', 'action': '../DavLocks/manage_main'},
114
        {'label': 'Reference Counts', 'action': '../DebugInfo/manage_main'},
115
        {'label': 'ZODB Connections',
116
         'action': '../ZODBConnections/manage_main'},
117
    )
118
    MANAGE_TABS_NO_BANNER = True
1✔
119

120
    def manage_getSysPath(self):
1✔
121
        return sorted(sys.path)
1✔
122

123
    def manage_getConfiguration(self):
1✔
124
        config_results = []
1✔
125
        config = getConfiguration()
1✔
126

127
        try:
1✔
128
            keys = config.getSectionAttributes()
1✔
129
        except AttributeError:
1✔
130
            # This happens in unit tests with a DefaultConfiguration object
131
            keys = config.__dict__.keys()
1✔
132

133
        for key in keys:
1✔
134

135
            # Databases are visible on the Database Chooser already
136
            if key == 'databases':
1!
137
                continue
×
138

139
            config_results.append({'name': key,
1✔
140
                                   'value': str(getattr(config, key))})
141
        return config_results
1✔
142

143

144
InitializeClass(ConfigurationViewer)
1✔
145

146

147
# refcount snapshot info
148
_v_rcs = None
1✔
149
_v_rst = None
1✔
150

151

152
class DebugManager(Tabs, Traversable, Implicit):
1✔
153
    """ Debug and profiling information
154
    """
155
    manage = manage_main = manage_workspace = DTMLFile('dtml/debug', globals())
1✔
156
    manage_main._setName('manage_main')
1✔
157
    id = 'DebugInfo'
1✔
158
    name = title = 'Reference Counts'
1✔
159
    meta_type = name
1✔
160
    zmi_icon = 'fas fa-bug'
1✔
161

162
    manage_options = (
1✔
163
        {'label': 'Control Panel', 'action': '../manage_main'},
164
        {'label': 'Databases', 'action': '../Database/manage_main'},
165
        {'label': 'Configuration', 'action': '../Configuration/manage_main'},
166
        {'label': 'DAV Locks', 'action': '../DavLocks/manage_main'},
167
        {'label': 'Reference Counts', 'action': 'manage_main'},
168
        {'label': 'ZODB Connections',
169
         'action': '../ZODBConnections/manage_main'},
170
    )
171

172
    def refcount(self, n=None, t=(type(Implicit), type(object))):
1✔
173
        # return class reference info
174
        counts = {}
1✔
175
        for m in list(sys.modules.values()):
1✔
176
            if m is None:
1!
177
                continue
×
178
            if not isinstance(m, types.ModuleType) or 'six.' in m.__name__:
1!
179
                continue
×
180
            for sym in dir(m):
1✔
181
                ob = getattr(m, sym)
1✔
182
                if type(ob) in t:
1✔
183
                    counts[ob] = sys.getrefcount(ob)
1✔
184
        pairs = []
1✔
185
        for ob, v in counts.items():
1✔
186
            ob_name = getattr(ob, "__name__", "unknown")
1✔
187
            if hasattr(ob, '__module__'):
1!
188
                name = f'{ob.__module__}.{ob_name}'
1✔
189
            else:
190
                name = '%s' % ob_name
×
191
            pairs.append((v, name))
1✔
192
        pairs.sort()
1✔
193
        pairs.reverse()
1✔
194
        if n is not None:
1!
195
            pairs = pairs[:n]
×
196
        return pairs
1✔
197

198
    def refdict(self):
1✔
199
        counts = {}
1✔
200
        for v, n in self.refcount():
1✔
201
            counts[n] = v
1✔
202
        return counts
1✔
203

204
    def rcsnapshot(self):
1✔
205
        global _v_rcs
206
        global _v_rst
207
        _v_rcs = self.refdict()
1✔
208
        _v_rst = DateTime()
1✔
209

210
    def rcdate(self):
1✔
211
        return _v_rst
1✔
212

213
    def rcdeltas(self):
1✔
214
        if _v_rcs is None:
1!
215
            self.rcsnapshot()
×
216
        nc = self.refdict()
1✔
217
        rc = _v_rcs
1✔
218
        rd = []
1✔
219
        for n, c in nc.items():
1✔
220
            try:
1✔
221
                prev = rc.get(n, 0)
1✔
222
                if c > prev:
1✔
223
                    rd.append((c - prev, (c, prev, n)))
1✔
224
            except Exception:
×
225
                pass
×
226
        rd.sort()
1✔
227
        rd.reverse()
1✔
228
        return [{'name': n[1][2],
1✔
229
                 'delta': n[0],
230
                 'pc': n[1][1],
231
                 'rc': n[1][0],
232
                 } for n in rd]
233

234
    def manage_getSysPath(self):
1✔
235
        return list(sys.path)
1✔
236

237

238
InitializeClass(DebugManager)
1✔
239

240

241
class ApplicationManager(Persistent,
1✔
242
                         Tabs,
243
                         Traversable,
244
                         Implicit):
245
    """System management
246
    """
247
    __allow_access_to_unprotected_subobjects__ = 1
1✔
248
    __roles__ = ('Manager',)
1✔
249

250
    id = 'Control_Panel'
1✔
251
    name = title = 'Control Panel'
1✔
252
    meta_type = 'Control Panel'
1✔
253
    zmi_icon = 'fa fa-cog'
1✔
254
    process_start = int(time.time())
1✔
255

256
    Database = DatabaseChooser()
1✔
257
    Configuration = ConfigurationViewer()
1✔
258
    DavLocks = DavLockManager()
1✔
259
    DebugInfo = DebugManager()
1✔
260
    ZODBConnections = ZODBConnectionDebugger()
1✔
261

262
    manage = manage_main = DTMLFile('dtml/cpContents', globals())
1✔
263
    manage_main._setName('manage_main')
1✔
264
    manage_options = (
1✔
265
        {'label': 'Control Panel', 'action': 'manage_main'},
266
        {'label': 'Databases', 'action': 'Database/manage_main'},
267
        {'label': 'Configuration', 'action': 'Configuration/manage_main'},
268
        {'label': 'DAV Locks', 'action': 'DavLocks/manage_main'},
269
        {'label': 'Reference Counts', 'action': 'DebugInfo/manage_main'},
270
        {'label': 'ZODB Connections', 'action': 'ZODBConnections/manage_main'},
271
    )
272
    MANAGE_TABS_NO_BANNER = True
1✔
273

274
    def version_txt(self):
1✔
275
        if not hasattr(self, '_v_version_txt'):
1!
276
            self._v_version_txt = version_txt()
1✔
277

278
        return self._v_version_txt
1✔
279

280
    def process_id(self):
1✔
281
        return os.getpid()
×
282

283
    def process_time(self, _when=None):
1✔
284
        if _when is None:
1!
285
            _when = time.time()
×
286
        s = int(_when) - self.process_start
1✔
287
        d = int(s / 86400)
1✔
288
        s = s - (d * 86400)
1✔
289
        h = int(s / 3600)
1✔
290
        s = s - (h * 3600)
1✔
291
        m = int(s / 60)
1✔
292
        s = s - (m * 60)
1✔
293
        d = d and ('%d day%s' % (d, (d != 1 and 's' or ''))) or ''
1✔
294
        h = h and ('%d hour%s' % (h, (h != 1 and 's' or ''))) or ''
1✔
295
        m = m and ('%d min' % m) or ''
1✔
296
        s = '%d sec' % s
1✔
297
        return f'{d} {h} {m} {s}'
1✔
298

299
    def sys_version(self):
1✔
300
        return sys.version
1✔
301

302
    def sys_platform(self):
1✔
303
        return sys.platform
1✔
304

305
    def thread_get_ident(self):
1✔
306
        return get_ident()
×
307

308
    def debug_mode(self):
1✔
309
        return getConfiguration().debug_mode
×
310

311
    def getINSTANCE_HOME(self):
1✔
312
        return getConfiguration().instancehome
1✔
313

314
    def getCLIENT_HOME(self):
1✔
315
        return getConfiguration().clienthome
1✔
316

317

318
class AltDatabaseManager(CacheManager, Traversable, UndoSupport):
1✔
319
    """ Database management DBTab-style
320
    """
321
    id = 'DatabaseManagement'
1✔
322
    name = title = 'Database Management'
1✔
323
    meta_type = 'Database Management'
1✔
324

325
    manage = manage_main = DTMLFile('dtml/dbMain', globals())
1✔
326
    manage_main._setName('manage_main')
1✔
327
    manage_options = (
1✔
328
        {'label': 'Control Panel', 'action': '../../manage_main'},
329
        {'label': 'Databases', 'action': '../manage_main'},
330
        {'label': 'Database', 'action': 'manage_main'},
331
    ) + UndoSupport.manage_options
332
    MANAGE_TABS_NO_BANNER = True
1✔
333

334
    def _getDB(self):
1✔
335
        return self._p_jar.db()
1✔
336

337
    def cache_length(self):
1✔
338
        return self._getDB().cacheSize()
×
339

340
    def cache_active_and_inactive_count(self):
1✔
341
        return sum([x['size'] for x in self._getDB().cacheDetailSize()])
×
342

343
    def cache_length_bytes(self):
1✔
344
        return self._getDB().getCacheSizeBytes()
×
345

346
    def cache_detail_length(self):
1✔
347
        return self._getDB().cacheDetailSize()
×
348

349
    def cache_size(self):
1✔
350
        db = self._getDB()
1✔
351
        return db.getCacheSize()
1✔
352

353
    def database_size(self):
1✔
354
        return self._getDB().objectCount()
×
355

356
    def db_name(self):
1✔
357
        return self._getDB().getName()
1✔
358

359
    def db_size(self):
1✔
360
        s = self._getDB().getSize()
1✔
361
        if isinstance(s, str):
1✔
362
            return s
1✔
363

364
        if s >= 1048576.0:
1✔
365
            return '%.1fM' % (s / 1048576.0)
1✔
366
        return '%.1fK' % (s / 1024.0)
1✔
367

368
    @requestmethod('POST')
1✔
369
    def manage_minimize(self, value=1, REQUEST=None):
1✔
370
        "Perform a full sweep through the cache"
371
        # XXX Add a deprecation warning about value?
372
        self._getDB().cacheMinimize()
×
373

374
        if REQUEST is not None:
×
375
            msg = 'ZODB in-memory caches minimized.'
×
376
            url = f'{REQUEST["URL1"]}/manage_main?manage_tabs_message={msg}'
×
377
            REQUEST.RESPONSE.redirect(url)
×
378

379
    @requestmethod('POST')
1✔
380
    def manage_pack(self, days=0, REQUEST=None):
1✔
381
        """Pack the database"""
382
        if not isinstance(days, (int, float)):
1✔
383
            try:
1✔
384
                days = float(days)
1✔
385
            except ValueError:
1✔
386
                days = None
1✔
387

388
        if days is not None:
1✔
389
            t = time.time() - (days * 86400)
1✔
390
            self._getDB().pack(t)
1✔
391
            msg = 'Database packed to %s days' % str(days)
1✔
392
        else:
393
            t = None
1✔
394
            msg = 'Invalid days value %s' % str(days)
1✔
395

396
        if REQUEST is not None:
1!
397
            url = f'{REQUEST["URL1"]}/manage_main?manage_tabs_message={msg}'
×
398
            REQUEST['RESPONSE'].redirect(url)
×
399

400
        return t
1✔
401

402

403
InitializeClass(AltDatabaseManager)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc