PageRenderTime 115ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/flaskext/mongoengine/panels.py

https://github.com/linuxnow/flask-mongoengine
Python | 192 lines | 125 code | 29 blank | 38 comment | 16 complexity | 31cf89611e563c5db681d5d34183a967 MD5 | raw file
  1. import re
  2. import pymongo
  3. from flaskext.debugtoolbar.panels import DebugPanel
  4. from jinja2 import PackageLoader, ChoiceLoader
  5. from mongoengine.connection import _get_db
  6. import operation_tracker
  7. _ = lambda x: x
  8. class MongoenginePanel(DebugPanel):
  9. """
  10. Panel that displays the number of mongodb queries using mongoengine.
  11. * nscanned_vs_nreturned_ratio: Ratio to determine if a query needs index.
  12. * nscanned_update_limit: Limit to determine if a update query needs index.
  13. * slow_query_limit: Limit (ms) to determine if a query is slow.
  14. """
  15. name = 'Mongoengine'
  16. has_content = True
  17. nscanned_vs_nreturned_ratio = 1000
  18. nscanned_update_limit = 1000
  19. slow_query_limit = 100
  20. def __init__(self, *args, **kwargs):
  21. """
  22. We need to patch jinja_env loader to include flaskext.mongoengine
  23. templates folder.
  24. """
  25. super(MongoenginePanel, self).__init__(*args, **kwargs)
  26. self.jinja_env.loader = ChoiceLoader([self.jinja_env.loader,
  27. PackageLoader('flaskext.mongoengine', 'templates')])
  28. self.db = _get_db()
  29. def process_request(self, request):
  30. """
  31. This panel use the mongodb Database Profiler [1]
  32. On every request we deactivate, get the biggest ts and reactivate the
  33. profiler.
  34. [1] http://www.mongodb.org/display/DOCS/Database+Profiler
  35. """
  36. try:
  37. self.start_ts = self.db.system.profile.find()\
  38. .sort("ts", pymongo.DESCENDING)\
  39. .limit(1)[0].get('ts')
  40. except IndexError:
  41. self.start_ts = None
  42. self.db.set_profiling_level(2)
  43. def _get_property(self, key, query):
  44. try:
  45. return float(re.search(r'%s\:(\d+)' % key, query).group(1))
  46. except:
  47. return None
  48. def _should_optimize(self, query):
  49. """
  50. Try to determine if there are any applicable obvious optimizations
  51. http://www.mongodb.org/display/DOCS/Database+Profiler
  52. index: If nscanned is much higher than nreturned, the database is
  53. scanning many objects to find the target objects. Consider
  54. creating an index to improve this.
  55. select members: (reslen) A large number of bytes returned (hundreds
  56. of kilobytes or more) causes slow performance. Consider passing
  57. find() a second parameter of the member names you require.
  58. """
  59. optimizations = []
  60. if query['operation_type'] == 'query':
  61. nscanned = self._get_property('nscanned', query['org_info'])
  62. nreturned = self._get_property('nreturned', query['org_info'])
  63. if (nreturned and nscanned) and \
  64. (nscanned > nreturned * self.nscanned_vs_nreturned_ratio):
  65. optimizations.append("index")
  66. reslen = self._get_property('reslen', query['org_info'])
  67. if reslen > 100 * 1024:
  68. optimizations.append("select members")
  69. elif query['operation_type'] == 'update':
  70. nscanned = self._get_property('nscanned', query['org_info'])
  71. if nscanned and nscanned > self.nscanned_update_limit:
  72. optimizations.append("index")
  73. if query['millis'] > self.slow_query_limit:
  74. optimizations.append("slow")
  75. return optimizations
  76. def _process_query(self, query):
  77. """
  78. Split the query to recover all interesting information.
  79. """
  80. out = {}
  81. out['millis'] = query.get('millis', 0)
  82. out['ts'] = query.get('ts')
  83. out['org_info'] = query.get('info')
  84. out['operation_type'] = query.get('op')
  85. out['collection'] = query.get('ns')
  86. out['query'] = query.get('query')
  87. out['optimizations'] = ', '.join(self._should_optimize(out))
  88. return out
  89. def process_response(self, request, response):
  90. """
  91. Get queries from system.profile with ts > self.start_ts
  92. """
  93. if self.start_ts:
  94. query = {'ts': {'$gt': self.start_ts}}
  95. else:
  96. query = {}
  97. self.queries = self.db.system.profile.find(query, timeout=False)
  98. self.queries = [self._process_query(q) for q in self.queries]
  99. self.queries_count = len(self.queries)
  100. self.total_time = sum([q.get('millis') for q in self.queries])
  101. def nav_title(self):
  102. return _('Mongoengine')
  103. def nav_subtitle(self):
  104. return "%s queries in %sms" % (self.queries_count, self.total_time)
  105. def title(self):
  106. return _('Mongoengine Usage')
  107. def url(self):
  108. return ''
  109. def content(self):
  110. context = self.context.copy()
  111. context.update({
  112. 'queries': self.queries,
  113. })
  114. return self.render('panels/mongoengine.html', context)
  115. class MongoDebugPanel(DebugPanel):
  116. """Panel that shows information about MongoDB operations (including stack)
  117. Adapted from https://github.com/hmarr/django-debug-toolbar-mongo
  118. """
  119. name = 'MongoDB'
  120. has_content = True
  121. def __init__(self, *args, **kwargs):
  122. super(self.__class__, self).__init__(*args, **kwargs)
  123. operation_tracker.install_tracker()
  124. def process_request(self, request):
  125. operation_tracker.reset()
  126. def nav_title(self):
  127. return 'MongoDB'
  128. def nav_subtitle(self):
  129. num_queries = len(operation_tracker.queries)
  130. attrs = ['queries', 'inserts', 'updates', 'removes']
  131. total_time = sum(sum(o['time'] for o in getattr(operation_tracker, a))
  132. for a in attrs)
  133. return '{0} operations in {1:.2f}ms'.format(num_queries, total_time)
  134. def title(self):
  135. return 'MongoDB Operations'
  136. def url(self):
  137. return ''
  138. def content(self):
  139. context = self.context.copy()
  140. context['queries'] = operation_tracker.queries
  141. context['inserts'] = operation_tracker.inserts
  142. context['updates'] = operation_tracker.updates
  143. context['removes'] = operation_tracker.removes
  144. return self.render('panels/mongo-panel.html', context)