postgresai 0.14.0-dev.45 → 0.14.0-dev.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/postgres-ai.js +643 -2718
- package/lib/checkup.ts +276 -0
- package/lib/metrics-loader.ts +449 -97
- package/package.json +2 -2
- package/test/checkup.test.ts +49 -0
- package/test/schema-validation.test.ts +69 -0
- package/dist/sql/01.role.sql +0 -16
- package/dist/sql/02.permissions.sql +0 -37
- package/dist/sql/03.optional_rds.sql +0 -6
- package/dist/sql/04.optional_self_managed.sql +0 -8
- package/dist/sql/05.helpers.sql +0 -415
package/lib/checkup.ts
CHANGED
|
@@ -780,6 +780,276 @@ export async function generateH004(client: Client, nodeName: string = "node-01")
|
|
|
780
780
|
return report;
|
|
781
781
|
}
|
|
782
782
|
|
|
783
|
+
/**
|
|
784
|
+
* Generate D004 report - pg_stat_statements and pg_stat_kcache settings
|
|
785
|
+
*/
|
|
786
|
+
async function generateD004(client: Client, nodeName: string): Promise<Report> {
|
|
787
|
+
const report = createBaseReport("D004", "pg_stat_statements and pg_stat_kcache settings", nodeName);
|
|
788
|
+
const postgresVersion = await getPostgresVersion(client);
|
|
789
|
+
const allSettings = await getSettings(client);
|
|
790
|
+
|
|
791
|
+
// Filter settings related to pg_stat_statements and pg_stat_kcache
|
|
792
|
+
const pgssSettings: Record<string, SettingInfo> = {};
|
|
793
|
+
for (const [name, setting] of Object.entries(allSettings)) {
|
|
794
|
+
if (name.startsWith("pg_stat_statements") || name.startsWith("pg_stat_kcache")) {
|
|
795
|
+
pgssSettings[name] = setting;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Check pg_stat_statements extension
|
|
800
|
+
let pgssAvailable = false;
|
|
801
|
+
let pgssMetricsCount = 0;
|
|
802
|
+
let pgssTotalCalls = 0;
|
|
803
|
+
const pgssSampleQueries: Array<{ queryid: string; user: string; database: string; calls: number }> = [];
|
|
804
|
+
|
|
805
|
+
try {
|
|
806
|
+
const extCheck = await client.query(
|
|
807
|
+
"select 1 from pg_extension where extname = 'pg_stat_statements'"
|
|
808
|
+
);
|
|
809
|
+
if (extCheck.rows.length > 0) {
|
|
810
|
+
pgssAvailable = true;
|
|
811
|
+
const statsResult = await client.query(`
|
|
812
|
+
select count(*) as cnt, coalesce(sum(calls), 0) as total_calls
|
|
813
|
+
from pg_stat_statements
|
|
814
|
+
`);
|
|
815
|
+
pgssMetricsCount = parseInt(statsResult.rows[0]?.cnt || "0", 10);
|
|
816
|
+
pgssTotalCalls = parseInt(statsResult.rows[0]?.total_calls || "0", 10);
|
|
817
|
+
|
|
818
|
+
// Get sample queries (top 5 by calls)
|
|
819
|
+
const sampleResult = await client.query(`
|
|
820
|
+
select
|
|
821
|
+
queryid::text as queryid,
|
|
822
|
+
coalesce(usename, 'unknown') as "user",
|
|
823
|
+
coalesce(datname, 'unknown') as database,
|
|
824
|
+
calls
|
|
825
|
+
from pg_stat_statements s
|
|
826
|
+
left join pg_database d on s.dbid = d.oid
|
|
827
|
+
left join pg_user u on s.userid = u.usesysid
|
|
828
|
+
order by calls desc
|
|
829
|
+
limit 5
|
|
830
|
+
`);
|
|
831
|
+
for (const row of sampleResult.rows) {
|
|
832
|
+
pgssSampleQueries.push({
|
|
833
|
+
queryid: row.queryid,
|
|
834
|
+
user: row.user,
|
|
835
|
+
database: row.database,
|
|
836
|
+
calls: parseInt(row.calls, 10),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
} catch {
|
|
841
|
+
// Extension not available or accessible
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Check pg_stat_kcache extension
|
|
845
|
+
let kcacheAvailable = false;
|
|
846
|
+
let kcacheMetricsCount = 0;
|
|
847
|
+
let kcacheTotalExecTime = 0;
|
|
848
|
+
let kcacheTotalUserTime = 0;
|
|
849
|
+
let kcacheTotalSystemTime = 0;
|
|
850
|
+
const kcacheSampleQueries: Array<{ queryid: string; user: string; exec_total_time: number }> = [];
|
|
851
|
+
|
|
852
|
+
try {
|
|
853
|
+
const extCheck = await client.query(
|
|
854
|
+
"select 1 from pg_extension where extname = 'pg_stat_kcache'"
|
|
855
|
+
);
|
|
856
|
+
if (extCheck.rows.length > 0) {
|
|
857
|
+
kcacheAvailable = true;
|
|
858
|
+
const statsResult = await client.query(`
|
|
859
|
+
select
|
|
860
|
+
count(*) as cnt,
|
|
861
|
+
coalesce(sum(exec_user_time + exec_system_time), 0) as total_exec_time,
|
|
862
|
+
coalesce(sum(exec_user_time), 0) as total_user_time,
|
|
863
|
+
coalesce(sum(exec_system_time), 0) as total_system_time
|
|
864
|
+
from pg_stat_kcache
|
|
865
|
+
`);
|
|
866
|
+
kcacheMetricsCount = parseInt(statsResult.rows[0]?.cnt || "0", 10);
|
|
867
|
+
kcacheTotalExecTime = parseFloat(statsResult.rows[0]?.total_exec_time || "0");
|
|
868
|
+
kcacheTotalUserTime = parseFloat(statsResult.rows[0]?.total_user_time || "0");
|
|
869
|
+
kcacheTotalSystemTime = parseFloat(statsResult.rows[0]?.total_system_time || "0");
|
|
870
|
+
|
|
871
|
+
// Get sample queries (top 5 by exec time)
|
|
872
|
+
const sampleResult = await client.query(`
|
|
873
|
+
select
|
|
874
|
+
queryid::text as queryid,
|
|
875
|
+
coalesce(usename, 'unknown') as "user",
|
|
876
|
+
(exec_user_time + exec_system_time) as exec_total_time
|
|
877
|
+
from pg_stat_kcache k
|
|
878
|
+
left join pg_user u on k.userid = u.usesysid
|
|
879
|
+
order by (exec_user_time + exec_system_time) desc
|
|
880
|
+
limit 5
|
|
881
|
+
`);
|
|
882
|
+
for (const row of sampleResult.rows) {
|
|
883
|
+
kcacheSampleQueries.push({
|
|
884
|
+
queryid: row.queryid,
|
|
885
|
+
user: row.user,
|
|
886
|
+
exec_total_time: parseFloat(row.exec_total_time),
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
} catch {
|
|
891
|
+
// Extension not available or accessible
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
report.results[nodeName] = {
|
|
895
|
+
data: {
|
|
896
|
+
settings: pgssSettings,
|
|
897
|
+
pg_stat_statements_status: {
|
|
898
|
+
extension_available: pgssAvailable,
|
|
899
|
+
metrics_count: pgssMetricsCount,
|
|
900
|
+
total_calls: pgssTotalCalls,
|
|
901
|
+
sample_queries: pgssSampleQueries,
|
|
902
|
+
},
|
|
903
|
+
pg_stat_kcache_status: {
|
|
904
|
+
extension_available: kcacheAvailable,
|
|
905
|
+
metrics_count: kcacheMetricsCount,
|
|
906
|
+
total_exec_time: kcacheTotalExecTime,
|
|
907
|
+
total_user_time: kcacheTotalUserTime,
|
|
908
|
+
total_system_time: kcacheTotalSystemTime,
|
|
909
|
+
sample_queries: kcacheSampleQueries,
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
postgres_version: postgresVersion,
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
return report;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Generate F001 report - Autovacuum: current settings
|
|
920
|
+
*/
|
|
921
|
+
async function generateF001(client: Client, nodeName: string): Promise<Report> {
|
|
922
|
+
const report = createBaseReport("F001", "Autovacuum: current settings", nodeName);
|
|
923
|
+
const postgresVersion = await getPostgresVersion(client);
|
|
924
|
+
const allSettings = await getSettings(client);
|
|
925
|
+
|
|
926
|
+
// Filter autovacuum-related settings
|
|
927
|
+
const autovacuumSettings: Record<string, SettingInfo> = {};
|
|
928
|
+
for (const [name, setting] of Object.entries(allSettings)) {
|
|
929
|
+
if (name.includes("autovacuum") || name.includes("vacuum")) {
|
|
930
|
+
autovacuumSettings[name] = setting;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
report.results[nodeName] = {
|
|
935
|
+
data: autovacuumSettings,
|
|
936
|
+
postgres_version: postgresVersion,
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
return report;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Generate G001 report - Memory-related settings
|
|
944
|
+
*/
|
|
945
|
+
async function generateG001(client: Client, nodeName: string): Promise<Report> {
|
|
946
|
+
const report = createBaseReport("G001", "Memory-related settings", nodeName);
|
|
947
|
+
const postgresVersion = await getPostgresVersion(client);
|
|
948
|
+
const allSettings = await getSettings(client);
|
|
949
|
+
|
|
950
|
+
// Memory-related setting names
|
|
951
|
+
const memorySettingNames = [
|
|
952
|
+
"shared_buffers",
|
|
953
|
+
"work_mem",
|
|
954
|
+
"maintenance_work_mem",
|
|
955
|
+
"effective_cache_size",
|
|
956
|
+
"wal_buffers",
|
|
957
|
+
"temp_buffers",
|
|
958
|
+
"max_connections",
|
|
959
|
+
"autovacuum_work_mem",
|
|
960
|
+
"hash_mem_multiplier",
|
|
961
|
+
"logical_decoding_work_mem",
|
|
962
|
+
"max_stack_depth",
|
|
963
|
+
"max_prepared_transactions",
|
|
964
|
+
"max_locks_per_transaction",
|
|
965
|
+
"max_pred_locks_per_transaction",
|
|
966
|
+
];
|
|
967
|
+
|
|
968
|
+
const memorySettings: Record<string, SettingInfo> = {};
|
|
969
|
+
for (const name of memorySettingNames) {
|
|
970
|
+
if (allSettings[name]) {
|
|
971
|
+
memorySettings[name] = allSettings[name];
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Calculate memory usage estimates
|
|
976
|
+
interface MemoryUsage {
|
|
977
|
+
shared_buffers_bytes: number;
|
|
978
|
+
shared_buffers_pretty: string;
|
|
979
|
+
wal_buffers_bytes: number;
|
|
980
|
+
wal_buffers_pretty: string;
|
|
981
|
+
shared_memory_total_bytes: number;
|
|
982
|
+
shared_memory_total_pretty: string;
|
|
983
|
+
work_mem_per_connection_bytes: number;
|
|
984
|
+
work_mem_per_connection_pretty: string;
|
|
985
|
+
max_work_mem_usage_bytes: number;
|
|
986
|
+
max_work_mem_usage_pretty: string;
|
|
987
|
+
maintenance_work_mem_bytes: number;
|
|
988
|
+
maintenance_work_mem_pretty: string;
|
|
989
|
+
effective_cache_size_bytes: number;
|
|
990
|
+
effective_cache_size_pretty: string;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
let memoryUsage: MemoryUsage | Record<string, never> = {};
|
|
994
|
+
|
|
995
|
+
try {
|
|
996
|
+
// Get actual byte values from PostgreSQL
|
|
997
|
+
const memQuery = await client.query(`
|
|
998
|
+
select
|
|
999
|
+
pg_size_bytes(current_setting('shared_buffers')) as shared_buffers_bytes,
|
|
1000
|
+
pg_size_bytes(current_setting('wal_buffers')) as wal_buffers_bytes,
|
|
1001
|
+
pg_size_bytes(current_setting('work_mem')) as work_mem_bytes,
|
|
1002
|
+
pg_size_bytes(current_setting('maintenance_work_mem')) as maintenance_work_mem_bytes,
|
|
1003
|
+
pg_size_bytes(current_setting('effective_cache_size')) as effective_cache_size_bytes,
|
|
1004
|
+
current_setting('max_connections')::int as max_connections
|
|
1005
|
+
`);
|
|
1006
|
+
|
|
1007
|
+
if (memQuery.rows.length > 0) {
|
|
1008
|
+
const row = memQuery.rows[0];
|
|
1009
|
+
const sharedBuffersBytes = parseInt(row.shared_buffers_bytes, 10);
|
|
1010
|
+
const walBuffersBytes = parseInt(row.wal_buffers_bytes, 10);
|
|
1011
|
+
const workMemBytes = parseInt(row.work_mem_bytes, 10);
|
|
1012
|
+
const maintenanceWorkMemBytes = parseInt(row.maintenance_work_mem_bytes, 10);
|
|
1013
|
+
const effectiveCacheSizeBytes = parseInt(row.effective_cache_size_bytes, 10);
|
|
1014
|
+
const maxConnections = row.max_connections;
|
|
1015
|
+
|
|
1016
|
+
const sharedMemoryTotal = sharedBuffersBytes + walBuffersBytes;
|
|
1017
|
+
const maxWorkMemUsage = workMemBytes * maxConnections;
|
|
1018
|
+
|
|
1019
|
+
memoryUsage = {
|
|
1020
|
+
shared_buffers_bytes: sharedBuffersBytes,
|
|
1021
|
+
shared_buffers_pretty: formatBytes(sharedBuffersBytes),
|
|
1022
|
+
wal_buffers_bytes: walBuffersBytes,
|
|
1023
|
+
wal_buffers_pretty: formatBytes(walBuffersBytes),
|
|
1024
|
+
shared_memory_total_bytes: sharedMemoryTotal,
|
|
1025
|
+
shared_memory_total_pretty: formatBytes(sharedMemoryTotal),
|
|
1026
|
+
work_mem_per_connection_bytes: workMemBytes,
|
|
1027
|
+
work_mem_per_connection_pretty: formatBytes(workMemBytes),
|
|
1028
|
+
max_work_mem_usage_bytes: maxWorkMemUsage,
|
|
1029
|
+
max_work_mem_usage_pretty: formatBytes(maxWorkMemUsage),
|
|
1030
|
+
maintenance_work_mem_bytes: maintenanceWorkMemBytes,
|
|
1031
|
+
maintenance_work_mem_pretty: formatBytes(maintenanceWorkMemBytes),
|
|
1032
|
+
effective_cache_size_bytes: effectiveCacheSizeBytes,
|
|
1033
|
+
effective_cache_size_pretty: formatBytes(effectiveCacheSizeBytes),
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
} catch {
|
|
1037
|
+
// If we can't calculate, leave empty object (schema allows this)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
report.results[nodeName] = {
|
|
1041
|
+
data: {
|
|
1042
|
+
settings: memorySettings,
|
|
1043
|
+
analysis: {
|
|
1044
|
+
estimated_total_memory_usage: memoryUsage,
|
|
1045
|
+
},
|
|
1046
|
+
},
|
|
1047
|
+
postgres_version: postgresVersion,
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
return report;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
783
1053
|
/**
|
|
784
1054
|
* Available report generators
|
|
785
1055
|
*/
|
|
@@ -789,6 +1059,9 @@ export const REPORT_GENERATORS: Record<string, (client: Client, nodeName: string
|
|
|
789
1059
|
A004: generateA004,
|
|
790
1060
|
A007: generateA007,
|
|
791
1061
|
A013: generateA013,
|
|
1062
|
+
D004: generateD004,
|
|
1063
|
+
F001: generateF001,
|
|
1064
|
+
G001: generateG001,
|
|
792
1065
|
H001: generateH001,
|
|
793
1066
|
H002: generateH002,
|
|
794
1067
|
H004: generateH004,
|
|
@@ -803,6 +1076,9 @@ export const CHECK_INFO: Record<string, string> = {
|
|
|
803
1076
|
A004: "Cluster information",
|
|
804
1077
|
A007: "Altered settings",
|
|
805
1078
|
A013: "Postgres minor version",
|
|
1079
|
+
D004: "pg_stat_statements and pg_stat_kcache settings",
|
|
1080
|
+
F001: "Autovacuum: current settings",
|
|
1081
|
+
G001: "Memory-related settings",
|
|
806
1082
|
H001: "Invalid indexes",
|
|
807
1083
|
H002: "Unused indexes",
|
|
808
1084
|
H004: "Redundant indexes",
|