2 # +------------------------------------------------------------------+
3 # | ____ _ _ __ __ _ __ |
4 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
5 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
6 # | | |___| | | | __/ (__| < | | | | . \ |
7 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | Copyright Mathias Kettner 2015 mk@mathias-kettner.de |
10 # +------------------------------------------------------------------+
12 # This file is part of Check_MK.
13 # The official homepage is at http://mathias-kettner.de/check_mk.
15 # check_mk is free software; you can redistribute it and/or modify it
16 # under the terms of the GNU General Public License as published by
17 # the Free Software Foundation in version 2. check_mk is distributed
18 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
19 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
20 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
21 # tails. You should have received a copy of the GNU General Public
22 # License along with GNU Make; see the file COPYING. If not, write
23 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
24 # Boston, MA 02110-1301 USA.
27 # TODO postgres_connections output format
30 # .--common funcs--------------------------------------------------------.
32 # | ___ ___ _ __ ___ _ __ ___ ___ _ __ / _|_ _ _ __ ___ ___ |
33 # | / __/ _ \| '_ ` _ \| '_ ` _ \ / _ \| '_ \ | |_| | | | '_ \ / __/ __| |
34 # || (_| (_) | | | | | | | | | | | (_) | | | || _| |_| | | | | (__\__ \ |
35 # | \___\___/|_| |_| |_|_| |_| |_|\___/|_| |_||_| \__,_|_| |_|\___|___/ |
37 # '----------------------------------------------------------------------'
40 function compare_version_greater_equal
() {
42 GREATER_ONE
=$
(echo "$1 $2" |
awk '{if ($1 >= $2) print $1; else print $2}')
43 if [ "$GREATER_ONE" == "$1" ] ; then
52 # .--section funcs-------------------------------------------------------.
54 # | ___ ___ ___| |_(_) ___ _ __ / _|_ _ _ __ ___ ___ |
55 # | / __|/ _ \/ __| __| |/ _ \| '_ \ | |_| | | | '_ \ / __/ __| |
56 # | \__ \ __/ (__| |_| | (_) | | | | | _| |_| | | | | (__\__ \ |
57 # | |___/\___|\___|\__|_|\___/|_| |_| |_| \__,_|_| |_|\___|___/ |
59 # '----------------------------------------------------------------------'
62 function postgres_instances
() {
63 echo '<<<postgres_instances>>>'
64 # If we have no instances we take db id (pqsql/postgres) because
65 # ps output may be unreadable
66 # In case of instances ps output shows them readable
67 if [ ! -z "${1}" ]; then
70 pgrep
-laf bin
/postgres
74 function postgres_sessions
() {
75 # Postgres 9.2 uses 'query' instead of 'current_query'
77 OUTPUT
="$(echo "\
echo '<<<postgres_sessions>>>${INSTANCE_SECTION}'
80 FROM information_schema.columns
81 WHERE table_name
='pg_stat_activity' AND column_name
in ('query', 'current_query')
82 ) = '<IDLE>' as query
, count
(*)
84 GROUP BY
(query
= '<IDLE>');" |\
85 su - "$DBUSER" -c "$export_PGPASSFILE $psql -X --variable ON_ERROR_STOP
=1 -d $PGDATABASE ${EXTRA_ARGS} -A -t -F' '" 2>/dev/null)"
88 # line with number of idle sessions is sometimes missing on Postgres 8.x. This can lead
89 # to an altogether empty section and thus the check disappearing.
90 echo "$OUTPUT" |
grep -q '^t ' ||
echo "t 0"
94 function postgres_simple_queries
() {
96 # Supports versions >= 8.3, > 9.1
98 if compare_version_greater_equal
"$POSTGRES_VERSION" "9.2" ; then
99 QUERYTIME_QUERY
="SELECT datname, datid, usename, client_addr, state AS state, COALESCE(ROUND(EXTRACT(epoch FROM now()-query_start)),0) AS seconds,
100 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;"
102 QUERYTIME_QUERY
="SELECT datname, datid, usename, client_addr, '' AS state, COALESCE(ROUND(EXTRACT(epoch FROM now()-query_start)),0) AS seconds,
103 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;"
106 # Number of current connections per database
107 # We need to output the databases, too.
108 # This query does not report databases without an active query
109 local CONNECTIONS_QUERY
110 if compare_version_greater_equal
"$POSTGRES_VERSION" "9.2" ; then
111 CONNECTIONS_QUERY
="SELECT COUNT(datid) AS current,
112 (SELECT setting AS mc FROM pg_settings WHERE name = 'max_connections') AS mc,
115 LEFT JOIN pg_stat_activity s ON (s.datid = d.oid) WHERE state <> 'idle'
119 CONNECTIONS_QUERY
="SELECT COUNT(datid) AS current,
120 (SELECT setting AS mc FROM pg_settings WHERE name = 'max_connections') AS mc,
123 LEFT JOIN pg_stat_activity s ON (s.datid = d.oid) WHERE current_query <> '<IDLE>'
128 echo "\pset footer off
129 \echo '<<<postgres_stat_database:sep(59)>>>${INSTANCE_SECTION}'
130 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;
132 \echo '<<<postgres_locks:sep(59)>>>${INSTANCE_SECTION}'
133 \echo '[databases_start]'
135 \echo '[databases_end]'
136 SELECT datname, granted, mode FROM pg_locks l RIGHT JOIN pg_database d ON (d.oid=l.database) WHERE d.datallowconn;
138 \echo '<<<postgres_query_duration:sep(59)>>>${INSTANCE_SECTION}'
139 \echo '[databases_start]'
141 \echo '[databases_end]'
144 \echo '<<<postgres_connections:sep(59)>>>${INSTANCE_SECTION}'
145 \echo '[databases_start]'
147 \echo '[databases_end]'
148 $CONNECTIONS_QUERY" \
149 | su
- "$DBUSER" -c "$export_PGPASSFILE $psql -X -d $PGDATABASE ${EXTRA_ARGS} -q -A -F';'"
153 function postgres_stats
() {
154 # Contains last vacuum time and analyze time
155 local LASTVACUUM
="SELECT current_database() AS datname, nspname AS sname, relname AS tname,
156 CASE WHEN v IS NULL THEN -1 ELSE round(extract(epoch FROM v)) END AS vtime,
157 CASE WHEN g IS NULL THEN -1 ELSE round(extract(epoch FROM v)) END AS atime
158 FROM (SELECT nspname, relname, GREATEST(pg_stat_get_last_vacuum_time(c.oid), pg_stat_get_last_autovacuum_time(c.oid)) AS v,
159 GREATEST(pg_stat_get_last_analyze_time(c.oid), pg_stat_get_last_autoanalyze_time(c.oid)) AS g
160 FROM pg_class c, pg_namespace n
161 WHERE relkind = 'r' AND n.oid = c.relnamespace AND n.nspname <> 'information_schema'
165 local QUERY
="\pset footer off
167 SET statement_timeout=30000;
170 \echo '<<<postgres_stats:sep(59)>>>${INSTANCE_SECTION}'
171 \echo '[databases_start]'
173 \echo '[databases_end]'"
175 for db
in $DATABASES ; do
180 if [ -z $FIRST ] ; then
187 echo "$QUERY" | su
- "$DBUSER" -c "$export_PGPASSFILE $psql -X ${EXTRA_ARGS} -q -A -F';'" |
grep -v -e 'COMMIT$' -e 'SET$' -e 'BEGIN$'
191 function postgres_version
() {
192 # Postgres version an connection time
193 echo -e "<<<postgres_version:sep(1)>>>${INSTANCE_SECTION}"
194 (TIMEFORMAT
='%3R'; time echo "SELECT version() AS v" |\
195 su
- "$DBUSER" -c "$export_PGPASSFILE $psql -X -d $PGDATABASE ${EXTRA_ARGS} -t -A -F';'; echo -e '<<<postgres_conn_time>>>${INSTANCE_SECTION}'") 2>&1
199 function postgres_bloat
() {
200 # Bloat index and tables
201 # Supports versions <9.0, >=9.0
202 # This huge query has been gratefully taken from Greg Sabino Mullane's check_postgres.pl
204 if compare_version_greater_equal
"$POSTGRES_VERSION" "9.0" ; then
206 current_database() AS db, schemaname, tablename, reltuples::bigint AS tups, relpages::bigint AS pages, otta,
207 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,
208 CASE WHEN relpages < otta THEN 0 ELSE relpages::bigint - otta END AS wastedpages,
209 CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::bigint END AS wastedbytes,
210 CASE WHEN relpages < otta THEN 0 ELSE (bs*(relpages-otta))::bigint END AS wastedsize,
211 iname, ituples::bigint AS itups, ipages::bigint AS ipages, iotta,
212 ROUND(CASE WHEN iotta=0 OR ipages=0 OR ipages=iotta THEN 0.0 ELSE ipages/iotta::numeric END,1) AS ibloat,
213 CASE WHEN ipages < iotta THEN 0 ELSE ipages::bigint - iotta END AS wastedipages,
214 CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes,
215 CASE WHEN ipages < iotta THEN 0 ELSE (bs*(ipages-iotta))::bigint END AS wastedisize,
216 CASE WHEN relpages < otta THEN
217 CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta::bigint) END
218 ELSE CASE WHEN ipages < iotta THEN bs*(relpages-otta::bigint)
219 ELSE bs*(relpages-otta::bigint + ipages-iotta::bigint) END
220 END AS totalwastedbytes
223 nn.nspname AS schemaname,
224 cc.relname AS tablename,
225 COALESCE(cc.reltuples,0) AS reltuples,
226 COALESCE(cc.relpages,0) AS relpages,
227 COALESCE(bs,0) AS bs,
228 COALESCE(CEIL((cc.reltuples*((datahdr+ma-
229 (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)),0) AS otta,
230 COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
231 COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
234 JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname <> 'information_schema'
238 ma,bs,foo.nspname,foo.relname,
239 (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
240 (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
243 ns.nspname, tbl.relname, hdr, ma, bs,
244 SUM((1-coalesce(null_frac,0))*coalesce(avg_width, 2048)) AS datawidth,
245 MAX(coalesce(null_frac,0)) AS maxfracsum,
249 WHERE null_frac<>0 AND s2.schemaname = ns.nspname AND s2.tablename = tbl.relname
251 FROM pg_attribute att
252 JOIN pg_class tbl ON att.attrelid = tbl.oid
253 JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
254 LEFT JOIN pg_stats s ON s.schemaname=ns.nspname
255 AND s.tablename = tbl.relname
256 AND s.inherited=false
257 AND s.attname=att.attname,
260 (SELECT current_setting('block_size')::numeric) AS bs,
261 CASE WHEN SUBSTRING(SPLIT_PART(v, ' ', 2) FROM '#\[0-9]+.[0-9]+#\%' for '#')
262 IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr,
263 CASE WHEN v ~ 'mingw32' OR v ~ '64-bit' THEN 8 ELSE 4 END AS ma
264 FROM (SELECT version() AS v) AS foo
266 WHERE att.attnum > 0 AND tbl.relkind='r'
270 ON cc.relname = rs.relname AND nn.nspname = rs.nspname
271 LEFT JOIN pg_index i ON indrelid = cc.oid
272 LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
274 WHERE sml.relpages - otta > 0 OR ipages - iotta > 10 ORDER BY totalwastedbytes DESC LIMIT 10;"
277 current_database() AS db, schemaname, tablename, reltuples::bigint AS tups, relpages::bigint AS pages, otta,
278 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,
279 CASE WHEN relpages < otta THEN 0 ELSE relpages::bigint - otta END AS wastedpages,
280 CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::bigint END AS wastedbytes,
281 CASE WHEN relpages < otta THEN '0 bytes'::text ELSE (bs*(relpages-otta))::bigint || ' bytes' END AS wastedsize,
282 iname, ituples::bigint AS itups, ipages::bigint AS ipages, iotta,
283 ROUND(CASE WHEN iotta=0 OR ipages=0 OR ipages=iotta THEN 0.0 ELSE ipages/iotta::numeric END,1) AS ibloat,
284 CASE WHEN ipages < iotta THEN 0 ELSE ipages::bigint - iotta END AS wastedipages,
285 CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes,
286 CASE WHEN ipages < iotta THEN '0 bytes' ELSE (bs*(ipages-iotta))::bigint || ' bytes' END AS wastedisize,
287 CASE WHEN relpages < otta THEN
288 CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta::bigint) END
289 ELSE CASE WHEN ipages < iotta THEN bs*(relpages-otta::bigint)
290 ELSE bs*(relpages-otta::bigint + ipages-iotta::bigint) END
291 END AS totalwastedbytes
294 nn.nspname AS schemaname,
295 cc.relname AS tablename,
296 COALESCE(cc.reltuples,0) AS reltuples,
297 COALESCE(cc.relpages,0) AS relpages,
298 COALESCE(bs,0) AS bs,
299 COALESCE(CEIL((cc.reltuples*((datahdr+ma-
300 (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)),0) AS otta,
301 COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
302 COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
305 JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname <> 'information_schema'
309 ma,bs,foo.nspname,foo.relname,
310 (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
311 (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
314 ns.nspname, tbl.relname, hdr, ma, bs,
315 SUM((1-coalesce(null_frac,0))*coalesce(avg_width, 2048)) AS datawidth,
316 MAX(coalesce(null_frac,0)) AS maxfracsum,
320 WHERE null_frac<>0 AND s2.schemaname = ns.nspname AND s2.tablename = tbl.relname
322 FROM pg_attribute att
323 JOIN pg_class tbl ON att.attrelid = tbl.oid
324 JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
325 LEFT JOIN pg_stats s ON s.schemaname=ns.nspname
326 AND s.tablename = tbl.relname
327 AND s.attname=att.attname,
330 (SELECT current_setting('block_size')::numeric) AS bs,
331 CASE WHEN SUBSTRING(SPLIT_PART(v, ' ', 2) FROM '#\"[0-9]+.[0-9]+#\"%' for '#')
332 IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr,
333 CASE WHEN v ~ 'mingw32' OR v ~ '64-bit' THEN 8 ELSE 4 END AS ma
334 FROM (SELECT version() AS v) AS foo
336 WHERE att.attnum > 0 AND tbl.relkind='r'
340 ON cc.relname = rs.relname AND nn.nspname = rs.nspname
341 LEFT JOIN pg_index i ON indrelid = cc.oid
342 LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
344 WHERE sml.relpages - otta > 0 OR ipages - iotta > 10 ORDER BY totalwastedbytes DESC LIMIT 10;"
348 local QUERY
="\pset footer off
349 \echo '<<<postgres_bloat:sep(59)>>>${INSTANCE_SECTION}'
350 \echo '[databases_start]'
352 \echo '[databases_end]'"
354 for db
in $DATABASES ; do
359 if [ -z $FIRST ] ; then
366 echo "$QUERY" | su
- "$DBUSER" -c "$export_PGPASSFILE $psql -X ${EXTRA_ARGS} -q -A -F';'"
371 # .--main----------------------------------------------------------------.
373 # | _ __ ___ __ _(_)_ __ |
374 # | | '_ ` _ \ / _` | | '_ \ |
375 # | | | | | | | (_| | | | | | |
376 # | |_| |_| |_|\__,_|_|_| |_| |
378 # '----------------------------------------------------------------------'
382 # DBUSER=OS_USER_NAME
383 # INSTANCE=/home/postgres/db1.env:USER_NAME:/PATH/TO/.pgpass
384 # INSTANCE=/home/postgres/db2.env:USER_NAME:/PATH/TO/.pgpass
386 # TODO @dba USERNAME in .pgpass ?
387 # INSTANCE=/home/postgres/db2.env:/PATH/TO/.pgpass
390 function postgres_main
() {
391 if [ -z "$DBUSER" ] ||
[ -z "$PGDATABASE" ] ; then
396 if [ ! -z "$PGUSER" ]; then
397 EXTRA_ARGS
=$EXTRA_ARGS" -U $PGUSER"
399 if [ ! -z "$PGPORT" ]; then
400 EXTRA_ARGS
=$EXTRA_ARGS" -p $PGPORT"
403 if [ ! -z "$PGPASSFILE" ]; then
404 export_PGPASSFILE
="export PGPASSFILE=$PGPASSFILE; "
407 DATABASES
="$(echo "SELECT datname FROM pg_database WHERE datistemplate
= false
;" |\
408 su - "$DBUSER" -c "$export_PGPASSFILE $psql -X -d $PGDATABASE ${EXTRA_ARGS} -t -A -F';'")"
409 ECHO_DATABASES
="$(echo "$DATABASES" | sed 's/^/\\echo /')"
411 POSTGRES_VERSION
=$
(su
- "$DBUSER" -c "$psql -X -V -d $PGDATABASE ${EXTRA_ARGS} | egrep -o '[0-9]{1,}\.[0-9]{1,}'")
414 postgres_simple_queries
421 MK_CONFFILE
=$MK_CONFDIR/postgres.cfg
422 if [ -e "$MK_CONFFILE" ]; then
426 DBUSER
=$
(grep DBUSER
"$MK_CONFFILE" |
sed 's/.*=//g')
427 cat "$MK_CONFFILE" |
while read line
438 if [ ! -z "$instance" ]; then
439 instance_path
=$
(echo "$instance" |
sed 's/.*=\(.*\):.*:.*$/\1/g')
440 instance_name
=$
(echo "$instance_path" |
sed -e 's/.*\/\(.*\)/\1/g' -e 's/\.env$//g')
441 if [ ! -z "$instance_name" ]; then
442 INSTANCE_SECTION
="\n[[[$instance_name]]]"
447 psql
="/$DBUSER/$(grep "^
export PGVERSION
=" "$instance_path" |
448 sed -e 's/.*=//g' -e 's/\s*#.*$//g')/bin/psql"
450 PGUSER
=$
(echo "$instance" |
sed 's/.*=.*:\(.*\):.*$/\1/g')
451 PGPASSFILE
="$(echo "$instance" | sed 's/.*=.*:.*:\(.*\)$/\1/g')"
452 PGDATABASE
=$
(grep "^export PGDATABASE=" "$instance_path" |
453 sed -e 's/.*=//g' -e 's/\s*#.*$//g')
454 PGPORT
=$
(grep "^export PGPORT=" "$instance_path" |
455 sed -e 's/.*=//g' -e 's/\s*#.*$//g')
458 if [ ! -f "$psql" ]; then
459 psql
="$(cat $instance_path | grep "^
export PGHOME
=" |
460 sed -e 's/.*=//g' -e 's/\s*#.*$//g')/psql"
470 if id pgsql
>/dev
/null
2>&1; then
472 elif id postgres
>/dev
/null
2>&1; then
479 postgres_instances
"$DBUSER"