summaryrefslogtreecommitdiffstats
path: root/venv/lib/python3.9/site-packages/pympler/tracker.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.9/site-packages/pympler/tracker.py')
-rw-r--r--venv/lib/python3.9/site-packages/pympler/tracker.py267
1 files changed, 267 insertions, 0 deletions
diff --git a/venv/lib/python3.9/site-packages/pympler/tracker.py b/venv/lib/python3.9/site-packages/pympler/tracker.py
new file mode 100644
index 00000000..ec88e116
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pympler/tracker.py
@@ -0,0 +1,267 @@
+"""The tracker module allows you to track changes in the memory usage over
+time.
+
+Using the SummaryTracker, you can create summaries and compare them
+with each other. Stored summaries can be ignored during comparison,
+avoiding the observer effect.
+
+The ObjectTracker allows to monitor object creation. You create objects from
+one time and compare with objects from an earlier time.
+
+"""
+import gc
+import inspect
+
+from pympler import muppy, summary
+from pympler.util import compat
+
+
+class SummaryTracker(object):
+ """ Helper class to track changes between two summaries taken.
+
+ Detailed information on single objects will be lost, e.g. object size or
+ object id. But often summaries are sufficient to monitor the memory usage
+ over the lifetime of an application.
+
+ On initialisation, a first summary is taken. Every time `diff` is called,
+ a new summary will be created. Thus, a diff between the new and the last
+ summary can be extracted.
+
+ Be aware that filtering out previous summaries is time-intensive. You
+ should therefore restrict yourself to the number of summaries you really
+ need.
+
+ """
+ def __init__(self, ignore_self=True):
+ """Constructor.
+
+ The number of summaries managed by the tracker has a performance
+ impact on new summaries, iff you decide to exclude them from further
+ summaries. Therefore it is suggested to use them economically.
+
+ Keyword arguments:
+ ignore_self -- summaries managed by this object will be ignored.
+ """
+ self.s0 = summary.summarize(muppy.get_objects())
+ self.summaries = {}
+ self.ignore_self = ignore_self
+
+ def create_summary(self):
+ """Return a summary.
+
+ See also the notes on ignore_self in the class as well as the
+ initializer documentation.
+
+ """
+ if not self.ignore_self:
+ res = summary.summarize(muppy.get_objects())
+ else:
+ # If the user requested the data required to store summaries to be
+ # ignored in the summaries, we need to identify all objects which
+ # are related to each summary stored.
+ # Thus we build a list of all objects used for summary storage as
+ # well as a dictionary which tells us how often an object is
+ # referenced by the summaries.
+ # During this identification process, more objects are referenced,
+ # namely int objects identifying referenced objects as well as the
+ # corresponding count.
+ # For all these objects it will be checked whether they are
+ # referenced from outside the monitor's scope. If not, they will be
+ # subtracted from the snapshot summary, otherwise they are
+ # included (as this indicates that they are relevant to the
+ # application).
+
+ all_of_them = [] # every single object
+ ref_counter = {} # how often it is referenced; (id(o), o) pairs
+
+ def store_info(o):
+ all_of_them.append(o)
+ if id(o) in ref_counter:
+ ref_counter[id(o)] += 1
+ else:
+ ref_counter[id(o)] = 1
+
+ # store infos on every single object related to the summaries
+ store_info(self.summaries)
+ for k, v in self.summaries.items():
+ store_info(k)
+ summary._traverse(v, store_info)
+
+ # do the summary
+ res = summary.summarize(muppy.get_objects())
+
+ # remove ids stored in the ref_counter
+ for _id in ref_counter:
+ # referenced in frame, ref_counter, ref_counter.keys()
+ if len(gc.get_referrers(_id)) == (3):
+ summary._subtract(res, _id)
+ for o in all_of_them:
+ # referenced in frame, summary, all_of_them
+ if len(gc.get_referrers(o)) == (ref_counter[id(o)] + 2):
+ summary._subtract(res, o)
+
+ return res
+
+ def diff(self, summary1=None, summary2=None):
+ """Compute diff between to summaries.
+
+ If no summary is provided, the diff from the last to the current
+ summary is used. If summary1 is provided the diff from summary1
+ to the current summary is used. If summary1 and summary2 are
+ provided, the diff between these two is used.
+
+ """
+ res = None
+ if summary2 is None:
+ self.s1 = self.create_summary()
+ if summary1 is None:
+ res = summary.get_diff(self.s0, self.s1)
+ else:
+ res = summary.get_diff(summary1, self.s1)
+ self.s0 = self.s1
+ else:
+ if summary1 is not None:
+ res = summary.get_diff(summary1, summary2)
+ else:
+ raise ValueError(
+ "You cannot provide summary2 without summary1.")
+ return summary._sweep(res)
+
+ def print_diff(self, summary1=None, summary2=None):
+ """Compute diff between to summaries and print it.
+
+ If no summary is provided, the diff from the last to the current
+ summary is used. If summary1 is provided the diff from summary1
+ to the current summary is used. If summary1 and summary2 are
+ provided, the diff between these two is used.
+ """
+ summary.print_(self.diff(summary1=summary1, summary2=summary2))
+
+ def format_diff(self, summary1=None, summary2=None):
+ """Compute diff between to summaries and return a list of formatted
+ lines.
+
+ If no summary is provided, the diff from the last to the current
+ summary is used. If summary1 is provided the diff from summary1
+ to the current summary is used. If summary1 and summary2 are
+ provided, the diff between these two is used.
+ """
+ return summary.format_(self.diff(summary1=summary1, summary2=summary2))
+
+ def store_summary(self, key):
+ """Store a current summary in self.summaries."""
+ self.summaries[key] = self.create_summary()
+
+
+class ObjectTracker(object):
+ """
+ Helper class to track changes in the set of existing objects.
+
+ Each time you invoke a diff with this tracker, the objects which existed
+ during the last invocation are compared with the objects which exist during
+ the current invocation.
+
+ Please note that in order to do so, strong references to all objects will
+ be stored. This means that none of these objects can be garbage collected.
+ A use case for the ObjectTracker is the monitoring of a state which should
+ be stable, but you see new objects being created nevertheless. With the
+ ObjectTracker you can identify these new objects.
+
+ """
+
+ # Some precaution needs to be taken when handling frame objects (see
+ # warning at http://docs.python.org/lib/inspect-stack.html). All ignore
+ # lists used need to be emptied so no frame objects remain referenced.
+
+ def __init__(self):
+ """On initialisation, the current state of objects is stored.
+
+ Note that all objects which exist at this point in time will not be
+ released until you destroy this ObjectTracker instance.
+ """
+ self.o0 = self._get_objects(ignore=(inspect.currentframe(),))
+
+ def _get_objects(self, ignore=()):
+ """Get all currently existing objects.
+
+ XXX - ToDo: This method is a copy&paste from muppy.get_objects, but
+ some modifications are applied. Specifically, it allows to ignore
+ objects (which includes the current frame).
+
+ keyword arguments
+ ignore -- list of objects to ignore
+ """
+ def remove_ignore(objects, ignore=()):
+ # remove all objects listed in the ignore list
+ res = []
+ for o in objects:
+ if not compat.object_in_list(o, ignore):
+ res.append(o)
+ return res
+
+ tmp = gc.get_objects()
+ ignore += (inspect.currentframe(), self, ignore, remove_ignore)
+ if hasattr(self, 'o0'):
+ ignore += (self.o0,)
+ if hasattr(self, 'o1'):
+ ignore += (self.o1,)
+ # this implies that referenced objects are also ignored
+ tmp = remove_ignore(tmp, ignore)
+ res = []
+ for o in tmp:
+ # gc.get_objects returns only container objects, but we also want
+ # the objects referenced by them
+ refs = muppy.get_referents(o)
+ for ref in refs:
+ if not gc.is_tracked(ref):
+ # we already got the container objects, now we only add
+ # non-container objects
+ res.append(ref)
+ res.extend(tmp)
+ res = muppy._remove_duplicates(res)
+ if ignore is not None:
+ # repeat to filter out objects which may have been referenced
+ res = remove_ignore(res, ignore)
+ # manual cleanup, see comment above
+ del ignore
+ return res
+
+ def get_diff(self, ignore=()):
+ """Get the diff to the last time the state of objects was measured.
+
+ keyword arguments
+ ignore -- list of objects to ignore
+ """
+ # ignore this and the caller frame
+ self.o1 = self._get_objects(ignore+(inspect.currentframe(),))
+ diff = muppy.get_diff(self.o0, self.o1)
+ self.o0 = self.o1
+ # manual cleanup, see comment above
+ return diff
+
+ def print_diff(self, ignore=()):
+ """Print the diff to the last time the state of objects was measured.
+
+ keyword arguments
+ ignore -- list of objects to ignore
+ """
+ # ignore this and the caller frame
+ for line in self.format_diff(ignore+(inspect.currentframe(),)):
+ print(line)
+
+ def format_diff(self, ignore=()):
+ """Format the diff to the last time the state of objects was measured.
+
+ keyword arguments
+ ignore -- list of objects to ignore
+ """
+ # ignore this and the caller frame
+ lines = []
+ diff = self.get_diff(ignore+(inspect.currentframe(),))
+ lines.append("Added objects:")
+ for line in summary.format_(summary.summarize(diff['+'])):
+ lines.append(line)
+ lines.append("Removed objects:")
+ for line in summary.format_(summary.summarize(diff['-'])):
+ lines.append(line)
+ return lines