Customized Statistics Views in PostgreSQL

I always use my customized statistics views shown below because the original ones provide only minimum information.

I show my tools in this post.

Extension

I made an extension to use them easily.

Repository

Download the extension from here:

git clone https://github.com/s-hironobu/pg_stats.git

Installation

Execute make install on the contrib directory, or Put two files, pg_stats.control and pg_stats–1.0.sql, on the ~/share/postgresql/extension/ subdirectory.

Then, execute CREATE EXTENSION pg_stats on all databases you want to use this extension.

TABLES

The view pg_stat_tables is created by joining with pg_stat_user_tables and pg_statio_user_tables, and is added some columns.

Additional columns

column description
idx_scan_ratio Ratio [%] of number of index scans to number of total scans, i.e., 100 * idx_scan/(seq_scan + idx_scan)
hit_ratio Ratio [%] of number of buffer hits to total number of read blocks, i.e., 100 * heap_blks_hit/(heap_blks_read + heap_blks_hit)
ins_ratio Ratio [%] of number of INSERT operations to number of total operations, i.e., 100 * n_tup_ins/(n_tup_ins + n_tup_upd + n_tup_del)
upd_ratio Ratio [%] of number of UPDATE operations to number of total operations, i.e., 100 * n_tup_upd/(n_tup_ins + n_tup_upd + n_tup_del)
del_ratio Ratio [%] of number of DELETE operations to number of total operations, i.e., 100 * n_tup_del/(n_tup_ins + n_tup_upd + n_tup_del)
hot_upd_ratio Ratio [%] of number of rows HOT updated to number of rows updated, i.e., 100 * n_tup_hot_upd / n_tup_upd
table_size Disk space used by the specified fork (‘main’, ‘fsm’, ‘vm’) of the specified table
total_size Total disk space used by the specified table, including all indexes and TOAST data

Example

# SELECT * FROM pg_stat_tables;
 schemaname |     relname      | relid | seq_scan | idx_scan | idx_scan_ratio | seq_tup_read | idx_tup_fetch | heap_blks_read | heap_blks_hit | hit_ratio | n_tup_ins | n_tup_upd | n_tup_del | ins_ratio | upd_ratio | del_ratio | n_tup_hot_upd | hot_upd_ratio | table_size | total_size |          last_vacuum          | last_autovacuum | vacuum_count | autovacuum_count |         last_analyze          |       last_autoanalyze        | analyze_count | autoanalyze_count 
------------+------------------+-------+----------+----------+----------------+--------------+---------------+----------------+---------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+---------------+---------------+------------+------------+-------------------------------+-----------------+--------------+------------------+-------------------------------+-------------------------------+---------------+-------------------
 public     | pgbench_accounts | 16391 |        1 |       60 |             98 |       100000 |            60 |           1644 |          8424 |     83.00 |    100000 |        30 |         0 |     99.97 |      0.03 |      0.00 |             0 |             0 | 13 MB      | 15 MB      | 2016-09-25 07:33:06.279177+00 |                 |            1 |                0 | 2016-09-25 07:33:06.32018+00  | 2016-09-25 07:34:18.637476+00 |             1 |                 1
 public     | pgbench_branches | 16394 |       34 |        0 |              0 |           34 |             0 |              5 |            94 |     94.00 |         1 |        30 |         0 |      3.23 |     96.77 |      0.00 |            30 |           100 | 8192 bytes | 56 kB      | 2016-09-25 07:48:44.158106+00 |                 |            4 |                0 | 2016-09-25 07:33:06.263738+00 |                               |             1 |                 0
 public     | pgbench_history  | 16385 |        0 |          |                |            0 |               |              3 |            27 |     90.00 |        30 |         0 |         0 |    100.00 |      0.00 |      0.00 |             0 |           NaN | 8192 bytes | 8192 bytes | 2016-09-25 07:33:06.382247+00 |                 |            1 |                0 | 2016-09-25 07:33:06.382425+00 |                               |             1 |                 0
 public     | pgbench_tellers  | 16388 |       31 |        0 |              0 |          310 |             0 |              5 |           100 |     95.00 |        10 |        30 |         0 |     25.00 |     75.00 |      0.00 |            30 |           100 | 8192 bytes | 56 kB      | 2016-09-25 07:48:44.158822+00 |                 |            4 |                0 | 2016-09-25 07:33:06.267694+00 |                               |             1 |                 0
(4 rows)

INDEXES

The view pg_stat_indexes is created by joining with pg_stat_user_indexes and pg_statio_user_indexes, and is added some columns.

Additional columns

column description
idx_hit_ratio Ratio [%] of number of buffer hits to number of all index read, i.e., 100 * idx_blks_hit/(idx_blks_read + idx_blks_hit)
index_size Disk space used by the specified fork (‘main’, ‘fsm’) of the specified index

Example

# SELECT * FROM pg_stat_indexes;
 schemaname |     relname      |     indexrelname      | relid | idx_scan | idx_tup_read | idx_tup_fetch | idx_blks_read | idx_blks_hit | idx_hit_ratio | index_size 
------------+------------------+-----------------------+-------+----------+--------------+---------------+---------------+--------------+---------------+------------
 public     | pgbench_accounts | pgbench_accounts_pkey | 16391 |       60 |           90 |            60 |            33 |          153 |            82 | 2208 kB
 public     | pgbench_branches | pgbench_branches_pkey | 16394 |        0 |            0 |             0 |             2 |            7 |            77 | 16 kB
 public     | pgbench_tellers  | pgbench_tellers_pkey  | 16388 |        0 |            0 |             0 |             2 |            4 |            66 | 16 kB
(3 rows)

USERS

The view pg_stat_users shows login time of each user.

column description
dattname Name of the database this backend is connected to
usename Name of the user logged into this backend
pid Process ID of this backend
backend_start Time when this process was started, i.e., when the client connected to the server
login_time How long this backend is running

Example

# SELECT * FROM pg_stat_users;
 datname  | usename | pid  |         backend_start         |  login_time  
----------+---------+------+-------------------------------+--------------
 sampledb | vagrant | 4972 | 2016-09-26 03:32:06.782921+00 | 00:05:09.257
 sampledb | vagrant | 4988 | 2016-09-26 03:35:34.716738+00 | 00:01:41.324
 sampledb | vagrant | 4998 | 2016-09-26 03:37:11.236835+00 | 00:00:04.804
 sampledb | vagrant | 4999 | 2016-09-26 03:37:11.239046+00 | 00:00:04.801
 sampledb | vagrant | 5000 | 2016-09-26 03:37:11.240234+00 | 00:00:04.8
 sampledb | vagrant | 5001 | 2016-09-26 03:37:11.241369+00 | 00:00:04.799
(6 rows)

QUERIES

The view pg_stat_queries shows queries and their durations.

column description
dattname Name of the database this backend is connected to
usename Name of the user logged into this backend
pid Process ID of this backend
duration How long this query is running
waiting True if this backend is currently waiting on a lock
query Text of this backend’s most recent query

Example

# SELECT * FROM pg_stat_queries;
 datname  | usename | pid  |   duration   | waiting |                                   query                                    
----------+---------+------+--------------+---------+----------------------------------------------------------------------------
 sampledb | vagrant | 4988 | 00:01:42.832 | f       | BEGIN;
 sampledb | vagrant | 4998 | 00:00:00.004 | t       | UPDATE pgbench_branches SET bbalance = bbalance + -4057 WHERE bid = 1;
 sampledb | vagrant | 4999 | 00:00:00.004 | f       | END;
 sampledb | vagrant | 5000 | 00:00:00.001 | f       | UPDATE pgbench_accounts SET abalance = abalance + -1378 WHERE aid = 62207;
 sampledb | vagrant | 5001 | 00:00:00.002 | t       | UPDATE pgbench_branches SET bbalance = bbalance + 550 WHERE bid = 1;
(5 rows)

LONG TRANSACTIONS

The view pg_stat_long_trx catches long transactions.

column description
pid Process ID of this backend.
waiting True if this backend is currently waiting on a lock.
duration How long this transaction is running.
query Text of this backend’s most recent query.

Example

# SELECT * FROM pg_stat_long_trx;
 pid  | waiting |   duration   |                                 query                                  
------+---------+--------------+------------------------------------------------------------------------
 4988 | t       | 00:00:34.031 | UPDATE test SET id = 10;
 5026 | f       | 00:05:43.063 | LOCK test;
 5054 | t       | 00:00:00.003 | UPDATE pgbench_branches SET bbalance = bbalance + -3093 WHERE bid = 1;
 5055 | f       | 00:00:00.002 | UPDATE pgbench_tellers SET tbalance = tbalance + -1561 WHERE tid = 1;
 5056 | f       | 00:00:00.001 | SELECT abalance FROM pgbench_accounts WHERE aid = 42689;
 5057 | f       | 00:00:00.007 | END;
(6 rows)

WAITING LOCKS

The view pg_stat_waiting_locks shows waiting locks.

column description
locktype Type of the lockable object
relname Name of the table, index, view, etc.
pid Process ID of this backend
mode Name of the lock mode held or desired by this process
query Text of this backend’s most recent query
duration How long this lock is waiting

Example

# SELECT * FROM pg_stat_waiting_locks;
   locktype    | relname | pid  |       mode       | query  |   duration   
---------------+---------+------+------------------+--------+--------------
 relation      | test    | 4988 | RowExclusiveLock | UPDATE | 00:01:02.087
 transactionid |         | 5061 | ShareLock        | UPDATE | 00:00:00.001
 transactionid |         | 5063 | ShareLock        | UPDATE | 00:00:00.006
 transactionid |         | 5064 | ShareLock        | UPDATE | 00:00:00.01
(4 rows)

Appendix

pg_stat_tables

CREATE OR REPLACE VIEW pg_stat_tables
AS
WITH s AS (
SELECT *, cast((n_tup_ins + n_tup_upd + n_tup_del) AS numeric) AS total
       FROM pg_stat_user_tables
)
SELECT s.schemaname,       s.relname,       s.relid,
       s.seq_scan,         s.idx_scan,
       CASE WHEN s.seq_scan + s.idx_scan = 0 THEN 'NaN'::double precision
            ELSE round(100 * s.idx_scan/(s.seq_scan+s.idx_scan),2)  END AS idx_scan_ratio,

       s.seq_tup_read,       s.idx_tup_fetch,

       sio.heap_blks_read,       sio.heap_blks_hit,
       CASE WHEN sio.heap_blks_read = 0 THEN 0.00
            ELSE round(100*sio.heap_blks_hit/(sio.heap_blks_read+sio.heap_blks_hit),2)  END AS hit_ratio,

       n_tup_ins,       n_tup_upd,       n_tup_del,
       CASE WHEN s.total = 0 THEN 0.00
            ELSE round((100*cast(s.n_tup_ins AS numeric)/s.total) ,2) END AS ins_ratio,
       CASE WHEN s.total = 0 THEN 0.00
            ELSE round((100*cast(s.n_tup_upd AS numeric)/s.total) ,2) END AS upd_ratio,
       CASE WHEN s.total = 0 THEN 0.00
            ELSE round((100*cast(s.n_tup_del AS numeric)/s.total) ,2) END AS del_ratio,

       s.n_tup_hot_upd,
       CASE WHEN s.n_tup_upd = 0 THEN 'NaN'::double precision
            ELSE round(100*cast(cast(n_tup_hot_upd as numeric)/n_tup_upd as numeric), 2) END AS hot_upd_ratio,

       pg_size_pretty(pg_relation_size(sio.relid)) AS "table_size",
       pg_size_pretty(pg_total_relation_size(sio.relid)) AS "total_size",

       s.last_vacuum,       s.last_autovacuum,
       s.vacuum_count,      s.autovacuum_count,
       s.last_analyze,      s.last_autoanalyze,
       s.analyze_count,     s.autoanalyze_count
FROM s, pg_statio_user_tables AS sio WHERE s.relid = sio.relid ORDER BY relname;

pg_stat_indexes

CREATE OR REPLACE VIEW pg_stat_indexes
AS
SELECT s.schemaname,       s.relname,       s.indexrelname,       s.relid,
       s.idx_scan,       s.idx_tup_read,       s.idx_tup_fetch,
       sio.idx_blks_read,       sio.idx_blks_hit,
       CASE WHEN sio.idx_blks_read  + sio.idx_blks_hit = 0 THEN 'NaN'::double precision
       ELSE round(100 * sio.idx_blks_hit/(sio.idx_blks_read + sio.idx_blks_hit), 2) END AS idx_hit_ratio,
       pg_size_pretty(pg_relation_size(s.indexrelid)) AS "index_size"
FROM pg_stat_user_indexes AS s, pg_statio_user_indexes AS sio
WHERE s.relid = sio.relid ORDER BY relname;

pg_stat_users

CREATE OR REPLACE VIEW pg_stat_users
AS
SELECT datname,       usename,       pid,       backend_start, 
       (current_timestamp - backend_start)::interval(3) AS "login_time"
FROM pg_stat_activity;

pg_stat_queries

CREATE OR REPLACE VIEW pg_stat_queries 
AS
SELECT datname,       usename,       pid,
       (current_timestamp - xact_start)::interval(3) AS duration, 
       waiting,       query
FROM pg_stat_activity WHERE pid != pg_backend_pid();

pg_stat_long_trx

CREATE OR REPLACE VIEW pg_stat_long_trx 
AS
SELECT pid,        waiting,
    (current_timestamp - xact_start)::interval(3) AS duration, query
FROM pg_stat_activity
WHERE pid <> pg_backend_pid();

pg_stat_waiting_locks

CREATE OR REPLACE VIEW pg_stat_waiting_locks
AS
SELECT l.locktype,       c.relname,       l.pid,       l.mode,
       substring(a.query, 1, 6) AS query,
       (current_timestamp - xact_start)::interval(3) AS duration
FROM pg_locks AS l
  LEFT OUTER JOIN pg_stat_activity AS a ON l.pid = a.pid
  LEFT OUTER JOIN pg_class AS c ON l.relation = c.oid 
WHERE  NOT l.granted ORDER BY l.pid;