diff --git a/frappe/tests/test_perf.py b/frappe/tests/test_perf.py index e0a9e4bf92..98ab0453f6 100644 --- a/frappe/tests/test_perf.py +++ b/frappe/tests/test_perf.py @@ -27,6 +27,7 @@ import frappe from frappe.frappeclient import FrappeClient from frappe.model.base_document import get_controller from frappe.query_builder.utils import db_type_is +from frappe.tests.test_api import FrappeAPITestCase from frappe.tests.test_query_builder import run_only_if from frappe.tests.utils import FrappeTestCase from frappe.utils import cint @@ -196,3 +197,40 @@ class TestPerformance(FrappeTestCase): def test_no_cyclic_references(self): doc = frappe.get_doc("User", "Administrator") self.assertEqual(sys.getrefcount(doc), 2) # Note: This always returns +1 + + def test_get_doc_cache_calls(self): + frappe.get_doc("User", "Administrator") + with self.assertRedisCallCounts(1): + frappe.get_doc("User", "Administrator") + + +@run_only_if(db_type_is.MARIADB) +class TestOverheadCalls(FrappeAPITestCase): + """Test that typical redis and db calls remain same overtime. + + If this tests fail on your PR, make sure you're not introducing something in hot-path of these + endpoints. Only update values if you're really sure that's the right call. + Every call increase here is an actual increase in cost! + """ + + BASE_SQL_CALLS = 2 # rollback + begin + + def test_ping_overheads(self): + self.get(self.method("ping"), {"sid": "Guest"}) + with self.assertRedisCallCounts(12), self.assertQueryCount(self.BASE_SQL_CALLS): + self.get(self.method("ping"), {"sid": "Guest"}) + + def test_list_view_overheads(self): + sid = self.sid + self.get(self.resource("ToDo"), {"sid": sid}) + self.get(self.resource("ToDo"), {"sid": sid}) + with self.assertRedisCallCounts(24), self.assertQueryCount(self.BASE_SQL_CALLS + 1): + self.get(self.resource("ToDo"), {"sid": sid}) + + def test_get_doc_overheads(self): + sid = self.sid + tables = len(frappe.get_meta("User").get_table_fields()) + self.get(self.resource("User", "Administrator"), {"sid": sid}) + self.get(self.resource("User", "Administrator"), {"sid": sid}) + with self.assertRedisCallCounts(19), self.assertQueryCount(self.BASE_SQL_CALLS + 1 + tables): + self.get(self.resource("User", "Administrator"), {"sid": sid}) diff --git a/frappe/tests/utils.py b/frappe/tests/utils.py index f9d8ee54b5..10baaa3c7c 100644 --- a/frappe/tests/utils.py +++ b/frappe/tests/utils.py @@ -134,16 +134,38 @@ class FrappeTestCase(unittest.TestCase): def _sql_with_count(*args, **kwargs): ret = orig_sql(*args, **kwargs) - queries.append(frappe.db.last_query) + queries.append(args[0].last_query) return ret try: - orig_sql = frappe.db.sql - frappe.db.sql = _sql_with_count + orig_sql = frappe.db.__class__.sql + frappe.db.__class__.sql = _sql_with_count yield - self.assertLessEqual(len(queries), count, msg="Queries executed: " + "\n\n".join(queries)) + self.assertLessEqual(len(queries), count, msg="Queries executed: \n" + "\n\n".join(queries)) finally: - frappe.db.sql = orig_sql + frappe.db.__class__.sql = orig_sql + + @contextmanager + def assertRedisCallCounts(self, count): + commands = [] + + def execute_command_and_count(*args, **kwargs): + ret = orig_execute(*args, **kwargs) + key_len = 2 + if "H" in args[0]: + key_len = 3 + commands.append((args)[:key_len]) + return ret + + try: + orig_execute = frappe.cache.execute_command + frappe.cache.execute_command = execute_command_and_count + yield + self.assertLessEqual( + len(commands), count, msg="commands executed: \n" + "\n".join(str(c) for c in commands) + ) + finally: + frappe.cache.execute_command = orig_execute @contextmanager def assertRowsRead(self, count):