Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

192 rader
4.7 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
  3. # MIT License. See license.txt
  4. from __future__ import unicode_literals
  5. from collections import Counter
  6. import datetime
  7. import inspect
  8. import json
  9. import re
  10. import time
  11. import frappe
  12. import sqlparse
  13. from frappe import _
  14. RECORDER_INTERCEPT_FLAG = "recorder-intercept"
  15. RECORDER_REQUEST_SPARSE_HASH = "recorder-requests-sparse"
  16. RECORDER_REQUEST_HASH = "recorder-requests"
  17. def sql(*args, **kwargs):
  18. start_time = time.time()
  19. result = frappe.db._sql(*args, **kwargs)
  20. end_time = time.time()
  21. stack = list(get_current_stack_frames())
  22. if frappe.db.db_type == "postgres":
  23. query = frappe.db._cursor.query
  24. else:
  25. query = frappe.db._cursor._executed
  26. query = sqlparse.format(query.strip(), keyword_case="upper", reindent=True)
  27. # Collect EXPLAIN for executed query
  28. if query.lower().strip().split()[0] in ("select", "update", "delete"):
  29. # Only SELECT/UPDATE/DELETE queries can be "EXPLAIN"ed
  30. explain_result = frappe.db._sql("EXPLAIN {}".format(query), as_dict=True)
  31. else:
  32. explain_result = []
  33. data = {
  34. "query": query,
  35. "stack": stack,
  36. "explain_result": explain_result,
  37. "time": start_time,
  38. "duration": float("{:.3f}".format((end_time - start_time) * 1000)),
  39. }
  40. frappe.local._recorder.register(data)
  41. return result
  42. def get_current_stack_frames():
  43. try:
  44. current = inspect.currentframe()
  45. frames = inspect.getouterframes(current, context=10)
  46. for frame, filename, lineno, function, context, index in list(reversed(frames))[:-2]:
  47. if "/apps/" in filename:
  48. yield {
  49. "filename": re.sub(".*/apps/", "", filename),
  50. "lineno": lineno,
  51. "function": function,
  52. }
  53. except Exception:
  54. pass
  55. def record():
  56. if __debug__:
  57. if frappe.cache().get_value(RECORDER_INTERCEPT_FLAG):
  58. frappe.local._recorder = Recorder()
  59. def dump():
  60. if __debug__:
  61. if hasattr(frappe.local, "_recorder"):
  62. frappe.local._recorder.dump()
  63. class Recorder:
  64. def __init__(self):
  65. self.uuid = frappe.generate_hash(length=10)
  66. self.time = datetime.datetime.now()
  67. self.calls = []
  68. self.path = frappe.request.path
  69. self.cmd = frappe.local.form_dict.cmd or ""
  70. self.method = frappe.request.method
  71. self.headers = dict(frappe.local.request.headers)
  72. self.form_dict = frappe.local.form_dict
  73. _patch()
  74. def register(self, data):
  75. self.calls.append(data)
  76. def dump(self):
  77. request_data = {
  78. "uuid": self.uuid,
  79. "path": self.path,
  80. "cmd": self.cmd,
  81. "time": self.time,
  82. "queries": len(self.calls),
  83. "time_queries": float(
  84. "{:0.3f}".format(sum(call["duration"] for call in self.calls))
  85. ),
  86. "duration": float(
  87. "{:0.3f}".format((datetime.datetime.now() - self.time).total_seconds() * 1000)
  88. ),
  89. "method": self.method,
  90. }
  91. frappe.cache().hset(RECORDER_REQUEST_SPARSE_HASH, self.uuid, request_data)
  92. frappe.publish_realtime(
  93. event="recorder-dump-event", message=json.dumps(request_data, default=str)
  94. )
  95. self.mark_duplicates()
  96. request_data["calls"] = self.calls
  97. request_data["headers"] = self.headers
  98. request_data["form_dict"] = self.form_dict
  99. frappe.cache().hset(RECORDER_REQUEST_HASH, self.uuid, request_data)
  100. def mark_duplicates(self):
  101. counts = Counter([call["query"] for call in self.calls])
  102. for index, call in enumerate(self.calls):
  103. call["index"] = index
  104. call["exact_copies"] = counts[call["query"]]
  105. def _patch():
  106. frappe.db._sql = frappe.db.sql
  107. frappe.db.sql = sql
  108. def do_not_record(function):
  109. def wrapper(*args, **kwargs):
  110. if hasattr(frappe.local, "_recorder"):
  111. del frappe.local._recorder
  112. frappe.db.sql = frappe.db._sql
  113. return function(*args, **kwargs)
  114. return wrapper
  115. def administrator_only(function):
  116. def wrapper(*args, **kwargs):
  117. if frappe.session.user != "Administrator":
  118. frappe.throw(_("Only Administrator is allowed to use Recorder"))
  119. return function(*args, **kwargs)
  120. return wrapper
  121. @frappe.whitelist()
  122. @do_not_record
  123. @administrator_only
  124. def status(*args, **kwargs):
  125. return bool(frappe.cache().get_value(RECORDER_INTERCEPT_FLAG))
  126. @frappe.whitelist()
  127. @do_not_record
  128. @administrator_only
  129. def start(*args, **kwargs):
  130. frappe.cache().set_value(RECORDER_INTERCEPT_FLAG, 1)
  131. @frappe.whitelist()
  132. @do_not_record
  133. @administrator_only
  134. def stop(*args, **kwargs):
  135. frappe.cache().delete_value(RECORDER_INTERCEPT_FLAG)
  136. @frappe.whitelist()
  137. @do_not_record
  138. @administrator_only
  139. def get(uuid=None, *args, **kwargs):
  140. if uuid:
  141. result = frappe.cache().hget(RECORDER_REQUEST_HASH, uuid)
  142. else:
  143. result = list(frappe.cache().hgetall(RECORDER_REQUEST_SPARSE_HASH).values())
  144. return result
  145. @frappe.whitelist()
  146. @do_not_record
  147. @administrator_only
  148. def delete(*args, **kwargs):
  149. frappe.cache().delete_value(RECORDER_REQUEST_SPARSE_HASH)
  150. frappe.cache().delete_value(RECORDER_REQUEST_HASH)