postgresai 0.14.0-dev.82 → 0.14.0-dev.83
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/bin/postgres-ai.ts +11 -7
- package/bun.lock +4 -4
- package/dist/bin/postgres-ai.js +13 -224
- package/lib/checkup.ts +0 -268
- package/lib/mcp-server.ts +1 -1
- package/lib/supabase.ts +17 -4
- package/package.json +3 -3
- package/test/checkup.test.ts +62 -1
- package/test/init.test.ts +10 -10
- package/test/issues.test.ts +11 -11
- package/test/mcp-server.test.ts +40 -40
- package/test/supabase.test.ts +141 -0
package/bin/postgres-ai.ts
CHANGED
|
@@ -758,7 +758,7 @@ program
|
|
|
758
758
|
|
|
759
759
|
const supabaseClient = new SupabaseClient(supabaseConfig);
|
|
760
760
|
|
|
761
|
-
// Fetch database URL for JSON output (
|
|
761
|
+
// Fetch database URL for JSON output (best-effort, errors return null)
|
|
762
762
|
let databaseUrl: string | null = null;
|
|
763
763
|
if (jsonOutput) {
|
|
764
764
|
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig, opts.monitoringUser);
|
|
@@ -1648,12 +1648,13 @@ program
|
|
|
1648
1648
|
.option("--check-id <id>", `specific check to run (see list below), or ALL`, "ALL")
|
|
1649
1649
|
.option("--node-name <name>", "node name for reports", "node-01")
|
|
1650
1650
|
.option("--output <path>", "output directory for JSON files")
|
|
1651
|
-
.option("--
|
|
1651
|
+
.option("--upload", "upload JSON results to PostgresAI (requires API key)")
|
|
1652
|
+
.option("--no-upload", "disable upload to PostgresAI")
|
|
1652
1653
|
.option(
|
|
1653
1654
|
"--project <project>",
|
|
1654
1655
|
"project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)"
|
|
1655
1656
|
)
|
|
1656
|
-
.option("--json", "output JSON to stdout
|
|
1657
|
+
.option("--json", "output JSON to stdout")
|
|
1657
1658
|
.addHelpText(
|
|
1658
1659
|
"after",
|
|
1659
1660
|
[
|
|
@@ -1678,7 +1679,9 @@ program
|
|
|
1678
1679
|
|
|
1679
1680
|
const shouldPrintJson = !!opts.json;
|
|
1680
1681
|
const uploadExplicitlyRequested = opts.upload === true;
|
|
1681
|
-
|
|
1682
|
+
// Note: --json and --upload/--no-upload are independent flags.
|
|
1683
|
+
// Use --no-upload to explicitly disable upload when using --json.
|
|
1684
|
+
const uploadExplicitlyDisabled = opts.upload === false;
|
|
1682
1685
|
let shouldUpload = !uploadExplicitlyDisabled;
|
|
1683
1686
|
|
|
1684
1687
|
// Preflight: validate/create output directory BEFORE connecting / running checks.
|
|
@@ -1830,7 +1833,8 @@ async function resolveOrInitPaths(): Promise<PathResolution> {
|
|
|
1830
1833
|
*/
|
|
1831
1834
|
function isDockerRunning(): boolean {
|
|
1832
1835
|
try {
|
|
1833
|
-
|
|
1836
|
+
// Note: timeout is supported by Bun but not in @types/bun
|
|
1837
|
+
const result = spawnSync("docker", ["info"], { stdio: "pipe", timeout: 5000 } as Parameters<typeof spawnSync>[2]);
|
|
1834
1838
|
return result.status === 0;
|
|
1835
1839
|
} catch {
|
|
1836
1840
|
return false;
|
|
@@ -1842,7 +1846,7 @@ function isDockerRunning(): boolean {
|
|
|
1842
1846
|
*/
|
|
1843
1847
|
function getComposeCmd(): string[] | null {
|
|
1844
1848
|
const tryCmd = (cmd: string, args: string[]): boolean =>
|
|
1845
|
-
spawnSync(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
|
|
1849
|
+
spawnSync(cmd, args, { stdio: "ignore", timeout: 5000 } as Parameters<typeof spawnSync>[2]).status === 0;
|
|
1846
1850
|
if (tryCmd("docker-compose", ["version"])) return ["docker-compose"];
|
|
1847
1851
|
if (tryCmd("docker", ["compose", "version"])) return ["docker", "compose"];
|
|
1848
1852
|
return null;
|
|
@@ -1856,7 +1860,7 @@ function checkRunningContainers(): { running: boolean; containers: string[] } {
|
|
|
1856
1860
|
const result = spawnSync(
|
|
1857
1861
|
"docker",
|
|
1858
1862
|
["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"],
|
|
1859
|
-
{ stdio: "pipe", encoding: "utf8", timeout: 5000 }
|
|
1863
|
+
{ stdio: "pipe", encoding: "utf8", timeout: 5000 } as Parameters<typeof spawnSync>[2]
|
|
1860
1864
|
);
|
|
1861
1865
|
|
|
1862
1866
|
if (result.status === 0 && result.stdout) {
|
package/bun.lock
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
"pg": "^8.16.3",
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@types/bun": "^1.
|
|
14
|
+
"@types/bun": "^1.3.6",
|
|
15
15
|
"@types/js-yaml": "^4.0.9",
|
|
16
16
|
"@types/pg": "^8.15.6",
|
|
17
17
|
"ajv": "^8.17.1",
|
|
18
18
|
"ajv-formats": "^3.0.1",
|
|
19
|
-
"typescript": "^5.
|
|
19
|
+
"typescript": "^5.9.3",
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
},
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.1", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ=="],
|
|
27
27
|
|
|
28
|
-
"@types/bun": ["@types/bun@1.3.
|
|
28
|
+
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
|
29
29
|
|
|
30
30
|
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
|
|
31
31
|
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
"body-parser": ["body-parser@2.2.1", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
|
|
45
45
|
|
|
46
|
-
"bun-types": ["bun-types@1.3.
|
|
46
|
+
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
|
47
47
|
|
|
48
48
|
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
|
49
49
|
|
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.83",
|
|
13068
13068
|
description: "postgres_ai CLI",
|
|
13069
13069
|
license: "Apache-2.0",
|
|
13070
13070
|
private: false,
|
|
@@ -13109,12 +13109,12 @@ var package_default = {
|
|
|
13109
13109
|
pg: "^8.16.3"
|
|
13110
13110
|
},
|
|
13111
13111
|
devDependencies: {
|
|
13112
|
-
"@types/bun": "^1.
|
|
13112
|
+
"@types/bun": "^1.3.6",
|
|
13113
13113
|
"@types/js-yaml": "^4.0.9",
|
|
13114
13114
|
"@types/pg": "^8.15.6",
|
|
13115
13115
|
ajv: "^8.17.1",
|
|
13116
13116
|
"ajv-formats": "^3.0.1",
|
|
13117
|
-
typescript: "^5.
|
|
13117
|
+
typescript: "^5.9.3"
|
|
13118
13118
|
},
|
|
13119
13119
|
publishConfig: {
|
|
13120
13120
|
access: "public"
|
|
@@ -15889,7 +15889,7 @@ var Result = import_lib.default.Result;
|
|
|
15889
15889
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
15890
15890
|
var defaults = import_lib.default.defaults;
|
|
15891
15891
|
// package.json
|
|
15892
|
-
var version = "0.14.0-dev.
|
|
15892
|
+
var version = "0.14.0-dev.83";
|
|
15893
15893
|
var package_default2 = {
|
|
15894
15894
|
name: "postgresai",
|
|
15895
15895
|
version,
|
|
@@ -15937,12 +15937,12 @@ var package_default2 = {
|
|
|
15937
15937
|
pg: "^8.16.3"
|
|
15938
15938
|
},
|
|
15939
15939
|
devDependencies: {
|
|
15940
|
-
"@types/bun": "^1.
|
|
15940
|
+
"@types/bun": "^1.3.6",
|
|
15941
15941
|
"@types/js-yaml": "^4.0.9",
|
|
15942
15942
|
"@types/pg": "^8.15.6",
|
|
15943
15943
|
ajv: "^8.17.1",
|
|
15944
15944
|
"ajv-formats": "^3.0.1",
|
|
15945
|
-
typescript: "^5.
|
|
15945
|
+
typescript: "^5.9.3"
|
|
15946
15946
|
},
|
|
15947
15947
|
publishConfig: {
|
|
15948
15948
|
access: "public"
|
|
@@ -25378,6 +25378,9 @@ class SupabaseClient {
|
|
|
25378
25378
|
}
|
|
25379
25379
|
async function fetchPoolerDatabaseUrl(config2, username) {
|
|
25380
25380
|
const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(config2.projectRef)}/config/database/pooler`;
|
|
25381
|
+
const suffix = `.${config2.projectRef}`;
|
|
25382
|
+
const effectiveUsername = username.endsWith(suffix) ? username : `${username}${suffix}`;
|
|
25383
|
+
const encodedUsername = encodeURIComponent(effectiveUsername);
|
|
25381
25384
|
try {
|
|
25382
25385
|
const response = await fetch(url, {
|
|
25383
25386
|
method: "GET",
|
|
@@ -25392,13 +25395,13 @@ async function fetchPoolerDatabaseUrl(config2, username) {
|
|
|
25392
25395
|
if (Array.isArray(data) && data.length > 0) {
|
|
25393
25396
|
const pooler = data[0];
|
|
25394
25397
|
if (pooler.db_host && pooler.db_port && pooler.db_name) {
|
|
25395
|
-
return `postgresql://${
|
|
25398
|
+
return `postgresql://${encodedUsername}@${pooler.db_host}:${pooler.db_port}/${pooler.db_name}`;
|
|
25396
25399
|
}
|
|
25397
25400
|
if (typeof pooler.connection_string === "string") {
|
|
25398
25401
|
try {
|
|
25399
25402
|
const connUrl = new URL(pooler.connection_string);
|
|
25400
25403
|
const portPart = connUrl.port ? `:${connUrl.port}` : "";
|
|
25401
|
-
return `postgresql://${
|
|
25404
|
+
return `postgresql://${encodedUsername}@${connUrl.hostname}${portPart}${connUrl.pathname}`;
|
|
25402
25405
|
} catch {
|
|
25403
25406
|
return null;
|
|
25404
25407
|
}
|
|
@@ -27789,218 +27792,6 @@ async function generateF001(client, nodeName) {
|
|
|
27789
27792
|
};
|
|
27790
27793
|
return report;
|
|
27791
27794
|
}
|
|
27792
|
-
async function generateF004(client, nodeName) {
|
|
27793
|
-
const report = createBaseReport("F004", "Autovacuum: heap bloat estimate", nodeName);
|
|
27794
|
-
const postgresVersion = await getPostgresVersion(client);
|
|
27795
|
-
let bloatData = [];
|
|
27796
|
-
let bloatError = null;
|
|
27797
|
-
try {
|
|
27798
|
-
const result = await client.query(`
|
|
27799
|
-
select
|
|
27800
|
-
schemaname,
|
|
27801
|
-
tblname,
|
|
27802
|
-
(bs * tblpages)::bigint as real_size_bytes,
|
|
27803
|
-
pg_size_pretty((bs * tblpages)::bigint) as real_size_pretty,
|
|
27804
|
-
((tblpages - est_tblpages) * bs)::bigint as extra_size_bytes,
|
|
27805
|
-
pg_size_pretty(((tblpages - est_tblpages) * bs)::bigint) as extra_size_pretty,
|
|
27806
|
-
case when tblpages > 0 and tblpages - est_tblpages > 0
|
|
27807
|
-
then round(100.0 * (tblpages - est_tblpages) / tblpages, 2)
|
|
27808
|
-
else 0
|
|
27809
|
-
end as extra_pct,
|
|
27810
|
-
fillfactor,
|
|
27811
|
-
case when tblpages - est_tblpages_ff > 0
|
|
27812
|
-
then ((tblpages - est_tblpages_ff) * bs)::bigint
|
|
27813
|
-
else 0
|
|
27814
|
-
end as bloat_size_bytes,
|
|
27815
|
-
pg_size_pretty(case when tblpages - est_tblpages_ff > 0
|
|
27816
|
-
then ((tblpages - est_tblpages_ff) * bs)::bigint
|
|
27817
|
-
else 0
|
|
27818
|
-
end) as bloat_size_pretty,
|
|
27819
|
-
case when tblpages > 0 and tblpages - est_tblpages_ff > 0
|
|
27820
|
-
then round(100.0 * (tblpages - est_tblpages_ff) / tblpages, 2)
|
|
27821
|
-
else 0
|
|
27822
|
-
end as bloat_pct,
|
|
27823
|
-
is_na::text
|
|
27824
|
-
from (
|
|
27825
|
-
select
|
|
27826
|
-
ceil(reltuples / ((bs - page_hdr) / tpl_size)) + ceil(toasttuples / 4) as est_tblpages,
|
|
27827
|
-
ceil(reltuples / ((bs - page_hdr) * fillfactor / (tpl_size * 100))) + ceil(toasttuples / 4) as est_tblpages_ff,
|
|
27828
|
-
tblpages, fillfactor, bs, schemaname, tblname, is_na
|
|
27829
|
-
from (
|
|
27830
|
-
select
|
|
27831
|
-
(4 + tpl_hdr_size + tpl_data_size + (2 * ma)
|
|
27832
|
-
- case when tpl_hdr_size % ma = 0 then ma else tpl_hdr_size % ma end
|
|
27833
|
-
- case when ceil(tpl_data_size)::int % ma = 0 then ma else ceil(tpl_data_size)::int % ma end
|
|
27834
|
-
) as tpl_size,
|
|
27835
|
-
(heappages + toastpages) as tblpages,
|
|
27836
|
-
reltuples, toasttuples, bs, page_hdr, schemaname, tblname, fillfactor, is_na
|
|
27837
|
-
from (
|
|
27838
|
-
select
|
|
27839
|
-
ns.nspname as schemaname,
|
|
27840
|
-
tbl.relname as tblname,
|
|
27841
|
-
tbl.reltuples,
|
|
27842
|
-
tbl.relpages as heappages,
|
|
27843
|
-
coalesce(toast.relpages, 0) as toastpages,
|
|
27844
|
-
coalesce(toast.reltuples, 0) as toasttuples,
|
|
27845
|
-
coalesce(substring(array_to_string(tbl.reloptions, ' ') from 'fillfactor=([0-9]+)')::smallint, 100) as fillfactor,
|
|
27846
|
-
current_setting('block_size')::numeric as bs,
|
|
27847
|
-
case when version() ~ 'mingw32' or version() ~ '64-bit|x86_64|ppc64|ia64|amd64' then 8 else 4 end as ma,
|
|
27848
|
-
24 as page_hdr,
|
|
27849
|
-
23 + case when max(coalesce(s.null_frac, 0)) > 0 then (7 + count(s.attname)) / 8 else 0::int end
|
|
27850
|
-
+ case when bool_or(att.attname = 'oid' and att.attnum < 0) then 4 else 0 end as tpl_hdr_size,
|
|
27851
|
-
sum((1 - coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 0)) as tpl_data_size,
|
|
27852
|
-
(bool_or(att.atttypid = 'pg_catalog.name'::regtype)
|
|
27853
|
-
or sum(case when att.attnum > 0 then 1 else 0 end) <> count(s.attname))::int as is_na
|
|
27854
|
-
from pg_attribute as att
|
|
27855
|
-
join pg_class as tbl on att.attrelid = tbl.oid
|
|
27856
|
-
join pg_namespace as ns on ns.oid = tbl.relnamespace
|
|
27857
|
-
left join pg_stats as s on s.schemaname = ns.nspname
|
|
27858
|
-
and s.tablename = tbl.relname and s.attname = att.attname
|
|
27859
|
-
left join pg_class as toast on tbl.reltoastrelid = toast.oid
|
|
27860
|
-
where not att.attisdropped
|
|
27861
|
-
and tbl.relkind in ('r', 'm')
|
|
27862
|
-
and ns.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')
|
|
27863
|
-
group by ns.nspname, tbl.relname, tbl.reltuples, tbl.relpages, toast.relpages, toast.reltuples, tbl.reloptions
|
|
27864
|
-
) as s
|
|
27865
|
-
) as s2
|
|
27866
|
-
) as s3
|
|
27867
|
-
where tblpages > 0 and (bs * tblpages) >= 1024 * 1024 -- exclude tables < 1 MiB
|
|
27868
|
-
order by bloat_size_bytes desc
|
|
27869
|
-
limit 100
|
|
27870
|
-
`);
|
|
27871
|
-
bloatData = result.rows;
|
|
27872
|
-
} catch (err) {
|
|
27873
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
27874
|
-
console.log(`[F004] Error estimating table bloat: ${errorMsg}`);
|
|
27875
|
-
bloatError = errorMsg;
|
|
27876
|
-
}
|
|
27877
|
-
report.results[nodeName] = {
|
|
27878
|
-
data: {
|
|
27879
|
-
tables: bloatData,
|
|
27880
|
-
...bloatError && { error: bloatError }
|
|
27881
|
-
},
|
|
27882
|
-
postgres_version: postgresVersion
|
|
27883
|
-
};
|
|
27884
|
-
return report;
|
|
27885
|
-
}
|
|
27886
|
-
async function generateF005(client, nodeName) {
|
|
27887
|
-
const report = createBaseReport("F005", "Autovacuum: index bloat estimate", nodeName);
|
|
27888
|
-
const postgresVersion = await getPostgresVersion(client);
|
|
27889
|
-
let bloatData = [];
|
|
27890
|
-
let bloatError = null;
|
|
27891
|
-
try {
|
|
27892
|
-
const result = await client.query(`
|
|
27893
|
-
select
|
|
27894
|
-
nspname as schemaname,
|
|
27895
|
-
tblname,
|
|
27896
|
-
idxname,
|
|
27897
|
-
(bs * relpages)::bigint as real_size_bytes,
|
|
27898
|
-
pg_size_pretty((bs * relpages)::bigint) as real_size_pretty,
|
|
27899
|
-
pg_relation_size(tbloid)::bigint as table_size_bytes,
|
|
27900
|
-
pg_size_pretty(pg_relation_size(tbloid)) as table_size_pretty,
|
|
27901
|
-
((relpages - est_pages) * bs)::bigint as extra_size_bytes,
|
|
27902
|
-
pg_size_pretty(((relpages - est_pages) * bs)::bigint) as extra_size_pretty,
|
|
27903
|
-
round(100.0 * (relpages - est_pages) / relpages, 2) as extra_pct,
|
|
27904
|
-
fillfactor,
|
|
27905
|
-
case when relpages > est_pages_ff
|
|
27906
|
-
then ((relpages - est_pages_ff) * bs)::bigint
|
|
27907
|
-
else 0
|
|
27908
|
-
end as bloat_size_bytes,
|
|
27909
|
-
pg_size_pretty(case when relpages > est_pages_ff
|
|
27910
|
-
then ((relpages - est_pages_ff) * bs)::bigint
|
|
27911
|
-
else 0
|
|
27912
|
-
end) as bloat_size_pretty,
|
|
27913
|
-
case when relpages > est_pages_ff
|
|
27914
|
-
then round(100.0 * (relpages - est_pages_ff) / relpages, 2)
|
|
27915
|
-
else 0
|
|
27916
|
-
end as bloat_pct,
|
|
27917
|
-
is_na::text
|
|
27918
|
-
from (
|
|
27919
|
-
select
|
|
27920
|
-
coalesce(1 + ceil(reltuples / floor((bs - pageopqdata - pagehdr) / (4 + nulldatahdrwidth)::float)), 0) as est_pages,
|
|
27921
|
-
coalesce(1 + ceil(reltuples / floor((bs - pageopqdata - pagehdr) * fillfactor / (100 * (4 + nulldatahdrwidth)::float))), 0) as est_pages_ff,
|
|
27922
|
-
bs, nspname, tblname, idxname, relpages, fillfactor, is_na, tbloid
|
|
27923
|
-
from (
|
|
27924
|
-
select
|
|
27925
|
-
maxalign, bs, nspname, tblname, idxname, reltuples, relpages, idxoid, fillfactor, tbloid,
|
|
27926
|
-
(index_tuple_hdr_bm + maxalign
|
|
27927
|
-
- case when index_tuple_hdr_bm % maxalign = 0 then maxalign else index_tuple_hdr_bm % maxalign end
|
|
27928
|
-
+ nulldatawidth + maxalign
|
|
27929
|
-
- case when nulldatawidth = 0 then 0
|
|
27930
|
-
when nulldatawidth::integer % maxalign = 0 then maxalign
|
|
27931
|
-
else nulldatawidth::integer % maxalign end
|
|
27932
|
-
)::numeric as nulldatahdrwidth,
|
|
27933
|
-
pagehdr, pageopqdata, is_na
|
|
27934
|
-
from (
|
|
27935
|
-
select
|
|
27936
|
-
n.nspname, i.tblname, i.idxname, i.reltuples, i.relpages, i.tbloid, i.idxoid, i.fillfactor,
|
|
27937
|
-
current_setting('block_size')::numeric as bs,
|
|
27938
|
-
case when version() ~ 'mingw32' or version() ~ '64-bit|x86_64|ppc64|ia64|amd64' then 8 else 4 end as maxalign,
|
|
27939
|
-
24 as pagehdr,
|
|
27940
|
-
16 as pageopqdata,
|
|
27941
|
-
case when max(coalesce(s.null_frac, 0)) = 0
|
|
27942
|
-
then 8
|
|
27943
|
-
else 8 + ((32 + 8 - 1) / 8)
|
|
27944
|
-
end as index_tuple_hdr_bm,
|
|
27945
|
-
sum((1 - coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 1024)) as nulldatawidth,
|
|
27946
|
-
(max(case when i.atttypid = 'pg_catalog.name'::regtype then 1 else 0 end) > 0)::int as is_na
|
|
27947
|
-
from (
|
|
27948
|
-
select
|
|
27949
|
-
ct.relname as tblname, ct.relnamespace, ic.idxname, ic.attpos, ic.indkey,
|
|
27950
|
-
ic.indkey[ic.attpos], ic.reltuples, ic.relpages, ic.tbloid, ic.idxoid, ic.fillfactor,
|
|
27951
|
-
coalesce(a1.attnum, a2.attnum) as attnum,
|
|
27952
|
-
coalesce(a1.attname, a2.attname) as attname,
|
|
27953
|
-
coalesce(a1.atttypid, a2.atttypid) as atttypid,
|
|
27954
|
-
case when a1.attnum is null then ic.idxname else ct.relname end as attrelname
|
|
27955
|
-
from (
|
|
27956
|
-
select
|
|
27957
|
-
idxname, reltuples, relpages, tbloid, idxoid, fillfactor,
|
|
27958
|
-
indkey, generate_subscripts(indkey, 1) as attpos
|
|
27959
|
-
from (
|
|
27960
|
-
select
|
|
27961
|
-
ci.relname as idxname, ci.reltuples, ci.relpages, i.indrelid as tbloid,
|
|
27962
|
-
i.indexrelid as idxoid,
|
|
27963
|
-
coalesce(substring(array_to_string(ci.reloptions, ' ') from 'fillfactor=([0-9]+)')::smallint, 90) as fillfactor,
|
|
27964
|
-
i.indkey
|
|
27965
|
-
from pg_index as i
|
|
27966
|
-
join pg_class as ci on ci.oid = i.indexrelid
|
|
27967
|
-
join pg_namespace as ns on ns.oid = ci.relnamespace
|
|
27968
|
-
join pg_am as am on am.oid = ci.relam
|
|
27969
|
-
where am.amname = 'btree'
|
|
27970
|
-
and ci.relpages > 0
|
|
27971
|
-
and ns.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')
|
|
27972
|
-
) as idx_data
|
|
27973
|
-
) as ic
|
|
27974
|
-
join pg_class as ct on ct.oid = ic.tbloid
|
|
27975
|
-
left join pg_attribute as a1 on a1.attrelid = ic.idxoid and a1.attnum = ic.indkey[ic.attpos]
|
|
27976
|
-
left join pg_attribute as a2 on a2.attrelid = ic.tbloid and a2.attnum = ic.indkey[ic.attpos]
|
|
27977
|
-
) as i
|
|
27978
|
-
join pg_namespace as n on n.oid = i.relnamespace
|
|
27979
|
-
left join pg_stats as s on s.schemaname = n.nspname
|
|
27980
|
-
and s.tablename = i.attrelname and s.attname = i.attname
|
|
27981
|
-
group by n.nspname, i.tblname, i.idxname, i.reltuples, i.relpages, i.tbloid, i.idxoid, i.fillfactor
|
|
27982
|
-
) as rows_data_stats
|
|
27983
|
-
) as rows_hdr_pdg_stats
|
|
27984
|
-
) as relation_stats
|
|
27985
|
-
where relpages > 0 and (bs * relpages) >= 1024 * 1024 -- exclude indexes < 1 MiB
|
|
27986
|
-
order by bloat_size_bytes desc
|
|
27987
|
-
limit 100
|
|
27988
|
-
`);
|
|
27989
|
-
bloatData = result.rows;
|
|
27990
|
-
} catch (err) {
|
|
27991
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
27992
|
-
console.log(`[F005] Error estimating index bloat: ${errorMsg}`);
|
|
27993
|
-
bloatError = errorMsg;
|
|
27994
|
-
}
|
|
27995
|
-
report.results[nodeName] = {
|
|
27996
|
-
data: {
|
|
27997
|
-
indexes: bloatData,
|
|
27998
|
-
...bloatError && { error: bloatError }
|
|
27999
|
-
},
|
|
28000
|
-
postgres_version: postgresVersion
|
|
28001
|
-
};
|
|
28002
|
-
return report;
|
|
28003
|
-
}
|
|
28004
27795
|
async function generateG001(client, nodeName) {
|
|
28005
27796
|
const report = createBaseReport("G001", "Memory-related settings", nodeName);
|
|
28006
27797
|
const postgresVersion = await getPostgresVersion(client);
|
|
@@ -28151,8 +27942,6 @@ var REPORT_GENERATORS = {
|
|
|
28151
27942
|
D001: generateD001,
|
|
28152
27943
|
D004: generateD004,
|
|
28153
27944
|
F001: generateF001,
|
|
28154
|
-
F004: generateF004,
|
|
28155
|
-
F005: generateF005,
|
|
28156
27945
|
G001: generateG001,
|
|
28157
27946
|
G003: generateG003,
|
|
28158
27947
|
H001: generateH001,
|
|
@@ -29698,7 +29487,7 @@ program2.command("unprepare-db [conn]").description("remove monitoring setup: dr
|
|
|
29698
29487
|
closeReadline();
|
|
29699
29488
|
}
|
|
29700
29489
|
});
|
|
29701
|
-
program2.command("checkup [conn]").description("generate health check reports directly from PostgreSQL (express mode)").option("--check-id <id>", `specific check to run (see list below), or ALL`, "ALL").option("--node-name <name>", "node name for reports", "node-01").option("--output <path>", "output directory for JSON files").option("--
|
|
29490
|
+
program2.command("checkup [conn]").description("generate health check reports directly from PostgreSQL (express mode)").option("--check-id <id>", `specific check to run (see list below), or ALL`, "ALL").option("--node-name <name>", "node name for reports", "node-01").option("--output <path>", "output directory for JSON files").option("--upload", "upload JSON results to PostgresAI (requires API key)").option("--no-upload", "disable upload to PostgresAI").option("--project <project>", "project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)").option("--json", "output JSON to stdout").addHelpText("after", [
|
|
29702
29491
|
"",
|
|
29703
29492
|
"Available checks:",
|
|
29704
29493
|
...Object.entries(CHECK_INFO).map(([id, title]) => ` ${id}: ${title}`),
|
|
@@ -29718,7 +29507,7 @@ program2.command("checkup [conn]").description("generate health check reports di
|
|
|
29718
29507
|
}
|
|
29719
29508
|
const shouldPrintJson = !!opts.json;
|
|
29720
29509
|
const uploadExplicitlyRequested = opts.upload === true;
|
|
29721
|
-
const uploadExplicitlyDisabled = opts.upload === false
|
|
29510
|
+
const uploadExplicitlyDisabled = opts.upload === false;
|
|
29722
29511
|
let shouldUpload = !uploadExplicitlyDisabled;
|
|
29723
29512
|
const outputPath = prepareOutputDirectory(opts.output);
|
|
29724
29513
|
if (outputPath === null) {
|