commit-gnue
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnue] r7392 - trunk/gnue-common/src/apps


From: johannes
Subject: [gnue] r7392 - trunk/gnue-common/src/apps
Date: Mon, 18 Apr 2005 04:34:24 -0500 (CDT)

Author: johannes
Date: 2005-04-18 04:34:19 -0500 (Mon, 18 Apr 2005)
New Revision: 7392

Modified:
   trunk/gnue-common/src/apps/GBaseApp.py
Log:
Added --debug-gc to GBaseApp. If this option is given a SIGUSR1 could be sent 
to a descendant of GBaseApp to debug the current state of Python's garbage 
collection (including detection of referece-cycles)


Modified: trunk/gnue-common/src/apps/GBaseApp.py
===================================================================
--- trunk/gnue-common/src/apps/GBaseApp.py      2005-04-18 09:20:40 UTC (rev 
7391)
+++ trunk/gnue-common/src/apps/GBaseApp.py      2005-04-18 09:34:19 UTC (rev 
7392)
@@ -1,6 +1,9 @@
+# GNU Enterprise Common Library - Application Services - Base Application Class
 #
-# This file is part of GNU Enterprise.
+# Copyright 2001-2005 Free Software Foundation
 #
+# This file is part of GNU Enterprise
+#
 # GNU Enterprise 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; either
@@ -16,21 +19,22 @@
 # write to the Free Software Foundation, Inc., 59 Temple Place
 # - Suite 330, Boston, MA 02111-1307, USA.
 #
-# Copyright 2000-2005 Free Software Foundation
-#
-# FILE:
-# GBaseApp.py
-#
-# DESCRIPTION:
+# $Id$
 """
 Class that provides a basis for GNUe applications.
 
-Typically, this class will not be called; rather, a tool will
-be a GClientApp or GServerApp.
+Typically, this class will not be called; rather, a tool will be a GClientApp
+or GServerApp.
 """
 
-import sys, os, getopt, string, types
+import sys
+import os
+import getopt
+import string
+import types
 import ConfigParser
+import gc
+import signal
 
 from gnue import paths
 
@@ -38,9 +42,19 @@
 from gnue.common.datasources import GConnections
 from CommandOption import CommandOption
 
+
+# =============================================================================
+# Exceptions
+# =============================================================================
+
 class StartupError (errors.UserError):
   pass
 
+
+# =============================================================================
+# Base class for GNUe Applications
+# =============================================================================
+
 class GBaseApp:
   """
   The base class of the various GNUe application classes.
@@ -51,41 +65,47 @@
     - An integrated profiler
     - An integrated debugger
   """
+
   # Attributes to be overwritten by subclasses
   VERSION = "0.0.0"
-  NAME = "GNUe Application"
-  COMMAND_OPTIONS = []  # Should be in same format as _base_options below
+  NAME    = "GNUe Application"
+  COMMAND_OPTIONS = []      # Should be in same format as _base_options below
   SUMMARY = "A brief summary of the program goes here."
   COMMAND = "app"
-  USAGE = "[options]"
-  USE_CONNECTIONS = 1       # Set to 1 if the program uses dbdrivers
+  USAGE   = "[options]"
+  USE_CONNECTIONS      = 1  # Set to 1 if the program uses dbdrivers
   USE_DATABASE_OPTIONS = 0  # Also implies USE_CONNECTIONS = 1
-  USE_RPC_OPTIONS = 0
+  USE_RPC_OPTIONS      = 0
 
 
-  # More options, but won't be changed unless
-  # this is a non-GNUe app using GNUe-Common
-  AUTHOR = "GNU Enterprise Project"
-  EMAIL = "address@hidden"
+  # More options, but won't be changed unless this is a non-GNUe app using
+  # GNUe-Common
+  AUTHOR         = "GNU Enterprise Project"
+  EMAIL          = "address@hidden"
   REPORT_BUGS_TO = "Please report any bugs to address@hidden"
-  CONFIGFILE="gnue.conf"
+  CONFIGFILE     = "gnue.conf"
 
-  #  Run the program
-  #  Should be overwritten by subclasses
-  def run(self):
-    pass
 
-
   # Attributes that will be set by GClientApp after __init__ has run
-  OPTIONS = {}        # Will contain a hash containing command line options
-  ARGUMENTS = []      # Will contain an array of command line arguments
+  OPTIONS     = {}    # Will contain a hash containing command line options
+  ARGUMENTS   = []    # Will contain an array of command line arguments
   connections = None  # Will contain a GConnection object
 
-  def __init__(self, connections=None, application='common', defaults=None):
+  # ---------------------------------------------------------------------------
+  # Create a new GNUe Application
+  # ---------------------------------------------------------------------------
 
-    self.configDefaults = defaults 
+  def __init__ (self, connections = None, application = 'common',
+      defaults = None):
+    """
+    @param connections:
+    @param application:
+    @param defaults:
+    """
+
+    self.configDefaults = defaults
     
-    sys.excepthook = self.excepthook    
+    sys.excepthook = self.excepthook
 
     # Basic options
     self._base_options = [
@@ -100,17 +120,16 @@
         help = _('Enables debugging messages.  Argument specifies the '
                  'level of messages to display (e.g., "--debug-level 5" '
                  'displays all debugging messages at level 5 or below.)')),
+
       CommandOption ('debug-file', category = "base", argument = _("filename"),
         help = _('Sends all debugging messages to a specified file '
                  '(e.g., "--debug-file trace.log" sends all output to '
                  '"trace.log")')),
       
-      # This is actually handled during the initial GDebug import
-      # but is added here so that applications won't abort with 
-      # an unknown option 
+      # This is actually handled during the initial GDebug import but is added
+      # here so that applications won't abort with an unknown option.
       CommandOption ('debug-imports', category = "dev",
-        help = _('All python imports are logged to stdout'
-                 )),
+        help = _('All python imports are logged to stdout')),
 
       CommandOption ('silent', category = "base",
         help = _('Displays no output at all.')),
@@ -153,6 +172,13 @@
 
       CommandOption ('interactive-debugger', category = "dev",
         help = _("Run the app inside Python's built-in debugger ")),
+
+      CommandOption ('debug-gc', 'g', 'debug-gc', True, None, "logfile",
+          category = "dev", action = self.__installGCHandler,
+          help = _("Debug Python's garbage collection on a SIGUSR1. If the "
+                   "argument is empty 'garbage.log' will be used as 
logfile.")),
+
+      #CommandOption ('garbagelog', 'l'
     ]
 
     if self.USE_DATABASE_OPTIONS:
@@ -205,16 +231,17 @@
     # Get all command line options and arguments
     #
     shortoptions = ""
-    longoptions = []
-    lookup = {}
-    actions = {}
+    longoptions  = []
+    lookup       = {}
+    actions      = {}
 
     # Convert old-style options to new-style
-    if self.COMMAND_OPTIONS and type(self.COMMAND_OPTIONS[0]) == 
types.ListType:
-      options=self.COMMAND_OPTIONS
+    if self.COMMAND_OPTIONS and \
+        isinstance (self.COMMAND_OPTIONS [0], types.ListType):
+      options = self.COMMAND_OPTIONS
       self.COMMAND_OPTIONS = []
       for option in options:
-        self.COMMAND_OPTIONS.append(CommandOption(*option))
+        self.COMMAND_OPTIONS.append (CommandOption (*option))
 
     for optionset in [self._base_options, self.COMMAND_OPTIONS]:
       for option in optionset:
@@ -294,8 +321,8 @@
       self.run = self._debugger
 
     try:
-      GDebug.setDebug("%s" % self.OPTIONS['debug-level'],
-          self.OPTIONS['debug-file'])
+      GDebug.setDebug ("%s" % self.OPTIONS ['debug-level'],
+                       self.OPTIONS ['debug-file'])
     except ValueError:
        raise StartupError, \
            _('The debug_level option ("-d") expects numerical values.')
@@ -307,8 +334,9 @@
     # Read the config files
     if application:
       try:
-        self.configurationManager = GConfig.GConfig (application, 
self.configDefaults,
-                                              configFilename = self.CONFIGFILE)
+        self.configurationManager = GConfig.GConfig (application,
+                         self.configDefaults, configFilename = self.CONFIGFILE)
+
       except ConfigParser.NoSectionError, msg:
         raise errors.AdminError, \
             u_("The gnue.conf file is incomplete:\n   %s") % msg
@@ -360,38 +388,77 @@
             u_("Unable to load the connections definition file: %s.") \
             % self.connections_file
 
+
+  # ---------------------------------------------------------------------------
+  # Run the program
+  # ---------------------------------------------------------------------------
+
+  def run(self):
+    """
+    Run the program. This function will be overriden by a descendant.
+    """
+
+    pass
+
+
+  # ---------------------------------------------------------------------------
+  # Add a new option to the program
+  # ---------------------------------------------------------------------------
+
   def addCommandOption(self, *args, **parms):
-    self.COMMAND_OPTIONS.append(CommandOption(*args, **parms))
+    """
+    Create a new command option and add it to the options sequence.
 
-  #
-  #  Display version information for this application
-  #
-  def printVersion(self):
+    @param args: positional arguments for the command option's constructor
+    @param parms: keyword arguments for the command option's constructor
+    """
+
+    self.COMMAND_OPTIONS.append (CommandOption (*args, **parms))
+
+
+  # ---------------------------------------------------------------------------
+  # Display version information for this application
+  # ---------------------------------------------------------------------------
+
+  def printVersion (self):
+    """
+    Display version information for this application
+    """
+
     from gnue.common import VERSION as commonVersion
     print o(u_("\n%(name)s\nVersion %(version)s\n") \
              % {'name': self.NAME, 'version': self.VERSION})
     print o(u_("GNUe Common Version %s\n") % commonVersion)
 
 
-  #
-  #  "Help" helpers
-  #
-  def buildHelpOptions(self, category=None):
-    allOptions = {}
+  # ---------------------------------------------------------------------------
+  # Build help options
+  # ---------------------------------------------------------------------------
+
+  def buildHelpOptions (self, category = None):
+    """
+    Build 'help text' for all options of the given category. If no category is
+    given all options except the 'dev' options will be included.
+
+    @param category: if not None only options of this category will be 
included.
+
+    @return: string with the help text for all matching options.
+    """
+
+    allOptions  = {}
     descriptors = {}
-    maxLength = 0
+    maxLength   = 0
 
     for optionset in [self._base_options, self.COMMAND_OPTIONS]:
       for option in optionset:
-
-        # Limit this to the correct category. A category
-        # of None implies all options except "dev".
+        # Limit this to the correct category. A category of None implies all
+        # options except "dev".
         if not (  ( category is None and \
                     option.category != "dev" ) or \
                   ( category == option.category ) ):
           continue
 
-        allOptions[string.upper(option.longOption)] = option
+        allOptions [string.upper (option.longOption)] = option
 
         if option.acceptsArgument:
           descr = '--%s <%s>' % (option.longOption, option.argumentName)
@@ -400,37 +467,36 @@
         if option.shortOption:
           descr += ', -%s' % option.shortOption
 
-        descriptors[string.upper(option.longOption)] = descr
+        descriptors [string.upper (option.longOption)] = descr
 
-        if len(descr) > maxLength:
-          maxLength = len(descr)
+        if len (descr) > maxLength:
+          maxLength = len (descr)
 
     if maxLength > 10:
       maxLength = 10
 
-    sorted = allOptions.keys()
-    sorted.sort()
+    sorted = allOptions.keys ()
+    sorted.sort ()
 
     dispOptions = ""
 
     for optionKey in sorted:
-
       margin = maxLength + 4
-      width = 78 - margin
-      pos = 0
+      width  = 78 - margin
+      pos    = 0
 
-      if len(descriptors[optionKey]) > maxLength:
-        dispOptions += "\n  %s\n%s" % (descriptors[optionKey], " " * margin)
+      if len (descriptors [optionKey]) > maxLength:
+        dispOptions += "\n  %s\n%s" % (descriptors [optionKey], " " * margin)
       else:
-        dispOptions += "\n  %s  %s" % (descriptors[optionKey],
-               " " * (maxLength - len(descriptors[optionKey])))
+        dispOptions += "\n  %s  %s" % (descriptors [optionKey],
+               " " * (maxLength - len (descriptors [optionKey])))
 
-      for word in string.split(allOptions[optionKey].help):
-        if (len(word) + pos) > width:
+      for word in string.split (allOptions [optionKey].help):
+        if (len (word) + pos) > width:
           pos = 0
           dispOptions += "\n" + " " * margin
 
-        pos = pos + len(word) + 1
+        pos = pos + len (word) + 1
 
         dispOptions += word + " "
 
@@ -438,161 +504,241 @@
 
     return dispOptions
 
+
+  # ---------------------------------------------------------------------------
+  # Print a usage header
+  # ---------------------------------------------------------------------------
+
   def printHelpHeader(self):
-    self.printVersion()
+    """
+    Print version information and the usage header
+    """
+
+    self.printVersion ()
     print _("Usage:  ") + self.COMMAND + ' ' + self.USAGE
     print
 
+
+  # ---------------------------------------------------------------------------
+  # Print help footer
+  # ---------------------------------------------------------------------------
+
   def printHelpFooter(self):
+    """
+    Print the help footer including the address for bug reports.
+    """
+
     print
     print "%s\n" % self.REPORT_BUGS_TO
 
-  #
-  #  Display help information for this program
-  #
-  def printHelp(self):
-    self.printVersion()
-    print _("Usage:  ") + self.COMMAND + ' ' + self.USAGE
+
+  # ---------------------------------------------------------------------------
+  # Display help information for this program
+  # ---------------------------------------------------------------------------
+
+  def printHelp (self):
+    """
+    Print help information for this application and quit the program. This
+    includes the version, the usage, the application's summary and all
+    available command options (without the 'developer' options).
+    """
+
+    self.printHelpHeader ()
     print "\n" + self.SUMMARY + '\n'
 
     print _('Available command line options:')
-    print self.buildHelpOptions()
-    self.printHelpFooter()
-    sys.exit()
+    print self.buildHelpOptions ()
+    self.printHelpFooter ()
 
-  #
-  #  Display dev help information for this program
-  #
-  def printHelpDev(self):
-    self.printHelpHeader()
+    sys.exit ()
+
+
+  # ---------------------------------------------------------------------------
+  # Display dev help information for this program
+  # ---------------------------------------------------------------------------
+
+  def printHelpDev (self):
+    """
+    Print help information for this application and quit the program. This
+    includes the version, usage and all available developer's command options.
+    """
+
+    self.printHelpHeader ()
+
     print _("The following options are mainly of interest to GNUe developers.")
     print _("To view general help, run this command with the --help option.")
     print
     print _('Developer-specific command line options:')
-    print self.buildHelpOptions("dev")
-    self.printHelpFooter()
-    sys.exit()
+    print self.buildHelpOptions ("dev")
+    self.printHelpFooter ()
 
-  def printHelpConn(self):
-    self.printHelpHeader()
+    sys.exit ()
+
+
+  # ---------------------------------------------------------------------------
+  # Print connection-specific help information
+  # ---------------------------------------------------------------------------
+
+  def printHelpConn (self):
+    """
+    Print connection/database-related help information and quit the program.
+    """
+
+    self.printHelpHeader ()
+
     print _("The following connection/database-related options are available.")
     print _("To view general help, run this command with the --help option.")
     print
     print _('Database/connection command line options:')
-    print self.buildHelpOptions("connections")
+    print self.buildHelpOptions ("connections")
     print
     print _('The following database drivers are installed on your system:')
     print "   TODO\n"
     # print self.connections.getAvailableDrivers()
-    self.printHelpFooter()
-    sys.exit()
+    self.printHelpFooter ()
 
-  def printHelpRPC(self):
-    self.printHelpHeader()
-    print _("The following options are mainly of interest to GNUe developers.")
-    print _("To view general help, run this command with the --help option.")
-    print
-    print _('Developer-specific command line options:')
-    print self.buildHelpOptions()
-    self.printHelpFooter()
-    sys.exit()
+    sys.exit ()
 
-  def selfdoc(self, command, handle, format=None, options={}):
+
+  # ---------------------------------------------------------------------------
+  # Run the self-documentation for a program
+  # ---------------------------------------------------------------------------
+
+  def selfdoc (self, command, handle, format = None, options = {}):
+    """
+    Run the self-documentation for an application. Currently only the command
+    'manpage' is supported.
+
+    @param command: can be 'manpage' only atm
+    @param handle: file-like object to write the documentation contents to.
+        This file handle must be already opened for writing.
+    @param format: not used in the current version
+    @param options: not used in the current version
+    """
+
     if command == 'manpage':
       import manpage
-      manpage.ManPage(self, handle, format, options)
+      manpage.ManPage (self, handle, format, options)
 
-  #
-  # Converts a
-  #
-  def getCommandLineParameters(self,paramList):
+
+  # ---------------------------------------------------------------------------
+  # 
+  # ---------------------------------------------------------------------------
+
+  def getCommandLineParameters (self, paramList):
+    """
+    Convert a sequence of parameters (i.e. '--foo=bar') into a parameter
+    dictionary, where the paramter ('--foo') is the key and it's argument
+    ('bar') is the value.
+
+    @param paramList: sequence of parameters (usually from self.ARGUMENTS)
+    @return: dictionary of parameters, splitted by the first '='
+
+    @raises StartupError: if a parameter has no value assigned
+    """
+
     parameters = {}
     for param in paramList:
-      psplit = string.split(param,'=',1)
-      if len(psplit) == 1:
+      psplit = string.split (param, '=', 1)
+      if len (psplit) == 1:
         raise StartupError, \
-            'Parameter "%s" specified, but no value supplied.' % psplit[0]
-      parameters[string.lower(psplit[0])] = psplit[1]
+            'Parameter "%s" specified, but no value supplied.' % psplit [0]
+      parameters [string.lower (psplit [0])] = psplit [1]
 
-      gDebug(2,'Param "%s"="%s" ' % \
-                         (string.lower(psplit[0]), psplit[1]))
+      gDebug (2,'Param "%s"="%s" ' % (string.lower (psplit [0]), psplit [1]))
+
     return parameters
 
-  #
-  #  Display a startup error and exit gracefully with a message on
-  #  how to get help
-  #
-  def handleStartupError(self, msg):
-    self.printVersion()
 
+  # ---------------------------------------------------------------------------
+  # Display a startup error and exit gracefully
+  # ---------------------------------------------------------------------------
+
+  def handleStartupError (self, msg):
+    """
+    Display a startup error and exit gracefully. This function is depreciated.
+    Descendants should use the concpet of exceptions instead, which will be
+    handled by the exception hook installed by this class.
+    """
+
+    self.printVersion ()
+
     # if msg is multiline, then surround with dashes to set it apart
-    if string.find("%s" % msg, "\n") + 1:
+    if string.find ("%s" % msg, "\n") + 1:
       print '-' * 60
+
     print o(u_("Error: %s") % msg)
-    if string.find("%s" % msg, "\n") + 1:
+    if string.find ("%s" % msg, "\n") + 1:
       print '-' * 60
+
     print o(u_("\nFor help, type:\n   %s --help\n") % self.COMMAND)
-    sys.exit()
 
+    sys.exit ()
 
-  #
-  # Used when interactive debugger in use
-  #
-  def _debugger(self):
 
-    import pdb
-    debugger = pdb.Pdb()
-    GDebug.setDebugger(debugger)
-    debugger.runctx( 'self._run()', globals(), locals() )
+  # ---------------------------------------------------------------------------
+  # Display version information
+  # ---------------------------------------------------------------------------
 
-  #
-  #  Used when profiling
-  #
-  def _profile(self):
+  def doVersion (self):
+    """
+    Display the version information and quit the program.
+    """
 
-    import profile
-    prof = profile.Profile()
-    prof.runctx( 'self._run()', globals(), locals() )
+    self.printVersion ()
+    sys.exit ()
 
-    import pstats
-    p = pstats.Stats(prof)
-    p.sort_stats('time').print_stats(50)
-    p.sort_stats('cumulative').print_stats(50)
-    p.sort_stats('calls').print_stats(50)
 
+  # ---------------------------------------------------------------------------
+  # Run the self documentation
+  # ---------------------------------------------------------------------------
 
-  #
-  # Commands run by the COMMAND_OPTIONS list
-  #
-  def doVersion(self):
-    self.printVersion()
-    sys.exit()
+  def doSelfDoc (self):
+    """
+    Run the self documentation. If a documentation file is specified the
+    contents will be written to that file, otherwise it will be printed to
+    stdout.
+    """
 
-  def doSelfDoc(self):
-    if self.OPTIONS['selfdoc-file']:
+    if self.OPTIONS ['selfdoc-file']:
       doprint = False
-      handle = open(self.OPTIONS['selfdoc-file'],'w')
+      handle  = open (self.OPTIONS ['selfdoc-file'], 'w')
     else:
       doprint = True
       import StringIO
       handle = StringIO.StringIO
 
-    self.selfdoc(self.OPTIONS['selfdoc'], handle,
-                 self.OPTIONS['selfdoc-format'],
-                 self.OPTIONS['selfdoc-options'])
-    if doprint:
-      handle.seek(0)
-      print handle.read()
-    handle.close()
-    sys.exit()
+    try:
+      self.selfdoc (self.OPTIONS ['selfdoc'], handle,
+                    self.OPTIONS ['selfdoc-format'],
+                    self.OPTIONS ['selfdoc-options'])
+      if doprint:
+        handle.seek (0)
+        print handle.read ()
 
-  def doHelpConfig(self):
-    self.printHelpHeader()
-    print GConfig.printableConfigOptions(self.configDefaults)
-    sys.exit()
+    finally:
+      handle.close ()
 
+    sys.exit ()
 
+
   # ---------------------------------------------------------------------------
+  # Display information about the configuration settings
+  # ---------------------------------------------------------------------------
+
+  def doHelpConfig (self):
+    """
+    Display all configuration settings and their default values and quit the
+    program.
+    """
+
+    self.printHelpHeader ()
+    print GConfig.printableConfigOptions (self.configDefaults)
+
+    sys.exit ()
+
+
+  # ---------------------------------------------------------------------------
   # Catch an exception
   # ---------------------------------------------------------------------------
 
@@ -608,6 +754,43 @@
 
 
   # ---------------------------------------------------------------------------
+  # Used when interactive debugger in use
+  # ---------------------------------------------------------------------------
+
+  def _debugger (self):
+    """
+    Run the application in the python debugger.
+    """
+
+    import pdb
+
+    debugger = pdb.Pdb ()
+    GDebug.setDebugger (debugger)
+    debugger.runctx ('self._run ()', globals (), locals ())
+
+
+  # ---------------------------------------------------------------------------
+  # Used when profiling
+  # ---------------------------------------------------------------------------
+
+  def _profile (self):
+    """
+    Run the application through the python profiler and print some statistics
+    afterwards.
+    """
+
+    import profile
+    prof = profile.Profile ()
+    prof.runctx ('self._run ()', globals (), locals ())
+
+    import pstats
+    p = pstats.Stats (prof)
+    p.sort_stats ('time').print_stats (50)
+    p.sort_stats ('cumulative').print_stats (50)
+    p.sort_stats ('calls').print_stats (50)
+
+
+  # ---------------------------------------------------------------------------
   # Show an exception
   # ---------------------------------------------------------------------------
 
@@ -626,3 +809,215 @@
                            o(u_("For help, type: %s --help") % self.COMMAND))
     else:
       sys.stderr.write ("%s\n" % o(detail))
+
+
+  # ---------------------------------------------------------------------------
+  # Install a signal handler for SIGUSR1
+  # ---------------------------------------------------------------------------
+
+  def __installGCHandler (self):
+    """
+    Enable automatic garbage collection, set it's debug-level to DEBUG_LEAK and
+    install a signal handler for SIGUSR1, which actually performs the
+    debugging.
+    """
+
+    gc.enable
+    gc.set_debug (gc.DEBUG_LEAK)
+
+    signal.signal (signal.SIGUSR1, self.debugGarbageCollection)
+
+
+  # ---------------------------------------------------------------------------
+  # Debug Python's garbage collection
+  # ---------------------------------------------------------------------------
+
+  def debugGarbageCollection (self, signal, frame):
+    """
+    Debug Python's garbage collection.
+
+    @param signal: signal number caught by this handler (=SIGUSR1)
+    @param frame: the current stack frame or None
+    """
+
+    filename = "garbage.log"
+    if isinstance (self.OPTIONS ['debug-gc'], basestring):
+      filename = self.OPTIONS ['debug-gc']
+
+    log = open (filename, 'w')
+    try:
+      unreachable = gc.collect ()
+
+      try:
+        self.__gcLog (log, "Number of unreachable objects: %d" % unreachable)
+        self.__gcLog (log,
+                      "Items in gc.garbage sequence : %d" % len (gc.garbage))
+        self.__gcLog (log, "Dump of gc.garbage sequence:")
+        self.__gcLog (log, "-" * 70)
+
+        for item in gc.garbage:
+          try:
+            itemrep = "%s: %s" % (type (item), repr (item))
+            cycle   = self.findCycle (item, item)
+
+          except:
+            itemrep = "No representation available for object (weakref/proxy?)"
+            cycle   = None
+
+          self.__gcLog (log, "%s" % itemrep)
+
+          if cycle:
+            self.__gcLog (log, "=" * 70)
+
+            for line in self.analyzeCycle (cycle):
+              self.__gcLog (log, line)
+
+            self.__gcLog (log, "=" * 70)
+
+        self.__gcLog (log, "-" * 70)
+
+      finally:
+        del gc.garbage [:]
+
+    finally:
+      log.close ()
+
+
+  # ---------------------------------------------------------------------------
+
+  def __gcLog (self, filehandle, message):
+
+    filehandle.write ("%s%s" % (message, os.linesep))
+    print message
+
+
+  # ---------------------------------------------------------------------------
+  # Find a reference cycle starting from a given object
+  # ---------------------------------------------------------------------------
+
+  def findCycle (self, search, current, last = None, path = [], seen = {}):
+    """
+    Find a reference cycle starting from a given object (current) and ending
+    with a given object (search). The result is either None if no such cycle
+    exists, or a sequence of tuples (repr, propertyname) describing the
+    reference cycle. 'repr' is either a string representation of an object
+    holding the reference or None. 'propertyname' could be one of the
+    following:
+      'name'    name of the property within 'repr' holding the reference
+      '[n]'     the reference is the n-th element of a sequence
+      '[name']  the reference is the value of key 'name' in a dictionary
+      '{}'      the reference is a key in a dictionary
+
+    The latter three variants could be cumulative (i.e. [1][3]['foo']) and the
+    corresponding propertyname is the last one encountered.
+
+    @return: None or sequence of tuples (repr, propertyname)
+    """
+
+    if last is not None:
+      path = path + [last]
+
+    currentId = id (current)
+
+    # If we have already visited this object, no need to do further processing
+    if seen.has_key (currentId):
+      return None
+
+    seen [currentId] = path
+
+    # If the current object has a __dict__ property, iterate over all it's
+    # properties
+    if hasattr (current, '__dict__'):
+      for (name, attr) in current.__dict__.items ():
+        prop = (repr (current), name)
+
+        if attr == search:
+          return path + [prop]
+
+        else:
+          newpath = self.findCycle (search, attr, prop, path, seen)
+          if newpath:
+            return newpath
+
+    # A bound method has a reference to self
+    elif isinstance (current, types.MethodType):
+      if current.im_self == search:
+        return path + [(repr (current), "im_self")]
+
+    # For Sequences or Tuples iterate over all elements
+    elif isinstance (current, types.ListType) or \
+         isinstance (current, types.TupleType):
+
+      for (index, element) in enumerate (current):
+        prop = (None, "[%d]" % index)
+        if element == search:
+          return path + [prop]
+
+        else:
+          newpath = self.findCycle (search, element, prop, path, seen)
+          if newpath:
+            return newpath
+
+    # For dictionaries iterate over all items
+    elif isinstance (current, types.DictType):
+      for (key, element) in current.items ():
+        prop = (None, "[%s]" % key)
+
+        if element == search:
+          return path + [prop]
+
+        elif  key == search:
+          return path + [(None, "{}")]
+
+        else:
+          newpath = self.findCycle (search, element, prop, path, seen)
+          if newpath:
+            return newpath
+
+    # a generator keeps has always reference to self, so we have to iterate
+    # it's local namespace
+    elif isinstance (current, types.GeneratorType):
+      for (key, element) in current.gi_frame.f_locals.items ():
+        prop = (None, "[%s]" % key)
+        if element == search:
+          return path + [prop]
+
+        elif key == search:
+          return path + [(None, "{}")]
+
+        else:
+          newpath = self.findCycle (search, element, prop, path, seen)
+          if newpath:
+            return newpath
+
+    return None
+
+
+  # ---------------------------------------------------------------------------
+  # Analyze a given reference cycle
+  # ---------------------------------------------------------------------------
+
+  def analyzeCycle (self, cycle):
+    """
+    Return a generator for iterating a given reference cycle.
+
+    @param cycle: None or a sequence of tuples (repr, propertyname)
+    @return: iterator
+    """
+
+    if cycle:
+      for (index, (rep, name)) in enumerate (cycle):
+        if index == 0:
+          yield 'self = %s' % rep
+          lastname = 'self.%s' % name
+        else:
+          if rep is not None:
+            yield '%s = %s' % (lastname, rep)
+            jsymb = '.'
+          else:
+            jsymb = ' '
+
+          lastname = jsymb.join ([lastname, name])
+
+      yield '%s = self' % lastname
+





reply via email to

[Prev in Thread] Current Thread [Next in Thread]