Skip to content

Commit

Permalink
feat: finer frappe Recorder control with decorator (backport #19220) (#…
Browse files Browse the repository at this point in the history
…19221)

* 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")
```

(cherry picked from commit ec3f705)

* Update test_recorder.py

Co-authored-by: Ankush Menat <ankush@frappe.io>
  • Loading branch information
mergify[bot] and ankush committed Dec 9, 2022
1 parent 458707e commit 3b18e7e
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 7 deletions.
44 changes: 37 additions & 7 deletions frappe/recorder.py
Expand Up @@ -4,11 +4,13 @@
from __future__ import unicode_literals

import datetime
import functools
import inspect
import json
import re
import time
from collections import Counter
from typing import Callable

import sqlparse

Expand Down Expand Up @@ -68,9 +70,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()


Expand All @@ -85,11 +87,19 @@ def __init__(self):
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):
Expand Down Expand Up @@ -134,6 +144,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"):
Expand Down Expand Up @@ -198,3 +212,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
12 changes: 12 additions & 0 deletions frappe/tests/test_recorder.py
Expand Up @@ -128,3 +128,15 @@ def test_duplicate_queries(self):
def test_error_page_rendering(self):
content = render_page("error")
self.assertIn("Error", content)


class TestRecorderDeco(unittest.TestCase):
def test_recorder_flag(self):
frappe.recorder.delete()

@frappe.recorder.record_queries
def test():
frappe.get_all("User")

test()
self.assertTrue(frappe.recorder.get())

0 comments on commit 3b18e7e

Please sign in to comment.