From 7856f7bc2eb9808537fd21b094cf15fc71abd233 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:52:37 +0000 Subject: [PATCH] feat(db-perf-report): added database performance report Endpoint - /benches//sites//database/performance-report The API endpoint will send back some summarized reports - Top 10 time consuming SQL Queries - Top 10 queries with full table scans - Unused Indexes - Redundant Indexes --- agent/database.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++ agent/site.py | 4 ++++ agent/web.py | 16 ++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/agent/database.py b/agent/database.py index f23c1849..91a37c93 100644 --- a/agent/database.py +++ b/agent/database.py @@ -321,6 +321,64 @@ def fetch_database_column_statistics(self, table): return result + def fetch_summarized_performance_report(self): + queries = f""" +-- Top 10 time consuming queries; +SELECT + (SUM_TIMER_WAIT / SUM(SUM_TIMER_WAIT) OVER() * 100) AS percent, + round(SUM_TIMER_WAIT/1000000000, 1) AS total_time_ms, + COUNT_STAR AS calls, + round(AVG_TIMER_WAIT/1000000000, 1) AS avg_time_ms, + DIGEST_TEXT AS query +FROM performance_schema.events_statements_summary_by_digest + WHERE SCHEMA_NAME='{self.database_name}' + ORDER BY SUM_TIMER_WAIT DESC + LIMIT 10; + +-- Top 10 queries with full table scans; +SELECT t1.exec_count AS calls, + t1.rows_examined AS rows_examined, + t1.rows_sent AS rows_sent, + t1.query AS query, + t2.DIGEST_TEXT AS example +FROM sys.statements_with_full_table_scans AS t1 +LEFT JOIN ( + SELECT DIGEST, FIRST_VALUE(DIGEST_TEXT) OVER (PARTITION BY DIGEST ORDER BY RAND()) AS DIGEST_TEXT + FROM performance_schema.events_statements_summary_by_digest +) AS t2 ON t1.digest = t2.DIGEST +ORDER BY rows_examined DESC +LIMIT 10; + +-- Unused Indexes; +SELECT + index_name, + object_name AS table_name +FROM + sys.schema_unused_indexes +WHERE + object_schema='{self.database_name}'; + +-- Redundant Indexes; +SELECT + table_name, + redundant_index_name, + redundant_index_columns, + dominant_index_name, + dominant_index_columns +FROM + sys.schema_redundant_indexes +WHERE + table_schema='{self.database_name}'; +""" + + result = self._run_sql(queries, as_dict=True) + return { + "top_10_time_consuming_queries": result[0]["output"], + "top_10_queries_with_full_table_scan": result[1]["output"], + "unused_indexes": result[2]["output"], + "redundant_indexes": result[3]["output"], + } + # Private helper methods def _run_sql( # noqa C901 self, query: str, commit: bool = False, as_dict: bool = False, allow_all_stmt_types: bool = False diff --git a/agent/site.py b/agent/site.py index 91f521ed..374ede8e 100644 --- a/agent/site.py +++ b/agent/site.py @@ -914,6 +914,10 @@ def analyze_slow_queries(self, queries: list[dict], database_root_password: str) result.append(query) return result + def fetch_summarized_database_performance_report(self, mariadb_root_password: str): + database = self.db_instance(username="root", password=mariadb_root_password) + return database.fetch_summarized_performance_report() + def db_instance(self, username: str | None = None, password: str | None = None) -> Database: if not username: username = self.user diff --git a/agent/web.py b/agent/web.py index 9ecbeedf..219643d3 100644 --- a/agent/web.py +++ b/agent/web.py @@ -591,7 +591,7 @@ def run_sql(bench, site): @application.route( - "/benches//sites//database/analyze_slow_queries", methods=["GET"] + "/benches//sites//database/analyze-slow-queries", methods=["GET"] ) @validate_bench_and_site def analyze_slow_queries(bench: str, site: str): @@ -602,6 +602,20 @@ def analyze_slow_queries(bench: str, site: str): return {"job": job} +@application.route( + "/benches//sites//database/performance-report", methods=["POST"] +) +def database_performance_report(bench, site): + data = request.json + result = ( + Server() + .benches[bench] + .sites[site] + .fetch_summarized_database_performance_report(data["mariadb_root_password"]) + ) + return jsonify(json.loads(json.dumps(result, cls=JSONEncoderForSQLQueryResult))) + + @application.route("/benches//sites//database/users", methods=["POST"]) @validate_bench_and_site def create_database_user(bench, site):