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