Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

198 linhas
4.8 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
  3. # License: MIT. See LICENSE
  4. import datetime
  5. import inspect
  6. import json
  7. import re
  8. import time
  9. from collections import Counter
  10. import sqlparse
  11. import frappe
  12. from frappe import _
  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. 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": TRACEBACK_PATH_PATTERN.sub("", 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("{:0.3f}".format(sum(call["duration"] for call in self.calls))),
  84. "duration": float(
  85. "{:0.3f}".format((datetime.datetime.now() - self.time).total_seconds() * 1000)
  86. ),
  87. "method": self.method,
  88. }
  89. frappe.cache().hset(RECORDER_REQUEST_SPARSE_HASH, self.uuid, request_data)
  90. frappe.publish_realtime(
  91. event="recorder-dump-event", message=json.dumps(request_data, default=str)
  92. )
  93. self.mark_duplicates()
  94. request_data["calls"] = self.calls
  95. request_data["headers"] = self.headers
  96. request_data["form_dict"] = self.form_dict
  97. frappe.cache().hset(RECORDER_REQUEST_HASH, self.uuid, request_data)
  98. def mark_duplicates(self):
  99. counts = Counter([call["query"] for call in self.calls])
  100. for index, call in enumerate(self.calls):
  101. call["index"] = index
  102. call["exact_copies"] = counts[call["query"]]
  103. def _patch():
  104. frappe.db._sql = frappe.db.sql
  105. frappe.db.sql = sql
  106. def do_not_record(function):
  107. def wrapper(*args, **kwargs):
  108. if hasattr(frappe.local, "_recorder"):
  109. del frappe.local._recorder
  110. frappe.db.sql = frappe.db._sql
  111. return function(*args, **kwargs)
  112. return wrapper
  113. def administrator_only(function):
  114. def wrapper(*args, **kwargs):
  115. if frappe.session.user != "Administrator":
  116. frappe.throw(_("Only Administrator is allowed to use Recorder"))
  117. return function(*args, **kwargs)
  118. return wrapper
  119. @frappe.whitelist()
  120. @do_not_record
  121. @administrator_only
  122. def status(*args, **kwargs):
  123. return bool(frappe.cache().get_value(RECORDER_INTERCEPT_FLAG))
  124. @frappe.whitelist()
  125. @do_not_record
  126. @administrator_only
  127. def start(*args, **kwargs):
  128. frappe.cache().set_value(RECORDER_INTERCEPT_FLAG, 1)
  129. @frappe.whitelist()
  130. @do_not_record
  131. @administrator_only
  132. def stop(*args, **kwargs):
  133. frappe.cache().delete_value(RECORDER_INTERCEPT_FLAG)
  134. @frappe.whitelist()
  135. @do_not_record
  136. @administrator_only
  137. def get(uuid=None, *args, **kwargs):
  138. if uuid:
  139. result = frappe.cache().hget(RECORDER_REQUEST_HASH, uuid)
  140. else:
  141. result = list(frappe.cache().hgetall(RECORDER_REQUEST_SPARSE_HASH).values())
  142. return result
  143. @frappe.whitelist()
  144. @do_not_record
  145. @administrator_only
  146. def export_data(*args, **kwargs):
  147. return list(frappe.cache().hgetall(RECORDER_REQUEST_HASH).values())
  148. @frappe.whitelist()
  149. @do_not_record
  150. @administrator_only
  151. def delete(*args, **kwargs):
  152. frappe.cache().delete_value(RECORDER_REQUEST_SPARSE_HASH)
  153. frappe.cache().delete_value(RECORDER_REQUEST_HASH)