feat: finer frappe Recorder control with decorator (#19220)
Currently frappe recorder can be enabled globally and profiles every
request. This is often way too much info. If you already know where
problem lies you use this decorator sparingly to only profile relevant
functions.
Usage:
```py
from frappe.recorder import record_queries
@record_queries
def sus_slow_function():
frappe.db.sql("select everything from everywhere")
```
This commit is contained in:
parent
442ba5dbf2
commit
ec3f705e4f
2 changed files with 49 additions and 7 deletions
|
|
@ -1,11 +1,13 @@
|
|||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
import datetime
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from collections import Counter
|
||||
from typing import Callable
|
||||
|
||||
import sqlparse
|
||||
|
||||
|
|
@ -61,9 +63,9 @@ def get_current_stack_frames():
|
|||
pass
|
||||
|
||||
|
||||
def record():
|
||||
def record(force=False):
|
||||
if __debug__:
|
||||
if frappe.cache().get_value(RECORDER_INTERCEPT_FLAG):
|
||||
if frappe.cache().get_value(RECORDER_INTERCEPT_FLAG) or force:
|
||||
frappe.local._recorder = Recorder()
|
||||
|
||||
|
||||
|
|
@ -78,11 +80,19 @@ class Recorder:
|
|||
self.uuid = frappe.generate_hash(length=10)
|
||||
self.time = datetime.datetime.now()
|
||||
self.calls = []
|
||||
self.path = frappe.request.path
|
||||
self.cmd = frappe.local.form_dict.cmd or ""
|
||||
self.method = frappe.request.method
|
||||
self.headers = dict(frappe.local.request.headers)
|
||||
self.form_dict = frappe.local.form_dict
|
||||
if frappe.request:
|
||||
self.path = frappe.request.path
|
||||
self.cmd = frappe.local.form_dict.cmd or ""
|
||||
self.method = frappe.request.method
|
||||
self.headers = dict(frappe.local.request.headers)
|
||||
self.form_dict = frappe.local.form_dict
|
||||
else:
|
||||
self.path = None
|
||||
self.cmd = None
|
||||
self.method = None
|
||||
self.headers = None
|
||||
self.form_dict = None
|
||||
|
||||
_patch()
|
||||
|
||||
def register(self, data):
|
||||
|
|
@ -125,6 +135,10 @@ def _patch():
|
|||
frappe.db.sql = sql
|
||||
|
||||
|
||||
def _unpatch():
|
||||
frappe.db.sql = frappe.db._sql
|
||||
|
||||
|
||||
def do_not_record(function):
|
||||
def wrapper(*args, **kwargs):
|
||||
if hasattr(frappe.local, "_recorder"):
|
||||
|
|
@ -189,3 +203,19 @@ def export_data(*args, **kwargs):
|
|||
def delete(*args, **kwargs):
|
||||
frappe.cache().delete_value(RECORDER_REQUEST_SPARSE_HASH)
|
||||
frappe.cache().delete_value(RECORDER_REQUEST_HASH)
|
||||
|
||||
|
||||
def record_queries(func: Callable):
|
||||
"""Decorator to profile a specific function using recorder."""
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
record(force=True)
|
||||
frappe.local._recorder.path = f"Function call: {func.__module__}.{func.__qualname__}"
|
||||
ret = func(*args, **kwargs)
|
||||
dump()
|
||||
_unpatch()
|
||||
print("Recorded queries, open recorder to view them.")
|
||||
return ret
|
||||
|
||||
return wrapped
|
||||
|
|
|
|||
|
|
@ -123,3 +123,15 @@ class TestRecorder(FrappeTestCase):
|
|||
def test_error_page_rendering(self):
|
||||
content = get_response_content("error")
|
||||
self.assertIn("Error", content)
|
||||
|
||||
|
||||
class TestRecorderDeco(FrappeTestCase):
|
||||
def test_recorder_flag(self):
|
||||
frappe.recorder.delete()
|
||||
|
||||
@frappe.recorder.record_queries
|
||||
def test():
|
||||
frappe.get_all("User")
|
||||
|
||||
test()
|
||||
self.assertTrue(frappe.recorder.get())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue