postgresai 0.14.0-dev.45 → 0.14.0-dev.46
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 +209 -2
- package/lib/checkup.ts +276 -0
- package/package.json +1 -1
- package/test/checkup.test.ts +49 -0
- package/test/schema-validation.test.ts +69 -0
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -13064,7 +13064,7 @@ var {
|
|
|
13064
13064
|
// package.json
|
|
13065
13065
|
var package_default = {
|
|
13066
13066
|
name: "postgresai",
|
|
13067
|
-
version: "0.14.0-dev.
|
|
13067
|
+
version: "0.14.0-dev.46",
|
|
13068
13068
|
description: "postgres_ai CLI",
|
|
13069
13069
|
license: "Apache-2.0",
|
|
13070
13070
|
private: false,
|
|
@@ -15881,7 +15881,7 @@ var Result = import_lib.default.Result;
|
|
|
15881
15881
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
15882
15882
|
var defaults = import_lib.default.defaults;
|
|
15883
15883
|
// package.json
|
|
15884
|
-
var version = "0.14.0-dev.
|
|
15884
|
+
var version = "0.14.0-dev.46";
|
|
15885
15885
|
var package_default2 = {
|
|
15886
15886
|
name: "postgresai",
|
|
15887
15887
|
version,
|
|
@@ -27375,12 +27375,216 @@ async function generateH004(client, nodeName = "node-01") {
|
|
|
27375
27375
|
};
|
|
27376
27376
|
return report;
|
|
27377
27377
|
}
|
|
27378
|
+
async function generateD004(client, nodeName) {
|
|
27379
|
+
const report = createBaseReport("D004", "pg_stat_statements and pg_stat_kcache settings", nodeName);
|
|
27380
|
+
const postgresVersion = await getPostgresVersion(client);
|
|
27381
|
+
const allSettings = await getSettings(client);
|
|
27382
|
+
const pgssSettings = {};
|
|
27383
|
+
for (const [name, setting] of Object.entries(allSettings)) {
|
|
27384
|
+
if (name.startsWith("pg_stat_statements") || name.startsWith("pg_stat_kcache")) {
|
|
27385
|
+
pgssSettings[name] = setting;
|
|
27386
|
+
}
|
|
27387
|
+
}
|
|
27388
|
+
let pgssAvailable = false;
|
|
27389
|
+
let pgssMetricsCount = 0;
|
|
27390
|
+
let pgssTotalCalls = 0;
|
|
27391
|
+
const pgssSampleQueries = [];
|
|
27392
|
+
try {
|
|
27393
|
+
const extCheck = await client.query("select 1 from pg_extension where extname = 'pg_stat_statements'");
|
|
27394
|
+
if (extCheck.rows.length > 0) {
|
|
27395
|
+
pgssAvailable = true;
|
|
27396
|
+
const statsResult = await client.query(`
|
|
27397
|
+
select count(*) as cnt, coalesce(sum(calls), 0) as total_calls
|
|
27398
|
+
from pg_stat_statements
|
|
27399
|
+
`);
|
|
27400
|
+
pgssMetricsCount = parseInt(statsResult.rows[0]?.cnt || "0", 10);
|
|
27401
|
+
pgssTotalCalls = parseInt(statsResult.rows[0]?.total_calls || "0", 10);
|
|
27402
|
+
const sampleResult = await client.query(`
|
|
27403
|
+
select
|
|
27404
|
+
queryid::text as queryid,
|
|
27405
|
+
coalesce(usename, 'unknown') as "user",
|
|
27406
|
+
coalesce(datname, 'unknown') as database,
|
|
27407
|
+
calls
|
|
27408
|
+
from pg_stat_statements s
|
|
27409
|
+
left join pg_database d on s.dbid = d.oid
|
|
27410
|
+
left join pg_user u on s.userid = u.usesysid
|
|
27411
|
+
order by calls desc
|
|
27412
|
+
limit 5
|
|
27413
|
+
`);
|
|
27414
|
+
for (const row of sampleResult.rows) {
|
|
27415
|
+
pgssSampleQueries.push({
|
|
27416
|
+
queryid: row.queryid,
|
|
27417
|
+
user: row.user,
|
|
27418
|
+
database: row.database,
|
|
27419
|
+
calls: parseInt(row.calls, 10)
|
|
27420
|
+
});
|
|
27421
|
+
}
|
|
27422
|
+
}
|
|
27423
|
+
} catch {}
|
|
27424
|
+
let kcacheAvailable = false;
|
|
27425
|
+
let kcacheMetricsCount = 0;
|
|
27426
|
+
let kcacheTotalExecTime = 0;
|
|
27427
|
+
let kcacheTotalUserTime = 0;
|
|
27428
|
+
let kcacheTotalSystemTime = 0;
|
|
27429
|
+
const kcacheSampleQueries = [];
|
|
27430
|
+
try {
|
|
27431
|
+
const extCheck = await client.query("select 1 from pg_extension where extname = 'pg_stat_kcache'");
|
|
27432
|
+
if (extCheck.rows.length > 0) {
|
|
27433
|
+
kcacheAvailable = true;
|
|
27434
|
+
const statsResult = await client.query(`
|
|
27435
|
+
select
|
|
27436
|
+
count(*) as cnt,
|
|
27437
|
+
coalesce(sum(exec_user_time + exec_system_time), 0) as total_exec_time,
|
|
27438
|
+
coalesce(sum(exec_user_time), 0) as total_user_time,
|
|
27439
|
+
coalesce(sum(exec_system_time), 0) as total_system_time
|
|
27440
|
+
from pg_stat_kcache
|
|
27441
|
+
`);
|
|
27442
|
+
kcacheMetricsCount = parseInt(statsResult.rows[0]?.cnt || "0", 10);
|
|
27443
|
+
kcacheTotalExecTime = parseFloat(statsResult.rows[0]?.total_exec_time || "0");
|
|
27444
|
+
kcacheTotalUserTime = parseFloat(statsResult.rows[0]?.total_user_time || "0");
|
|
27445
|
+
kcacheTotalSystemTime = parseFloat(statsResult.rows[0]?.total_system_time || "0");
|
|
27446
|
+
const sampleResult = await client.query(`
|
|
27447
|
+
select
|
|
27448
|
+
queryid::text as queryid,
|
|
27449
|
+
coalesce(usename, 'unknown') as "user",
|
|
27450
|
+
(exec_user_time + exec_system_time) as exec_total_time
|
|
27451
|
+
from pg_stat_kcache k
|
|
27452
|
+
left join pg_user u on k.userid = u.usesysid
|
|
27453
|
+
order by (exec_user_time + exec_system_time) desc
|
|
27454
|
+
limit 5
|
|
27455
|
+
`);
|
|
27456
|
+
for (const row of sampleResult.rows) {
|
|
27457
|
+
kcacheSampleQueries.push({
|
|
27458
|
+
queryid: row.queryid,
|
|
27459
|
+
user: row.user,
|
|
27460
|
+
exec_total_time: parseFloat(row.exec_total_time)
|
|
27461
|
+
});
|
|
27462
|
+
}
|
|
27463
|
+
}
|
|
27464
|
+
} catch {}
|
|
27465
|
+
report.results[nodeName] = {
|
|
27466
|
+
data: {
|
|
27467
|
+
settings: pgssSettings,
|
|
27468
|
+
pg_stat_statements_status: {
|
|
27469
|
+
extension_available: pgssAvailable,
|
|
27470
|
+
metrics_count: pgssMetricsCount,
|
|
27471
|
+
total_calls: pgssTotalCalls,
|
|
27472
|
+
sample_queries: pgssSampleQueries
|
|
27473
|
+
},
|
|
27474
|
+
pg_stat_kcache_status: {
|
|
27475
|
+
extension_available: kcacheAvailable,
|
|
27476
|
+
metrics_count: kcacheMetricsCount,
|
|
27477
|
+
total_exec_time: kcacheTotalExecTime,
|
|
27478
|
+
total_user_time: kcacheTotalUserTime,
|
|
27479
|
+
total_system_time: kcacheTotalSystemTime,
|
|
27480
|
+
sample_queries: kcacheSampleQueries
|
|
27481
|
+
}
|
|
27482
|
+
},
|
|
27483
|
+
postgres_version: postgresVersion
|
|
27484
|
+
};
|
|
27485
|
+
return report;
|
|
27486
|
+
}
|
|
27487
|
+
async function generateF001(client, nodeName) {
|
|
27488
|
+
const report = createBaseReport("F001", "Autovacuum: current settings", nodeName);
|
|
27489
|
+
const postgresVersion = await getPostgresVersion(client);
|
|
27490
|
+
const allSettings = await getSettings(client);
|
|
27491
|
+
const autovacuumSettings = {};
|
|
27492
|
+
for (const [name, setting] of Object.entries(allSettings)) {
|
|
27493
|
+
if (name.includes("autovacuum") || name.includes("vacuum")) {
|
|
27494
|
+
autovacuumSettings[name] = setting;
|
|
27495
|
+
}
|
|
27496
|
+
}
|
|
27497
|
+
report.results[nodeName] = {
|
|
27498
|
+
data: autovacuumSettings,
|
|
27499
|
+
postgres_version: postgresVersion
|
|
27500
|
+
};
|
|
27501
|
+
return report;
|
|
27502
|
+
}
|
|
27503
|
+
async function generateG001(client, nodeName) {
|
|
27504
|
+
const report = createBaseReport("G001", "Memory-related settings", nodeName);
|
|
27505
|
+
const postgresVersion = await getPostgresVersion(client);
|
|
27506
|
+
const allSettings = await getSettings(client);
|
|
27507
|
+
const memorySettingNames = [
|
|
27508
|
+
"shared_buffers",
|
|
27509
|
+
"work_mem",
|
|
27510
|
+
"maintenance_work_mem",
|
|
27511
|
+
"effective_cache_size",
|
|
27512
|
+
"wal_buffers",
|
|
27513
|
+
"temp_buffers",
|
|
27514
|
+
"max_connections",
|
|
27515
|
+
"autovacuum_work_mem",
|
|
27516
|
+
"hash_mem_multiplier",
|
|
27517
|
+
"logical_decoding_work_mem",
|
|
27518
|
+
"max_stack_depth",
|
|
27519
|
+
"max_prepared_transactions",
|
|
27520
|
+
"max_locks_per_transaction",
|
|
27521
|
+
"max_pred_locks_per_transaction"
|
|
27522
|
+
];
|
|
27523
|
+
const memorySettings = {};
|
|
27524
|
+
for (const name of memorySettingNames) {
|
|
27525
|
+
if (allSettings[name]) {
|
|
27526
|
+
memorySettings[name] = allSettings[name];
|
|
27527
|
+
}
|
|
27528
|
+
}
|
|
27529
|
+
let memoryUsage = {};
|
|
27530
|
+
try {
|
|
27531
|
+
const memQuery = await client.query(`
|
|
27532
|
+
select
|
|
27533
|
+
pg_size_bytes(current_setting('shared_buffers')) as shared_buffers_bytes,
|
|
27534
|
+
pg_size_bytes(current_setting('wal_buffers')) as wal_buffers_bytes,
|
|
27535
|
+
pg_size_bytes(current_setting('work_mem')) as work_mem_bytes,
|
|
27536
|
+
pg_size_bytes(current_setting('maintenance_work_mem')) as maintenance_work_mem_bytes,
|
|
27537
|
+
pg_size_bytes(current_setting('effective_cache_size')) as effective_cache_size_bytes,
|
|
27538
|
+
current_setting('max_connections')::int as max_connections
|
|
27539
|
+
`);
|
|
27540
|
+
if (memQuery.rows.length > 0) {
|
|
27541
|
+
const row = memQuery.rows[0];
|
|
27542
|
+
const sharedBuffersBytes = parseInt(row.shared_buffers_bytes, 10);
|
|
27543
|
+
const walBuffersBytes = parseInt(row.wal_buffers_bytes, 10);
|
|
27544
|
+
const workMemBytes = parseInt(row.work_mem_bytes, 10);
|
|
27545
|
+
const maintenanceWorkMemBytes = parseInt(row.maintenance_work_mem_bytes, 10);
|
|
27546
|
+
const effectiveCacheSizeBytes = parseInt(row.effective_cache_size_bytes, 10);
|
|
27547
|
+
const maxConnections = row.max_connections;
|
|
27548
|
+
const sharedMemoryTotal = sharedBuffersBytes + walBuffersBytes;
|
|
27549
|
+
const maxWorkMemUsage = workMemBytes * maxConnections;
|
|
27550
|
+
memoryUsage = {
|
|
27551
|
+
shared_buffers_bytes: sharedBuffersBytes,
|
|
27552
|
+
shared_buffers_pretty: formatBytes(sharedBuffersBytes),
|
|
27553
|
+
wal_buffers_bytes: walBuffersBytes,
|
|
27554
|
+
wal_buffers_pretty: formatBytes(walBuffersBytes),
|
|
27555
|
+
shared_memory_total_bytes: sharedMemoryTotal,
|
|
27556
|
+
shared_memory_total_pretty: formatBytes(sharedMemoryTotal),
|
|
27557
|
+
work_mem_per_connection_bytes: workMemBytes,
|
|
27558
|
+
work_mem_per_connection_pretty: formatBytes(workMemBytes),
|
|
27559
|
+
max_work_mem_usage_bytes: maxWorkMemUsage,
|
|
27560
|
+
max_work_mem_usage_pretty: formatBytes(maxWorkMemUsage),
|
|
27561
|
+
maintenance_work_mem_bytes: maintenanceWorkMemBytes,
|
|
27562
|
+
maintenance_work_mem_pretty: formatBytes(maintenanceWorkMemBytes),
|
|
27563
|
+
effective_cache_size_bytes: effectiveCacheSizeBytes,
|
|
27564
|
+
effective_cache_size_pretty: formatBytes(effectiveCacheSizeBytes)
|
|
27565
|
+
};
|
|
27566
|
+
}
|
|
27567
|
+
} catch {}
|
|
27568
|
+
report.results[nodeName] = {
|
|
27569
|
+
data: {
|
|
27570
|
+
settings: memorySettings,
|
|
27571
|
+
analysis: {
|
|
27572
|
+
estimated_total_memory_usage: memoryUsage
|
|
27573
|
+
}
|
|
27574
|
+
},
|
|
27575
|
+
postgres_version: postgresVersion
|
|
27576
|
+
};
|
|
27577
|
+
return report;
|
|
27578
|
+
}
|
|
27378
27579
|
var REPORT_GENERATORS = {
|
|
27379
27580
|
A002: generateA002,
|
|
27380
27581
|
A003: generateA003,
|
|
27381
27582
|
A004: generateA004,
|
|
27382
27583
|
A007: generateA007,
|
|
27383
27584
|
A013: generateA013,
|
|
27585
|
+
D004: generateD004,
|
|
27586
|
+
F001: generateF001,
|
|
27587
|
+
G001: generateG001,
|
|
27384
27588
|
H001: generateH001,
|
|
27385
27589
|
H002: generateH002,
|
|
27386
27590
|
H004: generateH004
|
|
@@ -27391,6 +27595,9 @@ var CHECK_INFO = {
|
|
|
27391
27595
|
A004: "Cluster information",
|
|
27392
27596
|
A007: "Altered settings",
|
|
27393
27597
|
A013: "Postgres minor version",
|
|
27598
|
+
D004: "pg_stat_statements and pg_stat_kcache settings",
|
|
27599
|
+
F001: "Autovacuum: current settings",
|
|
27600
|
+
G001: "Memory-related settings",
|
|
27394
27601
|
H001: "Invalid indexes",
|
|
27395
27602
|
H002: "Unused indexes",
|
|
27396
27603
|
H004: "Redundant indexes"
|
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",
|
package/package.json
CHANGED
package/test/checkup.test.ts
CHANGED
|
@@ -99,6 +99,25 @@ function createMockClient(versionRows: any[], settingsRows: any[], options: Mock
|
|
|
99
99
|
if (sql.includes("redundant_indexes") && sql.includes("columns like")) {
|
|
100
100
|
return { rows: redundantIndexesRows };
|
|
101
101
|
}
|
|
102
|
+
// D004: pg_stat_statements extension check
|
|
103
|
+
if (sql.includes("pg_extension") && sql.includes("pg_stat_statements")) {
|
|
104
|
+
return { rows: [] }; // Extension not installed by default
|
|
105
|
+
}
|
|
106
|
+
// D004: pg_stat_kcache extension check
|
|
107
|
+
if (sql.includes("pg_extension") && sql.includes("pg_stat_kcache")) {
|
|
108
|
+
return { rows: [] }; // Extension not installed by default
|
|
109
|
+
}
|
|
110
|
+
// G001: Memory settings query
|
|
111
|
+
if (sql.includes("pg_size_bytes") && sql.includes("shared_buffers") && sql.includes("work_mem")) {
|
|
112
|
+
return { rows: [{
|
|
113
|
+
shared_buffers_bytes: "134217728",
|
|
114
|
+
wal_buffers_bytes: "4194304",
|
|
115
|
+
work_mem_bytes: "4194304",
|
|
116
|
+
maintenance_work_mem_bytes: "67108864",
|
|
117
|
+
effective_cache_size_bytes: "4294967296",
|
|
118
|
+
max_connections: 100,
|
|
119
|
+
}] };
|
|
120
|
+
}
|
|
102
121
|
throw new Error(`Unexpected query: ${sql}`);
|
|
103
122
|
},
|
|
104
123
|
};
|
|
@@ -208,6 +227,21 @@ describe("CHECK_INFO", () => {
|
|
|
208
227
|
expect("H004" in checkup.CHECK_INFO).toBe(true);
|
|
209
228
|
expect(checkup.CHECK_INFO.H004).toBe("Redundant indexes");
|
|
210
229
|
});
|
|
230
|
+
|
|
231
|
+
test("contains D004", () => {
|
|
232
|
+
expect("D004" in checkup.CHECK_INFO).toBe(true);
|
|
233
|
+
expect(checkup.CHECK_INFO.D004).toBe("pg_stat_statements and pg_stat_kcache settings");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("contains F001", () => {
|
|
237
|
+
expect("F001" in checkup.CHECK_INFO).toBe(true);
|
|
238
|
+
expect(checkup.CHECK_INFO.F001).toBe("Autovacuum: current settings");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("contains G001", () => {
|
|
242
|
+
expect("G001" in checkup.CHECK_INFO).toBe(true);
|
|
243
|
+
expect(checkup.CHECK_INFO.G001).toBe("Memory-related settings");
|
|
244
|
+
});
|
|
211
245
|
});
|
|
212
246
|
|
|
213
247
|
// Tests for REPORT_GENERATORS
|
|
@@ -252,6 +286,21 @@ describe("REPORT_GENERATORS", () => {
|
|
|
252
286
|
expect(typeof checkup.REPORT_GENERATORS.H004).toBe("function");
|
|
253
287
|
});
|
|
254
288
|
|
|
289
|
+
test("has generator for D004", () => {
|
|
290
|
+
expect("D004" in checkup.REPORT_GENERATORS).toBe(true);
|
|
291
|
+
expect(typeof checkup.REPORT_GENERATORS.D004).toBe("function");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("has generator for F001", () => {
|
|
295
|
+
expect("F001" in checkup.REPORT_GENERATORS).toBe(true);
|
|
296
|
+
expect(typeof checkup.REPORT_GENERATORS.F001).toBe("function");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("has generator for G001", () => {
|
|
300
|
+
expect("G001" in checkup.REPORT_GENERATORS).toBe(true);
|
|
301
|
+
expect(typeof checkup.REPORT_GENERATORS.G001).toBe("function");
|
|
302
|
+
});
|
|
303
|
+
|
|
255
304
|
test("REPORT_GENERATORS and CHECK_INFO have same keys", () => {
|
|
256
305
|
const generatorKeys = Object.keys(checkup.REPORT_GENERATORS).sort();
|
|
257
306
|
const infoKeys = Object.keys(checkup.CHECK_INFO).sort();
|
|
@@ -28,6 +28,7 @@ function validateReport(report: any, checkId: string): { valid: boolean; errors:
|
|
|
28
28
|
// Mock client for testing
|
|
29
29
|
function createMockClient(options: {
|
|
30
30
|
versionRows?: any[];
|
|
31
|
+
settingsRows?: any[];
|
|
31
32
|
invalidIndexesRows?: any[];
|
|
32
33
|
unusedIndexesRows?: any[];
|
|
33
34
|
redundantIndexesRows?: any[];
|
|
@@ -37,6 +38,12 @@ function createMockClient(options: {
|
|
|
37
38
|
{ name: "server_version", setting: "16.3" },
|
|
38
39
|
{ name: "server_version_num", setting: "160003" },
|
|
39
40
|
],
|
|
41
|
+
settingsRows = [
|
|
42
|
+
{ name: "shared_buffers", setting: "128MB", unit: "", category: "Resource Usage / Memory", context: "postmaster", vartype: "string", pretty_value: "128 MB" },
|
|
43
|
+
{ name: "work_mem", setting: "4MB", unit: "", category: "Resource Usage / Memory", context: "user", vartype: "string", pretty_value: "4 MB" },
|
|
44
|
+
{ name: "autovacuum", setting: "on", unit: "", category: "Autovacuum", context: "sighup", vartype: "bool", pretty_value: "on" },
|
|
45
|
+
{ name: "pg_stat_statements.max", setting: "5000", unit: "", category: "Custom", context: "superuser", vartype: "integer", pretty_value: "5000" },
|
|
46
|
+
],
|
|
40
47
|
invalidIndexesRows = [],
|
|
41
48
|
unusedIndexesRows = [],
|
|
42
49
|
redundantIndexesRows = [],
|
|
@@ -47,6 +54,10 @@ function createMockClient(options: {
|
|
|
47
54
|
if (sql.includes("server_version") && sql.includes("server_version_num") && !sql.includes("order by")) {
|
|
48
55
|
return { rows: versionRows };
|
|
49
56
|
}
|
|
57
|
+
// Full settings query
|
|
58
|
+
if (sql.includes("pg_settings") && sql.includes("order by") && sql.includes("is_default")) {
|
|
59
|
+
return { rows: settingsRows };
|
|
60
|
+
}
|
|
50
61
|
if (sql.includes("current_database()") && sql.includes("pg_database_size")) {
|
|
51
62
|
return { rows: [{ datname: "testdb", size_bytes: "1073741824" }] };
|
|
52
63
|
}
|
|
@@ -68,6 +79,25 @@ function createMockClient(options: {
|
|
|
68
79
|
if (sql.includes("redundant_indexes") && sql.includes("columns like")) {
|
|
69
80
|
return { rows: redundantIndexesRows };
|
|
70
81
|
}
|
|
82
|
+
// D004: pg_stat_statements extension check
|
|
83
|
+
if (sql.includes("pg_extension") && sql.includes("pg_stat_statements")) {
|
|
84
|
+
return { rows: [] }; // Extension not installed
|
|
85
|
+
}
|
|
86
|
+
// D004: pg_stat_kcache extension check
|
|
87
|
+
if (sql.includes("pg_extension") && sql.includes("pg_stat_kcache")) {
|
|
88
|
+
return { rows: [] }; // Extension not installed
|
|
89
|
+
}
|
|
90
|
+
// G001: Memory settings query
|
|
91
|
+
if (sql.includes("pg_size_bytes") && sql.includes("shared_buffers") && sql.includes("work_mem")) {
|
|
92
|
+
return { rows: [{
|
|
93
|
+
shared_buffers_bytes: "134217728",
|
|
94
|
+
wal_buffers_bytes: "4194304",
|
|
95
|
+
work_mem_bytes: "4194304",
|
|
96
|
+
maintenance_work_mem_bytes: "67108864",
|
|
97
|
+
effective_cache_size_bytes: "4294967296",
|
|
98
|
+
max_connections: 100,
|
|
99
|
+
}] };
|
|
100
|
+
}
|
|
71
101
|
throw new Error(`Unexpected query: ${sql}`);
|
|
72
102
|
},
|
|
73
103
|
};
|
|
@@ -186,3 +216,42 @@ describe("H004 schema validation", () => {
|
|
|
186
216
|
});
|
|
187
217
|
});
|
|
188
218
|
|
|
219
|
+
describe("D004 schema validation", () => {
|
|
220
|
+
test("D004 report validates against schema (extensions not installed)", async () => {
|
|
221
|
+
const mockClient = createMockClient();
|
|
222
|
+
const report = await checkup.REPORT_GENERATORS.D004(mockClient as any, "node-01");
|
|
223
|
+
|
|
224
|
+
const result = validateReport(report, "D004");
|
|
225
|
+
if (!result.valid) {
|
|
226
|
+
console.error("D004 validation errors:", result.errors);
|
|
227
|
+
}
|
|
228
|
+
expect(result.valid).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("F001 schema validation", () => {
|
|
233
|
+
test("F001 report validates against schema", async () => {
|
|
234
|
+
const mockClient = createMockClient();
|
|
235
|
+
const report = await checkup.REPORT_GENERATORS.F001(mockClient as any, "node-01");
|
|
236
|
+
|
|
237
|
+
const result = validateReport(report, "F001");
|
|
238
|
+
if (!result.valid) {
|
|
239
|
+
console.error("F001 validation errors:", result.errors);
|
|
240
|
+
}
|
|
241
|
+
expect(result.valid).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("G001 schema validation", () => {
|
|
246
|
+
test("G001 report validates against schema", async () => {
|
|
247
|
+
const mockClient = createMockClient();
|
|
248
|
+
const report = await checkup.REPORT_GENERATORS.G001(mockClient as any, "node-01");
|
|
249
|
+
|
|
250
|
+
const result = validateReport(report, "G001");
|
|
251
|
+
if (!result.valid) {
|
|
252
|
+
console.error("G001 validation errors:", result.errors);
|
|
253
|
+
}
|
|
254
|
+
expect(result.valid).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|