diff --git a/checkmk/checkmk-files/mk_postgres b/checkmk/checkmk-files/mk_postgres
deleted file mode 100644
index 47fef50d06e80945eda5a9436e08093581431def..0000000000000000000000000000000000000000
--- a/checkmk/checkmk-files/mk_postgres
+++ /dev/null
@@ -1,520 +0,0 @@
-#!/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
-
- # shellcheck disable=SC2009
- # The pgrep command would be different for older distros. Newer distros
- # need the -a option, but older ones need the -f option. The first idea
- # was to use:
- # pgrep -af bin/postgres 2>/dev/null || pgrep -lf bin/postgres
- # but SLES 11 returns the exit code 0 for a wrong command line.
- ps -eo pid,command | grep bin/postgres | grep -vE '^[ ]*[0-9]+ grep '
-}
-
-
-function postgres_sessions() {
- local OUTPUT
-
- # In postgresql 9.2 the column state was introduced, which has to be queried to
- # find the state of the sessions. The column state can be NULL.
- CONDITION="$ROW = $IDLE"
- OUTPUT="$(echo "\echo '<<<postgres_sessions>>>${INSTANCE_SECTION}'
- SELECT $CONDITION, count(*) FROM pg_stat_activity WHERE $ROW IS NOT NULL GROUP BY ($CONDITION);" |\
- su - "$DBUSER" -c "$export_PGPASSFILE $psql -X --variable ON_ERROR_STOP=1 ${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
-
- # Here the order of the columns did not match with what the server side expects,
- # with the result, that there was never any active connection shown in the
- # web-gui.
- CONDITION="$ROW <> $IDLE"
- CONNECTIONS_QUERY="SELECT d.datname, COUNT(datid) AS current,
- (SELECT setting AS mc FROM pg_settings WHERE name = 'max_connections') AS mc
- FROM pg_database d
- LEFT JOIN pg_stat_activity s ON (s.datid = d.oid) WHERE $CONDITION
- GROUP BY 1
- ORDER BY datname;"
-
- 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" \
- | su - "$DBUSER" -c "$export_PGPASSFILE $psql -X ${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" | su - "$DBUSER" -c "$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" |\
- su - "$DBUSER" -c "$export_PGPASSFILE $psql -X ${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 sml.relpages=0 OR sml.relpages=otta THEN 0.0 ELSE (sml.relpages-otta::numeric)/sml.relpages END,3) 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 ipages=0 OR ipages<=iotta THEN 0.0 ELSE (ipages-iotta::numeric)/ipages END,3) 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 sml.relpages=0 OR sml.relpages=otta THEN 0.0 ELSE (sml.relpages-otta::numeric)/sml.relpages END,3) 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 ipages=0 OR ipages<=iotta THEN 0.0 ELSE (ipages-iotta::numeric)/ipages END,3) 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" | su - "$DBUSER" -c "$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 "$PGDATABASE" ]; then
- EXTRA_ARGS=$EXTRA_ARGS" -d $PGDATABASE"
- 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;" |\
- su - "$DBUSER" -c "$export_PGPASSFILE $psql -X ${EXTRA_ARGS} -t -A -F';'")"
- # shellcheck disable=SC2001
- # DATABASES contains one database per line. To print each database in the psql terminal
- # we have to prefix each line with '\echo '. ${varialbe//search/replace} is not
- # sufficient in this case.
- ECHO_DATABASES="$(echo "$DATABASES" | sed 's/^/\\echo /')"
-
- postgres_check_server_version
- postgres_sessions
- postgres_simple_queries
- postgres_stats
- postgres_version
- postgres_bloat
-}
-
-
-function postgres_check_server_version() {
- # Query the server version from the server. Originally the version of the
- # client tools was taken, but what really matters is the version of the server.
- # Terminate the script if the server can not be queried.
- # We want $(su ...) to be
- # shellcheck disable=SC2046
- set -- $(su - "$DBUSER" -c "$psql -A -t -X ${EXTRA_ARGS} -c 'SHOW server_version;'" | tr '.' ' ')
- test $# -eq 0 && exit 0
- POSTGRES_VERSION="$1.$2"
- postgres_set_condition_vars
-}
-
-
-function postgres_set_condition_vars() {
- if compare_version_greater_equal "$POSTGRES_VERSION" "9.2" ; then
- ROW=state
- IDLE="'idle'"
- else
- ROW=current_query
- IDLE="'<IDLE>'"
- fi
-}
-
-
-MK_CONFFILE=$MK_CONFDIR/postgres.cfg
-if [ -e "$MK_CONFFILE" ]; then
-
- postgres_instances
-
- DBUSER=$(grep DBUSER "$MK_CONFFILE" | sed 's/.*=//g')
-
- # read -r: we do not want to interpret backslashes before spaces and line feeds
- while read -r line; do
- case $line in
- INSTANCE*)
- instance=$line
- ;;
- *)
- instance=
- ;;
- esac
-
- if [ ! -z "$instance" ]; then
- # Too complicated for ${variable//search/replace}
- # shellcheck disable=SC2001
- 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"
-
- # Too complicated for ${variable//search/replace}
- # shellcheck disable=SC2001
- PGUSER=$(echo "$instance" | sed 's/.*=.*:\(.*\):.*$/\1/g')
- # Too complicated for ${variable//search/replace}
- # shellcheck disable=SC2001
- 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="$(grep "^export PGHOME=" "$instance_path" |
- sed -e 's/.*=//g' -e 's/\s*#.*$//g')/psql"
- fi
-
- postgres_main
-
- fi
- done < "$MK_CONFFILE"
-
-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
-
- PGDATABASE=postgres
- INSTANCE_SECTION=""
- psql="psql"
-
- postgres_instances "$DBUSER"
- postgres_main
-
-fi
diff --git a/checkmk/checkmk-files/mk_postgres.py b/checkmk/checkmk-files/mk_postgres.py
new file mode 100644
index 0000000000000000000000000000000000000000..43915b5f67fca4460c8a98d0c44c87f043c3bab3
--- /dev/null
+++ b/checkmk/checkmk-files/mk_postgres.py
@@ -0,0 +1,1011 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
+# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
+# conditions defined in the file COPYING, which is part of this source code package.
+r"""Check_MK Agent Plugin: mk_postgres
+
+This is a Check_MK Agent plugin. If configured, it will be called by the
+agent without any arguments.
+"""
+
+__version__ = "2.0.0p12"
+
+import io
+import subprocess
+import re
+import os
+import abc
+import platform
+import sys
+import logging
+# optparse exist in python2.6 up to python 3.8. Do not use argparse, because it will not run with python2.6
+import optparse # pylint: disable=W0402
+
+try:
+ from typing import Any, Dict, List, Optional, Tuple
+except ImportError:
+ # We need typing only for testing
+ pass
+
+# For Python 3 sys.stdout creates \r\n as newline for Windows.
+# Checkmk can't handle this therefore we rewrite sys.stdout to a new_stdout function.
+# If you want to use the old behaviour just use old_stdout.
+if sys.version_info[0] >= 3:
+ new_stdout = io.TextIOWrapper(sys.stdout.buffer,
+ newline='\n',
+ encoding=sys.stdout.encoding,
+ errors=sys.stdout.errors)
+ old_stdout, sys.stdout = sys.stdout, new_stdout
+
+OS = platform.system()
+IS_LINUX = OS == "Linux"
+IS_WINDOWS = OS == "Windows"
+LOGGER = logging.getLogger(__name__)
+
+if IS_LINUX:
+ import resource
+elif IS_WINDOWS:
+ import time
+else:
+ raise NotImplementedError("The OS type(%s) is not yet implemented." % platform.system())
+
+
+# for compatibility with python 2.6
+def subprocess_check_output(args):
+ return subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0]
+
+
+# Borrowed from six
+def ensure_str(s):
+ if sys.version_info[0] >= 3:
+ if isinstance(s, bytes):
+ return s.decode("utf-8")
+ else:
+ if isinstance(s, unicode): # pylint: disable=undefined-variable
+ return s.encode("utf-8")
+ return s
+
+
+class PostgresPsqlError(RuntimeError):
+ pass
+
+
+class PostgresBase:
+ """
+ Base class for x-plattform postgres queries
+ :param db_user: The postgres db user
+ :param instance: Pass an instance, in case of monitoring a server with multiple instances
+
+ All abstract methods must have individual implementation depending on the OS type
+ which runs postgres.
+ All non-abstract methods are meant to work on all OS types which were subclassed.
+ """
+ __metaclass__ = abc.ABCMeta
+ _supported_pg_versions = ["12"]
+
+ def __init__(self, db_user, instance):
+ # type: (str, Dict) -> None
+ self.db_user = db_user
+ self.name = instance["name"]
+ self.pg_user = instance["pg_user"]
+ self.pg_port = instance["pg_port"]
+ self.pg_database = instance["pg_database"]
+ self.my_env = os.environ.copy()
+ self.my_env["PGPASSFILE"] = instance.get("pg_passfile", "")
+ self.sep = os.sep
+ self.psql, self.bin_path = self.get_psql_and_bin_path()
+ self.conn_time = "" # For caching as conn_time and version are in one query
+
+ @abc.abstractmethod
+ def run_sql_as_db_user(self,
+ sql_cmd,
+ extra_args="",
+ field_sep=";",
+ quiet=True,
+ rows_only=True,
+ mixed_cmd=False):
+ # type: (str, str, str, bool, bool, bool) -> str
+ """This method implements the system specific way to call the psql interface"""
+
+ @abc.abstractmethod
+ def get_psql_and_bin_path(self):
+ """This method returns the system specific psql binary and its path"""
+
+ @abc.abstractmethod
+ def get_instances(self):
+ """Gets all instances"""
+
+ @abc.abstractmethod
+ def get_stats(self, databases):
+ """Get the stats"""
+
+ @abc.abstractmethod
+ def get_version_and_connection_time(self):
+ """Get the pg version and the time for the query connection"""
+
+ @abc.abstractmethod
+ def get_bloat(self, databases, numeric_version):
+ """Get the db bloats"""
+
+ def get_databases(self):
+ """Gets all non template databases"""
+ sql_cmd = "SELECT datname FROM pg_database WHERE datistemplate = false;"
+ out = self.run_sql_as_db_user(sql_cmd)
+ return out.replace("\r", "").split("\n")
+
+ def get_server_version(self):
+ """Gets the server version"""
+ out = self.run_sql_as_db_user('SHOW server_version;')
+ if out == "":
+ raise PostgresPsqlError("psql connection returned with no data")
+ version_as_string = out.split()[0]
+ # Use Major and Minor version for float casting: "12.6.4" -> 12.6
+ return float(".".join(version_as_string.split(".")[0:2]))
+
+ def get_condition_vars(self, numeric_version):
+ """Gets condition variables for other queries"""
+ if numeric_version > 9.2:
+ return "state", "'idle'"
+ return "current_query", "'<IDLE>'"
+
+ def get_connections(self):
+ """Gets the the idle and active connections"""
+ connection_sql_cmd = ("SELECT datname, "
+ "(SELECT setting AS mc FROM pg_settings "
+ "WHERE name = 'max_connections') AS mc, "
+ "COUNT(state) FILTER (WHERE state='idle') AS idle, "
+ "COUNT(state) FILTER (WHERE state='active') AS active "
+ "FROM pg_stat_activity group by 1;")
+
+ return self.run_sql_as_db_user(connection_sql_cmd,
+ rows_only=False,
+ extra_args="-P footer=off")
+
+ def get_sessions(self, row, idle):
+ """Gets idle and open sessions"""
+ condition = "%s = %s" % (row, idle)
+
+ sql_cmd = ("SELECT %s, count(*) FROM pg_stat_activity "
+ "WHERE %s IS NOT NULL GROUP BY (%s);") % (condition, row, condition)
+
+ out = self.run_sql_as_db_user(sql_cmd,
+ quiet=False,
+ extra_args="--variable ON_ERROR_STOP=1",
+ field_sep=" ")
+
+ # 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.
+ if not out.startswith("t"):
+ out += "\nt 0"
+ return out
+
+ def get_query_duration(self, numeric_version):
+ """Gets the query duration"""
+ # Previously part of simple_queries
+
+ if numeric_version > 9.2:
+ querytime_sql_cmd = ("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_sql_cmd = ("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;")
+
+ return self.run_sql_as_db_user(querytime_sql_cmd,
+ rows_only=False,
+ extra_args="-P footer=off")
+
+ def get_stat_database(self):
+ """Gets the database stats"""
+ # Previously part of simple_queries
+ sql_cmd = ("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;")
+ return self.run_sql_as_db_user(sql_cmd, rows_only=False, extra_args="-P footer=off")
+
+ def get_locks(self):
+ """Get the locks"""
+ # Previously part of simple_queries
+ sql_cmd = ("SELECT datname, granted, mode FROM pg_locks l RIGHT "
+ "JOIN pg_database d ON (d.oid=l.database) WHERE d.datallowconn;")
+ return self.run_sql_as_db_user(sql_cmd, rows_only=False, extra_args="-P footer=off")
+
+ def get_version(self):
+ """Wrapper around get_version_conn_time"""
+ version, self.conn_time = self.get_version_and_connection_time()
+ return version
+
+ def get_connection_time(self):
+ """
+ Wrapper around get_version_conn time.
+ Execute query only if conn_time wasn't already set
+ """
+ if self.conn_time == "":
+ _, self.conn_time = self.get_version_and_connection_time()
+ return self.conn_time
+
+ def is_pg_ready(self):
+ """Executes pg_isready.
+ pg_isready is a utility for checking the connection status of a PostgreSQL database server.
+ """
+
+ out = subprocess_check_output(
+ ["%s%spg_isready" % (self.bin_path, self.sep), "-p", self.pg_port],)
+
+ sys.stdout.write("%s\n" % ensure_str(out))
+
+ def execute_all_queries(self):
+ """Executes all queries and writes the output formatted to stdout"""
+ instance = "\n[[[%s]]]" % self.name
+
+ try:
+ databases = self.get_databases()
+ database_text = "\n[databases_start]\n%s\n[databases_end]" % "\n".join(databases)
+ version = self.get_server_version()
+ row, idle = self.get_condition_vars(version)
+ except PostgresPsqlError:
+ # if tcp connection to db instance failed variables are empty
+ databases = ""
+ database_text = ""
+ version = None
+ row, idle = "", ""
+
+ out = "<<<postgres_instances>>>"
+ out += instance
+ out += "\n%s" % self.get_instances()
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_sessions>>>"
+ if row and idle:
+ out += instance
+ out += "\n%s" % self.get_sessions(row, idle)
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_stat_database:sep(59)>>>"
+ out += instance
+ out += "\n%s" % self.get_stat_database()
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_locks:sep(59)>>>"
+ if database_text:
+ out += instance
+ out += database_text
+ out += "\n%s" % self.get_locks()
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_query_duration:sep(59)>>>"
+ if version:
+ out += instance
+ out += database_text
+ out += "\n%s" % self.get_query_duration(version)
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_connections:sep(59)>>>"
+ if database_text:
+ out += instance
+ out += database_text
+ out += "\n%s" % self.get_connections()
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_stats:sep(59)>>>"
+ if databases:
+ out += instance
+ out += database_text
+ out += "\n%s" % self.get_stats(databases)
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_version:sep(1)>>>"
+ out += instance
+ out += "\n%s" % self.get_version()
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_conn_time>>>"
+ out += instance
+ out += "\n%s" % self.get_connection_time()
+ sys.stdout.write("%s\n" % out)
+
+ out = "<<<postgres_bloat:sep(59)>>>"
+ if databases and version:
+ out += instance
+ out += database_text
+ out += "\n%s" % self.get_bloat(databases, version)
+ sys.stdout.write("%s\n" % out)
+
+
+class PostgresWin(PostgresBase):
+ def run_sql_as_db_user(self,
+ sql_cmd,
+ extra_args="",
+ field_sep=";",
+ quiet=True,
+ rows_only=True,
+ mixed_cmd=False):
+ # type: (str, str, str, Optional[bool], Optional[bool],Optional[bool]) -> str
+ """This method implements the system specific way to call the psql interface"""
+ extra_args += " -U %s" % self.pg_user
+ extra_args += " -d %s" % self.pg_database
+ extra_args += " -p %s" % self.pg_port
+
+ if quiet:
+ extra_args += " -q"
+ if rows_only:
+ extra_args += " -t"
+
+ if mixed_cmd:
+ cmd_str = "cmd /c echo %s | cmd /c \"\"%s\" -X %s -A -F\"%s\" -U %s\"" % (
+ sql_cmd, self.psql, extra_args, field_sep, self.db_user)
+
+ else:
+ cmd_str = "cmd /c \"\"%s\" -X %s -A -F\"%s\" -U %s -c \"%s\"\" " % (
+ self.psql, extra_args, field_sep, self.db_user, sql_cmd)
+
+ proc = subprocess.Popen(
+ cmd_str,
+ env=self.my_env,
+ stdout=subprocess.PIPE,
+ )
+ out = ensure_str(proc.communicate()[0])
+ return out.rstrip()
+
+ def get_psql_and_bin_path(self):
+ # type: () -> Tuple[str, str]
+ """This method returns the system specific psql interface binary as callable string"""
+
+ # TODO: Make this more clever...
+ for pg_ver in self._supported_pg_versions:
+ bin_path = "C:\\Program Files\\PostgreSQL\\%s\\bin" % pg_ver
+ psql_path = "%s\\psql.exe" % bin_path
+ if os.path.isfile(psql_path):
+ return psql_path, bin_path
+
+ raise IOError("Could not determine psql bin and its path.")
+
+ def get_instances(self):
+ # type: () -> str
+ """Gets all instances"""
+
+ procs_to_match = [
+ re.compile(pattern) for pattern in
+ [r"(.*)bin\\postgres(.*)", r"(.*)bin\\postmaster(.*)", r"(.*)bin\\edb-postgres(.*)"]
+ ]
+
+ taskslist = ensure_str(
+ subprocess_check_output(
+ ["wmic", "process", "get", "processid,commandline",
+ "/format:list"])).split("\r\r\n\r\r\n\r\r\n")
+
+ out = ""
+ for task in taskslist:
+ task = task.lstrip().rstrip()
+ if len(task) == 0:
+ continue
+ cmd_line, PID = task.split("\r\r\n")
+ cmd_line = cmd_line.split("CommandLine=")[1]
+ PID = PID.split("ProcessId=")[1]
+ if any(pat.search(cmd_line) for pat in procs_to_match):
+ if task.find(self.name) != -1:
+ out += "%s %s\n" % (PID, cmd_line)
+ return out.rstrip()
+
+ def get_stats(self, databases):
+ # type: (List[str]) -> str
+ """Get the stats"""
+ # The next query had to be slightly modified:
+ # As cmd.exe interprets > as redirect and we need <> as "not equal", this was changed to
+ # != as it has the same SQL implementation
+ sql_cmd_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 g)) "
+ "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;")
+
+ query = "\\pset footer off \\\\ BEGIN;SET statement_timeout=30000;COMMIT;"
+
+ cur_rows_only = False
+ for cnt, database in enumerate(databases):
+
+ query = "%s \\c %s \\\\ %s" % (query, database, sql_cmd_lastvacuum)
+ if cnt == 0:
+ query = "%s \\pset tuples_only on" % query
+
+ return self.run_sql_as_db_user(query, mixed_cmd=True, rows_only=cur_rows_only)
+
+ def get_version_and_connection_time(self):
+ # type: () -> Tuple[str, str]
+ """Get the pg version and the time for the query connection"""
+ cmd = "SELECT version() AS v"
+
+ # TODO: Verify this time measurement
+ start_time = time.time()
+ out = self.run_sql_as_db_user(cmd)
+ diff = time.time() - start_time
+ return out, '%.3f' % diff
+
+ def get_bloat(self, databases, numeric_version):
+ # type: (List[Any], float) -> str
+ """Get the db bloats"""
+ # 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
+ if numeric_version > 9.0:
+ # TODO: Reformat query in a more readable way
+ # Here as well: "<" and ">" must be escaped. As we're using meta-command + SQL in one
+ # query, we need to use pipe. Due to Window's cmd behaviour, we need to escape those
+ # symbols with 3 (!) carets. See https://ss64.com/nt/syntax-redirection.html
+ bloat_query = (
+ "SELECT current_database() AS db, "
+ "schemaname, tablename, reltuples::bigint "
+ "AS tups, relpages::bigint AS pages, otta, "
+ "ROUND(CASE WHEN sml.relpages=0 "
+ "OR sml.relpages=otta THEN 0.0 "
+ "ELSE (sml.relpages-otta::numeric)/sml.relpages END,3) 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 ipages=0 OR ipages^^^<=iotta THEN 0.0 "
+ "ELSE (ipages-iotta::numeric)/ipages END,3) 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 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 sml.relpages=0 OR sml.relpages=otta THEN 0.0 "
+ "ELSE (sml.relpages-otta::numeric)/sml.relpages END,3) 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 ipages=0 OR ipages^^^<=iotta THEN 0.0 ELSE "
+ "(ipages-iotta::numeric)/ipages END,3) 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 "
+ "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;")
+
+ query = "\\pset footer off \\\\"
+
+ cur_rows_only = False
+ for idx, database in enumerate(databases):
+
+ query = "%s \\c %s \\\\ %s" % (query, database, bloat_query)
+ if idx == 0:
+ query = "%s \\pset tuples_only on" % query
+
+ return self.run_sql_as_db_user(query, mixed_cmd=True, rows_only=cur_rows_only)
+
+
+class PostgresLinux(PostgresBase):
+ def run_sql_as_db_user(self,
+ sql_cmd,
+ extra_args="",
+ field_sep=";",
+ quiet=True,
+ rows_only=True,
+ mixed_cmd=False):
+ # type: (str, str, str, bool, bool, bool) -> str
+ base_cmd_list = ["su", "-", self.db_user, "-c", r"""%s -X %s -A -F'%s'%s"""]
+ extra_args += " -U %s" % self.pg_user
+ extra_args += " -d %s" % self.pg_database
+ extra_args += " -p %s" % self.pg_port
+
+ if quiet:
+ extra_args += " -q"
+ if rows_only:
+ extra_args += " -t"
+
+ # In case we want to use postgres meta commands AND SQL queries in one call, we need to pipe
+ # the full cmd string into psql executable
+ # see https://www.postgresql.org/docs/9.2/app-psql.html
+ if mixed_cmd:
+ cmd_to_pipe = subprocess.Popen(["echo", sql_cmd], stdout=subprocess.PIPE)
+ base_cmd_list[-1] = base_cmd_list[-1] % (self.psql, extra_args, field_sep, "")
+
+ receiving_pipe = subprocess.Popen(base_cmd_list,
+ stdin=cmd_to_pipe.stdout,
+ stdout=subprocess.PIPE,
+ env=self.my_env)
+ out = ensure_str(receiving_pipe.communicate()[0])
+
+ else:
+ base_cmd_list[-1] = base_cmd_list[-1] % (self.psql, extra_args, field_sep,
+ " -c \"%s\" " % sql_cmd)
+ proc = subprocess.Popen(base_cmd_list, env=self.my_env, stdout=subprocess.PIPE)
+ out = ensure_str(proc.communicate()[0])
+
+ return out.rstrip()
+
+ def get_psql_and_bin_path(self):
+ # type: () -> Tuple[str, str]
+ try:
+ proc = subprocess.Popen(["which", "psql"], stdout=subprocess.PIPE)
+ out = ensure_str(proc.communicate()[0])
+ except subprocess.CalledProcessError:
+ raise RuntimeError("Could not determine psql executable.")
+
+ return out.split("/")[-1].rstrip(), out.replace("psql", "").rstrip()
+
+ def get_instances(self):
+ # type: () -> str
+
+ procs_to_match = [
+ re.compile(pattern) for pattern in
+ ["(.*)bin/postgres(.*)", "(.*)bin/postmaster(.*)", "(.*)bin/edb-postgres(.*)"]
+ ]
+
+ procs_list = ensure_str(subprocess_check_output(["ps", "h", "-eo",
+ "pid:1,command:1"])).split("\n")
+ out = ""
+ for proc in procs_list:
+ if any(pat.search(proc) for pat in procs_to_match):
+ if proc.find(self.name) != -1:
+ out += proc + "\n"
+ return out.rstrip()
+
+ def get_query_duration(self, numeric_version):
+ # type: (float) -> str
+ # Previously part of simple_queries
+
+ if numeric_version > 9.2:
+ querytime_sql_cmd = ("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_sql_cmd = ("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;")
+
+ return self.run_sql_as_db_user(querytime_sql_cmd,
+ rows_only=False,
+ extra_args="-P footer=off")
+
+ def get_stats(self, databases):
+ # type: (List[str]) -> str
+ sql_cmd_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;")
+
+ query = "\\pset footer off\nBEGIN;\nSET statement_timeout=30000;\nCOMMIT;"
+
+ cur_rows_only = False
+ for cnt, database in enumerate(databases):
+
+ query = "%s\n\\c %s\n%s" % (query, database, sql_cmd_lastvacuum)
+ if cnt == 0:
+ query = "%s\n\\pset tuples_only on" % query
+
+ return self.run_sql_as_db_user(query, mixed_cmd=True, rows_only=cur_rows_only)
+
+ def get_version_and_connection_time(self):
+ # type: () -> Tuple[str, str]
+ cmd = "SELECT version() AS v"
+ usage_start = resource.getrusage(resource.RUSAGE_CHILDREN)
+ out = self.run_sql_as_db_user(cmd)
+ usage_end = resource.getrusage(resource.RUSAGE_CHILDREN)
+
+ sys_time = usage_end.ru_stime - usage_start.ru_stime
+ usr_time = usage_end.ru_utime - usage_start.ru_utime
+ real = sys_time + usr_time
+
+ return out, '%.3f' % real
+
+ def get_bloat(self, databases, numeric_version):
+ # type: (List[Any], float) -> str
+ # 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
+ if numeric_version > 9.0:
+ # TODO: Reformat query in a more readable way
+ bloat_query = (
+ "SELECT current_database() AS db, schemaname, tablename, reltuples::bigint "
+ "AS tups, relpages::bigint AS pages, otta, ROUND(CASE WHEN sml.relpages=0 "
+ "OR sml.relpages=otta THEN 0.0 "
+ "ELSE (sml.relpages-otta::numeric)/sml.relpages END,3) 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 ipages=0 OR ipages<=iotta THEN 0.0 "
+ "ELSE (ipages-iotta::numeric)/ipages END,3) 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 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 sml.relpages=0 OR sml.relpages=otta THEN 0.0 "
+ "ELSE (sml.relpages-otta::numeric)/sml.relpages END,3) 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 ipages=0 OR ipages<=iotta THEN 0.0 ELSE "
+ "(ipages-iotta::numeric)/ipages END,3) 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 "
+ "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;")
+
+ query = "\\pset footer off"
+
+ cur_rows_only = False
+ for idx, database in enumerate(databases):
+
+ query = "%s\n\\c %s\n%s" % (query, database, bloat_query)
+ if idx == 0:
+ query = "%s\n\\pset tuples_only on" % query
+
+ return self.run_sql_as_db_user(query, mixed_cmd=True, rows_only=cur_rows_only)
+
+
+def postgres_factory(db_user, pg_instance):
+ # type: (str, Dict[str, str]) -> PostgresBase
+ if IS_LINUX:
+ return PostgresLinux(db_user, pg_instance)
+ if IS_WINDOWS:
+ return PostgresWin(db_user, pg_instance)
+ raise NotImplementedError("The OS type(%s) is not yet implemented." % platform.system())
+
+
+def open_env_file(file_to_open):
+ """Wrapper around built-in open to be able to monkeypatch through all python versions"""
+ return open(file_to_open).readlines()
+
+
+def parse_env_file(env_file):
+ pg_port = None # mandatory in env_file
+ pg_database = "postgres" # default value
+ for line in open_env_file(env_file):
+ line = line.strip()
+ if 'PGDATABASE=' in line:
+ pg_database = re.sub(re.compile("#.*"), "", line.split("=")[-1]).strip()
+ if 'PGPORT=' in line:
+ pg_port = re.sub(re.compile("#.*"), "", line.split("=")[-1]).strip()
+ if pg_port is None:
+ raise ValueError("PGPORT is not specified in %s" % env_file)
+ return pg_database, pg_port
+
+
+def parse_postgres_cfg(postgres_cfg):
+ """
+ Parser for Postgres config. x-Plattform compatible.
+
+ Example for .cfg file:
+ DBUSER=postgres
+ INSTANCE=/home/postgres/db1.env:USER_NAME:/PATH/TO/.pgpass
+ INSTANCE=/home/postgres/db2.env:USER_NAME:/PATH/TO/.pgpass
+ """
+ if IS_LINUX:
+ conf_sep = ":"
+ elif IS_WINDOWS:
+ conf_sep = "|"
+ else:
+ raise NotImplementedError("The OS type(%s) is not yet implemented." % platform.system())
+ dbuser = None
+ instances = []
+ for line in postgres_cfg:
+ if line.startswith("#") or "=" not in line:
+ continue
+ line = line.strip()
+ key, value = line.split("=")
+ if key == "DBUSER":
+ dbuser = value.rstrip()
+ if key == "INSTANCE":
+ env_file, pg_user, pg_passfile = value.split(conf_sep)
+ env_file = env_file.strip()
+ pg_database, pg_port = parse_env_file(env_file)
+ instances.append({
+ "name": env_file.split(os.sep)[-1].split(".")[0],
+ "pg_user": pg_user.strip(),
+ "pg_passfile": pg_passfile.strip(),
+ "pg_database": pg_database,
+ "pg_port": pg_port,
+ })
+ if dbuser is None:
+ raise ValueError("DBUSER must be specified in postgres.cfg")
+ return dbuser, instances
+
+
+def parse_arguments(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('-v', '--verbose', action="count", default=0)
+ parser.add_option("-t",
+ "--test-connection",
+ default=False,
+ action="store_true",
+ help="Test if postgres is ready")
+ options, _ = parser.parse_args(argv)
+ return options
+
+
+def get_postgres_user_linux():
+ for user_id in ("pgsql", "postgres"):
+ try:
+ proc = subprocess.Popen(["id", user_id], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ proc.communicate()
+ if proc.returncode == 0:
+ return user_id.rstrip()
+ except subprocess.CalledProcessError:
+ pass
+ raise ValueError("Could not determine postgres user!")
+
+
+def main(argv=None):
+ # type: (Optional[List]) -> int
+
+ if argv is None:
+ argv = sys.argv[1:]
+
+ opt = parse_arguments(argv)
+
+ logging.basicConfig(
+ format="%(levelname)s %(asctime)s %(name)s: %(message)s",
+ datefmt='%Y-%m-%d %H:%M:%S',
+ level={
+ 0: logging.WARN,
+ 1: logging.INFO,
+ 2: logging.DEBUG
+ }.get(opt.verbose, logging.DEBUG),
+ )
+
+ if IS_LINUX:
+ default_path = "/etc/check_mk"
+ fallback_dbuser = get_postgres_user_linux()
+ elif IS_WINDOWS:
+ default_path = "c:\\ProgramData\\checkmk\\agent\\config"
+ fallback_dbuser = "postgres"
+ else:
+ raise NotImplementedError("The OS type(%s) is not yet implemented." % platform.system())
+
+ dbuser = fallback_dbuser
+ instances = []
+ try:
+ postgres_cfg_path = os.path.join(os.getenv("MK_CONFDIR", default_path), "postgres.cfg")
+ postgres_cfg = open(postgres_cfg_path).readlines()
+ dbuser, instances = parse_postgres_cfg(postgres_cfg)
+ except Exception:
+ _, e = sys.exc_info()[:2] # python2 and python3 compatible exception logging
+ LOGGER.debug("try_parse_config: exception: %s", str(e))
+ LOGGER.debug("Using \"%s\" as default postgres user.", dbuser)
+
+ if not instances:
+ default_postgres_installation_parameters = {
+ # default database name of postgres installation
+ "name": "main" if IS_LINUX else "data",
+ "pg_user": "postgres",
+ "pg_database": "postgres",
+ "pg_port": "5432",
+ # Assumption: if no pg_passfile is specified no password will be required.
+ # If a password is required but no pg_passfile is specified the process will
+ # interactivly prompt for a password.
+ "pg_passfile": "",
+ }
+ instances.append(default_postgres_installation_parameters)
+
+ for instance in instances:
+ postgres = postgres_factory(dbuser, instance)
+ if opt.test_connection:
+ postgres.is_pg_ready()
+ sys.exit(0)
+ postgres.execute_all_queries()
+ return 0
+
+
+if __name__ == "__main__":
+ main()
diff --git a/checkmk/debian/mk_postgres.sls b/checkmk/debian/mk_postgres.sls
index 9c9e65516d80c8ced13670ffc8f1ce3ed5ad6491..d0fda95585f8c744f7f08c7bc6cd80cb4175aae6 100644
--- a/checkmk/debian/mk_postgres.sls
+++ b/checkmk/debian/mk_postgres.sls
@@ -1,7 +1,7 @@
hsh_checkmk_mk_postgres_plugin:
file.managed:
- name: /usr/lib/check_mk_agent/plugins/mk_postgres
- - source: salt://checkmk/checkmk-files/mk_postgres
+ - source: salt://checkmk/checkmk-files/mk_postgres.py
- mode: 755
- user: root
- group: root