# Compute and gather statistics about garbage collection in this process. # This module depends on the CPython gc module and garbage collection behavior. import gc, logging, pprint verbose = False # A mapping from type objects to a count of instances of those types in the # garbage collectors all objects list on the previous call to # _log_garbage_collector_stats(). _previous_obj_type_map = {} # A set of object ids for everything in the all objects list on the # previous call to _log_garbage_collector_stats(). _previous_obj_ids = set() def _log_garbage_collector_stats(minimum_count=10): """ Log statistics about how many of what type of Python object exist in this process. @param minimum_count: The minimum number of instances of a type for it to be considered worthy of logging. """ global _previous_obj_type_map global _previous_obj_ids # We get all objects -before- creating any new objects within this function. # to avoid having our own local instances in the list. all_objects = gc.get_objects() obj = None new_objects = [] try: obj_type_map = {} object_ids = set() for obj in all_objects: obj_type = type(obj) obj_type_map.setdefault(obj_type, 0) obj_type_map[obj_type] += 1 object_ids.add(id(obj)) whats_new_big_str = '' if verbose and _previous_obj_ids: new_object_ids = object_ids - _previous_obj_ids for obj in all_objects: if id(obj) in new_object_ids: new_objects.append(obj) whats_new_big_str = pprint.pformat(new_objects, indent=1) finally: # Never keep references to stuff returned by gc.get_objects() around # or it'll just make the future cyclic gc runs more difficult. del all_objects del obj del new_objects delta = {} for obj_type, count in obj_type_map.iteritems(): if obj_type not in _previous_obj_type_map: delta[obj_type] = count elif _previous_obj_type_map[obj_type] != count: delta[obj_type] = count - _previous_obj_type_map[obj_type] sorted_stats = reversed(sorted( (count, obj_type) for obj_type, count in obj_type_map.iteritems())) sorted_delta = reversed(sorted( (count, obj_type) for obj_type, count in delta.iteritems())) logging.debug('Garbage collector object type counts:') for count, obj_type in sorted_stats: if count >= minimum_count: logging.debug(' %d\t%s', count, obj_type) logging.info('Change in object counts since previous GC stats:') for change, obj_type in sorted_delta: if obj_type_map[obj_type] > minimum_count: logging.info(' %+d\t%s\tto %d', change, obj_type, obj_type_map[obj_type]) if verbose and whats_new_big_str: logging.debug('Pretty printed representation of the new objects:') logging.debug(whats_new_big_str) _previous_obj_type_map = obj_type_map if verbose: _previous_obj_ids = object_ids