486 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			486 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
#!/bin/bash
 | 
						|
# +------------------------------------------------------------------+
 | 
						|
# |             ____ _               _        __  __ _  __           |
 | 
						|
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
 | 
						|
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
 | 
						|
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
 | 
						|
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
 | 
						|
# |                                                                  |
 | 
						|
# | Copyright Mathias Kettner 2015             mk@mathias-kettner.de |
 | 
						|
# +------------------------------------------------------------------+
 | 
						|
#
 | 
						|
# This file is part of Check_MK.
 | 
						|
# The official homepage is at http://mathias-kettner.de/check_mk.
 | 
						|
#
 | 
						|
# check_mk is free software;  you can redistribute it and/or modify it
 | 
						|
# under the  terms of the  GNU General Public License  as published by
 | 
						|
# the Free Software Foundation in version 2.  check_mk is  distributed
 | 
						|
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
 | 
						|
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
 | 
						|
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
 | 
						|
# tails. You should have  received  a copy of the  GNU  General Public
 | 
						|
# License along with GNU Make; see the file  COPYING.  If  not,  write
 | 
						|
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
 | 
						|
# Boston, MA 02110-1301 USA.
 | 
						|
 | 
						|
 | 
						|
# TODO postgres_connections output format
 | 
						|
 | 
						|
 | 
						|
#   .--common funcs--------------------------------------------------------.
 | 
						|
#   |                                             __                       |
 | 
						|
#   |  ___ ___  _ __ ___  _ __ ___   ___  _ __   / _|_   _ _ __   ___ ___  |
 | 
						|
#   | / __/ _ \| '_ ` _ \| '_ ` _ \ / _ \| '_ \ | |_| | | | '_ \ / __/ __| |
 | 
						|
#   || (_| (_) | | | | | | | | | | | (_) | | | ||  _| |_| | | | | (__\__ \ |
 | 
						|
#   | \___\___/|_| |_| |_|_| |_| |_|\___/|_| |_||_|  \__,_|_| |_|\___|___/ |
 | 
						|
#   |                                                                      |
 | 
						|
#   '----------------------------------------------------------------------'
 | 
						|
 | 
						|
 | 
						|
function compare_version_greater_equal() {
 | 
						|
    local GREATER_ONE
 | 
						|
    GREATER_ONE=$(echo "$1 $2" | awk '{if ($1 >= $2) print $1; else print $2}')
 | 
						|
    if [ "$GREATER_ONE" == "$1" ] ; then
 | 
						|
        return 0
 | 
						|
    else
 | 
						|
        return 1
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#.
 | 
						|
#   .--section funcs-------------------------------------------------------.
 | 
						|
#   |                   _   _                __                            |
 | 
						|
#   |     ___  ___  ___| |_(_) ___  _ __    / _|_   _ _ __   ___ ___       |
 | 
						|
#   |    / __|/ _ \/ __| __| |/ _ \| '_ \  | |_| | | | '_ \ / __/ __|      |
 | 
						|
#   |    \__ \  __/ (__| |_| | (_) | | | | |  _| |_| | | | | (__\__ \      |
 | 
						|
#   |    |___/\___|\___|\__|_|\___/|_| |_| |_|  \__,_|_| |_|\___|___/      |
 | 
						|
#   |                                                                      |
 | 
						|
#   '----------------------------------------------------------------------'
 | 
						|
 | 
						|
 | 
						|
function postgres_instances() {
 | 
						|
    echo '<<<postgres_instances>>>'
 | 
						|
    # If we have no instances we take db id (pqsql/postgres) because
 | 
						|
    # ps output may be unreadable
 | 
						|
    # In case of instances ps output shows them readable
 | 
						|
    if [ ! -z "${1}" ]; then
 | 
						|
        echo "[[[${1}]]]"
 | 
						|
    fi
 | 
						|
    pgrep -laf bin/postgres
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function postgres_sessions() {
 | 
						|
    # Postgres 9.2 uses 'query' instead of 'current_query'
 | 
						|
    local OUTPUT
 | 
						|
    OUTPUT="$(echo "\echo '<<<postgres_sessions>>>${INSTANCE_SECTION}'
 | 
						|
                    SELECT (
 | 
						|
                        SELECT column_name
 | 
						|
                        FROM information_schema.columns
 | 
						|
                        WHERE table_name='pg_stat_activity' AND column_name in ('query', 'current_query')
 | 
						|
                    ) = '<IDLE>' as query, count(*)
 | 
						|
                    FROM pg_stat_activity
 | 
						|
                    GROUP BY (query = '<IDLE>');" |\
 | 
						|
              sudo -u "$DBUSER" $export_PGPASSFILE $psql -X --variable ON_ERROR_STOP=1 -d $PGDATABASE ${EXTRA_ARGS} -A -t -F' ' 2>/dev/null)"
 | 
						|
 | 
						|
    echo "$OUTPUT"
 | 
						|
    # line with number of idle sessions is sometimes missing on Postgres 8.x. This can lead
 | 
						|
    # to an altogether empty section and thus the check disappearing.
 | 
						|
    echo "$OUTPUT" | grep -q '^t ' || echo "t 0"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function postgres_simple_queries() {
 | 
						|
    # Querytime
 | 
						|
    # Supports versions >= 8.3, > 9.1
 | 
						|
    local QUERYTIME_QUERY
 | 
						|
    if compare_version_greater_equal "$POSTGRES_VERSION" "9.2" ; then
 | 
						|
        QUERYTIME_QUERY="SELECT datname, datid, usename, client_addr, state AS state, COALESCE(ROUND(EXTRACT(epoch FROM now()-query_start)),0) AS seconds,
 | 
						|
        pid, regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) AS current_query FROM pg_stat_activity WHERE (query_start IS NOT NULL AND (state NOT LIKE 'idle%' OR state IS NULL)) ORDER BY query_start, pid DESC;"
 | 
						|
    else
 | 
						|
        QUERYTIME_QUERY="SELECT datname, datid, usename, client_addr, '' AS state,    COALESCE(ROUND(EXTRACT(epoch FROM now()-query_start)),0) AS seconds,
 | 
						|
        procpid as pid, regexp_replace(current_query, E'[\\n\\r\\u2028]+', ' ', 'g' ) AS current_query FROM pg_stat_activity WHERE (query_start IS NOT NULL AND current_query NOT LIKE '<IDLE>%') ORDER BY query_start, procpid DESC;"
 | 
						|
    fi
 | 
						|
 | 
						|
    # Number of current connections per database
 | 
						|
    # We need to output the databases, too.
 | 
						|
    # This query does not report databases without an active query
 | 
						|
    local CONNECTIONS_QUERY
 | 
						|
    if compare_version_greater_equal "$POSTGRES_VERSION" "9.2" ; then
 | 
						|
        CONNECTIONS_QUERY="SELECT COUNT(datid) AS current,
 | 
						|
          (SELECT setting AS mc FROM pg_settings WHERE name = 'max_connections') AS mc,
 | 
						|
          d.datname
 | 
						|
        FROM pg_database d
 | 
						|
        LEFT JOIN pg_stat_activity s ON (s.datid = d.oid) WHERE state <> 'idle'
 | 
						|
        GROUP BY 2,3
 | 
						|
        ORDER BY datname;"
 | 
						|
    else
 | 
						|
        CONNECTIONS_QUERY="SELECT COUNT(datid) AS current,
 | 
						|
          (SELECT setting AS mc FROM pg_settings WHERE name = 'max_connections') AS mc,
 | 
						|
          d.datname
 | 
						|
        FROM pg_database d
 | 
						|
        LEFT JOIN pg_stat_activity s ON (s.datid = d.oid) WHERE current_query <> '<IDLE>'
 | 
						|
        GROUP BY 2,3
 | 
						|
        ORDER BY datname;"
 | 
						|
    fi
 | 
						|
 | 
						|
    echo "\pset footer off
 | 
						|
          \echo '<<<postgres_stat_database:sep(59)>>>${INSTANCE_SECTION}'
 | 
						|
          SELECT datid, datname, numbackends, xact_commit, xact_rollback, blks_read, blks_hit, tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted, pg_database_size(datname) AS datsize FROM pg_stat_database;
 | 
						|
 | 
						|
          \echo '<<<postgres_locks:sep(59)>>>${INSTANCE_SECTION}'
 | 
						|
          \echo '[databases_start]'
 | 
						|
          $ECHO_DATABASES
 | 
						|
          \echo '[databases_end]'
 | 
						|
          SELECT datname, granted, mode FROM pg_locks l RIGHT JOIN pg_database d ON (d.oid=l.database) WHERE d.datallowconn;
 | 
						|
 | 
						|
          \echo '<<<postgres_query_duration:sep(59)>>>${INSTANCE_SECTION}'
 | 
						|
          \echo '[databases_start]'
 | 
						|
          $ECHO_DATABASES
 | 
						|
          \echo '[databases_end]'
 | 
						|
          $QUERYTIME_QUERY
 | 
						|
 | 
						|
          \echo '<<<postgres_connections:sep(59)>>>${INSTANCE_SECTION}'
 | 
						|
          \echo '[databases_start]'
 | 
						|
          $ECHO_DATABASES
 | 
						|
          \echo '[databases_end]'
 | 
						|
          $CONNECTIONS_QUERY" \
 | 
						|
        | sudo -u "$DBUSER" $export_PGPASSFILE $psql -X -d $PGDATABASE ${EXTRA_ARGS} -q -A -F';'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function postgres_stats() {
 | 
						|
    # Contains last vacuum time and analyze time
 | 
						|
    local LASTVACUUM="SELECT current_database() AS datname, nspname AS sname, relname AS tname,
 | 
						|
                          CASE WHEN v IS NULL THEN -1 ELSE round(extract(epoch FROM v)) END AS vtime,
 | 
						|
                          CASE WHEN g IS NULL THEN -1 ELSE round(extract(epoch FROM v)) END AS atime
 | 
						|
                      FROM (SELECT nspname, relname, GREATEST(pg_stat_get_last_vacuum_time(c.oid), pg_stat_get_last_autovacuum_time(c.oid)) AS v,
 | 
						|
                                GREATEST(pg_stat_get_last_analyze_time(c.oid), pg_stat_get_last_autoanalyze_time(c.oid)) AS g
 | 
						|
                            FROM pg_class c, pg_namespace n
 | 
						|
                            WHERE relkind = 'r' AND n.oid = c.relnamespace AND n.nspname <> 'information_schema'
 | 
						|
                      ORDER BY 3) AS foo;"
 | 
						|
 | 
						|
    local FIRST=
 | 
						|
    local QUERY="\pset footer off
 | 
						|
                 BEGIN;
 | 
						|
                 SET statement_timeout=30000;
 | 
						|
                 COMMIT;
 | 
						|
 | 
						|
                 \echo '<<<postgres_stats:sep(59)>>>${INSTANCE_SECTION}'
 | 
						|
                 \echo '[databases_start]'
 | 
						|
                 $ECHO_DATABASES
 | 
						|
                 \echo '[databases_end]'"
 | 
						|
 | 
						|
    for db in $DATABASES ; do
 | 
						|
        QUERY="$QUERY
 | 
						|
               \c $db
 | 
						|
               $LASTVACUUM
 | 
						|
              "
 | 
						|
        if [ -z $FIRST ] ; then
 | 
						|
            FIRST=false
 | 
						|
            QUERY="$QUERY
 | 
						|
                   \pset tuples_only on
 | 
						|
                  "
 | 
						|
        fi
 | 
						|
    done
 | 
						|
    echo "$QUERY" | sudo -u "$DBUSER" $export_PGPASSFILE $psql -X ${EXTRA_ARGS} -q -A -F';' | grep -v -e 'COMMIT$' -e 'SET$' -e 'BEGIN$'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function postgres_version() {
 | 
						|
    # Postgres version an connection time
 | 
						|
    echo -e "<<<postgres_version:sep(1)>>>${INSTANCE_SECTION}"
 | 
						|
    (TIMEFORMAT='%3R'; time echo "SELECT version() AS v" |\
 | 
						|
    sudo -u "$DBUSER" $export_PGPASSFILE $psql -X -d $PGDATABASE ${EXTRA_ARGS} -t -A -F';'; echo -e "<<<postgres_conn_time>>>${INSTANCE_SECTION}") 2>&1
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function postgres_bloat() {
 | 
						|
    # Bloat index and tables
 | 
						|
    # Supports versions <9.0, >=9.0
 | 
						|
    # This huge query has been gratefully taken from Greg Sabino Mullane's check_postgres.pl
 | 
						|
    local BLOAT_QUERY
 | 
						|
    if compare_version_greater_equal "$POSTGRES_VERSION" "9.0" ; then
 | 
						|
        BLOAT_QUERY="SELECT
 | 
						|
          current_database() AS db, schemaname, tablename, reltuples::bigint AS tups, relpages::bigint AS pages, otta,
 | 
						|
          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,
 | 
						|
          CASE WHEN relpages < otta THEN 0 ELSE relpages::bigint - otta END AS wastedpages,
 | 
						|
          CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::bigint END AS wastedbytes,
 | 
						|
          CASE WHEN relpages < otta THEN 0 ELSE (bs*(relpages-otta))::bigint END AS wastedsize,
 | 
						|
          iname, ituples::bigint AS itups, ipages::bigint AS ipages, iotta,
 | 
						|
          ROUND(CASE WHEN iotta=0 OR ipages=0 OR ipages=iotta THEN 0.0 ELSE ipages/iotta::numeric END,1) AS ibloat,
 | 
						|
          CASE WHEN ipages < iotta THEN 0 ELSE ipages::bigint - iotta END AS wastedipages,
 | 
						|
          CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes,
 | 
						|
          CASE WHEN ipages < iotta THEN 0 ELSE (bs*(ipages-iotta))::bigint END AS wastedisize,
 | 
						|
          CASE WHEN relpages < otta THEN
 | 
						|
            CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta::bigint) END
 | 
						|
            ELSE CASE WHEN ipages < iotta THEN bs*(relpages-otta::bigint)
 | 
						|
              ELSE bs*(relpages-otta::bigint + ipages-iotta::bigint) END
 | 
						|
          END AS totalwastedbytes
 | 
						|
        FROM (
 | 
						|
          SELECT
 | 
						|
            nn.nspname AS schemaname,
 | 
						|
            cc.relname AS tablename,
 | 
						|
            COALESCE(cc.reltuples,0) AS reltuples,
 | 
						|
            COALESCE(cc.relpages,0) AS relpages,
 | 
						|
            COALESCE(bs,0) AS bs,
 | 
						|
            COALESCE(CEIL((cc.reltuples*((datahdr+ma-
 | 
						|
              (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)),0) AS otta,
 | 
						|
            COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
 | 
						|
            COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
 | 
						|
          FROM
 | 
						|
             pg_class cc
 | 
						|
          JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname <> 'information_schema'
 | 
						|
          LEFT JOIN
 | 
						|
          (
 | 
						|
            SELECT
 | 
						|
              ma,bs,foo.nspname,foo.relname,
 | 
						|
              (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
 | 
						|
              (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
 | 
						|
            FROM (
 | 
						|
              SELECT
 | 
						|
                ns.nspname, tbl.relname, hdr, ma, bs,
 | 
						|
                SUM((1-coalesce(null_frac,0))*coalesce(avg_width, 2048)) AS datawidth,
 | 
						|
                MAX(coalesce(null_frac,0)) AS maxfracsum,
 | 
						|
                hdr+(
 | 
						|
                  SELECT 1+count(*)/8
 | 
						|
                  FROM pg_stats s2
 | 
						|
                  WHERE null_frac<>0 AND s2.schemaname = ns.nspname AND s2.tablename = tbl.relname
 | 
						|
                ) AS nullhdr
 | 
						|
              FROM pg_attribute att
 | 
						|
              JOIN pg_class tbl ON att.attrelid = tbl.oid
 | 
						|
              JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
 | 
						|
              LEFT JOIN pg_stats s ON s.schemaname=ns.nspname
 | 
						|
              AND s.tablename = tbl.relname
 | 
						|
              AND s.inherited=false
 | 
						|
              AND s.attname=att.attname,
 | 
						|
              (
 | 
						|
                SELECT
 | 
						|
                  (SELECT current_setting('block_size')::numeric) AS bs,
 | 
						|
                    CASE WHEN SUBSTRING(SPLIT_PART(v, ' ', 2) FROM '#\[0-9]+.[0-9]+#\%' for '#')
 | 
						|
                      IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr,
 | 
						|
                  CASE WHEN v ~ 'mingw32' OR v ~ '64-bit' THEN 8 ELSE 4 END AS ma
 | 
						|
                FROM (SELECT version() AS v) AS foo
 | 
						|
              ) AS constants
 | 
						|
              WHERE att.attnum > 0 AND tbl.relkind='r'
 | 
						|
              GROUP BY 1,2,3,4,5
 | 
						|
            ) AS foo
 | 
						|
          ) AS rs
 | 
						|
          ON cc.relname = rs.relname AND nn.nspname = rs.nspname
 | 
						|
          LEFT JOIN pg_index i ON indrelid = cc.oid
 | 
						|
          LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
 | 
						|
        ) AS sml
 | 
						|
         WHERE sml.relpages - otta > 0 OR ipages - iotta > 10 ORDER BY totalwastedbytes DESC LIMIT 10;"
 | 
						|
    else
 | 
						|
        BLOAT_QUERY="SELECT
 | 
						|
          current_database() AS db, schemaname, tablename, reltuples::bigint AS tups, relpages::bigint AS pages, otta,
 | 
						|
          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,
 | 
						|
          CASE WHEN relpages < otta THEN 0 ELSE relpages::bigint - otta END AS wastedpages,
 | 
						|
          CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::bigint END AS wastedbytes,
 | 
						|
          CASE WHEN relpages < otta THEN '0 bytes'::text ELSE (bs*(relpages-otta))::bigint || ' bytes' END AS wastedsize,
 | 
						|
          iname, ituples::bigint AS itups, ipages::bigint AS ipages, iotta,
 | 
						|
          ROUND(CASE WHEN iotta=0 OR ipages=0 OR ipages=iotta THEN 0.0 ELSE ipages/iotta::numeric END,1) AS ibloat,
 | 
						|
          CASE WHEN ipages < iotta THEN 0 ELSE ipages::bigint - iotta END AS wastedipages,
 | 
						|
          CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes,
 | 
						|
          CASE WHEN ipages < iotta THEN '0 bytes' ELSE (bs*(ipages-iotta))::bigint || ' bytes' END AS wastedisize,
 | 
						|
          CASE WHEN relpages < otta THEN
 | 
						|
            CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta::bigint) END
 | 
						|
            ELSE CASE WHEN ipages < iotta THEN bs*(relpages-otta::bigint)
 | 
						|
              ELSE bs*(relpages-otta::bigint + ipages-iotta::bigint) END
 | 
						|
          END AS totalwastedbytes
 | 
						|
        FROM (
 | 
						|
          SELECT
 | 
						|
            nn.nspname AS schemaname,
 | 
						|
            cc.relname AS tablename,
 | 
						|
            COALESCE(cc.reltuples,0) AS reltuples,
 | 
						|
            COALESCE(cc.relpages,0) AS relpages,
 | 
						|
            COALESCE(bs,0) AS bs,
 | 
						|
            COALESCE(CEIL((cc.reltuples*((datahdr+ma-
 | 
						|
              (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)),0) AS otta,
 | 
						|
            COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
 | 
						|
            COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
 | 
						|
          FROM
 | 
						|
             pg_class cc
 | 
						|
          JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname <> 'information_schema'
 | 
						|
          LEFT JOIN
 | 
						|
          (
 | 
						|
            SELECT
 | 
						|
              ma,bs,foo.nspname,foo.relname,
 | 
						|
              (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
 | 
						|
              (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
 | 
						|
            FROM (
 | 
						|
              SELECT
 | 
						|
                ns.nspname, tbl.relname, hdr, ma, bs,
 | 
						|
                SUM((1-coalesce(null_frac,0))*coalesce(avg_width, 2048)) AS datawidth,
 | 
						|
                MAX(coalesce(null_frac,0)) AS maxfracsum,
 | 
						|
                hdr+(
 | 
						|
                  SELECT 1+count(*)/8
 | 
						|
                  FROM pg_stats s2
 | 
						|
                  WHERE null_frac<>0 AND s2.schemaname = ns.nspname AND s2.tablename = tbl.relname
 | 
						|
                ) AS nullhdr
 | 
						|
              FROM pg_attribute att
 | 
						|
              JOIN pg_class tbl ON att.attrelid = tbl.oid
 | 
						|
              JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
 | 
						|
              LEFT JOIN pg_stats s ON s.schemaname=ns.nspname
 | 
						|
              AND s.tablename = tbl.relname
 | 
						|
              AND s.attname=att.attname,
 | 
						|
              (
 | 
						|
                SELECT
 | 
						|
                  (SELECT current_setting('block_size')::numeric) AS bs,
 | 
						|
                    CASE WHEN SUBSTRING(SPLIT_PART(v, ' ', 2) FROM '#\"[0-9]+.[0-9]+#\"%' for '#')
 | 
						|
                      IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr,
 | 
						|
                  CASE WHEN v ~ 'mingw32' OR v ~ '64-bit' THEN 8 ELSE 4 END AS ma
 | 
						|
                FROM (SELECT version() AS v) AS foo
 | 
						|
              ) AS constants
 | 
						|
              WHERE att.attnum > 0 AND tbl.relkind='r'
 | 
						|
              GROUP BY 1,2,3,4,5
 | 
						|
            ) AS foo
 | 
						|
          ) AS rs
 | 
						|
          ON cc.relname = rs.relname AND nn.nspname = rs.nspname
 | 
						|
          LEFT JOIN pg_index i ON indrelid = cc.oid
 | 
						|
          LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
 | 
						|
        ) AS sml
 | 
						|
         WHERE sml.relpages - otta > 0 OR ipages - iotta > 10 ORDER BY totalwastedbytes DESC LIMIT 10;"
 | 
						|
    fi
 | 
						|
 | 
						|
    local FIRST=
 | 
						|
    local QUERY="\pset footer off
 | 
						|
                 \echo '<<<postgres_bloat:sep(59)>>>${INSTANCE_SECTION}'
 | 
						|
                 \echo '[databases_start]'
 | 
						|
                 $ECHO_DATABASES
 | 
						|
                 \echo '[databases_end]'"
 | 
						|
 | 
						|
    for db in $DATABASES ; do
 | 
						|
        QUERY="$QUERY
 | 
						|
               \c $db
 | 
						|
               $BLOAT_QUERY
 | 
						|
              "
 | 
						|
        if [ -z $FIRST ] ; then
 | 
						|
            FIRST=false
 | 
						|
            QUERY="$QUERY
 | 
						|
                   \pset tuples_only on
 | 
						|
                  "
 | 
						|
        fi
 | 
						|
    done
 | 
						|
    echo "$QUERY" | sudo -u "$DBUSER" $export_PGPASSFILE $psql -X ${EXTRA_ARGS} -q -A -F';'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#.
 | 
						|
#   .--main----------------------------------------------------------------.
 | 
						|
#   |                                       _                              |
 | 
						|
#   |                       _ __ ___   __ _(_)_ __                         |
 | 
						|
#   |                      | '_ ` _ \ / _` | | '_ \                        |
 | 
						|
#   |                      | | | | | | (_| | | | | |                       |
 | 
						|
#   |                      |_| |_| |_|\__,_|_|_| |_|                       |
 | 
						|
#   |                                                                      |
 | 
						|
#   '----------------------------------------------------------------------'
 | 
						|
 | 
						|
 | 
						|
### postgres.cfg ##
 | 
						|
# DBUSER=OS_USER_NAME
 | 
						|
# INSTANCE=/home/postgres/db1.env:USER_NAME:/PATH/TO/.pgpass
 | 
						|
# INSTANCE=/home/postgres/db2.env:USER_NAME:/PATH/TO/.pgpass
 | 
						|
 | 
						|
# TODO @dba USERNAME in .pgpass ?
 | 
						|
# INSTANCE=/home/postgres/db2.env:/PATH/TO/.pgpass
 | 
						|
 | 
						|
 | 
						|
function postgres_main() {
 | 
						|
    if [ -z "$DBUSER" ] || [ -z "$PGDATABASE" ] ; then
 | 
						|
        exit 0
 | 
						|
    fi
 | 
						|
 | 
						|
    EXTRA_ARGS=""
 | 
						|
    if [ ! -z "$PGUSER" ]; then
 | 
						|
        EXTRA_ARGS=$EXTRA_ARGS" -U $PGUSER"
 | 
						|
    fi
 | 
						|
    if [ ! -z "$PGPORT" ]; then
 | 
						|
        EXTRA_ARGS=$EXTRA_ARGS" -p $PGPORT"
 | 
						|
    fi
 | 
						|
 | 
						|
    if [ ! -z "$PGPASSFILE" ]; then
 | 
						|
        export_PGPASSFILE="export PGPASSFILE=$PGPASSFILE; "
 | 
						|
    fi
 | 
						|
 | 
						|
    DATABASES="$(echo "SELECT datname FROM pg_database WHERE datistemplate = false;" |\
 | 
						|
                 sudo -u "$DBUSER" $export_PGPASSFILE $psql -X -d $PGDATABASE ${EXTRA_ARGS} -t -A -F';')"
 | 
						|
    ECHO_DATABASES="$(echo "$DATABASES" | sed 's/^/\\echo /')"
 | 
						|
 | 
						|
    POSTGRES_VERSION=$(sudo -u "$DBUSER" $psql -X -V -d $PGDATABASE ${EXTRA_ARGS} | egrep -o '[0-9]{1,}\.[0-9]{1,}')
 | 
						|
 | 
						|
    postgres_sessions
 | 
						|
    postgres_simple_queries
 | 
						|
    #postgres_stats
 | 
						|
    postgres_version
 | 
						|
    postgres_bloat
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
MK_CONFFILE=$MK_CONFDIR/postgres.cfg
 | 
						|
if [ -e "$MK_CONFFILE" ]; then
 | 
						|
 | 
						|
    postgres_instances
 | 
						|
 | 
						|
    DBUSER=$(grep DBUSER "$MK_CONFFILE" | sed 's/.*=//g')
 | 
						|
    cat "$MK_CONFFILE" | while read line
 | 
						|
    do
 | 
						|
        case $line in
 | 
						|
            INSTANCE*)
 | 
						|
                instance=$line
 | 
						|
                ;;
 | 
						|
            *)
 | 
						|
                instance=
 | 
						|
                ;;
 | 
						|
        esac
 | 
						|
 | 
						|
        if [ ! -z "$instance" ]; then
 | 
						|
            instance_path=$(echo "$instance" | sed 's/.*=\(.*\):.*:.*$/\1/g')
 | 
						|
            instance_name=$(echo "$instance_path" | sed -e 's/.*\/\(.*\)/\1/g' -e 's/\.env$//g')
 | 
						|
            if [ ! -z "$instance_name" ]; then
 | 
						|
                INSTANCE_SECTION="\n[[[$instance_name]]]"
 | 
						|
            else
 | 
						|
                INSTANCE_SECTION=""
 | 
						|
            fi
 | 
						|
 | 
						|
            psql="/$DBUSER/$(grep "^export PGVERSION=" "$instance_path" |
 | 
						|
                 sed -e 's/.*=//g' -e 's/\s*#.*$//g')/bin/psql"
 | 
						|
 | 
						|
            PGUSER=$(echo "$instance" | sed 's/.*=.*:\(.*\):.*$/\1/g')
 | 
						|
            PGPASSFILE="$(echo "$instance" | sed 's/.*=.*:.*:\(.*\)$/\1/g')"
 | 
						|
            PGDATABASE=$(grep "^export PGDATABASE=" "$instance_path" |
 | 
						|
                         sed -e 's/.*=//g' -e 's/\s*#.*$//g')
 | 
						|
            PGPORT=$(grep "^export PGPORT=" "$instance_path" |
 | 
						|
                     sed -e 's/.*=//g' -e 's/\s*#.*$//g')
 | 
						|
 | 
						|
            # Fallback
 | 
						|
            if [ ! -f "$psql" ]; then
 | 
						|
                psql="$(cat $instance_path | grep "^export PGHOME=" |
 | 
						|
                     sed -e 's/.*=//g' -e 's/\s*#.*$//g')/psql"
 | 
						|
            fi
 | 
						|
 | 
						|
            postgres_main
 | 
						|
 | 
						|
        fi
 | 
						|
    done
 | 
						|
 | 
						|
else
 | 
						|
 | 
						|
    if id pgsql >/dev/null 2>&1; then
 | 
						|
        DBUSER=pgsql
 | 
						|
    elif id postgres >/dev/null 2>&1; then
 | 
						|
        DBUSER=postgres
 | 
						|
    else
 | 
						|
        exit 0
 | 
						|
    fi
 | 
						|
    INSTANCE_SECTION=""
 | 
						|
 | 
						|
    postgres_instances "$DBUSER"
 | 
						|
 | 
						|
    psql="psql"
 | 
						|
    PGDATABASE=postgres
 | 
						|
    postgres_main
 | 
						|
 | 
						|
fi
 |