postgresai 0.14.0-dev.51 → 0.14.0-dev.52

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.
@@ -1,514 +0,0 @@
1
- /**
2
- * Embedded SQL queries for express checkup reports
3
- *
4
- * IMPORTANT: These SQL queries are extracted from config/pgwatch-prometheus/metrics.yml
5
- * and embedded here for the CLI npm package to work without external dependencies.
6
- *
7
- * When updating queries, ensure both this file AND metrics.yml are kept in sync.
8
- * The metrics.yml remains the source of truth for the monitoring stack.
9
- */
10
-
11
- /**
12
- * Embedded SQL queries for each metric.
13
- * Keys are metric names, values are SQL query strings.
14
- */
15
- const EMBEDDED_SQL: Record<string, string> = {
16
- // =========================================================================
17
- // EXPRESS REPORTS - Simple settings and version queries
18
- // =========================================================================
19
-
20
- express_version: `
21
- select
22
- name,
23
- setting
24
- from pg_settings
25
- where name in ('server_version', 'server_version_num');
26
- `,
27
-
28
- express_settings: `
29
- select
30
- name,
31
- setting,
32
- unit,
33
- category,
34
- context,
35
- vartype,
36
- case when (source <> 'default') then 0 else 1 end as is_default,
37
- case
38
- when unit = '8kB' then pg_size_pretty(setting::bigint * 8192)
39
- when unit = 'kB' then pg_size_pretty(setting::bigint * 1024)
40
- when unit = 'MB' then pg_size_pretty(setting::bigint * 1024 * 1024)
41
- when unit = 'B' then pg_size_pretty(setting::bigint)
42
- when unit = 'ms' then setting || ' ms'
43
- when unit = 's' then setting || ' s'
44
- when unit = 'min' then setting || ' min'
45
- else setting
46
- end as pretty_value
47
- from pg_settings
48
- order by name;
49
- `,
50
-
51
- express_altered_settings: `
52
- select
53
- name,
54
- setting,
55
- unit,
56
- category,
57
- case
58
- when unit = '8kB' then pg_size_pretty(setting::bigint * 8192)
59
- when unit = 'kB' then pg_size_pretty(setting::bigint * 1024)
60
- when unit = 'MB' then pg_size_pretty(setting::bigint * 1024 * 1024)
61
- when unit = 'B' then pg_size_pretty(setting::bigint)
62
- when unit = 'ms' then setting || ' ms'
63
- when unit = 's' then setting || ' s'
64
- when unit = 'min' then setting || ' min'
65
- else setting
66
- end as pretty_value
67
- from pg_settings
68
- where source <> 'default'
69
- order by name;
70
- `,
71
-
72
- express_database_sizes: `
73
- select
74
- datname,
75
- pg_database_size(datname) as size_bytes
76
- from pg_database
77
- where datistemplate = false
78
- order by size_bytes desc;
79
- `,
80
-
81
- express_cluster_stats: `
82
- select
83
- sum(numbackends) as total_connections,
84
- sum(xact_commit) as total_commits,
85
- sum(xact_rollback) as total_rollbacks,
86
- sum(blks_read) as blocks_read,
87
- sum(blks_hit) as blocks_hit,
88
- sum(tup_returned) as tuples_returned,
89
- sum(tup_fetched) as tuples_fetched,
90
- sum(tup_inserted) as tuples_inserted,
91
- sum(tup_updated) as tuples_updated,
92
- sum(tup_deleted) as tuples_deleted,
93
- sum(deadlocks) as total_deadlocks,
94
- sum(temp_files) as temp_files_created,
95
- sum(temp_bytes) as temp_bytes_written
96
- from pg_stat_database
97
- where datname is not null;
98
- `,
99
-
100
- express_connection_states: `
101
- select
102
- coalesce(state, 'null') as state,
103
- count(*) as count
104
- from pg_stat_activity
105
- group by state;
106
- `,
107
-
108
- express_uptime: `
109
- select
110
- pg_postmaster_start_time() as start_time,
111
- current_timestamp - pg_postmaster_start_time() as uptime;
112
- `,
113
-
114
- express_stats_reset: `
115
- select
116
- extract(epoch from stats_reset) as stats_reset_epoch,
117
- stats_reset::text as stats_reset_time,
118
- extract(day from (now() - stats_reset))::integer as days_since_reset,
119
- extract(epoch from pg_postmaster_start_time()) as postmaster_startup_epoch,
120
- pg_postmaster_start_time()::text as postmaster_startup_time
121
- from pg_stat_database
122
- where datname = current_database();
123
- `,
124
-
125
- express_current_database: `
126
- select
127
- current_database() as datname,
128
- pg_database_size(current_database()) as size_bytes;
129
- `,
130
-
131
- // =========================================================================
132
- // INDEX HEALTH REPORTS - H001, H002, H004
133
- // =========================================================================
134
-
135
- pg_invalid_indexes: `
136
- with fk_indexes as (
137
- select
138
- schemaname as tag_schema_name,
139
- (indexrelid::regclass)::text as tag_index_name,
140
- (relid::regclass)::text as tag_table_name,
141
- (confrelid::regclass)::text as tag_fk_table_ref,
142
- array_to_string(indclass, ', ') as tag_opclasses
143
- from
144
- pg_stat_all_indexes
145
- join pg_index using (indexrelid)
146
- left join pg_constraint
147
- on array_to_string(indkey, ',') = array_to_string(conkey, ',')
148
- and schemaname = (connamespace::regnamespace)::text
149
- and conrelid = relid
150
- and contype = 'f'
151
- where idx_scan = 0
152
- and indisunique is false
153
- and conkey is not null
154
- ), data as (
155
- select
156
- pci.relname as tag_index_name,
157
- pn.nspname as tag_schema_name,
158
- pct.relname as tag_table_name,
159
- quote_ident(pn.nspname) as tag_schema_name,
160
- quote_ident(pci.relname) as tag_index_name,
161
- quote_ident(pct.relname) as tag_table_name,
162
- coalesce(nullif(quote_ident(pn.nspname), 'public') || '.', '') || quote_ident(pct.relname) as tag_relation_name,
163
- pg_relation_size(pidx.indexrelid) index_size_bytes,
164
- ((
165
- select count(1)
166
- from fk_indexes fi
167
- where
168
- fi.tag_fk_table_ref = pct.relname
169
- and fi.tag_opclasses like (array_to_string(pidx.indclass, ', ') || '%')
170
- ) > 0)::int as supports_fk
171
- from pg_index pidx
172
- join pg_class as pci on pci.oid = pidx.indexrelid
173
- join pg_class as pct on pct.oid = pidx.indrelid
174
- left join pg_namespace pn on pn.oid = pct.relnamespace
175
- where pidx.indisvalid = false
176
- ), data_total as (
177
- select
178
- sum(index_size_bytes) as index_size_bytes_sum
179
- from data
180
- ), num_data as (
181
- select
182
- row_number() over () num,
183
- data.*
184
- from data
185
- )
186
- select
187
- (extract(epoch from now()) * 1e9)::int8 as epoch_ns,
188
- current_database() as tag_datname,
189
- num_data.*
190
- from num_data
191
- limit 1000;
192
- `,
193
-
194
- unused_indexes: `
195
- with fk_indexes as (
196
- select
197
- n.nspname as schema_name,
198
- ci.relname as index_name,
199
- cr.relname as table_name,
200
- (confrelid::regclass)::text as fk_table_ref,
201
- array_to_string(indclass, ', ') as opclasses
202
- from pg_index i
203
- join pg_class ci on ci.oid = i.indexrelid and ci.relkind = 'i'
204
- join pg_class cr on cr.oid = i.indrelid and cr.relkind = 'r'
205
- join pg_namespace n on n.oid = ci.relnamespace
206
- join pg_constraint cn on cn.conrelid = cr.oid
207
- left join pg_stat_all_indexes as si on si.indexrelid = i.indexrelid
208
- where
209
- contype = 'f'
210
- and i.indisunique is false
211
- and conkey is not null
212
- and ci.relpages > 5
213
- and si.idx_scan < 10
214
- ), table_scans as (
215
- select relid,
216
- tables.idx_scan + tables.seq_scan as all_scans,
217
- ( tables.n_tup_ins + tables.n_tup_upd + tables.n_tup_del ) as writes,
218
- pg_relation_size(relid) as table_size
219
- from pg_stat_all_tables as tables
220
- join pg_class c on c.oid = relid
221
- where c.relpages > 5
222
- ), indexes as (
223
- select
224
- i.indrelid,
225
- i.indexrelid,
226
- n.nspname as schema_name,
227
- cr.relname as table_name,
228
- ci.relname as index_name,
229
- si.idx_scan,
230
- pg_relation_size(i.indexrelid) as index_bytes,
231
- ci.relpages,
232
- (case when a.amname = 'btree' then true else false end) as idx_is_btree,
233
- array_to_string(i.indclass, ', ') as opclasses
234
- from pg_index i
235
- join pg_class ci on ci.oid = i.indexrelid and ci.relkind = 'i'
236
- join pg_class cr on cr.oid = i.indrelid and cr.relkind = 'r'
237
- join pg_namespace n on n.oid = ci.relnamespace
238
- join pg_am a on ci.relam = a.oid
239
- left join pg_stat_all_indexes as si on si.indexrelid = i.indexrelid
240
- where
241
- i.indisunique = false
242
- and i.indisvalid = true
243
- and ci.relpages > 5
244
- ), index_ratios as (
245
- select
246
- i.indexrelid as index_id,
247
- i.schema_name,
248
- i.table_name,
249
- i.index_name,
250
- idx_scan,
251
- all_scans,
252
- round(( case when all_scans = 0 then 0.0::numeric
253
- else idx_scan::numeric/all_scans * 100 end), 2) as index_scan_pct,
254
- writes,
255
- round((case when writes = 0 then idx_scan::numeric else idx_scan::numeric/writes end), 2)
256
- as scans_per_write,
257
- index_bytes as index_size_bytes,
258
- table_size as table_size_bytes,
259
- i.relpages,
260
- idx_is_btree,
261
- i.opclasses,
262
- (
263
- select count(1)
264
- from fk_indexes fi
265
- where fi.fk_table_ref = i.table_name
266
- and fi.schema_name = i.schema_name
267
- and fi.opclasses like (i.opclasses || '%')
268
- ) > 0 as supports_fk
269
- from indexes i
270
- join table_scans ts on ts.relid = i.indrelid
271
- )
272
- select
273
- 'Never Used Indexes' as tag_reason,
274
- current_database() as tag_datname,
275
- index_id,
276
- schema_name as tag_schema_name,
277
- table_name as tag_table_name,
278
- index_name as tag_index_name,
279
- pg_get_indexdef(index_id) as index_definition,
280
- idx_scan,
281
- all_scans,
282
- index_scan_pct,
283
- writes,
284
- scans_per_write,
285
- index_size_bytes,
286
- table_size_bytes,
287
- relpages,
288
- idx_is_btree,
289
- opclasses as tag_opclasses,
290
- supports_fk
291
- from index_ratios
292
- where
293
- idx_scan = 0
294
- and idx_is_btree
295
- order by index_size_bytes desc
296
- limit 1000;
297
- `,
298
-
299
- redundant_indexes: `
300
- with fk_indexes as (
301
- select
302
- n.nspname as schema_name,
303
- ci.relname as index_name,
304
- cr.relname as table_name,
305
- (confrelid::regclass)::text as fk_table_ref,
306
- array_to_string(indclass, ', ') as opclasses
307
- from pg_index i
308
- join pg_class ci on ci.oid = i.indexrelid and ci.relkind = 'i'
309
- join pg_class cr on cr.oid = i.indrelid and cr.relkind = 'r'
310
- join pg_namespace n on n.oid = ci.relnamespace
311
- join pg_constraint cn on cn.conrelid = cr.oid
312
- left join pg_stat_all_indexes as si on si.indexrelid = i.indexrelid
313
- where
314
- contype = 'f'
315
- and i.indisunique is false
316
- and conkey is not null
317
- and ci.relpages > 5
318
- and si.idx_scan < 10
319
- ),
320
- index_data as (
321
- select
322
- *,
323
- indkey::text as columns,
324
- array_to_string(indclass, ', ') as opclasses
325
- from pg_index i
326
- join pg_class ci on ci.oid = i.indexrelid and ci.relkind = 'i'
327
- where indisvalid = true and ci.relpages > 5
328
- ), redundant_indexes as (
329
- select
330
- i2.indexrelid as index_id,
331
- tnsp.nspname as schema_name,
332
- trel.relname as table_name,
333
- pg_relation_size(trel.oid) as table_size_bytes,
334
- irel.relname as index_name,
335
- am1.amname as access_method,
336
- (i1.indexrelid::regclass)::text as reason,
337
- i1.indexrelid as reason_index_id,
338
- pg_get_indexdef(i1.indexrelid) main_index_def,
339
- pg_size_pretty(pg_relation_size(i1.indexrelid)) main_index_size,
340
- pg_get_indexdef(i2.indexrelid) index_def,
341
- pg_relation_size(i2.indexrelid) index_size_bytes,
342
- s.idx_scan as index_usage,
343
- quote_ident(tnsp.nspname) as formated_schema_name,
344
- coalesce(nullif(quote_ident(tnsp.nspname), 'public') || '.', '') || quote_ident(irel.relname) as formated_index_name,
345
- quote_ident(trel.relname) as formated_table_name,
346
- coalesce(nullif(quote_ident(tnsp.nspname), 'public') || '.', '') || quote_ident(trel.relname) as formated_relation_name,
347
- i2.opclasses
348
- from (
349
- select indrelid, indexrelid, opclasses, indclass, indexprs, indpred, indisprimary, indisunique, columns
350
- from index_data
351
- order by indexrelid
352
- ) as i1
353
- join index_data as i2 on (
354
- i1.indrelid = i2.indrelid
355
- and i1.indexrelid <> i2.indexrelid
356
- )
357
- inner join pg_opclass op1 on i1.indclass[0] = op1.oid
358
- inner join pg_opclass op2 on i2.indclass[0] = op2.oid
359
- inner join pg_am am1 on op1.opcmethod = am1.oid
360
- inner join pg_am am2 on op2.opcmethod = am2.oid
361
- join pg_stat_all_indexes as s on s.indexrelid = i2.indexrelid
362
- join pg_class as trel on trel.oid = i2.indrelid
363
- join pg_namespace as tnsp on trel.relnamespace = tnsp.oid
364
- join pg_class as irel on irel.oid = i2.indexrelid
365
- where
366
- not i2.indisprimary
367
- and not i2.indisunique
368
- and am1.amname = am2.amname
369
- and i1.columns like (i2.columns || '%')
370
- and i1.opclasses like (i2.opclasses || '%')
371
- and pg_get_expr(i1.indexprs, i1.indrelid) is not distinct from pg_get_expr(i2.indexprs, i2.indrelid)
372
- and pg_get_expr(i1.indpred, i1.indrelid) is not distinct from pg_get_expr(i2.indpred, i2.indrelid)
373
- ), redundant_indexes_fk as (
374
- select
375
- ri.*,
376
- ((
377
- select count(1)
378
- from fk_indexes fi
379
- where
380
- fi.fk_table_ref = ri.table_name
381
- and fi.opclasses like (ri.opclasses || '%')
382
- ) > 0)::int as supports_fk
383
- from redundant_indexes ri
384
- ),
385
- redundant_indexes_tmp_num as (
386
- select row_number() over () num, rig.*
387
- from redundant_indexes_fk rig
388
- ), redundant_indexes_tmp_links as (
389
- select
390
- ri1.*,
391
- ri2.num as r_num
392
- from redundant_indexes_tmp_num ri1
393
- left join redundant_indexes_tmp_num ri2 on ri2.reason_index_id = ri1.index_id and ri1.reason_index_id = ri2.index_id
394
- ), redundant_indexes_tmp_cut as (
395
- select
396
- *
397
- from redundant_indexes_tmp_links
398
- where num < r_num or r_num is null
399
- ), redundant_indexes_cut_grouped as (
400
- select
401
- distinct(num),
402
- *
403
- from redundant_indexes_tmp_cut
404
- order by index_size_bytes desc
405
- ), redundant_indexes_grouped as (
406
- select
407
- index_id,
408
- schema_name as tag_schema_name,
409
- table_name,
410
- table_size_bytes,
411
- index_name as tag_index_name,
412
- access_method as tag_access_method,
413
- string_agg(distinct reason, ', ') as tag_reason,
414
- index_size_bytes,
415
- index_usage,
416
- index_def as index_definition,
417
- formated_index_name as tag_index_name,
418
- formated_schema_name as tag_schema_name,
419
- formated_table_name as tag_table_name,
420
- formated_relation_name as tag_relation_name,
421
- supports_fk::int as supports_fk,
422
- json_agg(
423
- distinct jsonb_build_object(
424
- 'index_name', reason,
425
- 'index_definition', main_index_def
426
- )
427
- )::text as covering_indexes_json
428
- from redundant_indexes_cut_grouped
429
- group by
430
- index_id,
431
- table_size_bytes,
432
- schema_name,
433
- table_name,
434
- index_name,
435
- access_method,
436
- index_def,
437
- index_size_bytes,
438
- index_usage,
439
- formated_index_name,
440
- formated_schema_name,
441
- formated_table_name,
442
- formated_relation_name,
443
- supports_fk
444
- order by index_size_bytes desc
445
- )
446
- select * from redundant_indexes_grouped
447
- limit 1000;
448
- `,
449
- };
450
-
451
- /**
452
- * Get SQL query for a specific metric.
453
- *
454
- * @param metricName - Name of the metric (e.g., "pg_invalid_indexes", "express_version")
455
- * @param _pgMajorVersion - PostgreSQL major version (currently unused, for future version-specific queries)
456
- * @returns SQL query string
457
- */
458
- export function getMetricSql(metricName: string, _pgMajorVersion: number = 16): string {
459
- const sql = EMBEDDED_SQL[metricName];
460
-
461
- if (!sql) {
462
- throw new Error(`Metric "${metricName}" not found. Available metrics: ${Object.keys(EMBEDDED_SQL).join(", ")}`);
463
- }
464
-
465
- return sql;
466
- }
467
-
468
- /**
469
- * Metric names that correspond to express report checks.
470
- * These map check IDs to metric names in the EMBEDDED_SQL object.
471
- */
472
- export const METRIC_NAMES = {
473
- // Index health checks
474
- H001: "pg_invalid_indexes",
475
- H002: "unused_indexes",
476
- H004: "redundant_indexes",
477
- // Express report metrics
478
- version: "express_version",
479
- settings: "express_settings",
480
- alteredSettings: "express_altered_settings",
481
- databaseSizes: "express_database_sizes",
482
- clusterStats: "express_cluster_stats",
483
- connectionStates: "express_connection_states",
484
- uptimeInfo: "express_uptime",
485
- statsReset: "express_stats_reset",
486
- currentDatabase: "express_current_database",
487
- } as const;
488
-
489
- /**
490
- * Transform a row from metrics query output to JSON report format.
491
- * Metrics use `tag_` prefix for dimensions; we strip it for JSON reports.
492
- * Also removes Prometheus-specific fields like epoch_ns, num.
493
- */
494
- export function transformMetricRow(row: Record<string, unknown>): Record<string, unknown> {
495
- const result: Record<string, unknown> = {};
496
-
497
- for (const [key, value] of Object.entries(row)) {
498
- // Skip Prometheus-specific fields
499
- if (key === "epoch_ns" || key === "num" || key === "tag_datname") {
500
- continue;
501
- }
502
-
503
- // Strip tag_ prefix
504
- const newKey = key.startsWith("tag_") ? key.slice(4) : key;
505
- result[newKey] = value;
506
- }
507
-
508
- return result;
509
- }
510
-
511
- // Legacy export for backward compatibility (no longer loads from file)
512
- export function loadMetricsYml(): { metrics: Record<string, unknown> } {
513
- return { metrics: EMBEDDED_SQL };
514
- }