From 69ddfd0e99836ecdc258082e37550a44eebbe72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=99=93=E9=A3=9E?= Date: Fri, 18 Oct 2024 17:58:07 +0800 Subject: [PATCH] =?UTF-8?q?PgSQL=E6=94=AF=E6=8C=81=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=20(#2845)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: 王飞 --- common/static/dbdiagnostic/js/db_info.js | 31 +++++++++++++++++ sql/engines/pgsql.py | 42 ++++++++++++++++++++++++ sql/engines/tests.py | 34 +++++++++++++++++++ sql/templates/dbdiagnostic.html | 22 ++++++++++++- 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 common/static/dbdiagnostic/js/db_info.js diff --git a/common/static/dbdiagnostic/js/db_info.js b/common/static/dbdiagnostic/js/db_info.js new file mode 100644 index 0000000000..416fe4cecd --- /dev/null +++ b/common/static/dbdiagnostic/js/db_info.js @@ -0,0 +1,31 @@ +const pgsqlDiagnosticInfo = { + fieldsProcesslist: [ + 'pgsql', + ["All", "Not Idle"], + [ + { title: '', field: 'checkbox', checkbox: true }, + { title: 'PId', field: 'pid', sortable: true }, + { title: '阻塞PID', field: 'block_pids', sortable: false }, + { title: '数据库', field: 'datname', sortable: true }, + { title: '用户', field: 'usename', sortable: true }, + { title: '应用名称', field: 'application_name', sortable: true }, + { title: '状态', field: 'state', sortable: true }, + { title: '客户端地址', field: 'client_addr', sortable: true }, + { title: '耗时(秒)', field: 'elapsed_time_seconds', sortable: true }, + { title: '耗时', field: 'elapsed_time', sortable: true }, + { title: '查询语句', field: 'query', sortable: true }, + { title: '等待事件类型', field: 'wait_event_type', sortable: true }, + { title: '等待事件', field: 'wait_event', sortable: true }, + { title: '查询开始时间', field: 'query_start', sortable: true }, + { title: '后端开始时间', field: 'backend_start', sortable: true }, + { title: '父PID', field: 'leader_pid', sortable: true }, + { title: '客户端主机名', field: 'client_hostname', sortable: true }, + { title: '客户端端口', field: 'client_port', sortable: true }, + { title: '事务开始时间', field: 'transaction_start_time', sortable: true }, + { title: '状态变更时间', field: 'state_change', sortable: true }, + { title: '后端XID', field: 'backend_xid', sortable: true }, + { title: '后端XMIN', field: 'backend_xmin', sortable: true }, + { title: '后端类型', field: 'backend_type', sortable: true }, + ] + ] +} diff --git a/sql/engines/pgsql.py b/sql/engines/pgsql.py index cbe9150813..f686f33772 100644 --- a/sql/engines/pgsql.py +++ b/sql/engines/pgsql.py @@ -384,3 +384,45 @@ def close(self): if self.conn: self.conn.close() self.conn = None + + def processlist(self, command_type, **kwargs): + """获取连接信息""" + sql = """ + select psa.pid + ,concat('{',array_to_string(pg_blocking_pids(psa.pid),','),'}') block_pids + ,psa.leader_pid + ,psa.datname,psa.usename + ,psa.application_name + ,psa.state + ,psa.client_addr::text client_addr + ,round(GREATEST(EXTRACT(EPOCH FROM (now() - psa.query_start)),0)::numeric,4) elapsed_time_seconds + ,GREATEST(now() - psa.query_start, INTERVAL '0 second') AS elapsed_time + ,(case when psa.leader_pid is null then psa.query end) query + ,psa.wait_event_type,psa.wait_event + ,psa.query_start + ,psa.backend_start + ,psa.client_hostname,psa.client_port + ,psa.xact_start transaction_start_time + ,psa.state_change,psa.backend_xid,psa.backend_xmin,psa.backend_type + from pg_stat_activity psa + where 1=1 + AND psa.pid <> pg_backend_pid() + $state_not_idle$ + order by (case + when psa.state='active' then 10 + when psa.state like 'idle in transaction%' then 5 + when psa.state='idle' then 99 else 100 end) + ,elapsed_time_seconds desc + ,(case when psa.leader_pid is not null then 1 else 0 end); + """ + # escape + command_type = self.escape_string(command_type) + if not command_type: + command_type = "Not Idle" + + if command_type == "Not Idle": + sql = sql.replace("$state_not_idle$", "and psa.state<>'idle'") + + # 所有的模板进行替换 + sql = sql.replace("$state_not_idle$", "") + return self.query("postgres", sql) diff --git a/sql/engines/tests.py b/sql/engines/tests.py index 9a85dc04dd..8cb5c63eb3 100644 --- a/sql/engines/tests.py +++ b/sql/engines/tests.py @@ -856,6 +856,40 @@ def test_execute_workflow_exception(self, _conn, _cursor, _execute): execute_result.rows[0].__dict__.keys(), row.__dict__.keys() ) + @patch("psycopg2.connect") + def test_processlist_not_idle(self, mock_connect): + # 模拟数据库连接和游标 + mock_cursor = MagicMock() + mock_connect.return_value.cursor.return_value = mock_cursor + + # 假设 query 方法返回的结果 + mock_cursor.fetchall.return_value = [ + (123, "test_db", "user", "app_name", "active") + ] + + # 创建 PgSQLEngine 实例 + new_engine = PgSQLEngine(instance=self.ins) + + # 调用 processlist 方法 + result = new_engine.processlist(command_type="Not Idle") + self.assertEqual(result.rows, mock_cursor.fetchall.return_value) + + @patch("psycopg2.connect") + def test_processlist_idle(self, mock_connect): + # 模拟数据库连接和游标 + mock_cursor = MagicMock() + mock_connect.return_value.cursor.return_value = mock_cursor + + # 假设 query 方法返回的结果 + mock_cursor.fetchall.return_value = [ + (123, "test_db", "user", "app_name", "idle") + ] + # 创建 PgSQLEngine 实例 + new_engine = PgSQLEngine(instance=self.ins) + # 调用 processlist 方法 + result = new_engine.processlist(command_type="Idle") + self.assertEqual(result.rows, mock_cursor.fetchall.return_value) + class TestModel(TestCase): def setUp(self): diff --git a/sql/templates/dbdiagnostic.html b/sql/templates/dbdiagnostic.html index 6f5f263495..152b4d7f6d 100644 --- a/sql/templates/dbdiagnostic.html +++ b/sql/templates/dbdiagnostic.html @@ -28,6 +28,7 @@ +
@@ -94,6 +95,7 @@ {% load static %} +