1# Compute and gather statistics about garbage collection in this process.
2# This module depends on the CPython gc module and garbage collection behavior.
3
4import gc, logging, pprint
5
6
7verbose = False
8
9
10# A mapping from type objects to a count of instances of those types in the
11# garbage collectors all objects list on the previous call to
12# _log_garbage_collector_stats().
13_previous_obj_type_map = {}
14
15
16# A set of object ids for everything in the all objects list on the
17# previous call to _log_garbage_collector_stats().
18_previous_obj_ids = set()
19
20
21def _log_garbage_collector_stats(minimum_count=10):
22    """
23    Log statistics about how many of what type of Python object exist in this
24    process.
25
26    @param minimum_count: The minimum number of instances of a type for it
27            to be considered worthy of logging.
28    """
29    global _previous_obj_type_map
30    global _previous_obj_ids
31
32    # We get all objects -before- creating any new objects within this function.
33    # to avoid having our own local instances in the list.
34    all_objects = gc.get_objects()
35    obj = None
36    new_objects = []
37    try:
38        obj_type_map = {}
39        object_ids = set()
40        for obj in all_objects:
41            obj_type = type(obj)
42            obj_type_map.setdefault(obj_type, 0)
43            obj_type_map[obj_type] += 1
44            object_ids.add(id(obj))
45        whats_new_big_str = ''
46        if verbose and _previous_obj_ids:
47            new_object_ids = object_ids - _previous_obj_ids
48            for obj in all_objects:
49                if id(obj) in new_object_ids:
50                    new_objects.append(obj)
51            whats_new_big_str = pprint.pformat(new_objects, indent=1)
52    finally:
53        # Never keep references to stuff returned by gc.get_objects() around
54        # or it'll just make the future cyclic gc runs more difficult.
55        del all_objects
56        del obj
57        del new_objects
58
59
60    delta = {}
61    for obj_type, count in obj_type_map.iteritems():
62        if obj_type not in _previous_obj_type_map:
63            delta[obj_type] = count
64        elif _previous_obj_type_map[obj_type] != count:
65            delta[obj_type] = count - _previous_obj_type_map[obj_type]
66
67    sorted_stats = reversed(sorted(
68            (count, obj_type) for obj_type, count in obj_type_map.iteritems()))
69    sorted_delta = reversed(sorted(
70            (count, obj_type) for obj_type, count in delta.iteritems()))
71
72    logging.debug('Garbage collector object type counts:')
73    for count, obj_type in sorted_stats:
74        if count >= minimum_count:
75            logging.debug('  %d\t%s', count, obj_type)
76
77    logging.info('Change in object counts since previous GC stats:')
78    for change, obj_type in sorted_delta:
79        if obj_type_map[obj_type] > minimum_count:
80            logging.info('  %+d\t%s\tto %d', change, obj_type,
81                         obj_type_map[obj_type])
82
83    if verbose and whats_new_big_str:
84        logging.debug('Pretty printed representation of the new objects:')
85        logging.debug(whats_new_big_str)
86
87    _previous_obj_type_map = obj_type_map
88    if verbose:
89        _previous_obj_ids = object_ids
90