Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

profiling.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. from __future__ import absolute_import, division, unicode_literals
  2. import cProfile
  3. import os
  4. from colorsys import hsv_to_rgb
  5. from pstats import Stats
  6. from django.utils import six
  7. from django.utils.html import format_html
  8. from django.utils.translation import ugettext_lazy as _
  9. from debug_toolbar import settings as dt_settings
  10. from debug_toolbar.panels import Panel
  11. # Occasionally the disable method on the profiler is listed before
  12. # the actual view functions. This function call should be ignored as
  13. # it leads to an error within the tests.
  14. INVALID_PROFILER_FUNC = '_lsprof.Profiler'
  15. def contains_profiler(func_tuple):
  16. """Helper function that checks to see if the tuple contains
  17. the INVALID_PROFILE_FUNC in any string value of the tuple."""
  18. has_profiler = False
  19. for value in func_tuple:
  20. if isinstance(value, six.string_types):
  21. has_profiler |= INVALID_PROFILER_FUNC in value
  22. return has_profiler
  23. class DjangoDebugToolbarStats(Stats):
  24. __root = None
  25. def get_root_func(self):
  26. if self.__root is None:
  27. for func, (cc, nc, tt, ct, callers) in self.stats.items():
  28. if len(callers) == 0 and not contains_profiler(func):
  29. self.__root = func
  30. break
  31. return self.__root
  32. class FunctionCall(object):
  33. def __init__(self, statobj, func, depth=0, stats=None,
  34. id=0, parent_ids=[], hsv=(0, 0.5, 1)):
  35. self.statobj = statobj
  36. self.func = func
  37. if stats:
  38. self.stats = stats
  39. else:
  40. self.stats = statobj.stats[func][:4]
  41. self.depth = depth
  42. self.id = id
  43. self.parent_ids = parent_ids
  44. self.hsv = hsv
  45. def parent_classes(self):
  46. return self.parent_classes
  47. def background(self):
  48. r, g, b = hsv_to_rgb(*self.hsv)
  49. return 'rgb(%f%%,%f%%,%f%%)' % (r * 100, g * 100, b * 100)
  50. def func_std_string(self): # match what old profile produced
  51. func_name = self.func
  52. if func_name[:2] == ('~', 0):
  53. # special case for built-in functions
  54. name = func_name[2]
  55. if name.startswith('<') and name.endswith('>'):
  56. return '{%s}' % name[1:-1]
  57. else:
  58. return name
  59. else:
  60. file_name, line_num, method = self.func
  61. idx = file_name.find('/site-packages/')
  62. if idx > -1:
  63. file_name = file_name[(idx + 14):]
  64. split_path = file_name.rsplit(os.sep, 1)
  65. if len(split_path) > 1:
  66. file_path, file_name = file_name.rsplit(os.sep, 1)
  67. else:
  68. file_path = '<module>'
  69. return format_html(
  70. '<span class="djdt-path">{0}/</span>'
  71. '<span class="djdt-file">{1}</span>'
  72. ' in <span class="djdt-func">{3}</span>'
  73. '(<span class="djdt-lineno">{2}</span>)',
  74. file_path,
  75. file_name,
  76. line_num,
  77. method)
  78. def subfuncs(self):
  79. i = 0
  80. h, s, v = self.hsv
  81. count = len(self.statobj.all_callees[self.func])
  82. for func, stats in self.statobj.all_callees[self.func].items():
  83. i += 1
  84. h1 = h + (i / count) / (self.depth + 1)
  85. if stats[3] == 0:
  86. s1 = 0
  87. else:
  88. s1 = s * (stats[3] / self.stats[3])
  89. yield FunctionCall(self.statobj,
  90. func,
  91. self.depth + 1,
  92. stats=stats,
  93. id=str(self.id) + '_' + str(i),
  94. parent_ids=self.parent_ids + [self.id],
  95. hsv=(h1, s1, 1))
  96. def count(self):
  97. return self.stats[1]
  98. def tottime(self):
  99. return self.stats[2]
  100. def cumtime(self):
  101. cc, nc, tt, ct = self.stats
  102. return self.stats[3]
  103. def tottime_per_call(self):
  104. cc, nc, tt, ct = self.stats
  105. if nc == 0:
  106. return 0
  107. return tt / nc
  108. def cumtime_per_call(self):
  109. cc, nc, tt, ct = self.stats
  110. if cc == 0:
  111. return 0
  112. return ct / cc
  113. def indent(self):
  114. return 16 * self.depth
  115. class ProfilingPanel(Panel):
  116. """
  117. Panel that displays profiling information.
  118. """
  119. title = _("Profiling")
  120. template = 'debug_toolbar/panels/profiling.html'
  121. def process_view(self, request, view_func, view_args, view_kwargs):
  122. self.profiler = cProfile.Profile()
  123. args = (request,) + view_args
  124. return self.profiler.runcall(view_func, *args, **view_kwargs)
  125. def add_node(self, func_list, func, max_depth, cum_time=0.1):
  126. func_list.append(func)
  127. func.has_subfuncs = False
  128. if func.depth < max_depth:
  129. for subfunc in func.subfuncs():
  130. if subfunc.stats[3] >= cum_time:
  131. func.has_subfuncs = True
  132. self.add_node(func_list, subfunc, max_depth, cum_time=cum_time)
  133. def generate_stats(self, request, response):
  134. if not hasattr(self, 'profiler'):
  135. return None
  136. # Could be delayed until the panel content is requested (perf. optim.)
  137. self.profiler.create_stats()
  138. self.stats = DjangoDebugToolbarStats(self.profiler)
  139. self.stats.calc_callees()
  140. root_func = self.stats.get_root_func()
  141. # Ensure root function exists before continuing with function call analysis
  142. if root_func:
  143. root = FunctionCall(self.stats, root_func, depth=0)
  144. func_list = []
  145. self.add_node(func_list,
  146. root,
  147. dt_settings.get_config()['PROFILER_MAX_DEPTH'],
  148. root.stats[3] / 8)
  149. self.record_stats({'func_list': func_list})