1"""
2Expose a memory-profiling panel to the Django Debug toolbar.
3
4Shows process memory information (virtual size, resident set size) and model
5instances for the current request.
6
7Requires Django and Django Debug toolbar:
8
9https://github.com/django-debug-toolbar/django-debug-toolbar
10
11Pympler adds a memory panel as a third party addon (not included in the
12django-debug-toolbar). It can be added by overriding the `DEBUG_TOOLBAR_PANELS`
13setting in the Django project settings::
14
15    DEBUG_TOOLBAR_PANELS = (
16        'debug_toolbar.panels.timer.TimerDebugPanel',
17        'pympler.panels.MemoryPanel',
18        )
19
20Pympler also needs to be added to the `INSTALLED_APPS` in the Django settings::
21
22    INSTALLED_APPS = INSTALLED_APPS + ('debug_toolbar', 'pympler')
23"""
24
25from pympler.classtracker import ClassTracker
26from pympler.process import ProcessMemoryInfo
27from pympler.util.stringutils import pp
28
29try:
30    from debug_toolbar.panels import Panel
31    from django.db.models import get_models
32    from django.template import Context, Template
33    from django.template.loader import render_to_string
34except ImportError:
35    class Panel(object):
36        pass
37
38    class Template(object):
39        pass
40
41    class Context(object):
42        pass
43
44
45class MemoryPanel(Panel):
46
47    name = 'pympler'
48
49    title = 'Memory'
50
51    template = 'memory_panel.html'
52
53    classes = [Context, Template]
54
55    def process_request(self, request):
56        self._tracker = ClassTracker()
57        for cls in get_models() + self.classes:
58            self._tracker.track_class(cls)
59        self._tracker.create_snapshot('before')
60        self.record_stats({'before': ProcessMemoryInfo()})
61
62    def process_response(self, request, response):
63        self.record_stats({'after': ProcessMemoryInfo()})
64        self._tracker.create_snapshot('after')
65        stats = self._tracker.stats
66        stats.annotate()
67        self.record_stats({'stats': stats})
68
69    def enable_instrumentation(self):
70        self._tracker = ClassTracker()
71        for cls in get_models() + self.classes:
72            self._tracker.track_class(cls)
73
74    def disable_instrumentation(self):
75        self._tracker.detach_all_classes()
76
77    def nav_subtitle(self):
78        context = self.get_stats()
79        before = context['before']
80        after = context['after']
81        rss = after.rss
82        delta = rss - before.rss
83        delta = ('(+%s)' % pp(delta)) if delta > 0 else ''
84        return "%s %s" % (pp(rss), delta)
85
86    @property
87    def content(self):
88        context = self.get_stats()
89        before = context['before']
90        after = context['after']
91        stats = context['stats']
92        rows = [('Resident set size', after.rss),
93                ('Virtual size', after.vsz),
94                ]
95        rows.extend(after - before)
96        rows = [(key, pp(value)) for key, value in rows]
97        rows.extend(after.os_specific)
98
99        classes = []
100        snapshot = stats.snapshots[-1]
101        for model in stats.tracked_classes:
102            history = [cnt for _, cnt in stats.history[model]]
103            size = snapshot.classes.get(model, {}).get('sum', 0)
104            if history and history[-1] > 0:
105                classes.append((model, history, pp(size)))
106        context.update({'rows': rows, 'classes': classes})
107        return render_to_string(self.template, context)
108