github.com/netdata/go.d.plugin@v0.58.1/modules/postgres/queries.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package postgres 4 5 func queryServerVersion() string { 6 return "SHOW server_version_num;" 7 } 8 9 func queryIsSuperUser() string { 10 return "SELECT current_setting('is_superuser') = 'on' AS is_superuser;" 11 } 12 13 func queryPGIsInRecovery() string { 14 return "SELECT pg_is_in_recovery();" 15 } 16 17 func querySettingsMaxConnections() string { 18 return "SELECT current_setting('max_connections')::INT - current_setting('superuser_reserved_connections')::INT;" 19 } 20 21 func querySettingsMaxLocksHeld() string { 22 return ` 23 SELECT current_setting('max_locks_per_transaction')::INT * 24 (current_setting('max_connections')::INT + current_setting('max_prepared_transactions')::INT); 25 ` 26 } 27 28 // TODO: this is not correct and we should use pg_stat_activity. 29 // But we need to check what connections (backend_type) count towards 'max_connections'. 30 // I think python version query doesn't count it correctly. 31 // https://github.com/netdata/netdata/blob/1782e2d002bc5203128e5a5d2b801010e2822d2d/collectors/python.d.plugin/postgres/postgres.chart.py#L266 32 func queryServerCurrentConnectionsUsed() string { 33 return "SELECT sum(numbackends) FROM pg_stat_database;" 34 } 35 36 func queryServerConnectionsState() string { 37 return ` 38 SELECT state, 39 COUNT(*) 40 FROM pg_stat_activity 41 WHERE state IN 42 ( 43 'active', 44 'idle', 45 'idle in transaction', 46 'idle in transaction (aborted)', 47 'fastpath function call', 48 'disabled' 49 ) 50 GROUP BY state; 51 ` 52 } 53 54 func queryCheckpoints() string { 55 // definition by version: https://pgpedia.info/p/pg_stat_bgwriter.html 56 // docs: https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-BGWRITER-VIEW 57 // code: https://github.com/postgres/postgres/blob/366283961ac0ed6d89014444c6090f3fd02fce0a/src/backend/catalog/system_views.sql#L1104 58 59 return ` 60 SELECT checkpoints_timed, 61 checkpoints_req, 62 checkpoint_write_time, 63 checkpoint_sync_time, 64 buffers_checkpoint * current_setting('block_size')::numeric AS buffers_checkpoint_bytes, 65 buffers_clean * current_setting('block_size')::numeric AS buffers_clean_bytes, 66 maxwritten_clean, 67 buffers_backend * current_setting('block_size')::numeric AS buffers_backend_bytes, 68 buffers_backend_fsync, 69 buffers_alloc * current_setting('block_size')::numeric AS buffers_alloc_bytes 70 FROM pg_stat_bgwriter; 71 ` 72 } 73 74 func queryServerUptime() string { 75 return `SELECT EXTRACT(epoch FROM CURRENT_TIMESTAMP - pg_postmaster_start_time());` 76 } 77 78 func queryTXIDWraparound() string { 79 // https://www.crunchydata.com/blog/managing-transaction-id-wraparound-in-postgresql 80 return ` 81 WITH max_age AS ( SELECT 82 2000000000 as max_old_xid, 83 setting AS autovacuum_freeze_max_age 84 FROM 85 pg_catalog.pg_settings 86 WHERE 87 name = 'autovacuum_freeze_max_age'), per_database_stats AS ( SELECT 88 datname , 89 m.max_old_xid::int , 90 m.autovacuum_freeze_max_age::int , 91 age(d.datfrozenxid) AS oldest_current_xid 92 FROM 93 pg_catalog.pg_database d 94 JOIN 95 max_age m 96 ON (true) 97 WHERE 98 d.datallowconn) SELECT 99 max(oldest_current_xid) AS oldest_current_xid , 100 max(ROUND(100*(oldest_current_xid/max_old_xid::float))) AS percent_towards_wraparound , 101 max(ROUND(100*(oldest_current_xid/autovacuum_freeze_max_age::float))) AS percent_towards_emergency_autovacuum 102 FROM 103 per_database_stats; 104 ` 105 } 106 107 func queryWALWrites(version int) string { 108 if version < pgVersion10 { 109 return ` 110 SELECT 111 pg_xlog_location_diff( 112 CASE 113 pg_is_in_recovery() 114 WHEN 115 TRUE 116 THEN 117 pg_last_xlog_receive_location() 118 ELSE 119 pg_current_xlog_location() 120 END 121 , '0/0') AS wal_writes ; 122 ` 123 } 124 return ` 125 SELECT 126 pg_wal_lsn_diff( 127 CASE 128 pg_is_in_recovery() 129 WHEN 130 TRUE 131 THEN 132 pg_last_wal_receive_lsn() 133 ELSE 134 pg_current_wal_lsn() 135 END 136 , '0/0') AS wal_writes ; 137 ` 138 } 139 140 func queryWALFiles(version int) string { 141 if version < pgVersion10 { 142 return ` 143 SELECT count(*) FILTER (WHERE type = 'recycled') AS wal_recycled_files, 144 count(*) FILTER (WHERE type = 'written') AS wal_written_files 145 FROM (SELECT wal.name, 146 pg_xlogfile_name( 147 CASE pg_is_in_recovery() 148 WHEN true THEN NULL 149 ELSE pg_current_xlog_location() 150 END), 151 CASE 152 WHEN wal.name > pg_xlogfile_name( 153 CASE pg_is_in_recovery() 154 WHEN true THEN NULL 155 ELSE pg_current_xlog_location() 156 END) THEN 'recycled' 157 ELSE 'written' 158 END AS type 159 FROM pg_catalog.pg_ls_dir('pg_xlog') AS wal(name) 160 WHERE name ~ '^[0-9A-F]{24}$' 161 ORDER BY (pg_stat_file('pg_xlog/' || name, true)).modification, 162 wal.name DESC) sub; 163 ` 164 } 165 return ` 166 SELECT count(*) FILTER (WHERE type = 'recycled') AS wal_recycled_files, 167 count(*) FILTER (WHERE type = 'written') AS wal_written_files 168 FROM (SELECT wal.name, 169 pg_walfile_name( 170 CASE pg_is_in_recovery() 171 WHEN true THEN NULL 172 ELSE pg_current_wal_lsn() 173 END), 174 CASE 175 WHEN wal.name > pg_walfile_name( 176 CASE pg_is_in_recovery() 177 WHEN true THEN NULL 178 ELSE pg_current_wal_lsn() 179 END) THEN 'recycled' 180 ELSE 'written' 181 END AS type 182 FROM pg_catalog.pg_ls_dir('pg_wal') AS wal(name) 183 WHERE name ~ '^[0-9A-F]{24}$' 184 ORDER BY (pg_stat_file('pg_wal/' || name, true)).modification, 185 wal.name DESC) sub; 186 ` 187 } 188 189 func queryWALArchiveFiles(version int) string { 190 if version < pgVersion10 { 191 return ` 192 SELECT 193 CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.ready$$r$ as INT)), 194 0) AS INT) AS wal_archive_files_ready_count, 195 CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.done$$r$ AS INT)), 196 0) AS INT) AS wal_archive_files_done_count 197 FROM 198 pg_catalog.pg_ls_dir('pg_xlog/archive_status') AS archive_files (archive_file); 199 ` 200 } 201 return ` 202 SELECT 203 CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.ready$$r$ as INT)), 204 0) AS INT) AS wal_archive_files_ready_count, 205 CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.done$$r$ AS INT)), 206 0) AS INT) AS wal_archive_files_done_count 207 FROM 208 pg_catalog.pg_ls_dir('pg_wal/archive_status') AS archive_files (archive_file); 209 ` 210 } 211 212 func queryCatalogRelations() string { 213 // kind of same as 214 // https://github.com/netdata/netdata/blob/750810e1798e09cc6210e83594eb9ed4905f8f12/collectors/python.d.plugin/postgres/postgres.chart.py#L336-L354 215 // TODO: do we need that? It is optional and disabled by default in py version. 216 return ` 217 SELECT relkind, 218 COUNT(1), 219 SUM(relpages) * current_setting('block_size')::NUMERIC AS size 220 FROM pg_class 221 GROUP BY relkind; 222 ` 223 } 224 225 func queryAutovacuumWorkers() string { 226 // https://github.com/postgres/postgres/blob/9e4f914b5eba3f49ab99bdecdc4f96fac099571f/src/backend/postmaster/autovacuum.c#L3168-L3183 227 return ` 228 SELECT count(*) FILTER ( 229 WHERE 230 query LIKE 'autovacuum: ANALYZE%%' 231 AND query NOT LIKE '%%to prevent wraparound%%' 232 ) AS autovacuum_analyze, 233 count(*) FILTER ( 234 WHERE 235 query LIKE 'autovacuum: VACUUM ANALYZE%%' 236 AND query NOT LIKE '%%to prevent wraparound%%' 237 ) AS autovacuum_vacuum_analyze, 238 count(*) FILTER ( 239 WHERE 240 query LIKE 'autovacuum: VACUUM %.%%' 241 AND query NOT LIKE '%%to prevent wraparound%%' 242 ) AS autovacuum_vacuum, 243 count(*) FILTER ( 244 WHERE 245 query LIKE '%%to prevent wraparound%%' 246 ) AS autovacuum_vacuum_freeze, 247 count(*) FILTER ( 248 WHERE 249 query LIKE 'autovacuum: BRIN summarize%%' 250 ) AS autovacuum_brin_summarize 251 FROM pg_stat_activity 252 WHERE query NOT LIKE '%%pg_stat_activity%%'; 253 ` 254 } 255 256 func queryXactQueryRunningTime() string { 257 return ` 258 SELECT datname, 259 state, 260 EXTRACT(epoch from now() - xact_start) as xact_running_time, 261 EXTRACT(epoch from now() - query_start) as query_running_time 262 FROM pg_stat_activity 263 WHERE datname IS NOT NULL 264 AND state IN 265 ( 266 'active', 267 'idle in transaction', 268 'idle in transaction (aborted)' 269 ) 270 AND backend_type = 'client backend'; 271 ` 272 } 273 274 func queryReplicationStandbyAppDelta(version int) string { 275 if version < pgVersion10 { 276 return ` 277 SELECT application_name, 278 pg_xlog_location_diff( 279 CASE pg_is_in_recovery() 280 WHEN true THEN pg_last_xlog_receive_location() 281 ELSE pg_current_xlog_location() 282 END, 283 sent_location) AS sent_delta, 284 pg_xlog_location_diff( 285 sent_location, write_location) AS write_delta, 286 pg_xlog_location_diff( 287 write_location, flush_location) AS flush_delta, 288 pg_xlog_location_diff( 289 flush_location, replay_location) AS replay_delta 290 FROM pg_stat_replication psr 291 WHERE application_name IS NOT NULL; 292 ` 293 } 294 return ` 295 SELECT application_name, 296 pg_wal_lsn_diff( 297 CASE pg_is_in_recovery() 298 WHEN true THEN pg_last_wal_receive_lsn() 299 ELSE pg_current_wal_lsn() 300 END, 301 sent_lsn) AS sent_delta, 302 pg_wal_lsn_diff( 303 sent_lsn, write_lsn) AS write_delta, 304 pg_wal_lsn_diff( 305 write_lsn, flush_lsn) AS flush_delta, 306 pg_wal_lsn_diff( 307 flush_lsn, replay_lsn) AS replay_delta 308 FROM pg_stat_replication 309 WHERE application_name IS NOT NULL; 310 ` 311 } 312 313 func queryReplicationStandbyAppLag() string { 314 return ` 315 SELECT application_name, 316 COALESCE(EXTRACT(EPOCH FROM write_lag)::bigint, 0) AS write_lag, 317 COALESCE(EXTRACT(EPOCH FROM flush_lag)::bigint, 0) AS flush_lag, 318 COALESCE(EXTRACT(EPOCH FROM replay_lag)::bigint, 0) AS replay_lag 319 FROM pg_stat_replication psr 320 WHERE application_name IS NOT NULL; 321 ` 322 } 323 324 func queryReplicationSlotFiles(version int) string { 325 if version < pgVersion11 { 326 return ` 327 WITH wal_size AS ( 328 SELECT 329 current_setting('wal_block_size')::INT * setting::INT AS val 330 FROM pg_settings 331 WHERE name = 'wal_segment_size' 332 ) 333 SELECT 334 slot_name, 335 slot_type, 336 replslot_wal_keep, 337 count(slot_file) AS replslot_files 338 FROM 339 (SELECT 340 slot.slot_name, 341 CASE 342 WHEN slot_file <> 'state' THEN 1 343 END AS slot_file , 344 slot_type, 345 COALESCE ( 346 floor( 347 CASE WHEN pg_is_in_recovery() 348 THEN ( 349 pg_wal_lsn_diff(pg_last_wal_receive_lsn(), slot.restart_lsn) 350 -- this is needed to account for whole WAL retention and 351 -- not only size retention 352 + (pg_wal_lsn_diff(restart_lsn, '0/0') % s.val) 353 ) / s.val 354 ELSE ( 355 pg_wal_lsn_diff(pg_current_wal_lsn(), slot.restart_lsn) 356 -- this is needed to account for whole WAL retention and 357 -- not only size retention 358 + (pg_walfile_name_offset(restart_lsn)).file_offset 359 ) / s.val 360 END 361 ),0) AS replslot_wal_keep 362 FROM pg_replication_slots slot 363 LEFT JOIN ( 364 SELECT 365 slot2.slot_name, 366 pg_ls_dir('pg_replslot/' || slot2.slot_name) AS slot_file 367 FROM pg_replication_slots slot2 368 ) files (slot_name, slot_file) 369 ON slot.slot_name = files.slot_name 370 CROSS JOIN wal_size s 371 ) AS d 372 GROUP BY 373 slot_name, 374 slot_type, 375 replslot_wal_keep; 376 ` 377 } 378 379 return ` 380 WITH wal_size AS ( 381 SELECT 382 setting::int AS val 383 FROM pg_settings 384 WHERE name = 'wal_segment_size' 385 ) 386 SELECT 387 slot_name, 388 slot_type, 389 replslot_wal_keep, 390 count(slot_file) AS replslot_files 391 FROM 392 (SELECT 393 slot.slot_name, 394 CASE 395 WHEN slot_file <> 'state' THEN 1 396 END AS slot_file , 397 slot_type, 398 COALESCE ( 399 floor( 400 CASE WHEN pg_is_in_recovery() 401 THEN ( 402 pg_wal_lsn_diff(pg_last_wal_receive_lsn(), slot.restart_lsn) 403 -- this is needed to account for whole WAL retention and 404 -- not only size retention 405 + (pg_wal_lsn_diff(restart_lsn, '0/0') % s.val) 406 ) / s.val 407 ELSE ( 408 pg_wal_lsn_diff(pg_current_wal_lsn(), slot.restart_lsn) 409 -- this is needed to account for whole WAL retention and 410 -- not only size retention 411 + (pg_walfile_name_offset(restart_lsn)).file_offset 412 ) / s.val 413 END 414 ),0) AS replslot_wal_keep 415 FROM pg_replication_slots slot 416 LEFT JOIN ( 417 SELECT 418 slot2.slot_name, 419 pg_ls_dir('pg_replslot/' || slot2.slot_name) AS slot_file 420 FROM pg_replication_slots slot2 421 ) files (slot_name, slot_file) 422 ON slot.slot_name = files.slot_name 423 CROSS JOIN wal_size s 424 ) AS d 425 GROUP BY 426 slot_name, 427 slot_type, 428 replslot_wal_keep; 429 ` 430 } 431 432 func queryQueryableDatabaseList() string { 433 return ` 434 SELECT datname 435 FROM pg_database 436 WHERE datallowconn = true 437 AND datistemplate = false 438 AND datname != current_database() 439 AND has_database_privilege((SELECT CURRENT_USER), datname, 'connect'); 440 ` 441 } 442 443 func queryDatabaseStats() string { 444 // definition by version: https://pgpedia.info/p/pg_stat_database.html 445 // docs: https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-VIEW 446 // code: https://github.com/postgres/postgres/blob/366283961ac0ed6d89014444c6090f3fd02fce0a/src/backend/catalog/system_views.sql#L1018 447 448 return ` 449 SELECT stat.datname, 450 numbackends, 451 pg_database.datconnlimit, 452 xact_commit, 453 xact_rollback, 454 blks_read * current_setting('block_size')::numeric AS blks_read_bytes, 455 blks_hit * current_setting('block_size')::numeric AS blks_hit_bytes, 456 tup_returned, 457 tup_fetched, 458 tup_inserted, 459 tup_updated, 460 tup_deleted, 461 conflicts, 462 temp_files, 463 temp_bytes, 464 deadlocks 465 FROM pg_stat_database stat 466 INNER JOIN 467 pg_database 468 ON pg_database.datname = stat.datname 469 WHERE pg_database.datistemplate = false; 470 ` 471 } 472 473 func queryDatabaseSize(version int) string { 474 if version < pgVersion10 { 475 return ` 476 SELECT datname, 477 pg_database_size(datname) AS size 478 FROM pg_database 479 WHERE pg_database.datistemplate = false 480 AND has_database_privilege((SELECT CURRENT_USER), pg_database.datname, 'connect'); 481 ` 482 } 483 return ` 484 SELECT datname, 485 pg_database_size(datname) AS size 486 FROM pg_database 487 WHERE pg_database.datistemplate = false 488 AND (has_database_privilege((SELECT CURRENT_USER), datname, 'connect') 489 OR pg_has_role((SELECT CURRENT_USER), 'pg_read_all_stats', 'MEMBER')); 490 ` 491 } 492 493 func queryDatabaseConflicts() string { 494 // definition by version: https://pgpedia.info/p/pg_stat_database_conflicts.html 495 // docs: https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-CONFLICTS-VIEW 496 // code: https://github.com/postgres/postgres/blob/366283961ac0ed6d89014444c6090f3fd02fce0a/src/backend/catalog/system_views.sql#L1058 497 498 return ` 499 SELECT stat.datname, 500 confl_tablespace, 501 confl_lock, 502 confl_snapshot, 503 confl_bufferpin, 504 confl_deadlock 505 FROM pg_stat_database_conflicts stat 506 INNER JOIN 507 pg_database 508 ON pg_database.datname = stat.datname 509 WHERE pg_database.datistemplate = false; 510 ` 511 } 512 513 func queryDatabaseLocks() string { 514 // definition by version: https://pgpedia.info/p/pg_locks.html 515 // docs: https://www.postgresql.org/docs/current/view-pg-locks.html 516 517 return ` 518 SELECT pg_database.datname, 519 mode, 520 granted, 521 count(mode) AS locks_count 522 FROM pg_locks 523 INNER JOIN 524 pg_database 525 ON pg_database.oid = pg_locks.database 526 WHERE pg_database.datistemplate = false 527 GROUP BY datname, 528 mode, 529 granted 530 ORDER BY datname, 531 mode; 532 ` 533 } 534 535 func queryUserTablesCount() string { 536 return "SELECT count(*) from pg_stat_user_tables;" 537 } 538 539 func queryStatUserTables() string { 540 return ` 541 SELECT current_database() as datname, 542 schemaname, 543 relname, 544 inh.parent_relname, 545 seq_scan, 546 seq_tup_read, 547 idx_scan, 548 idx_tup_fetch, 549 n_tup_ins, 550 n_tup_upd, 551 n_tup_del, 552 n_tup_hot_upd, 553 n_live_tup, 554 n_dead_tup, 555 EXTRACT(epoch from now() - last_vacuum) as last_vacuum, 556 EXTRACT(epoch from now() - last_autovacuum) as last_autovacuum, 557 EXTRACT(epoch from now() - last_analyze) as last_analyze, 558 EXTRACT(epoch from now() - last_autoanalyze) as last_autoanalyze, 559 vacuum_count, 560 autovacuum_count, 561 analyze_count, 562 autoanalyze_count, 563 pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) as total_relation_size 564 FROM pg_stat_user_tables 565 LEFT JOIN( 566 SELECT 567 c.oid AS child_oid, 568 p.relname AS parent_relname 569 FROM 570 pg_inherits 571 JOIN pg_class AS c ON (inhrelid = c.oid) 572 JOIN pg_class AS p ON (inhparent = p.oid) 573 ) AS inh ON inh.child_oid = relid 574 WHERE has_schema_privilege(schemaname, 'USAGE'); 575 ` 576 } 577 578 func queryStatIOUserTables() string { 579 return ` 580 SELECT current_database() AS datname, 581 schemaname, 582 relname, 583 inh.parent_relname, 584 heap_blks_read * current_setting('block_size')::numeric AS heap_blks_read_bytes, 585 heap_blks_hit * current_setting('block_size')::numeric AS heap_blks_hit_bytes, 586 idx_blks_read * current_setting('block_size')::numeric AS idx_blks_read_bytes, 587 idx_blks_hit * current_setting('block_size')::numeric AS idx_blks_hit_bytes, 588 toast_blks_read * current_setting('block_size')::numeric AS toast_blks_read_bytes, 589 toast_blks_hit * current_setting('block_size')::numeric AS toast_blks_hit_bytes, 590 tidx_blks_read * current_setting('block_size')::numeric AS tidx_blks_read_bytes, 591 tidx_blks_hit * current_setting('block_size')::numeric AS tidx_blks_hit_bytes 592 FROM pg_statio_user_tables 593 LEFT JOIN( 594 SELECT 595 c.oid AS child_oid, 596 p.relname AS parent_relname 597 FROM 598 pg_inherits 599 JOIN pg_class AS c ON (inhrelid = c.oid) 600 JOIN pg_class AS p ON (inhparent = p.oid) 601 ) AS inh ON inh.child_oid = relid 602 WHERE has_schema_privilege(schemaname, 'USAGE'); 603 ` 604 } 605 606 func queryUserIndexesCount() string { 607 return "SELECT count(*) from pg_stat_user_indexes;" 608 } 609 610 func queryStatUserIndexes() string { 611 return ` 612 SELECT current_database() as datname, 613 schemaname, 614 relname, 615 indexrelname, 616 inh.parent_relname, 617 idx_scan, 618 idx_tup_read, 619 idx_tup_fetch, 620 pg_relation_size(quote_ident(schemaname) || '.' || quote_ident(indexrelname)::text) as size 621 FROM pg_stat_user_indexes 622 LEFT JOIN( 623 SELECT 624 c.oid AS child_oid, 625 p.relname AS parent_relname 626 FROM 627 pg_inherits 628 JOIN pg_class AS c ON (inhrelid = c.oid) 629 JOIN pg_class AS p ON (inhparent = p.oid) 630 ) AS inh ON inh.child_oid = relid 631 WHERE has_schema_privilege(schemaname, 'USAGE'); 632 ` 633 } 634 635 // The following query for bloat was taken from the venerable check_postgres 636 // script (https://bucardo.org/check_postgres/), which is: 637 // 638 // Copyright (c) 2007-2017 Greg Sabino Mullane 639 //------------------------------------------------------------------------------ 640 641 func queryBloat() string { 642 return ` 643 SELECT 644 current_database() AS db, schemaname, tablename, reltuples::bigint AS tups, relpages::bigint AS pages, otta, 645 ROUND(CASE WHEN otta=0 OR sml.relpages=0 OR sml.relpages=otta THEN 0.0 ELSE sml.relpages/otta::numeric END,1) AS tbloat, 646 CASE WHEN relpages < otta THEN 0 ELSE relpages::bigint - otta END AS wastedpages, 647 CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::bigint END AS wastedbytes, 648 CASE WHEN relpages < otta THEN '0 bytes'::text ELSE (bs*(relpages-otta))::bigint::text || ' bytes' END AS wastedsize, 649 iname, ituples::bigint AS itups, ipages::bigint AS ipages, iotta, 650 ROUND(CASE WHEN iotta=0 OR ipages=0 OR ipages=iotta THEN 0.0 ELSE ipages/iotta::numeric END,1) AS ibloat, 651 CASE WHEN ipages < iotta THEN 0 ELSE ipages::bigint - iotta END AS wastedipages, 652 CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes, 653 CASE WHEN ipages < iotta THEN '0 bytes' ELSE (bs*(ipages-iotta))::bigint::text || ' bytes' END AS wastedisize, 654 CASE WHEN relpages < otta THEN 655 CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta::bigint) END 656 ELSE CASE WHEN ipages < iotta THEN bs*(relpages-otta::bigint) 657 ELSE bs*(relpages-otta::bigint + ipages-iotta::bigint) END 658 END AS totalwastedbytes 659 FROM ( 660 SELECT 661 nn.nspname AS schemaname, 662 cc.relname AS tablename, 663 COALESCE(cc.reltuples,0) AS reltuples, 664 COALESCE(cc.relpages,0) AS relpages, 665 COALESCE(bs,0) AS bs, 666 COALESCE(CEIL((cc.reltuples*((datahdr+ma- 667 (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)),0) AS otta, 668 COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages, 669 COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols 670 FROM 671 pg_class cc 672 JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname <> 'information_schema' 673 LEFT JOIN 674 ( 675 SELECT 676 ma,bs,foo.nspname,foo.relname, 677 (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr, 678 (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2 679 FROM ( 680 SELECT 681 ns.nspname, tbl.relname, hdr, ma, bs, 682 SUM((1-coalesce(null_frac,0))*coalesce(avg_width, 2048)) AS datawidth, 683 MAX(coalesce(null_frac,0)) AS maxfracsum, 684 hdr+( 685 SELECT 1+count(*)/8 686 FROM pg_stats s2 687 WHERE null_frac<>0 AND s2.schemaname = ns.nspname AND s2.tablename = tbl.relname 688 ) AS nullhdr 689 FROM pg_attribute att 690 JOIN pg_class tbl ON att.attrelid = tbl.oid 691 JOIN pg_namespace ns ON ns.oid = tbl.relnamespace 692 LEFT JOIN pg_stats s ON s.schemaname=ns.nspname 693 AND s.tablename = tbl.relname 694 AND s.inherited=false 695 AND s.attname=att.attname, 696 ( 697 SELECT 698 (SELECT current_setting('block_size')::numeric) AS bs, 699 CASE WHEN SUBSTRING(SPLIT_PART(v, ' ', 2) FROM '#"[0-9]+.[0-9]+#"%' for '#') 700 IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr, 701 CASE WHEN v ~ 'mingw32' OR v ~ '64-bit' THEN 8 ELSE 4 END AS ma 702 FROM (SELECT version() AS v) AS foo 703 ) AS constants 704 WHERE att.attnum > 0 AND tbl.relkind='r' 705 GROUP BY 1,2,3,4,5 706 ) AS foo 707 ) AS rs 708 ON cc.relname = rs.relname AND nn.nspname = rs.nspname 709 LEFT JOIN pg_index i ON indrelid = cc.oid 710 LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid 711 ) AS sml 712 WHERE sml.relpages - otta > 10 OR ipages - iotta > 10; 713 ` 714 } 715 716 func queryColumnsStats() string { 717 return ` 718 SELECT current_database() AS datname, 719 nspname AS schemaname, 720 relname, 721 st.attname, 722 typname, 723 (st.null_frac * 100)::int AS null_percent, 724 case 725 when st.n_distinct >= 0 726 then st.n_distinct 727 else 728 abs(st.n_distinct) * reltuples 729 end AS "distinct" 730 FROM pg_class c 731 JOIN 732 pg_namespace ns 733 ON 734 (ns.oid = relnamespace) 735 JOIN 736 pg_attribute at 737 ON 738 (c.oid = attrelid) 739 JOIN 740 pg_type t 741 ON 742 (t.oid = atttypid) 743 JOIN 744 pg_stats st 745 ON 746 (st.tablename = relname AND st.attname = at.attname) 747 WHERE relkind = 'r' 748 AND nspname NOT LIKE E'pg\\_%' 749 AND nspname != 'information_schema' 750 AND NOT attisdropped 751 AND attstattarget != 0 752 AND reltuples >= 100 753 ORDER BY nspname, 754 relname, 755 st.attname; 756 ` 757 }