| # 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 |