An improved Leak (Uncollected Objects) Gramplet (#345)

* Improved leak detector (debugging tool)

* Fix DummyDb so it garbage collector can reclaim it after close

* Fix leak gramplet so pop-ups use correct transient parent
This commit is contained in:
Paul Culley 2017-07-26 21:15:38 -05:00 committed by Sam Manzi
parent 86fd14613e
commit ebb7111f25
3 changed files with 107 additions and 39 deletions

View File

@ -216,8 +216,9 @@ class DummyDb(M_A_M_B("NewBaseClass", (DbReadBase, Callback, object,), {})):
""" """
Create a new DummyDb instance. Create a new DummyDb instance.
""" """
DbReadBase.__init__(self)
Callback.__init__(self) Callback.__init__(self)
self.basedb = None
self.__feature = {} # {"feature": VALUE, ...}
self.db_is_open = False self.db_is_open = False
self.readonly = True self.readonly = True
self.name_formats = [] self.name_formats = []

View File

@ -1271,7 +1271,7 @@ register(GRAMPLET,
gramps_target_version=MODULE_VERSION, gramps_target_version=MODULE_VERSION,
status = UNSTABLE, status = UNSTABLE,
fname="leak.py", fname="leak.py",
height=200, height=400,
gramplet = 'Leak', gramplet = 'Leak',
gramplet_title="Uncollected Objects", gramplet_title="Uncollected Objects",
) )

141
gramps/plugins/gramplet/leak.py Normal file → Executable file
View File

@ -23,7 +23,13 @@
""" """
Show uncollected objects in a window. Show uncollected objects in a window.
""" """
#------------------------------------------------------------------------
#
# Python modules
#
#------------------------------------------------------------------------
import weakref
import sys
#------------------------------------------------------------------------ #------------------------------------------------------------------------
# #
# GNOME/GTK modules # GNOME/GTK modules
@ -40,10 +46,11 @@ import gc
#------------------------------------------------------------------------ #------------------------------------------------------------------------
from gramps.gen.plug import Gramplet from gramps.gen.plug import Gramplet
from gramps.gui.dialog import InfoDialog from gramps.gui.dialog import InfoDialog
from gramps.gui.utils import is_right_click from gramps.gui.utils import is_right_click, ProgressMeter
from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext _ = glocale.translation.gettext
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# Leak # Leak
@ -58,7 +65,7 @@ class Leak(Gramplet):
self.gui.get_container_widget().remove(self.gui.textview) self.gui.get_container_widget().remove(self.gui.textview)
self.gui.get_container_widget().add(self.gui.WIDGET) self.gui.get_container_widget().add(self.gui.WIDGET)
flags = gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_SAVEALL flags = gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_SAVEALL
if hasattr(gc, "DEBUG_OBJECTS"): if hasattr(gc, "DEBUG_OBJECTS"):
flags = flags | gc.DEBUG_OBJECTS flags = flags | gc.DEBUG_OBJECTS
gc.set_debug(flags) gc.set_debug(flags)
@ -74,24 +81,27 @@ class Leak(Gramplet):
self.top.pack_start(self.label, False, False, 6) self.top.pack_start(self.label, False, False, 6)
self.scroll = Gtk.ScrolledWindow() self.scroll = Gtk.ScrolledWindow()
#add a listview to the scrollable # add a listview to the scrollable
self.list = Gtk.TreeView() self.list = Gtk.TreeView()
self.list.set_headers_visible(True) self.list.set_headers_visible(True)
self.list.connect('button-press-event', self._button_press) self.list.connect('button-press-event', self._button_press)
self.scroll.add(self.list) self.scroll.add(self.list)
#make a model # make a model
self.modeldata = [] self.model = Gtk.ListStore(int, str, str)
self.model = Gtk.ListStore(int, str)
self.list.set_model(self.model) self.list.set_model(self.model)
#set the columns # set the columns
self.renderer = Gtk.CellRendererText() self.renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(_('Number'), self.renderer, text=0) column = Gtk.TreeViewColumn(_('Number'), self.renderer, text=0)
column.set_resizable(True) column.set_resizable(True)
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
self.list.append_column(column) self.list.append_column(column)
column = Gtk.TreeViewColumn(_('Referrer'), self.renderer, text=1)
column.set_resizable(True)
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
self.list.append_column(column)
column = Gtk.TreeViewColumn(_('Uncollected object'), self.renderer, column = Gtk.TreeViewColumn(_('Uncollected object'), self.renderer,
text=1) text=2)
column.set_resizable(True) column.set_resizable(True)
column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.list.append_column(column) self.list.append_column(column)
@ -109,7 +119,9 @@ class Leak(Gramplet):
return self.top return self.top
def main(self): def main(self):
self.display() self.label.set_text(_('Press Refresh to see initial results'))
self.model.clear()
# self.display() # We should only run this on demand
def _button_press(self, obj, event): def _button_press(self, obj, event):
if event.type == Gdk.EventType._2BUTTON_PRESS and event.button == 1: if event.type == Gdk.EventType._2BUTTON_PRESS and event.button == 1:
@ -120,32 +132,52 @@ class Leak(Gramplet):
return True return True
def referenced_in(self): def referenced_in(self):
model, iter = self.selection.get_selected() model, _iter = self.selection.get_selected()
if iter is not None: if _iter is not None:
count = model.get_value(iter, 0) count = model.get_value(_iter, 0)
referrers = gc.get_referrers(self.modeldata[count]) gc.collect(2)
referrers = gc.get_referrers(self.junk[count])
text = "" text = ""
for referrer in referrers: for referrer in referrers:
match = ""
try: try:
text += str(referrer) + '\n' if referrer is not self.junk:
match = "**** "
for indx in range(len(self.junk)):
if referrer is self.junk[indx]:
match = str(indx) + ": "
break
match += str(referrer) + '\n'
except ReferenceError: except ReferenceError:
pass match += 'weakly-referenced object no longer exists %s'\
InfoDialog(_('Referrers of %d') % count, text, % type(referrer)
parent=self.uistate.window) except:
print(sys.exc_info())
text += match
InfoDialog(_('Referrers of %d') % count, text, parent=self.parent)
def refers_to(self): def refers_to(self):
model, iter = self.selection.get_selected() model, _iter = self.selection.get_selected()
if iter is not None: if _iter is not None:
count = model.get_value(iter, 0) count = model.get_value(_iter, 0)
referents = gc.get_referents(self.modeldata[count]) referents = gc.get_referents(self.junk[count])
text = "" text = ""
for referent in referents: for referent in referents:
match = ""
try: try:
text += str(referent) + '\n' match = "****: "
for indx in range(len(self.junk)):
if referent is self.junk[indx]:
match = str(indx) + ': '
break
match += str(referent) + '\n'
except ReferenceError: except ReferenceError:
pass match += '%s weakly-referenced object no longer'\
InfoDialog(_('%d refers to') % count, text, ' exists\n' % type(referent)
parent=self.uistate.window) except:
print(sys.exc_info())
text += match
InfoDialog(_('%d refers to') % count, text, parent=self.parent)
def display(self): def display(self):
try: try:
@ -155,21 +187,56 @@ class Leak(Gramplet):
""" """
Dummy. Dummy.
""" """
gc.collect(2) self.parent = self.top.get_toplevel()
progress = ProgressMeter(
_('Updating display...'), '', parent=self.parent)
self.model.clear() self.model.clear()
count = 0 self.junk = []
if len(gc.garbage): gc.collect(2)
for each in gc.garbage: self.junk = gc.garbage
self.label.set_text(_('Uncollected Objects: %s') %
str(len(self.junk)))
progress.set_pass(_('Updating display...'), len(self.junk))
for count in range(0, len(self.junk)):
progress.step()
try:
refs = []
referrers = gc.get_referrers(self.junk[count])
for referrer in referrers:
try:
if referrer is not self.junk:
for indx in range(0, len(self.junk)):
if referrer is self.junk[indx]:
refs.append(str(indx) + ' ')
break
except:
print(sys.exc_info())
if len(refs) > 3:
ref = ' '.join(refs[0:2]) + "..."
else:
ref = ' '.join(refs)
try: try:
self.modeldata.append(each) self.model.append((count, ref, str(self.junk[count])))
self.model.append((count, str(each)))
except DBError: except DBError:
self.modeldata.append(each) self.model.append((count, ref,
self.model.append((count, 'db.DB instance at %s' % id(each))) 'db.DB instance at %s' %
id(self.junk[count])))
except ReferenceError: except ReferenceError:
pass self.model.append((
count += 1 count, ref,
self.label.set_text(_('Uncollected Objects: %s') % str(len(gc.garbage))) 'weakly-referenced object no longer exists %s'
% type(self.junk[count])))
except TypeError:
self.model.append((
count, ref,
'Object cannot be displayed %s'
% type(self.junk[count])))
except:
print(sys.exc_info())
except ReferenceError:
InfoDialog(_('Reference Error'), "Refresh to correct",
parent=self.parent)
progress.close()
def apply_clicked(self, obj): def apply_clicked(self, obj):
self.display() self.display()