Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

190 рядки
4.7 KiB

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