· 8 years ago · Aug 21, 2018, 01:30 PM
1#!/usr/bin/env perl
2# -*-mode:cperl; indent-tabs-mode: nil-*-
3
4## Perform many different checks against Postgres databases.
5## Designed primarily as a Nagios script.
6## Run with --help for a summary.
7##
8## Greg Sabino Mullane <greg@endpoint.com>
9## End Point Corporation http://www.endpoint.com/
10## BSD licensed, see complete license at bottom of this script
11## The latest version can be found at:
12## http://www.bucardo.org/check_postgres/
13##
14## See the HISTORY section for other contributors
15
16package check_postgres;
17
18use 5.006001;
19use strict;
20use warnings;
21use utf8;
22use Getopt::Long qw/GetOptions/;
23Getopt::Long::Configure(qw/ no_ignore_case pass_through /);
24use File::Basename qw/basename/;
25use File::Temp qw/tempfile tempdir/;
26File::Temp->safe_level( File::Temp::MEDIUM );
27use Cwd;
28use Data::Dumper qw/Dumper/;
29$Data::Dumper::Varname = 'POSTGRES';
30$Data::Dumper::Indent = 2;
31$Data::Dumper::Useqq = 1;
32
33binmode STDOUT, ':utf8';
34
35our $VERSION = '2.20.0';
36
37use vars qw/ %opt $PGBINDIR $PSQL $res $COM $SQL $db /;
38
39## Which user to connect as if --dbuser is not given
40$opt{defaultuser} = 'postgres';
41
42## Which port to connect to if --dbport is not given
43$opt{defaultport} = 5432;
44
45## What type of output to use by default
46our $DEFAULT_OUTPUT = 'nagios';
47
48## If psql binaries are not in your path, it is recommended to hardcode it here,
49## as an alternative to the --PGBINDIR option
50$PGBINDIR = '';
51
52## If this is true, $opt{PSQL} and $opt{PGBINDIR} are disabled for security reasons
53our $NO_PSQL_OPTION = 1;
54
55## If true, we show how long each query took by default. Requires Time::HiRes to be installed.
56$opt{showtime} = 1;
57
58## If true, we show "after the pipe" statistics
59$opt{showperf} = 1;
60
61## Default time display format, used for last_vacuum and last_analyze
62our $SHOWTIME = 'HH24:MI FMMonth DD, YYYY';
63
64## Always prepend 'postgres_' to the name of the service in the output string
65our $FANCYNAME = 1;
66
67## Change the service name to uppercase
68our $YELLNAME = 1;
69
70## Preferred order of ways to fetch pages for new_version checks
71our $get_method_timeout = 30;
72our @get_methods = (
73 "GET -t $get_method_timeout -H 'Pragma: no-cache'",
74 "wget --quiet --timeout=$get_method_timeout --no-cache -O -",
75 "curl --silent --max-time=$get_method_timeout -H 'Pragma: no-cache'",
76 "fetch -q -T $get_method_timeout -o -",
77 "lynx --connect-timeout=$get_method_timeout --dump",
78 'links -dump',
79);
80
81## Nothing below this line should need to be changed for normal usage.
82## If you do find yourself needing to change something,
83## please email the author as it probably indicates something
84## that could be made into a command-line option or moved above.
85
86## Messages. Translations always welcome
87## Items without a leading tab still need translating
88## no critic (RequireInterpolationOfMetachars)
89our %msg = (
90'en' => {
91 'address' => q{address},
92 'age' => q{age},
93 'backends-fatal' => q{Could not connect: too many connections},
94 'backends-mrtg' => q{DB=$1 Max connections=$2},
95 'backends-msg' => q{$1 of $2 connections ($3%)},
96 'backends-nomax' => q{Could not determine max_connections},
97 'backends-oknone' => q{No connections},
98 'backends-po' => q{sorry, too many clients already},
99 'backends-users' => q{$1 for number of users must be a number or percentage},
100 'bloat-index' => q{(db $1) index $2 rows:$3 pages:$4 shouldbe:$5 ($6X) wasted bytes:$7 ($8)},
101 'bloat-nomin' => q{no relations meet the minimum bloat criteria},
102 'bloat-table' => q{(db $1) table $2.$3 rows:$4 pages:$5 shouldbe:$6 ($7X) wasted size:$8 ($9)},
103 'bug-report' => q{Please report these details to check_postgres@bucardo.org:},
104 'checkcluster-id' => q{Database system identifier:},
105 'checkcluster-msg' => q{cluster_id: $1},
106 'checkcluster-nomrtg'=> q{Must provide a number via the --mrtg option},
107 'checkmode-prod' => q{in production},
108 'checkmode-recovery' => q{in archive recovery},
109 'checkmode-state' => q{Database cluster state:},
110 'checkpoint-baddir' => q{Invalid data_directory: "$1"},
111 'checkpoint-baddir2' => q{pg_controldata could not read the given data directory: "$1"},
112 'checkpoint-badver' => q{Failed to run pg_controldata - probably the wrong version ($1)},
113 'checkpoint-badver2' => q{Failed to run pg_controldata - is it the correct version?},
114 'checkpoint-nodir' => q{Must supply a --datadir argument or set the PGDATA environment variable},
115 'checkpoint-nodp' => q{Must install the Perl module Date::Parse to use the checkpoint action},
116 'checkpoint-noparse' => q{Unable to parse pg_controldata output: "$1"},
117 'checkpoint-noregex' => q{Unable to find the regex for this check},
118 'checkpoint-nosys' => q{Could not call pg_controldata: $1},
119 'checkpoint-ok' => q{Last checkpoint was 1 second ago},
120 'checkpoint-ok2' => q{Last checkpoint was $1 seconds ago},
121 'checkpoint-po' => q{Time of latest checkpoint:},
122 'checksum-msg' => q{checksum: $1},
123 'checksum-nomd' => q{Must install the Perl module Digest::MD5 to use the checksum action},
124 'checksum-nomrtg' => q{Must provide a checksum via the --mrtg option},
125 'custom-invalid' => q{Invalid format returned by custom query},
126 'custom-norows' => q{No rows returned},
127 'custom-nostring' => q{Must provide a query string},
128 'database' => q{database},
129 'dbsize-version' => q{Target database must be version 8.1 or higher to run the database_size action},
130 'depr-pgcontroldata' => q{PGCONTROLDATA is deprecated, use PGBINDIR instead.},
131 'die-action-version' => q{Cannot run "$1": server version must be >= $2, but is $3},
132 'die-badtime' => q{Value for '$1' must be a valid time. Examples: -$2 1s -$2 "10 minutes"},
133 'die-badversion' => q{Invalid version string: $1},
134 'die-noset' => q{Cannot run "$1" $2 is not set to on},
135 'die-nosetting' => q{Could not fetch setting '$1'},
136 'diskspace-fail' => q{Invalid result from command "$1": $2},
137 'diskspace-msg' => q{FS $1 mounted on $2 is using $3 of $4 ($5%)},
138 'diskspace-nodata' => q{Could not determine data_directory: are you connecting as a superuser?},
139 'diskspace-nodf' => q{Could not find required executable /bin/df},
140 'diskspace-nodir' => q{Could not find data directory "$1"},
141 'file-noclose' => q{Could not close $1: $2},
142 'files' => q{files},
143 'fsm-page-highver' => q{Cannot check fsm_pages on servers version 8.4 or greater},
144 'fsm-page-msg' => q{fsm page slots used: $1 of $2 ($3%)},
145 'fsm-rel-highver' => q{Cannot check fsm_relations on servers version 8.4 or greater},
146 'fsm-rel-msg' => q{fsm relations used: $1 of $2 ($3%)},
147 'hs-no-role' => q{Not a master/slave couple},
148 'hs-no-location' => q{Could not get current xlog location on $1},
149 'hs-receive-delay' => q{receive-delay},
150 'hs-replay-delay' => q{replay_delay},
151 'index' => q{Index},
152 'invalid-option' => q{Invalid option},
153 'invalid-query' => q{Invalid query returned: $1},
154 'language' => q{Language},
155 'listener-msg' => q{listeners found: $1},
156 'listening' => q{listening},
157 'locks-msg' => q{total "$1" locks: $2},
158 'locks-msg2' => q{total locks: $1},
159 'logfile-bad' => q{Invalid logfile "$1"},
160 'logfile-debug' => q{Final logfile: $1},
161 'logfile-dne' => q{logfile $1 does not exist!},
162 'logfile-fail' => q{fails logging to: $1},
163 'logfile-ok' => q{logs to: $1},
164 'logfile-openfail' => q{logfile "$1" failed to open: $2},
165 'logfile-opt-bad' => q{Invalid logfile option},
166 'logfile-seekfail' => q{Seek on $1 failed: $2},
167 'logfile-stderr' => q{Logfile output has been redirected to stderr: please provide a filename},
168 'logfile-syslog' => q{Database is using syslog, please specify path with --logfile option (fac=$1)},
169 'mode-standby' => q{Server in standby mode},
170 'mode' => q{mode},
171 'mrtg-fail' => q{Action $1 failed: $2},
172 'new-ver-nocver' => q{Could not download version information for $1},
173 'new-ver-badver' => q{Could not parse version information for $1},
174 'new-ver-dev' => q{Cannot compare versions on development versions: you have $1 version $2},
175 'new-ver-nolver' => q{Could not determine local version information for $1},
176 'new-ver-ok' => q{Version $1 is the latest for $2},
177 'new-ver-warn' => q{Please upgrade to version $1 of $2. You are running $3},
178 'new-ver-tt' => q{Your version of $1 ($2) appears to be ahead of the current release! ($3)},
179 'no-db' => q{No databases},
180 'no-match-db' => q{No matching databases found due to exclusion/inclusion options},
181 'no-match-fs' => q{No matching file systems found due to exclusion/inclusion options},
182 'no-match-rel' => q{No matching relations found due to exclusion/inclusion options},
183 'no-match-set' => q{No matching settings found due to exclusion/inclusion options},
184 'no-match-table' => q{No matching tables found due to exclusion/inclusion options},
185 'no-match-user' => q{No matching entries found due to user exclusion/inclusion options},
186 'no-parse-psql' => q{Could not parse psql output!},
187 'no-time-hires' => q{Cannot find Time::HiRes, needed if 'showtime' is true},
188 'opt-output-invalid' => q{Invalid output: must be 'nagios' or 'mrtg' or 'simple' or 'cacti'},
189 'opt-psql-badpath' => q{Invalid psql argument: must be full path to a file named psql},
190 'opt-psql-noexec' => q{The file "$1" does not appear to be executable},
191 'opt-psql-noexist' => q{Cannot find given psql executable: $1},
192 'opt-psql-nofind' => q{Could not find a suitable psql executable},
193 'opt-psql-nover' => q{Could not determine psql version},
194 'opt-psql-restrict' => q{Cannot use the --PGBINDIR or --PSQL option when NO_PSQL_OPTION is on},
195 'pgagent-jobs-ok' => q{No failed jobs},
196 'pgbouncer-pool' => q{Pool=$1 $2=$3},
197 'pgb-backends-mrtg' => q{DB=$1 Max connections=$2},
198 'pgb-backends-msg' => q{$1 of $2 connections ($3%)},
199 'pgb-backends-none' => q{No connections},
200 'pgb-backends-users' => q{$1 for number of users must be a number or percentage},
201 'PID' => q{PID},
202 'port' => q{port},
203 'preptxn-none' => q{No prepared transactions found},
204 'psa-disabled' => q{No queries - is stats_command_string or track_activities off?},
205 'psa-noexact' => q{Unknown error},
206 'psa-nosuper' => q{No matches - please run as a superuser},
207 'qtime-count-msg' => q{Total queries: $1},
208 'qtime-count-none' => q{not more than $1 queries},
209 'qtime-for-msg' => q{$1 queries longer than $2s, longest: $3s$4 $5},
210 'qtime-msg' => q{longest query: $1s$2 $3},
211 'qtime-none' => q{no queries},
212 'queries' => q{queries},
213 'query-time' => q{query_time},
214 'range-badcs' => q{Invalid '$1' option: must be a checksum},
215 'range-badlock' => q{Invalid '$1' option: must be number of locks, or "type1=#:type2=#"},
216 'range-badpercent' => q{Invalid '$1' option: must be a percentage},
217 'range-badpercsize' => q{Invalid '$1' option: must be a size or a percentage},
218 'range-badsize' => q{Invalid size for '$1' option},
219 'range-badtype' => q{validate_range called with unknown type '$1'},
220 'range-badversion' => q{Invalid string for '$1' option: $2},
221 'range-cactionly' => q{This action is for cacti use only and takes no warning or critical arguments},
222 'range-int' => q{Invalid argument for '$1' option: must be an integer},
223 'range-int-pos' => q{Invalid argument for '$1' option: must be a positive integer},
224 'range-neg-percent' => q{Cannot specify a negative percentage!},
225 'range-none' => q{No warning or critical options are needed},
226 'range-noopt-both' => q{Must provide both 'warning' and 'critical' options},
227 'range-noopt-one' => q{Must provide a 'warning' or 'critical' option},
228 'range-noopt-only' => q{Can only provide 'warning' OR 'critical' option},
229 'range-noopt-orboth' => q{Must provide a 'warning' option, a 'critical' option, or both},
230 'range-noopt-size' => q{Must provide a warning and/or critical size},
231 'range-nosize' => q{Must provide a warning and/or critical size},
232 'range-notime' => q{Must provide a warning and/or critical time},
233 'range-seconds' => q{Invalid argument to '$1' option: must be number of seconds},
234 'range-version' => q{must be in the format X.Y or X.Y.Z, where X is the major version number, },
235 'range-warnbig' => q{The 'warning' option cannot be greater than the 'critical' option},
236 'range-warnbigsize' => q{The 'warning' option ($1 bytes) cannot be larger than the 'critical' option ($2 bytes)},
237 'range-warnbigtime' => q{The 'warning' option ($1 s) cannot be larger than the 'critical' option ($2 s)},
238 'range-warnsmall' => q{The 'warning' option cannot be less than the 'critical' option},
239 'range-nointfortime' => q{Invalid argument for '$1' options: must be an integer, time or integer for time},
240 'relsize-msg-ind' => q{largest index is "$1": $2},
241 'relsize-msg-reli' => q{largest relation is index "$1": $2},
242 'relsize-msg-relt' => q{largest relation is table "$1": $2},
243 'relsize-msg-tab' => q{largest table is "$1": $2},
244 'rep-badarg' => q{Invalid repinfo argument: expected 6 comma-separated values},
245 'rep-duh' => q{Makes no sense to test replication with same values},
246 'rep-fail' => q{Row not replicated to slave $1},
247 'rep-noarg' => q{Need a repinfo argument},
248 'rep-norow' => q{Replication source row not found: $1},
249 'rep-noslaves' => q{No slaves found},
250 'rep-notsame' => q{Cannot test replication: values are not the same},
251 'rep-ok' => q{Row was replicated},
252 'rep-sourcefail' => q{Source update failed},
253 'rep-timeout' => q{Row was not replicated. Timeout: $1},
254 'rep-unknown' => q{Replication check failed},
255 'rep-wrongvals' => q{Cannot test replication: values are not the right ones ($1 not $2 nor $3)},
256 'runcommand-err' => q{Unknown error inside of the "run_command" function},
257 'runcommand-nodb' => q{No target databases could be found},
258 'runcommand-nodupe' => q{Could not dupe STDERR},
259 'runcommand-noerr' => q{Could not open STDERR?!},
260 'runcommand-nosys' => q{System call failed with a $1},
261 'runcommand-pgpass' => q{Created temporary pgpass file $1},
262 'runcommand-timeout' => q{Command timed out! Consider boosting --timeout higher than $1},
263 'runtime-badmrtg' => q{invalid queryname?},
264 'runtime-badname' => q{Invalid queryname option: must be a simple view name},
265 'runtime-msg' => q{query runtime: $1 seconds},
266 'schema' => q{Schema},
267 'ss-createfile' => q{Created file $1},
268 'ss-different' => q{"$1" is different:},
269 'ss-existson' => q{Exists on:},
270 'ss-failed' => q{Databases were different. Items not matched: $1},
271 'ss-matched' => q{All databases have identical items},
272 'ss-missingon' => q{Missing on:},
273 'ss-noexist' => q{$1 "$2" does not exist on all databases:},
274 'ss-notset' => q{"$1" is not set on all databases:},
275 'ss-suffix' => q{Error: cannot use suffix unless looking at time-based schemas},
276 'seq-die' => q{Could not determine information about sequence $1},
277 'seq-msg' => q{$1=$2% (calls left=$3)},
278 'seq-none' => q{No sequences found},
279 'size' => q{size},
280 'slony-noschema' => q{Could not determine the schema for Slony},
281 'slony-nonumber' => q{Call to sl_status did not return a number},
282 'slony-lagtime' => q{Slony lag time: $1},
283 'symlink-create' => q{Created "$1"},
284 'symlink-done' => q{Not creating "$1": $2 already linked to "$3"},
285 'symlink-exists' => q{Not creating "$1": $2 file already exists},
286 'symlink-fail1' => q{Failed to unlink "$1": $2},
287 'symlink-fail2' => q{Could not symlink $1 to $2: $3},
288 'symlink-name' => q{This command will not work unless the program has the word "postgres" in it},
289 'symlink-unlink' => q{Unlinking "$1":$2 },
290 'table' => q{Table},
291 'testmode-end' => q{END OF TEST MODE},
292 'testmode-fail' => q{Connection failed: $1 $2},
293 'testmode-norun' => q{Cannot run "$1" on $2: version must be >= $3, but is $4},
294 'testmode-noset' => q{Cannot run "$1" on $2: $3 is not set to on},
295 'testmode-nover' => q{Could not find version for $1},
296 'testmode-ok' => q{Connection ok: $1},
297 'testmode-start' => q{BEGIN TEST MODE},
298 'time-day' => q{day},
299 'time-days' => q{days},
300 'time-hour' => q{hour},
301 'time-hours' => q{hours},
302 'time-minute' => q{minute},
303 'time-minutes' => q{minutes},
304 'time-month' => q{month},
305 'time-months' => q{months},
306 'time-second' => q{second},
307 'time-seconds' => q{seconds},
308 'time-week' => q{week},
309 'time-weeks' => q{weeks},
310 'time-year' => q{year},
311 'time-years' => q{years},
312 'timesync-diff' => q{diff},
313 'timesync-msg' => q{timediff=$1 DB=$2 Local=$3},
314 'transactions' => q{transactions},
315 'trigger-msg' => q{Disabled triggers: $1},
316 'txn-time' => q{transaction_time},
317 'txnidle-count-msg' => q{Total idle in transaction: $1},
318 'txnidle-count-none' => q{not more than $1 idle in transaction},
319 'txnidle-for-msg' => q{$1 idle transactions longer than $2s, longest: $3s$4 $5},
320 'txnidle-msg' => q{longest idle in txn: $1s$2 $3},
321 'txnidle-none' => q{no idle in transaction},
322 'txntime-count-msg' => q{Total transactions: $1},
323 'txntime-count-none' => q{not more than $1 transactions},
324 'txntime-for-msg' => q{$1 transactions longer than $2s, longest: $3s$4 $5},
325 'txntime-msg' => q{longest txn: $1s$2 $3},
326 'txntime-none' => q{No transactions},
327 'txnwrap-cbig' => q{The 'critical' value must be less than 2 billion},
328 'txnwrap-wbig' => q{The 'warning' value must be less than 2 billion},
329 'unknown-error' => q{Unknown error},
330 'usage' => qq{\nUsage: \$1 <options>\n Try "\$1 --help" for a complete list of options\n Try "\$1 --man" for the full manual\n},
331 'user' => q{User},
332 'username' => q{username},
333 'vac-nomatch-a' => q{No matching tables have ever been analyzed},
334 'vac-nomatch-v' => q{No matching tables have ever been vacuumed},
335 'version' => q{version $1},
336 'version-badmrtg' => q{Invalid mrtg version argument},
337 'version-fail' => q{version $1, but expected $2},
338 'version-ok' => q{version $1},
339 'wal-numfound' => q{WAL files found: $1},
340 'wal-numfound2' => q{WAL "$2" files found: $1},
341},
342'fr' => {
343 'address' => q{adresse},
344 'age' => q{âge},
345 'backends-fatal' => q{N'a pas pu se connecter : trop de connexions},
346 'backends-mrtg' => q{DB=$1 Connexions maximum=$2},
347 'backends-msg' => q{$1 connexions sur $2 ($3%)},
348 'backends-nomax' => q{N'a pas pu déterminer max_connections},
349 'backends-oknone' => q{Aucune connexion},
350 'backends-po' => q{désolé, trop de clients sont déjà connectés},
351 'backends-users' => q{$1 pour le nombre d'utilisateurs doit être un nombre ou un pourcentage},
352 'bloat-index' => q{(db $1) index $2 lignes:$3 pages:$4 devrait être:$5 ($6X) octets perdus:$7 ($8)},
353 'bloat-nomin' => q{aucune relation n'atteint le critère minimum de fragmentation},
354 'bloat-table' => q{(db $1) table $2.$3 lignes:$4 pages:$5 devrait être:$6 ($7X) place perdue:$8 ($9)},
355 'bug-report' => q{Merci de rapporter ces d??tails ?? check_postgres@bucardo.org:},
356 'checkcluster-id' => q{Identifiant système de la base de données :},
357 'checkcluster-msg' => q{cluster_id : $1},
358 'checkcluster-nomrtg'=> q{Doit fournir un numéro via l'option --mrtg},
359 'checkmode-prod' => q{en production},
360 'checkmode-recovery' => q{en restauration d'archives},
361 'checkmode-state' => q{État de l'instance :},
362 'checkpoint-baddir' => q{data_directory invalide : "$1"},
363 'checkpoint-baddir2' => q{pg_controldata n'a pas pu lire le répertoire des données indiqué : « $1 »},
364 'checkpoint-badver' => q{Échec lors de l'exécution de pg_controldata - probablement la mauvaise version ($1)},
365 'checkpoint-badver2' => q{Échec lors de l'exécution de pg_controldata - est-ce la bonne version ?},
366 'checkpoint-nodir' => q{Vous devez fournir un argument --datadir ou configurer la variable d'environnement PGDATA},
367 'checkpoint-nodp' => q{Vous devez installer le module Perl Date::Parse pour utiliser l'action checkpoint},
368 'checkpoint-noparse' => q{Incapable d'analyser le résultat de la commande pg_controldata : "$1"},
369 'checkpoint-noregex' => q{La regex pour ce test n'a pas été trouvée},
370 'checkpoint-nosys' => q{N'a pas pu appeler pg_controldata : $1},
371 'checkpoint-ok' => q{Le dernier CHECKPOINT est survenu il y a une seconde},
372 'checkpoint-ok2' => q{Le dernier CHECKPOINT est survenu il y a $1 secondes},
373 'checkpoint-po' => q{Heure du dernier point de contrôle :},
374 'checksum-msg' => q{somme de contrôle : $1},
375 'checksum-nomd' => q{Vous devez installer le module Perl Digest::MD5 pour utiliser l'action checksum},
376 'checksum-nomrtg' => q{Vous devez fournir une somme de contrôle avec l'option --mrtg},
377 'custom-invalid' => q{Format invalide renvoyé par la requête personnalisée},
378 'custom-norows' => q{Aucune ligne renvoyée},
379 'custom-nostring' => q{Vous devez fournir une requête},
380 'database' => q{base de données},
381 'dbsize-version' => q{La base de données cible doit être une version 8.1 ou ultérieure pour exécuter l'action database_size},
382 'die-action-version' => q{Ne peut pas exécuter « $1 » : la version du serveur doit être supérieure ou égale à $2, alors qu'elle est $3},
383 'die-badtime' => q{La valeur de « $1 » doit être une heure valide. Par exemple, -$2 1s -$2 « 10 minutes »},
384 'die-badversion' => q{Version invalide : $1},
385 'die-noset' => q{Ne peut pas exécuter « $1 » $2 n'est pas activé},
386 'die-nosetting' => q{N'a pas pu récupérer le paramètre « $1 »},
387 'diskspace-fail' => q{Résultat invalide pour la commande « $1 » : $2},
388 'diskspace-msg' => q{Le système de fichiers $1 monté sur $2 utilise $3 sur $4 ($5%)},
389 'diskspace-nodata' => q{N'a pas pu déterminer data_directory : êtes-vous connecté en tant que super-utilisateur ?},
390 'diskspace-nodf' => q{N'a pas pu trouver l'exécutable /bin/df},
391 'diskspace-nodir' => q{N'a pas pu trouver le répertoire des données « $1 »},
392 'files' => q{fichiers},
393 'file-noclose' => q{N'a pas pu fermer $1 : $2},
394 'fsm-page-highver' => q{Ne peut pas vérifier fsm_pages sur des serveurs en version 8.4 ou ultérieure},
395 'fsm-page-msg' => q{emplacements de pages utilisés par la FSM : $1 sur $2 ($3%)},
396 'fsm-rel-highver' => q{Ne peut pas vérifier fsm_relations sur des serveurs en version 8.4 ou ultérieure},
397 'fsm-rel-msg' => q{relations tracées par la FSM : $1 sur $2 ($3%)},
398 'hs-no-role' => q{Pas de couple ma??tre/esclave},
399 'hs-no-location' => q{N'a pas pu obtenir l'emplacement courant dans le journal des transactions sur $1},
400 'hs-receive-delay' => q{délai de réception},
401 'hs-replay-delay' => q{délai de rejeu},
402 'index' => q{Index},
403 'invalid-option' => q{Option invalide},
404 'invalid-query' => q{Une requête invalide a renvoyé : $1},
405 'language' => q{Langage},
406 'listener-msg' => q{processus LISTEN trouvés : $1},
407 'listening' => q{en écoute},
408 'locks-msg' => q{total des verrous « $1 » : $2},
409 'locks-msg2' => q{total des verrous : $1},
410 'logfile-bad' => q{Option logfile invalide « $1 »},
411 'logfile-debug' => q{Journal applicatif final : $1},
412 'logfile-dne' => q{le journal applicatif $1 n'existe pas !},
413 'logfile-fail' => q{échec pour tracer dans : $1},
414 'logfile-ok' => q{trace dans : $1},
415 'logfile-openfail' => q{échec pour l'ouverture du journal applicatif « $1 » : $2},
416 'logfile-opt-bad' => q{Option logfile invalide},
417 'logfile-seekfail' => q{Échec de la recherche dans $1 : $2},
418 'logfile-stderr' => q{La sortie des traces a été redirigés stderr : merci de fournir un nom de fichier},
419 'logfile-syslog' => q{La base de données utiliser syslog, merci de spécifier le chemin avec l'option --logfile (fac=$1)},
420 'mode-standby' => q{Serveur en mode standby},
421 'mode' => q{mode},
422 'mrtg-fail' => q{Échec de l'action $1 : $2},
423 'new-ver-nocver' => q{N'a pas pu t??l??charger les informations de version pour $1},
424 'new-ver-badver' => q{N'a pas pu analyser les informations de version pour $1},
425 'new-ver-dev' => q{Ne peut pas comparer les versions sur des versions de d??veloppement : vous avez $1 version $2},
426 'new-ver-nolver' => q{N'a pas pu d??terminer les informations de version locale pour $1},
427 'new-ver-ok' => q{La version $1 est la dernière pour $2},
428 'new-ver-warn' => q{Merci de mettre à jour vers la version $1 de $2. Vous utilisez actuellement la $3},
429 'new-ver-tt' => q{Votre version de $1 ($2) semble ult??rieure ?? la version courante ! ($3)},
430 'no-db' => q{Pas de bases de données},
431 'no-match-db' => q{Aucune base de données trouvée à cause des options d'exclusion/inclusion},
432 'no-match-fs' => q{Aucun système de fichier trouvé à cause des options d'exclusion/inclusion},
433 'no-match-rel' => q{Aucune relation trouvée à cause des options d'exclusion/inclusion},
434 'no-match-set' => q{Aucun paramètre trouvé à cause des options d'exclusion/inclusion},
435 'no-match-table' => q{Aucune table trouvée à cause des options d'exclusion/inclusion},
436 'no-match-user' => q{Aucune entrée trouvée à cause options d'exclusion/inclusion},
437 'no-parse-psql' => q{N'a pas pu analyser la sortie de psql !},
438 'no-time-hires' => q{N'a pas trouvé le module Time::HiRes, nécessaire quand « showtime » est activé},
439 'opt-output-invalid' => q{Sortie invalide : doit être 'nagios' ou 'mrtg' ou 'simple' ou 'cacti'},
440 'opt-psql-badpath' => q{Argument invalide pour psql : doit être le chemin complet vers un fichier nommé psql},
441 'opt-psql-noexec' => q{ Le fichier « $1 » ne paraît pas exécutable},
442 'opt-psql-noexist' => q{Ne peut pas trouver l'exécutable psql indiqué : $1},
443 'opt-psql-nofind' => q{N'a pas pu trouver un psql exécutable},
444 'opt-psql-nover' => q{N'a pas pu déterminer la version de psql},
445 'opt-psql-restrict' => q{Ne peut pas utiliser l'option --PGBINDIR ou --PSQL si NO_PSQL_OPTION est activé},
446 'pgbouncer-pool' => q{Pool=$1 $2=$3},
447 'pgb-backends-mrtg' => q{base=$1 connexions max=$2},
448 'pgb-backends-msg' => q{$1 connexions sur $2 ($3%)},
449 'pgb-backends-none' => q{Aucune connection},
450 'pgb-backends-users' => q{Le nombre d'utilisateurs, $1, doit être un nombre ou un pourcentage},
451 'PID' => q{PID},
452 'port' => q{port},
453 'preptxn-none' => q{Aucune transaction préparée trouvée},
454 'psa-disabled' => q{Pas de requ??te - est-ce que stats_command_string ou track_activities sont d??sactiv??s ?},
455 'psa-noexact' => q{Erreur inconnue},
456 'psa-nosuper' => q{Aucune correspondance - merci de m'ex??cuter en tant que superutilisateur},
457 'qtime-count-msg' => q{Requêtes totales : $1},
458 'qtime-count-none' => q{pas plus que $1 requêtes},
459 'qtime-for-msg' => q{$1 requêtes plus longues que $2s, requête la plus longue : $3s$4 $5},
460 'qtime-msg' => q{requête la plus longue : $1s$2 $3},
461 'qtime-none' => q{aucune requête},
462 'queries' => q{requêtes},
463 'query-time' => q{durée de la requête},
464 'range-badcs' => q{Option « $1 » invalide : doit être une somme de contrôle},
465 'range-badlock' => q{Option « $1 » invalide : doit être un nombre de verrou ou « type1=#:type2=# »},
466 'range-badpercent' => q{Option « $1 » invalide : doit être un pourcentage},
467 'range-badpercsize' => q{Option « $1 » invalide : doit être une taille ou un pourcentage},
468 'range-badsize' => q{Taille invalide pour l'option « $1 »},
469 'range-badtype' => q{validate_range appelé avec un type inconnu « $1 »},
470 'range-badversion' => q{Chaîne invalide pour l'option « $1 » : $2},
471 'range-cactionly' => q{Cette action est pour cacti seulement et ne prend pas les arguments warning et critical},
472 'range-int' => q{Argument invalide pour l'option « $1 » : doit être un entier},
473 'range-int-pos' => q{Argument invalide pour l'option « $1 » : doit être un entier positif},
474 'range-neg-percent' => q{Ne peut pas indiquer un pourcentage négatif !},
475 'range-none' => q{Les options warning et critical ne sont pas nécessaires},
476 'range-noopt-both' => q{Doit fournir les options warning et critical},
477 'range-noopt-one' => q{Doit fournir une option warning ou critical},
478 'range-noopt-only' => q{Peut seulement fournir une option warning ou critical},
479 'range-noopt-orboth' => q{Doit fournir une option warning, une option critical ou les deux},
480 'range-noopt-size' => q{Doit fournir une taille warning et/ou critical},
481 'range-nosize' => q{Doit fournir une taille warning et/ou critical},
482 'range-notime' => q{Doit fournir une heure warning et/ou critical},
483 'range-seconds' => q{Argument invalide pour l'option « $1 » : doit être un nombre de secondes},
484 'range-version' => q{doit être dans le format X.Y ou X.Y.Z, où X est le numéro de version majeure, },
485 'range-warnbig' => q{L'option warning ne peut pas être plus grand que l'option critical},
486 'range-warnbigsize' => q{L'option warning ($1 octets) ne peut pas être plus grand que l'option critical ($2 octets)},
487 'range-warnbigtime' => q{L'option warning ($1 s) ne peut pas être plus grand que l'option critical ($2 s)},
488 'range-warnsmall' => q{L'option warningne peut pas être plus petit que l'option critical},
489 'range-nointfortime' => q{Argument invalide pour l'option '$1' : doit être un entier, une heure ou un entier horaire},
490 'relsize-msg-ind' => q{le plus gros index est « $1 » : $2},
491 'relsize-msg-reli' => q{la plus grosse relation est l'index « $1 » : $2},
492 'relsize-msg-relt' => q{la plus grosse relation est la table « $1 » : $2},
493 'relsize-msg-tab' => q{la plus grosse table est « $1 » : $2},
494 'rep-badarg' => q{Argument repinfo invalide : 6 valeurs séparées par des virgules attendues},
495 'rep-duh' => q{Aucun sens à tester la réplication avec les mêmes valeurs},
496 'rep-fail' => q{Ligne non répliquée sur l'esclave $1},
497 'rep-noarg' => q{A besoin d'un argument repinfo},
498 'rep-norow' => q{Ligne source de la réplication introuvable : $1},
499 'rep-noslaves' => q{Aucun esclave trouvé},
500 'rep-notsame' => q{Ne peut pas tester la réplication : les valeurs ne sont pas identiques},
501 'rep-ok' => q{La ligne a été répliquée},
502 'rep-sourcefail' => q{Échec de la mise à jour de la source},
503 'rep-timeout' => q{La ligne n'a pas été répliquée. Délai dépassé : $1},
504 'rep-unknown' => q{Échec du test de la réplication},
505 'rep-wrongvals' => q{Ne peut pas tester la réplication : les valeurs ne sont pas les bonnes (ni $1 ni $2 ni $3)},
506 'runcommand-err' => q{Erreur inconnue de la fonction « run_command »},
507 'runcommand-nodb' => q{Aucune base de données cible trouvée},
508 'runcommand-nodupe' => q{N'a pas pu dupliqué STDERR},
509 'runcommand-noerr' => q{N'a pas pu ouvrir STDERR},
510 'runcommand-nosys' => q{Échec de l'appel système avec un $1},
511 'runcommand-pgpass' => q{Création du fichier pgpass temporaire $1},
512 'runcommand-timeout' => q{Délai épuisée pour la commande ! Essayez d'augmenter --timeout à une valeur plus importante que $1},
513 'runtime-badmrtg' => q{queryname invalide ?},
514 'runtime-badname' => q{Option invalide pour queryname option : doit être le nom d'une vue},
515 'runtime-msg' => q{durée d'exécution de la requête : $1 secondes},
516 'schema' => q{Schéma},
517 'ss-createfile' => q{Création du fichier $1},
518 'ss-different' => q{"$1" est différent:},
519 'ss-existson' => q{Existe sur :},
520 'ss-failed' => q{Les bases de données sont différentes. Éléments différents : $1},
521 'ss-matched' => q{Les bases de données ont les mêmes éléments},
522 'ss-missingon' => q{Manque sur :},
523 'ss-noexist' => q{$1 "$2" n'existe pas sur toutes les bases de données :},
524 'ss-notset' => q{"$1" n'est pas configuré sur toutes les bases de données :},
525 'ss-suffix' => q{Erreur : ne peut pas utiliser le suffixe sauf à rechercher des schémas basés sur l'horloge},
526 'size' => q{taille},
527 'slony-noschema' => q{N'a pas pu déterminer le schéma de Slony},
528 'slony-nonumber' => q{L'appel à sl_status n'a pas renvoyé un numéro},
529 'slony-lagtime' => q{Durée de lag de Slony : $1},
530 'seq-die' => q{N'a pas pu récupérer d'informations sur la séquence $1},
531 'seq-msg' => q{$1=$2% (appels restant=$3)},
532 'seq-none' => q{Aucune sequences trouvée},
533 'symlink-create' => q{Création de « $1 »},
534 'symlink-done' => q{Création impossible de « $1 »: $2 est déjà lié à "$3"},
535 'symlink-exists' => q{Création impossible de « $1 »: le fichier $2 existe déjà },
536 'symlink-fail1' => q{Échec de la suppression de « $1 » : $2},
537 'symlink-fail2' => q{N'a pas pu supprimer le lien symbolique $1 vers $2 : $3},
538 'symlink-name' => q{Cette commande ne fonctionnera pas sauf si le programme contient le mot « postgres »},
539 'symlink-unlink' => q{Supression de « $1 » :$2 },
540 'table' => q{Table},
541 'testmode-end' => q{FIN DU MODE DE TEST},
542 'testmode-fail' => q{Échec de la connexion : $1 $2},
543 'testmode-norun' => q{N'a pas pu exécuter « $1 » sur $2 : la version doit être supérieure ou égale à $3, mais est $4},
544 'testmode-noset' => q{N'a pas pu exécuter « $1 » sur $2 : $3 n'est pas activé},
545 'testmode-nover' => q{N'a pas pu trouver la version de $1},
546 'testmode-ok' => q{Connexion OK : $1},
547 'testmode-start' => q{DÉBUT DU MODE DE TEST},
548 'time-day' => q{jour},
549 'time-days' => q{jours},
550 'time-hour' => q{heure},
551 'time-hours' => q{heures},
552 'time-minute' => q{minute},
553 'time-minutes' => q{minutes},
554 'time-month' => q{mois},
555 'time-months' => q{mois},
556 'time-second' => q{seconde},
557 'time-seconds' => q{secondes},
558 'time-week' => q{semaine},
559 'time-weeks' => q{semaines},
560 'time-year' => q{année},
561 'time-years' => q{années},
562 'timesync-diff' => q{diff},
563 'timesync-msg' => q{timediff=$1 Base de données=$2 Local=$3},
564 'transactions' => q{transactions},
565 'trigger-msg' => q{Triggers désactivés : $1},
566 'txn-time' => q{durée de la transaction},
567 'txnidle-count-msg' => q{Transactions en attente totales : $1},
568 'txnidle-count-none' => q{pas plus de $1 transaction en attente},
569 'txnidle-for-msg' => q{$1 transactions en attente plus longues que $2s, transaction la plus longue : $3s$4 $5},
570 'txnidle-msg' => q{transaction en attente la plus longue : $1s$2 $3},
571 'txnidle-none' => q{Aucun processus en attente dans une transaction},
572 'txntime-count-msg' => q{Transactions totales : $1},
573 'txntime-count-none' => q{pas plus que $1 transactions},
574 'txntime-for-msg' => q{$1 transactions plus longues que $2s, transaction la plus longue : $3s$4 $5},
575 'txntime-msg' => q{Transaction la plus longue : $1s$2 $3},
576 'txntime-none' => q{Aucune transaction},
577 'txnwrap-cbig' => q{La valeur critique doit être inférieure à 2 milliards},
578 'txnwrap-wbig' => q{La valeur d'avertissement doit être inférieure à 2 milliards},
579 'unknown-error' => q{erreur inconnue},
580 'usage' => qq{\nUsage: \$1 <options>\n Essayez « \$1 --help » pour liste complète des options\n\n},
581 'username' => q{nom utilisateur},
582 'user' => q{Utilisateur},
583 'vac-nomatch-a' => q{Aucune des tables correspondantes n'a eu d'opération ANALYZE},
584 'vac-nomatch-v' => q{Aucune des tables correspondantes n'a eu d'opération VACUUM},
585 'version' => q{version $1},
586 'version-badmrtg' => q{Argument invalide pour la version de mrtg},
587 'version-fail' => q{version $1, alors que la version attendue est $2},
588 'version-ok' => q{version $1},
589 'wal-numfound' => q{Fichiers WAL trouvés : $1},
590 'wal-numfound2' => q{Fichiers WAL "$2" trouvés : $1},
591},
592'af' => {
593},
594'cs' => {
595 'checkpoint-po' => q{�as posledn�ho kontroln�ho bodu:},
596},
597'de' => {
598 'backends-po' => q{tut mir leid, schon zu viele Verbindungen},
599 'checkpoint-po' => q{Zeit des letzten Checkpoints:},
600},
601'es' => {
602 'backends-po' => q{lo siento, ya tenemos demasiados clientes},
603 'checkpoint-po' => q{Instante de �ltimo checkpoint:},
604},
605'fa' => {
606 'checkpoint-po' => q{زمان آخرین وارسی:},
607},
608'hr' => {
609 'backends-po' => q{nažalost, već je otvoreno previše klijentskih veza},
610},
611'hu' => {
612 'checkpoint-po' => q{A legut�bbi ellen�rz�pont ideje:},
613},
614'it' => {
615 'checkpoint-po' => q{Orario ultimo checkpoint:},
616},
617'ja' => {
618 'backends-po' => q{ç¾åœ¨ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆæ•°ãŒå¤šã™ãŽã¾ã™},
619 'checkpoint-po' => q{最終ãƒã‚§ãƒƒã‚¯ãƒã‚¤ãƒ³ãƒˆæ™‚刻:},
620},
621'ko' => {
622 'backends-po' => q{최대 ë™ì‹œ ì ‘ì†ìž 수를 초과했습니다.},
623 'checkpoint-po' => q{������ üũ����Ʈ �ð�:},
624},
625'nb' => {
626 'backends-po' => q{beklager, for mange klienter},
627 'checkpoint-po' => q{Tidspunkt for nyeste kontrollpunkt:},
628},
629'nl' => {
630},
631'pl' => {
632 'checkpoint-po' => q{Czas najnowszego punktu kontrolnego:},
633},
634'pt_BR' => {
635 'backends-po' => q{desculpe, muitos clientes conectados},
636 'checkpoint-po' => q{Hora do último ponto de controle:},
637},
638'ro' => {
639 'checkpoint-po' => q{Timpul ultimului punct de control:},
640},
641'ru' => {
642 'backends-po' => q{��������, ��� ������� ����� ��������},
643 'checkpoint-po' => q{����� ��������� checkpoint:},
644},
645'sk' => {
646 'backends-po' => q{je mi ��to, je u� pr�li� ve�a klientov},
647 'checkpoint-po' => q{Čas posledného kontrolného bodu:},
648},
649'sl' => {
650 'backends-po' => q{povezanih je �e preve� odjemalcev},
651 'checkpoint-po' => q{�as zadnje kontrolne to�ke ............},
652},
653'sv' => {
654 'backends-po' => q{ledsen, f�r m�nga klienter},
655 'checkpoint-po' => q{Tidpunkt f�r senaste kontrollpunkt:},
656},
657'ta' => {
658 'checkpoint-po' => q{நவீன சோதனை மையதà¯à®¤à®¿à®©à¯ நேரமà¯:},
659},
660'tr' => {
661 'backends-po' => q{üzgünüm, istemci sayısı çok fazla},
662 'checkpoint-po' => q{En son checkpoint'in zamanı:},
663},
664'zh_CN' => {
665 'backends-po' => q{�Բ���, �Ѿ���̫���Ŀͻ�},
666 'checkpoint-po' => q{���¼�������ʱ��:},
667},
668'zh_TW' => {
669 'backends-po' => q{å°ä¸èµ·ï¼Œç”¨æˆ¶ç«¯éŽå¤š},
670 'checkpoint-po' => q{最新的檢查點時間:},
671},
672);
673## use critic
674
675our $lang = $ENV{LC_ALL} || $ENV{LC_MESSAGES} || $ENV{LANG} || 'en';
676$lang = substr($lang,0,2);
677
678## Messages are stored in these until the final output via finishup()
679our (%ok, %warning, %critical, %unknown);
680
681our $ME = basename($0);
682our $ME2 = 'check_postgres.pl';
683our $USAGE = msg('usage', $ME);
684
685## This gets turned on for meta-commands which don't hit a Postgres database
686our $nohost = 0;
687
688## Global error string, mostly used for MRTG error handling
689our $ERROR = '';
690
691$opt{test} = 0;
692$opt{timeout} = 30;
693
694## Look for any rc files to control additional parameters
695## Command line options always overwrite these
696## Format of these files is simply name=val
697
698## This option must come before the GetOptions call
699for my $arg (@ARGV) {
700 if ($arg eq '--no-check_postgresrc') {
701 $opt{'no-check_postgresrc'} = 1;
702 last;
703 }
704}
705
706## Used by same_schema in the find_catalog_info sub
707my %catalog_info = (
708
709 user => {
710 SQL => q{
711SELECT *, usename AS name, quote_ident(usename) AS safeusename
712FROM pg_user},
713 deletecols => [ qw{ passwd } ],
714 },
715
716 schema => {
717 SQL => q{
718SELECT n.oid, quote_ident(nspname) AS name, quote_ident(usename) AS owner, nspacl
719FROM pg_namespace n
720JOIN pg_user u ON (u.usesysid = n.nspowner)},
721 deletecols => [ ],
722 exclude => 'temp_schemas',
723 },
724 language => {
725 SQL => q{
726SELECT l.*, lanname AS name, quote_ident(usename) AS owner
727FROM pg_language l
728JOIN pg_user u ON (u.usesysid = l.lanowner)},
729 SQL2 => q{
730SELECT l.*, lanname AS name
731FROM pg_language l
732 },
733 },
734 type => {
735 SQL => q{
736SELECT t.oid AS oid, t.*, quote_ident(usename) AS owner, quote_ident(nspname) AS schema,
737 nspname||'.'||typname AS name
738FROM pg_type t
739JOIN pg_user u ON (u.usesysid = t.typowner)
740JOIN pg_namespace n ON (n.oid = t.typnamespace)
741WHERE t.typtype NOT IN ('b','c')},
742 exclude => 'system',
743 },
744 sequence => {
745 SQL => q{
746SELECT c.*, nspname||'.'||relname AS name, quote_ident(usename) AS owner,
747 (quote_ident(nspname)||'.'||quote_ident(relname)) AS safename,
748quote_ident(nspname) AS schema
749FROM pg_class c
750JOIN pg_user u ON (u.usesysid = c.relowner)
751JOIN pg_namespace n ON (n.oid = c.relnamespace)
752WHERE c.relkind = 'S'},
753 innerSQL => 'SELECT * FROM ROWSAFENAME',
754 },
755 view => {
756 SQL => q{
757SELECT c.*, nspname||'.'||relname AS name, quote_ident(usename) AS owner,
758 quote_ident(relname) AS safename, quote_ident(nspname) AS schema,
759 TRIM(pg_get_viewdef(c.oid, TRUE)) AS viewdef, spcname AS tablespace
760FROM pg_class c
761JOIN pg_user u ON (u.usesysid = c.relowner)
762JOIN pg_namespace n ON (n.oid = c.relnamespace)
763LEFT JOIN pg_tablespace s ON (s.oid = c.reltablespace)
764WHERE c.relkind = 'v'},
765 exclude => 'system',
766 },
767 table => {
768 SQL => q{
769SELECT c.*, nspname||'.'||relname AS name, quote_ident(usename) AS owner,
770 quote_ident(relname) AS safename, quote_ident(nspname) AS schema,
771 spcname AS tablespace
772FROM pg_class c
773JOIN pg_user u ON (u.usesysid = c.relowner)
774JOIN pg_namespace n ON (n.oid = c.relnamespace)
775LEFT JOIN pg_tablespace s ON (s.oid = c.reltablespace)
776WHERE c.relkind = 'r'},
777 exclude => 'system',
778 },
779 index => {
780 SQL => q{
781SELECT c.*, i.*, nspname||'.'||relname AS name, quote_ident(usename) AS owner,
782 quote_ident(relname) AS safename, quote_ident(nspname) AS schema,
783 spcname AS tablespace, amname,
784 pg_get_indexdef(c.oid) AS indexdef
785FROM pg_class c
786JOIN pg_user u ON (u.usesysid = c.relowner)
787JOIN pg_namespace n ON (n.oid = c.relnamespace)
788JOIN pg_index i ON (c.oid = i.indexrelid)
789LEFT JOIN pg_tablespace s ON (s.oid = c.reltablespace)
790LEFT JOIN pg_am a ON (a.oid = c.relam)
791WHERE c.relkind = 'i'},
792 exclude => 'system',
793 },
794 operator => {
795 SQL => q{
796SELECT o.*, o.oid, nspname||'.'||o.oprname AS name, quote_ident(o.oprname) AS safename,
797 usename AS owner, nspname AS schema,
798 t1.typname AS resultname,
799 t2.typname AS leftname, t3.typname AS rightname
800FROM pg_operator o
801JOIN pg_user u ON (u.usesysid = o.oprowner)
802JOIN pg_namespace n ON (n.oid = o.oprnamespace)
803JOIN pg_proc p1 ON (p1.oid = o.oprcode)
804JOIN pg_type t1 ON (t1.oid = o.oprresult)
805LEFT JOIN pg_type t2 ON (t2.oid = o.oprleft)
806LEFT JOIN pg_type t3 ON (t3.oid = o.oprright)},
807 exclude => 'system',
808 },
809 trigger => {
810 SQL => q{
811SELECT t.*, n1.nspname||'.'||t.tgname AS name, quote_ident(t.tgname) AS safename, quote_ident(usename) AS owner,
812 n1.nspname AS tschema, c1.relname AS tname,
813 n2.nspname AS cschema, c2.relname AS cname,
814 n3.nspname AS procschema, p.proname AS procname
815FROM pg_trigger t
816JOIN pg_class c1 ON (c1.oid = t.tgrelid)
817JOIN pg_user u ON (u.usesysid = c1.relowner)
818JOIN pg_namespace n1 ON (n1.oid = c1.relnamespace)
819JOIN pg_proc p ON (p.oid = t.tgfoid)
820JOIN pg_namespace n3 ON (n3.oid = p.pronamespace)
821LEFT JOIN pg_class c2 ON (c2.oid = t.tgconstrrelid)
822LEFT JOIN pg_namespace n2 ON (n2.oid = c2.relnamespace)
823WHERE t.tgconstrrelid = 0 AND tgname !~ '^pg_'},
824 },
825 function => {
826 SQL => q{
827SELECT p.*, p.oid, nspname||'.'||p.proname AS name, quote_ident(p.proname) AS safename,
828 md5(prosrc) AS source_checksum,
829 usename AS owner, nspname AS schema
830FROM pg_proc p
831JOIN pg_user u ON (u.usesysid = p.proowner)
832JOIN pg_namespace n ON (n.oid = p.pronamespace)},
833 exclude => 'system',
834 },
835 constraint => {
836 SQL => q{
837SELECT c.*, c.oid, n.nspname||'.'||c.conname AS name, quote_ident(c.conname) AS safename,
838 n.nspname AS schema, relname AS tname
839FROM pg_constraint c
840JOIN pg_namespace n ON (n.oid = c.connamespace)
841JOIN pg_class r ON (r.oid = c.conrelid)
842JOIN pg_namespace n2 ON (n2.oid = r.relnamespace)},
843 exclude => 'system',
844 },
845 column => {
846 SQL => q{
847SELECT a.*, n.nspname||'.'||c.relname||'.'||attname AS name, quote_ident(a.attname) AS safename,
848 n.nspname||'.'||c.relname AS tname,
849 typname, quote_ident(nspname) AS schema,
850 pg_get_expr(d.adbin, a.attrelid, true) AS default
851FROM pg_attribute a
852JOIN pg_type t ON (t.oid = a.atttypid)
853JOIN pg_class c ON (c.oid = a.attrelid AND c.relkind = 'r')
854JOIN pg_namespace n ON (n.oid = c.relnamespace)
855LEFT JOIN pg_attrdef d ON (d.adrelid = a.attrelid AND d.adnum = a.attnum)
856WHERE attnum >= 1
857AND NOT attisdropped},
858 postSQL => q{ORDER BY n.nspname, c.relname, a.attnum},
859 exclude => 'system',
860 },
861);
862
863my $rcfile;
864if (! $opt{'no-check_postgresrc'}) {
865 if (-e '.check_postgresrc') {
866 $rcfile = '.check_postgresrc';
867 }
868 elsif (-e "$ENV{HOME}/.check_postgresrc") {
869 $rcfile = "$ENV{HOME}/.check_postgresrc";
870 }
871 elsif (-e '/etc/check_postgresrc') {
872 $rcfile = '/etc/check_postgresrc';
873 }
874 elsif (-e '/usr/local/etc/check_postgresrc') {
875 $rcfile = '/usr/local/etc/check_postgresrc';
876 }
877}
878## We need a temporary hash so that multi-value options can be overridden on the command line
879my %tempopt;
880if (defined $rcfile) {
881 open my $rc, '<', $rcfile or die qq{Could not open "$rcfile": $!\n};
882 RCLINE:
883 while (<$rc>) {
884 next if /^\s*#/;
885 next unless /^\s*(\w+)\s*=\s*(.+?)\s*$/o;
886 my ($name,$value) = ($1,$2); ## no critic (ProhibitCaptureWithoutTest)
887 ## Map alternate option spellings to preferred names
888 if ($name eq 'dbport' or $name eq 'p' or $name eq 'dbport1' or $name eq 'p1' or $name eq 'port1') {
889 $name = 'port';
890 }
891 elsif ($name eq 'dbhost' or $name eq 'H' or $name eq 'dbhost1' or $name eq 'H1' or $name eq 'host1') {
892 $name = 'host';
893 }
894 elsif ($name eq 'db' or $name eq 'db1' or $name eq 'dbname1') {
895 $name = 'dbname';
896 }
897 elsif ($name eq 'u' or $name eq 'u1' or $name eq 'dbuser1') {
898 $name = 'dbuser';
899 }
900 ## Now for all the additional non-1 databases
901 elsif ($name =~ /^dbport(\d+)$/o or $name eq /^p(\d+)$/o) {
902 $name = "port$1";
903 }
904 elsif ($name =~ /^dbhost(\d+)$/o or $name eq /^H(\d+)$/o) {
905 $name = "host$1";
906 }
907 elsif ($name =~ /^db(\d)$/o) {
908 $name = "dbname$1";
909 }
910 elsif ($name =~ /^u(\d+)$/o) {
911 $name = "dbuser$1";
912 }
913
914 ## These options are multiples ('@s')
915 for my $arr (qw/include exclude includeuser excludeuser host port
916 dbuser dbname dbpass dbservice schema/) {
917 next if $name ne $arr and $name ne "${arr}2";
918 push @{$tempopt{$name}} => $value;
919 ## Don't set below as a normal value
920 next RCLINE;
921 }
922 $opt{$name} = $value;
923 }
924 close $rc or die;
925}
926
927die $USAGE if ! @ARGV;
928
929GetOptions(
930 \%opt,
931 'version|V',
932 'verbose|v+',
933 'vv',
934 'help|h',
935 'quiet|q',
936 'man',
937 'output=s',
938 'simple',
939 'showperf=i',
940 'perflimit=i',
941 'showtime=i',
942 'timeout|t=i',
943 'test',
944 'symlinks',
945 'debugoutput=s',
946 'no-check_postgresrc',
947 'assume-standby-mode',
948 'assume-prod',
949
950 'action=s',
951 'warning=s',
952 'critical=s',
953 'include=s@',
954 'exclude=s@',
955 'includeuser=s@',
956 'excludeuser=s@',
957
958 'host|dbhost|H|dbhost1|H1=s@',
959 'port|dbport|p|port1|dbport1|p1=s@',
960 'dbname|db|dbname1|db1=s@',
961 'dbuser|u|dbuser1|u1=s@',
962 'dbpass|dbpass1=s@',
963 'dbservice|dbservice1=s@',
964
965 'PGBINDIR=s',
966 'PSQL=s',
967
968 'tempdir=s',
969 'get_method=s',
970 'language=s',
971 'mrtg=s', ## used by MRTG checks only
972 'logfile=s', ## used by check_logfile only
973 'queryname=s', ## used by query_runtime only
974 'query=s', ## used by custom_query only
975 'valtype=s', ## used by custom_query only
976 'reverse', ## used by custom_query only
977 'repinfo=s', ## used by replicate_row only
978 'noidle', ## used by backends only
979 'datadir=s', ## used by checkpoint only
980 'schema=s@', ## used by slony_status only
981 'filter=s@', ## used by same_schema only
982 'suffix=s', ## used by same_schema only
983 'replace', ## used by same_schema only
984);
985
986die $USAGE if ! keys %opt and ! @ARGV;
987
988## Process the args that are not so easy for Getopt::Long
989my @badargs;
990
991while (my $arg = pop @ARGV) {
992
993 ## These must be of the form x=y
994 if ($arg =~ /^\-?\-?(\w+)\s*=\s*(.+)/o) {
995 my ($name,$value) = (lc $1, $2);
996 if ($name =~ /^(?:db)?port(\d+)$/o or $name =~ /^p(\d+)$/o) {
997 $opt{"port$1"} = $value;
998 }
999 elsif ($name =~ /^(?:db)?host(\d+)$/o or $name =~ /^H(\d+)$/o) {
1000 $opt{"host$1"} = $value;
1001 }
1002 elsif ($name =~ /^db(?:name)?(\d+)$/o) {
1003 $opt{"dbname$1"} = $value;
1004 }
1005 elsif ($name =~ /^dbuser(\d+)$/o or $name =~ /^u(\d+)/o) {
1006 $opt{"dbuser$1"} = $value;
1007 }
1008 elsif ($name =~ /^dbpass(\d+)$/o) {
1009 $opt{"dbpass$1"} = $value;
1010 }
1011 elsif ($name =~ /^dbservice(\d+)$/o) {
1012 $opt{"dbservice$1"} = $value;
1013 }
1014 else {
1015 push @badargs => $arg;
1016 }
1017 next;
1018 }
1019 push @badargs => $arg;
1020}
1021
1022if (@badargs) {
1023 warn "Invalid arguments:\n";
1024 for (@badargs) {
1025 warn " $_\n";
1026 }
1027 die $USAGE;
1028}
1029
1030if ( $opt{man} ) {
1031 require Pod::Usage;
1032 Pod::Usage::pod2usage({-verbose => 2});
1033 exit;
1034}
1035
1036## Put multi-val options from check_postgresrc in place, only if no command-line args!
1037for my $mv (keys %tempopt) {
1038 $opt{$mv} ||= delete $tempopt{$mv};
1039}
1040
1041our $VERBOSE = $opt{verbose} || 0;
1042$VERBOSE = 5 if $opt{vv};
1043
1044our $OUTPUT = lc($opt{output} || '');
1045
1046## Allow the optimization of the get_methods list by an argument
1047if ($opt{get_method}) {
1048 my $found = 0;
1049 for my $meth (@get_methods) {
1050 if ($meth =~ /^$opt{get_method}/io) {
1051 @get_methods = ($meth);
1052 $found = 1;
1053 last;
1054 }
1055 }
1056 if (!$found) {
1057 print "Unknown value for get_method: $opt{get_method}\n";
1058 print "Valid choices are:\n";
1059 print (join "\n" => map { s/(\w+).*/$1/; $_ } @get_methods);
1060 print "\n";
1061 exit;
1062 }
1063}
1064
1065## Allow the language to be changed by an explicit option
1066if ($opt{language}) {
1067 $lang = substr($opt{language},0,2);
1068}
1069
1070## Output the actual string returned by psql in the normal output
1071## Argument is 'a' for all, 'w' for warning, 'c' for critical, 'u' for unknown
1072## Can be grouped together
1073our $DEBUGOUTPUT = $opt{debugoutput} || '';
1074our $DEBUG_INFO = '?';
1075
1076## If not explicitly given an output, check the current directory,
1077## then fall back to the default.
1078
1079if (!$OUTPUT) {
1080 my $dir = getcwd;
1081 if ($dir =~ /(nagios|mrtg|simple|cacti)/io) {
1082 $OUTPUT = lc $1;
1083 }
1084 elsif ($opt{simple}) {
1085 $OUTPUT = 'simple';
1086 }
1087 else {
1088 $OUTPUT = $DEFAULT_OUTPUT;
1089 }
1090}
1091
1092
1093## Extract transforms from the output
1094$opt{transform} = '';
1095if ($OUTPUT =~ /\b(kb|mb|gb|tb|eb)\b/) {
1096 $opt{transform} = uc $1;
1097}
1098if ($OUTPUT =~ /(nagios|mrtg|simple|cacti)/io) {
1099 $OUTPUT = lc $1;
1100}
1101## Check for a valid output setting
1102if ($OUTPUT ne 'nagios' and $OUTPUT ne 'mrtg' and $OUTPUT ne 'simple' and $OUTPUT ne 'cacti') {
1103 die msgn('opt-output-invalid');
1104}
1105
1106our $MRTG = ($OUTPUT eq 'mrtg' or $OUTPUT eq 'simple') ? 1 : 0;
1107our (%stats, %statsmsg);
1108our $SIMPLE = $OUTPUT eq 'simple' ? 1 : 0;
1109
1110## See if we need to invoke something based on our name
1111our $action = $opt{action} || '';
1112if ($ME =~ /check_postgres_(\w+)/ and ! defined $opt{action}) {
1113 $action = $1;
1114}
1115
1116$VERBOSE >= 3 and warn Dumper \%opt;
1117
1118if ($opt{version}) {
1119 print qq{$ME2 version $VERSION\n};
1120 exit 0;
1121}
1122
1123## Quick hash to put normal action information in one place:
1124our $action_info = {
1125 # Name # clusterwide? # helpstring
1126 archive_ready => [1, 'Check the number of WAL files ready in the pg_xlog/archive_status'],
1127 autovac_freeze => [1, 'Checks how close databases are to autovacuum_freeze_max_age.'],
1128 backends => [1, 'Number of connections, compared to max_connections.'],
1129 bloat => [0, 'Check for table and index bloat.'],
1130 checkpoint => [1, 'Checks how long since the last checkpoint'],
1131 cluster_id => [1, 'Checks the Database System Identifier'],
1132 commitratio => [0, 'Report if the commit ratio of a database is too low.'],
1133 connection => [0, 'Simple connection check.'],
1134 custom_query => [0, 'Run a custom query.'],
1135 database_size => [0, 'Report if a database is too big.'],
1136 dbstats => [1, 'Returns stats from pg_stat_database: Cacti output only'],
1137 disabled_triggers => [0, 'Check if any triggers are disabled'],
1138 disk_space => [1, 'Checks space of local disks Postgres is using.'],
1139 fsm_pages => [1, 'Checks percentage of pages used in free space map.'],
1140 fsm_relations => [1, 'Checks percentage of relations used in free space map.'],
1141 hitratio => [0, 'Report if the hit ratio of a database is too low.'],
1142 hot_standby_delay => [1, 'Check the replication delay in hot standby setup'],
1143 index_size => [0, 'Checks the size of indexes only.'],
1144 table_size => [0, 'Checks the size of tables only.'],
1145 relation_size => [0, 'Checks the size of tables and indexes.'],
1146 last_analyze => [0, 'Check the maximum time in seconds since any one table has been analyzed.'],
1147 last_vacuum => [0, 'Check the maximum time in seconds since any one table has been vacuumed.'],
1148 last_autoanalyze => [0, 'Check the maximum time in seconds since any one table has been autoanalyzed.'],
1149 last_autovacuum => [0, 'Check the maximum time in seconds since any one table has been autovacuumed.'],
1150 listener => [0, 'Checks for specific listeners.'],
1151 locks => [0, 'Checks the number of locks.'],
1152 logfile => [1, 'Checks that the logfile is being written to correctly.'],
1153 new_version_bc => [0, 'Checks if a newer version of Bucardo is available.'],
1154 new_version_box => [0, 'Checks if a newer version of boxinfo is available.'],
1155 new_version_cp => [0, 'Checks if a newer version of check_postgres.pl is available.'],
1156 new_version_pg => [0, 'Checks if a newer version of Postgres is available.'],
1157 new_version_tnm => [0, 'Checks if a newer version of tail_n_mail is available.'],
1158 pgb_pool_cl_active => [1, 'Check the number of active clients in each pgbouncer pool.'],
1159 pgb_pool_cl_waiting => [1, 'Check the number of waiting clients in each pgbouncer pool.'],
1160 pgb_pool_sv_active => [1, 'Check the number of active server connections in each pgbouncer pool.'],
1161 pgb_pool_sv_idle => [1, 'Check the number of idle server connections in each pgbouncer pool.'],
1162 pgb_pool_sv_used => [1, 'Check the number of used server connections in each pgbouncer pool.'],
1163 pgb_pool_sv_tested => [1, 'Check the number of tested server connections in each pgbouncer pool.'],
1164 pgb_pool_sv_login => [1, 'Check the number of login server connections in each pgbouncer pool.'],
1165 pgb_pool_maxwait => [1, 'Check the current maximum wait time for client connections in pgbouncer pools.'],
1166 pgbouncer_backends => [0, 'Check how many clients are connected to pgbouncer compared to max_client_conn.'],
1167 pgbouncer_checksum => [0, 'Check that no pgbouncer settings have changed since the last check.'],
1168 pgagent_jobs => [0, 'Check for no failed pgAgent jobs within a specified period of time.'],
1169 prepared_txns => [1, 'Checks number and age of prepared transactions.'],
1170 query_runtime => [0, 'Check how long a specific query takes to run.'],
1171 query_time => [1, 'Checks the maximum running time of current queries.'],
1172 replicate_row => [0, 'Verify a simple update gets replicated to another server.'],
1173 same_schema => [0, 'Verify that two databases have the exact same tables, columns, etc.'],
1174 sequence => [0, 'Checks remaining calls left in sequences.'],
1175 settings_checksum => [0, 'Check that no settings have changed since the last check.'],
1176 slony_status => [1, 'Ensure Slony is up to date via sl_status.'],
1177 timesync => [0, 'Compare database time to local system time.'],
1178 txn_idle => [1, 'Checks the maximum "idle in transaction" time.'],
1179 txn_time => [1, 'Checks the maximum open transaction time.'],
1180 txn_wraparound => [1, 'See how close databases are getting to transaction ID wraparound.'],
1181 version => [1, 'Check for proper Postgres version.'],
1182 wal_files => [1, 'Check the number of WAL files in the pg_xlog directory'],
1183};
1184
1185## XXX Need to i18n the above
1186our $action_usage = '';
1187our $longname = 1;
1188for (keys %$action_info) {
1189 $longname = length($_) if length($_) > $longname;
1190}
1191for (sort keys %$action_info) {
1192 $action_usage .= sprintf " %-*s - %s\n", 2+$longname, $_, $action_info->{$_}[1];
1193}
1194
1195
1196if ($opt{help}) {
1197 print qq{Usage: $ME2 <options>
1198Run various tests against one or more Postgres databases.
1199Returns with an exit code of 0 (success), 1 (warning), 2 (critical), or 3 (unknown)
1200This is version $VERSION.
1201
1202Common connection options:
1203 -H, --host=NAME hostname(s) to connect to; defaults to none (Unix socket)
1204 -p, --port=NUM port(s) to connect to; defaults to $opt{defaultport}.
1205 -db, --dbname=NAME database name(s) to connect to; defaults to 'postgres' or 'template1'
1206 -u --dbuser=NAME database user(s) to connect as; defaults to '$opt{defaultuser}'
1207 --dbpass=PASS database password(s); use a .pgpass file instead when possible
1208 --dbservice=NAME service name to use inside of pg_service.conf
1209
1210Connection options can be grouped: --host=a,b --host=c --port=1234 --port=3344
1211would connect to a-1234, b-1234, and c-3344
1212
1213Limit options:
1214 -w value, --warning=value the warning threshold, range depends on the action
1215 -c value, --critical=value the critical threshold, range depends on the action
1216 --include=name(s) items to specifically include (e.g. tables), depends on the action
1217 --exclude=name(s) items to specifically exclude (e.g. tables), depends on the action
1218 --includeuser=include objects owned by certain users
1219 --excludeuser=exclude objects owned by certain users
1220
1221Other options:
1222 --assume-standby-mode assume that server in continious WAL recovery mode
1223 --assume-prod assume that server in production mode
1224 --PGBINDIR=PATH path of the postgresql binaries; avoid using if possible
1225 --PSQL=FILE (deprecated) location of the psql executable; avoid using if possible
1226 -v, --verbose verbosity level; can be used more than once to increase the level
1227 -h, --help display this help information
1228 --man display the full manual
1229 -t X, --timeout=X how long in seconds before we timeout. Defaults to 30 seconds.
1230 --symlinks create named symlinks to the main program for each action
1231
1232Actions:
1233Which test is determined by the --action option, or by the name of the program
1234$action_usage
1235
1236For a complete list of options and full documentation, view the manual.
1237
1238 $ME --man
1239
1240Or visit: http://bucardo.org/check_postgres/
1241
1242
1243};
1244 exit 0;
1245}
1246
1247build_symlinks() if $opt{symlinks};
1248
1249$action =~ /\w/ or die $USAGE;
1250
1251## Be nice and figure out what they meant
1252$action =~ s/\-/_/g;
1253$action = lc $action;
1254
1255## Build symlinked copies of this file
1256build_symlinks() if $action =~ /build_symlinks/; ## Does not return, may be 'build_symlinks_force'
1257
1258## Die if Time::HiRes is needed but not found
1259if ($opt{showtime}) {
1260 eval {
1261 require Time::HiRes;
1262 import Time::HiRes qw/gettimeofday tv_interval sleep/;
1263 };
1264 if ($@) {
1265 die msg('no-time-hires');
1266 }
1267}
1268
1269## We don't (usually) want to die, but want a graceful Nagios-like exit instead
1270sub ndie {
1271 eval { File::Temp::cleanup(); };
1272 my $msg = shift;
1273 chomp $msg;
1274 ## If this message already starts with an ERROR, filter that out for prettiness
1275 $msg =~ s/^\s*ERROR:\s*/ /;
1276 ## Trim whitespace
1277 $msg =~ s/^\s*(.+)\s*$/$1/;
1278 print "ERROR: $msg\n";
1279 exit 3;
1280}
1281
1282sub msg { ## no critic
1283
1284 my $name = shift || '?';
1285
1286 my $msg = '';
1287
1288 if (exists $msg{$lang}{$name}) {
1289 $msg = $msg{$lang}{$name};
1290 }
1291 elsif (exists $msg{'en'}{$name}) {
1292 $msg = $msg{'en'}{$name};
1293 }
1294 else {
1295 ## Allow for non-matches in certain rare cases
1296 return '' if $opt{nomsgok};
1297 my $line = (caller)[2];
1298 die qq{Invalid message "$name" from line $line\n};
1299 }
1300
1301 my $x=1;
1302 {
1303 my $val = $_[$x-1];
1304 $val = '?' if ! defined $val;
1305 last unless $msg =~ s/\$$x/$val/g;
1306 $x++;
1307 redo;
1308 }
1309 return $msg;
1310
1311} ## end of msg
1312
1313sub msgn { ## no critic
1314 return msg(@_) . "\n";
1315}
1316
1317sub msg_en {
1318
1319 my $name = shift || '?';
1320
1321 return $msg{'en'}{$name};
1322
1323} ## end of msg_en
1324
1325## Everything from here on out needs psql, so find and verify a working version:
1326if ($NO_PSQL_OPTION) {
1327 (delete $opt{PGBINDIR} or delete $opt{PSQL}) and ndie msg('opt-psql-restrict');
1328}
1329if (! defined $PGBINDIR or ! length $PGBINDIR) {
1330 if (defined $ENV{PGBINDIR} and length $ENV{PGBINDIR}){
1331 $PGBINDIR = $ENV{PGBINDIR};
1332 }
1333 elsif (defined $opt{PGBINDIR} and length $opt{PGBINDIR}){
1334 $PGBINDIR = $opt{PGBINDIR};
1335 }
1336 else {
1337 undef $PGBINDIR;
1338 }
1339}
1340if (exists $opt{PSQL}) {
1341 $PSQL = $opt{PSQL};
1342 $PSQL =~ m{^/[\w\d\/]*psql$} or ndie msg('opt-psql-badpath');
1343 -e $PSQL or ndie msg('opt-psql-noexist', $PSQL);
1344}
1345else {
1346 my $psql = (defined $PGBINDIR) ? "$PGBINDIR/psql" : 'psql';
1347 chomp($PSQL = qx{which "$psql"});
1348 $PSQL or ndie msg('opt-psql-nofind');
1349}
1350-x $PSQL or ndie msg('opt-psql-noexec', $PSQL);
1351$res = qx{$PSQL --version};
1352$res =~ /psql\D+(\d+\.\d+)/ or ndie msg('opt-psql-nover');
1353our $psql_version = $1;
1354
1355$VERBOSE >= 2 and warn qq{psql=$PSQL version=$psql_version\n};
1356
1357$opt{defaultdb} = $psql_version >= 8.0 ? 'postgres' : 'template1';
1358$opt{defaultdb} = 'pgbouncer' if $action =~ /^pgb/;
1359
1360## Check the current database mode
1361our $STANDBY = 0;
1362our $MASTER = 0;
1363make_sure_standby_mode() if $opt{'assume-standby-mode'};
1364make_sure_prod() if $opt{'assume-prod'};
1365
1366## Create the list of databases we are going to connect to
1367my @targetdb = setup_target_databases();
1368
1369sub add_response {
1370
1371 my ($type,$msg) = @_;
1372
1373 $db->{host} ||= '';
1374
1375 if ($STANDBY) {
1376 $action_info->{$action}[0] = 1;
1377 }
1378
1379 if ($nohost) {
1380 push @{$type->{''}} => [$msg, length $nohost > 1 ? $nohost : ''];
1381 return;
1382 }
1383
1384 my $dbservice = $db->{dbservice};
1385 my $dbname = defined $db->{dbname} ? qq{DB "$db->{dbname}"} : '';
1386 my $dbhost = (!$db->{host} or $db->{host} eq '<none>') ? '' : qq{ (host:$db->{host})};
1387 my $dbport = defined $db->{port} ? ($db->{port} eq $opt{defaultport} ? '' : qq{ (port=$db->{port}) }) : '';
1388
1389 ## Same_schema gets some different output
1390 my $same_schema_header = '';
1391 if ($action eq 'same_schema') {
1392
1393 ## Pretty display of what exactly those numbers mean!
1394 my $number = 0;
1395 my $historical = 0;
1396 for my $row (@targetdb) {
1397 $number++;
1398 if (exists $row->{filename}) {
1399 $historical = 1;
1400 $same_schema_header .= sprintf "\nDB %s: File=%s\nDB %s: %s: %s %s: %s",
1401 $number,
1402 $row->{filename},
1403 $number,
1404 'Creation date',
1405 $row->{ctime},
1406 'CP version',
1407 $row->{cversion};
1408 }
1409 $same_schema_header .= sprintf "\nDB %s: %s%s%s%s%s",
1410 $number,
1411 defined $row->{dbservice} ? qq{dbservice=$row->{dbservice} } : '',
1412 defined $row->{port} ? qq{port=$row->{port} } : '',
1413 defined $row->{host} ? qq{host=$row->{host} } : '',
1414 defined $row->{dbname} ? qq{dbname=$row->{dbname} } : '',
1415 defined $row->{dbuser} ? qq{user=$row->{dbuser} } : '';
1416 $same_schema_header .= "\nDB $number: PG version: $row->{pgversion}";
1417 $same_schema_header .= "\nDB $number: Total objects: $row->{objects}";
1418 }
1419
1420 ## Databases
1421 $number = 1;
1422 my %dlist = map { ($_->{dbname} || ''), $number++; } @targetdb;
1423 if (keys %dlist > 1 and ! $historical) {
1424 my $dblist = join ',' => sort { $dlist{$a} <=> $dlist{$b} } keys %dlist;
1425 $dbname = qq{ (databases:$dblist)};
1426 }
1427 ## Hosts
1428 $number = 1;
1429 my %hostlist = map { ($_->{host} || ''), $number++; } @targetdb;
1430 if (keys %hostlist > 1 and ! $historical) {
1431 my $dblist = join ',' => sort { $hostlist{$a} <=> $hostlist{$b} } keys %hostlist;
1432 $dbhost = qq{ (hosts:$dblist)};
1433 }
1434 ## Ports
1435 $number = 1;
1436 my %portlist = map { ($_->{port} || ''), $number++; } @targetdb;
1437 if (keys %portlist > 1 and ! $historical) {
1438 my $dblist = join ',' => sort { $portlist{$a} <=> $portlist{$b} } keys %portlist;
1439 $dbport = qq{ (ports:$dblist)};
1440 }
1441 }
1442
1443 my $header = sprintf q{%s%s%s%s},
1444 ($action_info->{$action}[0] ? '' : (defined $dbservice and length $dbservice)) ?
1445 qq{service=$dbservice} : $dbname,
1446 (defined $db->{showschema} ? qq{ schema:$db->{showschema} } : ''),
1447 $dbhost,
1448 $dbport;
1449 $header =~ s/\s+$//;
1450 $header =~ s/^ //;
1451 my $perf = ($opt{showtime} and $db->{totaltime} and $action ne 'bloat') ? "time=$db->{totaltime}s" : '';
1452 if ($db->{perf}) {
1453 $db->{perf} =~ s/^ +//;
1454 if (length $same_schema_header) {
1455 $db->{perf} =~ s/^\n//;
1456 $db->{perf} = "$same_schema_header\n$db->{perf}";
1457 }
1458 $perf .= sprintf '%s%s', length($perf) ? ' ' : '', $db->{perf};
1459 }
1460
1461 ## Strip trailing semicolons as allowed by the Nagios spec
1462 ## But not for same_schema, where we might have (for example) a view def
1463 if ($action ne 'same_schema') {
1464 $perf =~ s/; / /;
1465 $perf =~ s/;$//;
1466 }
1467
1468 push @{$type->{$header}} => [$msg,$perf];
1469
1470 return;
1471
1472} ## end of add_response
1473
1474
1475sub add_unknown {
1476 my $msg = shift || $db->{error};
1477 $msg =~ s/[\r\n]\s*/\\n /g;
1478 $msg =~ s/\|/<PIPE>/g if $opt{showperf};
1479 add_response \%unknown, $msg;
1480}
1481sub add_critical {
1482 add_response \%critical, shift;
1483}
1484sub add_warning {
1485 add_response \%warning, shift;
1486}
1487sub add_ok {
1488 add_response \%ok, shift;
1489}
1490
1491
1492sub do_mrtg {
1493 ## Hashref of info to pass out for MRTG or stat
1494 my $arg = shift;
1495 my $one = $arg->{one} || 0;
1496 my $two = $arg->{two} || 0;
1497 if ($SIMPLE) {
1498 $one = $two if (length $two and $two > $one);
1499 if ($opt{transform} eq 'KB' and $one =~ /^\d+$/) {
1500 $one = int $one/(1024);
1501 }
1502 if ($opt{transform} eq 'MB' and $one =~ /^\d+$/) {
1503 $one = int $one/(1024*1024);
1504 }
1505 elsif ($opt{transform} eq 'GB' and $one =~ /^\d+$/) {
1506 $one = int $one/(1024*1024*1024);
1507 }
1508 elsif ($opt{transform} eq 'TB' and $one =~ /^\d+$/) {
1509 $one = int $one/(1024*1024*1024*1024);
1510 }
1511 elsif ($opt{transform} eq 'EB' and $one =~ /^\d+$/) {
1512 $one = int $one/(1024*1024*1024*1024*1024);
1513 }
1514 print "$one\n";
1515 }
1516 else {
1517 my $uptime = $arg->{uptime} || '';
1518 my $message = $arg->{msg} || '';
1519 print "$one\n$two\n$uptime\n$message\n";
1520 }
1521 exit 0;
1522}
1523
1524
1525sub bad_mrtg {
1526 my $msg = shift;
1527 $ERROR and ndie $ERROR;
1528 warn msgn('mrtg-fail', $action, $msg);
1529 exit 3;
1530}
1531
1532
1533sub do_mrtg_stats {
1534
1535 ## Show the two highest items for mrtg stats hash
1536
1537 my $msg = shift;
1538 defined $msg or ndie('unknown-error');
1539
1540 keys %stats or bad_mrtg($msg);
1541 my ($one,$two) = ('','');
1542 for (sort { $stats{$b} <=> $stats{$a} } keys %stats) {
1543 if ($one eq '') {
1544 $one = $stats{$_};
1545 $msg = exists $statsmsg{$_} ? $statsmsg{$_} : "DB: $_";
1546 next;
1547 }
1548 $two = $stats{$_};
1549 last;
1550 }
1551 do_mrtg({one => $one, two => $two, msg => $msg});
1552}
1553
1554sub make_sure_mode_is {
1555
1556 ## Requires $ENV{PGDATA} or --datadir
1557
1558 $db->{host} = '<none>';
1559
1560 ## Run pg_controldata, grab the mode
1561 $res = open_controldata();
1562
1563 my $regex = msg('checkmode-state');
1564 if ($res !~ /$regex\s*(.+)/) { ## no critic (ProhibitUnusedCapture)
1565 ## Just in case, check the English one as well
1566 $regex = msg_en('checkmode-state');
1567 if ($res !~ /$regex\s*(.+)/) {
1568 ndie msg('checkpoint-noregex');
1569 }
1570 }
1571 my $last = $1;
1572
1573 return $last;
1574
1575}
1576
1577sub make_sure_standby_mode {
1578
1579 ## Checks if database in standby mode
1580 ## Requires $ENV{PGDATA} or --datadir
1581
1582 my $last = make_sure_mode_is();
1583
1584 my $regex = msg('checkmode-recovery');
1585 if ($last =~ /$regex/) {
1586 $STANDBY = 1;
1587 }
1588
1589 return;
1590
1591} ## end of make_sure_standby_mode
1592
1593sub make_sure_prod {
1594
1595 ## Checks if database in production mode
1596 ## Requires $ENV{PGDATA} or --datadir
1597
1598 my $last = make_sure_mode_is();
1599
1600 my $regex = msg('checkmode-prod');
1601 if ($last =~ /$regex/) {
1602 $MASTER = 1;
1603 }
1604
1605 return;
1606
1607} ## end of make_sure_production_mode
1608
1609sub finishup {
1610
1611 ## Final output
1612 ## These are meant to be compact and terse: sometimes messages go to pagers
1613
1614 if ($MRTG) {
1615 ## Try hard to ferret out a message in case we short-circuited here
1616 my $msg = [[]];
1617 if (keys %critical) {
1618 ($msg) = values %critical;
1619 }
1620 elsif (keys %warning) {
1621 ($msg) = values %warning;
1622 }
1623 elsif (keys %ok) {
1624 ($msg) = values %ok;
1625 }
1626 elsif (keys %unknown) {
1627 ($msg) = values %unknown;
1628 }
1629 do_mrtg_stats($msg->[0][0]);
1630 }
1631
1632 $action =~ s/^\s*(\S+)\s*$/$1/;
1633 my $service = sprintf "%s$action", $FANCYNAME ? 'postgres_' : '';
1634 if (keys %critical or keys %warning or keys %ok or keys %unknown) {
1635 ## If in quiet mode, print nothing if all is ok
1636 if ($opt{quiet} and ! keys %critical and ! keys %warning and ! keys %unknown) {
1637 }
1638 else {
1639 printf '%s ', $YELLNAME ? uc $service : $service;
1640 }
1641 }
1642
1643 sub dumpresult {
1644 my ($type,$info) = @_;
1645 my $SEP = ' * ';
1646 ## Are we showing DEBUG_INFO?
1647 my $showdebug = 0;
1648 if ($DEBUGOUTPUT) {
1649 $showdebug = 1 if $DEBUGOUTPUT =~ /a/io
1650 or ($DEBUGOUTPUT =~ /c/io and $type eq 'c')
1651 or ($DEBUGOUTPUT =~ /w/io and $type eq 'w')
1652 or ($DEBUGOUTPUT =~ /o/io and $type eq 'o')
1653 or ($DEBUGOUTPUT =~ /u/io and $type eq 'u');
1654 }
1655 for (sort keys %$info) {
1656 printf '%s %s%s ',
1657 $_,
1658 $showdebug ? "[DEBUG: $DEBUG_INFO] " : '',
1659 join $SEP => map { $_->[0] } @{$info->{$_}};
1660 }
1661 if ($opt{showperf}) {
1662 my $pmsg = '';
1663 for (sort keys %$info) {
1664 my $m = sprintf '%s ', join ' ' => map { $_->[1] } @{$info->{$_}};
1665 $pmsg .= $m;
1666 }
1667 $pmsg =~ s/^\s+//;
1668 $pmsg and print "| $pmsg";
1669 }
1670 print "\n";
1671
1672 return;
1673
1674 }
1675
1676 if (keys %critical) {
1677 print 'CRITICAL: ';
1678 dumpresult(c => \%critical);
1679 exit 2;
1680 }
1681 if (keys %warning) {
1682 print 'WARNING: ';
1683 dumpresult(w => \%warning);
1684 exit 1;
1685 }
1686 if (keys %ok) {
1687 ## We print nothing if in quiet mode
1688 if (! $opt{quiet}) {
1689 print 'OK: ';
1690 dumpresult(o => \%ok);
1691 }
1692 exit 0;
1693 }
1694 if (keys %unknown) {
1695 print 'UNKNOWN: ';
1696 dumpresult(u => \%unknown);
1697 exit 3;
1698 }
1699
1700 die $USAGE;
1701
1702} ## end of finishup
1703
1704
1705## For options that take a size e.g. --critical="10 GB"
1706our $sizere = qr{^\s*(\d+\.?\d?)\s*([bkmgtepz])?\w*$}i; ## Don't care about the rest of the string
1707
1708## For options that take a time e.g. --critical="10 minutes" Fractions are allowed.
1709our $timere = qr{^\s*(\d+(?:\.\d+)?)\s*(\w*)\s*$}i;
1710
1711## For options that must be specified in seconds
1712our $timesecre = qr{^\s*(\d+)\s*(?:s(?:econd|ec)?)?s?\s*$};
1713
1714## For simple checksums:
1715our $checksumre = qr{^[a-f0-9]{32}$};
1716
1717## If in test mode, verify that we can run each requested action
1718our %testaction = (
1719 autovac_freeze => 'VERSION: 8.2',
1720 last_vacuum => 'ON: stats_row_level(<8.3) VERSION: 8.2',
1721 last_analyze => 'ON: stats_row_level(<8.3) VERSION: 8.2',
1722 last_autovacuum => 'ON: stats_row_level(<8.3) VERSION: 8.2',
1723 last_autoanalyze => 'ON: stats_row_level(<8.3) VERSION: 8.2',
1724 prepared_txns => 'VERSION: 8.1',
1725 database_size => 'VERSION: 8.1',
1726 disabled_triggers => 'VERSION: 8.1',
1727 relation_size => 'VERSION: 8.1',
1728 sequence => 'VERSION: 8.1',
1729 table_size => 'VERSION: 8.1',
1730 index_size => 'VERSION: 8.1',
1731 query_time => 'VERSION: 8.1',
1732 txn_time => 'VERSION: 8.3',
1733 wal_files => 'VERSION: 8.1',
1734 archive_ready => 'VERSION: 8.1',
1735 fsm_pages => 'VERSION: 8.2 MAX: 8.3',
1736 fsm_relations => 'VERSION: 8.2 MAX: 8.3',
1737 hot_standby_delay => 'VERSION: 9.0',
1738 listener => 'MAX: 8.4',
1739);
1740if ($opt{test}) {
1741 print msgn('testmode-start');
1742 my $info = run_command('SELECT name, setting FROM pg_settings');
1743 my %set; ## port, host, name, user
1744 for my $db (@{$info->{db}}) {
1745 if (exists $db->{fail}) {
1746 (my $err = $db->{error}) =~ s/\s*\n\s*/ \| /g;
1747 print msgn('testmode-fail', $db->{pname}, $err);
1748 next;
1749 }
1750 print msgn('testmode-ok', $db->{pname});
1751 for (@{ $db->{slurp} }) {
1752 $set{$_->{name}} = $_->{setting};
1753 }
1754 }
1755 for my $ac (split /\s+/ => $action) {
1756 my $limit = $testaction{lc $ac};
1757 next if ! defined $limit;
1758
1759 if ($limit =~ /VERSION: ((\d+)\.(\d+))/) {
1760 my ($rver,$rmaj,$rmin) = ($1,$2,$3);
1761 for my $db (@{$info->{db}}) {
1762 next unless exists $db->{ok};
1763 if ($set{server_version} !~ /((\d+)\.(\d+))/) {
1764 print msgn('testmode-nover', $db->{pname});
1765 next;
1766 }
1767 my ($sver,$smaj,$smin) = ($1,$2,$3);
1768 if ($smaj < $rmaj or ($smaj==$rmaj and $smin < $rmin)) {
1769 print msgn('testmode-norun', $ac, $db->{pname}, $rver, $sver);
1770 }
1771 $db->{version} = $sver;
1772 }
1773 }
1774
1775 if ($limit =~ /MAX: ((\d+)\.(\d+))/) {
1776 my ($rver,$rmaj,$rmin) = ($1,$2,$3);
1777 for my $db (@{$info->{db}}) {
1778 next unless exists $db->{ok};
1779 if ($set{server_version} !~ /((\d+)\.(\d+))/) {
1780 print msgn('testmode-nover', $db->{pname});
1781 next;
1782 }
1783 my ($sver,$smaj,$smin) = ($1,$2,$3);
1784 if ($smaj > $rmaj or ($smaj==$rmaj and $smin > $rmin)) {
1785 print msgn('testmode-norun', $ac, $db->{pname}, $rver, $sver);
1786 }
1787 }
1788 }
1789
1790 while ($limit =~ /\bON: (\w+)(?:\(([<>=])(\d+\.\d+)\))?/g) {
1791 my ($setting,$op,$ver) = ($1,$2||'',$3||0);
1792 for my $db (@{$info->{db}}) {
1793 next unless exists $db->{ok};
1794 if ($ver) {
1795 next if $op eq '<' and $db->{version} >= $ver;
1796 next if $op eq '>' and $db->{version} <= $ver;
1797 next if $op eq '=' and $db->{version} != $ver;
1798 }
1799 my $val = $set{$setting};
1800 if ($val ne 'on') {
1801 print msgn('testmode-noset', $ac, $db->{pname}, $setting);
1802 }
1803 }
1804 }
1805 }
1806 print msgn('testmode-end');
1807 exit 0;
1808}
1809
1810## Expand the list of included/excluded users into a standard format
1811our $USERWHERECLAUSE = '';
1812if ($opt{includeuser}) {
1813 my %userlist;
1814 for my $user (@{$opt{includeuser}}) {
1815 for my $u2 (split /,/ => $user) {
1816 $userlist{$u2}++;
1817 }
1818 }
1819 my $safename;
1820 if (1 == keys %userlist) {
1821 ($safename = each %userlist) =~ s/'/''/g;
1822 $USERWHERECLAUSE = " AND usename = '$safename'";
1823 }
1824 else {
1825 $USERWHERECLAUSE = ' AND usename IN (';
1826 for my $user (sort keys %userlist) {
1827 ($safename = $user) =~ s/'/''/g;
1828 $USERWHERECLAUSE .= "'$safename',";
1829 }
1830 chop $USERWHERECLAUSE;
1831 $USERWHERECLAUSE .= ')';
1832 }
1833}
1834elsif ($opt{excludeuser}) {
1835 my %userlist;
1836 for my $user (@{$opt{excludeuser}}) {
1837 for my $u2 (split /,/ => $user) {
1838 $userlist{$u2}++;
1839 }
1840 }
1841 my $safename;
1842 if (1 == keys %userlist) {
1843 ($safename = each %userlist) =~ s/'/''/g;
1844 $USERWHERECLAUSE = " AND usename <> '$safename'";
1845 }
1846 else {
1847 $USERWHERECLAUSE = ' AND usename NOT IN (';
1848 for my $user (sort keys %userlist) {
1849 ($safename = $user) =~ s/'/''/g;
1850 $USERWHERECLAUSE .= "'$safename',";
1851 }
1852 chop $USERWHERECLAUSE;
1853 $USERWHERECLAUSE .= ')';
1854 }
1855}
1856
1857## Check number of connections, compare to max_connections
1858check_backends() if $action eq 'backends';
1859
1860## Table and index bloat
1861check_bloat() if $action eq 'bloat';
1862
1863## Simple connection, warning or critical options
1864check_connection() if $action eq 'connection';
1865
1866## Check the commitratio of one or more databases
1867check_commitratio() if $action eq 'commitratio';
1868
1869## Check the hitratio of one or more databases
1870check_hitratio() if $action eq 'hitratio';
1871
1872## Check the size of one or more databases
1873check_database_size() if $action eq 'database_size';
1874
1875## Check local disk_space - local means it must be run from the same box!
1876check_disk_space() if $action eq 'disk_space';
1877
1878## Check the size of relations, or more specifically, tables and indexes
1879check_index_size() if $action eq 'index_size';
1880check_table_size() if $action eq 'table_size';
1881check_relation_size() if $action eq 'relation_size';
1882
1883## Check how long since the last full analyze
1884check_last_analyze() if $action eq 'last_analyze';
1885
1886## Check how long since the last full vacuum
1887check_last_vacuum() if $action eq 'last_vacuum';
1888
1889## Check how long since the last AUTOanalyze
1890check_last_analyze('auto') if $action eq 'last_autoanalyze';
1891
1892## Check how long since the last full AUTOvacuum
1893check_last_vacuum('auto') if $action eq 'last_autovacuum';
1894
1895## Check that someone is listening for a specific thing
1896check_listener() if $action eq 'listener';
1897
1898## Check number and type of locks
1899check_locks() if $action eq 'locks';
1900
1901## Logfile is being written to
1902check_logfile() if $action eq 'logfile';
1903
1904## Known query finishes in a good amount of time
1905check_query_runtime() if $action eq 'query_runtime';
1906
1907## Check the length of running queries
1908check_query_time() if $action eq 'query_time';
1909
1910## Verify that the settings are what we think they should be
1911check_settings_checksum() if $action eq 'settings_checksum';
1912
1913## Compare DB time to localtime, alert on number of seconds difference
1914check_timesync() if $action eq 'timesync';
1915
1916## Check for transaction ID wraparound in all databases
1917check_txn_wraparound() if $action eq 'txn_wraparound';
1918
1919## Compare DB versions. warning = just major.minor, critical = full string
1920check_version() if $action eq 'version';
1921
1922## Check the number of WAL files. warning and critical are numbers
1923check_wal_files() if $action eq 'wal_files';
1924
1925## Check the number of WAL files ready to archive. warning and critical are numbers
1926check_archive_ready() if $action eq 'archive_ready';
1927
1928## Check the replication delay in hot standby setup
1929check_hot_standby_delay() if $action eq 'hot_standby_delay';
1930
1931## Check the maximum transaction age of all connections
1932check_txn_time() if $action eq 'txn_time';
1933
1934## Check the maximum age of idle in transaction connections
1935check_txn_idle() if $action eq 'txn_idle';
1936
1937## Run a custom query
1938check_custom_query() if $action eq 'custom_query';
1939
1940## Test of replication
1941check_replicate_row() if $action eq 'replicate_row';
1942
1943## Compare database schemas
1944check_same_schema() if $action eq 'same_schema';
1945
1946## Check sequence values
1947check_sequence() if $action eq 'sequence';
1948
1949## See how close we are to autovacuum_freeze_max_age
1950check_autovac_freeze() if $action eq 'autovac_freeze';
1951
1952## See how many pages we have used up compared to max_fsm_pages
1953check_fsm_pages() if $action eq 'fsm_pages';
1954
1955## See how many relations we have used up compared to max_fsm_relations
1956check_fsm_relations() if $action eq 'fsm_relations';
1957
1958## Spit back info from the pg_stat_database table. Cacti only
1959check_dbstats() if $action eq 'dbstats';
1960
1961## Check how long since the last checkpoint
1962check_checkpoint() if $action eq 'checkpoint';
1963
1964## Check the Database System Identifier
1965check_cluster_id() if $action eq 'cluster_id';
1966
1967## Check for disabled triggers
1968check_disabled_triggers() if $action eq 'disabled_triggers';
1969
1970## Check for any prepared transactions
1971check_prepared_txns() if $action eq 'prepared_txns';
1972
1973## Make sure Slony is behaving
1974check_slony_status() if $action eq 'slony_status';
1975
1976## Verify that the pgbouncer settings are what we think they should be
1977check_pgbouncer_checksum() if $action eq 'pgbouncer_checksum';
1978
1979## Check the number of active clients in each pgbouncer pool
1980check_pgb_pool('cl_active') if $action eq 'pgb_pool_cl_active';
1981
1982## Check the number of waiting clients in each pgbouncer pool
1983check_pgb_pool('cl_waiting') if $action eq 'pgb_pool_cl_waiting';
1984
1985## Check the number of active server connections in each pgbouncer pool
1986check_pgb_pool('sv_active') if $action eq 'pgb_pool_sv_active';
1987
1988## Check the number of idle server connections in each pgbouncer pool
1989check_pgb_pool('sv_idle') if $action eq 'pgb_pool_sv_idle';
1990
1991## Check the number of used server connections in each pgbouncer pool
1992check_pgb_pool('sv_used') if $action eq 'pgb_pool_sv_used';
1993
1994## Check the number of tested server connections in each pgbouncer pool
1995check_pgb_pool('sv_tested') if $action eq 'pgb_pool_sv_tested';
1996
1997## Check the number of login server connections in each pgbouncer pool
1998check_pgb_pool('sv_login') if $action eq 'pgb_pool_sv_login';
1999
2000## Check the current maximum wait time for client connections in pgbouncer pools
2001check_pgb_pool('maxwait') if $action eq 'pgb_pool_maxwait';
2002
2003## Check how many clients are connected to pgbouncer compared to max_client_conn.
2004check_pgbouncer_backends() if $action eq 'pgbouncer_backends';
2005
2006check_pgagent_jobs() if $action eq 'pgagent_jobs';
2007
2008##
2009## Everything past here does not hit a Postgres database
2010##
2011$nohost = 1;
2012
2013## Check for new versions of check_postgres.pl
2014check_new_version_cp() if $action eq 'new_version_cp';
2015
2016## Check for new versions of Postgres
2017check_new_version_pg() if $action eq 'new_version_pg';
2018
2019## Check for new versions of Bucardo
2020check_new_version_bc() if $action eq 'new_version_bc';
2021
2022## Check for new versions of boxinfo
2023check_new_version_box() if $action eq 'new_version_box';
2024
2025## Check for new versions of tail_n_mail
2026check_new_version_tnm() if $action eq 'new_version_tnm';
2027
2028finishup();
2029
2030exit 0;
2031
2032
2033sub build_symlinks {
2034
2035 ## Create symlinks to most actions
2036 $ME =~ /postgres/
2037 or die msgn('symlink-name');
2038
2039 my $force = $action =~ /force/ ? 1 : 0;
2040 for my $action (sort keys %$action_info) {
2041 my $space = ' ' x ($longname - length $action);
2042 my $file = "check_postgres_$action";
2043 if (-l $file) {
2044 if (!$force) {
2045 my $source = readlink $file;
2046 print msgn('symlink-done', $file, $space, $source);
2047 next;
2048 }
2049 print msg('symlink-unlink', $file, $space);
2050 unlink $file or die msgn('symlink-fail1', $file, $!);
2051 }
2052 elsif (-e $file) {
2053 print msgn('symlink-exists', $file, $space);
2054 next;
2055 }
2056
2057 if (symlink $0, $file) {
2058 print msgn('symlink-create', $file);
2059 }
2060 else {
2061 print msgn('symlink-fail2', $file, $ME, $!);
2062 }
2063 }
2064
2065 exit 0;
2066
2067} ## end of build_symlinks
2068
2069
2070sub pretty_size {
2071
2072 ## Transform number of bytes to a SI display similar to Postgres' format
2073
2074 my $bytes = shift;
2075 my $rounded = shift || 0;
2076
2077 return "$bytes bytes" if $bytes < 10240;
2078
2079 my @unit = qw/kB MB GB TB PB EB YB ZB/;
2080
2081 for my $p (1..@unit) {
2082 if ($bytes <= 1024**$p) {
2083 $bytes /= (1024**($p-1));
2084 return $rounded ?
2085 sprintf ('%d %s', $bytes, $unit[$p-2]) :
2086 sprintf ('%.2f %s', $bytes, $unit[$p-2]);
2087 }
2088 }
2089
2090 return $bytes;
2091
2092} ## end of pretty_size
2093
2094
2095sub pretty_time {
2096
2097 ## Transform number of seconds to a more human-readable format
2098 ## First argument is number of seconds
2099 ## Second optional arg is highest transform: s,m,h,d,w
2100 ## If uppercase, it indicates to "round that one out"
2101
2102 my $sec = shift;
2103 my $tweak = shift || '';
2104
2105 ## Just seconds (< 2:00)
2106 if ($sec < 120 or $tweak =~ /s/) {
2107 return sprintf "$sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
2108 }
2109
2110 ## Minutes and seconds (< 60:00)
2111 if ($sec < 60*60 or $tweak =~ /m/) {
2112 my $min = int $sec / 60;
2113 $sec %= 60;
2114 my $ret = sprintf "$min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
2115 $sec and $tweak !~ /S/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
2116 return $ret;
2117 }
2118
2119 ## Hours, minutes, and seconds (< 48:00:00)
2120 if ($sec < 60*60*24*2 or $tweak =~ /h/) {
2121 my $hour = int $sec / (60*60);
2122 $sec -= ($hour*60*60);
2123 my $min = int $sec / 60;
2124 $sec -= ($min*60);
2125 my $ret = sprintf "$hour %s", $hour==1 ? msg('time-hour') : msg('time-hours');
2126 $min and $tweak !~ /M/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
2127 $sec and $tweak !~ /[SM]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
2128 return $ret;
2129 }
2130
2131 ## Days, hours, minutes, and seconds (< 28 days)
2132 if ($sec < 60*60*24*28 or $tweak =~ /d/) {
2133 my $day = int $sec / (60*60*24);
2134 $sec -= ($day*60*60*24);
2135 my $our = int $sec / (60*60);
2136 $sec -= ($our*60*60);
2137 my $min = int $sec / 60;
2138 $sec -= ($min*60);
2139 my $ret = sprintf "$day %s", $day==1 ? msg('time-day') : msg('time-days');
2140 $our and $tweak !~ /H/ and $ret .= sprintf " $our %s", $our==1 ? msg('time-hour') : msg('time-hours');
2141 $min and $tweak !~ /[HM]/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
2142 $sec and $tweak !~ /[HMS]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
2143 return $ret;
2144 }
2145
2146 ## Weeks, days, hours, minutes, and seconds (< 28 days)
2147 my $week = int $sec / (60*60*24*7);
2148 $sec -= ($week*60*60*24*7);
2149 my $day = int $sec / (60*60*24);
2150 $sec -= ($day*60*60*24);
2151 my $our = int $sec / (60*60);
2152 $sec -= ($our*60*60);
2153 my $min = int $sec / 60;
2154 $sec -= ($min*60);
2155 my $ret = sprintf "$week %s", $week==1 ? msg('time-week') : msg('time-weeks');
2156 $day and $tweak !~ /D/ and $ret .= sprintf " $day %s", $day==1 ? msg('time-day') : msg('time-days');
2157 $our and $tweak !~ /[DH]/ and $ret .= sprintf " $our %s", $our==1 ? msg('time-hour') : msg('time-hours');
2158 $min and $tweak !~ /[DHM]/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
2159 $sec and $tweak !~ /[DHMS]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
2160 return $ret;
2161
2162} ## end of pretty_time
2163
2164
2165sub run_command {
2166
2167 ## Run a command string against each of our databases using psql
2168 ## Optional args in a hashref:
2169 ## "failok" - don't report if we failed
2170 ## "fatalregex" - allow this FATAL regex through
2171 ## "target" - use this targetlist instead of generating one
2172 ## "timeout" - change the timeout from the default of $opt{timeout}
2173 ## "regex" - the query must match this or we throw an error
2174 ## "emptyok" - it's okay to not match any rows at all
2175 ## "version" - alternate SQL for different versions of Postgres
2176 ## "dbnumber" - connect with this specific entry from @targetdb
2177 ## "conninfo" - return the connection information string without doing anything
2178
2179 my $string = shift || '';
2180 my $arg = shift || {};
2181 my $info = { command => $string, db => [], hosts => 0 };
2182
2183 ## First of all check if the server in standby mode, if so end this
2184 ## with OK status.
2185
2186 if ($STANDBY) {
2187 $db->{'totaltime'} = '0.00';
2188 add_ok msg('mode-standby');
2189 if ($MRTG) {
2190 do_mrtg({one => 1});
2191 }
2192 finishup();
2193 exit 0;
2194 }
2195
2196 $VERBOSE >= 3 and warn qq{Starting run_command with: $string\n};
2197
2198 my (%host,$passfile,$passfh,$tempdir,$tempfile,$tempfh,$errorfile,$errfh);
2199 my $offset = -1;
2200
2201 ## The final list of targets has been set inside @targetdb
2202
2203 if (! @targetdb) {
2204 ndie msg('runcommand-nodb');
2205 }
2206
2207 ## Create a temp file to store our results
2208 my @tempdirargs = (CLEANUP => 1);
2209 if ($opt{tempdir}) {
2210 push @tempdirargs => 'DIR', $opt{tempdir};
2211 }
2212
2213 $tempdir = tempdir(@tempdirargs);
2214 ($tempfh,$tempfile) = tempfile('check_postgres_psql.XXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
2215
2216 ## Create another one to catch any errors
2217 ($errfh,$errorfile) = tempfile('check_postgres_psql_stderr.XXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
2218
2219 ## Mild cleanup of the query
2220 $string =~ s/^\s*(.+?)\s*$/$1/s;
2221
2222 ## Set a statement_timeout, as a last-ditch safety measure
2223 my $timeout = $arg->{timeout} || $opt{timeout};
2224 my $dbtimeout = $timeout * 1000;
2225 if ($action !~ /^pgb/) {
2226 $string = "BEGIN;SET statement_timeout=$dbtimeout;COMMIT;$string";
2227 }
2228
2229 ## Keep track of which database we are on, to allow dbnumber to work
2230 my $num = 0;
2231
2232 ## Loop through and run the command on each target database
2233 for $db (@targetdb) {
2234
2235 ## Skip this one if we are using dbnumber and this is not our choice
2236 $num++;
2237 if ($arg->{dbnumber} and $arg->{dbnumber} != $num) {
2238 next;
2239 }
2240
2241 ## Just to keep things clean:
2242 truncate $tempfh, 0;
2243 truncate $errfh, 0;
2244
2245 ## Store this target in the global target list
2246 push @{$info->{db}}, $db;
2247
2248 my @args = ('-q', '-t');
2249 if (defined $db->{dbservice} and length $db->{dbservice}) { ## XX Check for simple names
2250 $db->{pname} = "service=$db->{dbservice}";
2251 $ENV{PGSERVICE} = $db->{dbservice};
2252 }
2253 else {
2254 $db->{pname} = "port=$db->{port} host=$db->{host} db=$db->{dbname} user=$db->{dbuser}";
2255 }
2256
2257 ## If all we want is a connection string, give it and leave now
2258 if ($arg->{conninfo}) {
2259 return $db->{pname};
2260 }
2261
2262 defined $db->{dbname} and push @args, '-d', $db->{dbname};
2263 defined $db->{dbuser} and push @args, '-U', $db->{dbuser};
2264 defined $db->{port} and push @args => '-p', $db->{port};
2265 if ($db->{host} ne '<none>') {
2266 push @args => '-h', $db->{host};
2267 $host{$db->{host}}++; ## For the overall count
2268 }
2269
2270 if (defined $db->{dbpass} and length $db->{dbpass}) {
2271 ## Make a custom PGPASSFILE. Far better to simply use your own .pgpass of course
2272 ($passfh,$passfile) = tempfile('check_postgres.XXXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
2273 $VERBOSE >= 3 and warn msgn('runcommand-pgpass', $passfile);
2274 $ENV{PGPASSFILE} = $passfile;
2275 printf $passfh "%s:%s:%s:%s:%s\n",
2276 $db->{host} eq '<none>' ? '*' : $db->{host}, $db->{port}, $db->{dbname}, $db->{dbuser}, $db->{dbpass};
2277 close $passfh or ndie msg('file-noclose', $passfile, $!);
2278 }
2279
2280 push @args, '-o', $tempfile;
2281 push @args => '-x';
2282
2283 ## If we've got different SQL, use this first run to simply grab the version
2284 ## Then we'll use that info to pick the real query
2285 if ($arg->{version}) {
2286 if (!$db->{version}) {
2287 $arg->{versiononly} = 1;
2288 $arg->{oldstring} = $string;
2289 $string = 'SELECT version()';
2290 }
2291 else {
2292 $string = $arg->{oldstring} || $arg->{string};
2293 for my $row (@{$arg->{version}}) {
2294 if ($row !~ s/^([<>]?)(\d+\.\d+)\s+//) {
2295 ndie msg('die-badversion', $row);
2296 }
2297 my ($mod,$ver) = ($1||'',$2);
2298 if ($mod eq '>' and $db->{version} > $ver) {
2299 $string = $row;
2300 last;
2301 }
2302 if ($mod eq '<' and $db->{version} < $ver) {
2303 $string = $row;
2304 last;
2305 }
2306 if ($mod eq '' and $db->{version} eq $ver) {
2307 $string = $row;
2308 }
2309 }
2310 delete $arg->{version};
2311 $info->{command} = $string;
2312 }
2313 }
2314
2315 local $SIG{ALRM} = sub { die 'Timed out' };
2316 alarm 0;
2317
2318 push @args, '-c', $string;
2319
2320 $VERBOSE >= 3 and warn Dumper \@args;
2321
2322 my $start = $opt{showtime} ? [gettimeofday()] : 0;
2323 open my $oldstderr, '>&', \*STDERR or ndie msg('runcommand-nodupe');
2324 open STDERR, '>', $errorfile or ndie msg('runcommand-noerr');
2325 eval {
2326 alarm $timeout;
2327 $res = system $PSQL => @args;
2328 };
2329 my $err = $@;
2330 alarm 0;
2331 open STDERR, '>&', $oldstderr or ndie msg('runcommand-noerr');
2332 close $oldstderr or ndie msg('file-noclose', 'STDERR copy', $!);
2333 if ($err) {
2334 if ($err =~ /Timed out/) {
2335 ndie msg('runcommand-timeout', $timeout);
2336 }
2337 else {
2338 ndie msg('runcommand-err');
2339 }
2340 }
2341
2342 $db->{totaltime} = sprintf '%.2f', $opt{showtime} ? tv_interval($start) : 0;
2343
2344 if ($res) {
2345 $db->{fail} = $res;
2346 $VERBOSE >= 3 and !$arg->{failok} and warn msgn('runcommand-nosys', $res);
2347 seek $errfh, 0, 0;
2348 {
2349 local $/;
2350 $db->{error} = <$errfh> || '';
2351 $db->{error} =~ s/\s*$//;
2352 $db->{error} =~ s/^psql: //;
2353 $ERROR = $db->{error};
2354 }
2355
2356 if ($db->{error} =~ /FATAL/) {
2357 ## If we are just trying to connect, this should be a normal error
2358 if ($action eq 'connection') {
2359 $info->{fatal} = 1;
2360 return $info;
2361 }
2362
2363 if (exists $arg->{fatalregex} and $db->{error} =~ /$arg->{fatalregex}/) {
2364 $info->{fatalregex} = $db->{error};
2365 next;
2366 }
2367 else {
2368 ndie "$db->{error}";
2369 }
2370 }
2371
2372 elsif ($db->{error} =~ /statement timeout/) {
2373 ndie msg('runcommand-timeout', $timeout);
2374 }
2375
2376 if ($db->{fail} and !$arg->{failok} and !$arg->{noverify}) {
2377
2378 ## Check if problem is due to backend being too old for this check
2379 verify_version();
2380
2381 if (exists $db->{error}) {
2382 ndie $db->{error};
2383 }
2384
2385 add_unknown;
2386 ## Remove it from the returned hash
2387 pop @{$info->{db}};
2388 }
2389 }
2390 else {
2391 seek $tempfh, 0, 0;
2392 {
2393 local $/;
2394 $db->{slurp} = <$tempfh>;
2395 }
2396 $db->{ok} = 1;
2397
2398 ## Unfortunately, psql outputs "(No rows)" even with -t and -x
2399 $db->{slurp} = '' if ! defined $db->{slurp} or index($db->{slurp},'(')==0;
2400
2401 ## Allow an empty query (no matching rows) if requested
2402 if ($arg->{emptyok} and $db->{slurp} =~ /^\s*$/o) {
2403 $arg->{emptyok2} = 1;
2404 }
2405 ## If we just want a version, grab it and redo
2406 if ($arg->{versiononly}) {
2407 if ($db->{error}) {
2408 ndie $db->{error};
2409 }
2410 if ($db->{slurp} !~ /(\d+\.\d+)/) {
2411 ndie msg('die-badversion', $db->{slurp});
2412 }
2413 $db->{version} = $1;
2414 $db->{ok} = 0;
2415 delete $arg->{versiononly};
2416 ## Remove this from the returned hash
2417 pop @{$info->{db}};
2418 redo;
2419 }
2420
2421 ## If we were provided with a regex, check and bail if it fails
2422 if ($arg->{regex} and ! $arg->{emptyok2}) {
2423 if ($db->{slurp} !~ $arg->{regex}) {
2424 ## Check if problem is due to backend being too old for this check
2425
2426 verify_version();
2427
2428 add_unknown msg('invalid-query', $db->{slurp});
2429
2430 finishup();
2431 exit 0;
2432 }
2433 }
2434
2435 ## Transform psql output into an arrayref of hashes
2436 my @stuff;
2437 my $lnum = 0;
2438 my $lastval;
2439 for my $line (split /\n/ => $db->{slurp}) {
2440
2441 if (index($line,'-')==0) {
2442 $lnum++;
2443 next;
2444 }
2445 if ($line =~ /^([\?\w]+)\s+\| (.*)/) {
2446 $stuff[$lnum]{$1} = $2;
2447 $lastval = $1;
2448 }
2449 elsif ($line =~ /^QUERY PLAN\s+\| (.*)/) {
2450 $stuff[$lnum]{queryplan} = $1;
2451 $lastval = 'queryplan';
2452 }
2453 elsif ($line =~ /^\s+: (.*)/) {
2454 $stuff[$lnum]{$lastval} .= "\n$1";
2455 }
2456 elsif ($line =~ /^\s+\| (.+)/) {
2457 $stuff[$lnum]{$lastval} .= "\n$1";
2458 }
2459 ## No content: can happen in the source of functions, for example
2460 elsif ($line =~ /^\s+\|\s+$/) {
2461 $stuff[$lnum]{$lastval} .= "\n";
2462 }
2463 else {
2464 my $msg = msg('no-parse-psql');
2465 warn "$msg\n";
2466 $msg = msg('bug-report');
2467 warn "$msg\n";
2468 my $cline = (caller)[2];
2469 my $args = join ' ' => @args;
2470 warn "Version: $VERSION\n";
2471 warn "Action: $action\n";
2472 warn "Calling line: $cline\n";
2473 warn "Output: $line\n";
2474 $args =~ s/ -c (.+)/-c "$1"/s;
2475 warn "Command: $PSQL $args\n";
2476 ## Last thing is to see if we can grab the PG version
2477 if (! $opt{stop_looping}) {
2478 ## Just in case...
2479 $opt{stop_looping} = 1;
2480 my $linfo = run_command('SELECT version() AS version');
2481 (my $v = $linfo->{db}[0]{slurp}[0]{version}) =~ s/(\w+ \S+).+/$1/;
2482 warn "Postgres version: $v\n";
2483 }
2484 exit 1;
2485 }
2486 }
2487 $db->{slurp} = \@stuff;
2488 } ## end valid system call
2489
2490 } ## end each database
2491
2492 close $errfh or ndie msg('file-noclose', $errorfile, $!);
2493 close $tempfh or ndie msg('file-noclose', $tempfile, $!);
2494
2495 eval { File::Temp::cleanup(); };
2496
2497 $info->{hosts} = keys %host;
2498
2499 $VERBOSE >= 3 and warn Dumper $info;
2500
2501 if ($DEBUGOUTPUT) {
2502 if (defined $info->{db} and defined $info->{db}[0]{slurp}) {
2503 $DEBUG_INFO = $info->{db}[0]{slurp};
2504 $DEBUG_INFO =~ s/\n/\\n/g;
2505 $DEBUG_INFO =~ s/\|/<SEP>/g;
2506 }
2507 }
2508
2509 return $info;
2510
2511} ## end of run_command
2512
2513
2514sub setup_target_databases {
2515
2516 ## Build a list of all databases to connect to.
2517 ## Returns a list of all such databases with connection information:
2518 ## -- dbuser, --dbpass, --dbservice, --port, --dbname, --host
2519 ##
2520 ## Items are determined by host, port, and db arguments
2521 ## Multi-args are grouped together: host, port, dbuser, dbpass
2522 ## Groups are kept together for first pass
2523 ## The final arg in a group is passed on
2524 ##
2525 ## Examples:
2526 ## --host=a,b --port=5433 --db=c
2527
2528 ## Connects twice to port 5433, using database c, to hosts a and b
2529 ## a-5433-c b-5433-c
2530 ##
2531 ## --host=a,b --port=5433 --db=c,d
2532 ## Connects four times: a-5433-c a-5433-d b-5433-c b-5433-d
2533 ##
2534 ## --host=a,b --host=foo --port=1234 --port=5433 --db=e,f
2535 ## Connects six times: a-1234-e a-1234-f b-1234-e b-1234-f foo-5433-e foo-5433-f
2536 ##
2537 ## --host=a,b --host=x --port=5432,5433 --dbuser=alice --dbuser=bob --db=baz
2538 ## Connects three times: a-5432-alice-baz b-5433-alice-baz x-5433-bob-baz
2539
2540 ## Returns a list of targets as a hashref
2541
2542 my $arg = shift || {};
2543
2544 ## The final list of targets:
2545 my @target;
2546
2547 ## Default connection options
2548 my $conn =
2549 {
2550 host => [$ENV{PGHOST} || '<none>'],
2551 port => [$ENV{PGPORT} || $opt{defaultport}],
2552 dbname => [$ENV{PGDATABASE} || $opt{defaultdb}],
2553 dbuser => [$ENV{PGUSER} || $opt{defaultuser}],
2554 dbpass => [$ENV{PGPASSWORD} || ''],
2555 dbservice => [''],
2556 };
2557
2558 ## Don't set any default values if a service is being used
2559 if (defined $opt{dbservice} and defined $opt{dbservice}->[0] and length $opt{dbservice}->[0]) {
2560 $conn->{dbname} = [];
2561 $conn->{port} = [];
2562 $conn->{dbuser} = [];
2563 }
2564
2565 ## If we were passed in a target, use that and move on
2566 if (exists $arg->{target}) {
2567 ## Make a copy, in case we are passed in a ref
2568 my $newtarget;
2569 for my $key (keys %$conn) {
2570 $newtarget->{$key} = exists $arg->{target}{$key} ? $arg->{target}{$key} : $conn->{$key};
2571 }
2572 return [$newtarget];
2573 }
2574
2575 ## Global count of total places we are connecting to
2576 ## We don't mess with this if using {target} above
2577 $opt{numdbs} = 0;
2578
2579 ## The current group number we are looking at
2580 my $group_num = 0;
2581
2582 GROUP: {
2583
2584 ## This level controls a "group" of targets
2585
2586 ## Start bubbling all our targets into other stuff
2587 my %group;
2588 my $found_new_var = 0;
2589
2590 for my $v (keys %$conn) { ## For each connection var such as port, host...
2591 my $vname = $v;
2592
2593 ## Check if something exists at the current slot number for this var
2594 if (defined $opt{$v}->[$group_num]) {
2595
2596 my $new = $opt{$v}->[$group_num];
2597
2598 ## Strip out whitespace unless this is a service or host
2599 $new =~ s/\s+//g unless $vname eq 'dbservice' or $vname eq 'host';
2600
2601 ## Set this as the new default for this connection var moving forward
2602 $conn->{$vname} = [split /,/ => $new];
2603
2604 ## Make a note that we found something new this round
2605 $found_new_var = 1;
2606 }
2607
2608 $group{$vname} = $conn->{$vname};
2609 }
2610
2611 ## If we found nothing new, we must be done building our groups
2612 last GROUP if ! $found_new_var and @target;
2613
2614 $group_num++;
2615
2616 ## Now break the newly created group into individual targets
2617 my $tbin = 0;
2618 TARGET: {
2619 my $foundtarget = 0;
2620 my %temptarget;
2621 for my $g (keys %group) {
2622 if (defined $group{$g}->[$tbin]) {
2623 $conn->{$g} = [$group{$g}->[$tbin]];
2624 $foundtarget = 1;
2625 }
2626 $temptarget{$g} = $conn->{$g}[0];
2627 }
2628
2629 ## Leave if nothing new
2630 last TARGET if ! $foundtarget;
2631
2632 ## Add to our master list
2633 push @target => \%temptarget;
2634
2635 $tbin++;
2636
2637 redo TARGET;
2638
2639 } ## end TARGET
2640
2641 last GROUP if ! $found_new_var;
2642
2643 redo GROUP;
2644
2645 } ## end GROUP
2646
2647 return @target;
2648
2649} ## end of setup_target_databases
2650
2651
2652sub verify_version {
2653
2654 ## Check if the backend can handle the current action
2655 my $limit = $testaction{lc $action} || '';
2656
2657 my $versiononly = shift || 0;
2658
2659 return if ! $limit and ! $versiononly;
2660
2661 ## We almost always need the version, so just grab it for any limitation
2662 $SQL = q{SELECT setting FROM pg_settings WHERE name = 'server_version'};
2663 my $oldslurp = $db->{slurp} || '';
2664 my $info = run_command($SQL, {noverify => 1});
2665 if (defined $info->{db}[0]
2666 and exists $info->{db}[0]{error}
2667 and defined $info->{db}[0]{error}
2668 ) {
2669 ndie $info->{db}[0]{error};
2670 }
2671
2672 if (!defined $info->{db}[0] or $info->{db}[0]{slurp}[0]{setting} !~ /((\d+)\.(\d+))/) {
2673 ndie msg('die-badversion', $SQL);
2674 }
2675 my ($sver,$smaj,$smin) = ($1,$2,$3);
2676
2677 if ($versiononly) {
2678 return $sver;
2679 }
2680
2681 if ($limit =~ /VERSION: ((\d+)\.(\d+))/) {
2682 my ($rver,$rmaj,$rmin) = ($1,$2,$3);
2683 if ($smaj < $rmaj or ($smaj==$rmaj and $smin < $rmin)) {
2684 ndie msg('die-action-version', $action, $rver, $sver);
2685 }
2686 }
2687
2688 while ($limit =~ /\bON: (\w+)(?:\(([<>=])(\d+\.\d+)\))?/g) {
2689 my ($setting,$op,$ver) = ($1,$2||'',$3||0);
2690 if ($ver) {
2691 next if $op eq '<' and $sver >= $ver;
2692 next if $op eq '>' and $sver <= $ver;
2693 next if $op eq '=' and $sver != $ver;
2694 }
2695
2696 $SQL = qq{SELECT setting FROM pg_settings WHERE name = '$setting'};
2697 my $info2 = run_command($SQL);
2698 if (!defined $info2->{db}[0]) {
2699 ndie msg('die-nosetting', $setting);
2700 }
2701 my $val = $info2->{db}[0]{slurp}[0]{setting};
2702 if ($val !~ /^\s*on\b/) {
2703 ndie msg('die-noset', $action, $setting);
2704 }
2705 }
2706
2707 $db->{slurp} = $oldslurp;
2708 return;
2709
2710} ## end of verify_version
2711
2712
2713sub size_in_bytes { ## no critic (RequireArgUnpacking)
2714
2715 ## Given a number and a unit, return the number of bytes.
2716 ## Defaults to bytes
2717
2718 my ($val,$unit) = ($_[0],lc substr($_[1]||'s',0,1));
2719 return $val * ($unit eq 'b' ? 1 : $unit eq 'k' ? 1024 : $unit eq 'm' ? 1024**2 :
2720 $unit eq 'g' ? 1024**3 : $unit eq 't' ? 1024**4 :
2721 $unit eq 'p' ? 1024**5 : $unit eq 'e' ? 1024**6 :
2722 $unit eq 'z' ? 1024**7 : 1);
2723
2724} ## end of size_in_bytes
2725
2726
2727sub size_in_seconds {
2728
2729 my ($string,$type) = @_;
2730
2731 return '' if ! length $string;
2732 if ($string !~ $timere) {
2733 ndie msg('die-badtime', $type, substr($type,0,1));
2734 }
2735 my ($val,$unit) = ($1,lc substr($2||'s',0,1));
2736 my $tempval = sprintf '%.9f', $val * (
2737 $unit eq 's' ? 1 :
2738 $unit eq 'm' ? 60 :
2739 $unit eq 'h' ? 3600 :
2740 $unit eq 'd' ? 86400 :
2741 $unit eq 'w' ? 604800 :
2742 $unit eq 'y' ? 31536000 :
2743 ndie msg('die-badtime', $type, substr($type,0,1))
2744 );
2745 $tempval =~ s/0+$//;
2746 $tempval = int $tempval if $tempval =~ /\.$/;
2747 return $tempval;
2748
2749} ## end of size_in_seconds
2750
2751
2752sub skip_item {
2753
2754 ## Determine if something should be skipped due to inclusion/exclusion options
2755 ## Exclusion checked first: inclusion can pull it back in.
2756 my $name = shift;
2757 my $schema = shift || '';
2758
2759 my $stat = 0;
2760 ## Is this excluded?
2761 if (defined $opt{exclude}) {
2762 $stat = 1;
2763 for (@{$opt{exclude}}) {
2764 for my $ex (split /\s*,\s*/o => $_) {
2765 if ($ex =~ s/\.$//) {
2766 if ($ex =~ s/^~//) {
2767 ($stat += 2 and last) if $schema =~ /$ex/;
2768 }
2769 else {
2770 ($stat += 2 and last) if $schema eq $ex;
2771 }
2772 }
2773 elsif ($ex =~ s/^~//) {
2774 ($stat += 2 and last) if $name =~ /$ex/;
2775 }
2776 else {
2777 ($stat += 2 and last) if $name eq $ex;
2778 }
2779 }
2780 }
2781 }
2782 if (defined $opt{include}) {
2783 $stat += 4;
2784 for (@{$opt{include}}) {
2785 for my $in (split /\s*,\s*/o => $_) {
2786 if ($in =~ s/\.$//) {
2787 if ($in =~ s/^~//) {
2788 ($stat += 8 and last) if $schema =~ /$in/;
2789 }
2790 else {
2791 ($stat += 8 and last) if $schema eq $in;
2792 }
2793 }
2794 elsif ($in =~ s/^~//) {
2795 ($stat += 8 and last) if $name =~ /$in/;
2796 }
2797 else {
2798 ($stat += 8 and last) if $name eq $in;
2799 }
2800 }
2801 }
2802 }
2803
2804 ## Easiest to state the cases when we DO skip:
2805 return 1 if
2806 3 == $stat ## exclude matched, no inclusion checking
2807 or 4 == $stat ## include check only, no match
2808 or 7 == $stat; ## exclude match, no inclusion match
2809
2810 return 0;
2811
2812} ## end of skip_item
2813
2814
2815sub validate_range {
2816
2817 ## Valid that warning and critical are set correctly.
2818 ## Returns new values of both
2819
2820 my $arg = shift;
2821 defined $arg and ref $arg eq 'HASH' or ndie qq{validate_range must be called with a hashref\n};
2822
2823 return ('','') if $MRTG and !$arg->{forcemrtg};
2824
2825 my $type = $arg->{type} or ndie qq{validate_range must be provided a 'type'\n};
2826
2827 ## The 'default default' is an empty string, which should fail all mandatory tests
2828 ## We only set the 'arg' default if neither option is provided.
2829 my $warning = exists $opt{warning} ? $opt{warning} :
2830 exists $opt{critical} ? '' : $arg->{default_warning} || '';
2831 my $critical = exists $opt{critical} ? $opt{critical} :
2832 exists $opt{warning} ? '' : $arg->{default_critical} || '';
2833
2834 if ('string' eq $type) {
2835 ## Don't use this unless you have to
2836 }
2837 elsif ('seconds' eq $type) {
2838 if (length $warning) {
2839 if ($warning !~ $timesecre) {
2840 ndie msg('range-seconds', 'warning');
2841 }
2842 $warning = $1;
2843 }
2844 if (length $critical) {
2845 if ($critical !~ $timesecre) {
2846 ndie msg('range-seconds', 'critical')
2847 }
2848 $critical = $1;
2849 if (!$arg->{any_warning} and length $warning and $warning > $critical) {
2850 ndie msg('range-warnbigtime', $warning, $critical);
2851 }
2852 }
2853 }
2854 elsif ('time' eq $type) {
2855 $critical = size_in_seconds($critical, 'critical');
2856 $warning = size_in_seconds($warning, 'warning');
2857 if (! length $critical and ! length $warning) {
2858 ndie msg('range-notime');
2859 }
2860 if (!$arg->{any_warning} and length $warning and length $critical and $warning > $critical) {
2861 ndie msg('range-warnbigtime', $warning, $critical);
2862 }
2863 }
2864 elsif ('version' eq $type) {
2865 my $msg = msg('range-version');
2866 if (length $warning and $warning !~ /^\d+\.\d+\.?[\d\w]*$/) {
2867 ndie msg('range-badversion', 'warning', $msg);
2868 }
2869 if (length $critical and $critical !~ /^\d+\.\d+\.?[\d\w]*$/) {
2870 ndie msg('range-badversion', 'critical', $msg);
2871 }
2872 if (! length $critical and ! length $warning) {
2873 ndie msg('range-noopt-orboth');
2874 }
2875 }
2876 elsif ('size' eq $type) {
2877 if (length $critical) {
2878 if ($critical !~ $sizere) {
2879 ndie msg('range-badsize', 'critical');
2880 }
2881 $critical = size_in_bytes($1,$2);
2882 }
2883 if (length $warning) {
2884 if ($warning !~ $sizere) {
2885 ndie msg('range-badsize', 'warning');
2886 }
2887 $warning = size_in_bytes($1,$2);
2888 if (!$arg->{any_warning} and length $critical and $warning > $critical) {
2889 ndie msg('range-warnbigsize', $warning, $critical);
2890 }
2891 }
2892 elsif (!length $critical) {
2893 ndie msg('range-nosize');
2894 }
2895 }
2896 elsif ($type =~ /integer/) {
2897 $warning =~ s/_//g;
2898 if (length $warning and $warning !~ /^[-+]?\d+$/) {
2899 ndie $type =~ /positive/ ? msg('range-int-pos', 'warning') : msg('range-int', 'warning');
2900 }
2901 elsif (length $warning and $type =~ /positive/ and $warning <= 0) {
2902 ndie msg('range-int-pos', 'warning');
2903 }
2904
2905 $critical =~ s/_//g;
2906 if (length $critical and $critical !~ /^[-+]?\d+$/) {
2907 ndie $type =~ /positive/ ? msg('range-int-pos', 'critical') : msg('range-int', 'critical');
2908 }
2909 elsif (length $critical and $type =~ /positive/ and $critical <= 0) {
2910 ndie msg('range-int-pos', 'critical');
2911 }
2912
2913 if (length $warning
2914 and length $critical
2915 and (
2916 ($opt{reverse} and $warning < $critical)
2917 or
2918 (!$opt{reverse} and $warning > $critical)
2919 )
2920 ) {
2921 ndie msg('range-warnbig');
2922 }
2923 if ($type !~ /string/) {
2924 $warning = int $warning if length $warning;
2925 $critical = int $critical if length $critical;
2926 }
2927 }
2928 elsif ('restringex' eq $type) {
2929 if (! length $critical and ! length $warning) {
2930 ndie msg('range-noopt-one');
2931 }
2932 if (length $critical and length $warning) {
2933 ndie msg('range-noopt-only');
2934 }
2935 my $string = length $critical ? $critical : $warning;
2936 my $regex = ($string =~ s/^~//) ? '~' : '=';
2937 $string =~ /^\w+$/ or ndie msg('invalid-option');
2938 }
2939 elsif ('percent' eq $type) {
2940 if (length $critical) {
2941 if ($critical !~ /^(\d+)\%$/) {
2942 ndie msg('range-badpercent', 'critical');
2943 }
2944 $critical = $1;
2945 }
2946 if (length $warning) {
2947 if ($warning !~ /^(\d+)\%$/) {
2948 ndie msg('range-badpercent', 'warning');
2949 }
2950 $warning = $1;
2951 }
2952 }
2953 elsif ('size or percent' eq $type) {
2954 if (length $critical) {
2955 if ($critical =~ $sizere) {
2956 $critical = size_in_bytes($1,$2);
2957 }
2958 elsif ($critical !~ /^\d+\%$/) {
2959 ndie msg('range-badpercsize', 'critical');
2960 }
2961 }
2962 if (length $warning) {
2963 if ($warning =~ $sizere) {
2964 $warning = size_in_bytes($1,$2);
2965 }
2966 elsif ($warning !~ /^\d+\%$/) {
2967 ndie msg('range-badpercsize', 'warning');
2968 }
2969 }
2970 elsif (! length $critical) {
2971 ndie msg('range-noopt-size');
2972 }
2973 }
2974 elsif ('checksum' eq $type) {
2975 if (length $critical and $critical !~ $checksumre and $critical ne '0') {
2976 ndie msg('range-badcs', 'critical');
2977 }
2978 if (length $warning and $warning !~ $checksumre) {
2979 ndie msg('range-badcs', 'warning');
2980 }
2981 }
2982 elsif ('multival' eq $type) { ## Simple number, or foo=#;bar=#
2983 ## Note: only used for check_locks
2984 my %err;
2985 while ($critical =~ /(\w+)\s*=\s*(\d+)/gi) {
2986 my ($name,$val) = (lc $1,$2);
2987 $name =~ s/lock$//;
2988 $err{$name} = $val;
2989 }
2990 if (keys %err) {
2991 $critical = \%err;
2992 }
2993 elsif (length $critical and $critical =~ /^(\d+)$/) {
2994 $err{total} = $1;
2995 $critical = \%err;
2996 }
2997 elsif (length $critical) {
2998 ndie msg('range-badlock', 'critical');
2999 }
3000 my %warn;
3001 while ($warning =~ /(\w+)\s*=\s*(\d+)/gi) {
3002 my ($name,$val) = (lc $1,$2);
3003 $name =~ s/lock$//;
3004 $warn{$name} = $val;
3005 }
3006 if (keys %warn) {
3007 $warning = \%warn;
3008 }
3009 elsif (length $warning and $warning =~ /^(\d+)$/) {
3010 $warn{total} = $1;
3011 $warning = \%warn;
3012 }
3013 elsif (length $warning) {
3014 ndie msg('range-badlock', 'warning');
3015 }
3016 }
3017 elsif ('cacti' eq $type) { ## Takes no args, just dumps data
3018 if (length $warning or length $critical) {
3019 ndie msg('range-cactionly');
3020 }
3021 }
3022 else {
3023 ndie msg('range-badtype', $type);
3024 }
3025
3026 if ($arg->{both}) {
3027 if (! length $warning or ! length $critical) {
3028 ndie msg('range-noopt-both');
3029 }
3030 }
3031 if ($arg->{leastone}) {
3032 if (! length $warning and ! length $critical) {
3033 ndie msg('range-noopt-one');
3034 }
3035 }
3036 elsif ($arg->{onlyone}) {
3037 if (length $warning and length $critical) {
3038 ndie msg('range-noopt-only');
3039 }
3040 if (! length $warning and ! length $critical) {
3041 ndie msg('range-noopt-one');
3042 }
3043 }
3044
3045 return ($warning,$critical);
3046
3047} ## end of validate_range
3048
3049
3050sub validate_size_or_percent_with_oper {
3051
3052 my $arg = shift || {};
3053 ndie qq{validate_range must be called with a hashref\n}
3054 unless ref $arg eq 'HASH';
3055
3056 my $warning = exists $opt{warning} ? $opt{warning} :
3057 exists $opt{critical} ? '' : $arg->{default_warning} || '';
3058 my $critical = exists $opt{critical} ? $opt{critical} :
3059 exists $opt{warning} ? '' : $arg->{default_critical} || '';
3060
3061 ndie msg('range-noopt-size') unless length $critical || length $warning;
3062 my @subs;
3063 for my $val ($warning, $critical) {
3064 if ($val =~ /^(.+?)\s([&|]{2}|and|or)\s(.+)$/i) {
3065 my ($l, $op, $r) = ($1, $2, $3);
3066 local $opt{warning} = $l;
3067 local $opt{critical} = 0;
3068 ($l) = validate_range({ type => 'size or percent' });
3069 $opt{warning} = $r;
3070 ($r) = validate_range({ type => 'size or percent' });
3071 if ($l =~ s/%$//) {
3072 ($l, $r) = ($r, $l);
3073 }
3074 else {
3075 $r =~ s/%$//;
3076 }
3077 push @subs, $op eq '&&' || lc $op eq 'and' ? sub {
3078 $_[0] >= $l && $_[1] >= $r;
3079 } : sub {
3080 $_[0] >= $l || $_[1] >= $r;
3081 };
3082 }
3083 else {
3084 local $opt{warning} = $val;
3085 local $opt{critical} = 0;
3086 my ($v) = validate_range({ type => 'size or percent' });
3087 push @subs, !length $v ? sub { 0 }
3088 : $v =~ s/%$// ? sub { $_[1] >= $v }
3089 : sub { $_[0] >= $v };
3090 }
3091 }
3092
3093 return @subs;
3094
3095} ## end of validate_size_or_percent_with_oper
3096
3097
3098sub validate_integer_for_time {
3099
3100 my $arg = shift || {};
3101 ndie qq{validate_integer_for_time must be called with a hashref\n}
3102 unless ref $arg eq 'HASH';
3103
3104 my $warning = exists $opt{warning} ? $opt{warning} :
3105 exists $opt{critical} ? '' : $arg->{default_warning} || '';
3106 my $critical = exists $opt{critical} ? $opt{critical} :
3107 exists $opt{warning} ? '' : $arg->{default_critical} || '';
3108 ndie msg('range-nointfortime', 'critical') unless length $critical or length $warning;
3109
3110 my @ret;
3111 for my $spec ([ warning => $warning], [critical => $critical]) {
3112 my ($level, $val) = @{ $spec };
3113 if (length $val) {
3114 if ($val =~ /^(.+?)\sfor\s(.+)$/i) {
3115 my ($int, $time) = ($1, $2);
3116
3117 # Integer first, time second.
3118 ($int, $time) = ($time, $int)
3119 if $int =~ /[a-zA-Z]$/ || $time =~ /^[-+]\d+$/;
3120
3121 # Determine the values.
3122 $time = size_in_seconds($time, $level);
3123 ndie msg('range-int', $level) if $time !~ /^[-+]?\d+$/;
3124 push @ret, int $int, $time;
3125 }
3126 else {
3127 # Disambiguate int from time int by sign.
3128 if ($val =~ /^[-+]\d+$/) {
3129 ndie msg('range-int', $level) if $val !~ /^[-+]?\d+$/;
3130 push @ret, int $val, '';
3131 }
3132 else {
3133 # Assume time for backwards compatibility.
3134 push @ret, '', size_in_seconds($val, $level);
3135 }
3136 }
3137 }
3138 else {
3139 push @ret, '', '';
3140 }
3141 }
3142
3143 return @ret;
3144
3145} ## end of validate_integer_for_time
3146
3147
3148sub perfname {
3149
3150 ## Return a safe label name for Nagios performance data
3151 my $name = shift;
3152
3153 my $escape = 0;
3154
3155 $name =~ s/'/''/g and $escape++;
3156
3157 if ($escape or index($name, ' ') >=0) {
3158 $name = qq{'$name'};
3159 }
3160
3161 return $name;
3162
3163} ## end of perfname;
3164
3165
3166sub open_controldata {
3167 ## Requires $ENV{PGDATA} or --datadir
3168
3169 ## Find the data directory, make sure it exists
3170 my $dir = $opt{datadir} || $ENV{PGDATA};
3171
3172 if (!defined $dir or ! length $dir) {
3173 ndie msg('checkpoint-nodir');
3174 }
3175
3176 if (! -d $dir) {
3177 ndie msg('checkpoint-baddir', $dir);
3178 }
3179
3180 ## Run pg_controldata
3181 ## We still catch deprecated option
3182 my $pgc;
3183 if (defined $ENV{PGCONTROLDATA} and length $ENV{PGCONTROLDATA}) {
3184 # ndie msg('depr-pgcontroldata');
3185 $pgc = "$ENV{PGCONTROLDATA}";
3186 }
3187 else {
3188 $pgc = (defined $PGBINDIR) ? "$PGBINDIR/pg_controldata" : 'pg_controldata';
3189 chomp($pgc = qx{which "$pgc"});
3190 }
3191 -x $pgc or ndie msg('opt-psql-noexec', $pgc);
3192
3193 $COM = qq{$pgc "$dir"};
3194 eval {
3195 $res = qx{$COM 2>&1};
3196 };
3197 if ($@) {
3198 ndie msg('checkpoint-nosys', $@);
3199 }
3200
3201 ## If the path is echoed back, we most likely have an invalid data dir
3202 if ($res =~ /$dir/) {
3203 ndie msg('checkpoint-baddir2', $dir);
3204 }
3205
3206 if ($res =~ /WARNING: Calculated CRC checksum/) {
3207 ndie msg('checkpoint-badver', $dir);
3208 }
3209 if ($res !~ /^pg_control.+\d+/) {
3210 ndie msg('checkpoint-badver2');
3211 }
3212
3213 ##Â return the pg_controldata output
3214 return $res;
3215}
3216
3217
3218sub check_archive_ready {
3219
3220 ## Check on the number of WAL archive with status "ready"
3221 ## Supports: Nagios, MRTG
3222 ## Must run as a superuser
3223 ## Critical and warning are the number of files
3224 ## Example: --critical=10
3225
3226 return check_wal_files('/archive_status', '.ready', 10, 15);
3227
3228} ## end of check_archive_ready
3229
3230
3231sub check_autovac_freeze {
3232
3233 ## Check how close all databases are to autovacuum_freeze_max_age
3234 ## Supports: Nagios, MRTG
3235 ## It makes no sense to run this more than once on the same cluster
3236 ## Warning and criticals are percentages
3237 ## Can also ignore databases with exclude, and limit with include
3238
3239 my ($warning, $critical) = validate_range
3240 ({
3241 type => 'percent',
3242 default_warning => '90%',
3243 default_critical => '95%',
3244 forcemrtg => 1,
3245 });
3246
3247 (my $w = $warning) =~ s/\D//;
3248 (my $c = $critical) =~ s/\D//;
3249
3250 my $SQL = q{SELECT freez, txns, ROUND(100*(txns/freez::float)) AS perc, datname}.
3251 q{ FROM (SELECT foo.freez::int, age(datfrozenxid) AS txns, datname}.
3252 q{ FROM pg_database d JOIN (SELECT setting AS freez FROM pg_settings WHERE name = 'autovacuum_freeze_max_age') AS foo}.
3253 q{ ON (true) WHERE d.datallowconn) AS foo2 ORDER BY 3 DESC, 4 ASC};
3254
3255 my $info = run_command($SQL, {regex => qr{\w+} } );
3256
3257 $db = $info->{db}[0];
3258
3259 my (@crit,@warn,@ok);
3260 my ($maxp,$maxt,$maxdb) = (0,0,''); ## used by MRTG only
3261 SLURP: for my $r (@{$db->{slurp}}) {
3262 next SLURP if skip_item($r->{datname});
3263
3264 if ($MRTG) {
3265 if ($r->{perc} > $maxp) {
3266 $maxdb = $r->{datname};
3267 $maxp = $r->{perc};
3268 }
3269 elsif ($r->{perc} == $maxp) {
3270 $maxdb .= sprintf '%s%s', (length $maxdb ? ' | ' : ''), $r->{datname};
3271 }
3272 $maxt = $r->{txns} if $r->{txns} > $maxt;
3273 next SLURP;
3274 }
3275
3276 my $msg = sprintf ' %s=%s%%;%s;%s', perfname($r->{datname}), $r->{perc}, $w, $c;
3277 $db->{perf} .= " $msg";
3278 if (length $critical and $r->{perc} >= $c) {
3279 push @crit => $msg;
3280 }
3281 elsif (length $warning and $r->{perc} >= $w) {
3282 push @warn => $msg;
3283 }
3284 else {
3285 push @ok => $msg;
3286 }
3287 }
3288 if ($MRTG) {
3289 do_mrtg({one => $maxp, two => $maxt, msg => $maxdb});
3290 }
3291 if (@crit) {
3292 add_critical join ' ' => @crit;
3293 }
3294 elsif (@warn) {
3295 add_warning join ' ' => @warn;
3296 }
3297 else {
3298 add_ok join ' ' => @ok;
3299 }
3300
3301 return;
3302
3303} ## end of check_autovac_freeze
3304
3305
3306sub check_backends {
3307
3308 ## Check the number of connections
3309 ## Supports: Nagios, MRTG
3310 ## It makes no sense to run this more than once on the same cluster
3311 ## Need to be superuser, else only your queries will be visible
3312 ## Warning and criticals can take three forms:
3313 ## critical = 12 -- complain if there are 12 or more connections
3314 ## critical = 95% -- complain if >= 95% of available connections are used
3315 ## critical = -5 -- complain if there are only 5 or fewer connection slots left
3316 ## The former two options only work with simple numbers - no percentage or negative
3317 ## Can also ignore databases with exclude, and limit with include
3318
3319 my $warning = $opt{warning} || '90%';
3320 my $critical = $opt{critical} || '95%';
3321 my $noidle = $opt{noidle} || 0;
3322
3323 ## If only critical was used, remove the default warning
3324 if ($opt{critical} and !$opt{warning}) {
3325 $warning = $critical;
3326 }
3327
3328 my $validre = qr{^(\-?)(\d+)(\%?)$};
3329 if ($critical !~ $validre) {
3330 ndie msg('backends-users', 'Critical');
3331 }
3332 my ($e1,$e2,$e3) = ($1,$2,$3);
3333 if ($warning !~ $validre) {
3334 ndie msg('backends-users', 'Warning');
3335 }
3336 my ($w1,$w2,$w3) = ($1,$2,$3);
3337
3338 ## If number is greater, all else is same, and not minus
3339 if ($w2 > $e2 and $w1 eq $e1 and $w3 eq $e3 and $w1 eq '') {
3340 ndie msg('range-warnbig');
3341 }
3342 ## If number is less, all else is same, and minus
3343 if ($w2 < $e2 and $w1 eq $e1 and $w3 eq $e3 and $w1 eq '-') {
3344 ndie msg('range-warnsmall');
3345 }
3346 if (($w1 and $w3) or ($e1 and $e3)) {
3347 ndie msg('range-neg-percent');
3348 }
3349
3350 my $MAXSQL = q{SELECT setting AS mc FROM pg_settings WHERE name = 'max_connections'};
3351
3352 my $NOIDLE = $noidle ? q{WHERE current_query <> '<IDLE>'} : '';
3353 $SQL = qq{
3354SELECT COUNT(datid) AS current,
3355 ($MAXSQL) AS mc,
3356 d.datname
3357FROM pg_database d
3358LEFT JOIN pg_stat_activity s ON (s.datid = d.oid) $NOIDLE
3359GROUP BY 2,3
3360ORDER BY datname
3361};
3362 my $info = run_command($SQL, {regex => qr{\d+}, fatalregex => 'too many clients' } );
3363
3364 $db = $info->{db}[0];
3365
3366 ## If we cannot connect because of too many clients, we treat as a critical error
3367 if (exists $info->{fatalregex}) {
3368 my $regmsg = msg('backends-po');
3369 my $regmsg2 = msg_en('backends-po');
3370 if ($info->{fatalregex} =~ /$regmsg/ or $info->{fatalregex} =~ /$regmsg2/) {
3371 add_critical msg('backends-fatal');
3372 return;
3373 }
3374 }
3375
3376 ## There may be no entries returned if we catch pg_stat_activity at the right
3377 ## moment in older versions of Postgres
3378 if (! defined $db) {
3379 $info = run_command($MAXSQL, {regex => qr[\d] } );
3380 $db = $info->{db}[0];
3381 if (!defined $db->{slurp} or $db->{slurp} !~ /(\d+)/) {
3382 undef %unknown;
3383 add_unknown msg('backends-nomax');
3384 return;
3385 }
3386 my $limit = $1;
3387 if ($MRTG) {
3388 do_mrtg({one => 1, msg => msg('backends-mrtg', $db->{dbname}, $limit)});
3389 }
3390 my $percent = (int 1/$limit*100) || 1;
3391 add_ok msg('backends-msg', 1, $limit, $percent);
3392 return;
3393 }
3394
3395 my $total = 0;
3396 my $grandtotal = @{$db->{slurp}};
3397
3398 ## If no max_connections, something is wrong
3399 if ($db->{slurp}[0]{mc} !~ /\d/) {
3400 add_unknown msg('backends-nomax');
3401 return;
3402 }
3403 my $limit = $db->{slurp}[0]{mc};
3404
3405 for my $r (@{$db->{slurp}}) {
3406
3407 ## Always want perf to show all
3408 my $nwarn=$w2;
3409 my $ncrit=$e2;
3410 if ($e1) {
3411 $ncrit = $limit-$e2;
3412 }
3413 elsif ($e3) {
3414 $ncrit = (int $e2*$limit/100);
3415 }
3416 if ($w1) {
3417 $nwarn = $limit-$w2;
3418 }
3419 elsif ($w3) {
3420 $nwarn = (int $w2*$limit/100)
3421 }
3422
3423 if (! skip_item($r->{datname})) {
3424 $db->{perf} .= sprintf ' %s=%s;%s;%s;0;%s',
3425 perfname($r->{datname}), $r->{current}, $nwarn, $ncrit, $limit;
3426 $total += $r->{current};
3427 }
3428 }
3429
3430 if ($MRTG) {
3431 do_mrtg({one => $total, msg => msg('backends-mrtg', $db->{dbname}, $limit)});
3432 }
3433
3434 if (!$total) {
3435 if ($grandtotal) {
3436 ## We assume that exclude/include rules are correct, and we simply had no entries
3437 ## at all in the specific databases we wanted
3438 add_ok msg('backends-oknone');
3439 }
3440 else {
3441 add_unknown msg('no-match-db');
3442 }
3443 return;
3444 }
3445
3446 my $percent = (int $total / $limit*100) || 1;
3447 my $msg = msg('backends-msg', $total, $limit, $percent);
3448 my $ok = 1;
3449
3450 if ($e1) { ## minus
3451 $ok = 0 if $limit-$total <= $e2;
3452 }
3453 elsif ($e3) { ## percent
3454 my $nowpercent = $total/$limit*100;
3455 $ok = 0 if $nowpercent >= $e2;
3456 }
3457 else { ## raw number
3458 $ok = 0 if $total >= $e2;
3459 }
3460 if (!$ok) {
3461 add_critical $msg;
3462 return;
3463 }
3464
3465 if ($w1) {
3466 $ok = 0 if $limit-$total <= $w2;
3467 }
3468 elsif ($w3) {
3469 my $nowpercent = $total/$limit*100;
3470 $ok = 0 if $nowpercent >= $w2;
3471 }
3472 else {
3473 $ok = 0 if $total >= $w2;
3474 }
3475 if (!$ok) {
3476 add_warning $msg;
3477 return;
3478 }
3479
3480 add_ok $msg;
3481
3482 return;
3483
3484} ## end of check_backends
3485
3486
3487sub check_bloat {
3488
3489 ## Check how bloated the tables and indexes are
3490 ## Supports: Nagios, MRTG
3491 ## NOTE! This check depends on ANALYZE being run regularly
3492 ## Also requires stats collection to be on
3493 ## This action may be very slow on large databases
3494 ## By default, checks all relations
3495 ## Can check specific one(s) with include; can ignore some with exclude
3496 ## Begin name with a '~' to make it a regular expression
3497 ## Warning and critical are in sizes, defaults to bytes
3498 ## Valid units: b, k, m, g, t, e
3499 ## All above may be written as plural or with a trailing 'b'
3500 ## Example: --critical="25 GB" --include="mylargetable"
3501 ## Can also specify percentages
3502
3503 ## Don't bother with tables or indexes unless they have at least this many bloated pages
3504 my $MINPAGES = 0;
3505 my $MINIPAGES = 10;
3506
3507 my $LIMIT = 10;
3508 if ($opt{perflimit}) {
3509 $LIMIT = $opt{perflimit};
3510 }
3511
3512 my ($warning, $critical) = validate_size_or_percent_with_oper
3513 ({
3514 default_warning => '1 GB',
3515 default_critical => '5 GB',
3516 });
3517
3518 ## This was fun to write
3519 $SQL = q{
3520SELECT
3521 current_database() AS db, schemaname, tablename, reltuples::bigint AS tups, relpages::bigint AS pages, otta,
3522 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,
3523 CASE WHEN relpages < otta THEN 0 ELSE relpages::bigint - otta END AS wastedpages,
3524 CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::bigint END AS wastedbytes,
3525 CASE WHEN relpages < otta THEN '0 bytes'::text ELSE (bs*(relpages-otta))::bigint || ' bytes' END AS wastedsize,
3526 iname, ituples::bigint AS itups, ipages::bigint AS ipages, iotta,
3527 ROUND(CASE WHEN iotta=0 OR ipages=0 OR ipages=iotta THEN 0.0 ELSE ipages/iotta::numeric END,1) AS ibloat,
3528 CASE WHEN ipages < iotta THEN 0 ELSE ipages::bigint - iotta END AS wastedipages,
3529 CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes,
3530 CASE WHEN ipages < iotta THEN '0 bytes' ELSE (bs*(ipages-iotta))::bigint || ' bytes' END AS wastedisize,
3531 CASE WHEN relpages < otta THEN
3532 CASE WHEN ipages < iotta THEN 0 ELSE ipages-iotta::bigint END
3533 ELSE CASE WHEN ipages < iotta THEN relpages-otta::bigint
3534 ELSE relpages-otta::bigint + ipages-iotta::bigint END
3535 END AS totalwastedbytes
3536FROM (
3537 SELECT
3538 nn.nspname AS schemaname,
3539 cc.relname AS tablename,
3540 COALESCE(cc.reltuples,0) AS reltuples,
3541 COALESCE(cc.relpages,0) AS relpages,
3542 COALESCE(bs,0) AS bs,
3543 COALESCE(CEIL((cc.reltuples*((datahdr+ma-
3544 (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)),0) AS otta,
3545 COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
3546 COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
3547 FROM
3548 pg_class cc
3549 JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname <> 'information_schema'
3550 LEFT JOIN
3551 (
3552 SELECT
3553 ma,bs,schemaname,tablename,
3554 (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
3555 (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
3556 FROM (
3557 SELECT
3558 schemaname, tablename, hdr, ma, bs,
3559 SUM((1-null_frac)*avg_width) AS datawidth,
3560 MAX(null_frac) AS maxfracsum,
3561 hdr+(
3562 SELECT 1+count(*)/8
3563 FROM pg_stats s2
3564 WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
3565 ) AS nullhdr
3566 FROM pg_stats s, (
3567 SELECT
3568 BLOCK_SIZE,
3569 CASE WHEN SUBSTRING(SPLIT_PART(v, ' ', 2) FROM '#"[0-9]+.[0-9]+#"%' for '#')
3570 IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr,
3571 CASE WHEN v ~ 'mingw32' OR v ~ '64-bit' THEN 8 ELSE 4 END AS ma
3572 FROM (SELECT version() AS v) AS foo
3573 ) AS constants
3574 GROUP BY 1,2,3,4,5
3575 ) AS foo
3576 ) AS rs
3577 ON cc.relname = rs.tablename AND nn.nspname = rs.schemaname
3578 LEFT JOIN pg_index i ON indrelid = cc.oid
3579 LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
3580) AS sml
3581};
3582
3583 if (! defined $opt{include} and ! defined $opt{exclude}) {
3584 $SQL .= " WHERE sml.relpages - otta > $MINPAGES OR ipages - iotta > $MINIPAGES";
3585 $SQL .= " ORDER BY totalwastedbytes DESC LIMIT $LIMIT";
3586 }
3587 else {
3588 $SQL .= ' ORDER BY totalwastedbytes DESC';
3589 }
3590
3591 if ($psql_version <= 7.4) {
3592 $SQL =~ s/BLOCK_SIZE/(SELECT 8192) AS bs/;
3593 }
3594 else {
3595 $SQL =~ s/BLOCK_SIZE/(SELECT current_setting('block_size')::numeric) AS bs/;
3596 }
3597
3598 my $info = run_command($SQL);
3599
3600 if (defined $info->{db}[0] and exists $info->{db}[0]{error}) {
3601 ndie $info->{db}[0]{error};
3602 }
3603
3604 my %seenit;
3605
3606 ## Store the perf data for sorting at the end
3607 my %perf;
3608
3609 $db = $info->{db}[0];
3610
3611 if ($db->{slurp} !~ /\w+/o) {
3612 add_ok msg('bloat-nomin') unless $MRTG;
3613 return;
3614 }
3615 ## Not a 'regex' to run_command as we need to check the above first.
3616 if ($db->{slurp} !~ /\d+/) {
3617 add_unknown msg('invalid-query', $db->{slurp}) unless $MRTG;
3618 return;
3619 }
3620
3621 my $max = -1;
3622 my $maxmsg = '?';
3623
3624 ## The perf must be added before the add_x, so we defer the settings:
3625 my (@addwarn, @addcrit);
3626
3627 for my $r (@{ $db->{slurp} }) {
3628
3629 for my $v (values %$r) {
3630 $v =~ s/(\d+) bytes/pretty_size($1,1)/ge;
3631 }
3632
3633 my ($dbname,$schema,$table,$tups,$pages,$otta,$bloat,$wp,$wb,$ws) = @$r{
3634 qw/ db schemaname tablename tups pages otta tbloat wastedpages wastedbytes wastedsize/};
3635
3636 next if skip_item($table, $schema);
3637
3638 my ($index,$irows,$ipages,$iotta,$ibloat,$iwp,$iwb,$iws) = @$r{
3639 qw/ iname irows ipages iotta ibloat wastedipgaes wastedibytes wastedisize/};
3640
3641 ## Made it past the exclusions
3642 $max = -2 if $max == -1;
3643
3644 ## Do the table first if we haven't seen it
3645 if (! $seenit{"$dbname.$schema.$table"}++) {
3646 my $nicename = perfname("$schema.$table");
3647 $perf{$wb}{$nicename}++;
3648 my $msg = msg('bloat-table', $dbname, $schema, $table, $tups, $pages, $otta, $bloat, $wb, $ws);
3649 my $ok = 1;
3650 my $perbloat = $bloat * 100;
3651
3652 if ($MRTG) {
3653 $stats{table}{"DB=$dbname TABLE=$schema.$table"} = [$wb, $bloat];
3654 next;
3655 }
3656 if ($critical->($wb, $perbloat)) {
3657 push @addcrit => $msg;
3658 $ok = 0;
3659 }
3660
3661 if ($ok and $warning->($wb, $perbloat)) {
3662 push @addwarn => $msg;
3663 $ok = 0;
3664 }
3665 ($max = $wb, $maxmsg = $msg) if $wb > $max and $ok;
3666 }
3667
3668 ## Now the index, if it exists
3669 if ($index ne '?') {
3670 my $nicename = perfname($index);
3671 $perf{$iwb}{$nicename}++;
3672 my $msg = msg('bloat-index', $dbname, $index, $irows, $ipages, $iotta, $ibloat, $iwb, $iws);
3673 my $ok = 1;
3674 my $iperbloat = $ibloat * 100;
3675
3676 if ($MRTG) {
3677 $stats{index}{"DB=$dbname INDEX=$index"} = [$iwb, $ibloat];
3678 next;
3679 }
3680 if ($critical->($iwb, $iperbloat)) {
3681 push @addcrit => $msg;
3682 $ok = 0;
3683 }
3684
3685 if ($ok and $warning->($iwb, $iperbloat)) {
3686 push @addwarn => $msg;
3687 $ok = 0;
3688 }
3689 ($max = $iwb, $maxmsg = $msg) if $iwb > $max and $ok;
3690 }
3691 }
3692
3693 ## Set a sorted limited perf
3694 $db->{perf} = '';
3695 my $count = 0;
3696 PERF: for my $size (sort {$b <=> $a } keys %perf) {
3697 for my $name (sort keys %{ $perf{$size} }) {
3698 $db->{perf} .= "$name=${size}B ";
3699 last PERF if $opt{perflimit} and ++$count >= $opt{perflimit};
3700 }
3701 }
3702
3703 ## Now we can set the critical and warning
3704 for (@addcrit) {
3705 add_critical $_;
3706 $db->{perf} = '';
3707 }
3708 for (@addwarn) {
3709 add_warning $_;
3710 $db->{perf} = '';
3711 }
3712
3713 if ($max == -1) {
3714 add_unknown msg('no-match-rel');
3715 }
3716 elsif ($max != -1) {
3717 add_ok $maxmsg;
3718 }
3719
3720 if ($MRTG) {
3721 keys %stats or bad_mrtg(msg('unknown-error'));
3722 ## We are going to report the highest wasted bytes for table and index
3723 my ($one,$two,$msg) = ('','');
3724 ## Can also sort by ratio
3725 my $sortby = exists $opt{mrtg} and $opt{mrtg} eq 'ratio' ? 1 : 0;
3726 for (sort { $stats{table}{$b}->[$sortby] <=> $stats{table}{$a}->[$sortby] } keys %{$stats{table}}) {
3727 $one = $stats{table}{$_}->[$sortby];
3728 $msg = $_;
3729 last;
3730 }
3731 for (sort { $stats{index}{$b}->[$sortby] <=> $stats{index}{$a}->[$sortby] } keys %{$stats{index}}) {
3732 $two = $stats{index}{$_}->[$sortby];
3733 $msg .= " $_";
3734 last;
3735 }
3736 do_mrtg({one => $one, two => $two, msg => $msg});
3737 }
3738
3739 return;
3740
3741} ## end of check_bloat
3742
3743sub check_checkpoint {
3744
3745 ## Checks how long in seconds since the last checkpoint on a WAL slave
3746
3747 ## Note that this value is actually the last checkpoint on the
3748 ## *master* (as copied from the WAL checkpoint record), so it more
3749 ## indicative that the master has been unable to complete a
3750 ## checkpoint for some other reason (i.e., unable to write dirty
3751 ## buffers or archive_command failure, etc). As such, this check
3752 ## may make more sense on the master, or we may want to look at
3753 ## the WAL segments received/processed instead of the checkpoint
3754 ## timestamp.
3755 ## This check can use the optional --assume-standby-mode or
3756 ## --assume-prod: if the mode found is not the mode assumed, a
3757 ## CRITICAL is emitted.
3758
3759 ## Supports: Nagios, MRTG
3760 ## Warning and critical are seconds
3761 ## Requires $ENV{PGDATA} or --datadir
3762
3763 my ($warning, $critical) = validate_range
3764 ({
3765 type => 'time',
3766 leastone => 1,
3767 forcemrtg => 1,
3768 });
3769
3770 $db->{host} = '<none>';
3771
3772 ## Run pg_controldata, grab the time
3773 $res = open_controldata();
3774
3775 my $regex = msg('checkpoint-po');
3776 if ($res !~ /$regex\s*(.+)/) { ## no critic (ProhibitUnusedCapture)
3777 ## Just in case, check the English one as well
3778 $regex = msg_en('checkpoint-po');
3779 if ($res !~ /$regex\s*(.+)/) {
3780 ndie msg('checkpoint-noregex');
3781 }
3782 }
3783 my $last = $1;
3784
3785 ## Convert to number of seconds
3786 eval {
3787 require Date::Parse;
3788 import Date::Parse;
3789 };
3790 if ($@) {
3791 ndie msg('checkpoint-nodp');
3792 }
3793 my $dt = str2time($last);
3794 if ($dt !~ /^\d+$/) {
3795 ndie msg('checkpoint-noparse', $last);
3796 }
3797 my $diff = time - $dt;
3798 my $msg = $diff==1 ? msg('checkpoint-ok') : msg('checkpoint-ok2', $diff);
3799 $db->{perf} = sprintf '%s=%s;%s;%s',
3800 perfname(msg('age')), $diff, $warning, $critical;
3801
3802 my $mode = '';
3803 if ($STANDBY) {
3804 $mode = 'STANDBY';
3805 }
3806 if ($MASTER) {
3807 $mode = 'MASTER';
3808 }
3809
3810 ## If we have an assume flag, then honor it.
3811 my $goodmode = 1;
3812 if ($opt{'assume-standby-mode'} and not $STANDBY) {
3813 $goodmode = 0;
3814 $mode = 'NOT STANDBY';
3815 }
3816 elsif ($opt{'assume-prod'} and not $MASTER) {
3817 $goodmode = 0;
3818 $mode = 'NOT MASTER';
3819 }
3820
3821 if (length($mode) > 0) {
3822 $db->{perf} .= sprintf ' %s=%s',
3823 perfname(msg('mode')), $mode;
3824 }
3825
3826 if ($MRTG) {
3827 do_mrtg({one => $diff, msg => $msg});
3828 }
3829
3830 if ((length $critical and $diff >= $critical) or not $goodmode) {
3831 add_critical $msg;
3832 return;
3833 }
3834
3835 if (length $warning and $diff >= $warning) {
3836 add_warning $msg;
3837 return;
3838 }
3839
3840 add_ok $msg;
3841
3842 return;
3843
3844} ## end of check_checkpoint
3845
3846
3847sub check_cluster_id {
3848
3849
3850 ## Verify the Database System Identifier provided by pg_controldata
3851 ## Supports: Nagios, MRTG
3852 ## One of warning or critical must be given (but not both)
3853 ## It should run one time to find out the expected cluster-id
3854 ## You can use --critical="0" to find out the current cluster-id
3855 ## You can include or exclude settings as well
3856 ## Example:
3857 ## check_postgres_cluster_id --critical="5633695740047915125"
3858
3859 my ($warning, $critical) = validate_range({type => 'integer_string', onlyone => 1});
3860
3861 $db->{host} = '<none>';
3862
3863 ## Run pg_controldata, grab the cluster-id
3864 $res = open_controldata();
3865
3866 my $regex = msg('checkcluster-id');
3867 if ($res !~ /$regex\s*(.+)/) { ## no critic (ProhibitUnusedCapture)
3868 ## Just in case, check the English one as well
3869 $regex = msg_en('checkcluster-id');
3870 if ($res !~ /$regex\s*(.+)/) {
3871 ndie msg('checkpoint-noregex');
3872 }
3873 }
3874 my $ident = $1;
3875
3876 my $msg = msg('checkcluster-msg', $ident);
3877 if ($MRTG) {
3878 $opt{mrtg} or ndie msg('checksum-nomrtg');
3879 do_mrtg({one => $opt{mrtg} eq $ident ? 1 : 0, msg => $ident});
3880 }
3881 if ($critical and $critical ne $ident) {
3882 add_critical $msg;
3883 }
3884 elsif ($warning and $warning ne $ident) {
3885 add_warning $msg;
3886 }
3887 elsif (!$critical and !$warning) {
3888 add_unknown $msg;
3889 }
3890 else {
3891 add_ok $msg;
3892 }
3893
3894 return;
3895
3896} ## end of check_cluster_id
3897
3898
3899sub check_commitratio {
3900
3901 ## Check the commitratio of one or more databases
3902 ## Supports: Nagios, MRTG
3903 ## mrtg reports the largest two databases
3904 ## By default, checks all databases
3905 ## Can check specific one(s) with include
3906 ## Can ignore some with exclude
3907 ## Warning and criticals are percentages
3908 ## Limit to a specific user (db owner) with the includeuser option
3909 ## Exclude users with the excludeuser option
3910
3911 my ($warning, $critical) = validate_range({type => 'percent'});
3912
3913 $SQL = qq{
3914SELECT
3915 round(100.*sd.xact_commit/(sd.xact_commit+sd.xact_rollback), 2) AS dcommitratio,
3916 d.datname,
3917 u.usename
3918FROM pg_stat_database sd
3919JOIN pg_database d ON (d.oid=sd.datid)
3920JOIN pg_user u ON (u.usesysid=d.datdba)
3921WHERE sd.xact_commit+sd.xact_rollback<>0
3922$USERWHERECLAUSE
3923};
3924 if ($opt{perflimit}) {
3925 $SQL .= " ORDER BY 1 DESC LIMIT $opt{perflimit}";
3926 }
3927
3928 my $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1, } );
3929 my $found = 0;
3930
3931 for $db (@{$info->{db}}) {
3932 my $min = 101;
3933 $found = 1;
3934 my %s;
3935 for my $r (@{$db->{slurp}}) {
3936
3937 next if skip_item($r->{datname});
3938
3939 if ($r->{dcommitratio} <= $min) {
3940 $min = $r->{dcommitratio};
3941 }
3942 $s{$r->{datname}} = $r->{dcommitratio};
3943 }
3944
3945 if ($MRTG) {
3946 do_mrtg({one => $min, msg => "DB: $db->{dbname}"});
3947 }
3948 if ($min > 100) {
3949 $stats{$db->{dbname}} = 0;
3950 if ($USERWHERECLAUSE) {
3951 add_ok msg('no-match-user');
3952 }
3953 else {
3954 add_unknown msg('no-match-db');
3955 }
3956 next;
3957 }
3958
3959 my $msg = '';
3960 for (reverse sort {$s{$b} <=> $s{$a} or $a cmp $b } keys %s) {
3961 $msg .= "$_: $s{$_} ";
3962 $db->{perf} .= sprintf ' %s=%s;%s;%s',
3963 perfname($_), $s{$_}, $warning, $critical;
3964 }
3965 if (length $critical and $min <= $critical) {
3966 add_critical $msg;
3967 }
3968 elsif (length $warning and $min <= $warning) {
3969 add_warning $msg;
3970 }
3971 else {
3972 add_ok $msg;
3973 }
3974 }
3975
3976 ## If no results, probably a version problem
3977 if (!$found and keys %unknown) {
3978 (my $first) = values %unknown;
3979 if ($first->[0][0] =~ /pg_database_size/) {
3980 ndie msg('dbsize-version');
3981 }
3982 }
3983
3984 return;
3985
3986} ## end of check_commitratio
3987
3988
3989sub check_connection {
3990
3991 ## Check the connection, get the connection time and version
3992 ## No comparisons made: warning and critical are not allowed
3993 ## Suports: Nagios, MRTG
3994
3995 if ($opt{warning} or $opt{critical}) {
3996 ndie msg('range-none');
3997 }
3998
3999 my $info = run_command('SELECT version() AS v');
4000
4001 for $db (@{$info->{db}}) {
4002
4003 my $err = $db->{error} || '';
4004 if ($err =~ /FATAL/) {
4005 $MRTG and do_mrtg({one => 0});
4006 add_critical $db->{error};
4007 return;
4008 }
4009
4010 my $ver = ($db->{slurp}[0]{v} =~ /(\d+\.\d+\S+)/o) ? $1 : '';
4011
4012 $MRTG and do_mrtg({one => $ver ? 1 : 0});
4013
4014 if ($ver) {
4015 add_ok msg('version', $ver);
4016 }
4017 else {
4018 add_unknown msg('invalid-query', $db->{slurp}[0]{v});
4019 }
4020 }
4021
4022 return;
4023
4024} ## end of check_connection
4025
4026
4027sub check_custom_query {
4028
4029 ## Run a user-supplied query, then parse the results
4030 ## If you end up using this to make a useful query, consider making it
4031 ## into a specific action and sending in a patch!
4032 ## valtype must be one of: string, time, size, integer
4033
4034 my $valtype = $opt{valtype} || 'integer';
4035
4036 my ($warning, $critical) = validate_range({type => $valtype, leastone => 1});
4037
4038 my $query = $opt{query} or ndie msg('custom-nostring');
4039
4040 my $reverse = $opt{reverse} || 0;
4041
4042 my $info = run_command($query);
4043
4044 for $db (@{$info->{db}}) {
4045
4046 if (! @{$db->{slurp}}) {
4047 add_unknown msg('custom-norows');
4048 next;
4049 }
4050
4051 my $goodrow = 0;
4052
4053 ## The other column tells it the name to use as the perfdata value
4054 my $perfname;
4055
4056 for my $r (@{$db->{slurp}}) {
4057 my $result = $r->{result};
4058 if (! defined $perfname) {
4059 $perfname = '';
4060 for my $name (keys %$r) {
4061 next if $name eq 'result';
4062 $perfname = $name;
4063 last;
4064 }
4065 }
4066 $goodrow++;
4067 if ($perfname) {
4068 $db->{perf} .= sprintf ' %s=%s;%s;%s',
4069 perfname($perfname), $r->{$perfname}, $warning, $critical;
4070 }
4071 my $gotmatch = 0;
4072 if (! defined $result) {
4073 add_unknown msg('custom-invalid');
4074 return;
4075 }
4076 if (length $critical) {
4077 if (($valtype eq 'string' and $result eq $critical)
4078 or
4079 ($valtype ne 'string' and $reverse ? $result <= $critical : $result >= $critical)) { ## covers integer, time, size
4080 add_critical "$result";
4081 $gotmatch = 1;
4082 }
4083 }
4084
4085 if (length $warning and ! $gotmatch) {
4086 if (($valtype eq 'string' and $result eq $warning)
4087 or
4088 ($valtype ne 'string' and length $result and $reverse ? $result <= $warning : $result >= $warning)) {
4089 add_warning "$result";
4090 $gotmatch = 1;
4091 }
4092 }
4093
4094 if (! $gotmatch) {
4095 add_ok "$result";
4096 }
4097
4098 } ## end each row returned
4099
4100 if (!$goodrow) {
4101 add_unknown msg('custom-invalid');
4102 }
4103 }
4104
4105 return;
4106
4107} ## end of check_custom_query
4108
4109
4110sub check_database_size {
4111
4112 ## Check the size of one or more databases
4113 ## Supports: Nagios, MRTG
4114 ## mrtg reports the largest two databases
4115 ## By default, checks all databases
4116 ## Can check specific one(s) with include
4117 ## Can ignore some with exclude
4118 ## Warning and critical are bytes
4119 ## Valid units: b, k, m, g, t, e
4120 ## All above may be written as plural or with a trailing 'b'
4121 ## Limit to a specific user (db owner) with the includeuser option
4122 ## Exclude users with the excludeuser option
4123
4124 my ($warning, $critical) = validate_range({type => 'size'});
4125
4126 $USERWHERECLAUSE =~ s/AND/WHERE/;
4127
4128 $SQL = qq{
4129SELECT pg_database_size(d.oid) AS dsize,
4130 pg_size_pretty(pg_database_size(d.oid)) AS pdsize,
4131 datname,
4132 usename
4133FROM pg_database d
4134LEFT JOIN pg_user u ON (u.usesysid=d.datdba)$USERWHERECLAUSE
4135};
4136 if ($opt{perflimit}) {
4137 $SQL .= " ORDER BY 1 DESC LIMIT $opt{perflimit}";
4138 }
4139
4140 my $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1, } );
4141 my $found = 0;
4142
4143 for $db (@{$info->{db}}) {
4144 my $max = -1;
4145 $found = 1;
4146 my %s;
4147 for my $r (@{$db->{slurp}}) {
4148
4149 next if skip_item($r->{datname});
4150
4151 if ($r->{dsize} >= $max) {
4152 $max = $r->{dsize};
4153 }
4154 $s{$r->{datname}} = [$r->{dsize},$r->{pdsize}];
4155 }
4156
4157 if ($MRTG) {
4158 do_mrtg({one => $max, msg => "DB: $db->{dbname}"});
4159 }
4160 if ($max < 0) {
4161 $stats{$db->{dbname}} = 0;
4162 if ($USERWHERECLAUSE) {
4163 add_ok msg('no-match-user');
4164 }
4165 else {
4166 add_unknown msg('no-match-db');
4167 }
4168 next;
4169 }
4170
4171 my $msg = '';
4172 for (sort {$s{$b}[0] <=> $s{$a}[0] or $a cmp $b } keys %s) {
4173 $msg .= "$_: $s{$_}[0] ($s{$_}[1]) ";
4174 $db->{perf} .= sprintf ' %s=%s;%s;%s',
4175 perfname($_), $s{$_}[0], $warning, $critical;
4176 }
4177 if (length $critical and $max >= $critical) {
4178 add_critical $msg;
4179 }
4180 elsif (length $warning and $max >= $warning) {
4181 add_warning $msg;
4182 }
4183 else {
4184 add_ok $msg;
4185 }
4186 }
4187
4188 ## If no results, probably a version problem
4189 if (!$found and keys %unknown) {
4190 (my $first) = values %unknown;
4191 if ($first->[0][0] =~ /pg_database_size/) {
4192 ndie msg('dbsize-version');
4193 }
4194 }
4195
4196 return;
4197
4198} ## end of check_database_size
4199
4200
4201sub check_dbstats {
4202
4203 ## Returns values from the pg_stat_database view
4204 ## Supports: Cacti
4205 ## Assumes psql and target are the same version for the 8.3 check
4206
4207 my ($warning, $critical) = validate_range
4208 ({
4209 type => 'cacti',
4210 });
4211
4212 my $SQL = q{SELECT datname,
4213 numbackends AS backends,xact_commit AS commits,xact_rollback AS rollbacks,
4214 blks_read AS read, blks_hit AS hit};
4215 if ($opt{dbname}) {
4216 $SQL .= q{
4217 ,(SELECT SUM(idx_scan) FROM pg_stat_user_indexes) AS idxscan
4218 ,COALESCE((SELECT SUM(idx_tup_read) FROM pg_stat_user_indexes),0) AS idxtupread
4219 ,COALESCE((SELECT SUM(idx_tup_fetch) FROM pg_stat_user_indexes),0) AS idxtupfetch
4220 ,COALESCE((SELECT SUM(idx_blks_read) FROM pg_statio_user_indexes),0) AS idxblksread
4221 ,COALESCE((SELECT SUM(idx_blks_hit) FROM pg_statio_user_indexes),0) AS idxblkshit
4222 ,COALESCE((SELECT SUM(seq_scan) FROM pg_stat_user_tables),0) AS seqscan
4223 ,COALESCE((SELECT SUM(seq_tup_read) FROM pg_stat_user_tables),0) AS seqtupread
4224};
4225 }
4226 $SQL .= q{ FROM pg_stat_database};
4227 (my $SQL2 = $SQL) =~ s/AS seqtupread/AS seqtupread, tup_returned AS ret, tup_fetched AS fetch, tup_inserted AS ins, tup_updated AS upd, tup_deleted AS del/;
4228
4229 my $info = run_command($SQL, {regex => qr{\w}, version => [ ">8.2 $SQL2" ] } );
4230
4231 for $db (@{$info->{db}}) {
4232 ROW: for my $r (@{$db->{slurp}}) {
4233
4234 my $dbname = $r->{datname};
4235
4236 next ROW if skip_item($dbname);
4237
4238 ## If dbnames were specififed, use those for filtering as well
4239 if (@{$opt{dbname}}) {
4240 my $keepit = 0;
4241 for my $drow (@{$opt{dbname}}) {
4242 for my $d (split /,/ => $drow) {
4243 $d eq $dbname and $keepit = 1;
4244 }
4245 }
4246 next ROW unless $keepit;
4247 }
4248
4249 my $msg = '';
4250 for my $col (qw/
4251backends commits rollbacks
4252read hit
4253idxscan idxtupread idxtupfetch idxblksread idxblkshit
4254seqscan seqtupread
4255ret fetch ins upd del/) {
4256 $msg .= "$col:";
4257 $msg .= (exists $r->{$col} and length $r->{$col}) ? $r->{$col} : 0;
4258 $msg .= ' ';
4259 }
4260 print "${msg}dbname:$dbname\n";
4261 }
4262 }
4263
4264 exit 0;
4265
4266} ## end of check_dbstats
4267
4268
4269sub check_disabled_triggers {
4270
4271 ## Checks how many disabled triggers are in the database
4272 ## Supports: Nagios, MRTG
4273 ## Warning and critical are integers, defaults to 1
4274
4275 my ($warning, $critical) = validate_range
4276 ({
4277 type => 'positive integer',
4278 default_warning => 1,
4279 default_critical => 1,
4280 forcemrtg => 1,
4281 });
4282
4283 $SQL = q{
4284SELECT tgrelid::regclass AS tname, tgname, tgenabled
4285FROM pg_trigger
4286WHERE tgenabled IS NOT TRUE ORDER BY tgname
4287};
4288 my $SQL83 = q{
4289SELECT tgrelid::regclass AS tname, tgname, tgenabled
4290FROM pg_trigger
4291WHERE tgenabled = 'D' ORDER BY tgname
4292};
4293 my $SQLOLD = q{SELECT 'FAIL' AS fail};
4294
4295 my $info = run_command($SQL, { version => [ ">8.2 $SQL83", "<8.1 $SQLOLD" ] } );
4296
4297 if (exists $info->{db}[0]{fail}) {
4298 ndie msg('die-action-version', $action, '8.1', $db->{version});
4299 }
4300
4301 my $count = 0;
4302 my $dislis = '';
4303 for $db (@{$info->{db}}) {
4304
4305 ROW: for my $r (@{$db->{slurp}}) {
4306 $count++;
4307 $dislis .= " $r->{tname}=>$r->{tgname}";
4308 }
4309 $MRTG and do_mrtg({one => $count});
4310
4311 my $msg = msg('trigger-msg', "$count$dislis");
4312
4313 if ($critical and $count >= $critical) {
4314 add_critical $msg;
4315 }
4316 elsif ($warning and $count >= $warning) {
4317 add_warning $msg;
4318 }
4319 else {
4320 add_ok $msg;
4321 }
4322 }
4323
4324 return;
4325
4326} ## end of check_disabled_triggers
4327
4328
4329sub check_disk_space {
4330
4331 ## Check the available disk space used by postgres
4332 ## Supports: Nagios, MRTG
4333 ## Requires the executable "/bin/df"
4334 ## Must run as a superuser in the database (to examine 'data_directory' setting)
4335 ## Critical and warning are maximum size, or percentages
4336 ## Example: --critical="40 GB"
4337 ## NOTE: Needs to run on the same system (for now)
4338 ## XXX Allow custom ssh commands for remote df and the like
4339
4340 my ($warning, $critical) = validate_size_or_percent_with_oper
4341 ({
4342 default_warning => '90%',
4343 default_critical => '95%',
4344 });
4345
4346 -x '/bin/df' or ndie msg('diskspace-nodf');
4347
4348 ## Figure out where everything is.
4349 $SQL = q{
4350SELECT 'S' AS syn, name AS nn, setting AS val
4351FROM pg_settings
4352WHERE name = 'data_directory'
4353OR name ='log_directory'
4354UNION ALL
4355SELECT 'T' AS syn, spcname AS nn, spclocation AS val
4356FROM pg_tablespace
4357WHERE spclocation <> ''
4358};
4359
4360 my $info = run_command($SQL);
4361
4362 my %dir; ## 1 = normal 2 = been checked -1 = does not exist
4363 my %seenfs;
4364 for $db (@{$info->{db}}) {
4365 my %i;
4366 for my $r (@{$db->{slurp}}) {
4367 $i{$r->{syn}}{$r->{nn}} = $r->{val};
4368 }
4369 if (! exists $i{S}{data_directory}) {
4370 add_unknown msg('diskspace-nodata');
4371 next;
4372 }
4373 my ($datadir,$logdir) = ($i{S}{data_directory},$i{S}{log_directory}||'');
4374
4375 if (!exists $dir{$datadir}) {
4376 if (! -d $datadir) {
4377 add_unknown msg('diskspace-nodir', $datadir);
4378 $dir{$datadir} = -1;
4379 next;
4380 }
4381 $dir{$datadir} = 1;
4382
4383 ## Check if the WAL files are on a separate disk
4384 my $xlog = "$datadir/pg_xlog";
4385 if (-l $xlog) {
4386 my $linkdir = readlink($xlog);
4387 $dir{$linkdir} = 1 if ! exists $dir{$linkdir};
4388 }
4389 }
4390
4391 ## Check log_directory: relative or absolute
4392 if (length $logdir) {
4393 if ($logdir =~ /^\w/) { ## relative, check only if symlinked
4394 $logdir = "$datadir/$logdir";
4395 if (-l $logdir) {
4396 my $linkdir = readlink($logdir);
4397 $dir{$linkdir} = 1 if ! exists $dir{$linkdir};
4398 }
4399 }
4400 else { ## absolute, always check
4401 if ($logdir ne $datadir and ! exists $dir{$logdir}) {
4402 $dir{$logdir} = 1;
4403 }
4404 }
4405 }
4406
4407 ## Check all tablespaces
4408 for my $tsname (keys %{$i{T}}) {
4409 my $tsdir = $i{T}{$tsname};
4410 $dir{$tsdir} = 1 if ! exists $dir{$tsdir};
4411 }
4412
4413 my $gotone = 0;
4414 for my $dir (keys %dir) {
4415 next if $dir{$dir} != 1;
4416
4417 $dir{$dir} = 1;
4418
4419 $COM = qq{/bin/df -kP "$dir" 2>&1};
4420 $res = qx{$COM};
4421
4422 if ($res !~ /^.+\n(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\%\s+(\S+)/) {
4423 ndie msg('diskspace-fail', $COM, $res);
4424 }
4425 my ($fs,$total,$used,$avail,$percent,$mount) = ($1,$2*1024,$3*1024,$4*1024,$5,$6);
4426
4427 ## If we've already done this one, skip it
4428 next if $seenfs{$fs}++;
4429
4430 next if skip_item($fs);
4431
4432 if ($MRTG) {
4433 $stats{$fs} = [$total,$used,$avail,$percent];
4434 next;
4435 }
4436
4437 $gotone = 1;
4438
4439 ## Rather than make another call with -h, do it ourselves
4440 my $prettyused = pretty_size($used);
4441 my $prettytotal = pretty_size($total);
4442
4443 my $msg = msg('diskspace-msg', $fs, $mount, $prettyused, $prettytotal, $percent);
4444
4445 $db->{perf} = sprintf '%s=%sB',
4446 perfname(msg('size')), $used;
4447
4448 my $ok = 1;
4449 if ($critical->($used, $percent)) {
4450 add_critical $msg;
4451 $ok = 0;
4452 }
4453
4454 if ($ok and $warning->($used, $percent)) {
4455 add_warning $msg;
4456 $ok = 0;
4457 }
4458
4459 if ($ok) {
4460 add_ok $msg;
4461 }
4462 } ## end each dir
4463
4464 next if $MRTG;
4465
4466 if (!$gotone) {
4467 add_unknown msg('no-match-fs');
4468 }
4469 }
4470
4471 if ($MRTG) {
4472 keys %stats or bad_mrtg(msg('unknown-error'));
4473 ## Get the highest by total size or percent (total, used, avail, percent)
4474 ## We default to 'available'
4475 my $sortby = exists $opt{mrtg}
4476 ? $opt{mrtg} eq 'total' ? 0
4477 : $opt{mrtg} eq 'used' ? 1
4478 : $opt{mrtg} eq 'avail' ? 2
4479 : $opt{mrtg} eq 'percent' ? 3 : 2 : 2;
4480 my ($one,$two,$msg) = ('','','');
4481 for (sort { $stats{$b}->[$sortby] <=> $stats{$a}->[$sortby] } keys %stats) {
4482 if ($one eq '') {
4483 $one = $stats{$_}->[$sortby];
4484 $msg = $_;
4485 next;
4486 }
4487 $two = $stats{$_}->[$sortby];
4488 last;
4489 }
4490 do_mrtg({one => $one, two => $two, msg => $msg});
4491 }
4492
4493 return;
4494
4495} ## end of check_disk_space
4496
4497
4498sub check_fsm_pages {
4499
4500 ## Check on the percentage of free space map pages in use
4501 ## Supports: Nagios, MRTG
4502 ## Must run as superuser
4503 ## Requires pg_freespacemap contrib module
4504 ## Critical and warning are a percentage of max_fsm_pages
4505 ## Example: --critical=95
4506
4507 my ($warning, $critical) = validate_range
4508 ({
4509 type => 'percent',
4510 default_warning => '85%',
4511 default_critical => '95%',
4512 });
4513
4514 (my $w = $warning) =~ s/\D//;
4515 (my $c = $critical) =~ s/\D//;
4516 my $SQL = q{
4517SELECT pages, maxx, ROUND(100*(pages/maxx)) AS percent
4518FROM
4519 (SELECT (sumrequests+numrels)*chunkpages AS pages
4520 FROM (SELECT SUM(CASE WHEN avgrequest IS NULL
4521 THEN interestingpages/32 ELSE interestingpages/16 END) AS sumrequests,
4522 COUNT(relfilenode) AS numrels, 16 AS chunkpages FROM pg_freespacemap_relations) AS foo) AS foo2,
4523 (SELECT setting::NUMERIC AS maxx FROM pg_settings WHERE name = 'max_fsm_pages') AS foo3
4524};
4525 my $SQLNOOP = q{SELECT 'FAIL' AS fail};
4526
4527 my $info = run_command($SQL, { version => [ ">8.3 $SQLNOOP" ] } );
4528
4529 if (exists $info->{db}[0]{slurp}[0]{fail}) {
4530 add_unknown msg('fsm-page-highver');
4531 return;
4532 }
4533
4534 for $db (@{$info->{db}}) {
4535 for my $r (@{$db->{slurp}}) {
4536 my ($pages,$max,$percent) = ($r->{pages}||0,$r->{maxx},$r->{percent}||0);
4537
4538 $MRTG and do_mrtg({one => $percent, two => $pages});
4539
4540 my $msg = msg('fsm-page-msg', $pages, $max, $percent);
4541
4542 if (length $critical and $percent >= $c) {
4543 add_critical $msg;
4544 }
4545 elsif (length $warning and $percent >= $w) {
4546 add_warning $msg;
4547 }
4548 else {
4549 add_ok $msg;
4550 }
4551 }
4552 }
4553
4554 return;
4555
4556} ## end of check_fsm_pages
4557
4558
4559sub check_fsm_relations {
4560
4561 ## Check on the % of free space map relations in use
4562 ## Supports: Nagios, MRTG
4563 ## Must run as superuser
4564 ## Requires pg_freespacemap contrib module
4565 ## Critical and warning are a percentage of max_fsm_relations
4566 ## Example: --critical=95
4567
4568 my ($warning, $critical) = validate_range
4569 ({
4570 type => 'percent',
4571 default_warning => '85%',
4572 default_critical => '95%',
4573 });
4574
4575 (my $w = $warning) =~ s/\D//;
4576 (my $c = $critical) =~ s/\D//;
4577
4578 my $SQL = q{
4579SELECT maxx, cur, ROUND(100*(cur/maxx)) AS percent
4580FROM (SELECT
4581 (SELECT COUNT(*) FROM pg_freespacemap_relations) AS cur,
4582 (SELECT setting::NUMERIC FROM pg_settings WHERE name='max_fsm_relations') AS maxx) x
4583};
4584 my $SQLNOOP = q{SELECT 'FAIL' AS fail};
4585
4586 my $info = run_command($SQL, { version => [ ">8.3 $SQLNOOP" ] } );
4587
4588 if (exists $info->{db}[0]{slurp}[0]{fail}) {
4589 add_unknown msg('fsm-rel-highver');
4590 return;
4591 }
4592
4593 for $db (@{$info->{db}}) {
4594
4595 for my $r (@{$db->{slurp}}) {
4596 my ($max,$cur,$percent) = ($r->{maxx},$r->{cur},$r->{percent}||0);
4597
4598 $MRTG and do_mrtg({one => $percent, two => $cur});
4599
4600 my $msg = msg('fsm-rel-msg', $cur, $max, $percent);
4601
4602 if (length $critical and $percent >= $c) {
4603 add_critical $msg;
4604 }
4605 elsif (length $warning and $percent >= $w) {
4606 add_warning $msg;
4607 }
4608 else {
4609 add_ok $msg;
4610 }
4611 }
4612
4613 }
4614
4615 return;
4616
4617} ## end of check_fsm_relations
4618
4619
4620sub check_hitratio {
4621
4622 ## Check the hitratio of one or more databases
4623 ## Supports: Nagios, MRTG
4624 ## mrtg reports the largest two databases
4625 ## By default, checks all databases
4626 ## Can check specific one(s) with include
4627 ## Can ignore some with exclude
4628 ## Warning and criticals are percentages
4629 ## Limit to a specific user (db owner) with the includeuser option
4630 ## Exclude users with the excludeuser option
4631
4632 my ($warning, $critical) = validate_range({type => 'percent'});
4633
4634 $SQL = qq{
4635SELECT
4636 round(100.*sd.blks_hit/(sd.blks_read+sd.blks_hit), 2) AS dhitratio,
4637 d.datname,
4638 u.usename
4639FROM pg_stat_database sd
4640JOIN pg_database d ON (d.oid=sd.datid)
4641JOIN pg_user u ON (u.usesysid=d.datdba)
4642WHERE sd.blks_read+sd.blks_hit<>0
4643$USERWHERECLAUSE
4644};
4645 if ($opt{perflimit}) {
4646 $SQL .= " ORDER BY 1 DESC LIMIT $opt{perflimit}";
4647 }
4648
4649 my $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1, } );
4650 my $found = 0;
4651
4652 for $db (@{$info->{db}}) {
4653 my $min = 101;
4654 $found = 1;
4655 my %s;
4656 for my $r (@{$db->{slurp}}) {
4657
4658 next if skip_item($r->{datname});
4659
4660 if ($r->{dhitratio} <= $min) {
4661 $min = $r->{dhitratio};
4662 }
4663 $s{$r->{datname}} = $r->{dhitratio};
4664 }
4665
4666 if ($MRTG) {
4667 do_mrtg({one => $min, msg => "DB: $db->{dbname}"});
4668 }
4669 if ($min > 100) {
4670 $stats{$db->{dbname}} = 0;
4671 if ($USERWHERECLAUSE) {
4672 add_ok msg('no-match-user');
4673 }
4674 else {
4675 add_unknown msg('no-match-db');
4676 }
4677 next;
4678 }
4679
4680 my $msg = '';
4681 for (reverse sort {$s{$b} <=> $s{$a} or $a cmp $b } keys %s) {
4682 $msg .= "$_: $s{$_} ";
4683 $db->{perf} .= sprintf ' %s=%s;%s;%s',
4684 perfname($_), $s{$_}, $warning, $critical;
4685 }
4686 if (length $critical and $min <= $critical) {
4687 add_critical $msg;
4688 }
4689 elsif (length $warning and $min <= $warning) {
4690 add_warning $msg;
4691 }
4692 else {
4693 add_ok $msg;
4694 }
4695 }
4696
4697 ## If no results, probably a version problem
4698 if (!$found and keys %unknown) {
4699 (my $first) = values %unknown;
4700 if ($first->[0][0] =~ /pg_database_size/) {
4701 ndie msg('dbsize-version');
4702 }
4703 }
4704
4705 return;
4706
4707} ## end of check_hitratio
4708
4709
4710sub check_hot_standby_delay {
4711
4712 ## Check on the delay in PITR replication between master and slave
4713 ## Supports: Nagios, MRTG
4714 ## Critical and warning are the delay between master and slave xlog locations
4715 ## Example: --critical=1024
4716
4717 my ($warning, $critical) = validate_range({type => 'integer', leastone => 1});
4718
4719 # check if master and slave comply with the check using pg_is_in_recovery()
4720 my ($master, $slave);
4721 $SQL = q{SELECT pg_is_in_recovery() AS recovery;};
4722
4723 # Check if master is online (e.g. really a master)
4724 for my $x (1..2) {
4725 my $info = run_command($SQL, { dbnumber => $x, regex => qr(t|f) });
4726
4727 for $db (@{$info->{db}}) {
4728 my $status = $db->{slurp}[0];
4729 if ($status->{recovery} eq 't') {
4730 $slave = $x;
4731 last;
4732 }
4733 if ($status->{recovery} eq 'f') {
4734 $master = $x;
4735 last;
4736 }
4737 }
4738 }
4739 if (! defined $slave and ! defined $master) {
4740 add_unknown msg('hs-no-role');
4741 return;
4742 }
4743
4744 ## If the slave is "db1" and master "db2", go ahead and switch them around for clearer output
4745 if (1 == $slave) {
4746 ($slave, $master) = (2, 1);
4747 for my $k (qw(host port dbname dbuser dbpass)) {
4748 ($opt{$k}, $opt{$k . 2}) = ($opt{$k . 2}, $opt{$k}); ## no critic (ProhibitMismatchedOperators)
4749 }
4750 }
4751
4752 ## Get xlog positions
4753 my ($moffset, $s_rec_offset, $s_rep_offset);
4754 ## On master
4755 $SQL = q{SELECT pg_current_xlog_location() AS location};
4756 my $info = run_command($SQL, { dbnumber => $master });
4757 my $saved_db;
4758 for $db (@{$info->{db}}) {
4759 my $location = $db->{slurp}[0]{location};
4760 next if ! defined $location;
4761
4762 my ($x, $y) = split(/\//, $location);
4763 $moffset = (hex('ff000000') * hex($x)) + hex($y);
4764 $saved_db = $db if ! defined $saved_db;
4765 }
4766
4767 if (! defined $moffset) {
4768 add_unknown msg('hs-no-location', 'master');
4769 return;
4770 }
4771
4772 ## On slave
4773 $SQL = q{SELECT pg_last_xlog_receive_location() AS receive, pg_last_xlog_replay_location() AS replay};
4774
4775 $info = run_command($SQL, { dbnumber => $slave, regex => qr/\// });
4776
4777 for $db (@{$info->{db}}) {
4778 my $receive = $db->{slurp}[0]{receive};
4779 my $replay = $db->{slurp}[0]{replay};
4780
4781 if (defined $receive) {
4782 my ($a, $b) = split(/\//, $receive);
4783 $s_rec_offset = (hex('ff000000') * hex($a)) + hex($b);
4784 }
4785
4786 if (defined $replay) {
4787 my ($a, $b) = split(/\//, $replay);
4788 $s_rep_offset = (hex('ff000000') * hex($a)) + hex($b);
4789 }
4790
4791 $saved_db = $db if ! defined $saved_db;
4792 }
4793
4794 if (! defined $s_rec_offset and ! defined $s_rep_offset) {
4795 add_unknown msg('hs-no-location', 'slave');
4796 return;
4797 }
4798
4799 ## Compute deltas
4800 $db = $saved_db;
4801 my $rec_delta = $moffset - $s_rec_offset;
4802 my $rep_delta = $moffset - $s_rep_offset;
4803
4804 $MRTG and do_mrtg({one => $rep_delta, two => $rec_delta});
4805
4806 $db->{perf} = sprintf ' %s=%s;%s;%s ',
4807 perfname(msg('hs-replay-delay')), $rep_delta, $warning, $critical;
4808 $db->{perf} .= sprintf ' %s=%s;%s;%s',
4809 perfname(msg('hs-receive-delay')), $rec_delta, $warning, $critical;
4810
4811 ## Do the check on replay delay in case SR has disconnected because it way too far behind
4812 my $msg = qq{$rep_delta};
4813 if (length $critical and $rep_delta > $critical) {
4814 add_critical $msg;
4815 }
4816 elsif (length $warning and $rep_delta > $warning) {
4817 add_warning $msg;
4818 }
4819 else {
4820 add_ok $msg;
4821 }
4822
4823 return;
4824
4825} ## end of check_hot_standby_delay
4826
4827
4828sub check_last_analyze {
4829 my $auto = shift || '';
4830 return check_last_vacuum_analyze('analyze', $auto);
4831}
4832
4833
4834sub check_last_vacuum {
4835 my $auto = shift || '';
4836 return check_last_vacuum_analyze('vacuum', $auto);
4837}
4838
4839
4840sub check_last_vacuum_analyze {
4841
4842 my $type = shift || 'vacuum';
4843 my $auto = shift || 0;
4844
4845 ## Check the last time things were vacuumed or analyzed
4846 ## Supports: Nagios, MRTG
4847 ## NOTE: stats_row_level must be set to on in your database (if version 8.2)
4848 ## By default, reports on the oldest value in the database
4849 ## Can exclude and include tables
4850 ## Warning and critical are times, default to seconds
4851 ## Valid units: s[econd], m[inute], h[our], d[ay]
4852 ## All above may be written as plural as well (e.g. "2 hours")
4853 ## Limit to a specific user (relation owner) with the includeuser option
4854 ## Exclude users with the excludeuser option
4855 ## Example:
4856 ## --exclude=~pg_ --include=pg_class,pg_attribute
4857
4858 my ($warning, $critical) = validate_range
4859 ({
4860 type => 'time',
4861 default_warning => '1 day',
4862 default_critical => '2 days',
4863 });
4864
4865 my $criteria = $auto ?
4866 qq{pg_stat_get_last_auto${type}_time(c.oid)}
4867 : qq{GREATEST(pg_stat_get_last_${type}_time(c.oid), pg_stat_get_last_auto${type}_time(c.oid))};
4868
4869 ## Do include/exclude earlier for large pg_classes?
4870 $SQL = qq{
4871SELECT current_database() AS datname, nspname AS sname, relname AS tname,
4872 CASE WHEN v IS NULL THEN -1 ELSE round(extract(epoch FROM now()-v)) END AS ltime,
4873 CASE WHEN v IS NULL THEN '?' ELSE TO_CHAR(v, '$SHOWTIME') END AS ptime
4874FROM (SELECT nspname, relname, $criteria AS v
4875 FROM pg_class c, pg_namespace n
4876 WHERE relkind = 'r'
4877 AND n.oid = c.relnamespace
4878 AND n.nspname <> 'information_schema'
4879 ORDER BY 3) AS foo
4880};
4881 if ($opt{perflimit}) {
4882 $SQL .= ' ORDER BY 4 DESC';
4883 }
4884
4885 if ($USERWHERECLAUSE) {
4886 $SQL =~ s/ WHERE/, pg_user u WHERE u.usesysid=c.relowner$USERWHERECLAUSE AND/;
4887 }
4888
4889 my $info = run_command($SQL, { regex => qr{\w}, emptyok => 1 } );
4890
4891 for $db (@{$info->{db}}) {
4892
4893 if (! @{$db->{slurp}} and $USERWHERECLAUSE) {
4894 $stats{$db->{dbname}} = 0;
4895 add_ok msg('no-match-user');
4896 return;
4897 }
4898
4899 ## -1 means no tables found at all
4900 ## -2 means exclusion rules took effect
4901 ## -3 means no tables were ever vacuumed/analyzed
4902 my $maxtime = -1;
4903 my $maxptime = '?';
4904 my ($minrel,$maxrel) = ('?','?'); ## no critic
4905 my $mintime = 0; ## used for MRTG only
4906 my $count = 0;
4907 my $found = 0;
4908 ROW: for my $r (@{$db->{slurp}}) {
4909 my ($dbname,$schema,$name,$time,$ptime) = @$r{qw/ datname sname tname ltime ptime/};
4910 if (skip_item($name, $schema)) {
4911 $maxtime = -2 if $maxtime < 1;
4912 next ROW;
4913 }
4914 $found++;
4915 if ($time >= 0) {
4916 $db->{perf} .= sprintf ' %s=%ss;%s;%s',
4917 perfname("$dbname.$schema.$name"),$time, $warning, $critical;
4918 }
4919 if ($time > $maxtime) {
4920 $maxtime = $time;
4921 $maxrel = "DB: $dbname TABLE: $schema.$name";
4922 $maxptime = $ptime;
4923 }
4924 if ($time > 0 and ($time < $mintime or !$mintime)) {
4925 $mintime = $time;
4926 $minrel = "DB: $dbname TABLE: $schema.$name";
4927 }
4928 if ($opt{perflimit}) {
4929 last if ++$count >= $opt{perflimit};
4930 }
4931 }
4932 if ($MRTG) {
4933 $maxrel eq '?' and $maxrel = "DB: $db->{dbname} TABLE: ?";
4934 do_mrtg({one => $mintime, msg => $maxrel});
4935 return;
4936 }
4937 if ($maxtime == -2) {
4938 add_unknown (
4939 $found ? $type eq 'vacuum' ? msg('vac-nomatch-v')
4940 : msg('vac-nomatch-a')
4941 : msg('no-match-table') ## no critic (RequireTrailingCommaAtNewline)
4942 );
4943 }
4944 elsif ($maxtime < 0) {
4945 add_unknown $type eq 'vacuum' ? msg('vac-nomatch-v') : msg('vac-nomatch-a');
4946 }
4947 else {
4948 my $showtime = pretty_time($maxtime, 'S');
4949 my $msg = "$maxrel: $maxptime ($showtime)";
4950 if ($critical and $maxtime >= $critical) {
4951 add_critical $msg;
4952 }
4953 elsif ($warning and $maxtime >= $warning) {
4954 add_warning $msg;
4955 }
4956 else {
4957 add_ok $msg;
4958 }
4959 }
4960 }
4961
4962 return;
4963
4964} ## end of check_last_vacuum_analyze
4965
4966
4967sub check_listener {
4968
4969 ## Check for a specific listener
4970 ## Supports: Nagios, MRTG
4971 ## Critical and warning are simple strings, or regex if starts with a ~
4972 ## Example: --critical="~bucardo"
4973
4974 if ($MRTG and exists $opt{mrtg}) {
4975 $opt{critical} = $opt{mrtg};
4976 }
4977
4978 my ($warning, $critical) = validate_range({type => 'restringex', forcemrtg => 1});
4979
4980 my $string = length $critical ? $critical : $warning;
4981 my $regex = ($string =~ s/^~//) ? '~' : '=';
4982
4983 $SQL = "SELECT count(*) AS c FROM pg_listener WHERE relname $regex '$string'";
4984 my $info = run_command($SQL);
4985
4986 for $db (@{$info->{db}}) {
4987 if ($db->{slurp}[0]{c} !~ /(\d+)/) {
4988 add_unknown msg('invalid-query', $db->{slurp});
4989 next;
4990 }
4991 my $count = $1;
4992 if ($MRTG) {
4993 do_mrtg({one => $count});
4994 }
4995 $db->{perf} .= sprintf '%s=%s',
4996 perfname(msg('listening')), $count;
4997 my $msg = msg('listener-msg', $count);
4998 if ($count >= 1) {
4999 add_ok $msg;
5000 }
5001 elsif ($critical) {
5002 add_critical $msg;
5003 }
5004 else {
5005 add_warning $msg;
5006 }
5007 }
5008 return;
5009
5010} ## end of check_listener
5011
5012
5013sub check_locks {
5014
5015 ## Check the number of locks
5016 ## Supports: Nagios, MRTG
5017 ## By default, checks all databases
5018 ## Can check specific databases with include
5019 ## Can ignore databases with exclude
5020 ## Warning and critical are either simple numbers, or more complex:
5021 ## Use locktype=number:locktype2=number
5022 ## The locktype can be "total", "waiting", or the name of a lock
5023 ## Lock names are case-insensitive, and do not need the "lock" at the end.
5024 ## Example: --warning=100 --critical="total=200;exclusive=20;waiting=5"
5025
5026 my ($warning, $critical) = validate_range
5027 ({
5028 type => 'multival',
5029 default_warning => 100,
5030 default_critical => 150,
5031 });
5032
5033 $SQL = q{SELECT granted, mode, datname FROM pg_locks l RIGHT JOIN pg_database d ON (d.oid=l.database) WHERE d.datallowconn};
5034 my $info = run_command($SQL, { regex => qr[\s*\w+\s*\|\s*] });
5035
5036 # Locks are counted globally not by db.
5037 # We output for each db, following the specific warning and critical :
5038 # time=00.1 foodb.exclusive=2;;3 foodb.total=10;;30 postgres.exclusive=0;;3 postgres.total=1;;3
5039 for $db (@{$info->{db}}) {
5040 my $gotone = 0;
5041 my %dblock;
5042 my %totallock = (total => 0);
5043 ROW: for my $r (@{$db->{slurp}}) {
5044 my ($granted,$mode,$dbname) = ($r->{granted}, lc $r->{mode}, $r->{datname});
5045
5046 ## May be forcibly skipping this database via arguments
5047 next ROW if skip_item($dbname);
5048
5049 ## If we hit the right join, simply make an empty total entry
5050 if (! length $granted) {
5051 $dblock{$dbname}{total} ||= 0;
5052 }
5053 else {
5054 $dblock{$dbname}{total}++;
5055 $gotone = 1;
5056 $mode =~ s{lock$}{};
5057 $dblock{$dbname}{$mode}++;
5058 $dblock{$dbname}{waiting}++ if $granted ne 't';
5059 }
5060 }
5061
5062 # Compute total, add hash key for critical and warning specific check
5063 for my $k (keys %dblock) {
5064
5065 if ($warning) {
5066 for my $l (keys %{$warning}) {
5067 $dblock{$k}{$l} = 0 if ! exists $dblock{$k}{$l};
5068 }
5069 }
5070 if ($critical) {
5071 for my $l (keys %{$critical}) {
5072 #$dblock{$k}{$l} = 0 if ! exists $dblock{$k}{$l};
5073 }
5074 }
5075 for my $m (keys %{$dblock{$k}}){
5076 $totallock{$m} += $dblock{$k}{$m};
5077 }
5078 }
5079
5080 if ($MRTG) {
5081 do_mrtg( {one => $totallock{total}, msg => "DB: $db->{dbname}" } );
5082 }
5083
5084 # Nagios perfdata output
5085 for my $dbname (sort keys %dblock) {
5086 for my $type (sort keys %{ $dblock{$dbname} }) {
5087 next if ((! $critical or ! exists $critical->{$type})
5088 and (!$warning or ! exists $warning->{$type}));
5089 $db->{perf} .= sprintf ' %s=%s;',
5090 perfname("$dbname.$type"), $dblock{$dbname}{$type};
5091 if ($warning and exists $warning->{$type}) {
5092 $db->{perf} .= $warning->{$type};
5093 }
5094 if ($critical and $critical->{$type}) {
5095 $db->{perf} .= ";$critical->{$type}";
5096 }
5097 }
5098 }
5099
5100 if (!$gotone) {
5101 add_unknown msg('no-match-db');
5102 next;
5103 }
5104
5105 ## If not specific errors, just use the total
5106 my $ok = 1;
5107 for my $type (keys %totallock) {
5108 if ($critical and exists $critical->{$type} and $totallock{$type} >= $critical->{$type}) {
5109 ($type eq 'total')
5110 ? add_critical msg('locks-msg2', $totallock{total})
5111 : add_critical msg('locks-msg', $type, $totallock{$type});
5112 $ok = 0;
5113 }
5114 if ($warning and exists $warning->{$type} and $totallock{$type} >= $warning->{$type}) {
5115 ($type eq 'total')
5116 ? add_warning msg('locks-msg2', $totallock{total})
5117 : add_warning msg('locks-msg', $type, $totallock{$type});
5118 $ok = 0;
5119 }
5120 }
5121 if ($ok) {
5122 my %show;
5123 if (!keys %critical and !keys %warning) {
5124 $show{total} = 1;
5125 }
5126 for my $type (keys %critical) {
5127 $show{$type} = 1;
5128 }
5129 for my $type (keys %warning) {
5130 $show{$type} = 1;
5131 }
5132 my $msg = '';
5133 for (sort keys %show) {
5134 $msg .= sprintf "$_=%d ", $totallock{$_} || 0;
5135 }
5136 add_ok $msg;
5137 }
5138 }
5139
5140 return;
5141
5142} ## end of check_locks
5143
5144
5145sub check_logfile {
5146
5147 ## Make sure the logfile is getting written to
5148 ## Supports: Nagios, MRTG
5149 ## Especially useful for syslog redirectors
5150 ## Should be run on the system housing the logs
5151 ## Optional argument "logfile" tells where the logfile is
5152 ## Allows for some conversion characters.
5153 ## Example: --logfile="/syslog/%Y-m%-d%/H%/postgres.log"
5154 ## Critical and warning are not used: it's either ok or critical.
5155
5156 my $critwarn = $opt{warning} ? 0 : 1;
5157
5158 $SQL = q{
5159SELECT name, CASE WHEN length(setting)<1 THEN '?' ELSE setting END AS s
5160FROM pg_settings
5161WHERE name IN ('log_destination','log_directory','log_filename','redirect_stderr','syslog_facility')
5162ORDER BY name
5163};
5164
5165 my $logfilere = qr{^[\w_\s\/%\-\.]+$};
5166 if (exists $opt{logfile} and $opt{logfile} !~ $logfilere) {
5167 ndie msg('logfile-opt-bad');
5168 }
5169
5170 my $info = run_command($SQL);
5171 $VERBOSE >= 3 and warn Dumper $info;
5172
5173 for $db (@{$info->{db}}) {
5174 my $i;
5175 for my $r (@{$db->{slurp}}) {
5176 $i->{$r->{name}} = $r->{s} || '?';
5177 }
5178 for my $word (qw{ log_destination log_directory log_filename redirect_stderr syslog_facility }) {
5179 $i->{$word} = '?' if ! exists $i->{$word};
5180 }
5181
5182 ## Figure out what we think the log file will be
5183 my $logfile ='';
5184 if (exists $opt{logfile} and $opt{logfile} =~ /\w/) {
5185 $logfile = $opt{logfile};
5186 }
5187 else {
5188 if ($i->{log_destination} eq 'syslog') {
5189 ## We'll make a best effort to figure out where it is. Using the --logfile option is preferred.
5190 $logfile = '/var/log/messages';
5191 if (open my $cfh, '<', '/etc/syslog.conf') {
5192 while (<$cfh>) {
5193 if (/\b$i->{syslog_facility}\.(?!none).+?([\w\/]+)$/i) {
5194 $logfile = $1;
5195 }
5196 }
5197 }
5198 if (!$logfile or ! -e $logfile) {
5199 ndie msg('logfile-syslog', $i->{syslog_facility});
5200 }
5201 }
5202 elsif ($i->{log_destination} eq 'stderr') {
5203 if ($i->{redirect_stderr} ne 'yes') {
5204 ndie msg('logfile-stderr');
5205 }
5206 }
5207 }
5208
5209 ## We now have a logfile (or a template)..parse it into pieces.
5210 ## We need at least hour, day, month, year
5211 my @t = localtime;
5212 my ($H,$d,$m,$Y) = (sprintf ('%02d',$t[2]),sprintf('%02d',$t[3]),sprintf('%02d',$t[4]+1),$t[5]+1900);
5213 my $y = substr($Y,2,4);
5214 if ($logfile !~ $logfilere) {
5215 ndie msg('logfile-bad',$logfile);
5216 }
5217 $logfile =~ s/%%/~~/g;
5218 $logfile =~ s/%Y/$Y/g;
5219 $logfile =~ s/%y/$y/g;
5220 $logfile =~ s/%m/$m/g;
5221 $logfile =~ s/%d/$d/g;
5222 $logfile =~ s/%H/$H/g;
5223
5224 $VERBOSE >= 3 and warn msg('logfile-debug', $logfile);
5225
5226 if (! -e $logfile) {
5227 my $msg = msg('logfile-dne', $logfile);
5228 $MRTG and ndie $msg;
5229 if ($critwarn) {
5230 add_unknown $msg;
5231 }
5232 else {
5233 add_warning $msg;
5234 }
5235 next;
5236 }
5237 my $logfh;
5238 unless (open $logfh, '<', $logfile) {
5239 add_unknown msg('logfile-openfail', $logfile, $!);
5240 next;
5241 }
5242 seek($logfh, 0, 2) or ndie msg('logfile-seekfail', $logfile, $!);
5243
5244 ## Throw a custom error string.
5245 ## We do the number first as old versions only show part of the string.
5246 my $random_number = int rand(999999999999);
5247 my $funky = sprintf "check_postgres_logfile_error_$random_number $ME DB=$db->{dbname} PID=$$ Time=%s",
5248 scalar localtime;
5249
5250 ## Cause an error on just this target
5251 delete @{$db}{qw(ok slurp totaltime)};
5252 my $badinfo = run_command("$funky", {failok => 1, target => $db} );
5253
5254 my $MAXSLEEPTIME = $opt{timeout} || 20;
5255 my $SLEEP = 1;
5256 my $found = 0;
5257 LOGWAIT: {
5258 sleep $SLEEP;
5259 seek $logfh, 0, 1 or ndie msg('logfile-seekfail', $logfile, $!);
5260 while (<$logfh>) {
5261 if (/logfile_error_$random_number/) { ## Some logs break things up, so we don't use funky
5262 $found = 1;
5263 last LOGWAIT;
5264 }
5265 }
5266 $MAXSLEEPTIME -= $SLEEP;
5267 redo if $MAXSLEEPTIME > 0;
5268 my $msg = msg('logfile-fail', $logfile);
5269 $MRTG and do_mrtg({one => 0, msg => $msg});
5270 if ($critwarn) {
5271 add_critical $msg;
5272 }
5273 else {
5274 add_warning $msg;
5275 }
5276 }
5277 close $logfh or ndie msg('file-noclose', $logfile, $!);
5278
5279 if ($found == 1) {
5280 $MRTG and do_mrtg({one => 1});
5281 add_ok msg('logfile-ok', $logfile);
5282 }
5283 }
5284 return;
5285
5286} ## end of check_logfile
5287
5288
5289sub find_new_version {
5290
5291 ## Check for newer versions of some program
5292
5293 my $program = shift or die;
5294 my $exec = shift or die;
5295 my $url = shift or die;
5296
5297 ## The format is X.Y.Z [optional message]
5298 my $versionre = qr{((\d+)\.(\d+)\.(\d+))\s*(.*)};
5299 my ($cversion,$cmajor,$cminor,$crevision,$cmessage) = ('','','','','');
5300 my $found = 0;
5301
5302 ## Try to fetch the current version from the web
5303 for my $meth (@get_methods) {
5304 eval {
5305 my $COM = "$meth $url";
5306 $VERBOSE >= 1 and warn "TRYING: $COM\n";
5307 my $info = qx{$COM 2>/dev/null};
5308 ## Postgres is slightly different
5309 if ($program eq 'Postgres') {
5310 $cmajor = {};
5311 while ($info =~ /<title>(\d+)\.(\d+)\.(\d+)/g) {
5312 $found = 1;
5313 $cmajor->{"$1.$2"} = $3;
5314 }
5315 }
5316 elsif ($info =~ $versionre) {
5317 $found = 1;
5318 ($cversion,$cmajor,$cminor,$crevision,$cmessage) = ($1, int $2, int $3, int $4, $5);
5319 if ($VERBOSE >= 1) {
5320 $info =~ s/\s+$//s;
5321 warn "Remote version string: $info\n";
5322 warn "Remote version: $cversion\n";
5323 }
5324 }
5325 };
5326 last if $found;
5327 }
5328
5329 if (! $found) {
5330 add_unknown msg('new-ver-nocver', $program);
5331 return;
5332 }
5333
5334 ## Figure out the local copy's version
5335 my $output;
5336 eval {
5337 ## We may already know the version (e.g. ourselves)
5338 $output = ($exec =~ /\d+\.\d+/) ? $exec : qx{$exec --version 2>&1};
5339 };
5340 if ($@ or !$output) {
5341 if ($program eq 'tail_n_mail') {
5342 ## Check for the old name
5343 eval {
5344 $output = qx{tail_n_mail.pl --version 2>&1};
5345 };
5346 }
5347 if ($@ or !$output) {
5348 add_unknown msg('new-ver-badver', $program);
5349 return;
5350 }
5351 }
5352
5353 if ($output !~ $versionre) {
5354 add_unknown msg('new-ver-nolver', $program);
5355 return;
5356 }
5357 my ($lversion,$lmajor,$lminor,$lrevision) = ($1, int $2, int $3, int $4);
5358 if ($VERBOSE >= 1) {
5359 $output =~ s/\s+$//s;
5360 warn "Local version string: $output\n";
5361 warn "Local version: $lversion\n";
5362 }
5363
5364 ## Postgres is a special case
5365 if ($program eq 'Postgres') {
5366 my $lver = "$lmajor.$lminor";
5367 if (! exists $cmajor->{$lver}) {
5368 add_unknown msg('new-ver-nocver', $program);
5369 return;
5370 }
5371 $crevision = $cmajor->{$lver};
5372 $cmajor = $lmajor;
5373 $cminor = $lminor;
5374 $cversion = "$cmajor.$cminor.$crevision";
5375 }
5376
5377 ## Most common case: everything matches
5378 if ($lversion eq $cversion) {
5379 add_ok msg('new-ver-ok', $lversion, $program);
5380 return;
5381 }
5382
5383 ## Check for a revision update
5384 if ($lmajor==$cmajor and $lminor==$cminor and $lrevision<$crevision) {
5385 add_critical msg('new-ver-warn', $cversion, $program, $lversion);
5386 return;
5387 }
5388
5389 ## Check for a major update
5390 if ($lmajor<$cmajor or ($lmajor==$cmajor and $lminor<$cminor)) {
5391 add_warning msg('new-ver-warn', $cversion, $program, $lversion);
5392 return;
5393 }
5394
5395 ## Anything else must be time travel, which we cannot handle
5396 add_unknown msg('new-ver-tt', $program, $lversion, $cversion);
5397 return;
5398
5399} ## end of find_new_version
5400
5401
5402sub check_new_version_bc {
5403
5404 ## Check if a newer version of Bucardo is available
5405
5406 my $url = 'http://bucardo.org/bucardo/latest_version.txt';
5407 find_new_version('Bucardo', 'bucardo_ctl', $url);
5408
5409 return;
5410
5411} ## end of check_new_version_bc
5412
5413
5414sub check_new_version_box {
5415
5416 ## Check if a newer version of boxinfo is available
5417
5418 my $url = 'http://bucardo.org/boxinfo/latest_version.txt';
5419 find_new_version('boxinfo', 'boxinfo.pl', $url);
5420
5421 return;
5422
5423} ## end of check_new_version_box
5424
5425
5426sub check_new_version_cp {
5427
5428 ## Check if a new version of check_postgres.pl is available
5429
5430 my $url = 'http://bucardo.org/check_postgres/latest_version.txt';
5431 find_new_version('check_postgres', $VERSION, $url);
5432
5433 return;
5434
5435} ## end of check_new_version_cp
5436
5437
5438sub check_new_version_pg {
5439
5440 ## Check if a new version of Postgres is available
5441
5442 my $url = 'http://www.postgresql.org/versions.rss';
5443
5444 ## Grab the local version
5445 my $info = run_command('SELECT version() AS version');
5446 my $lversion = $info->{db}[0]{slurp}[0]{version};
5447 ## Make sure it is parseable and check for development versions
5448 if ($lversion !~ /\d+\.\d+\.\d+/) {
5449 if ($lversion =~ /(\d+\.\d+\S+)/) {
5450 add_ok msg('new-ver-dev', 'Postgres', $1);
5451 return;
5452 }
5453 add_unknown msg('new-ver-nolver', 'Postgres');
5454 return;
5455 }
5456
5457 find_new_version('Postgres', $lversion, $url);
5458
5459 return;
5460
5461} ## end of check_new_version_pg
5462
5463
5464sub check_new_version_tnm {
5465
5466 ## Check if a new version of tail_n_mail is available
5467
5468 my $url = 'http://bucardo.org/tail_n_mail/latest_version.txt';
5469 find_new_version('tail_n_mail', 'tail_n_mail', $url);
5470
5471 return;
5472
5473} ## end of check_new_version_tnm
5474
5475
5476sub check_pgagent_jobs {
5477 ## Check for failed pgAgent jobs.
5478 ## Supports: Nagios
5479 ## Critical and warning are intervals.
5480 ## Example: --critical="1 hour"
5481 ## Example: --warning="2 hours"
5482
5483 my ($warning, $critical) = validate_range({ type => 'time', any_warning => 1 });
5484
5485 # Determine critcal warning column contents.
5486 my $is_crit = $critical && $warning
5487 ? "GREATEST($critical - EXTRACT('epoch' FROM NOW() - (jlog.jlgstart + jlog.jlgduration)), 0)"
5488 : $critical ? 1 : 0;
5489
5490 # Determine max time to examine.
5491 my $seconds = do {
5492 no warnings;
5493 $warning > $critical ? $warning : $critical;
5494 };
5495
5496 $SQL = qq{
5497 SELECT jlog.jlgid
5498 , job.jobname
5499 , step.jstname
5500 , slog.jslresult
5501 , slog.jsloutput
5502 , $is_crit AS critical
5503 FROM pgagent.pga_job job
5504 JOIN pgagent.pga_joblog jlog ON job.jobid = jlog.jlgjobid
5505 JOIN pgagent.pga_jobstep step ON job.jobid = step.jstjobid
5506 JOIN pgagent.pga_jobsteplog slog ON jlog.jlgid = slog.jsljlgid AND step.jstid = slog.jsljstid
5507 WHERE slog.jslresult <> 0
5508 AND EXTRACT('epoch' FROM NOW() - (jlog.jlgstart + jlog.jlgduration)) < $seconds
5509 };
5510
5511 my $info = run_command($SQL);
5512
5513 for $db (@{$info->{db}}) {
5514 my @rows = @{ $db->{slurp} } or do {
5515 add_ok msg('pgagent-jobs-ok');
5516 next;
5517 };
5518
5519 if ($rows[0]{critical} !~ /^(?:[01]|\d+[.]\d+)$/) {
5520 add_unknown msg('invalid-query', $db->{slurp});
5521 next;
5522 }
5523
5524 my ($is_crit, @msg);
5525 my $log_id = -1;
5526 for my $step (@rows) {
5527 my $output = $step->{jsloutput} || '(NO OUTPUT)';
5528 push @msg => "$step->{jslresult} $step->{jobname}/$step->{jstname}: $output";
5529 $is_crit ||= $step->{critical};
5530 }
5531
5532 (my $msg = join '; ' => @msg) =~ s{\r?\n}{ }g;
5533 if ($is_crit) {
5534 add_critical $msg;
5535 } else {
5536 add_warning $msg;
5537 }
5538 }
5539
5540 return;
5541}
5542
5543sub check_pgbouncer_checksum {
5544
5545 ## Verify the checksum of all pgbouncer settings
5546 ## Supports: Nagios, MRTG
5547 ## Not that the connection will be done on the pgbouncer database
5548 ## One of warning or critical must be given (but not both)
5549 ## It should run one time to find out the expected checksum
5550 ## You can use --critical="0" to find out the checksum
5551 ## You can include or exclude settings as well
5552 ## Example:
5553 ## check_postgres_pgbouncer_checksum --critical="4e7ba68eb88915d3d1a36b2009da4acd"
5554
5555 my ($warning, $critical) = validate_range({type => 'checksum', onlyone => 1});
5556
5557 eval {
5558 require Digest::MD5;
5559 };
5560 if ($@) {
5561 ndie msg('checksum-nomd');
5562 }
5563
5564 $SQL = 'SHOW CONFIG';
5565 my $info = run_command($SQL, { regex => qr[log_pooler_errors] });
5566
5567 $db = $info->{db}[0];
5568
5569 my $newstring = '';
5570 for my $r (@{$db->{slurp}}) {
5571 my $key = $r->{key};
5572 next if skip_item($key);
5573 $newstring .= "$r->{key} = $r->{value}\n";
5574 }
5575
5576 if (! length $newstring) {
5577 add_unknown msg('no-match-set');
5578 }
5579
5580 my $checksum = Digest::MD5::md5_hex($newstring);
5581
5582 my $msg = msg('checksum-msg', $checksum);
5583 if ($MRTG) {
5584 $opt{mrtg} or ndie msg('checksum-nomrtg');
5585 do_mrtg({one => $opt{mrtg} eq $checksum ? 1 : 0, msg => $checksum});
5586 }
5587 if ($critical and $critical ne $checksum) {
5588 add_critical $msg;
5589 }
5590 elsif ($warning and $warning ne $checksum) {
5591 add_warning $msg;
5592 }
5593 elsif (!$critical and !$warning) {
5594 add_unknown $msg;
5595 }
5596 else {
5597 add_ok $msg;
5598 }
5599
5600 return;
5601
5602} ## end of check_pgbouncer_checksum
5603
5604sub check_pgbouncer_backends {
5605
5606 ## Check the number of connections to pgbouncer compared to
5607 ## max_client_conn
5608 ## Supports: Nagios, MRTG
5609 ## It makes no sense to run this more than once on the same cluster
5610 ## Need to be superuser, else only your queries will be visible
5611 ## Warning and criticals can take three forms:
5612 ## critical = 12 -- complain if there are 12 or more connections
5613 ## critical = 95% -- complain if >= 95% of available connections are used
5614 ## critical = -5 -- complain if there are only 5 or fewer connection slots left
5615 ## The former two options only work with simple numbers - no percentage or negative
5616 ## Can also ignore databases with exclude, and limit with include
5617
5618 my $warning = $opt{warning} || '90%';
5619 my $critical = $opt{critical} || '95%';
5620 my $noidle = $opt{noidle} || 0;
5621
5622 ## If only critical was used, remove the default warning
5623 if ($opt{critical} and !$opt{warning}) {
5624 $warning = $critical;
5625 }
5626
5627 my $validre = qr{^(\-?)(\d+)(\%?)$};
5628 if ($critical !~ $validre) {
5629 ndie msg('pgb-backends-users', 'Critical');
5630 }
5631 my ($e1,$e2,$e3) = ($1,$2,$3);
5632 if ($warning !~ $validre) {
5633 ndie msg('pgb-backends-users', 'Warning');
5634 }
5635 my ($w1,$w2,$w3) = ($1,$2,$3);
5636
5637 ## If number is greater, all else is same, and not minus
5638 if ($w2 > $e2 and $w1 eq $e1 and $w3 eq $e3 and $w1 eq '') {
5639 ndie msg('range-warnbig');
5640 }
5641 ## If number is less, all else is same, and minus
5642 if ($w2 < $e2 and $w1 eq $e1 and $w3 eq $e3 and $w1 eq '-') {
5643 ndie msg('range-warnsmall');
5644 }
5645 if (($w1 and $w3) or ($e1 and $e3)) {
5646 ndie msg('range-neg-percent');
5647 }
5648
5649 ## Grab information from the config
5650 $SQL = 'SHOW CONFIG';
5651
5652 my $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1 } );
5653
5654 ## Default values for information gathered
5655 my $limit = 0;
5656
5657 ## Determine max_client_conn
5658 for my $r (@{$info->{db}[0]{slurp}}) {
5659 if ($r->{key} eq 'max_client_conn') {
5660 $limit = $r->{value};
5661 last;
5662 }
5663 }
5664
5665 ## Grab information from pools
5666 $SQL = 'SHOW POOLS';
5667
5668 $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1 } );
5669
5670 $db = $info->{db}[0];
5671
5672 my $total = 0;
5673 my $grandtotal = @{$db->{slurp}};
5674
5675 for my $r (@{$db->{slurp}}) {
5676
5677 ## Always want perf to show all
5678 my $nwarn=$w2;
5679 my $ncrit=$e2;
5680 if ($e1) {
5681 $ncrit = $limit-$e2;
5682 }
5683 elsif ($e3) {
5684 $ncrit = (int $e2*$limit/100);
5685 }
5686 if ($w1) {
5687 $nwarn = $limit-$w2;
5688 }
5689 elsif ($w3) {
5690 $nwarn = (int $w2*$limit/100)
5691 }
5692
5693 if (! skip_item($r->{database})) {
5694 my $current = $r->{cl_active} + $r->{cl_waiting};
5695 $db->{perf} .= " '$r->{database}'=$current;$nwarn;$ncrit;0;$limit";
5696 $total += $current;
5697 }
5698 }
5699
5700 if ($MRTG) {
5701 $stats{$db->{dbname}} = $total;
5702 $statsmsg{$db->{dbname}} = msg('pgb-backends-mrtg', $db->{dbname}, $limit);
5703 return;
5704 }
5705
5706 if (!$total) {
5707 if ($grandtotal) {
5708 ## We assume that exclude/include rules are correct, and we simply had no entries
5709 ## at all in the specific databases we wanted
5710 add_ok msg('pgb-backends-none');
5711 }
5712 else {
5713 add_unknown msg('no-match-db');
5714 }
5715 return;
5716 }
5717
5718 my $percent = (int $total / $limit*100) || 1;
5719 my $msg = msg('pgb-backends-msg', $total, $limit, $percent);
5720 my $ok = 1;
5721
5722 if ($e1) { ## minus
5723 $ok = 0 if $limit-$total <= $e2;
5724 }
5725 elsif ($e3) { ## percent
5726 my $nowpercent = $total/$limit*100;
5727 $ok = 0 if $nowpercent >= $e2;
5728 }
5729 else { ## raw number
5730 $ok = 0 if $total >= $e2;
5731 }
5732 if (!$ok) {
5733 add_critical $msg;
5734 return;
5735 }
5736
5737 if ($w1) {
5738 $ok = 0 if $limit-$total <= $w2;
5739 }
5740 elsif ($w3) {
5741 my $nowpercent = $total/$limit*100;
5742 $ok = 0 if $nowpercent >= $w2;
5743 }
5744 else {
5745 $ok = 0 if $total >= $w2;
5746 }
5747 if (!$ok) {
5748 add_warning $msg;
5749 return;
5750 }
5751
5752 add_ok $msg;
5753
5754 return;
5755
5756} ## end of check_pgbouncer_backends
5757
5758
5759
5760sub check_pgb_pool {
5761
5762 # Check various bits of the pgbouncer SHOW POOLS ouptut
5763 my $stat = shift;
5764 my ($warning, $critical) = validate_range({type => 'positive integer'});
5765
5766 $SQL = 'SHOW POOLS';
5767 my $info = run_command($SQL, { regex => qr[$stat] });
5768
5769 $db = $info->{db}[0];
5770 my $output = $db->{slurp};
5771 my $gotone = 0;
5772 for my $i (@$output) {
5773 next if skip_item($i->{database});
5774 my $msg = "$i->{database}=$i->{$stat}";
5775
5776 if ($MRTG) {
5777 $stats{$i->{database}} = $i->{$stat};
5778 $statsmsg{$i->{database}} = msg('pgbouncer-pool', $i->{database}, $stat, $i->{$stat});
5779 next;
5780 }
5781
5782 if ($critical and $i->{$stat} >= $critical) {
5783 add_critical $msg;
5784 }
5785 elsif ($warning and $i->{$stat} >= $warning) {
5786 add_warning $msg;
5787 }
5788 else {
5789 add_ok $msg;
5790 }
5791 }
5792
5793 return;
5794
5795} ## end of check_pgb_pool
5796
5797
5798sub check_prepared_txns {
5799
5800 ## Checks age of prepared transactions
5801 ## Most installations probably want no prepared_transactions
5802 ## Supports: Nagios, MRTG
5803
5804 my ($warning, $critical) = validate_range
5805 ({
5806 type => 'seconds',
5807 default_warning => '1',
5808 default_critical => '30',
5809 });
5810
5811 my $SQL = q{
5812SELECT database, ROUND(EXTRACT(epoch FROM now()-prepared)) AS age, prepared
5813FROM pg_prepared_xacts
5814ORDER BY prepared ASC
5815};
5816
5817 my $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } );
5818
5819 my $msg = msg('preptxn-none');
5820 my $found = 0;
5821 for $db (@{$info->{db}}) {
5822 my (@crit,@warn,@ok);
5823 my ($maxage,$maxdb) = (0,''); ## used by MRTG only
5824 ROW: for my $r (@{$db->{slurp}}) {
5825 my ($dbname,$age,$date) = ($r->{database},$r->{age},$r->{prepared});
5826 $found = 1 if ! $found;
5827 next ROW if skip_item($dbname);
5828 $found = 2;
5829 if ($MRTG) {
5830 if ($age > $maxage) {
5831 $maxdb = $dbname;
5832 $maxage = $age;
5833 }
5834 elsif ($age == $maxage) {
5835 $maxdb .= sprintf "%s$dbname", length $maxdb ? ' | ' : '';
5836 }
5837 next;
5838 }
5839
5840 $msg = "$dbname=$date ($age)";
5841 $db->{perf} .= sprintf ' %s=%ss;%s;%s',
5842 perfname($dbname), $age, $warning, $critical;
5843 if (length $critical and $age >= $critical) {
5844 push @crit => $msg;
5845 }
5846 elsif (length $warning and $age >= $warning) {
5847 push @warn => $msg;
5848 }
5849 else {
5850 push @ok => $msg;
5851 }
5852 }
5853 if ($MRTG) {
5854 do_mrtg({one => $maxage, msg => $maxdb});
5855 }
5856 elsif (0 == $found) {
5857 add_ok msg('preptxn-none');
5858 }
5859 elsif (1 == $found) {
5860 add_unknown msg('no-match-db');
5861 }
5862 elsif (@crit) {
5863 add_critical join ' ' => @crit;
5864 }
5865 elsif (@warn) {
5866 add_warning join ' ' => @warn;
5867 }
5868 else {
5869 add_ok join ' ' => @ok;
5870 }
5871 }
5872
5873 return;
5874
5875} ## end of check_prepared_txns
5876
5877
5878sub check_query_runtime {
5879
5880 ## Make sure a known query runs at least as fast as we think it should
5881 ## Supports: Nagios, MRTG
5882 ## Warning and critical are time limits, defaulting to seconds
5883 ## Valid units: s[econd], m[inute], h[our], d[ay]
5884 ## Does a "EXPLAIN ANALYZE SELECT COUNT(1) FROM xyz"
5885 ## where xyz is given by the option --queryname
5886 ## This could also be a table or a function, or course, but must be a
5887 ## single word. If a function, it must be empty (with "()")
5888 ## Examples:
5889 ## --warning="100s" --critical="120s" --queryname="speedtest1"
5890 ## --warning="5min" --critical="15min" --queryname="speedtest()"
5891
5892 my ($warning, $critical) = validate_range({type => 'time'});
5893
5894 my $queryname = $opt{queryname} || '';
5895
5896 if ($queryname !~ /^[\w\_\.]+(?:\(\))?$/) {
5897 ndie msg('runtime-badname');
5898 }
5899
5900 $SQL = "EXPLAIN ANALYZE SELECT COUNT(1) FROM $queryname";
5901 my $info = run_command($SQL);
5902
5903 for $db (@{$info->{db}}) {
5904 if (! exists $db->{slurp}[0]{queryplan}) {
5905 add_unknown msg('invalid-query', $db->{slurp});
5906 next;
5907 }
5908 my $totalms = -1;
5909 for my $r (@{$db->{slurp}}) {
5910 if ($r->{queryplan} =~ / (\d+\.\d+) ms/) {
5911 $totalms = $1;
5912 }
5913 }
5914 my $totalseconds = sprintf '%.2f', $totalms / 1000.0;
5915 if ($MRTG) {
5916 $stats{$db->{dbname}} = $totalseconds;
5917 next;
5918 }
5919 $db->{perf} = sprintf '%s=%ss;%s;%s',
5920 perfname(msg('query-time')), $totalseconds, $warning, $critical;
5921 my $msg = msg('runtime-msg', $totalseconds);
5922 if (length $critical and $totalseconds >= $critical) {
5923 add_critical $msg;
5924 }
5925 elsif (length $warning and $totalseconds >= $warning) {
5926 add_warning $msg;
5927 }
5928 else {
5929 add_ok $msg;
5930 }
5931 }
5932
5933 $MRTG and do_mrtg_stats(msg('runtime-badmrtg'));
5934
5935 return;
5936
5937} ## end of check_query_runtime
5938
5939
5940sub check_query_time {
5941
5942 ## Check the length of running queries
5943
5944 check_txn_idle('qtime',
5945 msg('queries'),
5946 msg('query-time'),
5947 'query_start',
5948 q{query_start IS NOT NULL AND current_query NOT LIKE '<IDLE>%'});
5949
5950 return;
5951
5952} ## end of check_query_time
5953
5954
5955sub check_relation_size {
5956
5957 my $relkind = shift || 'relation';
5958
5959 ## Check the size of one or more relations
5960 ## Supports: Nagios, MRTG
5961 ## By default, checks all relations
5962 ## Can check specific one(s) with include
5963 ## Can ignore some with exclude
5964 ## Warning and critical are bytes
5965 ## Valid units: b, k, m, g, t, e
5966 ## All above may be written as plural or with a trailing 'g'
5967 ## Limit to a specific user (relation owner) with the includeuser option
5968 ## Exclude users with the excludeuser option
5969
5970 my ($warning, $critical) = validate_range({type => 'size'});
5971
5972 $SQL = sprintf q{
5973SELECT pg_relation_size(c.oid) AS rsize,
5974 pg_size_pretty(pg_relation_size(c.oid)) AS psize,
5975 relkind, relname, nspname
5976FROM pg_class c, pg_namespace n WHERE (relkind = %s) AND n.oid = c.relnamespace
5977},
5978 $relkind eq 'table' ? q{'r'}
5979 : $relkind eq 'index' ? q{'i'}
5980 : q{'r' OR relkind = 'i'};
5981
5982 if ($opt{perflimit}) {
5983 $SQL .= " ORDER BY 1 DESC LIMIT $opt{perflimit}";
5984 }
5985
5986 if ($USERWHERECLAUSE) {
5987 $SQL =~ s/ WHERE/, pg_user u WHERE u.usesysid=c.relowner$USERWHERECLAUSE AND/;
5988 }
5989
5990 my $info = run_command($SQL, {emptyok => 1});
5991
5992 my $found = 0;
5993 for $db (@{$info->{db}}) {
5994
5995 $found = 1;
5996 if ($db->{slurp}[0]{rsize} !~ /\d/ and $USERWHERECLAUSE) {
5997 $stats{$db->{dbname}} = 0;
5998 add_ok msg('no-match-user');
5999 next;
6000 }
6001
6002 my ($max,$pmax,$kmax,$nmax,$smax) = (-1,0,0,'?','?');
6003
6004 ROW: for my $r (@{$db->{slurp}}) {
6005 my ($size,$psize,$kind,$name,$schema) = @$r{qw/ rsize psize relkind relname nspname/};
6006
6007 next ROW if skip_item($name, $schema);
6008
6009 my $nicename = $kind eq 'r' ? "$schema.$name" : $name;
6010
6011 $db->{perf} .= sprintf '%s%s=%sB;%s;%s',
6012 $VERBOSE==1 ? "\n" : ' ',
6013 perfname($nicename), $size, $warning, $critical;
6014 ($max=$size, $pmax=$psize, $kmax=$kind, $nmax=$name, $smax=$schema) if $size > $max;
6015 }
6016 if ($max < 0) {
6017 add_unknown msg('no-match-rel');
6018 next;
6019 }
6020 if ($MRTG) {
6021 my $msg = sprintf 'DB: %s %s %s%s',
6022 $db->{dbname},
6023 $kmax eq 'i' ? 'INDEX:' : 'TABLE:',
6024 $kmax eq 'i' ? '' : "$smax.",
6025 $nmax;
6026 do_mrtg({one => $max, msg => $msg});
6027 next;
6028 }
6029
6030 my $msg;
6031 if ($relkind eq 'relation') {
6032 if ($kmax eq 'r') {
6033 $msg = msg('relsize-msg-relt', "$smax.$nmax", $pmax);
6034 }
6035 else {
6036 $msg = msg('relsize-msg-reli', $nmax, $pmax);
6037 }
6038 }
6039 elsif ($relkind eq 'table') {
6040 $msg = msg('relsize-msg-tab', "$smax.$nmax", $pmax);
6041 }
6042 else {
6043 $msg = msg('relsize-msg-ind', $nmax, $pmax);
6044 }
6045 if (length $critical and $max >= $critical) {
6046 add_critical $msg;
6047 }
6048 elsif (length $warning and $max >= $warning) {
6049 add_warning $msg;
6050 }
6051 else {
6052 add_ok $msg;
6053 }
6054 }
6055
6056 return;
6057
6058} ## end of check_relation_size
6059
6060
6061sub check_table_size {
6062 return check_relation_size('table');
6063}
6064sub check_index_size {
6065 return check_relation_size('index');
6066}
6067
6068
6069sub check_replicate_row {
6070
6071 ## Make an update on one server, make sure it propogates to others
6072 ## Supports: Nagios, MRTG
6073 ## Warning and critical are time to replicate to all slaves
6074
6075 my ($warning, $critical) = validate_range({type => 'time', leastone => 1, forcemrtg => 1});
6076
6077 if ($warning and $critical and $warning > $critical) {
6078 ndie msg('range-warnbig');
6079 }
6080
6081 if (!$opt{repinfo}) {
6082 ndie msg('rep-noarg');
6083 }
6084 my @repinfo = split /,/ => ($opt{repinfo} || '');
6085 if ($#repinfo != 5) {
6086 ndie msg('rep-badarg');
6087 }
6088 my ($table,$pk,$id,$col,$val1,$val2) = (@repinfo);
6089
6090 ## Quote everything, just to be safe (e.g. columns named 'desc')
6091 $table = qq{"$table"};
6092 $pk = qq{"$pk"};
6093 $col = qq{"$col"};
6094
6095 if ($val1 eq $val2) {
6096 ndie msg('rep-duh');
6097 }
6098
6099 $SQL = qq{UPDATE $table SET $col = 'X' WHERE $pk = '$id'};
6100 (my $update1 = $SQL) =~ s/X/$val1/;
6101 (my $update2 = $SQL) =~ s/X/$val2/;
6102 my $select = qq{SELECT $col AS c FROM $table WHERE $pk = '$id'};
6103
6104 ## Are they the same on both sides? Must be yes, or we error out
6105
6106 ## We assume this is a single server
6107 my $info1 = run_command($select);
6108 ## Squirrel away the $db setting for later
6109 my $sourcedb = $info1->{db}[0];
6110 if (!defined $sourcedb) {
6111 ndie msg('rep-norow', "$table.$col");
6112 }
6113 my $value1 = $info1->{db}[0]{slurp}[0]{c};
6114
6115 my $info2 = run_command($select, { dbnumber => 2 });
6116 my $slave = 0;
6117 for my $d (@{$info2->{db}}) {
6118 $slave++;
6119 my $value2 = $d->{slurp}[0]{c};
6120 if ($value1 ne $value2) {
6121 ndie msg('rep-notsame');
6122 }
6123 }
6124 my $numslaves = $slave;
6125 if ($numslaves < 1) {
6126 ndie msg('rep-noslaves');
6127 }
6128
6129 my ($update,$newval);
6130 if ($value1 eq $val1) {
6131 $update = $update2;
6132 $newval = $val2;
6133 }
6134 elsif ($value1 eq $val2) {
6135 $update = $update1;
6136 $newval = $val1;
6137 }
6138 else {
6139 ndie msg('rep-wrongvals', $value1, $val1, $val2);
6140 }
6141
6142 $info1 = run_command($update, { dbnumber => 1, failok => 1 } );
6143
6144 ## Make sure the update worked
6145 if (! defined $info1->{db}[0]) {
6146 ndie msg('rep-sourcefail');
6147 }
6148
6149 my $err = $info1->{db}[0]{error} || '';
6150 if ($err) {
6151 $err =~ s/ERROR://; ## e.g. Slony read-only
6152 ndie $err;
6153 }
6154
6155 ## Start the clock
6156 my $starttime = time();
6157
6158 ## Loop until we get a match, check each in turn
6159 my %slave;
6160 my $time = 0;
6161 LOOP: {
6162 $info2 = run_command($select, { dbnumber => 2 } );
6163 ## Reset for final output
6164 $db = $sourcedb;
6165
6166 $slave = 0;
6167 for my $d (@{$info2->{db}}) {
6168 $slave++;
6169 next if exists $slave{$slave};
6170 my $value2 = $d->{slurp}[0]{c};
6171 $time = $db->{totaltime} = time - $starttime;
6172 if ($value2 eq $newval) {
6173 $slave{$slave} = $time;
6174 next;
6175 }
6176 if ($warning and $time > $warning) {
6177 $MRTG and do_mrtg({one => 0, msg => $time});
6178 add_warning msg('rep-fail', $slave);
6179 return;
6180 }
6181 elsif ($critical and $time > $critical) {
6182 $MRTG and do_mrtg({one => 0, msg => $time});
6183 add_critical msg('rep-fail', $slave);
6184 return;
6185 }
6186 }
6187 ## Did they all match?
6188 my $k = keys %slave;
6189 if (keys %slave >= $numslaves) {
6190 $MRTG and do_mrtg({one => $time});
6191 add_ok msg('rep-ok');
6192 return;
6193 }
6194 sleep 1;
6195 redo;
6196 }
6197
6198 $MRTG and ndie msg('rep-timeout', $time);
6199 add_unknown msg('rep-unknown');
6200 return;
6201
6202} ## end of check_replicate_row
6203
6204
6205sub check_same_schema {
6206
6207 ## Verify that all relations inside two or more databases are the same
6208 ## Supports: Nagios
6209 ## Include and exclude are supported
6210 ## Warning and critical are not used
6211 ## The filter argument is supported
6212
6213 ## We override the usual $db->{totaltime} with our own counter
6214 my $start = [gettimeofday()];
6215
6216 ## Check for filtering rules, then store inside opt{filtered}
6217 my %filter;
6218 if (exists $opt{filter}) {
6219 for my $item (@{ $opt{filter} }) {
6220 ## Can separate by whitespace or commas
6221 for my $phrase (split /[\s,]+/ => $item) {
6222
6223 ## Can be plain (e.g. nouser) or regex based exclusion, e.g. nouser=bob
6224 next if $phrase !~ /(\w+)=?\s*(.*)/o;
6225 my ($name,$regex) = (lc $1,$2||'');
6226
6227 ## Names are standardized with regards to plurals and casing
6228 $name =~ s/([aeiou])s$/$1/o;
6229 $name =~ s/s$//o;
6230
6231 if (! length $regex) {
6232 $filter{"$name"} = 1;
6233 }
6234 else {
6235 push @{$filter{"${name}_regex"}} => $regex;
6236 }
6237 }
6238 $VERBOSE >= 3 and warn Dumper \%filter;
6239 }
6240 }
6241 $opt{filtered} = \%filter;
6242
6243 ## See how many databases we are using
6244 my $numdbs = @targetdb;
6245 $VERBOSE >= 3 and warn "Number of databases is $numdbs\n";
6246
6247 ## If only a single database is given, this is a time-based comparison
6248 ## In other words, we write and read a local file
6249 my $samedb = 0;
6250 if (1 == $numdbs) {
6251 $samedb = 1;
6252 $numdbs = 2;
6253 }
6254
6255 ## Sanity check
6256 if ($opt{suffix} and ! $samedb) {
6257 ndie msg('ss-suffix');
6258 }
6259
6260 ## Version information about each database, by number
6261 my %dbver;
6262
6263 ## Verify we can connect to each database, and grab version information
6264 for my $num (1..$numdbs) {
6265
6266 ## No need to check the same database twice!
6267 last if $samedb and $num > 1;
6268
6269 $SQL = 'SELECT version()';
6270 my $info = run_command($SQL, { dbnumber => $num } );
6271
6272 ## We need a global $db, so we'll use the first database
6273 $db = $info->{db}[0] if 1 == $num;
6274
6275 my $foo = $info->{db}[0];
6276 my $version = $foo->{slurp}[0]{version};
6277 $version =~ /\D+(\d+\.\d+)(\S+)/i or die qq{Invalid version: $version\n};
6278 my ($full,$major,$revision) = ("$1$2",$1,$2);
6279 $revision =~ s/^\.//;
6280 $dbver{$num} = {
6281 full => $version,
6282 version => $full,
6283 major => $major,
6284 revision => $revision,
6285 };
6286
6287 $targetdb[$num-1]{pgversion} = $full;
6288
6289 }
6290
6291 ## An ordered list of all the things we check.
6292 ## Order is important here, as when reporting, some things
6293 ## can mask reporting on others (e.g. no need to report missing tables
6294 ## if the entire schema has already been reported as missing)
6295 ## We also indicate which columns should be ignored when comparing,
6296 ## as well as which columns are of a 'list' nature
6297 my @catalog_items = (
6298 [user => 'usesysid', 'useconfig' ],
6299 [language => 'laninline,lanplcallfoid,lanvalidator', '' ],
6300 [operator => '', '' ],
6301 [type => '', '' ],
6302 [schema => '', '' ],
6303 [function => 'source_checksum,prolang,prorettype', '' ],
6304 [table => 'reltype,relfrozenxid,relpages,
6305 reltuples,relnatts', '' ],
6306 [view => 'reltype', '' ],
6307 [sequence => 'reltype,log_cnt,relnatts,is_called', '' ],
6308 [index => 'relpages,reltuples,indpred,indclass,
6309 indexprs,indcheckxmin', '' ],
6310 [trigger => '', '' ],
6311 [constraint => 'conbin', '' ],
6312 [column => 'atttypid,attnum,attbyval', '' ],
6313 );
6314
6315 ## Where we store all the information, per-database
6316 my %thing;
6317
6318 my $saved_db;
6319 for my $x (1..$numdbs) {
6320
6321 if ($x > 1 and $samedb) {
6322 ## This means we are looking at a single database over time
6323 ## We load the stored information into the current $dbinfo
6324 my $filename = audit_filename();
6325
6326 if (! -e $filename) {
6327 ## We've not saved any information about this database yet
6328 ## Store the info and exit!
6329 my $version = $dbver{1}{version};
6330 write_audit_file({ file => $filename, 'same_schema' => 1,
6331 info => $thing{1}, pgversion => $version });
6332 print msg('ss-createfile', $filename) . "\n";
6333 exit 0;
6334 }
6335
6336 ## Meta-information from the file
6337 my ($conninfo,$ctime,$cversion,$pgversion,$cdbname,$chost,$cport,$cuser);
6338
6339 ($thing{$x},$conninfo,$ctime,$cversion,$pgversion,$cdbname,$chost,$cport,$cuser)
6340 = read_audit_file($filename);
6341
6342 ## Count total objects
6343 my $totalcount = 0;
6344 for (keys %{ $thing{$x} }) {
6345 $totalcount += keys %{ $thing{$x}{$_} };
6346 }
6347
6348 ## Add the meta info back into the targetdb
6349 push @targetdb, {
6350 filename => $filename,
6351 conninfo => $conninfo,
6352 ctime => $ctime,
6353 cversion => $cversion,
6354 dbname => $cdbname,
6355 port => $cport,
6356 host => $chost,
6357 dbuser => $cuser,
6358 pgversion => $pgversion,
6359 objects => $totalcount,
6360 };
6361
6362 next;
6363
6364 } ## end if samedb
6365
6366 ## Hash of this round's information
6367 my $dbinfo;
6368
6369 for (@catalog_items) {
6370 my $name = $_->[0];
6371 $dbinfo->{$name} = find_catalog_info($name, $x, $dbver{$x});
6372 }
6373
6374 ## TODO:
6375 ## operator class, cast, aggregate, conversion, domain, tablespace, foreign tables
6376 ## foreign server, wrapper, collation, extensions, roles?
6377
6378 ## Map the oid back to the user, for ease later on
6379 for my $row (values %{ $dbinfo->{user} }) {
6380 $dbinfo->{useroid}{$row->{usesysid}} = $row->{usename};
6381 }
6382
6383 $thing{$x} = $dbinfo;
6384
6385 ## Count total objects
6386 my $totalcount = 0;
6387 for (keys %{ $thing{$x} }) {
6388 $totalcount += keys %{ $thing{$x}{$_} };
6389 }
6390
6391 $targetdb[$x-1]{objects} = $totalcount;
6392
6393
6394 } ## end each database to query
6395
6396 ## Start comparing, and put any differences into %fail
6397 my %fail;
6398
6399 ## Ugly, but going to use this as a global for the subroutines below:
6400 $opt{failcount} = 0;
6401
6402 ## Simple checks that items exist on each database
6403 for (@catalog_items) {
6404 my $name = $_->[0];
6405 $fail{$name}{exists} = schema_item_exists($name, \%thing);
6406 }
6407
6408 ## Now check for some more specific items for each item class.
6409 ## For many of these, we want to compare all columns except for
6410 ## certain known exceptions (e.g. anything oid-based)
6411 ## Because we may go across versions, if the column does not exist
6412 ## somewhere, it is simply silently ignored
6413 ## Some items are lists (e.g. acls) and must be treated differently
6414
6415 for (@catalog_items) {
6416 my ($name,$ignore,$lists) = @$_;
6417 $fail{$name}{diff} = schema_item_differences({
6418 items => \%thing,
6419 name => $name,
6420 ignore => $ignore,
6421 lists => $lists,
6422 });
6423 }
6424
6425 ## Remove empty hashes for a cleaner debug dump
6426 for (keys %fail) {
6427 if (exists $fail{$_}{diff} and ! keys %{ $fail{$_}{diff} }) {
6428 delete $fail{$_}{diff};
6429 }
6430 }
6431
6432 ## Set the total time
6433 $db->{totaltime} = sprintf '%.2f', tv_interval($start);
6434
6435 ## Before we outpu any results, rewrite the audit file if needed
6436 ## We do this if we are reading from a saved file,
6437 ## and the "replace" argument is set
6438 if ($samedb and $opt{replace}) {
6439 my $filename = audit_filename();
6440 if ( -e $filename) {
6441 ## Move the old one to a backup version
6442 my $backupfile = "$filename.backup";
6443 rename $filename, $backupfile;
6444 }
6445 my $version = $dbver{1}{version};
6446 write_audit_file({ file => $filename, 'same_schema' => 1,
6447 info => $thing{1}, pgversion => $version });
6448 ## Cannot print this message as we are outputting Nagios stuff
6449 #print msg('ss-createfile', $filename) . "\n";
6450 }
6451
6452 ## Comparison is done, let's report the results
6453 if (! $opt{failcount}) {
6454 add_ok msg('ss-matched');
6455 return;
6456 }
6457
6458 ## Build a pretty message giving all the gory details
6459 my $msg = '';
6460
6461 ## Adjust the output based on the leading message sizes
6462 my $maxsize = 1;
6463 my $msg_exists = msg('ss-existson');
6464 my $msg_missing = msg('ss-missingon');
6465 $maxsize = length $msg_exists if length $msg_exists > $maxsize;
6466 $maxsize = length $msg_missing if length $msg_missing > $maxsize;
6467
6468 ## Walk through each item type in alphabetical order and output the differences
6469 for (@catalog_items) {
6470 my $item = $_->[0];
6471
6472 ## Pretty name for this type of item. No matches is okay!
6473 $opt{nomsgok} = 1;
6474 my $pitem = msg($item) || ucfirst $item;
6475 $opt{nomsgok} = 0;
6476
6477 ## See if there are any items of this class that only exist on some
6478 my $e = $fail{$item}{exists};
6479 if (keys %$e) {
6480 for my $name (sort keys %$e) {
6481 ## We do not want to report twice on things that appear inside of schemas
6482 ## However, we do report if the schema *does* exist on any of the missing databases
6483 if ($item ne 'schema' and $name =~ /(.+?)\./) {
6484 my $schema = $1;
6485 ## How many databases do not have this?
6486 my $missingcount = keys %{ $e->{$name}{nothere} };
6487 my $noschemacount = 0;
6488 for my $db (keys %{ $e->{$name}{nothere} }) {
6489 if (exists $fail{schema}{exists}{$schema}{nothere}{$db}) {
6490 $noschemacount++;
6491 }
6492 }
6493 if ($missingcount == $noschemacount) {
6494 next;
6495 }
6496 }
6497
6498 ## Show the list of the item, and a CSV of which databases have it and which don't
6499 my $isthere = join ', ' => sort { $a<=>$b } keys %{ $e->{$name}{isthere} };
6500 my $nothere = join ', ' => sort { $a<=>$b } keys %{ $e->{$name}{nothere} };
6501 $msg .= sprintf "%s\n %-*s %s\n %-*s %s\n",
6502 msg('ss-noexist', $pitem, $name),
6503 $maxsize, $msg_exists,
6504 $isthere,
6505 $maxsize, $msg_missing,
6506 $nothere;
6507 }
6508 }
6509
6510 ## See if there are any items for this class that have differences
6511 my $d = $fail{$item}{diff};
6512 if (keys %$d) {
6513
6514 for my $name (sort keys %$d) {
6515 my $tdiff = $d->{$name};
6516
6517 ## Any raw column differences?
6518 if (exists $tdiff->{coldiff}) {
6519 my @msg;
6520
6521 for my $col (sort keys %{ $tdiff->{coldiff} }) {
6522
6523 ## Do not show index 'owners': already covered by the table itself
6524 if ($col eq 'owner' and $item eq 'index') {
6525 next;
6526 }
6527
6528 ## Do not show column number differences if filtered out with "noposition"
6529 if ($item eq 'column'
6530 and $col eq 'column_number'
6531 and $opt{filtered}{noposition}) {
6532 next;
6533 }
6534
6535 ## Do not show function body differences if filtered out with "nofuncbody"
6536 ## Also skip if the equivalent 'dash' and 'empty'
6537 if ($item eq 'function'
6538 and $col eq 'prosrc') {
6539
6540 next if $opt{filtered}{nofuncbody};
6541 my ($one,$two);
6542 for my $db (sort keys %{ $tdiff->{coldiff}{$col} }) {
6543 if (defined $one) {
6544 $two = $tdiff->{coldiff}{$col}{$db};
6545 }
6546 else {
6547 $one = $tdiff->{coldiff}{$col}{$db};
6548 }
6549 }
6550 next if $one eq '-' and $two eq '';
6551 next if $one eq '' and $two eq '-';
6552 }
6553
6554 ## If we are doing a historical comparison, skip some items
6555 if ($samedb) {
6556 if ($item eq 'sequence'
6557 and $col eq 'last_value') {
6558 next;
6559 }
6560 }
6561
6562 push @msg => sprintf " %s\n", msg('ss-different', $col);
6563 for my $db (sort keys %{ $tdiff->{coldiff}{$col} }) {
6564 push @msg => sprintf " %s %s: %s\n",
6565 ucfirst msg('database'),
6566 $db,
6567 $tdiff->{coldiff}{$col}{$db};
6568 }
6569 }
6570
6571 if (@msg) {
6572 $msg .= qq{$pitem "$name":\n};
6573 $msg .= $_ for @msg;
6574 }
6575 else {
6576 ## Everything got filtered out, so decrement this item
6577 $opt{failcount}--;
6578 }
6579 }
6580
6581 ## Any multi-item column differences?
6582 if (exists $tdiff->{list}) {
6583
6584 my @msg;
6585 for my $col (sort keys %{ $tdiff->{list} }) {
6586
6587 ## Exclude permissions if 'noperm' filter is set
6588 if ($col =~ /.acl$/ and $opt{filtered}{noperm}) {
6589 next;
6590 }
6591
6592 if (exists $tdiff->{list}{$col}{exists}) {
6593 my $ex = $tdiff->{list}{$col}{exists};
6594 for my $name (sort keys %$ex) {
6595 push @msg => sprintf qq{ "%s":\n %s\n},
6596 $col,
6597 msg('ss-notset', $name);
6598 my $isthere = join ', ' => sort { $a<=>$b } keys %{ $ex->{$name}{isthere} };
6599 my $nothere = join ', ' => sort { $a<=>$b } keys %{ $ex->{$name}{nothere} };
6600 push @msg => sprintf " %-*s %s\n %-*s %s\n",
6601 $maxsize, $msg_exists,
6602 $isthere,
6603 $maxsize, $msg_missing,
6604 $nothere;
6605 }
6606 }
6607 if (exists $tdiff->{list}{$col}{diff}) {
6608 for my $setting (sort keys %{ $tdiff->{list}{$col}{diff} }) {
6609
6610 push @msg => sprintf qq{ "%s":\n %s\n},
6611 $col,
6612 msg('ss-different', $setting);
6613 for my $db (sort keys %{ $tdiff->{list}{$col}{diff}{$setting} }) {
6614 my $val = $tdiff->{list}{$col}{diff}{$setting}{$db};
6615 push @msg => sprintf " %s %s: %s\n",
6616 ucfirst msg('database'),
6617 $db,
6618 $val;
6619 }
6620 }
6621 }
6622 }
6623
6624 if (@msg) {
6625 $msg .= qq{$pitem "$name":\n};
6626 $msg .= $_ for @msg;
6627 }
6628 else {
6629 ## No message means it was all filtered out, so we decrment the master count
6630 $opt{failcount}--;
6631 }
6632 }
6633 }
6634 }
6635 }
6636
6637 ## We may have no items due to exclusions!
6638 if (! $opt{failcount}) {
6639 add_ok msg('ss-matched');
6640 return;
6641 }
6642
6643 $db->{perf} = "\n$msg";
6644 add_critical msg('ss-failed', $opt{failcount});
6645 return;
6646
6647} ## end of check_same_schema
6648
6649
6650sub audit_filename {
6651
6652 ## Generate the name of the file to store audit information
6653
6654 ## Get the connection information for this connection
6655 my $filename = run_command('foo', { conninfo => 1 });
6656 ## Do not care about the username
6657 $filename =~ s/ user=(.+)//;
6658 ## Strip out the host if not used
6659 $filename =~ s/ host=<none>//;
6660 ## Replace any other spaces
6661 $filename =~ s/ /./g;
6662 ## Equals have to be escaped, so we'll change them to a dot
6663 $filename =~ s/=/./g;
6664 ## The final filename to use
6665 $filename = "check_postgres.audit.$filename";
6666
6667 ## The host name may have slashes, so change to underscores
6668 $filename =~ s{\/}{_}g;
6669
6670 ## Got a user-supplied extension? Add it now.
6671 if ($opt{suffix}) {
6672 $filename .= ".$opt{suffix}";
6673 }
6674
6675 return $filename;
6676
6677} ## end of audit_filename
6678
6679
6680sub write_audit_file {
6681
6682 ## Save a new copy of the audit file
6683 my $arg = shift || {};
6684 my $filename = $arg->{filename} || audit_filename();
6685 my $info = $arg->{info} || die;
6686
6687 ## Create a connection information string
6688 my $row = $targetdb[0];
6689 my $conninfo = sprintf '%s%s%s%s',
6690 defined $row->{port} ? qq{port=$row->{port} } : '',
6691 defined $row->{host} ? qq{host=$row->{host} } : '',
6692 defined $row->{dbname} ? qq{dbname=$row->{dbname} } : '',
6693 defined $row->{dbuser} ? qq{user=$row->{dbuser} } : '';
6694
6695 open my $fh, '>', $filename or die qq{Could not open "$filename": $!\n};
6696 print {$fh} "## Audit file for check_postgres\n";
6697 print {$fh} "## CP version: $VERSION\n";
6698 print {$fh} "## PG version: $arg->{pgversion}\n";
6699 printf {$fh} "## Created: %s\n", scalar localtime();
6700 print {$fh} "## Connection: $conninfo\n";
6701 print {$fh} "## Database name: $row->{dbname}\n";
6702 print {$fh} "## Host: $row->{host}\n";
6703 print {$fh} "## Port: $row->{port}\n";
6704 print {$fh} "## User: $row->{dbuser}\n";
6705 if ($arg->{same_schema}) {
6706 print {$fh} "## Start of same_schema information:\n";
6707 {
6708 local $Data::Dumper::Indent = 1;
6709 print {$fh} Dumper $info;
6710 }
6711 print {$fh} "## End of same_schema information\n";
6712 }
6713
6714 close $fh or warn qq{Could not close "$filename": $!\n};
6715
6716} ## end of write_audit_file
6717
6718
6719sub read_audit_file {
6720
6721 ## Read in the data from a historical file
6722 ## Returns four items:
6723 ## 1. The standard catalog structure that was saved
6724 ## 2. Connection information string
6725 ## 3. Date the file was created
6726 ## 4. The version it was created with
6727
6728 my $filename = shift;
6729
6730 open my $fh, '<', $filename or die qq{Could not open "$filename": $!\n};
6731 my $inside = 0;
6732 my $data = '';
6733 my ($conninfo,$ctime,$cversion,$pgversion) = ('???','???','???','???');
6734 my ($cdbname,$chost,$cport,$cuser) = ('???','???','???','???');
6735 while (<$fh>) {
6736 if (!$inside) {
6737 if (/Start of same_schema/) {
6738 $inside = 1;
6739 }
6740 elsif (/CP version: (.+)/) {
6741 $cversion = $1;
6742 }
6743 elsif (/PG version: (.+)/) {
6744 $pgversion = $1;
6745 }
6746 elsif (/Created: (.+)/) {
6747 $ctime = $1;
6748 }
6749 elsif (/Connection: (.+)/) {
6750 $conninfo = $1;
6751 }
6752 elsif (/Database name: (.+)/) {
6753 $cdbname = $1;
6754 }
6755 elsif (/Host: (.+)/) {
6756 $chost = $1;
6757 }
6758 elsif (/Port: (.+)/) {
6759 $cport = $1;
6760 }
6761 elsif (/User: (.+)/) {
6762 $cuser = $1;
6763 }
6764 }
6765 elsif (/End of same_schema/) {
6766 last;
6767 }
6768 else {
6769 $data .= $_;
6770 }
6771 }
6772 close $fh or warn qq{Could not close "$filename": $!\n};
6773
6774 my $POSTGRES1;
6775 eval $data; ## no critic (ProhibitStringyEval)
6776 if ($@) {
6777 die qq{Failed to parse file "$filename": $@\n};
6778 }
6779 return $POSTGRES1, $conninfo, $ctime, $cversion,
6780 $pgversion, $cdbname, $chost, $cport, $cuser;
6781
6782} ## end of read_audit_file
6783
6784
6785sub schema_item_exists {
6786
6787 ## Compare a certain class of items across all databases for existence
6788 ## Returns a hashref of item names, with "isthere" and "nothere"
6789 ## with keys of database numbers underneath that
6790
6791 my $item_class = shift;
6792 my $itemhash = shift;
6793
6794 ## Things that failed to match:
6795 my %nomatch;
6796
6797 my $key = "no${item_class}_regex";
6798 my $exclude_regex = exists $opt{filtered}->{$key} ? $opt{filtered}->{$key} : [];
6799
6800 for my $db1 (sort keys %$itemhash) {
6801 for my $db2 (sort keys %$itemhash) {
6802 next if $db1 == $db2;
6803 for my $name (sort keys %{ $itemhash->{$db1}{$item_class} }) {
6804
6805 ## Can exclude by 'filter' based regex
6806 next if grep { $name eq $_ } @$exclude_regex;
6807
6808 if (! exists $itemhash->{$db2}{$item_class}{$name}) {
6809
6810 ## Special exception for columns: do not add if the table is non-existent
6811 if ($item_class eq 'column') {
6812 (my $tablename = $name) =~ s/(.+)\..+/$1/;
6813 next if ! exists $itemhash->{$db2}{table}{$tablename};
6814 }
6815
6816 ## Special exception for triggers: do not add if the table is non-existent
6817 if ($item_class eq 'trigger') {
6818 my $it = $itemhash->{$db1}{$item_class}{$name};
6819 my $tablename = "$it->{tschema}.$it->{tname}";
6820 next if ! exists $itemhash->{$db2}{table}{$tablename};
6821 }
6822
6823 $nomatch{$name}{isthere}{$db1} = 1;
6824 $nomatch{$name}{nothere}{$db2} = 1;
6825 }
6826 }
6827 }
6828 }
6829
6830 ## Increment our fail count once per item mismatch
6831 $opt{failcount} += keys %nomatch;
6832
6833 return \%nomatch;
6834
6835} ## end of schema_item_exists
6836
6837
6838sub schema_item_differences {
6839
6840 ## Compare a certain class of items across all databases for differences
6841 ## Takes a hashref of argument, including:
6842 ## name: the item class name
6843 ## items: the main hashref of all items
6844 ## ignore: which fields to ignore. CSV
6845 ## lists: which fields are lists. CSV
6846 ## Modified the items hashref by incrementing items->{failcount}
6847 ## Returns s hashref of item names, with details as to the diffs therein
6848
6849 my $arg = shift;
6850
6851 my $item_class = $arg->{name} or die;
6852 my $itemhash = $arg->{items} or die;
6853
6854 ## Things we completely ignore:
6855 my $ignore = { oid => 1 };
6856 if (exists $arg->{ignore}) {
6857 for my $item (split /\s*,\s*/ => $arg->{ignore}) {
6858 $ignore->{$item} = 1;
6859 }
6860 }
6861
6862 ## Things that are handled as lists:
6863 my $lists = {};
6864 if (exists $arg->{lists}) {
6865 for my $item (split /\s*,\s*/ => $arg->{lists}) {
6866 $lists->{$item} = 1;
6867 }
6868 }
6869
6870 ## The final lists of mismatched items we pass back
6871 my %nomatch;
6872
6873 my $key = "no${item_class}_regex";
6874 my $exclude_regex = exists $opt{filtered}->{$key} ? $opt{filtered}->{$key} : [];
6875
6876 for my $db1 (sort keys %$itemhash) {
6877 for my $db2 (sort keys %$itemhash) {
6878 next if $db1 >= $db2;
6879 for my $name (sort keys %{ $itemhash->{$db1}{$item_class} }) {
6880
6881 ## Can exclude by 'filter' based regex
6882 next if grep { $name eq $_ } @$exclude_regex;
6883
6884 ## This case has already been handled:
6885 next if ! exists $itemhash->{$db2}{$item_class}{$name};
6886
6887 ## Special exception for columns: do not add if the table is non-existent
6888 if ($item_class eq 'column') {
6889 (my $tablename = $name) =~ s/(.+)\..+/$1/;
6890 next if ! exists $itemhash->{$db2}{table}{$tablename};
6891 }
6892
6893 my $one = $itemhash->{$db1}{$item_class}{$name};
6894 my $two = $itemhash->{$db2}{$item_class}{$name};
6895
6896 for my $col (keys %$one) {
6897
6898 ## Skip if this col is ignored
6899 next if exists $ignore->{$col};
6900
6901 ## If it doesn't exist on the other, just ignore it
6902 next if ! exists $two->{$col};
6903
6904 ## If they are the same, move on!
6905 next if $one->{$col} eq $two->{$col};
6906
6907 ## Skip certain known numeric fields that have text versions:
6908 next if $col =~ /.(?:namespace|owner|filenode|oid|relid)$/;
6909
6910 ## If not a list, just report on the exact match here and move on:
6911 if (! exists $lists->{$col} and $col !~ /.acl$/) {
6912 $nomatch{$name}{coldiff}{$col}{$db1} = $one->{$col};
6913 $nomatch{$name}{coldiff}{$col}{$db2} = $two->{$col};
6914 next;
6915 }
6916
6917 ## This is a list, so we have to break it down to see if it is really different
6918 ## May be empty or of the form {foo=bar,baz=yak}
6919
6920 my (%list1,%list2);
6921 my ($uno,$dos) = ($one->{$col}, $two->{$col});
6922
6923 if (length $uno) {
6924 die "Invalid list: $uno for db $db1:$name:$col\n" if $uno !~ /^{(.+)}$/;
6925 my $t = $1;
6926 my @tlist = ();
6927 push(@tlist, $+) while $t =~ m{"([^\"\\]*(?:\\.[^\"\\]*)*)",?
6928 | ([^,]+),?
6929 | ,
6930 }gx;
6931 push(@tlist, undef) if substr($t, -1,1) eq ',';
6932 %list1 = map { /(.*)=(.+)/ or die "Invalid list: $uno"; $1,$2 } @tlist;
6933 }
6934 if (length $dos) {
6935 die "Invalid list: $dos for db $db2:$name:$col\n" if $dos !~ /^{(.+)}$/;
6936 my $t = $1;
6937 my @tlist = ();
6938 push(@tlist, $+) while $t =~ m{"([^\"\\]*(?:\\.[^\"\\]*)*)",?
6939 | ([^,]+),?
6940 | ,
6941 }gx;
6942 push(@tlist, undef) if substr($t, -1,1) eq ',';
6943 %list2 = map { /(.*)=(.+)/ or die "Invalid list: $uno"; $1,$2 } @tlist;
6944 }
6945
6946 ## Items in 1 but not 2?
6947 for my $setting (sort keys %list1) {
6948 if (! exists $list2{$setting}) {
6949 $nomatch{$name}{list}{$col}{exists}{$setting}{isthere}{$db1} = 1;
6950 $nomatch{$name}{list}{$col}{exists}{$setting}{nothere}{$db2} = 1;
6951 }
6952 }
6953
6954 ## Items in 2 but not 1? Value diferences?
6955 for my $setting (sort keys %list2) {
6956 if (! exists $list1{$setting}) {
6957 $nomatch{$name}{list}{$col}{exists}{$setting}{isthere}{$db2} = 1;
6958 $nomatch{$name}{list}{$col}{exists}{$setting}{nothere}{$db1} = 1;
6959 }
6960 elsif ($list1{$setting} ne $list2{$setting}) {
6961 $nomatch{$name}{list}{$col}{diff}{$setting}{$db1} = $list1{$setting};
6962 $nomatch{$name}{list}{$col}{diff}{$setting}{$db2} = $list2{$setting};
6963 }
6964 }
6965 }
6966 }
6967 }
6968 }
6969
6970 $opt{failcount} += keys %nomatch;
6971
6972 return \%nomatch;
6973
6974} ## end of schema_item_differences
6975
6976
6977sub find_catalog_info {
6978
6979 ## Grab information from one or more catalog tables
6980 ## Convert into a happy hashref and return it
6981 ## Arguments: three
6982 ## 1. Type of object
6983 ## 2. Database number
6984 ## 3. Version information for the database
6985 ## Returns: large hashref of information
6986
6987 ## What type of catalog object this is
6988 my $type = shift;
6989
6990 ## We must know about this type
6991 if (! exists $catalog_info{$type}) {
6992 die "Unknown type of '$type' sent to find_catalog_info";
6993 }
6994 my $ci = $catalog_info{$type};
6995
6996 ## The final hashref of rows we return
6997 my $result = {};
6998
6999 ## Do nothing if we are excluding this type of object entirely
7000 return $result if $opt{filtered}{"no$type"};
7001
7002 ## Which database to run this against
7003 my $dbnum = shift or die;
7004
7005 ## The version information
7006 my $dbver = shift or die;
7007
7008 ## The SQL we use
7009 my $SQL = $ci->{SQL} or die "No SQL found for type '$type'\n";
7010
7011 ## Switch to alternate SQL for different versions
7012 if ($type eq 'language') {
7013 if (int $dbver->{major} <= 8.2) {
7014 $SQL = $ci->{SQL2};
7015 }
7016 }
7017
7018 if (exists $ci->{exclude}) {
7019 if ('temp_schemas' eq $ci->{exclude}) {
7020 if (! $opt{filtered}{system}) {
7021 $SQL .= q{ WHERE nspname !~ '^pg_t'};
7022 }
7023 }
7024 elsif ('system' eq $ci->{exclude}) {
7025 if (! $opt{filtered}{system}) {
7026 $SQL .= sprintf
7027 q{ %s n.nspname !~ '^pg' AND n.nspname <> 'information_schema'},
7028 $SQL =~ /WHERE/ ? 'AND' : 'WHERE';
7029 }
7030 }
7031 else {
7032 die "Unknown exclude '$ci->{exclude}' called";
7033 }
7034 }
7035
7036 ## Final wrapup
7037 if (exists $ci->{postSQL}) {
7038 $SQL .= " $ci->{postSQL}";
7039 }
7040
7041 ## Send our SQL to the correct database via psql and grab the results
7042 my $info = run_command($SQL, { dbnumber => $dbnum });
7043
7044 ## The row column we use as the main hash key
7045 my $key = $ci->{keyname} || 'name';
7046
7047 ## Keep track of the actual column numbers
7048 my $last_table = '';
7049 my $colnum = 1;
7050
7051 ## Only need to pull back the first and only db, so we can say [0] here
7052 ROW:
7053 for my $row (@{$info->{db}[0]{slurp}}) {
7054
7055 ## Remove any information that should be deleted
7056 for ( @{$info->{deletecols}}) {
7057 delete $row->{$_};
7058 }
7059
7060 ## Determine the name to use. For most things this is simply the passed in key
7061 my $name = $row->{$key};
7062
7063 ## For a function, we also want to put the args into the name
7064 if ($type eq 'function') {
7065 ## Once per database, grab all mappings
7066 if (! exists $opt{oid2type}{$dbnum}) {
7067 $SQL = 'SELECT oid, typname FROM pg_type';
7068 my $tinfo = run_command($SQL, { dbnumber => $dbnum });
7069 for my $row (@{ $tinfo->{db}[0]{slurp} }) {
7070 $opt{oid2type}{$dbnum}{$row->{oid}} = $row->{typname};
7071 }
7072 }
7073 (my $args = $row->{proargtypes}) =~ s/(\d+)/$opt{oid2type}{$dbnum}{$1}||$1/ge;
7074 $args =~ s/ /,/g;
7075 $args =~ s/ints/smallint/g;
7076 $args =~ s/int4/int/g;
7077 $args =~ s/int8/bigint/g;
7078 $name .= "($args)";
7079
7080 }
7081
7082 ## For columns, reduce the attnum to a simpler canonical form without holes
7083 if ($type eq 'column') {
7084 if ($row->{tname} ne $last_table) {
7085 $last_table = $row->{tname};
7086 $colnum = 1;
7087 }
7088 $row->{column_number} = $colnum++;
7089 }
7090
7091 ## Store this row into our result hash, using a good key
7092 $result->{$name} = $row;
7093
7094 ## We may want to run additional SQL per row returned
7095 if (exists $ci->{innerSQL}) {
7096
7097 if ($type eq 'sequence') {
7098 ## If this is a sequence, we want to grab them all at once to reduce
7099 ## the amount of round-trips we do with 'SELECT * FROM seqname'
7100 if (! exists $opt{seqinfoss}{$dbnum}) {
7101 $SQL = q{SELECT quote_ident(nspname)||'.'||quote_ident(relname) AS sname }
7102 . q{FROM pg_class }
7103 . q{JOIN pg_namespace n ON (n.oid = relnamespace) }
7104 . q{WHERE relkind = 'S'};
7105 my $sinfo = run_command($SQL, { dbnumber => $dbnum } );
7106 $SQL = join "\n UNION ALL\n" =>
7107 map { "SELECT '$_->{sname}' AS fullname, * FROM $_->{sname}" }
7108 @{ $sinfo->{db}[0]{slurp}};
7109 $sinfo = run_command($SQL, { dbnumber => $dbnum } );
7110
7111 ## Store it back into the global hash
7112 for my $row (@{ $sinfo->{db}[0]{slurp} }) {
7113 $opt{seqinfoss}{$dbnum}{$row->{fullname}} = $row;
7114 }
7115 }
7116
7117 ## If it does not exist in the cache, just fall through and do it manually!
7118 if (exists $opt{seqinfoss}{$dbnum}{$row->{safename}}) {
7119 $result->{$row->{safename}} = $opt{seqinfoss}{$dbnum}{$row->{safename}};
7120 next ROW;
7121 }
7122 }
7123
7124 (my $SQL2 = $ci->{innerSQL}) =~ s/ROW(\w+)/$row->{lc $1}/g;
7125 my $info2 = run_command($SQL2, { dbnumber => $dbnum } );
7126 for my $row2 (@{ $info2->{db}[0]{slurp} }) {
7127 for my $inner (keys %{ $row2 }) {
7128 $result->{$name}{$inner} = $row2->{$inner};
7129 }
7130 }
7131 }
7132 }
7133
7134 return $result;
7135
7136} ## end of find_catalog_info
7137
7138
7139sub check_sequence {
7140
7141 ## Checks how many values are left in sequences
7142 ## Supports: Nagios, MRTG
7143 ## Warning and critical are percentages
7144 ## Can exclude and include sequences
7145
7146 my ($warning, $critical) = validate_range
7147 ({
7148 type => 'percent',
7149 default_warning => '85%',
7150 default_critical => '95%',
7151 forcemrtg => 1,
7152 });
7153
7154 (my $w = $warning) =~ s/\D//;
7155 (my $c = $critical) =~ s/\D//;
7156
7157 ## Gather up all sequence names
7158 ## no critic
7159 my $SQL = q{
7160SELECT DISTINCT ON (nspname, seqname) nspname, seqname,
7161 quote_ident(nspname) || '.' || quote_ident(seqname) AS safename, typname
7162 -- sequences by column dependency
7163FROM (
7164 SELECT depnsp.nspname, dep.relname as seqname, typname
7165 FROM pg_depend
7166 JOIN pg_class on classid = pg_class.oid
7167 JOIN pg_class dep on dep.oid = objid
7168 JOIN pg_namespace depnsp on depnsp.oid= dep.relnamespace
7169 JOIN pg_class refclass on refclass.oid = refclassid
7170 JOIN pg_class ref on ref.oid = refobjid
7171 JOIN pg_namespace refnsp on refnsp.oid = ref.relnamespace
7172 JOIN pg_attribute refattr ON (refobjid, refobjsubid) = (refattr.attrelid, refattr.attnum)
7173 JOIN pg_type ON refattr.atttypid = pg_type.oid
7174 WHERE pg_class.relname = 'pg_class'
7175 AND refclass.relname = 'pg_class'
7176 AND dep.relkind in ('S')
7177 AND ref.relkind in ('r')
7178 AND typname IN ('int2', 'int4', 'int8')
7179 UNION ALL
7180 --sequences by parsing DEFAULT constraints
7181 SELECT nspname, seq.relname, typname
7182 FROM pg_attrdef
7183 JOIN pg_attribute ON (attrelid, attnum) = (adrelid, adnum)
7184 JOIN pg_type on pg_type.oid = atttypid
7185 JOIN pg_class rel ON rel.oid = attrelid
7186 JOIN pg_class seq ON seq.relname = regexp_replace(adsrc, $re$^nextval\('(.+?)'::regclass\)$$re$, $$\1$$)
7187 AND seq.relnamespace = rel.relnamespace
7188 JOIN pg_namespace nsp ON nsp.oid = seq.relnamespace
7189 WHERE adsrc ~ 'nextval' AND seq.relkind = 'S' AND typname IN ('int2', 'int4', 'int8')
7190 UNION ALL
7191 -- all sequences, to catch those whose associations are not obviously recorded in pg_catalog
7192 SELECT nspname, relname, CAST('int8' AS TEXT)
7193 FROM pg_class
7194 JOIN pg_namespace nsp ON nsp.oid = relnamespace
7195 WHERE relkind = 'S'
7196) AS seqs
7197WHERE nspname !~ '^pg_temp.*'
7198ORDER BY nspname, seqname, typname
7199};
7200 ## use critic
7201
7202 my $info = run_command($SQL, {regex => qr{\w}, emptyok => 1} );
7203
7204 my $MAXINT2 = 32767;
7205 my $MAXINT4 = 2147483647;
7206 my $MAXINT8 = 9223372036854775807;
7207
7208 my $limit = 0;
7209
7210 for $db (@{$info->{db}}) {
7211 my (@crit,@warn,@ok);
7212 my $maxp = 0;
7213 my %seqinfo;
7214 my %seqperf;
7215 my $multidb = @{$info->{db}} > 1 ? "$db->{dbname}." : '';
7216 for my $r (@{$db->{slurp}}) {
7217 my ($schema, $seq, $seqname, $typename) = @$r{qw/ nspname seqname safename typname /};
7218 next if skip_item($seq);
7219 my $maxValue = $typename eq 'int2' ? $MAXINT2 : $typename eq 'int4' ? $MAXINT4 : $MAXINT8;
7220 $SQL = qq{
7221SELECT last_value, slots, used, ROUND(used/slots*100) AS percent,
7222 CASE WHEN slots < used THEN 0 ELSE slots - used END AS numleft
7223FROM (
7224 SELECT last_value,
7225 CEIL((LEAST(max_value, $maxValue)-min_value::numeric+1)/increment_by::NUMERIC) AS slots,
7226 CEIL((last_value-min_value::numeric+1)/increment_by::NUMERIC) AS used
7227FROM $seqname) foo
7228};
7229
7230 my $seqinfo = run_command($SQL, { target => $db });
7231 my $r2 = $seqinfo->{db}[0]{slurp}[0];
7232 my ($last, $slots, $used, $percent, $left) = @$r2{qw/ last_value slots used percent numleft / };
7233 if (! defined $last) {
7234 ndie msg('seq-die', $seqname);
7235 }
7236 my $msg = msg('seq-msg', $seqname, $percent, $left);
7237 my $nicename = perfname("$multidb$seqname");
7238 $seqperf{$percent}{$seqname} = [$left, " $nicename=$percent%;$w%;$c%"];
7239 if ($percent >= $maxp) {
7240 $maxp = $percent;
7241 if (! exists $opt{perflimit} or $limit++ < $opt{perflimit}) {
7242 push @{$seqinfo{$percent}} => $MRTG ? [$seqname,$percent,$slots,$used,$left] : $msg;
7243 }
7244 }
7245 next if $MRTG;
7246
7247 if (length $critical and $percent >= $c) {
7248 push @crit => $msg;
7249 }
7250 elsif (length $warning and $percent >= $w) {
7251 push @warn => $msg;
7252 }
7253 }
7254 if ($MRTG) {
7255 my $msg = join ' | ' => map { $_->[0] } @{$seqinfo{$maxp}};
7256 do_mrtg({one => $maxp, msg => $msg});
7257 }
7258 $limit = 0;
7259 PERF: for my $val (sort { $b <=> $a } keys %seqperf) {
7260 for my $seq (sort { $seqperf{$val}{$a}->[0] <=> $seqperf{$val}{$b}->[0] or $a cmp $b } keys %{$seqperf{$val}}) {
7261 last PERF if exists $opt{perflimit} and $limit++ >= $opt{perflimit};
7262 $db->{perf} .= $seqperf{$val}{$seq}->[1];
7263 }
7264 }
7265
7266 if (@crit) {
7267 add_critical join ' ' => @crit;
7268 }
7269 elsif (@warn) {
7270 add_warning join ' ' => @warn;
7271 }
7272 else {
7273 if (keys %seqinfo) {
7274 add_ok join ' ' => @{$seqinfo{$maxp}};
7275 }
7276 else {
7277 add_ok msg('seq-none');
7278 }
7279 }
7280 }
7281
7282 return;
7283
7284} ## end of check_sequence
7285
7286
7287sub check_settings_checksum {
7288
7289 ## Verify the checksum of all settings
7290 ## Supports: Nagios, MRTG
7291 ## Not that this will vary from user to user due to ALTER USER
7292 ## and because superusers see additional settings
7293 ## One of warning or critical must be given (but not both)
7294 ## It should run one time to find out the expected checksum
7295 ## You can use --critical="0" to find out the checksum
7296 ## You can include or exclude settings as well
7297 ## Example:
7298 ## check_postgres_settings_checksum --critical="4e7ba68eb88915d3d1a36b2009da4acd"
7299
7300 my ($warning, $critical) = validate_range({type => 'checksum', onlyone => 1});
7301
7302 eval {
7303 require Digest::MD5;
7304 };
7305 if ($@) {
7306 ndie msg('checksum-nomd');
7307 }
7308
7309 $SQL = 'SELECT name, setting FROM pg_settings ORDER BY name';
7310 my $info = run_command($SQL, { regex => qr[client_encoding] });
7311
7312 for $db (@{$info->{db}}) {
7313
7314 my $newstring = '';
7315 for my $r (@{$db->{slurp}}) {
7316 next SLURP if skip_item($r->{name});
7317 $newstring .= "$r->{name} $r->{setting}\n";
7318 }
7319 if (! length $newstring) {
7320 add_unknown msg('no-match-set');
7321 }
7322
7323 my $checksum = Digest::MD5::md5_hex($newstring);
7324
7325 my $msg = msg('checksum-msg', $checksum);
7326 if ($MRTG) {
7327 $opt{mrtg} or ndie msg('checksum-nomrtg');
7328 do_mrtg({one => $opt{mrtg} eq $checksum ? 1 : 0, msg => $checksum});
7329 }
7330 if ($critical and $critical ne $checksum) {
7331 add_critical $msg;
7332 }
7333 elsif ($warning and $warning ne $checksum) {
7334 add_warning $msg;
7335 }
7336 elsif (!$critical and !$warning) {
7337 add_unknown $msg;
7338 }
7339 else {
7340 add_ok $msg;
7341 }
7342 }
7343
7344 return;
7345
7346} ## end of check_settings_checksum
7347
7348
7349sub check_slony_status {
7350
7351 ## Checks the sl_status table
7352 ## Returns unknown if sl_status is not found
7353 ## Returns critical is status is not "good"
7354 ## Otherwise, returns based on time-based warning and critical options
7355 ## Supports: Nagios, MRTG
7356
7357 my ($warning, $critical) = validate_range
7358 ({
7359 type => 'time',
7360 default_warning => '60',
7361 default_critical => '300',
7362 });
7363
7364 ## If given schemas on the command-line, map back to targetdbs
7365 if (defined $opt{schema}) {
7366 my $x = 0;
7367 for my $db (@targetdb) {
7368 $db->{schemalist} = $opt{schema}->[$x] || '';
7369 $x++;
7370 }
7371 }
7372 else {
7373 ## Otherwise, find all slony schemas and put them in ourselves
7374 $SQL = q{SELECT quote_ident(nspname) AS nspname FROM pg_namespace WHERE oid IN }.
7375 q{(SELECT relnamespace FROM pg_class WHERE relkind = 'v' AND relname = 'sl_status')};
7376 my $info = run_command($SQL);
7377 for my $db (@{ $info->{db} }) {
7378 $db->{schemalist} = join ',' => map { $_->{nspname} } @{ $db->{slurp} };
7379 }
7380 }
7381
7382 my $SLSQL =
7383q{SELECT
7384 ROUND(EXTRACT(epoch FROM st_lag_time)) AS lagtime,
7385 st_origin,
7386 st_received,
7387 current_database() AS cd,
7388 COALESCE(n1.no_comment, '') AS com1,
7389 COALESCE(n2.no_comment, '') AS com2
7390FROM SCHEMA.sl_status
7391JOIN SCHEMA.sl_node n1 ON (n1.no_id=st_origin)
7392JOIN SCHEMA.sl_node n2 ON (n2.no_id=st_received)};
7393
7394 my $maxlagtime = -1;
7395
7396 my $x = 1;
7397 for $db (@targetdb) {
7398 next if ! $db->{schemalist};
7399 $db->{perf} = '';
7400 my @perf;
7401 for my $schema (split /,/ => $db->{schemalist}) {
7402 ## Set for output
7403 $db->{showschema} = $schema;
7404
7405 (my $SQL = $SLSQL) =~ s/SCHEMA/$schema/g;
7406 my $info = run_command($SQL, { dbnumber => $x });
7407 my $slurp = $info->{db}[0]{slurp}[0];
7408 if (! defined $slurp->{lagtime}) {
7409 add_unknown msg('slony-nonumber');
7410 return;
7411 }
7412 my ($lag,$from,$to,$dbname,$fromc,$toc) = @$slurp{qw/ lagtime st_origin st_received cd com1 com2/};
7413 $maxlagtime = $lag if $lag > $maxlagtime;
7414 push @perf => [
7415 $lag,
7416 $from,
7417 qq{'$dbname.$schema Node $from($fromc) -> Node $to($toc)'=$lag;$warning;$critical},
7418 ];
7419
7420 } ## end each schema in this database
7421
7422 if ($MRTG) {
7423 do_mrtg({one => $maxlagtime});
7424 return;
7425 }
7426
7427 $db->{perf} .= join "\n" => map { $_->[2] } sort { $b->[0]<=>$a->[0] or $a->[1] cmp $b->[1] } @perf;
7428
7429 my $msg = msg('slony-lagtime', $maxlagtime);
7430 $msg .= sprintf ' (%s)', pretty_time($maxlagtime, $maxlagtime > 500 ? 'S' : '');
7431 if (length $critical and $maxlagtime >= $critical) {
7432 add_critical $msg;
7433 }
7434 elsif (length $warning and $maxlagtime >= $warning) {
7435 add_warning $msg;
7436 }
7437 else {
7438 add_ok $msg;
7439 }
7440
7441 $x++;
7442 }
7443
7444 if ($maxlagtime < 1) { ## No schemas found
7445 add_unknown msg('slony-noschema');
7446 }
7447
7448 return;
7449
7450} ## end of check_slony_status
7451
7452
7453sub check_timesync {
7454
7455 ## Compare local time to the database time
7456 ## Supports: Nagios, MRTG
7457 ## Warning and critical are given in number of seconds difference
7458
7459 my ($warning,$critical) = validate_range
7460 ({
7461 type => 'seconds',
7462 default_warning => 2,
7463 default_critical => 5,
7464 });
7465
7466 $SQL = q{SELECT round(extract(epoch FROM now())) AS epok, TO_CHAR(now(),'YYYY-MM-DD HH24:MI:SS') AS pretti};
7467 my $info = run_command($SQL);
7468 my $localepoch = time;
7469 my @l = localtime;
7470
7471 for $db (@{$info->{db}}) {
7472 my ($pgepoch,$pgpretty) = @{$db->{slurp}->[0]}{qw/ epok pretti /};
7473
7474 my $diff = abs($pgepoch - $localepoch);
7475 if ($MRTG) {
7476 do_mrtg({one => $diff, msg => "DB: $db->{dbname}"});
7477 }
7478 $db->{perf} = sprintf '%s=%ss;%s;%s',
7479 perfname(msg('timesync-diff')), $diff, $warning, $critical;
7480
7481 my $localpretty = sprintf '%d-%02d-%02d %02d:%02d:%02d', $l[5]+1900, $l[4]+1, $l[3],$l[2],$l[1],$l[0];
7482 my $msg = msg('timesync-msg', $diff, $pgpretty, $localpretty);
7483
7484 if (length $critical and $diff >= $critical) {
7485 add_critical $msg;
7486 }
7487 elsif (length $warning and $diff >= $warning) {
7488 add_warning $msg;
7489 }
7490 else {
7491 add_ok $msg;
7492 }
7493 }
7494 return;
7495
7496} ## end of check_timesync
7497
7498
7499sub check_txn_idle {
7500
7501 ## Check the duration and optionally number of "idle in transaction" processes
7502 ## Supports: Nagios, MRTG
7503 ## It makes no sense to run this more than once on the same cluster
7504 ## Warning and critical are time limits or counts for time limits - default to seconds
7505 ## Valid time units: s[econd], m[inute], h[our], d[ay]
7506 ## All above may be written as plural as well (e.g. "2 hours")
7507 ## Valid counts for time limits: "$int for $time"
7508 ## Can also ignore databases with exclude and limit with include
7509 ## Limit to a specific user with the includeuser option
7510 ## Exclude users with the excludeuser option
7511
7512 my $type = shift || 'txnidle';
7513 my $thing = shift || msg('transactions');
7514 my $perf = shift || msg('txn-time');
7515 my $start = shift || 'query_start';
7516 my $clause = shift || q{current_query ~ '^<'};
7517
7518 ## Extract the warning and critical seconds and counts.
7519 ## If not given, items will be an empty string
7520 my ($wcount, $wtime, $ccount, $ctime) = validate_integer_for_time();
7521
7522 ## We don't GROUP BY because we want details on every connection
7523 ## Someday we may even break things down by database
7524 my ($SQL2, $SQL3);
7525 if ($type ne 'qtime') {
7526 $SQL = q{SELECT datname, datid, procpid AS pid, usename, client_addr, xact_start, current_query AS current_query, }.
7527 q{CASE WHEN client_port < 0 THEN 0 ELSE client_port END AS client_port, }.
7528 qq{COALESCE(ROUND(EXTRACT(epoch FROM now()-$start)),0) AS seconds }.
7529 qq{FROM pg_stat_activity WHERE $clause$USERWHERECLAUSE }.
7530 q{ORDER BY xact_start, query_start, procpid DESC};
7531 ## Craft an alternate version for old servers that do not have the xact_start column:
7532 ($SQL2 = $SQL) =~ s/xact_start/query_start AS xact_start/;
7533 $SQL2 =~ s/BY xact_start,/BY/;
7534 }
7535 else {
7536 $SQL = q{SELECT datname, datid, procpid AS pid, usename, client_addr, current_query AS current_query, }.
7537 q{CASE WHEN client_port < 0 THEN 0 ELSE client_port END AS client_port, }.
7538 qq{COALESCE(ROUND(EXTRACT(epoch FROM now()-$start)),0) AS seconds }.
7539 qq{FROM pg_stat_activity WHERE $clause$USERWHERECLAUSE }.
7540 q{ORDER BY query_start, procpid DESC};
7541 }
7542
7543 ## Craft an alternate version for new servers which do not have procpid
7544 ($SQL3 = $SQL) =~ s/procpid/pid/g;
7545 $SQL3 =~ s/current_query/query/g;
7546
7547 my $info = run_command($SQL, { emptyok => 1 , version => [ "<8.3 $SQL2", ">9.1 $SQL3" ] } );
7548
7549 ## Extract the first entry
7550 $db = $info->{db}[0];
7551
7552 ## Store the current longest row
7553 my $maxr = { seconds => 0 };
7554
7555 ## How many valid rows did we get?
7556 my $count = 0;
7557
7558 ## Info about the top offender
7559 my $whodunit = '';
7560 if ($MRTG) {
7561 if (defined $db->{dbname}) {
7562 $whodunit = "DB: $db->{dbname}";
7563 } else {
7564 $whodunit = sprintf q{DB: %s}, msg('no-db');
7565 }
7566 }
7567
7568 ## Process each returned row
7569 for my $r (@{ $db->{slurp} }) {
7570
7571 ## Skip if we don't care about this database
7572 next if skip_item($r->{datname});
7573
7574 ## We do a lot of filtering based on the current_query
7575 my $cq = $r->{current_query};
7576
7577 ## Return unknown if we cannot see because we are a non-superuser
7578 if ($cq =~ /insufficient/o) {
7579 add_unknown msg('psa-nosuper');
7580 return;
7581 }
7582
7583 ## Return unknown if stats_command_string / track_activities is off
7584 if ($cq =~ /disabled/o or $cq =~ /<command string not enabled>/) {
7585 add_unknown msg('psa-disabled');
7586 return;
7587 }
7588
7589 ## Detect other cases where pg_stat_activity is not fully populated
7590 if ($type ne 'qtime' and length $r->{xact_start} and $r->{xact_start} !~ /\d/o) {
7591 add_unknown msg('psa-noexact');
7592 return;
7593 }
7594
7595 ## Filter out based on the action
7596 next if $action eq 'txn_idle' and $cq ne '<IDLE> in transaction';
7597
7598 ## Keep track of the longest overall time
7599 $maxr = $r if $r->{seconds} >= $maxr->{seconds};
7600
7601 $count++;
7602 }
7603
7604 ## If there were no matches, then there were no rows, or no non-excluded rows
7605 ## We don't care which at the moment, and return the same message
7606 if (! $count) {
7607 $MRTG and do_mrtg({one => 0, msg => $whodunit});
7608 $db->{perf} = "$perf=0;$wtime;$ctime";
7609
7610 add_ok msg("$type-none");
7611 return;
7612 }
7613
7614 ## Extract the seconds to avoid typing out the hash each time
7615 my $max = $maxr->{seconds};
7616
7617 ## See if we have a minimum number of matches
7618 my $base_count = $wcount || $ccount;
7619 if ($base_count and $count < $base_count) {
7620 $db->{perf} = "$perf=$count;$wcount;$ccount";
7621 add_ok msg("$type-count-none", $base_count);
7622 return;
7623 }
7624
7625 ## Details on who the top offender was
7626 if ($max > 0) {
7627 $whodunit = sprintf q{%s:%s %s:%s %s:%s%s%s},
7628 msg('PID'), $maxr->{pid},
7629 msg('database'), $maxr->{datname},
7630 msg('username'), $maxr->{usename},
7631 $maxr->{client_addr} eq '' ? '' : (sprintf ' %s:%s', msg('address'), $maxr->{client_addr}),
7632 ($maxr->{client_port} eq '' or $maxr->{client_port} < 1)
7633 ? '' : (sprintf ' %s:%s', msg('port'), $maxr->{client_port});
7634 }
7635
7636 ## For MRTG, we can simply exit right now
7637 if ($MRTG) {
7638 do_mrtg({one => $max, msg => $whodunit});
7639 exit;
7640 }
7641
7642 ## If the number of seconds is high, show an alternate form
7643 my $ptime = $max > 300 ? ' (' . pretty_time($max) . ')' : '';
7644
7645 ## Show the maximum number of seconds in the perf section
7646 $db->{perf} .= sprintf q{%s=%ss;%s;%s},
7647 $perf,
7648 $max,
7649 $wtime,
7650 $ctime;
7651
7652 if (length $ctime and length $ccount) {
7653 if ($max >= $ctime and $count >= $ccount) {
7654 add_critical msg("$type-for-msg", $count, $ctime, $max, $ptime, $whodunit);
7655 return;
7656 }
7657 }
7658 elsif (length $ctime) {
7659 if ($max >= $ctime) {
7660 add_critical msg("$type-msg", $max, $ptime, $whodunit);
7661 return;
7662 }
7663 }
7664 elsif (length $ccount) {
7665 if ($count >= $ccount) {
7666 add_critical msg("$type-count-msg", $count);
7667 return;
7668 }
7669 }
7670
7671 if (length $wtime and length $wcount) {
7672 if ($max >= $wtime and $count >= $wcount) {
7673 add_warning msg("$type-for-msg", $count, $wtime, $max, $ptime, $whodunit);
7674 return;
7675 }
7676 }
7677 elsif (length $wtime) {
7678 if ($max >= $wtime) {
7679 add_warning msg("$type-msg", $max, $ptime, $whodunit);
7680 return;
7681 }
7682 }
7683 elsif (length $wcount) {
7684 if ($count >= $wcount) {
7685 add_warning msg("$type-count-msg", $count);
7686 return;
7687 }
7688 }
7689
7690 add_ok msg("$type-msg", $max, $ptime, $whodunit);
7691
7692 return;
7693
7694} ## end of check_txn_idle
7695
7696
7697sub check_txn_time {
7698
7699 ## This is the same as check_txn_idle, but we want where the
7700 ## transaction start time is not null
7701
7702 check_txn_idle('txntime',
7703 '',
7704 '',
7705 'xact_start',
7706 q{xact_start IS NOT NULL});
7707
7708 return;
7709
7710} ## end of check_txn_time
7711
7712
7713sub check_txn_wraparound {
7714
7715 ## Check how close to transaction wraparound we are on all databases
7716 ## Supports: Nagios, MRTG
7717 ## Warning and critical are the number of transactions performed
7718 ## Thus, anything *over* that number will trip the alert
7719 ## See: http://www.postgresql.org/docs/current/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
7720 ## It makes no sense to run this more than once on the same cluster
7721
7722 my ($warning, $critical) = validate_range
7723 ({
7724 type => 'positive integer',
7725 default_warning => 1_300_000_000,
7726 default_critical => 1_400_000_000,
7727 });
7728
7729 if ($warning and $warning >= 2_000_000_000) {
7730 ndie msg('txnwrap-wbig');
7731 }
7732 if ($critical and $critical >= 2_000_000_000) {
7733 ndie msg('txnwrap-cbig');
7734 }
7735
7736 $SQL = q{SELECT datname, age(datfrozenxid) AS age FROM pg_database WHERE datallowconn ORDER BY 1, 2};
7737 my $info = run_command($SQL, { regex => qr[\w+\s+\|\s+\d+] } );
7738
7739 my ($mrtgmax,$mrtgmsg) = (0,'?');
7740 for $db (@{$info->{db}}) {
7741 my ($max,$msg) = (0,'?');
7742 for my $r (@{$db->{slurp}}) {
7743 my ($dbname,$dbtxns) = ($r->{datname},$r->{age});
7744 $db->{perf} .= sprintf ' %s=%s;%s;%s;%s;%s',
7745 perfname($dbname), $dbtxns, $warning, $critical, 0, 2000000000;
7746 next SLURP if skip_item($dbname);
7747 if ($dbtxns > $max) {
7748 $max = $dbtxns;
7749 $msg = qq{$dbname: $dbtxns};
7750 if ($dbtxns > $mrtgmax) {
7751 $mrtgmax = $dbtxns;
7752 $mrtgmsg = "DB: $dbname";
7753 }
7754 }
7755 }
7756 if (length $critical and $max >= $critical) {
7757 add_critical $msg;
7758 }
7759 elsif (length $warning and $max >= $warning) {
7760 add_warning $msg;
7761 }
7762 else {
7763 add_ok $msg;
7764 }
7765 }
7766 $MRTG and do_mrtg({one => $mrtgmax, msg => $mrtgmsg});
7767
7768 return;
7769
7770} ## end of check_txn_wraparound
7771
7772
7773sub check_version {
7774
7775 ## Compare version with what we think it should be
7776 ## Supports: Nagios, MRTG
7777 ## Warning and critical are the major and minor (e.g. 8.3)
7778 ## or the major, minor, and revision (e.g. 8.2.4 or even 8.3beta4)
7779
7780 if ($MRTG) {
7781 if (!exists $opt{mrtg} or $opt{mrtg} !~ /^\d+\.\d+/) {
7782 ndie msg('version-badmrtg');
7783 }
7784 if ($opt{mrtg} =~ /^\d+\.\d+$/) {
7785 $opt{critical} = $opt{mrtg};
7786 }
7787 else {
7788 $opt{warning} = $opt{mrtg};
7789 }
7790 }
7791
7792 my ($warning, $critical) = validate_range({type => 'version', forcemrtg => 1});
7793
7794 my ($warnfull, $critfull) = (($warning =~ /^\d+\.\d+$/ ? 0 : 1),($critical =~ /^\d+\.\d+$/ ? 0 : 1));
7795
7796 my $info = run_command('SELECT version() AS version');
7797
7798 for $db (@{$info->{db}}) {
7799 my $row = $db->{slurp}[0];
7800 if ($row->{version} !~ /((\d+\.\d+)(\w+|\.\d+))/o) {
7801 add_unknown msg('invalid-query', $row->{version});
7802 next;
7803 }
7804 my ($full,$version,$revision) = ($1,$2,$3||'?');
7805 $revision =~ s/^\.//;
7806
7807 my $ok = 1;
7808
7809 if (length $critical) {
7810 if (($critfull and $critical ne $full)
7811 or (!$critfull and $critical ne $version)) {
7812 $MRTG and do_mrtg({one => 0, msg => $full});
7813 add_critical msg('version-fail', $full, $critical);
7814 $ok = 0;
7815 }
7816 }
7817 elsif (length $warning) {
7818 if (($warnfull and $warning ne $full)
7819 or (!$warnfull and $warning ne $version)) {
7820 $MRTG and do_mrtg({one => 0, msg => $full});
7821 add_warning msg('version-fail', $full, $warning);
7822 $ok = 0;
7823 }
7824 }
7825 if ($ok) {
7826 $MRTG and do_mrtg({one => 1, msg => $full});
7827 add_ok msg('version-ok', $full);
7828 }
7829 }
7830
7831 return;
7832
7833} ## end of check_version
7834
7835
7836sub check_wal_files {
7837
7838 ## Check on the number of WAL, or WAL "ready", files in use
7839 ## Supports: Nagios, MRTG
7840 ## Must run as a superuser
7841 ## Critical and warning are the number of files
7842 ## Example: --critical=40
7843
7844 my $subdir = shift || '';
7845 my $extrabit = shift || '';
7846
7847 my $default_warning = shift || 10;
7848 my $default_critical = shift || 15;
7849
7850 my $arg = {type => 'positive integer', leastone => 1};
7851 if ($default_warning) {
7852 $arg->{default_warning} = $default_warning;
7853 }
7854 if ($default_critical) {
7855 $arg->{default_critical} = $default_critical;
7856 }
7857
7858 my ($warning, $critical) = validate_range($arg);
7859
7860 ## Figure out where the pg_xlog directory is
7861 $SQL = qq{SELECT count(*) AS count FROM pg_ls_dir('pg_xlog$subdir') WHERE pg_ls_dir ~ E'^[0-9A-F]{24}$extrabit\$'}; ## no critic (RequireInterpolationOfMetachars)
7862
7863 my $info = run_command($SQL, {regex => qr[\d] });
7864
7865 my $found = 0;
7866 for $db (@{$info->{db}}) {
7867 my $r = $db->{slurp}[0];
7868 my $numfiles = $r->{count};
7869 if ($MRTG) {
7870 do_mrtg({one => $numfiles});
7871 }
7872 my $msg = $extrabit ? msg('wal-numfound2', $numfiles, $extrabit)
7873 : msg('wal-numfound', $numfiles);
7874 $db->{perf} .= sprintf '%s=%s;%s;%s',
7875 perfname(msg('files')), $numfiles, $warning, $critical;
7876 if (length $critical and $numfiles > $critical) {
7877 add_critical $msg;
7878 }
7879 elsif (length $warning and $numfiles > $warning) {
7880 add_warning $msg;
7881 }
7882 else {
7883 add_ok $msg;
7884 }
7885 }
7886
7887 return;
7888
7889} ## end of check_wal_files
7890
7891
7892
7893=pod
7894
7895=head1 NAME
7896
7897B<check_postgres.pl> - a Postgres monitoring script for Nagios, MRTG, Cacti, and others
7898
7899This documents describes check_postgres.pl version 2.20.0
7900
7901=head1 SYNOPSIS
7902
7903 ## Create all symlinks
7904 check_postgres.pl --symlinks
7905
7906 ## Check connection to Postgres database 'pluto':
7907 check_postgres.pl --action=connection --db=pluto
7908
7909 ## Same things, but using the symlink
7910 check_postgres_connection --db=pluto
7911
7912 ## Warn if > 100 locks, critical if > 200, or > 20 exclusive
7913 check_postgres_locks --warning=100 --critical="total=200:exclusive=20"
7914
7915 ## Show the current number of idle connections on port 6543:
7916 check_postgres_txn_idle --port=6543 --output=simple
7917
7918 ## There are many other actions and options, please keep reading.
7919
7920 The latest news and documentation can always be found at:
7921 http://bucardo.org/check_postgres/
7922
7923=head1 DESCRIPTION
7924
7925check_postgres.pl is a Perl script that runs many different tests against
7926one or more Postgres databases. It uses the psql program to gather the
7927information, and outputs the results in one of three formats: Nagios, MRTG,
7928or simple.
7929
7930=head2 Output Modes
7931
7932The output can be changed by use of the C<--output> option. The default output
7933is nagios, although this can be changed at the top of the script if you wish. The
7934current option choices are B<nagios>, B<mrtg>, and B<simple>. To avoid having to
7935enter the output argument each time, the type of output is automatically set
7936if no --output argument is given, and if the current directory has one of the
7937output options in its name. For example, creating a directory named mrtg and
7938populating it with symlinks via the I<--symlinks> argument would ensure that
7939any actions run from that directory will always default to an output of "mrtg"
7940As a shortcut for --output=simple, you can enter --simple, which also overrides
7941the directory naming trick.
7942
7943
7944=head3 Nagios output
7945
7946The default output format is for Nagios, which is a single line of information, along
7947with four specific exit codes:
7948
7949=over 2
7950
7951=item 0 (OK)
7952
7953=item 1 (WARNING)
7954
7955=item 2 (CRITICAL)
7956
7957=item 3 (UNKNOWN)
7958
7959=back
7960
7961The output line is one of the words above, a colon, and then a short description of what
7962was measured. Additional statistics information, as well as the total time the command
7963took, can be output as well: see the documentation on the arguments
7964I<L<--showperf|/--showperf=VAL>>,
7965I<L<--perflimit|/--perflimit=i>>, and
7966I<L<--showtime|/--showtime=VAL>>.
7967
7968=head3 MRTG output
7969
7970The MRTG output is four lines, with the first line always giving a single number of importance.
7971When possible, this number represents an actual value such as a number of bytes, but it
7972may also be a 1 or a 0 for actions that only return "true" or "false", such as check_postgres_version.
7973The second line is an additional stat and is only used for some actions. The third line indicates
7974an "uptime" and is not used. The fourth line is a description and usually indicates the name of
7975the database the stat from the first line was pulled from, but may be different depending on the
7976action.
7977
7978Some actions accept an optional I<--mrtg> argument to further control the output.
7979
7980See the documentation on each action for details on the exact MRTG output for each one.
7981
7982=head3 Simple output
7983
7984The simple output is simply a truncated version of the MRTG one, and simply returns the first number
7985and nothing else. This is very useful when you just want to check the state of something, regardless
7986of any threshold. You can transform the numeric output by appending KB, MB, GB, TB, or EB to the output
7987argument, for example:
7988
7989 --output=simple,MB
7990
7991=head3 Cacti output
7992
7993The Cacti output consists of one or more items on the same line, with a simple name, a colon, and
7994then a number. At the moment, the only action with explicit Cacti output is 'dbstats', and using
7995the --output option is not needed in this case, as Cacti is the only output for this action. For many
7996other actions, using --simple is enough to make Cacti happy.
7997
7998=head1 DATABASE CONNECTION OPTIONS
7999
8000All actions accept a common set of database options.
8001
8002=over 4
8003
8004=item B<-H NAME> or B<--host=NAME>
8005
8006Connect to the host indicated by NAME. Can be a comma-separated list of names. Multiple host arguments
8007are allowed. If no host is given, defaults to the C<PGHOST> environment variable or no host at all
8008(which indicates using a local Unix socket). You may also use "--dbhost".
8009
8010=item B<-p PORT> or B<--port=PORT>
8011
8012Connects using the specified PORT number. Can be a comma-separated list of port numbers, and multiple
8013port arguments are allowed. If no port number is given, defaults to the C<PGPORT> environment variable. If
8014that is not set, it defaults to 5432. You may also use "--dbport"
8015
8016=item B<-db NAME> or B<--dbname=NAME>
8017
8018Specifies which database to connect to. Can be a comma-separated list of names, and multiple dbname
8019arguments are allowed. If no dbname option is provided, defaults to the C<PGDATABASE> environment variable.
8020If that is not set, it defaults to 'postgres' if psql is version 8 or greater, and 'template1' otherwise.
8021
8022=item B<-u USERNAME> or B<--dbuser=USERNAME>
8023
8024The name of the database user to connect as. Can be a comma-separated list of usernames, and multiple
8025dbuser arguments are allowed. If this is not provided, it defaults to the C<PGUSER> environment variable, otherwise
8026it defaults to 'postgres'.
8027
8028=item B<--dbpass=PASSWORD>
8029
8030Provides the password to connect to the database with. Use of this option is highly discouraged.
8031Instead, one should use a .pgpass or pg_service.conf file.
8032
8033=item B<--dbservice=NAME>
8034
8035The name of a service inside of the pg_service.conf file. Before version 9.0 of Postgres, this is
8036a global file, usually found in /etc/pg_service.conf. If you are using version 9.0 or higher of
8037Postgres, you can use the file ".pg_service.conf" in the home directory of the user running
8038the script, e.g. nagios.
8039
8040This file contains a simple list of connection options. You can also pass additional information
8041when using this option such as --dbservice="maindatabase sslmode=require"
8042
8043The documentation for this file can be found at
8044http://www.postgresql.org/docs/current/static/libpq-pgservice.html
8045
8046=back
8047
8048The database connection options can be grouped: I<--host=a,b --host=c --port=1234 --port=3344>
8049would connect to a-1234, b-1234, and c-3344. Note that once set, an option
8050carries over until it is changed again.
8051
8052Examples:
8053
8054 --host=a,b --port=5433 --db=c
8055 Connects twice to port 5433, using database c, to hosts a and b: a-5433-c b-5433-c
8056
8057 --host=a,b --port=5433 --db=c,d
8058 Connects four times: a-5433-c a-5433-d b-5433-c b-5433-d
8059
8060 --host=a,b --host=foo --port=1234 --port=5433 --db=e,f
8061 Connects six times: a-1234-e a-1234-f b-1234-e b-1234-f foo-5433-e foo-5433-f
8062
8063 --host=a,b --host=x --port=5432,5433 --dbuser=alice --dbuser=bob -db=baz
8064 Connects three times: a-5432-alice-baz b-5433-alice-baz x-5433-bob-baz
8065
8066 --dbservice="foo" --port=5433
8067 Connects using the named service 'foo' in the pg_service.conf file, but overrides the port
8068
8069=head1 OTHER OPTIONS
8070
8071Other options include:
8072
8073=over 4
8074
8075=item B<--action=NAME>
8076
8077States what action we are running. Required unless using a symlinked file,
8078in which case the name of the file is used to figure out the action.
8079
8080=item B<--warning=VAL or -w VAL>
8081
8082Sets the threshold at which a warning alert is fired. The valid options for this
8083option depends on the action used.
8084
8085=item B<--critical=VAL or -c VAL>
8086
8087Sets the threshold at which a critical alert is fired. The valid options for this
8088option depends on the action used.
8089
8090=item B<-t VAL> or B<--timeout=VAL>
8091
8092Sets the timeout in seconds after which the script will abort whatever it is doing
8093and return an UNKNOWN status. The timeout is per Postgres cluster, not for the entire
8094script. The default value is 10; the units are always in seconds.
8095
8096=item B<--assume-standby-mode>
8097
8098If specified, first the check if server in standby mode will be performed
8099(--datadir is required), if so, all checks that require SQL queries will be
8100ignored and "Server in standby mode" with OK status will be returned instead.
8101
8102Example:
8103
8104 postgres@db$./check_postgres.pl --action=version --warning=8.1 --datadir /var/lib/postgresql/8.3/main/ --assume-standby-mode
8105 POSTGRES_VERSION OK: Server in standby mode | time=0.00
8106
8107=item B<--assume-prod>
8108
8109If specified, check if server in production mode is performed (--datadir is required).
8110The option is only relevant for (C<symlink: check_postgres_checkpoint>).
8111
8112Example:
8113
8114 postgres@db$./check_postgres.pl --action=checkpoint --datadir /var/lib/postgresql/8.3/main/ --assume-prod
8115 POSTGRES_CHECKPOINT OK: Last checkpoint was 72 seconds ago | age=72;;300 mode=MASTER
8116
8117=item B<-h> or B<--help>
8118
8119Displays a help screen with a summary of all actions and options.
8120
8121=item B<--man>
8122
8123Displays the entire manual.
8124
8125=item B<-V> or B<--version>
8126
8127Shows the current version.
8128
8129=item B<-v> or B<--verbose>
8130
8131Set the verbosity level. Can call more than once to boost the level. Setting it to three
8132or higher (in other words, issuing C<-v -v -v>) turns on debugging information for this
8133program which is sent to stderr.
8134
8135=item B<--showperf=VAL>
8136
8137Determines if we output additional performance data in standard Nagios format
8138(at end of string, after a pipe symbol, using name=value).
8139VAL should be 0 or 1. The default is 1. Only takes effect if using Nagios output mode.
8140
8141=item B<--perflimit=i>
8142
8143Sets a limit as to how many items of interest are reported back when using the
8144I<showperf> option. This only has an effect for actions that return a large
8145number of items, such as B<table_size>. The default is 0, or no limit. Be
8146careful when using this with the I<--include> or I<--exclude> options, as
8147those restrictions are done I<after> the query has been run, and thus your
8148limit may not include the items you want. Only takes effect if using Nagios output mode.
8149
8150=item B<--showtime=VAL>
8151
8152Determines if the time taken to run each query is shown in the output. VAL
8153should be 0 or 1. The default is 1. No effect unless I<showperf> is on.
8154Only takes effect if using Nagios output mode.
8155
8156=item B<--test>
8157
8158Enables test mode. See the L</"TEST MODE"> section below.
8159
8160=item B<--PGBINDIR=PATH>
8161
8162Tells the script where to find the psql binaries. Useful if you have more than
8163one version of the PostgreSQL executables on your system, or if there are not
8164in your path. Note that this option is in all uppercase. By default, this option
8165is I<not allowed>. To enable it, you must change the C<$NO_PSQL_OPTION> near the
8166top of the script to 0. Avoid using this option if you can, and instead use
8167environment variable c<PGBINDIR> or hard-coded C<$PGBINDIR> variable, also near
8168the top of the script, to set the path to the PostgreSQL to use.
8169
8170=item B<--PSQL=PATH>
8171
8172I<(deprecated, this option may be removed in a future release!)>
8173Tells the script where to find the psql program. Useful if you have more than
8174one version of the psql executable on your system, or if there is no psql program
8175in your path. Note that this option is in all uppercase. By default, this option
8176is I<not allowed>. To enable it, you must change the C<$NO_PSQL_OPTION> near the
8177top of the script to 0. Avoid using this option if you can, and instead hard-code
8178your psql location into the C<$PSQL> variable, also near the top of the script.
8179
8180=item B<--symlinks>
8181
8182Creates symlinks to the main program for each action.
8183
8184=item B<--output=VAL>
8185
8186Determines the format of the output, for use in various programs. The
8187default is 'nagios'. Available options are 'nagios', 'mrtg', 'simple'
8188and 'cacti'.
8189
8190=item B<--mrtg=VAL>
8191
8192Used only for the MRTG or simple output, for a few specific actions.
8193
8194=item B<--debugoutput=VAL>
8195
8196Outputs the exact string returned by psql, for use in debugging. The value is one or more letters,
8197which determine if the output is displayed or not, where 'a' = all, 'c' = critical, 'w' = warning,
8198'o' = ok, and 'u' = unknown. Letters can be combined.
8199
8200=item B<--get_method=VAL>
8201
8202Allows specification of the method used to fetch information for the C<new_version_cp>,
8203C<new_version_pg>, C<new_version_bc>, C<new_version_box>, and C<new_version_tnm> checks.
8204The following programs are tried, in order, to grab the information from the web:
8205GET, wget, fetch, curl, lynx, links. To force the use of just one (and thus remove the
8206overhead of trying all the others until one of those works), enter one of the names as
8207the argument to get_method. For example, a BSD box might enter the following line in
8208their C<.check_postgresrc> file:
8209
8210 get_method=fetch
8211
8212=item B<--language=VAL>
8213
8214Set the language to use for all output messages. Normally, this is detected by examining
8215the environment variables LC_ALL, LC_MESSAGES, and LANG, but setting this option
8216will override any such detection.
8217
8218=back
8219
8220
8221=head1 ACTIONS
8222
8223The script runs one or more actions. This can either be done with the --action
8224flag, or by using a symlink to the main file that contains the name of the action
8225inside of it. For example, to run the action "timesync", you may either issue:
8226
8227 check_postgres.pl --action=timesync
8228
8229or use a program named:
8230
8231 check_postgres_timesync
8232
8233All the symlinks are created for you in the current directory
8234if use the option --symlinks
8235
8236 perl check_postgres.pl --symlinks
8237
8238If the file name already exists, it will not be overwritten. If the file exists
8239and is a symlink, you can force it to overwrite by using "--action=build_symlinks_force"
8240
8241Most actions take a I<--warning> and a I<--critical> option, indicating at what
8242point we change from OK to WARNING, and what point we go to CRITICAL. Note that
8243because criticals are always checked first, setting the warning equal to the
8244critical is an effective way to turn warnings off and always give a critical.
8245
8246The current supported actions are:
8247
8248=head2 B<archive_ready>
8249
8250(C<symlink: check_postgres_archive_ready>) Checks how many WAL files with extension F<.ready>
8251exist in the F<pg_xlog/archive_status> directory, which is found
8252off of your B<data_directory>. This action must be run as a superuser, in order to access the
8253contents of the F<pg_xlog/archive_status> directory. The minimum version to use this action is
8254Postgres 8.1. The I<--warning> and I<--critical> options are simply the number of
8255F<.ready> files in the F<pg_xlog/archive_status> directory.
8256Usually, these values should be low, turning on the archive mechanism, we usually want it to
8257archive WAL files as fast as possible.
8258
8259If the archive command fail, number of WAL in your F<pg_xlog> directory will grow until
8260exhausting all the disk space and force PostgreSQL to stop immediately.
8261
8262Example 1: Check that the number of ready WAL files is 10 or less on host "pluto"
8263
8264 check_postgres_archive_ready --host=pluto --critical=10
8265
8266For MRTG output, reports the number of ready WAL files on line 1.
8267
8268=head2 B<autovac_freeze>
8269
8270(C<symlink: check_postgres_autovac_freeze>) Checks how close each database is to the Postgres B<autovacuum_freeze_max_age> setting. This
8271action will only work for databases version 8.2 or higher. The I<--warning> and
8272I<--critical> options should be expressed as percentages. The 'age' of the transactions
8273in each database is compared to the autovacuum_freeze_max_age setting (200 million by default)
8274to generate a rounded percentage. The default values are B<90%> for the warning and B<95%> for
8275the critical. Databases can be filtered by use of the I<--include> and I<--exclude> options.
8276See the L</"BASIC FILTERING"> section for more details.
8277
8278Example 1: Give a warning when any databases on port 5432 are above 97%
8279
8280 check_postgres_autovac_freeze --port=5432 --warning="97%"
8281
8282For MRTG output, the highest overall percentage is reported on the first line, and the highest age is
8283reported on the second line. All databases which have the percentage from the first line are reported
8284on the fourth line, separated by a pipe symbol.
8285
8286=head2 B<backends>
8287
8288(C<symlink: check_postgres_backends>) Checks the current number of connections for one or more databases, and optionally
8289compares it to the maximum allowed, which is determined by the
8290Postgres configuration variable B<max_connections>. The I<--warning> and
8291I<--critical> options can take one of three forms. First, a simple number can be
8292given, which represents the number of connections at which the alert will be
8293given. This choice does not use the B<max_connections> setting. Second, the
8294percentage of available connections can be given. Third, a negative number can
8295be given which represents the number of connections left until B<max_connections>
8296is reached. The default values for I<--warning> and I<--critical> are '90%' and '95%'.
8297You can also filter the databases by use of the I<--include> and I<--exclude> options.
8298See the L</"BASIC FILTERING"> section for more details.
8299
8300To view only non-idle processes, you can use the I<--noidle> argument. Note that the
8301user you are connecting as must be a superuser for this to work properly.
8302
8303Example 1: Give a warning when the number of connections on host quirm reaches 120, and a critical if it reaches 150.
8304
8305 check_postgres_backends --host=quirm --warning=120 --critical=150
8306
8307Example 2: Give a critical when we reach 75% of our max_connections setting on hosts lancre or lancre2.
8308
8309 check_postgres_backends --warning='75%' --critical='75%' --host=lancre,lancre2
8310
8311Example 3: Give a warning when there are only 10 more connection slots left on host plasmid, and a critical
8312when we have only 5 left.
8313
8314 check_postgres_backends --warning=-10 --critical=-5 --host=plasmid
8315
8316Example 4: Check all databases except those with "test" in their name, but allow ones that are named "pg_greatest". Connect as port 5432 on the first two hosts, and as port 5433 on the third one. We want to always throw a critical when we reach 30 or more connections.
8317
8318 check_postgres_backends --dbhost=hong,kong --dbhost=fooey --dbport=5432 --dbport=5433 --warning=30 --critical=30 --exclude="~test" --include="pg_greatest,~prod"
8319
8320For MRTG output, the number of connections is reported on the first line, and the fourth line gives the name of the database,
8321plus the current maximum_connections. If more than one database has been queried, the one with the highest number of
8322connections is output.
8323
8324=head2 B<bloat>
8325
8326(C<symlink: check_postgres_bloat>) Checks the amount of bloat in tables and indexes. (Bloat is generally the amount
8327of dead unused space taken up in a table or index. This space is usually reclaimed
8328by use of the VACUUM command.) This action requires that stats collection be
8329enabled on the target databases, and requires that ANALYZE is run frequently.
8330The I<--include> and I<--exclude> options can be used to filter out which tables
8331to look at. See the L</"BASIC FILTERING"> section for more details.
8332
8333The I<--warning> and I<--critical> options can be specified as sizes, percents, or both.
8334Valid size units are bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes,
8335petabytes, and zettabytes. You can abbreviate all of those with the first letter. Items
8336without units are assumed to be 'bytes'. The default values are '1 GB' and '5 GB'. The value
8337represents the number of "wasted bytes", or the difference between what is actually
8338used by the table and index, and what we compute that it should be.
8339
8340Note that this action has two hard-coded values to avoid false alarms on
8341smaller relations. Tables must have at least 10 pages, and indexes at least 15,
8342before they can be considered by this test. If you really want to adjust these
8343values, you can look for the variables I<$MINPAGES> and I<$MINIPAGES> at the top of the
8344C<check_bloat> subroutine. These values are ignored if either I<--exclude> or
8345I<--include> is used.
8346
8347Only the top 10 most bloated relations are shown. You can change this number by
8348using the I<--perflimit> option to set your own limit.
8349
8350The schema named 'information_schema' is excluded from this test, as the only tables
8351it contains are small and do not change.
8352
8353Please note that the values computed by this action are not precise, and
8354should be used as a guideline only. Great effort was made to estimate the
8355correct size of a table, but in the end it is only an estimate. The correct
8356index size is even more of a guess than the correct table size, but both
8357should give a rough idea of how bloated things are.
8358
8359Example 1: Warn if any table on port 5432 is over 100 MB bloated, and critical if over 200 MB
8360
8361 check_postgres_bloat --port=5432 --warning='100 M' --critical='200 M'
8362
8363Example 2: Give a critical if table 'orders' on host 'sami' has more than 10 megs of bloat
8364
8365 check_postgres_bloat --host=sami --include=orders --critical='10 MB'
8366
8367Example 3: Give a critical if table 'q4' on database 'sales' is over 50% bloated
8368
8369 check_postgres_bloat --db=sales --include=q4 --critical='50%'
8370
8371Example 4: Give a critical any table is over 20% bloated I<and> has over 150
8372MB of bloat:
8373
8374 check_postgres_bloat --port=5432 --critical='20% and 150 M'
8375
8376Example 5: Give a critical any table is over 40% bloated I<or> has over 500 MB
8377of bloat:
8378
8379 check_postgres_bloat --port=5432 --warning='500 M or 40%'
8380
8381For MRTG output, the first line gives the highest number of wasted bytes for the tables, and the
8382second line gives the highest number of wasted bytes for the indexes. The fourth line gives the database
8383name, table name, and index name information. If you want to output the bloat ratio instead (how many
8384times larger the relation is compared to how large it should be), just pass in C<--mrtg=ratio>.
8385
8386=head2 B<checkpoint>
8387
8388(C<symlink: check_postgres_checkpoint>) Determines how long since the last checkpoint has
8389been run. This must run on the same server as the database that is being checked (e.g. the -h
8390flag will not work). This check is meant to run on a "warm standby" server that is actively
8391processing shipped WAL files, and is meant to check that your warm standby is truly 'warm'.
8392The data directory must be set, either by the environment variable C<PGDATA>, or passing
8393the C<--datadir> argument. It returns the number of seconds since the last checkpoint
8394was run, as determined by parsing the call to C<pg_controldata>. Because of this, the
8395pg_controldata executable must be available in the current path. Alternatively,
8396you can specify C<PGBINDIR> as the directory that it lives in.
8397It is also possible to use the special options I<--assume-prod> or
8398I<--assume-standby-mode>, if the mode found is not the one expected, a CRITICAL is emitted.
8399
8400At least one warning or critical argument must be set.
8401
8402This action requires the Date::Parse module.
8403
8404For MRTG or simple output, returns the number of seconds.
8405
8406=head2 B<cluster_id>
8407
8408(C<symlink: check_postgres_cluster-id>) Checks that the Database System Identifier
8409provided by pg_controldata is the same as last time you checked. This must run on the same
8410server as the database that is being checked (e.g. the -h flag will not work).
8411Either the I<--warning> or the I<--critical> option should be given, but not both. The value
8412of each one is the cluster identifier, an integer value. You can run with the special C<--critical=0> option
8413to find out an existing cluster identifier.
8414
8415Example 1: Find the initial identifier
8416
8417 check_postgres_cluster_id --critical=0 --datadir=/var//lib/postgresql/9.0/main
8418
8419Example 2: Make sure the cluster is the same and warn if not, using the result from above.
8420
8421 check_postgres_cluster_id --critical=5633695740047915135
8422
8423For MRTG output, returns a 1 or 0 indicating success of failure of the identifier to match. A
8424identifier must be provided as the C<--mrtg> argument. The fourth line always gives the
8425current identifier.
8426
8427=head2 B<commitratio>
8428
8429(C<symlink: check_postgres_commitratio>) Checks the commit ratio of all databases and complains when they are too low.
8430There is no need to run this command more than once per database cluster.
8431Databases can be filtered with
8432the I<--include> and I<--exclude> options. See the L</"BASIC FILTERING"> section
8433for more details.
8434They can also be filtered by the owner of the database with the
8435I<--includeuser> and I<--excludeuser> options.
8436See the L</"USER NAME FILTERING"> section for more details.
8437
8438The warning and critical options should be specified as percentages. There are not
8439defaults for this action: the warning and critical must be specified. The warning value
8440cannot be greater than the critical value. The output returns all databases sorted by
8441commitratio, smallest first.
8442
8443Example: Warn if any database on host flagg is less than 90% in commitratio, and critical if less then 80%.
8444
8445 check_postgres_database_commitratio --host=flagg --warning='90%' --critical='80%'
8446
8447For MRTG output, returns the percentage of the database with the smallest commitratio on the first line,
8448and the name of the database on the fourth line.
8449
8450=head2 B<connection>
8451
8452(C<symlink: check_postgres_connection>) Simply connects, issues a 'SELECT version()', and leaves.
8453Takes no I<--warning> or I<--critical> options.
8454
8455For MRTG output, simply outputs a 1 (good connection) or a 0 (bad connection) on the first line.
8456
8457=head2 B<custom_query>
8458
8459(C<symlink: check_postgres_custom_query>) Runs a custom query of your choosing, and parses the results.
8460The query itself is passed in through the C<query> argument, and should be kept as simple as possible.
8461If at all possible, wrap it in a view or a function to keep things easier to manage. The query should
8462return one or two columns. It is required that one of the columns be named "result" and is the item
8463that will be checked against your warning and critical values. The second column is for the performance
8464data and any name can be used: this will be the 'value' inside the performance data section.
8465
8466At least one warning or critical argument must be specified. What these are set to depends on the type of
8467query you are running. There are four types of custom_queries that can be run, specified by the C<valtype>
8468argument. If none is specified, this action defaults to 'integer'. The four types are:
8469
8470B<integer>:
8471Does a simple integer comparison. The first column should be a simple integer, and the warning and
8472critical values should be the same.
8473
8474B<string>:
8475The warning and critical are strings, and are triggered only if the value in the first column matches
8476it exactly. This is case-sensitive.
8477
8478B<time>:
8479The warning and the critical are times, and can have units of seconds, minutes, hours, or days.
8480Each may be written singular or abbreviated to just the first letter. If no units are given,
8481seconds are assumed. The first column should be an integer representing the number of seconds
8482to check.
8483
8484B<size>:
8485The warning and the critical are sizes, and can have units of bytes, kilobytes, megabytes, gigabytes,
8486terabytes, or exabytes. Each may be abbreviated to the first letter. If no units are given,
8487bytes are assumed. The first column should be an integer representing the number of bytes to check.
8488
8489Normally, an alert is triggered if the values returned are B<greater than> or equal to the critical or warning
8490value. However, an option of I<--reverse> will trigger the alert if the returned value is
8491B<lower than> or equal to the critical or warning value.
8492
8493Example 1: Warn if any relation over 100 pages is named "rad", put the number of pages
8494inside the performance data section.
8495
8496 check_postgres_custom_query --valtype=string -w "rad" --query=
8497 "SELECT relname AS result, relpages AS pages FROM pg_class WHERE relpages > 100"
8498
8499Example 2: Give a critical if the "foobar" function returns a number over 5MB:
8500
8501 check_postgres_custom_query --critical='5MB'--valtype=size --query="SELECT foobar() AS result"
8502
8503Example 2: Warn if the function "snazzo" returns less than 42:
8504
8505 check_postgres_custom_query --critical=42 --query="SELECT snazzo() AS result" --reverse
8506
8507If you come up with a useful custom_query, consider sending in a patch to this program
8508to make it into a standard action that other people can use.
8509
8510This action does not support MRTG or simple output yet.
8511
8512=head2 B<database_size>
8513
8514(C<symlink: check_postgres_database_size>) Checks the size of all databases and complains when they are too big.
8515There is no need to run this command more than once per database cluster.
8516Databases can be filtered with
8517the I<--include> and I<--exclude> options. See the L</"BASIC FILTERING"> section
8518for more details.
8519They can also be filtered by the owner of the database with the
8520I<--includeuser> and I<--excludeuser> options.
8521See the L</"USER NAME FILTERING"> section for more details.
8522
8523The warning and critical options can be specified as bytes, kilobytes, megabytes,
8524gigabytes, terabytes, or exabytes. Each may be abbreviated to the first letter as well.
8525If no unit is given, the units are assumed to be bytes. There are not defaults for this
8526action: the warning and critical must be specified. The warning value cannot be greater
8527than the critical value. The output returns all databases sorted by size largest first,
8528showing both raw bytes and a "pretty" version of the size.
8529
8530Example 1: Warn if any database on host flagg is over 1 TB in size, and critical if over 1.1 TB.
8531
8532 check_postgres_database_size --host=flagg --warning='1 TB' --critical='1.1 t'
8533
8534Example 2: Give a critical if the database template1 on port 5432 is over 10 MB.
8535
8536 check_postgres_database_size --port=5432 --include=template1 --warning='10MB' --critical='10MB'
8537
8538Example 3: Give a warning if any database on host 'tardis' owned by the user 'tom' is over 5 GB
8539
8540 check_postgres_database_size --host=tardis --includeuser=tom --warning='5 GB' --critical='10 GB'
8541
8542For MRTG output, returns the size in bytes of the largest database on the first line,
8543and the name of the database on the fourth line.
8544
8545=head2 B<dbstats>
8546
8547(C<symlink: check_postgres_dbstats>) Reports information from the pg_stat_database view,
8548and outputs it in a Cacti-friendly manner. No other output is supported, as the output
8549is informational and does not lend itself to alerts, such as used with Nagios. If no
8550options are given, all databases are returned, one per line. You can include a specific
8551database by use of the C<--include> option, or you can use the C<--dbname> option.
8552
8553Eleven items are returned on each line, in the format name:value, separated by a single
8554space. The items are:
8555
8556=over 4
8557
8558=item backends
8559
8560The number of currently running backends for this database.
8561
8562=item commits
8563
8564The total number of commits for this database since it was created or reset.
8565
8566=item rollbacks
8567
8568The total number of rollbacks for this database since it was created or reset.
8569
8570=item read
8571
8572The total number of disk blocks read.
8573
8574=item hit
8575
8576The total number of buffer hits.
8577
8578=item ret
8579
8580The total number of rows returned.
8581
8582=item fetch
8583
8584The total number of rows fetched.
8585
8586=item ins
8587
8588The total number of rows inserted.
8589
8590=item upd
8591
8592The total number of rows updated.
8593
8594=item del
8595
8596The total number of rows deleted.
8597
8598=item dbname
8599
8600The name of the database.
8601
8602=back
8603
8604Note that ret, fetch, ins, upd, and del items will always be 0 if Postgres is version 8.2 or lower, as those stats were
8605not available in those versions.
8606
8607If the dbname argument is given, seven additional items are returned:
8608
8609=over 4
8610
8611=item idxscan
8612
8613Total number of user index scans.
8614
8615=item idxtupread
8616
8617Total number of user index entries returned.
8618
8619=item idxtupfetch
8620
8621Total number of rows fetched by simple user index scans.
8622
8623=item idxblksread
8624
8625Total number of disk blocks read for all user indexes.
8626
8627=item idxblkshit
8628
8629Total number of buffer hits for all user indexes.
8630
8631=item seqscan
8632
8633Total number of sequential scans against all user tables.
8634
8635=item seqtupread
8636
8637Total number of tuples returned from all user tables.
8638
8639=back
8640
8641Example 1: Grab the stats for a database named "products" on host "willow":
8642
8643 check_postgres_dbstats --dbhost willow --dbname products
8644
8645The output returned will be like this (all on one line, not wrapped):
8646
8647 backends:82 commits:58374408 rollbacks:1651 read:268435543 hit:2920381758 idxscan:310931294 idxtupread:2777040927
8648 idxtupfetch:1840241349 idxblksread:62860110 idxblkshit:1107812216 seqscan:5085305 seqtupread:5370500520
8649 ret:0 fetch:0 ins:0 upd:0 del:0 dbname:willow
8650
8651=head2 B<disabled_triggers>
8652
8653(C<symlink: check_postgres_disabled_triggers>) Checks on the number of disabled triggers inside the database.
8654The I<--warning> and I<--critical> options are the number of such triggers found, and both
8655default to "1", as in normal usage having disabled triggers is a dangerous event. If the
8656database being checked is 8.3 or higher, the check is for the number of triggers that are
8657in a 'disabled' status (as opposed to being 'always' or 'replica'). The output will show
8658the name of the table and the name of the trigger for each disabled trigger.
8659
8660Example 1: Make sure that there are no disabled triggers
8661
8662 check_postgres_disabled_triggers
8663
8664For MRTG output, returns the number of disabled triggers on the first line.
8665
8666=head2 B<disk_space>
8667
8668(C<symlink: check_postgres_disk_space>) Checks on the available physical disk space used by Postgres. This action requires
8669that you have the executable "/bin/df" available to report on disk sizes, and it
8670also needs to be run as a superuser, so it can examine the B<data_directory>
8671setting inside of Postgres. The I<--warning> and I<--critical> options are
8672given in either sizes or percentages or both. If using sizes, the standard unit types
8673are allowed: bytes, kilobytes, gigabytes, megabytes, gigabytes, terabytes, or
8674exabytes. Each may be abbreviated to the first letter only; no units at all
8675indicates 'bytes'. The default values are '90%' and '95%'.
8676
8677This command checks the following things to determine all of the different
8678physical disks being used by Postgres.
8679
8680B<data_directory> - The disk that the main data directory is on.
8681
8682B<log directory> - The disk that the log files are on.
8683
8684B<WAL file directory> - The disk that the write-ahead logs are on (e.g. symlinked pg_xlog)
8685
8686B<tablespaces> - Each tablespace that is on a separate disk.
8687
8688The output shows the total size used and available on each disk, as well as
8689the percentage, ordered by highest to lowest percentage used. Each item above
8690maps to a file system: these can be included or excluded. See the
8691L</"BASIC FILTERING"> section for more details.
8692
8693Example 1: Make sure that no file system is over 90% for the database on port 5432.
8694
8695 check_postgres_disk_space --port=5432 --warning='90%' --critical='90%'
8696
8697Example 2: Check that all file systems starting with /dev/sda are smaller than 10 GB and 11 GB (warning and critical)
8698
8699 check_postgres_disk_space --port=5432 --warning='10 GB' --critical='11 GB' --include="~^/dev/sda"
8700
8701Example 4: Make sure that no file system is both over 50% I<and> has over 15 GB
8702
8703 check_postgres_disk_space --critical='50% and 15 GB'
8704
8705Example 5: Issue a warning if any file system is either over 70% full I<or> has
8706more than 1T
8707
8708 check_postgres_disk_space --warning='1T or 75'
8709
8710For MRTG output, returns the size in bytes of the file system on the first line,
8711and the name of the file system on the fourth line.
8712
8713=head2 B<fsm_pages>
8714
8715(C<symlink: check_postgres_fsm_pages>) Checks how close a cluster is to the Postgres B<max_fsm_pages> setting.
8716This action will only work for databases of 8.2 or higher, and it requires the contrib
8717module B<pg_freespacemap> be installed. The I<--warning> and I<--critical> options should be expressed
8718as percentages. The number of used pages in the free-space-map is determined by looking in the
8719pg_freespacemap_relations view, and running a formula based on the formula used for
8720outputting free-space-map pageslots in the vacuum verbose command. The default values are B<85%> for the
8721warning and B<95%> for the critical.
8722
8723Example 1: Give a warning when our cluster has used up 76% of the free-space pageslots, with pg_freespacemap installed in database robert
8724
8725 check_postgres_fsm_pages --dbname=robert --warning="76%"
8726
8727While you need to pass in the name of the database where pg_freespacemap is installed, you only need to run this check once per cluster. Also, checking this information does require obtaining special locks on the free-space-map, so it is recommend you do not run this check with short intervals.
8728
8729For MRTG output, returns the percent of free-space-map on the first line, and the number of pages currently used on
8730the second line.
8731
8732=head2 B<fsm_relations>
8733
8734(C<symlink: check_postgres_fsm_relations>) Checks how close a cluster is to the Postgres B<max_fsm_relations> setting.
8735This action will only work for databases of 8.2 or higher, and it requires the contrib module B<pg_freespacemap> be
8736installed. The I<--warning> and I<--critical> options should be expressed as percentages. The number of used relations
8737in the free-space-map is determined by looking in the pg_freespacemap_relations view. The default values are B<85%> for
8738the warning and B<95%> for the critical.
8739
8740Example 1: Give a warning when our cluster has used up 80% of the free-space relations, with pg_freespacemap installed in database dylan
8741
8742 check_postgres_fsm_relations --dbname=dylan --warning="75%"
8743
8744While you need to pass in the name of the database where pg_freespacemap is installed, you only need to run this check
8745once per cluster. Also,
8746checking this information does require obtaining special locks on the free-space-map, so it is recommend you do not
8747run this check with short intervals.
8748
8749For MRTG output, returns the percent of free-space-map on the first line, the number of relations currently used on
8750the second line.
8751
8752=head2 B<hitratio>
8753
8754(C<symlink: check_postgres_hitratio>) Checks the hit ratio of all databases and complains when they are too low.
8755There is no need to run this command more than once per database cluster.
8756Databases can be filtered with
8757the I<--include> and I<--exclude> options. See the L</"BASIC FILTERING"> section
8758for more details.
8759They can also be filtered by the owner of the database with the
8760I<--includeuser> and I<--excludeuser> options.
8761See the L</"USER NAME FILTERING"> section for more details.
8762
8763The warning and critical options should be specified as percentages. There are not
8764defaults for this action: the warning and critical must be specified. The warning value
8765cannot be greater than the critical value. The output returns all databases sorted by
8766hitratio, smallest first.
8767
8768Example: Warn if any database on host flagg is less than 90% in hitratio, and critical if less then 80%.
8769
8770 check_postgres_hitratio --host=flagg --warning='90%' --critical='80%'
8771
8772For MRTG output, returns the percentage of the database with the smallest hitratio on the first line,
8773and the name of the database on the fourth line.
8774
8775=head2 B<hot_standby_delay>
8776
8777(C<symlink: check_hot_standby_delay>) Checks the streaming replication lag by computing the delta
8778between the xlog position of a master server and the one of the slaves connected to it. The slave_
8779server must be in hot_standby (e.g. read only) mode, therefore the minimum version to use this_
8780action is Postgres 9.0. The I<--warning> and I<--critical> options are the delta between xlog
8781location. These values should match the volume of transactions needed to have the streaming
8782replication disconnect from the master because of too much lag.
8783
8784You must provide information on how to reach the second database by a connection
8785parameter ending in the number 2, such as "--dbport2=5543". If if it not given,
8786the action fails.
8787
8788=head2 B<index_size>
8789
8790=head2 B<table_size>
8791
8792=head2 B<relation_size>
8793
8794(symlinks: C<check_postgres_index_size>, C<check_postgres_table_size>, and C<check_postgres_relation_size>)
8795The actions B<table_size> and B<index_size> are simply variations of the
8796B<relation_size> action, which checks for a relation that has grown too big.
8797Relations (in other words, tables and indexes) can be filtered with the
8798I<--include> and I<--exclude> options. See the L</"BASIC FILTERING"> section
8799for more details. Relations can also be filtered by the user that owns them,
8800by using the I<--includeuser> and I<--excludeuser> options.
8801See the L</"USER NAME FILTERING"> section for more details.
8802
8803The values for the I<--warning> and I<--critical> options are file sizes, and
8804may have units of bytes, kilobytes, megabytes, gigabytes, terabytes, or exabytes.
8805Each can be abbreviated to the first letter. If no units are given, bytes are
8806assumed. There are no default values: both the warning and the critical option
8807must be given. The return text shows the size of the largest relation found.
8808
8809If the I<--showperf> option is enabled, I<all> of the relations with their sizes
8810will be given. To prevent this, it is recommended that you set the
8811I<--perflimit> option, which will cause the query to do a
8812C<ORDER BY size DESC LIMIT (perflimit)>.
8813
8814Example 1: Give a critical if any table is larger than 600MB on host burrick.
8815
8816 check_postgres_table_size --critical='600 MB' --warning='600 MB' --host=burrick
8817
8818Example 2: Warn if the table products is over 4 GB in size, and give a critical at 4.5 GB.
8819
8820 check_postgres_table_size --host=burrick --warning='4 GB' --critical='4.5 GB' --include=products
8821
8822Example 3: Warn if any index not owned by postgres goes over 500 MB.
8823
8824 check_postgres_index_size --port=5432 --excludeuser=postgres -w 500MB -c 600MB
8825
8826For MRTG output, returns the size in bytes of the largest relation, and the name of the database
8827and relation as the fourth line.
8828
8829=head2 B<last_analyze>
8830
8831=head2 B<last_vacuum>
8832
8833=head2 B<last_autoanalyze>
8834
8835=head2 B<last_autovacuum>
8836
8837(symlinks: C<check_postgres_last_analyze>, C<check_postgres_last_vacuum>,
8838C<check_postgres_last_autoanalyze>, and C<check_postgres_last_autovacuum>)
8839Checks how long it has been since vacuum (or analyze) was last run on each
8840table in one or more databases. Use of these actions requires that the target
8841database is version 8.3 or greater, or that the version is 8.2 and the
8842configuration variable B<stats_row_level> has been enabled. Tables can be filtered with the
8843I<--include> and I<--exclude> options. See the L</"BASIC FILTERING"> section
8844for more details.
8845Tables can also be filtered by their owner by use of the
8846I<--includeuser> and I<--excludeuser> options.
8847See the L</"USER NAME FILTERING"> section for more details.
8848
8849The units for I<--warning> and I<--critical> are specified as times.
8850Valid units are seconds, minutes, hours, and days; all can be abbreviated
8851to the first letter. If no units are given, 'seconds' are assumed. The
8852default values are '1 day' and '2 days'. Please note that there are cases
8853in which this field does not get automatically populated. If certain tables
8854are giving you problems, make sure that they have dead rows to vacuum,
8855or just exclude them from the test.
8856
8857The schema named 'information_schema' is excluded from this test, as the only tables
8858it contains are small and do not change.
8859
8860Note that the non-'auto' versions will also check on the auto versions as well. In other words,
8861using last_vacuum will report on the last vacuum, whether it was a normal vacuum, or
8862one run by the autovacuum daemon.
8863
8864Example 1: Warn if any table has not been vacuumed in 3 days, and give a
8865critical at a week, for host wormwood
8866
8867 check_postgres_last_vacuum --host=wormwood --warning='3d' --critical='7d'
8868
8869Example 2: Same as above, but skip tables belonging to the users 'eve' or 'mallory'
8870
8871 check_postgres_last_vacuum --host=wormwood --warning='3d' --critical='7d' --excludeusers=eve,mallory
8872
8873For MRTG output, returns (on the first line) the LEAST amount of time in seconds since a table was
8874last vacuumed or analyzed. The fourth line returns the name of the database and name of the table.
8875
8876=head2 B<listener>
8877
8878(C<symlink: check_postgres_listener>) Confirm that someone is listening for one or more
8879specific strings (using the LISTEN/NOTIFY system), by looking at the pg_listener table.
8880Only one of warning or critical is needed. The format is a simple string representing the
8881LISTEN target, or a tilde character followed by a string for a regular expression check.
8882Note that this check will not work on versions of Postgres 9.0 or higher.
8883
8884Example 1: Give a warning if nobody is listening for the string bucardo_mcp_ping on ports 5555 and 5556
8885
8886 check_postgres_listener --port=5555,5556 --warning=bucardo_mcp_ping
8887
8888Example 2: Give a critical if there are no active LISTEN requests matching 'grimm' on database oskar
8889
8890 check_postgres_listener --db oskar --critical=~grimm
8891
8892For MRTG output, returns a 1 or a 0 on the first, indicating success or failure. The name of the notice must
8893be provided via the I<--mrtg> option.
8894
8895=head2 B<locks>
8896
8897(C<symlink: check_postgres_locks>) Check the total number of locks on one or more databases. There is no
8898need to run this more than once per database cluster. Databases can be filtered
8899with the I<--include> and I<--exclude> options. See the L</"BASIC FILTERING"> section
8900for more details.
8901
8902The I<--warning> and I<--critical> options can be specified as simple numbers,
8903which represent the total number of locks, or they can be broken down by type of lock.
8904Valid lock names are C<'total'>, C<'waiting'>, or the name of a lock type used by Postgres.
8905These names are case-insensitive and do not need the "lock" part on the end,
8906so B<exclusive> will match 'ExclusiveLock'. The format is name=number, with different
8907items separated by colons or semicolons (or any other symbol).
8908
8909Example 1: Warn if the number of locks is 100 or more, and critical if 200 or more, on host garrett
8910
8911 check_postgres_locks --host=garrett --warning=100 --critical=200
8912
8913Example 2: On the host artemus, warn if 200 or more locks exist, and give a critical if over 250 total locks exist, or if over 20 exclusive locks exist, or if over 5 connections are waiting for a lock.
8914
8915 check_postgres_locks --host=artemus --warning=200 --critical="total=250:waiting=5:exclusive=20"
8916
8917For MRTG output, returns the number of locks on the first line, and the name of the database on the fourth line.
8918
8919=head2 B<logfile>
8920
8921(C<symlink: check_postgres_logfile>) Ensures that the logfile is in the expected location and is being logged to.
8922This action issues a command that throws an error on each database it is
8923checking, and ensures that the message shows up in the logs. It scans the
8924various log_* settings inside of Postgres to figure out where the logs should be.
8925If you are using syslog, it does a rough (but not foolproof) scan of
8926F</etc/syslog.conf>. Alternatively, you can provide the name of the logfile
8927with the I<--logfile> option. This is especially useful if the logs have a
8928custom rotation scheme driven be an external program. The B<--logfile> option
8929supports the following escape characters: C<%Y %m %d %H>, which represent
8930the current year, month, date, and hour respectively. An error is always
8931reported as critical unless the warning option has been passed in as a non-zero
8932value. Other than that specific usage, the C<--warning> and C<--critical>
8933options should I<not> be used.
8934
8935Example 1: On port 5432, ensure the logfile is being written to the file /home/greg/pg8.2.log
8936
8937 check_postgres_logfile --port=5432 --logfile=/home/greg/pg8.2.log
8938
8939Example 2: Same as above, but raise a warning, not a critical
8940
8941 check_postgres_logfile --port=5432 --logfile=/home/greg/pg8.2.log -w 1
8942
8943For MRTG output, returns a 1 or 0 on the first line, indicating success or failure. In case of a
8944failure, the fourth line will provide more detail on the failure encountered.
8945
8946=head2 B<new_version_bc>
8947
8948(C<symlink: check_postgres_new_version_bc>) Checks if a newer version of the Bucardo
8949program is available. The current version is obtained by running C<bucardo_ctl --version>.
8950If a major upgrade is available, a warning is returned. If a revision upgrade is
8951available, a critical is returned. (Bucardo is a master to slave, and master to master
8952replication system for Postgres: see http://bucardo.org for more information).
8953See also the information on the C<--get_method> option.
8954
8955=head2 B<new_version_box>
8956
8957(C<symlink: check_postgres_new_version_box>) Checks if a newer version of the boxinfo
8958program is available. The current version is obtained by running C<boxinfo.pl --version>.
8959If a major upgrade is available, a warning is returned. If a revision upgrade is
8960available, a critical is returned. (boxinfo is a program for grabbing important
8961information from a server and putting it into a HTML format: see
8962http://bucardo.org/wiki/boxinfo for more information). See also the information on
8963the C<--get_method> option.
8964
8965=head2 B<new_version_cp>
8966
8967(C<symlink: check_postgres_new_version_cp>) Checks if a newer version of this program
8968(check_postgres.pl) is available, by grabbing the version from a small text file
8969on the main page of the home page for the project. Returns a warning if the returned
8970version does not match the one you are running. Recommended interval to check is
8971once a day. See also the information on the C<--get_method> option.
8972
8973=head2 B<new_version_pg>
8974
8975(C<symlink: check_postgres_new_version_pg>) Checks if a newer revision of Postgres
8976exists for each database connected to. Note that this only checks for revision, e.g.
8977going from 8.3.6 to 8.3.7. Revisions are always 100% binary compatible and involve no
8978dump and restore to upgrade. Revisions are made to address bugs, so upgrading as soon
8979as possible is always recommended. Returns a warning if you do not have the latest revision.
8980It is recommended this check is run at least once a day. See also the information on
8981the C<--get_method> option.
8982
8983
8984=head2 B<new_version_tnm>
8985
8986(C<symlink: check_postgres_new_version_tnm>) Checks if a newer version of the
8987tail_n_mail program is available. The current version is obtained by running
8988C<tail_n_mail --version>. If a major upgrade is available, a warning is returned. If a
8989revision upgrade is available, a critical is returned. (tail_n_mail is a log monitoring
8990tool that can send mail when interesting events appear in your Postgres logs.
8991See: http://bucardo.org/wiki/Tail_n_mail for more information).
8992See also the information on the C<--get_method> option.
8993
8994=head2 B<pgb_pool_cl_active>
8995
8996=head2 B<pgb_pool_cl_waiting>
8997
8998=head2 B<pgb_pool_sv_active>
8999
9000=head2 B<pgb_pool_sv_idle>
9001
9002=head2 B<pgb_pool_sv_used>
9003
9004=head2 B<pgb_pool_sv_tested>
9005
9006=head2 B<pgb_pool_sv_login>
9007
9008=head2 B<pgb_pool_maxwait>
9009
9010(symlinks: C<check_postgres_pgb_pool_cl_active>, C<check_postgres_pgb_pool_cl_waiting>,
9011C<check_postgres_pgb_pool_sv_active>, C<check_postgres_pgb_pool_sv_idle>,
9012C<check_postgres_pgb_pool_sv_used>, C<check_postgres_pgb_pool_sv_tested>,
9013C<check_postgres_pgb_pool_sv_login>, and C<check_postgres_pgb_pool_maxwait>)
9014
9015Examines pgbouncer's pool statistics. Each pool has a set of "client"
9016connections, referring to connections from external clients, and "server"
9017connections, referring to connections to PostgreSQL itself. The related
9018check_postgres actions are prefixed by "cl_" and "sv_", respectively. Active
9019client connections are those connections currently linked with an active server
9020connection. Client connections may also be "waiting", meaning they have not yet
9021been allocated a server connection. Server connections are "active" (linked to
9022a client), "idle" (standing by for a client connection to link with), "used"
9023(just unlinked from a client, and not yet returned to the idle pool), "tested"
9024(currently being tested) and "login" (in the process of logging in). The
9025maxwait value shows how long in seconds the oldest waiting client connection
9026has been waiting.
9027
9028=head2 B<pgbouncer_backends>
9029
9030(C<symlink: check_postgres_pgbouncer_backends>) Checks the current number of
9031connections for one or more databases through pgbouncer, and optionally
9032compares it to the maximum allowed, which is determined by the pgbouncer
9033configuration variable B<max_client_conn>. The I<--warning> and I<--critical>
9034options can take one of three forms. First, a simple number can be given,
9035which represents the number of connections at which the alert will be given.
9036This choice does not use the B<max_connections> setting. Second, the
9037percentage of available connections can be given. Third, a negative number can
9038be given which represents the number of connections left until
9039B<max_connections> is reached. The default values for I<--warning> and
9040I<--critical> are '90%' and '95%'. You can also filter the databases by use
9041of the I<--include> and I<--exclude> options. See the L</"BASIC FILTERING">
9042section for more details.
9043
9044To view only non-idle processes, you can use the I<--noidle> argument. Note
9045that the user you are connecting as must be a superuser for this to work
9046properly.
9047
9048Example 1: Give a warning when the number of connections on host quirm reaches
9049120, and a critical if it reaches 150.
9050
9051 check_postgres_pgbouncer_backends --host=quirm --warning=120 --critical=150 -p 6432 -u pgbouncer
9052
9053Example 2: Give a critical when we reach 75% of our max_connections setting on
9054hosts lancre or lancre2.
9055
9056 check_postgres_pgbouncer_backends --warning='75%' --critical='75%' --host=lancre,lancre2 -p 6432 -u pgbouncer
9057
9058Example 3: Give a warning when there are only 10 more connection slots left on
9059host plasmid, and a critical when we have only 5 left.
9060
9061 check_postgres_pgbouncer_backends --warning=-10 --critical=-5 --host=plasmid -p 6432 -u pgbouncer
9062
9063For MRTG output, the number of connections is reported on the first line, and
9064the fourth line gives the name of the database, plus the current
9065max_client_conn. If more than one database has been queried, the one with the
9066highest number of connections is output.
9067
9068=head2 B<pgbouncer_checksum>
9069
9070(C<symlink: check_postgres_pgbouncer_checksum>) Checks that all the
9071pgBouncer settings are the same as last time you checked.
9072This is done by generating a checksum of a sorted list of setting names and
9073their values. Note that you shouldn't specify the database name, it will
9074automatically default to pgbouncer. Either the I<--warning> or the I<--critical> option
9075should be given, but not both. The value of each one is the checksum, a
907632-character hexadecimal value. You can run with the special C<--critical=0> option
9077to find out an existing checksum.
9078
9079This action requires the Digest::MD5 module.
9080
9081Example 1: Find the initial checksum for pgbouncer configuration on port 6432 using the default user (usually postgres)
9082
9083 check_postgres_pgbouncer_checksum --port=6432 --critical=0
9084
9085Example 2: Make sure no settings have changed and warn if so, using the checksum from above.
9086
9087 check_postgres_pgbouncer_checksum --port=6432 --warning=cd2f3b5e129dc2b4f5c0f6d8d2e64231
9088
9089For MRTG output, returns a 1 or 0 indicating success of failure of the checksum to match. A
9090checksum must be provided as the C<--mrtg> argument. The fourth line always gives the
9091current checksum.
9092
9093=head2 B<pgagent_jobs>
9094
9095(C<symlink: check_postgres_pgagent_jobs>) Checks that all the pgAgent jobs
9096that have executed in the preceding interval of time have succeeded. This is
9097done by checking for any steps that have a non-zero result.
9098
9099Either C<--warning> or C<--critical>, or both, may be specified as times, and
9100jobs will be checked for failures withing the specified periods of time before
9101the current time. Valid units are seconds, minutes, hours, and days; all can
9102be abbreviated to the first letter. If no units are given, 'seconds' are
9103assumed.
9104
9105Example 1: Give a critical when any jobs executed in the last day have failed.
9106
9107 check_postgres_pgagent_jobs --critical=1d
9108
9109Example 2: Give a warning when any jobs executed in the last week have failed.
9110
9111 check_postgres_pgagent_jobs --warning=7d
9112
9113Example 3: Give a critical for jobs that have failed in the last 2 hours and a
9114warning for jobs that have failed in the last 4 hours:
9115
9116 check_postgres_pgagent_jobs --critical=2h --warning=4h
9117
9118=head2 B<prepared_txns>
9119
9120(C<symlink: check_postgres_prepared_txns>) Check on the age of any existing prepared transactions.
9121Note that most people will NOT use prepared transactions, as they are part of two-part commit
9122and complicated to maintain. They should also not be confused with prepared STATEMENTS, which is
9123what most people think of when they hear prepare. The default value for a warning is 1 second, to
9124detect any use of prepared transactions, which is probably a mistake on most systems. Warning and
9125critical are the number of seconds a prepared transaction has been open before an alert is given.
9126
9127Example 1: Give a warning on detecting any prepared transactions:
9128
9129 check_postgres_prepared_txns -w 0
9130
9131Example 2: Give a critical if any prepared transaction has been open longer than 10 seconds, but allow
9132up to 360 seconds for the database 'shrike':
9133
9134 check_postgres_prepared_txns --critical=10 --exclude=shrike
9135 check_postgres_prepared_txns --critical=360 --include=shrike
9136
9137For MRTG output, returns the number of seconds the oldest transaction has been open as the first line,
9138and which database is came from as the final line.
9139
9140=head2 B<query_runtime>
9141
9142(C<symlink: check_postgres_query_runtime>) Checks how long a specific query takes to run, by executing a "EXPLAIN ANALYZE"
9143against it. The I<--warning> and I<--critical> options are the maximum amount of
9144time the query should take. Valid units are seconds, minutes, and hours; any can be
9145abbreviated to the first letter. If no units are given, 'seconds' are assumed.
9146Both the warning and the critical option must be given. The name of the view or
9147function to be run must be passed in to the I<--queryname> option. It must consist
9148of a single word (or schema.word), with optional parens at the end.
9149
9150Example 1: Give a critical if the function named "speedtest" fails to run in 10 seconds or less.
9151
9152 check_postgres_query_runtime --queryname='speedtest()' --critical=10 --warning=10
9153
9154For MRTG output, reports the time in seconds for the query to complete on the first line. The fourth
9155line lists the database.
9156
9157=head2 B<query_time>
9158
9159(C<symlink: check_postgres_query_time>) Checks the length of running queries on one or more databases.
9160There is no need to run this more than once on the same database cluster. Note that
9161this already excludes queries that are "idle in transaction". Databases can be filtered
9162by using the I<--include> and I<--exclude> options. See the L</"BASIC FILTERING">
9163section for more details. You can also filter on the user running the
9164query with the I<--includeuser> and I<--excludeuser> options.
9165See the L</"USER NAME FILTERING"> section for more details.
9166
9167The values for the I<--warning> and I<--critical> options are amounts of
9168time, and default to '2 minutes' and '5 minutes' respectively. Valid units
9169are 'seconds', 'minutes', 'hours', or 'days'. Each may be written singular or
9170abbreviated to just the first letter. If no units are given, the unit is
9171assumed to be seconds.
9172
9173This action requires Postgres 8.1 or better.
9174
9175Example 1: Give a warning if any query has been running longer than 3 minutes, and a critical if longer than 5 minutes.
9176
9177 check_postgres_query_time --port=5432 --warning='3 minutes' --critical='5 minutes'
9178
9179Example 2: Using default values (2 and 5 minutes), check all databases except those starting with 'template'.
9180
9181 check_postgres_query_time --port=5432 --exclude=~^template
9182
9183Example 3: Warn if user 'don' has a query running over 20 seconds
9184
9185 check_postgres_query_time --port=5432 --includeuser=don --warning=20s
9186
9187For MRTG output, returns the length in seconds of the longest running query on the first line. The fourth
9188line gives the name of the database.
9189
9190=head2 B<replicate_row>
9191
9192(C<symlink: check_postgres_replicate_row>) Checks that master-slave replication is working to one or more slaves.
9193The slaves are specified the same as the normal databases, except with
9194the number 2 at the end of them, so "--port2" instead of "--port", etc.
9195The values or the I<--warning> and I<--critical> options are units of time, and
9196at least one must be provided (no defaults). Valid units are 'seconds', 'minutes', 'hours',
9197or 'days'. Each may be written singular or abbreviated to just the first letter.
9198If no units are given, the units are assumed to be seconds.
9199
9200This check updates a single row on the master, and then measures how long it
9201takes to be applied to the slaves. To do this, you need to pick a table that
9202is being replicated, then find a row that can be changed, and is not going
9203to be changed by any other process. A specific column of this row will be changed
9204from one value to another. All of this is fed to the C<repinfo> option, and should
9205contain the following options, separated by commas: table name, primary key, key id,
9206column, first value, second value.
9207
9208Example 1: Slony is replicating a table named 'orders' from host 'alpha' to
9209host 'beta', in the database 'sales'. The primary key of the table is named
9210id, and we are going to test the row with an id of 3 (which is historical and
9211never changed). There is a column named 'salesrep' that we are going to toggle
9212from a value of 'slon' to 'nols' to check on the replication. We want to throw
9213a warning if the replication does not happen within 10 seconds.
9214
9215 check_postgres_replicate_row --host=alpha --dbname=sales --host2=beta
9216 --dbname2=sales --warning=10 --repinfo=orders,id,3,salesrep,slon,nols
9217
9218Example 2: Bucardo is replicating a table named 'receipt' from host 'green'
9219to hosts 'red', 'blue', and 'yellow'. The database for both sides is 'public'.
9220The slave databases are running on port 5455. The primary key is named 'receipt_id',
9221the row we want to use has a value of 9, and the column we want to change for the
9222test is called 'zone'. We'll toggle between 'north' and 'south' for the value of
9223this column, and throw a critical if the change is not on all three slaves within 5 seconds.
9224
9225 check_postgres_replicate_row --host=green --port2=5455 --host2=red,blue,yellow
9226 --critical=5 --repinfo=receipt,receipt_id,9,zone,north,south
9227
9228For MRTG output, returns on the first line the time in seconds the replication takes to finish.
9229The maximum time is set to 4 minutes 30 seconds: if no replication has taken place in that long
9230a time, an error is thrown.
9231
9232=head2 B<same_schema>
9233
9234(C<symlink: check_postgres_same_schema>) Verifies that two or more databases are identical as far as their
9235schema (but not the data within). This is particularly handy for making sure your slaves have not
9236been modified or corrupted in any way when using master to slave replication. Unlike most other
9237actions, this has no warning or critical criteria - the databases are either in sync, or are not.
9238If they are different, a detailed list of the differences is presented.
9239
9240You may want to exclude or filter out certain differences. The way to do this is to add strings
9241to the C<--filter> option. To exclude a type of object, use "noname", where 'name' is the type of
9242object, for example, "noschema". To exclude objects of a certain type by a regular expression against
9243their name, use "noname=regex". See the examples below for a better understanding.
9244
9245The types of objects that can be filtered include:
9246
9247=over 4
9248
9249=item user
9250
9251=item schema
9252
9253=item table
9254
9255=item view
9256
9257=item index
9258
9259=item sequence
9260
9261=item constraint
9262
9263=item trigger
9264
9265=item function
9266
9267=back
9268
9269The filter option "noposition" prevents verification of the position of
9270columns within a table.
9271
9272The filter option "nofuncbody" prevents comparison of the bodies of all
9273functions.
9274
9275The filter option "noperm" prevents comparison of object permissions.
9276
9277To provide the second database, just append the differences to the first one
9278by a call to the appropriate connection argument. For example, to compare
9279databases on hosts alpha and bravo, use "--dbhost=alpha,bravo". Also see the
9280examples below.
9281
9282If only a single host is given, it is assumed we are doing a "time-based" report.
9283The first time this is run a snapshot of all the items in the database is
9284saved to a local file. When you run it again, that snapshot is read in and
9285becomes "database #2" and is compared to the current database.
9286
9287To replace the old stored file with the new version, use the --replace argument.
9288
9289To enable snapshots at various points in time, you can use the "--suffix"
9290argument to make the filenames unique to each run. See the examples below.
9291
9292Example 1: Verify that two databases on hosts star and line are the same:
9293
9294 check_postgres_same_schema --dbhost=star,line
9295
9296Example 2: Same as before, but exclude any triggers with "slony" in their name
9297
9298 check_postgres_same_schema --dbhost=star,line --filter="notrigger=slony"
9299
9300Example 3: Same as before, but also exclude all indexes
9301
9302 check_postgres_same_schema --dbhost=star,line --filter="notrigger=slony noindexes"
9303
9304Example 4: Check differences for the database "battlestar" on different ports
9305
9306 check_postgres_same_schema --dbname=battlestar --dbport=5432,5544
9307
9308Example 5: Create a daily and weekly snapshot file
9309
9310 check_postgres_same_schema --dbname=cylon --suffix=daily
9311 check_postgres_same_schema --dbname=cylon --suffix=weekly
9312
9313Example 6: Run a historical comparison, then replace the file
9314
9315 check_postgres_same_schema --dbname=cylon --suffix=daily --replace
9316
9317=head2 B<sequence>
9318
9319(C<symlink: check_postgres_sequence>) Checks how much room is left on all sequences in the database.
9320This is measured as the percent of total possible values that have been used for each sequence.
9321The I<--warning> and I<--critical> options should be expressed as percentages. The default values
9322are B<85%> for the warning and B<95%> for the critical. You may use --include and --exclude to
9323control which sequences are to be checked. Note that this check does account for unusual B<minvalue>
9324and B<increment by> values, but does not care if the sequence is set to cycle or not.
9325
9326The output for Nagios gives the name of the sequence, the percentage used, and the number of 'calls'
9327left, indicating how many more times nextval can be called on that sequence before running into
9328the maximum value.
9329
9330The output for MRTG returns the highest percentage across all sequences on the first line, and
9331the name of each sequence with that percentage on the fourth line, separated by a "|" (pipe)
9332if there are more than one sequence at that percentage.
9333
9334Example 1: Give a warning if any sequences are approaching 95% full.
9335
9336 check_postgres_sequence --dbport=5432 --warning=95%
9337
9338Example 2: Check that the sequence named "orders_id_seq" is not more than half full.
9339
9340 check_postgres_sequence --dbport=5432 --critical=50% --include=orders_id_seq
9341
9342=head2 B<settings_checksum>
9343
9344(C<symlink: check_postgres_settings_checksum>) Checks that all the Postgres settings are the same as last time you checked.
9345This is done by generating a checksum of a sorted list of setting names and
9346their values. Note that different users in the same database may have different
9347checksums, due to ALTER USER usage, and due to the fact that superusers see more
9348settings than ordinary users. Either the I<--warning> or the I<--critical> option
9349should be given, but not both. The value of each one is the checksum, a
935032-character hexadecimal value. You can run with the special C<--critical=0> option
9351to find out an existing checksum.
9352
9353This action requires the Digest::MD5 module.
9354
9355Example 1: Find the initial checksum for the database on port 5555 using the default user (usually postgres)
9356
9357 check_postgres_settings_checksum --port=5555 --critical=0
9358
9359Example 2: Make sure no settings have changed and warn if so, using the checksum from above.
9360
9361 check_postgres_settings_checksum --port=5555 --warning=cd2f3b5e129dc2b4f5c0f6d8d2e64231
9362
9363For MRTG output, returns a 1 or 0 indicating success of failure of the checksum to match. A
9364checksum must be provided as the C<--mrtg> argument. The fourth line always gives the
9365current checksum.
9366
9367=head2 B<slony_status>
9368
9369(C<symlink: check_postgres_slony_status>) Checks in the status of a Slony cluster by looking
9370at the results of Slony's sl_status view. This is returned as the number of seconds of "lag time".
9371The I<--warning> and I<--critical> options should be expressed as times. The default values
9372are B<60 seconds> for the warning and B<300 seconds> for the critical.
9373
9374The optional argument I<--schema> indicated the schema that Slony is installed under. If it is
9375not given, the schema will be determined automatically each time this check is run.
9376
9377Example 1: Give a warning if any Slony is lagged by more than 20 seconds
9378
9379 check_postgres_slony_status --warning 20
9380
9381Example 2: Give a critical if Slony, installed under the schema "_slony", is over 10 minutes lagged
9382
9383 check_postgres_slony_status --schema=_slony --critical=600
9384
9385=head2 B<timesync>
9386
9387(C<symlink: check_postgres_timesync>) Compares the local system time with the time reported by one or more databases.
9388The I<--warning> and I<--critical> options represent the number of seconds between
9389the two systems before an alert is given. If neither is specified, the default values
9390are used, which are '2' and '5'. The warning value cannot be greater than the critical
9391value. Due to the non-exact nature of this test, values of '0' or '1' are not recommended.
9392
9393The string returned shows the time difference as well as the time on each side written out.
9394
9395Example 1: Check that databases on hosts ankh, morpork, and klatch are no more than 3 seconds off from the local time:
9396
9397 check_postgres_timesync --host=ankh,morpork,klatch --critical=3
9398
9399For MRTG output, returns one the first line the number of seconds difference between the local
9400time and the database time. The fourth line returns the name of the database.
9401
9402=head2 B<txn_idle>
9403
9404(C<symlink: check_postgres_txn_idle>) Checks the number and duration of "idle
9405in transaction" queries on one or more databases. There is no need to run this
9406more than once on the same database cluster. Databases can be filtered by
9407using the I<--include> and I<--exclude> options. See the L</"BASIC FILTERING">
9408section below for more details.
9409
9410The I<--warning> and I<--critical> options are given as units of time, signed
9411integers, or integers for units of time, and both must be provided (there are
9412no defaults). Valid units are 'seconds', 'minutes', 'hours', or 'days'. Each
9413may be written singular or abbreviated to just the first letter. If no units
9414are given and the numbers are unsigned, the units are assumed to be seconds.
9415
9416This action requires Postgres 8.3 or better.
9417
9418Example 1: Give a warning if any connection has been idle in transaction for more than 15 seconds:
9419
9420 check_postgres_txn_idle --port=5432 --warning='15 seconds'
9421
9422Example 2: Give a warning if there are 50 or more transactions
9423
9424 check_postgres_txn_idle --port=5432 --warning='+50'
9425
9426Example 3: Give a critical if 5 or more connections have been idle in
9427transaction for more than 10 seconds:
9428
9429 check_postgres_txn_idle --port=5432 --critical='5 for 10 seconds'
9430
9431For MRTG output, returns the time in seconds the longest idle transaction has been running. The fourth
9432line returns the name of the database and other information about the longest transaction.
9433
9434=head2 B<txn_time>
9435
9436(C<symlink: check_postgres_txn_time>) Checks the length of open transactions on one or more databases.
9437There is no need to run this command more than once per database cluster.
9438Databases can be filtered by use of the
9439I<--include> and I<--exclude> options. See the L</"BASIC FILTERING"> section
9440for more details. The owner of the transaction can also be filtered, by use of
9441the I<--includeuser> and I<--excludeuser> options.
9442See the L</"USER NAME FILTERING"> section for more details.
9443
9444The values or the I<--warning> and I<--critical> options are units of time, and
9445must be provided (no default). Valid units are 'seconds', 'minutes', 'hours',
9446or 'days'. Each may be written singular or abbreviated to just the first letter.
9447If no units are given, the units are assumed to be seconds.
9448
9449This action requires Postgres 8.3 or better.
9450
9451Example 1: Give a critical if any transaction has been open for more than 10 minutes:
9452
9453 check_postgres_txn_time --port=5432 --critical='10 minutes'
9454
9455Example 1: Warn if user 'warehouse' has a transaction open over 30 seconds
9456
9457 check_postgres_txn_time --port-5432 --warning=30s --includeuser=warehouse
9458
9459For MRTG output, returns the maximum time in seconds a transaction has been open on the
9460first line. The fourth line gives the name of the database.
9461
9462=head2 B<txn_wraparound>
9463
9464(C<symlink: check_postgres_txn_wraparound>) Checks how close to transaction wraparound one or more databases are getting.
9465The I<--warning> and I<--critical> options indicate the number of transactions done, and must be a positive integer.
9466If either option is not given, the default values of 1.3 and 1.4 billion are used. There is no need to run this command
9467more than once per database cluster. For a more detailed discussion of what this number represents and what to do about
9468it, please visit the page
9469L<http://www.postgresql.org/docs/current/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND>
9470
9471The warning and critical values can have underscores in the number for legibility, as Perl does.
9472
9473Example 1: Check the default values for the localhost database
9474
9475 check_postgres_txn_wraparound --host=localhost
9476
9477Example 2: Check port 6000 and give a critical when 1.7 billion transactions are hit:
9478
9479 check_postgres_txn_wraparound --port=6000 --critical=1_700_000_000
9480
9481For MRTG output, returns the highest number of transactions for all databases on line one,
9482while line 4 indicates which database it is.
9483
9484=head2 B<version>
9485
9486(C<symlink: check_postgres_version>) Checks that the required version of Postgres is running. The
9487I<--warning> and I<--critical> options (only one is required) must be of
9488the format B<X.Y> or B<X.Y.Z> where B<X> is the major version number,
9489B<Y> is the minor version number, and B<Z> is the revision.
9490
9491Example 1: Give a warning if the database on port 5678 is not version 8.4.10:
9492
9493 check_postgres_version --port=5678 -w=8.4.10
9494
9495Example 2: Give a warning if any databases on hosts valley,grain, or sunshine is not 8.3:
9496
9497 check_postgres_version -H valley,grain,sunshine --critical=8.3
9498
9499For MRTG output, reports a 1 or a 0 indicating success or failure on the first line. The
9500fourth line indicates the current version. The version must be provided via the C<--mrtg> option.
9501
9502=head2 B<wal_files>
9503
9504(C<symlink: check_postgres_wal_files>) Checks how many WAL files exist in the F<pg_xlog> directory, which is found
9505off of your B<data_directory>, sometimes as a symlink to another physical disk for
9506performance reasons. This action must be run as a superuser, in order to access the
9507contents of the F<pg_xlog> directory. The minimum version to use this action is
9508Postgres 8.1. The I<--warning> and I<--critical> options are simply the number of
9509files in the F<pg_xlog> directory. What number to set this to will vary, but a general
9510guideline is to put a number slightly higher than what is normally there, to catch
9511problems early.
9512
9513Normally, WAL files are closed and then re-used, but a long-running open
9514transaction, or a faulty B<archive_command> script, may cause Postgres to
9515create too many files. Ultimately, this will cause the disk they are on to run
9516out of space, at which point Postgres will shut down.
9517
9518Example 1: Check that the number of WAL files is 20 or less on host "pluto"
9519
9520 check_postgres_wal_files --host=pluto --critical=20
9521
9522For MRTG output, reports the number of WAL files on line 1.
9523
9524=head2 B<rebuild_symlinks>
9525
9526=head2 B<rebuild_symlinks_force>
9527
9528This action requires no other arguments, and does not connect to any databases,
9529but simply creates symlinks in the current directory for each action, in the form
9530B<check_postgres_E<lt>action_nameE<gt>>.
9531If the file already exists, it will not be overwritten. If the action is rebuild_symlinks_force,
9532then symlinks will be overwritten. The option --symlinks is a shorter way of saying
9533--action=rebuild_symlinks
9534
9535=head1 BASIC FILTERING
9536
9537The options I<--include> and I<--exclude> can be combined to limit which
9538things are checked, depending on the action. The name of the database can
9539be filtered when using the following actions:
9540backends, database_size, locks, query_time, txn_idle, and txn_time.
9541The name of a relation can be filtered when using the following actions:
9542bloat, index_size, table_size, relation_size, last_vacuum, last_autovacuum,
9543last_analyze, and last_autoanalyze.
9544The name of a setting can be filtered when using the settings_checksum action.
9545The name of a file system can be filtered when using the disk_space action.
9546
9547If only an include option is given, then ONLY those entries that match will be
9548checked. However, if given both exclude and include, the exclusion is done first,
9549and the inclusion after, to reinstate things that may have been excluded. Both
9550I<--include> and I<--exclude> can be given multiple times,
9551and/or as comma-separated lists. A leading tilde will match the following word
9552as a regular expression.
9553
9554To match a schema, end the search term with a single period. Leading tildes can
9555be used for schemas as well.
9556
9557Be careful when using filtering: an inclusion rule on the backends, for example,
9558may report no problems not only because the matching database had no backends,
9559but because you misspelled the name of the database!
9560
9561Examples:
9562
9563Only checks items named pg_class:
9564
9565 --include=pg_class
9566
9567Only checks items containing the letters 'pg_':
9568
9569 --include=~pg_
9570
9571Only check items beginning with 'pg_':
9572
9573 --include=~^pg_
9574
9575Exclude the item named 'test':
9576
9577 --exclude=test
9578
9579Exclude all items containing the letters 'test:
9580
9581 --exclude=~test
9582
9583Exclude all items in the schema 'pg_catalog':
9584
9585 --exclude='pg_catalog.'
9586
9587Exclude all items containing the letters 'ace', but allow the item 'faceoff':
9588
9589 --exclude=~ace --include=faceoff
9590
9591Exclude all items which start with the letters 'pg_', which contain the letters 'slon',
9592or which are named 'sql_settings' or 'green'. Specifically check items with the letters 'prod' in their names, and always check the item named 'pg_relname':
9593
9594 --exclude=~^pg_,~slon,sql_settings --exclude=green --include=~prod,pg_relname
9595
9596=head1 USER NAME FILTERING
9597
9598The options I<--includeuser> and I<--excludeuser> can be used on some actions
9599to only examine database objects owned by (or not owned by) one or more users.
9600An I<--includeuser> option always trumps an I<--excludeuser> option. You can
9601give each option more than once for multiple users, or you can give a
9602comma-separated list. The actions that currently use these options are:
9603
9604=over 4
9605
9606=item database_size
9607
9608=item last_analyze
9609
9610=item last_autoanalyze
9611
9612=item last_vacuum
9613
9614=item last_autovacuum
9615
9616=item query_time
9617
9618=item relation_size
9619
9620=item txn_time
9621
9622=back
9623
9624Examples:
9625
9626Only check items owned by the user named greg:
9627
9628 --includeuser=greg
9629
9630Only check items owned by either watson or crick:
9631
9632 --includeuser=watson,crick
9633
9634Only check items owned by crick,franklin, watson, or wilkins:
9635
9636 --includeuser=watson --includeuser=franklin --includeuser=crick,wilkins
9637
9638Check all items except for those belonging to the user scott:
9639
9640 --excludeuser=scott
9641
9642=head1 TEST MODE
9643
9644To help in setting things up, this program can be run in a "test mode" by
9645specifying the I<--test> option. This will perform some basic tests to
9646make sure that the databases can be contacted, and that certain per-action
9647prerequisites are met, such as whether the user is a superuser, if the version
9648of Postgres is new enough, and if stats_row_level is enabled.
9649
9650=head1 FILES
9651
9652In addition to command-line configurations, you can put any options inside of a file. The file
9653F<.check_postgresrc> in the current directory will be used if found. If not found, then the file
9654F<~/.check_postgresrc> will be used. Finally, the file /etc/check_postgresrc will be used if available.
9655The format of the file is option = value, one per line. Any line starting with a '#' will be skipped.
9656Any values loaded from a check_postgresrc file will be overwritten by command-line options. All
9657check_postgresrc files can be ignored by supplying a C<--no-checkpostgresrc> argument.
9658
9659=head1 ENVIRONMENT VARIABLES
9660
9661The environment variable I<$ENV{HOME}> is used to look for a F<.check_postgresrc> file.
9662The environment variable I<$ENV{PGBINDIR}> is used to look for PostgreSQL binaries.
9663
9664=head1 TIPS AND TRICKS
9665
9666Since this program uses the B<psql> program, make sure it is accessible to the
9667user running the script. If run as a cronjob, this often means modifying the
9668B<PATH> environment variable.
9669
9670If you are using Nagios in embedded Perl mode, use the C<--action> argument
9671instead of symlinks, so that the plugin only gets compiled one time.
9672
9673=head1 DEPENDENCIES
9674
9675Access to a working version of psql, and the following very standard Perl modules:
9676
9677=over 4
9678
9679=item B<Cwd>
9680
9681=item B<Getopt::Long>
9682
9683=item B<File::Basename>
9684
9685=item B<File::Temp>
9686
9687=item B<Time::HiRes> (if C<$opt{showtime}> is set to true, which is the default)
9688
9689=back
9690
9691The L</settings_checksum> action requires the B<Digest::MD5> module.
9692
9693The L</checkpoint> action requires the B<Date::Parse> module.
9694
9695Some actions require access to external programs. If psql is not explicitly
9696specified, the command B<C<which>> is used to find it. The program B<C</bin/df>>
9697is needed by the L</disk_space> action.
9698
9699=head1 DEVELOPMENT
9700
9701Development happens using the git system. You can clone the latest version by doing:
9702
9703 git clone git://bucardo.org/check_postgres.git
9704
9705=head1 MAILING LIST
9706
9707Three mailing lists are available. For discussions about the program, bug reports,
9708feature requests, and commit notices, send email to check_postgres@bucardo.org
9709
9710https://mail.endcrypt.com/mailman/listinfo/check_postgres
9711
9712A low-volume list for announcement of new versions and important notices is the
9713'check_postgres-announce' list:
9714
9715https://mail.endcrypt.com/mailman/listinfo/check_postgres-announce
9716
9717Source code changes (via git-commit) are sent to the
9718'check_postgres-commit' list:
9719
9720https://mail.endcrypt.com/mailman/listinfo/check_postgres-commit
9721
9722=head1 HISTORY
9723
9724Items not specifically attributed are by GSM (Greg Sabino Mullane).
9725
9726=over 4
9727
9728=item B<Version 2.20.0>
9729
9730 Add check for pgagent jobs (David E. Wheeler)
9731
9732 Force STDOUT to use utf8 for proper output
9733 (Greg Sabino Mullane; reported by Emmanuel Lesouef)
9734
9735 Allow for spaces in item lists when doing same_schema.
9736
9737 Allow txn_idle to work again for < 8.3 servers by switching to query_time.
9738
9739 Cache sequence information to speed up same_schema runs.
9740
9741 Adjust docs to show colon as a better separator inside args for locks
9742 (Charles Sprickman)
9743
9744=item B<Version 2.19.0> January 17, 2012
9745
9746 Add the --assume-prod option (Cédric Villemain)
9747
9748 Add the cluster_id check (Cédric Villemain)
9749
9750 Improve settings_checksum and checkpoint tests (Cédric Villemain)
9751
9752 Do no do an inner join to pg_user when checking database size
9753 (Greg Sabino Mullane; reported by Emmanuel Lesouef)
9754
9755 Use the full path when getting sequence information for same_schema.
9756 (Greg Sabino Mullane; reported by Cindy Wise)
9757
9758 Fix the formula for calculating xlog positions (Euler Taveira de Oliveira)
9759
9760 Better ordering of output for bloat check - make indexes as important
9761 as tables (Greg Sabino Mullane; reported by Jens Wilke)
9762
9763 Show the dbservice if it was used at top of same_schema output
9764 (Mike Blackwell)
9765
9766 Better installation paths (Greg Sabino Mullane, per bug 53)
9767
9768=item B<Version 2.18.0> October 2, 2011
9769
9770 Redo the same_schema action. Use new --filter argument for all filtering.
9771 Allow comparisons between any number of databases.
9772 Remove the dbname2, dbport2, etc. arguments.
9773 Allow comparison of the same db over time.
9774
9775 Swap db1 and db2 if the slave is 1 for the hot standby check (David E. Wheeler)
9776
9777 Allow multiple --schema arguments for the slony_status action (GSM and Jehan-Guillaume de Rorthais)
9778
9779 Fix ORDER BY in the last vacuum/analyze action (Nicolas Thauvin)
9780
9781 Fix check_hot_standby_delay perfdata output (Nicolas Thauvin)
9782
9783 Look in the correct place for the .ready files with the archive_ready action (Nicolas Thauvin)
9784
9785 New action: commitratio (Guillaume Lelarge)
9786
9787 New action: hitratio (Guillaume Lelarge)
9788
9789 Make sure --action overrides the symlink naming trick.
9790
9791 Set defaults for archive_ready and wal_files (Thomas Guettler, GSM)
9792
9793 Better output for wal_files and archive_ready (GSM)
9794
9795 Fix warning when client_port set to empty string (bug #79)
9796
9797 Account for "empty row" in -x output (i.e. source of functions).
9798
9799 Fix some incorrectly named data fields (Andy Lester)
9800
9801 Expand the number of pgbouncer actions (Ruslan Kabalin)
9802
9803 Give detailed information and refactor txn_idle, txn_time, and query_time
9804 (Per request from bug #61)
9805
9806 Set maxalign to 8 in the bloat check if box identified as '64-bit'
9807 (Michel Sijmons, bug #66)
9808
9809 Support non-standard version strings in the bloat check.
9810 (Michel Sijmons and Gurjeet Singh, bug #66)
9811
9812 Do not show excluded databases in some output (Ruslan Kabalin)
9813
9814 Allow "and", "or" inside arguments (David E. Wheeler)
9815
9816 Add the "new_version_box" action.
9817
9818 Fix psql version regex (Peter Eisentraut, bug #69)
9819
9820 Add the --assume-standby-mode option (Ruslan Kabalin)
9821
9822 Note that txn_idle and query_time require 8.3 (Thomas Guettler)
9823
9824 Standardize and clean up all perfdata output (bug #52)
9825
9826 Exclude "idle in transaction" from the query_time check (bug #43)
9827
9828 Fix the perflimit for the bloat action (bug #50)
9829
9830 Clean up the custom_query action a bit.
9831
9832 Fix space in perfdata for hot_standby_delay action (Nicolas Thauvin)
9833
9834 Handle undef percents in check_fsm_relations (Andy Lester)
9835
9836 Fix typo in dbstats action (Stas Vitkovsky)
9837
9838 Fix MRTG for last vacuum and last_analyze actions.
9839
9840=item B<Version 2.17.0> no public release
9841
9842=item B<Version 2.16.0> January 20, 2011
9843
9844 Add new action 'hot_standby_delay' (Nicolas Thauvin)
9845 Add cache-busting for the version-grabbing utilities.
9846 Fix problem with going to next method for new_version_pg
9847 (Greg Sabino Mullane, reported by Hywel Mallett in bug #65)
9848 Allow /usr/local/etc as an alternative location for the
9849 check_postgresrc file (Hywel Mallett)
9850 Do not use tgisconstraint in same_schema if Postgres >= 9
9851 (Guillaume Lelarge)
9852
9853=item B<Version 2.15.4> January 3, 2011
9854
9855 Fix warning when using symlinks
9856 (Greg Sabino Mullane, reported by Peter Eisentraut in bug #63)
9857
9858=item B<Version 2.15.3> December 30, 2010
9859
9860 Show OK for no matching txn_idle entries.
9861
9862=item B<Version 2.15.2> December 28, 2010
9863
9864 Better formatting of sizes in the bloat action output.
9865
9866 Remove duplicate perfs in bloat action output.
9867
9868=item B<Version 2.15.1> December 27, 2010
9869
9870 Fix problem when examining items in pg_settings (Greg Sabino Mullane)
9871
9872 For connection test, return critical, not unknown, on FATAL errors
9873 (Greg Sabino Mullane, reported by Peter Eisentraut in bug #62)
9874
9875=item B<Version 2.15.0> November 8, 2010
9876
9877 Add --quiet argument to suppress output on OK Nagios results
9878 Add index comparison for same_schema (Norman Yamada and Greg Sabino Mullane)
9879 Use $ENV{PGSERVICE} instead of "service=" to prevent problems (Guillaume Lelarge)
9880 Add --man option to show the entire manual. (Andy Lester)
9881 Redo the internal run_command() sub to use -x and hashes instead of regexes.
9882 Fix error in custom logic (Andreas Mager)
9883 Add the "pgbouncer_checksum" action (Guillaume Lelarge)
9884 Fix regex to work on WIN32 for check_fsm_relations and check_fsm_pages (Luke Koops)
9885 Don't apply a LIMIT when using --exclude on the bloat action (Marti Raudsepp)
9886 Change the output of query_time to show pid,user,port, and address (Giles Westwood)
9887 Fix to show database properly when using slony_status (Guillaume Lelarge)
9888 Allow warning items for same_schema to be comma-separated (Guillaume Lelarge)
9889 Constraint definitions across Postgres versions match better in same_schema.
9890 Work against "EnterpriseDB" databases (Sivakumar Krishnamurthy and Greg Sabino Mullane)
9891 Separate perfdata with spaces (Jehan-Guillaume (ioguix) de Rorthais)
9892 Add new action "archive_ready" (Jehan-Guillaume (ioguix) de Rorthais)
9893
9894=item B<Version 2.14.3> (March 1, 2010)
9895
9896 Allow slony_status action to handle more than one slave.
9897 Use commas to separate function args in same_schema output (Robert Treat)
9898
9899=item B<Version 2.14.2> (February 18, 2010)
9900
9901 Change autovac_freeze default warn/critical back to 90%/95% (Robert Treat)
9902 Put all items one-per-line for relation size actions if --verbose=1
9903
9904=item B<Version 2.14.1> (February 17, 2010)
9905
9906 Don't use $^T in logfile check, as script may be long-running
9907 Change the error string for the logfile action for easier exclusion
9908 by programs like tail_n_mail
9909
9910=item B<Version 2.14.0> (February 11, 2010)
9911
9912 Added the 'slony_status' action.
9913 Changed the logfile sleep from 0.5 to 1, as 0.5 gets rounded to 0 on some boxes!
9914
9915=item B<Version 2.13.2> (February 4, 2010)
9916
9917 Allow timeout option to be used for logtime 'sleep' time.
9918
9919=item B<Version 2.13.2> (February 4, 2010)
9920
9921 Show offending database for query_time action.
9922 Apply perflimit to main output for sequence action.
9923 Add 'noowner' option to same_schema action.
9924 Raise sleep timeout for logfile check to 15 seconds.
9925
9926=item B<Version 2.13.1> (February 2, 2010)
9927
9928 Fix bug preventing column constraint differences from 2 > 1 for same_schema from being shown.
9929 Allow aliases 'dbname1', 'dbhost1', 'dbport1',etc.
9930 Added "nolanguage" as a filter for the same_schema option.
9931 Don't track "generic" table constraints (e.. $1, $2) using same_schema
9932
9933=item B<Version 2.13.0> (January 29, 2010)
9934
9935 Allow "nofunctions" as a filter for the same_schema option.
9936 Added "noperm" as a filter for the same_schema option.
9937 Ignore dropped columns when considered positions for same_schema (Guillaume Lelarge)
9938
9939=item B<Version 2.12.1> (December 3, 2009)
9940
9941 Change autovac_freeze default warn/critical from 90%/95% to 105%/120% (Marti Raudsepp)
9942
9943=item B<Version 2.12.0> (December 3, 2009)
9944
9945 Allow the temporary directory to be specified via the "tempdir" argument,
9946 for systems that need it (e.g. /tmp is not owned by root).
9947 Fix so old versions of Postgres (< 8.0) use the correct default database (Giles Westwood)
9948 For "same_schema" trigger mismatches, show the attached table.
9949 Add the new_version_bc check for Bucardo version checking.
9950 Add database name to perf output for last_vacuum|analyze (Guillaume Lelarge)
9951 Fix for bloat action against old versions of Postgres without the 'block_size' param.
9952
9953=item B<Version 2.11.1> (August 27, 2009)
9954
9955 Proper Nagios output for last_vacuum|analyze actions. (Cédric Villemain)
9956 Proper Nagios output for locks action. (Cédric Villemain)
9957 Proper Nagios output for txn_wraparound action. (Cédric Villemain)
9958 Fix for constraints with embedded newlines for same_schema.
9959 Allow --exclude for all items when using same_schema.
9960
9961=item B<Version 2.11.0> (August 23, 2009)
9962
9963 Add Nagios perf output to the wal_files check (Cédric Villemain)
9964 Add support for .check_postgresrc, per request from Albe Laurenz.
9965 Allow list of web fetch methods to be changed with the --get_method option.
9966 Add support for the --language argument, which overrides any ENV.
9967 Add the --no-check_postgresrc flag.
9968 Ensure check_postgresrc options are completely overridden by command-line options.
9969 Fix incorrect warning > critical logic in replicate_rows (Glyn Astill)
9970
9971=item B<Version 2.10.0> (August 3, 2009)
9972
9973 For same_schema, compare view definitions, and compare languages.
9974 Make script into a global executable via the Makefile.PL file.
9975 Better output when comparing two databases.
9976 Proper Nagios output syntax for autovac_freeze and backends checks (Cédric Villemain)
9977
9978=item B<Version 2.9.5> (July 24, 2009)
9979
9980 Don't use a LIMIT in check_bloat if --include is used. Per complaint from Jeff Frost.
9981
9982=item B<Version 2.9.4> (July 21, 2009)
9983
9984 More French translations (Guillaume Lelarge)
9985
9986=item B<Version 2.9.3> (July 14, 2009)
9987
9988 Quote dbname in perf output for the backends check. (Davide Abrigo)
9989 Add 'fetch' as an alternative method for new_version checks, as this
9990 comes by default with FreeBSD. (Hywel Mallett)
9991
9992=item B<Version 2.9.2> (July 12, 2009)
9993
9994 Allow dots and dashes in database name for the backends check (Davide Abrigo)
9995 Check and display the database for each match in the bloat check (Cédric Villemain)
9996 Handle 'too many connections' FATAL error in the backends check with a critical,
9997 rather than a generic error (Greg, idea by Jürgen Schulz-Brüssel)
9998 Do not allow perflimit to interfere with exclusion rules in the vacuum and
9999 analyze tests. (Greg, bug reported by Jeff Frost)
10000
10001=item B<Version 2.9.1> (June 12, 2009)
10002
10003 Fix for multiple databases with the check_bloat action (Mark Kirkwood)
10004 Fixes and improvements to the same_schema action (Jeff Boes)
10005 Write tests for same_schema, other minor test fixes (Jeff Boes)
10006
10007=item B<Version 2.9.0> (May 28, 2009)
10008
10009 Added the same_schema action (Greg)
10010
10011=item B<Version 2.8.1> (May 15, 2009)
10012
10013 Added timeout via statement_timeout in addition to perl alarm (Greg)
10014
10015=item B<Version 2.8.0> (May 4, 2009)
10016
10017 Added internationalization support (Greg)
10018 Added the 'disabled_triggers' check (Greg)
10019 Added the 'prepared_txns' check (Greg)
10020 Added the 'new_version_cp' and 'new_version_pg' checks (Greg)
10021 French translations (Guillaume Lelarge)
10022 Make the backends search return ok if no matches due to inclusion rules,
10023 per report by Guillaume Lelarge (Greg)
10024 Added comprehensive unit tests (Greg, Jeff Boes, Selena Deckelmann)
10025 Make fsm_pages and fsm_relations handle 8.4 servers smoothly. (Greg)
10026 Fix missing 'upd' field in show_dbstats (Andras Fabian)
10027 Allow ENV{PGCONTROLDATA} and ENV{PGBINDIR}. (Greg)
10028 Add various Perl module infrastructure (e.g. Makefile.PL) (Greg)
10029 Fix incorrect regex in txn_wraparound (Greg)
10030 For txn_wraparound: consistent ordering and fix duplicates in perf output (Andras Fabian)
10031 Add in missing exabyte regex check (Selena Deckelmann)
10032 Set stats to zero if we bail early due to USERWHERECLAUSE (Andras Fabian)
10033 Add additional items to dbstats output (Andras Fabian)
10034 Remove --schema option from the fsm_ checks. (Greg Mullane and Robert Treat)
10035 Handle case when ENV{PGUSER} is set. (Andy Lester)
10036 Many various fixes. (Jeff Boes)
10037 Fix --dbservice: check version and use ENV{PGSERVICE} for old versions (Cédric Villemain)
10038
10039=item B<Version 2.7.3> (February 10, 2009)
10040
10041 Make the sequence action check if sequence being used for a int4 column and
10042 react appropriately. (Michael Glaesemann)
10043
10044=item B<Version 2.7.2> (February 9, 2009)
10045
10046 Fix to prevent multiple groupings if db arguments given.
10047
10048=item B<Version 2.7.1> (February 6, 2009)
10049
10050 Allow the -p argument for port to work again.
10051
10052=item B<Version 2.7.0> (February 4, 2009)
10053
10054 Do not require a connection argument, but use defaults and ENV variables when
10055 possible: PGHOST, PGPORT, PGUSER, PGDATABASE.
10056
10057=item B<Version 2.6.1> (February 4, 2009)
10058
10059 Only require Date::Parse to be loaded if using the checkpoint action.
10060
10061=item B<Version 2.6.0> (January 26, 2009)
10062
10063 Add the 'checkpoint' action.
10064
10065=item B<Version 2.5.4> (January 7, 2009)
10066
10067 Better checking of $opt{dbservice} structure (Cédric Villemain)
10068 Fix time display in timesync action output (Selena Deckelmann)
10069 Fix documentation typos (Josh Tolley)
10070
10071=item B<Version 2.5.3> (December 17, 2008)
10072
10073 Minor fix to regex in verify_version (Lee Jensen)
10074
10075=item B<Version 2.5.2> (December 16, 2008)
10076
10077 Minor documentation tweak.
10078
10079=item B<Version 2.5.1> (December 11, 2008)
10080
10081 Add support for --noidle flag to prevent backends action from counting idle processes.
10082 Patch by Selena Deckelmann.
10083
10084 Fix small undefined warning when not using --dbservice.
10085
10086=item B<Version 2.5.0> (December 4, 2008)
10087
10088 Add support for the pg_Service.conf file with the --dbservice option.
10089
10090=item B<Version 2.4.3> (November 7, 2008)
10091
10092 Fix options for replicate_row action, per report from Jason Gordon.
10093
10094=item B<Version 2.4.2> (November 6, 2008)
10095
10096 Wrap File::Temp::cleanup() calls in eval, in case File::Temp is an older version.
10097 Patch by Chris Butler.
10098
10099=item B<Version 2.4.1> (November 5, 2008)
10100
10101 Cast numbers to numeric to support sequences ranges > bigint in check_sequence action.
10102 Thanks to Scott Marlowe for reporting this.
10103
10104=item B<Version 2.4.0> (October 26, 2008)
10105
10106 Add Cacti support with the dbstats action.
10107 Pretty up the time output for last vacuum and analyze actions.
10108 Show the percentage of backends on the check_backends action.
10109
10110=item B<Version 2.3.10> (October 23, 2008)
10111
10112 Fix minor warning in action check_bloat with multiple databases.
10113 Allow warning to be greater than critical when using the --reverse option.
10114 Support the --perflimit option for the check_sequence action.
10115
10116=item B<Version 2.3.9> (October 23, 2008)
10117
10118 Minor tweak to way we store the default port.
10119
10120=item B<Version 2.3.8> (October 21, 2008)
10121
10122 Allow the default port to be changed easily.
10123 Allow transform of simple output by MB, GB, etc.
10124
10125=item B<Version 2.3.7> (October 14, 2008)
10126
10127 Allow multiple databases in 'sequence' action. Reported by Christoph Zwerschke.
10128
10129=item B<Version 2.3.6> (October 13, 2008)
10130
10131 Add missing $schema to check_fsm_pages. (Robert Treat)
10132
10133=item B<Version 2.3.5> (October 9, 2008)
10134
10135 Change option 'checktype' to 'valtype' to prevent collisions with -c[ritical]
10136 Better handling of errors.
10137
10138=item B<Version 2.3.4> (October 9, 2008)
10139
10140 Do explicit cleanups of the temp directory, per problems reported by sb@nnx.com.
10141
10142=item B<Version 2.3.3> (October 8, 2008)
10143
10144 Account for cases where some rounding queries give -0 instead of 0.
10145 Thanks to Glyn Astill for helping to track this down.
10146
10147=item B<Version 2.3.2> (October 8, 2008)
10148
10149 Always quote identifiers in check_replicate_row action.
10150
10151=item B<Version 2.3.1> (October 7, 2008)
10152
10153 Give a better error if one of the databases cannot be reached.
10154
10155=item B<Version 2.3.0> (October 4, 2008)
10156
10157 Add the "sequence" action, thanks to Gavin M. Roy for the idea.
10158 Fix minor problem with autovac_freeze action when using MRTG output.
10159 Allow output argument to be case-insensitive.
10160 Documentation fixes.
10161
10162=item B<Version 2.2.4> (October 3, 2008)
10163
10164 Fix some minor typos
10165
10166=item B<Version 2.2.3> (October 1, 2008)
10167
10168 Expand range of allowed names for --repinfo argument (Glyn Astill)
10169 Documentation tweaks.
10170
10171=item B<Version 2.2.2> (September 30, 2008)
10172
10173 Fixes for minor output and scoping problems.
10174
10175=item B<Version 2.2.1> (September 28, 2008)
10176
10177 Add MRTG output to fsm_pages and fsm_relations.
10178 Force error messages to one-line for proper Nagios output.
10179 Check for invalid prereqs on failed command. From conversations with Euler Taveira de Oliveira.
10180 Tweak the fsm_pages formula a little.
10181
10182=item B<Version 2.2.0> (September 25, 2008)
10183
10184 Add fsm_pages and fsm_relations actions. (Robert Treat)
10185
10186=item B<Version 2.1.4> (September 22, 2008)
10187
10188 Fix for race condition in txn_time action.
10189 Add --debugoutput option.
10190
10191=item B<Version 2.1.3> (September 22, 2008)
10192
10193 Allow alternate arguments "dbhost" for "host" and "dbport" for "port".
10194 Output a zero as default value for second line of MRTG output.
10195
10196=item B<Version 2.1.2> (July 28, 2008)
10197
10198 Fix sorting error in the "disk_space" action for non-Nagios output.
10199 Allow --simple as a shortcut for --output=simple.
10200
10201=item B<Version 2.1.1> (July 22, 2008)
10202
10203 Don't check databases with datallowconn false for the "autovac_freeze" action.
10204
10205=item B<Version 2.1.0> (July 18, 2008)
10206
10207 Add the "autovac_freeze" action, thanks to Robert Treat for the idea and design.
10208 Put an ORDER BY on the "txn_wraparound" action.
10209
10210=item B<Version 2.0.1> (July 16, 2008)
10211
10212 Optimizations to speed up the "bloat" action quite a bit.
10213 Fix "version" action to not always output in mrtg mode.
10214
10215=item B<Version 2.0.0> (July 15, 2008)
10216
10217 Add support for MRTG and "simple" output options.
10218 Many small improvements to nearly all actions.
10219
10220=item B<Version 1.9.1> (June 24, 2008)
10221
10222 Fix an error in the bloat SQL in 1.9.0
10223 Allow percentage arguments to be over 99%
10224 Allow percentages in the bloat --warning and --critical (thanks to Robert Treat for the idea)
10225
10226=item B<Version 1.9.0> (June 22, 2008)
10227
10228 Don't include information_schema in certain checks. (Jeff Frost)
10229 Allow --include and --exclude to use schemas by using a trailing period.
10230
10231=item B<Version 1.8.5> (June 22, 2008)
10232
10233 Output schema name before table name where appropriate.
10234 Thanks to Jeff Frost.
10235
10236=item B<Version 1.8.4> (June 19, 2008)
10237
10238 Better detection of problems in --replicate_row.
10239
10240=item B<Version 1.8.3> (June 18, 2008)
10241
10242 Fix 'backends' action: there may be no rows in pg_stat_activity, so run a second
10243 query if needed to find the max_connections setting.
10244 Thanks to Jeff Frost for the bug report.
10245
10246=item B<Version 1.8.2> (June 10, 2008)
10247
10248 Changes to allow working under Nagios' embedded Perl mode. (Ioannis Tambouras)
10249
10250=item B<Version 1.8.1> (June 9, 2008)
10251
10252 Allow 'bloat' action to work on Postgres version 8.0.
10253 Allow for different commands to be run for each action depending on the server version.
10254 Give better warnings when running actions not available on older Postgres servers.
10255
10256=item B<Version 1.8.0> (June 3, 2008)
10257
10258 Add the --reverse option to the custom_query action.
10259
10260=item B<Version 1.7.1> (June 2, 2008)
10261
10262 Fix 'query_time' action: account for race condition in which zero rows appear in pg_stat_activity.
10263 Thanks to Dustin Black for the bug report.
10264
10265=item B<Version 1.7.0> (May 11, 2008)
10266
10267 Add --replicate_row action
10268
10269=item B<Version 1.6.1> (May 11, 2008)
10270
10271 Add --symlinks option as a shortcut to --action=rebuild_symlinks
10272
10273=item B<Version 1.6.0> (May 11, 2008)
10274
10275 Add the custom_query action.
10276
10277=item B<Version 1.5.2> (May 2, 2008)
10278
10279 Fix problem with too eager creation of custom pgpass file.
10280
10281=item B<Version 1.5.1> (April 17, 2008)
10282
10283 Add example Nagios configuration settings (Brian A. Seklecki)
10284
10285=item B<Version 1.5.0> (April 16, 2008)
10286
10287 Add the --includeuser and --excludeuser options. Documentation cleanup.
10288
10289=item B<Version 1.4.3> (April 16, 2008)
10290
10291 Add in the 'output' concept for future support of non-Nagios programs.
10292
10293=item B<Version 1.4.2> (April 8, 2008)
10294
10295 Fix bug preventing --dbpass argument from working (Robert Treat).
10296
10297=item B<Version 1.4.1> (April 4, 2008)
10298
10299 Minor documentation fixes.
10300
10301=item B<Version 1.4.0> (April 2, 2008)
10302
10303 Have 'wal_files' action use pg_ls_dir (idea by Robert Treat).
10304 For last_vacuum and last_analyze, respect autovacuum effects, add separate
10305 autovacuum checks (ideas by Robert Treat).
10306
10307=item B<Version 1.3.1> (April 2, 2008)
10308
10309 Have txn_idle use query_start, not xact_start.
10310
10311=item B<Version 1.3.0> (March 23, 2008)
10312
10313 Add in txn_idle and txn_time actions.
10314
10315=item B<Version 1.2.0> (February 21, 2008)
10316
10317 Add the 'wal_files' action, which counts the number of WAL files
10318 in your pg_xlog directory.
10319 Fix some typos in the docs.
10320 Explicitly allow -v as an argument.
10321 Allow for a null syslog_facility in the 'logfile' action.
10322
10323=item B<Version 1.1.2> (February 5, 2008)
10324
10325 Fix error preventing --action=rebuild_symlinks from working.
10326
10327=item B<Version 1.1.1> (February 3, 2008)
10328
10329 Switch vacuum and analyze date output to use 'DD', not 'D'. (Glyn Astill)
10330
10331=item B<Version 1.1.0> (December 16, 2008)
10332
10333 Fixes, enhancements, and performance tracking.
10334 Add performance data tracking via --showperf and --perflimit
10335 Lots of refactoring and cleanup of how actions handle arguments.
10336 Do basic checks to figure out syslog file for 'logfile' action.
10337 Allow for exact matching of beta versions with 'version' action.
10338 Redo the default arguments to only populate when neither 'warning' nor 'critical' is provided.
10339 Allow just warning OR critical to be given for the 'timesync' action.
10340 Remove 'redirect_stderr' requirement from 'logfile' due to 8.3 changes.
10341 Actions 'last_vacuum' and 'last_analyze' are 8.2 only (Robert Treat)
10342
10343=item B<Version 1.0.16> (December 7, 2007)
10344
10345 First public release, December 2007
10346
10347=back
10348
10349=head1 BUGS AND LIMITATIONS
10350
10351The index bloat size optimization is rough.
10352
10353Some actions may not work on older versions of Postgres (before 8.0).
10354
10355Please report any problems to check_postgres@bucardo.org
10356
10357=head1 AUTHOR
10358
10359Greg Sabino Mullane <greg@endpoint.com>
10360
10361
10362=head1 NAGIOS EXAMPLES
10363
10364Some example Nagios configuration settings using this script:
10365
10366 define command {
10367 command_name check_postgres_size
10368 command_line $USER2$/check_postgres.pl -H $HOSTADDRESS$ -u pgsql -db postgres --action database_size -w $ARG1$ -c $ARG2$
10369 }
10370
10371 define command {
10372 command_name check_postgres_locks
10373 command_line $USER2$/check_postgres.pl -H $HOSTADDRESS$ -u pgsql -db postgres --action locks -w $ARG1$ -c $ARG2$
10374 }
10375
10376
10377 define service {
10378 use generic-other
10379 host_name dbhost.gtld
10380 service_description dbhost PostgreSQL Service Database Usage Size
10381 check_command check_postgres_size!256000000!512000000
10382 }
10383
10384 define service {
10385 use generic-other
10386 host_name dbhost.gtld
10387 service_description dbhost PostgreSQL Service Database Locks
10388 check_command check_postgres_locks!2!3
10389 }
10390
10391=head1 LICENSE AND COPYRIGHT
10392
10393Copyright (c) 2007-2012 Greg Sabino Mullane <greg@endpoint.com>.
10394
10395Redistribution and use in source and binary forms, with or without
10396modification, are permitted provided that the following conditions are met:
10397
10398 1. Redistributions of source code must retain the above copyright notice,
10399 this list of conditions and the following disclaimer.
10400 2. Redistributions in binary form must reproduce the above copyright notice,
10401 this list of conditions and the following disclaimer in the documentation
10402 and/or other materials provided with the distribution.
10403
10404THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
10405WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
10406MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
10407EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
10408EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
10409OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
10410INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
10411CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
10412IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
10413OF SUCH DAMAGE.
10414
10415=cut
10416
10417# vi: tabstop=4 shiftwidth=4 expandtab