## Preferred order of ways to fetch pages for new_version checks
our $get_method_timeout = 30;
our @get_methods = (
- "GET -t $get_method_timeout",
- "wget --quiet --timeout=$get_method_timeout -O -",
- "fetch -q -T $get_method_timeout -o -",
- "curl --silent --max-time=$get_method_timeout",
- "lynx --connect-timeout=$get_method_timeout --dump",
- 'links -dump',
+ "GET -t $get_method_timeout",
+ "wget --quiet --timeout=$get_method_timeout -O -",
+ "fetch -q -T $get_method_timeout -o -",
+ "curl --silent --max-time=$get_method_timeout",
+ "lynx --connect-timeout=$get_method_timeout --dump",
+ 'links -dump',
);
## Nothing below this line should need to be changed for normal usage.
## no critic (RequireInterpolationOfMetachars)
our %msg = (
'en' => {
- 'address' => q{address},
- 'backends-fatal' => q{Could not connect: too many connections},
- 'backends-mrtg' => q{DB=$1 Max connections=$2},
- 'backends-msg' => q{$1 of $2 connections ($3%)},
- 'backends-nomax' => q{Could not determine max_connections},
- 'backends-oknone' => q{No connections},
- 'backends-po' => q{sorry, too many clients already},
- 'backends-users' => q{$1 for number of users must be a number or percentage},
- 'bloat-index' => q{(db $1) index $2 rows:$3 pages:$4 shouldbe:$5 ($6X) wasted bytes:$7 ($8)},
- 'bloat-nomin' => q{no relations meet the minimum bloat criteria},
- 'bloat-table' => q{(db $1) table $2.$3 rows:$4 pages:$5 shouldbe:$6 ($7X) wasted size:$8 ($9)},
- 'checkpoint-baddir' => q{Invalid data_directory: "$1"},
- 'checkpoint-baddir2' => q{pg_controldata could not read the given data directory: "$1"},
- 'checkpoint-badver' => q{Failed to run pg_controldata - probably the wrong version},
- 'checkpoint-badver2' => q{Failed to run pg_controldata - is it the correct version?},
- 'checkpoint-nodir' => q{Must supply a --datadir argument or set the PGDATA environment variable},
- 'checkpoint-nodp' => q{Must install the Perl module Date::Parse to use the checkpoint action},
- 'checkpoint-noparse' => q{Unable to parse pg_controldata output: "$1"},
- 'checkpoint-noregex' => q{Call to pg_controldata $1 failed},
- 'checkpoint-nosys' => q{Could not call pg_controldata: $1},
- 'checkpoint-ok' => q{Last checkpoint was 1 second ago},
- 'checkpoint-ok2' => q{Last checkpoint was $1 seconds ago},
- 'checkpoint-po' => q{Time of latest checkpoint:},
- 'checksum-badline' => q{Invalid pg_setting line: $1},
- 'checksum-msg' => q{checksum: $1},
- 'checksum-nomd' => q{Must install the Perl module Digest::MD5 to use the checksum action},
- 'checksum-nomrtg' => q{Must provide a checksum via the --mrtg option},
- 'custom-invalid' => q{Invalid format returned by custom query},
- 'custom-norows' => q{No rows returned},
- 'custom-nostring' => q{Must provide a query string},
- 'database' => q{database},
- 'dbsize-version' => q{Target database must be version 8.1 or higher to run the database_size action},
- 'die-action-version' => q{Cannot run "$1": server version must be >= $2, but is $3},
- 'die-badtime' => q{Value for '$1' must be a valid time. Examples: -$2 1s -$2 "10 minutes"},
- 'die-badversion' => q{Invalid version string: $1},
- 'die-noset' => q{Cannot run "$1" $2 is not set to on},
- 'die-nosetting' => q{Could not fetch setting '$1'},
- 'diskspace-fail' => q{Invalid result from command "$1": $2},
- 'diskspace-msg' => q{FS $1 mounted on $2 is using $3 of $4 ($5%)},
- 'diskspace-nodata' => q{Could not determine data_directory: are you connecting as a superuser?},
- 'diskspace-nodf' => q{Could not find required executable /bin/df},
- 'diskspace-nodir' => q{Could not find data directory "$1"},
- 'file-noclose' => q{Could not close $1: $2},
- 'fsm-page-highver' => q{Cannot check on fsm_pages on servers version 8.4 or greater},
- 'fsm-page-msg' => q{fsm page slots used: $1 of $2 ($3%)},
- 'fsm-rel-highver' => q{Cannot check on fsm_relations on servers version 8.4 or greater},
- 'fsm-rel-msg' => q{fsm relations used: $1 of $2 ($3%)},
- 'invalid-option' => q{Invalid option},
- 'invalid-query' => q{Invalid query returned: $1},
- 'listener-count' => q{ listening=$1}, ## needs leading space
- 'listener-msg' => q{listeners found: $1},
- 'locks-msg' => q{total "$1" locks: $2},
- 'locks-msg2' => q{total locks: $1},
- 'logfile-bad' => q{Invalid logfile "$1"},
- 'logfile-debug' => q{Dest is $1, dir is $2, file is $3, facility is $4},
- 'logfile-debug2' => q{Final logfile: $1},
- 'logfile-dne' => q{logfile $1 does not exist!},
- 'logfile-fail' => q{fails logging to: $1},
- 'logfile-ok' => q{logs to: $1},
- 'logfile-openfail' => q{logfile "$1" failed to open: $2},
- 'logfile-opt-bad' => q{Invalid logfile option},
- 'logfile-seekfail' => q{Seek on $1 failed: $2},
- 'logfile-stderr' => q{Logfile output has been redirected to stderr: please provide a filename},
- 'logfile-syslog' => q{Database is using syslog, please specify path with --logfile option (fac=$1)},
- 'maxtime' => q{ maxtime=$1}, ## needs leading space
- 'mrtg-fail' => q{Action $1 failed: $2},
- 'new-bc-badver' => q{Could not determine the version of Bucardo},
- 'new-bc-fail' => q{Could not find current version information for Bucardo},
- 'new-bc-ok' => q{Version $1 is the latest for Bucardo},
- 'new-bc-warn' => q{Please upgrade to version $1 of Bucardo. You are running $2},
- 'new-cp-fail' => q{Unable to determine the current version of check_postgres.pl},
- 'new-cp-ok' => q{Version $1 is the latest for check_postgres.pl},
- 'new-cp-warn' => q{Version $1 of check_postgres.pl exists (this is version $2)},
- 'new-pg-badver' => q{Could not determine the Postgres revision (version was $1)},
- 'new-pg-badver2' => q{Could not find revision information for Postgres version $1},
- 'new-pg-big' => q{Please upgrade to version $1 of Postgres. You are running $2},
- 'new-pg-match' => q{Postgres is at the latest revision ($1)},
- 'new-pg-small' => q{The latest version of Postgres is $1, but you are running $2?},
- 'no-match-db' => q{No matching databases found due to exclusion/inclusion options},
- 'no-match-fs' => q{No matching file systems found due to exclusion/inclusion options},
- 'no-match-rel' => q{No matching relations found due to exclusion/inclusion options},
- 'no-match-set' => q{No matching settings found due to exclusion/inclusion options},
- 'no-match-table' => q{No matching tables found due to exclusion/inclusion options},
- 'no-match-user' => q{No matching entries found due to user exclusion/inclusion options},
- 'no-time-hires' => q{Cannot find Time::HiRes, needed if 'showtime' is true},
- 'opt-output-invalid' => q{Invalid output: must be 'nagios' or 'mrtg' or 'simple' or 'cacti'},
- 'opt-psql-badpath' => q{Invalid psql argument: must be full path to a file named psql},
- 'opt-psql-noexec' => q{The file "$1" does not appear to be executable},
- 'opt-psql-noexist' => q{Cannot find given psql executable: $1},
- 'opt-psql-nofind' => q{Could not find a suitable psql executable},
- 'opt-psql-nover' => q{Could not determine psql version},
- 'opt-psql-restrict' => q{Cannot use the --PSQL option when NO_PSQL_OPTION is on},
- 'PID' => q{PID},
- 'port' => q{port},
- 'preptxn-none' => q{No prepared transactions found},
- 'psa-nomatches' => q{No queries were found},
- 'psa-nosuper' => q{No matches - please run as a superuser},
- 'psa-skipped' => q{No matching rows were found (skipped rows: $1)},
- 'qtime-fail' => q{Cannot run the txn_idle action unless stats_command_string is set to 'on'!},
- 'qtime-msg' => q{longest query: $1s},
- 'qtime-nomatch' => q{No queries were found},
- 'Query' => q{Query: $1},
- 'range-badcs' => q{Invalid '$1' option: must be a checksum},
- 'range-badlock' => q{Invalid '$1' option: must be number of locks, or "type1=#;type2=#"},
- 'range-badpercent' => q{Invalid '$1' option: must be a percentage},
- 'range-badpercsize' => q{Invalid '$1' option: must be a size or a percentage},
- 'range-badsize' => q{Invalid size for '$1' option},
- 'range-badtype' => q{validate_range called with unknown type '$1'},
- 'range-badversion' => q{Invalid string for '$1' option: $2},
- 'range-cactionly' => q{This action is for cacti use only and takes no warning or critical arguments},
- 'range-int' => q{Invalid argument for '$1' option: must be an integer},
- 'range-int-pos' => q{Invalid argument for '$1' option: must be a positive integer},
- 'range-neg-percent' => q{Cannot specify a negative percent!},
- 'range-none' => q{No warning or critical options are needed},
- 'range-noopt-both' => q{Must provide both 'warning' and 'critical' options},
- 'range-noopt-one' => q{Must provide a 'warning' or 'critical' option},
- 'range-noopt-only' => q{Can only provide 'warning' OR 'critical' option},
- 'range-noopt-orboth' => q{Must provide a 'warning' option, a 'critical' option, or both},
- 'range-noopt-size' => q{Must provide a warning and/or critical size},
- 'range-nosize' => q{Must provide a warning and/or critical size},
- 'range-notime' => q{Must provide a warning and/or critical time},
- 'range-seconds' => q{Invalid argument to '$1' option: must be number of seconds},
- 'range-version' => q{must be in the format X.Y or X.Y.Z, where X is the major version number, },
- 'range-warnbig' => q{The 'warning' option cannot be greater than the 'critical' option},
- 'range-warnbigsize' => q{The 'warning' option ($1 bytes) cannot be larger than the 'critical' option ($2 bytes)},
- 'range-warnbigtime' => q{The 'warning' option ($1 s) cannot be larger than the 'critical' option ($2 s)},
- 'range-warnsmall' => q{The 'warning' option cannot be less than the 'critical' option},
- 'relsize-msg-ind' => q{largest index is "$1": $2},
- 'relsize-msg-reli' => q{largest relation is index "$1": $2},
- 'relsize-msg-relt' => q{largest relation is table "$1": $2},
- 'relsize-msg-tab' => q{largest table is "$1": $2},
- 'rep-badarg' => q{Invalid repinfo argument: expected 6 comma-separated values},
- 'rep-duh' => q{Makes no sense to test replication with same values},
- 'rep-fail' => q{Row not replicated to slave $1},
- 'rep-noarg' => q{Need a repinfo argument},
- 'rep-norow' => q{Replication source row not found: $1},
- 'rep-noslaves' => q{No slaves found},
- 'rep-notsame' => q{Cannot test replication: values are not the same},
- 'rep-ok' => q{Row was replicated},
- 'rep-sourcefail' => q{Source update failed},
- 'rep-timeout' => q{Row was not replicated. Timeout: $1},
- 'rep-unknown' => q{Replication check failed},
- 'rep-wrongvals' => q{Cannot test replication: values are not the right ones ($1 not $2 nor $3)},
- 'runcommand-err' => q{Unknown error inside of the "run_command" function},
- 'runcommand-nodb' => q{No target databases could be found},
- 'runcommand-nodupe' => q{Could not dupe STDERR},
- 'runcommand-noerr' => q{Could not open STDERR?!},
- 'runcommand-nosys' => q{System call failed with a $1},
- 'runcommand-pgpass' => q{Created temporary pgpass file $1},
- 'runcommand-timeout' => q{Command timed out! Consider boosting --timeout higher than $1},
- 'runtime-badmrtg' => q{invalid queryname?},
- 'runtime-badname' => q{Invalid queryname option: must be a simple view name},
- 'runtime-msg' => q{query runtime: $1 seconds},
- 'same-failed' => q{Databases were different. Items not matched: $1},
- 'same-matched' => q{Both databases have identical items},
- 'seq-die' => q{Could not determine information about sequence $1},
- 'seq-msg' => q{$1=$2% (calls left=$3)},
- 'seq-none' => q{No sequences found},
- 'slony-noschema' => q{Could not determine the schema for Slony},
- 'slony-nonumber' => q{Call to sl_status did not return a number},
- 'slony-noparse' => q{Could not parse call to sl_status},
- 'slony-lagtime' => q{Slony lag time: $1},
- 'symlink-create' => q{Created "$1"},
- 'symlink-done' => q{Not creating "$1": $2 already linked to "$3"},
- 'symlink-exists' => q{Not creating "$1": $2 file already exists},
- 'symlink-fail1' => q{Failed to unlink "$1": $2},
- 'symlink-fail2' => q{Could not symlink $1 to $2: $3},
- 'symlink-name' => q{This command will not work unless the program has the word "postgres" in it},
- 'symlink-unlink' => q{Unlinking "$1":$2 },
- 'testmode-end' => q{END OF TEST MODE},
- 'testmode-fail' => q{Connection failed: $1 $2},
- 'testmode-norun' => q{Cannot run "$1" on $2: version must be >= $3, but is $4},
- 'testmode-noset' => q{Cannot run "$1" on $2: $3 is not set to on},
- 'testmode-nover' => q{Could not find version for $1},
- 'testmode-ok' => q{Connection ok: $1},
- 'testmode-start' => q{BEGIN TEST MODE},
- 'time-day' => q{day},
- 'time-days' => q{days},
- 'time-hour' => q{hour},
- 'time-hours' => q{hours},
- 'time-minute' => q{minute},
- 'time-minutes' => q{minutes},
- 'time-month' => q{month},
- 'time-months' => q{months},
- 'time-second' => q{second},
- 'time-seconds' => q{seconds},
- 'time-week' => q{week},
- 'time-weeks' => q{weeks},
- 'time-year' => q{year},
- 'time-years' => q{years},
- 'timesync-diff' => q{ diff=$1}, ## needs leading space
- 'timesync-msg' => q{timediff=$1 DB=$2 Local=$3},
- 'trigger-msg' => q{Disabled triggers: $1},
- 'txnidle-msg' => q{longest idle in txn: $1s},
- 'txnidle-none' => q{no idle in transaction},
- 'txntime-fail' => q{Query failed},
- 'txntime-msg' => q{longest txn: $1s},
- 'txntime-none' => q{No transactions},
- 'txnwrap-cbig' => q{The 'critical' value must be less than 2 billion},
- 'txnwrap-wbig' => q{The 'warning' value must be less than 2 billion},
- 'unknown-error' => q{Unknown error},
- 'usage' => qq{\nUsage: \$1 <options>\n Try "\$1 --help" for a complete list of options\n Try "\$1 --man" for the full manual\n},
- 'username' => q{username},
- 'vac-msg' => q{DB: $1 TABLE: $2},
- 'vac-nomatch-a' => q{No matching tables have ever been analyzed},
- 'vac-nomatch-v' => q{No matching tables have ever been vacuumed},
- 'version' => q{version $1},
- 'version-badmrtg' => q{Invalid mrtg version argument},
- 'version-fail' => q{version $1, but expected $2},
- 'version-ok' => q{version $1},
+ 'address' => q{address},
+ 'backends-fatal' => q{Could not connect: too many connections},
+ 'backends-mrtg' => q{DB=$1 Max connections=$2},
+ 'backends-msg' => q{$1 of $2 connections ($3%)},
+ 'backends-nomax' => q{Could not determine max_connections},
+ 'backends-oknone' => q{No connections},
+ 'backends-po' => q{sorry, too many clients already},
+ 'backends-users' => q{$1 for number of users must be a number or percentage},
+ 'bloat-index' => q{(db $1) index $2 rows:$3 pages:$4 shouldbe:$5 ($6X) wasted bytes:$7 ($8)},
+ 'bloat-nomin' => q{no relations meet the minimum bloat criteria},
+ 'bloat-table' => q{(db $1) table $2.$3 rows:$4 pages:$5 shouldbe:$6 ($7X) wasted size:$8 ($9)},
+ 'checkpoint-baddir' => q{Invalid data_directory: "$1"},
+ 'checkpoint-baddir2' => q{pg_controldata could not read the given data directory: "$1"},
+ 'checkpoint-badver' => q{Failed to run pg_controldata - probably the wrong version},
+ 'checkpoint-badver2' => q{Failed to run pg_controldata - is it the correct version?},
+ 'checkpoint-nodir' => q{Must supply a --datadir argument or set the PGDATA environment variable},
+ 'checkpoint-nodp' => q{Must install the Perl module Date::Parse to use the checkpoint action},
+ 'checkpoint-noparse' => q{Unable to parse pg_controldata output: "$1"},
+ 'checkpoint-noregex' => q{Call to pg_controldata $1 failed},
+ 'checkpoint-nosys' => q{Could not call pg_controldata: $1},
+ 'checkpoint-ok' => q{Last checkpoint was 1 second ago},
+ 'checkpoint-ok2' => q{Last checkpoint was $1 seconds ago},
+ 'checkpoint-po' => q{Time of latest checkpoint:},
+ 'checksum-badline' => q{Invalid pg_setting line: $1},
+ 'checksum-msg' => q{checksum: $1},
+ 'checksum-nomd' => q{Must install the Perl module Digest::MD5 to use the checksum action},
+ 'checksum-nomrtg' => q{Must provide a checksum via the --mrtg option},
+ 'custom-invalid' => q{Invalid format returned by custom query},
+ 'custom-norows' => q{No rows returned},
+ 'custom-nostring' => q{Must provide a query string},
+ 'database' => q{database},
+ 'dbsize-version' => q{Target database must be version 8.1 or higher to run the database_size action},
+ 'die-action-version' => q{Cannot run "$1": server version must be >= $2, but is $3},
+ 'die-badtime' => q{Value for '$1' must be a valid time. Examples: -$2 1s -$2 "10 minutes"},
+ 'die-badversion' => q{Invalid version string: $1},
+ 'die-noset' => q{Cannot run "$1" $2 is not set to on},
+ 'die-nosetting' => q{Could not fetch setting '$1'},
+ 'diskspace-fail' => q{Invalid result from command "$1": $2},
+ 'diskspace-msg' => q{FS $1 mounted on $2 is using $3 of $4 ($5%)},
+ 'diskspace-nodata' => q{Could not determine data_directory: are you connecting as a superuser?},
+ 'diskspace-nodf' => q{Could not find required executable /bin/df},
+ 'diskspace-nodir' => q{Could not find data directory "$1"},
+ 'file-noclose' => q{Could not close $1: $2},
+ 'fsm-page-highver' => q{Cannot check on fsm_pages on servers version 8.4 or greater},
+ 'fsm-page-msg' => q{fsm page slots used: $1 of $2 ($3%)},
+ 'fsm-rel-highver' => q{Cannot check on fsm_relations on servers version 8.4 or greater},
+ 'fsm-rel-msg' => q{fsm relations used: $1 of $2 ($3%)},
+ 'invalid-option' => q{Invalid option},
+ 'invalid-query' => q{Invalid query returned: $1},
+ 'listener-count' => q{ listening=$1}, ## needs leading space
+ 'listener-msg' => q{listeners found: $1},
+ 'locks-msg' => q{total "$1" locks: $2},
+ 'locks-msg2' => q{total locks: $1},
+ 'logfile-bad' => q{Invalid logfile "$1"},
+ 'logfile-debug' => q{Dest is $1, dir is $2, file is $3, facility is $4},
+ 'logfile-debug2' => q{Final logfile: $1},
+ 'logfile-dne' => q{logfile $1 does not exist!},
+ 'logfile-fail' => q{fails logging to: $1},
+ 'logfile-ok' => q{logs to: $1},
+ 'logfile-openfail' => q{logfile "$1" failed to open: $2},
+ 'logfile-opt-bad' => q{Invalid logfile option},
+ 'logfile-seekfail' => q{Seek on $1 failed: $2},
+ 'logfile-stderr' => q{Logfile output has been redirected to stderr: please provide a filename},
+ 'logfile-syslog' => q{Database is using syslog, please specify path with --logfile option (fac=$1)},
+ 'maxtime' => q{ maxtime=$1}, ## needs leading space
+ 'mrtg-fail' => q{Action $1 failed: $2},
+ 'new-bc-badver' => q{Could not determine the version of Bucardo},
+ 'new-bc-fail' => q{Could not find current version information for Bucardo},
+ 'new-bc-ok' => q{Version $1 is the latest for Bucardo},
+ 'new-bc-warn' => q{Please upgrade to version $1 of Bucardo. You are running $2},
+ 'new-cp-fail' => q{Unable to determine the current version of check_postgres.pl},
+ 'new-cp-ok' => q{Version $1 is the latest for check_postgres.pl},
+ 'new-cp-warn' => q{Version $1 of check_postgres.pl exists (this is version $2)},
+ 'new-pg-badver' => q{Could not determine the Postgres revision (version was $1)},
+ 'new-pg-badver2' => q{Could not find revision information for Postgres version $1},
+ 'new-pg-big' => q{Please upgrade to version $1 of Postgres. You are running $2},
+ 'new-pg-match' => q{Postgres is at the latest revision ($1)},
+ 'new-pg-small' => q{The latest version of Postgres is $1, but you are running $2?},
+ 'no-match-db' => q{No matching databases found due to exclusion/inclusion options},
+ 'no-match-fs' => q{No matching file systems found due to exclusion/inclusion options},
+ 'no-match-rel' => q{No matching relations found due to exclusion/inclusion options},
+ 'no-match-set' => q{No matching settings found due to exclusion/inclusion options},
+ 'no-match-table' => q{No matching tables found due to exclusion/inclusion options},
+ 'no-match-user' => q{No matching entries found due to user exclusion/inclusion options},
+ 'no-time-hires' => q{Cannot find Time::HiRes, needed if 'showtime' is true},
+ 'opt-output-invalid' => q{Invalid output: must be 'nagios' or 'mrtg' or 'simple' or 'cacti'},
+ 'opt-psql-badpath' => q{Invalid psql argument: must be full path to a file named psql},
+ 'opt-psql-noexec' => q{The file "$1" does not appear to be executable},
+ 'opt-psql-noexist' => q{Cannot find given psql executable: $1},
+ 'opt-psql-nofind' => q{Could not find a suitable psql executable},
+ 'opt-psql-nover' => q{Could not determine psql version},
+ 'opt-psql-restrict' => q{Cannot use the --PSQL option when NO_PSQL_OPTION is on},
+ 'PID' => q{PID},
+ 'port' => q{port},
+ 'preptxn-none' => q{No prepared transactions found},
+ 'psa-nomatches' => q{No queries were found},
+ 'psa-nosuper' => q{No matches - please run as a superuser},
+ 'psa-skipped' => q{No matching rows were found (skipped rows: $1)},
+ 'qtime-fail' => q{Cannot run the txn_idle action unless stats_command_string is set to 'on'!},
+ 'qtime-msg' => q{longest query: $1s},
+ 'qtime-nomatch' => q{No queries were found},
+ 'Query' => q{Query: $1},
+ 'range-badcs' => q{Invalid '$1' option: must be a checksum},
+ 'range-badlock' => q{Invalid '$1' option: must be number of locks, or "type1=#;type2=#"},
+ 'range-badpercent' => q{Invalid '$1' option: must be a percentage},
+ 'range-badpercsize' => q{Invalid '$1' option: must be a size or a percentage},
+ 'range-badsize' => q{Invalid size for '$1' option},
+ 'range-badtype' => q{validate_range called with unknown type '$1'},
+ 'range-badversion' => q{Invalid string for '$1' option: $2},
+ 'range-cactionly' => q{This action is for cacti use only and takes no warning or critical arguments},
+ 'range-int' => q{Invalid argument for '$1' option: must be an integer},
+ 'range-int-pos' => q{Invalid argument for '$1' option: must be a positive integer},
+ 'range-neg-percent' => q{Cannot specify a negative percent!},
+ 'range-none' => q{No warning or critical options are needed},
+ 'range-noopt-both' => q{Must provide both 'warning' and 'critical' options},
+ 'range-noopt-one' => q{Must provide a 'warning' or 'critical' option},
+ 'range-noopt-only' => q{Can only provide 'warning' OR 'critical' option},
+ 'range-noopt-orboth' => q{Must provide a 'warning' option, a 'critical' option, or both},
+ 'range-noopt-size' => q{Must provide a warning and/or critical size},
+ 'range-nosize' => q{Must provide a warning and/or critical size},
+ 'range-notime' => q{Must provide a warning and/or critical time},
+ 'range-seconds' => q{Invalid argument to '$1' option: must be number of seconds},
+ 'range-version' => q{must be in the format X.Y or X.Y.Z, where X is the major version number, },
+ 'range-warnbig' => q{The 'warning' option cannot be greater than the 'critical' option},
+ 'range-warnbigsize' => q{The 'warning' option ($1 bytes) cannot be larger than the 'critical' option ($2 bytes)},
+ 'range-warnbigtime' => q{The 'warning' option ($1 s) cannot be larger than the 'critical' option ($2 s)},
+ 'range-warnsmall' => q{The 'warning' option cannot be less than the 'critical' option},
+ 'relsize-msg-ind' => q{largest index is "$1": $2},
+ 'relsize-msg-reli' => q{largest relation is index "$1": $2},
+ 'relsize-msg-relt' => q{largest relation is table "$1": $2},
+ 'relsize-msg-tab' => q{largest table is "$1": $2},
+ 'rep-badarg' => q{Invalid repinfo argument: expected 6 comma-separated values},
+ 'rep-duh' => q{Makes no sense to test replication with same values},
+ 'rep-fail' => q{Row not replicated to slave $1},
+ 'rep-noarg' => q{Need a repinfo argument},
+ 'rep-norow' => q{Replication source row not found: $1},
+ 'rep-noslaves' => q{No slaves found},
+ 'rep-notsame' => q{Cannot test replication: values are not the same},
+ 'rep-ok' => q{Row was replicated},
+ 'rep-sourcefail' => q{Source update failed},
+ 'rep-timeout' => q{Row was not replicated. Timeout: $1},
+ 'rep-unknown' => q{Replication check failed},
+ 'rep-wrongvals' => q{Cannot test replication: values are not the right ones ($1 not $2 nor $3)},
+ 'runcommand-err' => q{Unknown error inside of the "run_command" function},
+ 'runcommand-nodb' => q{No target databases could be found},
+ 'runcommand-nodupe' => q{Could not dupe STDERR},
+ 'runcommand-noerr' => q{Could not open STDERR?!},
+ 'runcommand-nosys' => q{System call failed with a $1},
+ 'runcommand-pgpass' => q{Created temporary pgpass file $1},
+ 'runcommand-timeout' => q{Command timed out! Consider boosting --timeout higher than $1},
+ 'runtime-badmrtg' => q{invalid queryname?},
+ 'runtime-badname' => q{Invalid queryname option: must be a simple view name},
+ 'runtime-msg' => q{query runtime: $1 seconds},
+ 'same-failed' => q{Databases were different. Items not matched: $1},
+ 'same-matched' => q{Both databases have identical items},
+ 'seq-die' => q{Could not determine information about sequence $1},
+ 'seq-msg' => q{$1=$2% (calls left=$3)},
+ 'seq-none' => q{No sequences found},
+ 'slony-noschema' => q{Could not determine the schema for Slony},
+ 'slony-nonumber' => q{Call to sl_status did not return a number},
+ 'slony-noparse' => q{Could not parse call to sl_status},
+ 'slony-lagtime' => q{Slony lag time: $1},
+ 'symlink-create' => q{Created "$1"},
+ 'symlink-done' => q{Not creating "$1": $2 already linked to "$3"},
+ 'symlink-exists' => q{Not creating "$1": $2 file already exists},
+ 'symlink-fail1' => q{Failed to unlink "$1": $2},
+ 'symlink-fail2' => q{Could not symlink $1 to $2: $3},
+ 'symlink-name' => q{This command will not work unless the program has the word "postgres" in it},
+ 'symlink-unlink' => q{Unlinking "$1":$2 },
+ 'testmode-end' => q{END OF TEST MODE},
+ 'testmode-fail' => q{Connection failed: $1 $2},
+ 'testmode-norun' => q{Cannot run "$1" on $2: version must be >= $3, but is $4},
+ 'testmode-noset' => q{Cannot run "$1" on $2: $3 is not set to on},
+ 'testmode-nover' => q{Could not find version for $1},
+ 'testmode-ok' => q{Connection ok: $1},
+ 'testmode-start' => q{BEGIN TEST MODE},
+ 'time-day' => q{day},
+ 'time-days' => q{days},
+ 'time-hour' => q{hour},
+ 'time-hours' => q{hours},
+ 'time-minute' => q{minute},
+ 'time-minutes' => q{minutes},
+ 'time-month' => q{month},
+ 'time-months' => q{months},
+ 'time-second' => q{second},
+ 'time-seconds' => q{seconds},
+ 'time-week' => q{week},
+ 'time-weeks' => q{weeks},
+ 'time-year' => q{year},
+ 'time-years' => q{years},
+ 'timesync-diff' => q{ diff=$1}, ## needs leading space
+ 'timesync-msg' => q{timediff=$1 DB=$2 Local=$3},
+ 'trigger-msg' => q{Disabled triggers: $1},
+ 'txnidle-msg' => q{longest idle in txn: $1s},
+ 'txnidle-none' => q{no idle in transaction},
+ 'txntime-fail' => q{Query failed},
+ 'txntime-msg' => q{longest txn: $1s},
+ 'txntime-none' => q{No transactions},
+ 'txnwrap-cbig' => q{The 'critical' value must be less than 2 billion},
+ 'txnwrap-wbig' => q{The 'warning' value must be less than 2 billion},
+ 'unknown-error' => q{Unknown error},
+ 'usage' => qq{\nUsage: \$1 <options>\n Try "\$1 --help" for a complete list of options\n Try "\$1 --man" for the full manual\n},
+ 'username' => q{username},
+ 'vac-msg' => q{DB: $1 TABLE: $2},
+ 'vac-nomatch-a' => q{No matching tables have ever been analyzed},
+ 'vac-nomatch-v' => q{No matching tables have ever been vacuumed},
+ 'version' => q{version $1},
+ 'version-badmrtg' => q{Invalid mrtg version argument},
+ 'version-fail' => q{version $1, but expected $2},
+ 'version-ok' => q{version $1},
},
'fr' => {
- 'address' => q{adresse},
- 'backends-fatal' => q{N'a pas pu se connecter : trop de connexions},
- 'backends-mrtg' => q{DB=$1 Connexions maximum=$2},
- 'backends-msg' => q{$1 connexions sur $2 ($3%)},
- 'backends-nomax' => q{N'a pas pu déterminer max_connections},
- 'backends-oknone' => q{Aucune connexion},
- 'backends-po' => q{désolé, trop de clients sont déjà connectés},
- 'backends-users' => q{$1 pour le nombre d'utilisateurs doit être un nombre ou un pourcentage},
- 'bloat-index' => q{(db $1) index $2 lignes:$3 pages:$4 devrait être:$5 ($6X) octets perdus:$7 ($8)},
- 'bloat-nomin' => q{aucune relation n'atteint le critère minimum de fragmentation},
- 'bloat-table' => q{(db $1) table $2.$3 lignes:$4 pages:$5 devrait être:$6 ($7X) place perdue:$8 ($9)},
- 'checkpoint-baddir' => q{data_directory invalide : "$1"},
- 'checkpoint-baddir2' => q{pg_controldata n'a pas pu lire le répertoire des données indiqué : « $1 »},
- 'checkpoint-badver' => q{Échec lors de l'exécution de pg_controldata - probablement la mauvaise version},
- 'checkpoint-badver2' => q{Échec lors de l'exécution de pg_controldata - est-ce la bonne version ?},
- 'checkpoint-nodir' => q{Vous devez fournir un argument --datadir ou configurer la variable d'environnement PGDATA},
- 'checkpoint-nodp' => q{Vous devez installer le module Perl Date::Parse pour utiliser l'action checkpoint},
- 'checkpoint-noparse' => q{Incapable d'analyser le résultat de la commande pg_controldata : "$1"},
- 'checkpoint-noregex' => q{Échec de l'appel à pg_controldata $1},
- 'checkpoint-nosys' => q{N'a pas pu appeler pg_controldata : $1},
- 'checkpoint-ok' => q{Le dernier CHECKPOINT est survenu il y a une seconde},
- 'checkpoint-ok2' => q{Le dernier CHECKPOINT est survenu il y a $1 secondes},
- 'checkpoint-po' => q{Heure du dernier point de contr�le :},
- 'checksum-badline' => q{Ligne pg_setting invalide : $1},
- 'checksum-msg' => q{somme de contrôle : $1},
- 'checksum-nomd' => q{Vous devez installer le module Perl Digest::MD5 pour utiliser l'action checksum},
- 'checksum-nomrtg' => q{Vous devez fournir une somme de contrôle avec l'option --mrtg},
- 'custom-invalid' => q{Format invalide renvoyé par la requête personnalisée},
- 'custom-norows' => q{Aucune ligne renvoyée},
- 'custom-nostring' => q{Vous devez fournir une requête},
- 'database' => q{base de données},
- '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},
- '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},
- 'die-badtime' => q{La valeur de « $1 » doit être une heure valide. Par exemple, -$2 1s -$2 « 10 minutes »},
- 'die-badversion' => q{Version invalide : $1},
- 'die-noset' => q{Ne peut pas exécuter « $1 » $2 n'est pas activé},
- 'die-nosetting' => q{N'a pas pu récupérer le paramètre « $1 »},
- 'diskspace-fail' => q{Résultat invalide pour la commande « $1 » : $2},
- 'diskspace-msg' => q{Le système de fichiers $1 monté sur $2 utilise $3 sur $4 ($5%)},
- 'diskspace-nodata' => q{N'a pas pu déterminer data_directory : êtes-vous connecté en tant que super-utilisateur ?},
- 'diskspace-nodf' => q{N'a pas pu trouver l'exécutable /bin/df},
- 'diskspace-nodir' => q{N'a pas pu trouver le répertoire des données « $1 »},
- 'file-noclose' => q{N'a pas pu fermer $1 : $2},
- 'fsm-page-highver' => q{Ne peut pas vérifier fsm_pages sur des serveurs en version 8.4 ou ultérieure},
- 'fsm-page-msg' => q{emplacements de pages utilisés par la FSM : $1 sur $2 ($3%)},
- 'fsm-rel-highver' => q{Ne peut pas vérifier fsm_relations sur des serveurs en version 8.4 ou ultérieure},
- 'fsm-rel-msg' => q{relations tracées par la FSM : $1 sur $2 ($3%)},
- 'invalid-option' => q{Option invalide},
- 'invalid-query' => q{Une requête invalide a renvoyé : $1},
- 'listener-count' => q{ en écoute=$1}, ## needs leading space
- 'listener-msg' => q{processus LISTEN trouvés : $1},
- 'locks-msg' => q{total des verrous « $1 » : $2},
- 'locks-msg2' => q{total des verrous : $1},
- 'logfile-bad' => q{Option logfile invalide « $1 »},
- 'logfile-debug' => q{la destination est $1, le répertoire est $2, le fichier est $3, l'option facility est $4},
- 'logfile-debug2' => q{Journal applicatif final : $1},
- 'logfile-dne' => q{le journal applicatif $1 n'existe pas !},
- 'logfile-fail' => q{échec pour tracer dans : $1},
- 'logfile-ok' => q{trace dans : $1},
- 'logfile-openfail' => q{échec pour l'ouverture du journal applicatif « $1 » : $2},
- 'logfile-opt-bad' => q{Option logfile invalide},
- 'logfile-seekfail' => q{Échec de la recherche dans $1 : $2},
- 'logfile-stderr' => q{La sortie des traces a été redirigés stderr : merci de fournir un nom de fichier},
- 'logfile-syslog' => q{La base de données utiliser syslog, merci de spécifier le chemin avec l'option --logfile (fac=$1)},
- 'maxtime' => q{ maxtime=$1}, ## needs leading space
- 'mrtg-fail' => q{Échec de l'action $1 : $2},
- 'new-bc-badver' => q{N'a pas pu déterminer la version de Bucardo},
- 'new-bc-fail' => q{N'a pas pu trouver la version actuelle pour Bucardo},
- 'new-bc-ok' => q{La version $1 est la dernière pour Bucardo},
- 'new-bc-warn' => q{Merci de mettre à jour vers la version $1 de Bucardo. Vous utilisez actuellement la $2},
- 'new-cp-fail' => q{Incapable de déterminer la version actuelle de check_postgres.pl},
- 'new-cp-ok' => q{La version $1 est la dernière pour check_postgres.pl},
- 'new-cp-warn' => q{La version $1 de check_postgres.pl existe (ceci est la version $2)},
- 'new-pg-badver' => q{N'a pas pu déterminer la révision de Postgres (la version était $1)},
- 'new-pg-badver2' => q{N'a pas pu trouver l'information de révision de Posrgres version $1},
- 'new-pg-big' => q{Veuillez mettre à jour Postgres vers la version $1. Vous utilisez actuellement la version $2},
- 'new-pg-match' => q{Postgres est à la dernière révision ($1)},
- 'new-pg-small' => q{La dernière version de Postgres est la $1, mais vous utilisez actuellement la version $2?},
- 'no-match-db' => q{Aucune base de données trouvée à cause des options d'exclusion/inclusion},
- 'no-match-fs' => q{Aucun système de fichier trouvé à cause des options d'exclusion/inclusion},
- 'no-match-rel' => q{Aucune relation trouvée à cause des options d'exclusion/inclusion},
- 'no-match-set' => q{Aucun paramètre trouvé à cause des options d'exclusion/inclusion},
- 'no-match-table' => q{Aucune table trouvée à cause des options d'exclusion/inclusion},
- 'no-match-user' => q{Aucune entrée trouvée à cause options d'exclusion/inclusion},
- 'no-time-hires' => q{N'a pas trouvé le module Time::HiRes, nécessaire quand « showtime » est activé},
- 'opt-output-invalid' => q{Sortie invalide : doit être 'nagios' ou 'mrtg' ou 'simple' ou 'cacti'},
- 'opt-psql-badpath' => q{Argument invalide pour psql : doit être le chemin complet vers un fichier nommé psql},
- 'opt-psql-noexec' => q{ Le fichier « $1 » ne paraît pas exécutable},
- 'opt-psql-noexist' => q{Ne peut pas trouver l'exécutable psql indiqué : $1},
- 'opt-psql-nofind' => q{N'a pas pu trouver un psql exécutable},
- 'opt-psql-nover' => q{N'a pas pu déterminer la version de psql},
- 'opt-psql-restrict' => q{Ne peut pas utiliser l'option --PSQL si NO_PSQL_OPTION est activé},
- 'PID' => q{PID},
- 'port' => q{port},
- 'preptxn-none' => q{Aucune transaction préparée trouvée},
- 'qtime-fail' => q{Ne peut pas exécuter l'action txn_idle si stats_command_string est désactivé !},
- 'qtime-msg' => q{requête la plus longue : $1s},
- 'qtime-nomatch' => q{Aucune entrée correspondante n'a été trouvée},
- 'range-badcs' => q{Option « $1 » invalide : doit être une somme de contrôle},
- 'range-badlock' => q{Option « $1 » invalide : doit être un nombre de verrou ou « type1=#;type2=# »},
- 'range-badpercent' => q{Option « $1 » invalide : doit être un pourcentage},
- 'range-badpercsize' => q{Option « $1 » invalide : doit être une taille ou un pourcentage},
- 'range-badsize' => q{Taille invalide pour l'option « $1 »},
- 'range-badtype' => q{validate_range appelé avec un type inconnu « $1 »},
- 'range-badversion' => q{Chaîne invalide pour l'option « $1 » : $2},
- 'range-cactionly' => q{Cette action est pour cacti seulement et ne prend pas les arguments warning et critical},
- 'range-int' => q{Argument invalide pour l'option « $1 » : doit être un entier},
- 'range-int-pos' => q{Argument invalide pour l'option « $1 » : doit être un entier positif},
- 'range-neg-percent' => q{Ne peut pas indiquer un pourcentage négatif !},
- 'range-none' => q{Les options warning et critical ne sont pas nécessaires},
- 'range-noopt-both' => q{Doit fournir les options warning et critical},
- 'range-noopt-one' => q{Doit fournir une option warning ou critical},
- 'range-noopt-only' => q{Peut seulement fournir une option warning ou critical},
- 'range-noopt-orboth' => q{Doit fournir une option warning, une option critical ou les deux},
- 'range-noopt-size' => q{Doit fournir une taille warning et/ou critical},
- 'range-nosize' => q{Doit fournir une taille warning et/ou critical},
- 'range-notime' => q{Doit fournir une heure warning et/ou critical},
- 'range-seconds' => q{Argument invalide pour l'option « $1 » : doit être un nombre de secondes},
- 'range-version' => q{doit être dans le format X.Y ou X.Y.Z, où X est le numéro de version majeure, },
- 'range-warnbig' => q{L'option warning ne peut pas être plus grand que l'option critical},
- 'range-warnbigsize' => q{L'option warning ($1 octets) ne peut pas être plus grand que l'option critical ($2 octets)},
- 'range-warnbigtime' => q{L'option warning ($1 s) ne peut pas être plus grand que l'option critical ($2 s)},
- 'range-warnsmall' => q{L'option warningne peut pas être plus petit que l'option critical},
- 'relsize-msg-ind' => q{le plus gros index est « $1 » : $2},
- 'relsize-msg-reli' => q{la plus grosse relation est l'index « $1 » : $2},
- 'relsize-msg-relt' => q{la plus grosse relation est la table « $1 » : $2},
- 'relsize-msg-tab' => q{la plus grosse table est « $1 » : $2},
- 'rep-badarg' => q{Argument repinfo invalide : 6 valeurs séparées par des virgules attendues},
- 'rep-duh' => q{Aucun sens à tester la réplication avec les mêmes valeurs},
- 'rep-fail' => q{Ligne non répliquée sur l'esclave $1},
- 'rep-noarg' => q{A besoin d'un argument repinfo},
- 'rep-norow' => q{Ligne source de la réplication introuvable : $1},
- 'rep-noslaves' => q{Aucun esclave trouvé},
- 'rep-notsame' => q{Ne peut pas tester la réplication : les valeurs ne sont pas identiques},
- 'rep-ok' => q{La ligne a été répliquée},
- 'rep-sourcefail' => q{Échec de la mise à jour de la source},
- 'rep-timeout' => q{La ligne n'a pas été répliquée. Délai dépassé : $1},
- 'rep-unknown' => q{Échec du test de la réplication},
- 'rep-wrongvals' => q{Ne peut pas tester la réplication : les valeurs ne sont pas les bonnes (ni $1 ni $2 ni $3)},
- 'runcommand-err' => q{Erreur inconnue de la fonction « run_command »},
- 'runcommand-nodb' => q{Aucune base de données cible trouvée},
- 'runcommand-nodupe' => q{N'a pas pu dupliqué STDERR},
- 'runcommand-noerr' => q{N'a pas pu ouvrir STDERR},
- 'runcommand-nosys' => q{Échec de l'appel système avec un $1},
- 'runcommand-pgpass' => q{Création du fichier pgpass temporaire $1},
- 'runcommand-timeout' => q{Délai épuisée pour la commande ! Essayez d'augmenter --timeout à une valeur plus importante que $1},
- 'runtime-badmrtg' => q{queryname invalide ?},
- 'runtime-badname' => q{Option invalide pour queryname option : doit être le nom d'une vue},
- 'runtime-msg' => q{durée d'exécution de la requête : $1 secondes},
- 'same-failed' => q{Les bases de données sont différentes. Éléments différents : $1},
- 'same-matched' => q{Les bases de données ont les mêmes éléments},
- 'slony-noschema' => q{N'a pas pu déterminer le schéma de Slony},
- 'slony-nonumber' => q{L'appel à sl_status n'a pas renvoyé un numéro},
- 'slony-noparse' => q{N'a pas pu analyser l'appel à sl_status},
- 'slony-lagtime' => q{Durée de lag de Slony : $1},
- 'seq-die' => q{N'a pas pu récupérer d'informations sur la séquence $1},
- 'seq-msg' => q{$1=$2% (appels restant=$3)},
- 'seq-none' => q{Aucune sequences trouvée},
- 'symlink-create' => q{Création de « $1 »},
- 'symlink-done' => q{Création impossible de « $1 »: $2 est déjà lié à "$3"},
- 'symlink-exists' => q{Création impossible de « $1 »: le fichier $2 existe déjà},
- 'symlink-fail1' => q{Échec de la suppression de « $1 » : $2},
- 'symlink-fail2' => q{N'a pas pu supprimer le lien symbolique $1 vers $2 : $3},
- 'symlink-name' => q{Cette commande ne fonctionnera pas sauf si le programme contient le mot « postgres »},
- 'symlink-unlink' => q{Supression de « $1 » :$2 },
- 'testmode-end' => q{FIN DU MODE DE TEST},
- 'testmode-fail' => q{Échec de la connexion : $1 $2},
- 'testmode-norun' => q{N'a pas pu exécuter « $1 » sur $2 : la version doit être supérieure ou égale à $3, mais est $4},
- 'testmode-noset' => q{N'a pas pu exécuter « $1 » sur $2 : $3 n'est pas activé},
- 'testmode-nover' => q{N'a pas pu trouver la version de $1},
- 'testmode-ok' => q{Connexion OK : $1},
- 'testmode-start' => q{DÉBUT DU MODE DE TEST},
- 'time-day' => q{jour},
- 'time-days' => q{jours},
- 'time-hour' => q{heure},
- 'time-hours' => q{heures},
- 'time-minute' => q{minute},
- 'time-minutes' => q{minutes},
- 'time-month' => q{mois},
- 'time-months' => q{mois},
- 'time-second' => q{seconde},
- 'time-seconds' => q{secondes},
- 'time-week' => q{semaine},
- 'time-weeks' => q{semaines},
- 'time-year' => q{année},
- 'time-years' => q{années},
- 'timesync-diff' => q{ diff=$1}, ## needs leading space
- 'timesync-msg' => q{timediff=$1 Base de données=$2 Local=$3},
- 'trigger-msg' => q{Triggers désactivés : $1},
- 'txnidle-msg' => q{transaction en attente la plus longue : $1s},
- 'txnidle-none' => q{Aucun processus en attente dans une transaction},
- 'txntime-fail' => q{Échec de la requête},
- 'txntime-msg' => q{Transaction la plus longue : $1s},
- 'txntime-none' => q{Aucune transaction},
- 'txnwrap-cbig' => q{La valeur critique doit être inférieure à 2 milliards},
- 'txnwrap-wbig' => q{La valeur d'avertissement doit être inférieure à 2 milliards},
- 'unknown-error' => q{erreur inconnue},
- 'usage' => qq{\nUsage: \$1 <options>\n Essayez « \$1 --help » pour liste complète des options\n\n},
- 'username' => q{nom utilisateur},
- 'vac-msg' => q{Base de données : $1 Table : $2},
- 'vac-nomatch-a' => q{Aucune des tables correspondantes n'a eu d'opération ANALYZE},
- 'vac-nomatch-v' => q{Aucune des tables correspondantes n'a eu d'opération VACUUM},
- 'version' => q{version $1},
- 'version-badmrtg' => q{Argument invalide pour la version de mrtg},
- 'version-fail' => q{version $1, alors que la version attendue est $2},
- 'version-ok' => q{version $1},
+ 'address' => q{adresse},
+ 'backends-fatal' => q{N'a pas pu se connecter : trop de connexions},
+ 'backends-mrtg' => q{DB=$1 Connexions maximum=$2},
+ 'backends-msg' => q{$1 connexions sur $2 ($3%)},
+ 'backends-nomax' => q{N'a pas pu déterminer max_connections},
+ 'backends-oknone' => q{Aucune connexion},
+ 'backends-po' => q{désolé, trop de clients sont déjà connectés},
+ 'backends-users' => q{$1 pour le nombre d'utilisateurs doit être un nombre ou un pourcentage},
+ 'bloat-index' => q{(db $1) index $2 lignes:$3 pages:$4 devrait être:$5 ($6X) octets perdus:$7 ($8)},
+ 'bloat-nomin' => q{aucune relation n'atteint le critère minimum de fragmentation},
+ 'bloat-table' => q{(db $1) table $2.$3 lignes:$4 pages:$5 devrait être:$6 ($7X) place perdue:$8 ($9)},
+ 'checkpoint-baddir' => q{data_directory invalide : "$1"},
+ 'checkpoint-baddir2' => q{pg_controldata n'a pas pu lire le répertoire des données indiqué : « $1 »},
+ 'checkpoint-badver' => q{Échec lors de l'exécution de pg_controldata - probablement la mauvaise version},
+ 'checkpoint-badver2' => q{Échec lors de l'exécution de pg_controldata - est-ce la bonne version ?},
+ 'checkpoint-nodir' => q{Vous devez fournir un argument --datadir ou configurer la variable d'environnement PGDATA},
+ 'checkpoint-nodp' => q{Vous devez installer le module Perl Date::Parse pour utiliser l'action checkpoint},
+ 'checkpoint-noparse' => q{Incapable d'analyser le résultat de la commande pg_controldata : "$1"},
+ 'checkpoint-noregex' => q{Échec de l'appel à pg_controldata $1},
+ 'checkpoint-nosys' => q{N'a pas pu appeler pg_controldata : $1},
+ 'checkpoint-ok' => q{Le dernier CHECKPOINT est survenu il y a une seconde},
+ 'checkpoint-ok2' => q{Le dernier CHECKPOINT est survenu il y a $1 secondes},
+ 'checkpoint-po' => q{Heure du dernier point de contr�le :},
+ 'checksum-badline' => q{Ligne pg_setting invalide : $1},
+ 'checksum-msg' => q{somme de contrôle : $1},
+ 'checksum-nomd' => q{Vous devez installer le module Perl Digest::MD5 pour utiliser l'action checksum},
+ 'checksum-nomrtg' => q{Vous devez fournir une somme de contrôle avec l'option --mrtg},
+ 'custom-invalid' => q{Format invalide renvoyé par la requête personnalisée},
+ 'custom-norows' => q{Aucune ligne renvoyée},
+ 'custom-nostring' => q{Vous devez fournir une requête},
+ 'database' => q{base de données},
+ '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},
+ '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},
+ 'die-badtime' => q{La valeur de « $1 » doit être une heure valide. Par exemple, -$2 1s -$2 « 10 minutes »},
+ 'die-badversion' => q{Version invalide : $1},
+ 'die-noset' => q{Ne peut pas exécuter « $1 » $2 n'est pas activé},
+ 'die-nosetting' => q{N'a pas pu récupérer le paramètre « $1 »},
+ 'diskspace-fail' => q{Résultat invalide pour la commande « $1 » : $2},
+ 'diskspace-msg' => q{Le système de fichiers $1 monté sur $2 utilise $3 sur $4 ($5%)},
+ 'diskspace-nodata' => q{N'a pas pu déterminer data_directory : êtes-vous connecté en tant que super-utilisateur ?},
+ 'diskspace-nodf' => q{N'a pas pu trouver l'exécutable /bin/df},
+ 'diskspace-nodir' => q{N'a pas pu trouver le répertoire des données « $1 »},
+ 'file-noclose' => q{N'a pas pu fermer $1 : $2},
+ 'fsm-page-highver' => q{Ne peut pas vérifier fsm_pages sur des serveurs en version 8.4 ou ultérieure},
+ 'fsm-page-msg' => q{emplacements de pages utilisés par la FSM : $1 sur $2 ($3%)},
+ 'fsm-rel-highver' => q{Ne peut pas vérifier fsm_relations sur des serveurs en version 8.4 ou ultérieure},
+ 'fsm-rel-msg' => q{relations tracées par la FSM : $1 sur $2 ($3%)},
+ 'invalid-option' => q{Option invalide},
+ 'invalid-query' => q{Une requête invalide a renvoyé : $1},
+ 'listener-count' => q{ en écoute=$1}, ## needs leading space
+ 'listener-msg' => q{processus LISTEN trouvés : $1},
+ 'locks-msg' => q{total des verrous « $1 » : $2},
+ 'locks-msg2' => q{total des verrous : $1},
+ 'logfile-bad' => q{Option logfile invalide « $1 »},
+ 'logfile-debug' => q{la destination est $1, le répertoire est $2, le fichier est $3, l'option facility est $4},
+ 'logfile-debug2' => q{Journal applicatif final : $1},
+ 'logfile-dne' => q{le journal applicatif $1 n'existe pas !},
+ 'logfile-fail' => q{échec pour tracer dans : $1},
+ 'logfile-ok' => q{trace dans : $1},
+ 'logfile-openfail' => q{échec pour l'ouverture du journal applicatif « $1 » : $2},
+ 'logfile-opt-bad' => q{Option logfile invalide},
+ 'logfile-seekfail' => q{Échec de la recherche dans $1 : $2},
+ 'logfile-stderr' => q{La sortie des traces a été redirigés stderr : merci de fournir un nom de fichier},
+ 'logfile-syslog' => q{La base de données utiliser syslog, merci de spécifier le chemin avec l'option --logfile (fac=$1)},
+ 'maxtime' => q{ maxtime=$1}, ## needs leading space
+ 'mrtg-fail' => q{Échec de l'action $1 : $2},
+ 'new-bc-badver' => q{N'a pas pu déterminer la version de Bucardo},
+ 'new-bc-fail' => q{N'a pas pu trouver la version actuelle pour Bucardo},
+ 'new-bc-ok' => q{La version $1 est la dernière pour Bucardo},
+ 'new-bc-warn' => q{Merci de mettre à jour vers la version $1 de Bucardo. Vous utilisez actuellement la $2},
+ 'new-cp-fail' => q{Incapable de déterminer la version actuelle de check_postgres.pl},
+ 'new-cp-ok' => q{La version $1 est la dernière pour check_postgres.pl},
+ 'new-cp-warn' => q{La version $1 de check_postgres.pl existe (ceci est la version $2)},
+ 'new-pg-badver' => q{N'a pas pu déterminer la révision de Postgres (la version était $1)},
+ 'new-pg-badver2' => q{N'a pas pu trouver l'information de révision de Posrgres version $1},
+ 'new-pg-big' => q{Veuillez mettre à jour Postgres vers la version $1. Vous utilisez actuellement la version $2},
+ 'new-pg-match' => q{Postgres est à la dernière révision ($1)},
+ 'new-pg-small' => q{La dernière version de Postgres est la $1, mais vous utilisez actuellement la version $2?},
+ 'no-match-db' => q{Aucune base de données trouvée à cause des options d'exclusion/inclusion},
+ 'no-match-fs' => q{Aucun système de fichier trouvé à cause des options d'exclusion/inclusion},
+ 'no-match-rel' => q{Aucune relation trouvée à cause des options d'exclusion/inclusion},
+ 'no-match-set' => q{Aucun paramètre trouvé à cause des options d'exclusion/inclusion},
+ 'no-match-table' => q{Aucune table trouvée à cause des options d'exclusion/inclusion},
+ 'no-match-user' => q{Aucune entrée trouvée à cause options d'exclusion/inclusion},
+ 'no-time-hires' => q{N'a pas trouvé le module Time::HiRes, nécessaire quand « showtime » est activé},
+ 'opt-output-invalid' => q{Sortie invalide : doit être 'nagios' ou 'mrtg' ou 'simple' ou 'cacti'},
+ 'opt-psql-badpath' => q{Argument invalide pour psql : doit être le chemin complet vers un fichier nommé psql},
+ 'opt-psql-noexec' => q{ Le fichier « $1 » ne paraît pas exécutable},
+ 'opt-psql-noexist' => q{Ne peut pas trouver l'exécutable psql indiqué : $1},
+ 'opt-psql-nofind' => q{N'a pas pu trouver un psql exécutable},
+ 'opt-psql-nover' => q{N'a pas pu déterminer la version de psql},
+ 'opt-psql-restrict' => q{Ne peut pas utiliser l'option --PSQL si NO_PSQL_OPTION est activé},
+ 'PID' => q{PID},
+ 'port' => q{port},
+ 'preptxn-none' => q{Aucune transaction préparée trouvée},
+ 'qtime-fail' => q{Ne peut pas exécuter l'action txn_idle si stats_command_string est désactivé !},
+ 'qtime-msg' => q{requête la plus longue : $1s},
+ 'qtime-nomatch' => q{Aucune entrée correspondante n'a été trouvée},
+ 'range-badcs' => q{Option « $1 » invalide : doit être une somme de contrôle},
+ 'range-badlock' => q{Option « $1 » invalide : doit être un nombre de verrou ou « type1=#;type2=# »},
+ 'range-badpercent' => q{Option « $1 » invalide : doit être un pourcentage},
+ 'range-badpercsize' => q{Option « $1 » invalide : doit être une taille ou un pourcentage},
+ 'range-badsize' => q{Taille invalide pour l'option « $1 »},
+ 'range-badtype' => q{validate_range appelé avec un type inconnu « $1 »},
+ 'range-badversion' => q{Chaîne invalide pour l'option « $1 » : $2},
+ 'range-cactionly' => q{Cette action est pour cacti seulement et ne prend pas les arguments warning et critical},
+ 'range-int' => q{Argument invalide pour l'option « $1 » : doit être un entier},
+ 'range-int-pos' => q{Argument invalide pour l'option « $1 » : doit être un entier positif},
+ 'range-neg-percent' => q{Ne peut pas indiquer un pourcentage négatif !},
+ 'range-none' => q{Les options warning et critical ne sont pas nécessaires},
+ 'range-noopt-both' => q{Doit fournir les options warning et critical},
+ 'range-noopt-one' => q{Doit fournir une option warning ou critical},
+ 'range-noopt-only' => q{Peut seulement fournir une option warning ou critical},
+ 'range-noopt-orboth' => q{Doit fournir une option warning, une option critical ou les deux},
+ 'range-noopt-size' => q{Doit fournir une taille warning et/ou critical},
+ 'range-nosize' => q{Doit fournir une taille warning et/ou critical},
+ 'range-notime' => q{Doit fournir une heure warning et/ou critical},
+ 'range-seconds' => q{Argument invalide pour l'option « $1 » : doit être un nombre de secondes},
+ 'range-version' => q{doit être dans le format X.Y ou X.Y.Z, où X est le numéro de version majeure, },
+ 'range-warnbig' => q{L'option warning ne peut pas être plus grand que l'option critical},
+ 'range-warnbigsize' => q{L'option warning ($1 octets) ne peut pas être plus grand que l'option critical ($2 octets)},
+ 'range-warnbigtime' => q{L'option warning ($1 s) ne peut pas être plus grand que l'option critical ($2 s)},
+ 'range-warnsmall' => q{L'option warningne peut pas être plus petit que l'option critical},
+ 'relsize-msg-ind' => q{le plus gros index est « $1 » : $2},
+ 'relsize-msg-reli' => q{la plus grosse relation est l'index « $1 » : $2},
+ 'relsize-msg-relt' => q{la plus grosse relation est la table « $1 » : $2},
+ 'relsize-msg-tab' => q{la plus grosse table est « $1 » : $2},
+ 'rep-badarg' => q{Argument repinfo invalide : 6 valeurs séparées par des virgules attendues},
+ 'rep-duh' => q{Aucun sens à tester la réplication avec les mêmes valeurs},
+ 'rep-fail' => q{Ligne non répliquée sur l'esclave $1},
+ 'rep-noarg' => q{A besoin d'un argument repinfo},
+ 'rep-norow' => q{Ligne source de la réplication introuvable : $1},
+ 'rep-noslaves' => q{Aucun esclave trouvé},
+ 'rep-notsame' => q{Ne peut pas tester la réplication : les valeurs ne sont pas identiques},
+ 'rep-ok' => q{La ligne a été répliquée},
+ 'rep-sourcefail' => q{Échec de la mise à jour de la source},
+ 'rep-timeout' => q{La ligne n'a pas été répliquée. Délai dépassé : $1},
+ 'rep-unknown' => q{Échec du test de la réplication},
+ 'rep-wrongvals' => q{Ne peut pas tester la réplication : les valeurs ne sont pas les bonnes (ni $1 ni $2 ni $3)},
+ 'runcommand-err' => q{Erreur inconnue de la fonction « run_command »},
+ 'runcommand-nodb' => q{Aucune base de données cible trouvée},
+ 'runcommand-nodupe' => q{N'a pas pu dupliqué STDERR},
+ 'runcommand-noerr' => q{N'a pas pu ouvrir STDERR},
+ 'runcommand-nosys' => q{Échec de l'appel système avec un $1},
+ 'runcommand-pgpass' => q{Création du fichier pgpass temporaire $1},
+ 'runcommand-timeout' => q{Délai épuisée pour la commande ! Essayez d'augmenter --timeout à une valeur plus importante que $1},
+ 'runtime-badmrtg' => q{queryname invalide ?},
+ 'runtime-badname' => q{Option invalide pour queryname option : doit être le nom d'une vue},
+ 'runtime-msg' => q{durée d'exécution de la requête : $1 secondes},
+ 'same-failed' => q{Les bases de données sont différentes. Éléments différents : $1},
+ 'same-matched' => q{Les bases de données ont les mêmes éléments},
+ 'slony-noschema' => q{N'a pas pu déterminer le schéma de Slony},
+ 'slony-nonumber' => q{L'appel à sl_status n'a pas renvoyé un numéro},
+ 'slony-noparse' => q{N'a pas pu analyser l'appel à sl_status},
+ 'slony-lagtime' => q{Durée de lag de Slony : $1},
+ 'seq-die' => q{N'a pas pu récupérer d'informations sur la séquence $1},
+ 'seq-msg' => q{$1=$2% (appels restant=$3)},
+ 'seq-none' => q{Aucune sequences trouvée},
+ 'symlink-create' => q{Création de « $1 »},
+ 'symlink-done' => q{Création impossible de « $1 »: $2 est déjà lié à "$3"},
+ 'symlink-exists' => q{Création impossible de « $1 »: le fichier $2 existe déjà},
+ 'symlink-fail1' => q{Échec de la suppression de « $1 » : $2},
+ 'symlink-fail2' => q{N'a pas pu supprimer le lien symbolique $1 vers $2 : $3},
+ 'symlink-name' => q{Cette commande ne fonctionnera pas sauf si le programme contient le mot « postgres »},
+ 'symlink-unlink' => q{Supression de « $1 » :$2 },
+ 'testmode-end' => q{FIN DU MODE DE TEST},
+ 'testmode-fail' => q{Échec de la connexion : $1 $2},
+ 'testmode-norun' => q{N'a pas pu exécuter « $1 » sur $2 : la version doit être supérieure ou égale à $3, mais est $4},
+ 'testmode-noset' => q{N'a pas pu exécuter « $1 » sur $2 : $3 n'est pas activé},
+ 'testmode-nover' => q{N'a pas pu trouver la version de $1},
+ 'testmode-ok' => q{Connexion OK : $1},
+ 'testmode-start' => q{DÉBUT DU MODE DE TEST},
+ 'time-day' => q{jour},
+ 'time-days' => q{jours},
+ 'time-hour' => q{heure},
+ 'time-hours' => q{heures},
+ 'time-minute' => q{minute},
+ 'time-minutes' => q{minutes},
+ 'time-month' => q{mois},
+ 'time-months' => q{mois},
+ 'time-second' => q{seconde},
+ 'time-seconds' => q{secondes},
+ 'time-week' => q{semaine},
+ 'time-weeks' => q{semaines},
+ 'time-year' => q{année},
+ 'time-years' => q{années},
+ 'timesync-diff' => q{ diff=$1}, ## needs leading space
+ 'timesync-msg' => q{timediff=$1 Base de données=$2 Local=$3},
+ 'trigger-msg' => q{Triggers désactivés : $1},
+ 'txnidle-msg' => q{transaction en attente la plus longue : $1s},
+ 'txnidle-none' => q{Aucun processus en attente dans une transaction},
+ 'txntime-fail' => q{Échec de la requête},
+ 'txntime-msg' => q{Transaction la plus longue : $1s},
+ 'txntime-none' => q{Aucune transaction},
+ 'txnwrap-cbig' => q{La valeur critique doit être inférieure à 2 milliards},
+ 'txnwrap-wbig' => q{La valeur d'avertissement doit être inférieure à 2 milliards},
+ 'unknown-error' => q{erreur inconnue},
+ 'usage' => qq{\nUsage: \$1 <options>\n Essayez « \$1 --help » pour liste complète des options\n\n},
+ 'username' => q{nom utilisateur},
+ 'vac-msg' => q{Base de données : $1 Table : $2},
+ 'vac-nomatch-a' => q{Aucune des tables correspondantes n'a eu d'opération ANALYZE},
+ 'vac-nomatch-v' => q{Aucune des tables correspondantes n'a eu d'opération VACUUM},
+ 'version' => q{version $1},
+ 'version-badmrtg' => q{Argument invalide pour la version de mrtg},
+ 'version-fail' => q{version $1, alors que la version attendue est $2},
+ 'version-ok' => q{version $1},
},
'af' => {
},
'cs' => {
- 'checkpoint-po' => q{�as posledn�ho kontroln�ho bodu:},
+ 'checkpoint-po' => q{�as posledn�ho kontroln�ho bodu:},
},
'de' => {
- 'backends-po' => q{tut mir leid, schon zu viele Verbindungen},
- 'checkpoint-po' => q{Zeit des letzten Checkpoints:},
+ 'backends-po' => q{tut mir leid, schon zu viele Verbindungen},
+ 'checkpoint-po' => q{Zeit des letzten Checkpoints:},
},
'es' => {
- 'backends-po' => q{lo siento, ya tenemos demasiados clientes},
- 'checkpoint-po' => q{Instante de �ltimo checkpoint:},
+ 'backends-po' => q{lo siento, ya tenemos demasiados clientes},
+ 'checkpoint-po' => q{Instante de �ltimo checkpoint:},
},
'fa' => {
- 'checkpoint-po' => q{زمان آخرین وارسی:},
+ 'checkpoint-po' => q{زمان آخرین وارسی:},
},
'hr' => {
- 'backends-po' => q{nažalost, već je otvoreno previše klijentskih veza},
+ 'backends-po' => q{nažalost, već je otvoreno previše klijentskih veza},
},
'hu' => {
- 'checkpoint-po' => q{A legut�bbi ellen�rz�pont ideje:},
+ 'checkpoint-po' => q{A legut�bbi ellen�rz�pont ideje:},
},
'it' => {
- 'checkpoint-po' => q{Orario ultimo checkpoint:},
+ 'checkpoint-po' => q{Orario ultimo checkpoint:},
},
'ja' => {
- 'backends-po' => q{現在クライアント数が多すぎます},
- 'checkpoint-po' => q{最終チェックポイント時刻:},
+ 'backends-po' => q{現在クライアント数が多すぎます},
+ 'checkpoint-po' => q{最終チェックポイント時刻:},
},
'ko' => {
- 'backends-po' => q{최대 동시 접속자 수를 초과했습니다.},
- 'checkpoint-po' => q{������ üũ����Ʈ �ð�:},
+ 'backends-po' => q{최대 동시 접속자 수를 초과했습니다.},
+ 'checkpoint-po' => q{������ üũ����Ʈ �ð�:},
},
'nb' => {
- 'backends-po' => q{beklager, for mange klienter},
- 'checkpoint-po' => q{Tidspunkt for nyeste kontrollpunkt:},
+ 'backends-po' => q{beklager, for mange klienter},
+ 'checkpoint-po' => q{Tidspunkt for nyeste kontrollpunkt:},
},
'nl' => {
},
'pl' => {
- 'checkpoint-po' => q{Czas najnowszego punktu kontrolnego:},
+ 'checkpoint-po' => q{Czas najnowszego punktu kontrolnego:},
},
'pt_BR' => {
- 'backends-po' => q{desculpe, muitos clientes conectados},
- 'checkpoint-po' => q{Hora do último ponto de controle:},
+ 'backends-po' => q{desculpe, muitos clientes conectados},
+ 'checkpoint-po' => q{Hora do último ponto de controle:},
},
'ro' => {
- 'checkpoint-po' => q{Timpul ultimului punct de control:},
+ 'checkpoint-po' => q{Timpul ultimului punct de control:},
},
'ru' => {
- 'backends-po' => q{��������, ��� ������� ����� ��������},
- 'checkpoint-po' => q{����� ��������� checkpoint:},
+ 'backends-po' => q{��������, ��� ������� ����� ��������},
+ 'checkpoint-po' => q{����� ��������� checkpoint:},
},
'sk' => {
- 'backends-po' => q{je mi ��to, je u� pr�li� ve�a klientov},
- 'checkpoint-po' => q{Čas posledného kontrolného bodu:},
+ 'backends-po' => q{je mi ��to, je u� pr�li� ve�a klientov},
+ 'checkpoint-po' => q{Čas posledného kontrolného bodu:},
},
'sl' => {
- 'backends-po' => q{povezanih je �e preve� odjemalcev},
- 'checkpoint-po' => q{�as zadnje kontrolne to�ke ............},
+ 'backends-po' => q{povezanih je �e preve� odjemalcev},
+ 'checkpoint-po' => q{�as zadnje kontrolne to�ke ............},
},
'sv' => {
- 'backends-po' => q{ledsen, f�r m�nga klienter},
- 'checkpoint-po' => q{Tidpunkt f�r senaste kontrollpunkt:},
+ 'backends-po' => q{ledsen, f�r m�nga klienter},
+ 'checkpoint-po' => q{Tidpunkt f�r senaste kontrollpunkt:},
},
'ta' => {
- 'checkpoint-po' => q{நவீன சோதனை மையத்தின் நேரம்:},
+ 'checkpoint-po' => q{நவீன சோதனை மையத்தின் நேரம்:},
},
'tr' => {
- 'backends-po' => q{üzgünüm, istemci sayısı çok fazla},
- 'checkpoint-po' => q{En son checkpoint'in zamanı:},
+ 'backends-po' => q{üzgünüm, istemci sayısı çok fazla},
+ 'checkpoint-po' => q{En son checkpoint'in zamanı:},
},
'zh_CN' => {
- 'backends-po' => q{�Բ���, �Ѿ���̫���Ŀͻ�},
- 'checkpoint-po' => q{���¼�������ʱ��:},
+ 'backends-po' => q{�Բ���, �Ѿ���̫���Ŀͻ�},
+ 'checkpoint-po' => q{���¼�������ʱ��:},
},
'zh_TW' => {
- 'backends-po' => q{對不起,用戶端過多},
- 'checkpoint-po' => q{最新的檢查點時間:},
+ 'backends-po' => q{對不起,用戶端過多},
+ 'checkpoint-po' => q{最新的檢查點時間:},
},
);
## use critic
## This option must come before the GetOptions call
for my $arg (@ARGV) {
- if ($arg eq '--no-check_postgresrc') {
- $opt{'no-check_postgresrc'} = 1;
- last;
- }
+ if ($arg eq '--no-check_postgresrc') {
+ $opt{'no-check_postgresrc'} = 1;
+ last;
+ }
}
my $rcfile;
if (! $opt{'no-check_postgresrc'}) {
- if (-e '.check_postgresrc') {
- $rcfile = '.check_postgresrc';
- }
- elsif (-e "$ENV{HOME}/.check_postgresrc") {
- $rcfile = "$ENV{HOME}/.check_postgresrc";
- }
- elsif (-e '/etc/check_postgresrc') {
- $rcfile = '/etc/check_postgresrc';
- }
+ if (-e '.check_postgresrc') {
+ $rcfile = '.check_postgresrc';
+ }
+ elsif (-e "$ENV{HOME}/.check_postgresrc") {
+ $rcfile = "$ENV{HOME}/.check_postgresrc";
+ }
+ elsif (-e '/etc/check_postgresrc') {
+ $rcfile = '/etc/check_postgresrc';
+ }
}
## We need a temporary hash so that multi-value options can be overridden on the command line
my %tempopt;
if (defined $rcfile) {
- open my $rc, '<', $rcfile or die qq{Could not open "$rcfile": $!\n};
- RCLINE:
- while (<$rc>) {
- next if /^\s*#/;
- next unless /^\s*(\w+)\s*=\s*(.+?)\s*$/o;
- my ($name,$value) = ($1,$2); ## no critic (ProhibitCaptureWithoutTest)
- ## Map alternate option spellings to preferred names
- if ($name eq 'dbport' or $name eq 'p' or $name eq 'dbport1' or $name eq 'p1' or $name eq 'port1') {
- $name = 'port';
- }
- elsif ($name eq 'dbhost' or $name eq 'H' or $name eq 'dbhost1' or $name eq 'H1' or $name eq 'host1') {
- $name = 'host';
- }
- elsif ($name eq 'db' or $name eq 'db1' or $name eq 'dbname1') {
- $name = 'dbname';
- }
- elsif ($name eq 'u' or $name eq 'u1' or $name eq 'dbuser1') {
- $name = 'dbuser';
- }
- if ($name eq 'dbport2' or $name eq 'p2') {
- $name = 'port2';
- }
- elsif ($name eq 'dbhost2' or $name eq 'H2') {
- $name = 'host2';
- }
- elsif ($name eq 'db2') {
- $name = 'dbname2';
- }
- elsif ($name eq 'u2') {
- $name = 'dbuser2';
- }
-
- ## These options are multiples ('@s')
- for my $arr (qw/include exclude includeuser excludeuser host port dbuser dbname dbpass dbservice/) {
- next if $name ne $arr and $name ne "${arr}2";
- push @{$tempopt{$name}} => $value;
- ## Don't set below as a normal value
- next RCLINE;
- }
- $opt{$name} = $value;
- }
- close $rc or die;
+ open my $rc, '<', $rcfile or die qq{Could not open "$rcfile": $!\n};
+ RCLINE:
+ while (<$rc>) {
+ next if /^\s*#/;
+ next unless /^\s*(\w+)\s*=\s*(.+?)\s*$/o;
+ my ($name,$value) = ($1,$2); ## no critic (ProhibitCaptureWithoutTest)
+ ## Map alternate option spellings to preferred names
+ if ($name eq 'dbport' or $name eq 'p' or $name eq 'dbport1' or $name eq 'p1' or $name eq 'port1') {
+ $name = 'port';
+ }
+ elsif ($name eq 'dbhost' or $name eq 'H' or $name eq 'dbhost1' or $name eq 'H1' or $name eq 'host1') {
+ $name = 'host';
+ }
+ elsif ($name eq 'db' or $name eq 'db1' or $name eq 'dbname1') {
+ $name = 'dbname';
+ }
+ elsif ($name eq 'u' or $name eq 'u1' or $name eq 'dbuser1') {
+ $name = 'dbuser';
+ }
+ if ($name eq 'dbport2' or $name eq 'p2') {
+ $name = 'port2';
+ }
+ elsif ($name eq 'dbhost2' or $name eq 'H2') {
+ $name = 'host2';
+ }
+ elsif ($name eq 'db2') {
+ $name = 'dbname2';
+ }
+ elsif ($name eq 'u2') {
+ $name = 'dbuser2';
+ }
+
+ ## These options are multiples ('@s')
+ for my $arr (qw/include exclude includeuser excludeuser host port dbuser dbname dbpass dbservice/) {
+ next if $name ne $arr and $name ne "${arr}2";
+ push @{$tempopt{$name}} => $value;
+ ## Don't set below as a normal value
+ next RCLINE;
+ }
+ $opt{$name} = $value;
+ }
+ close $rc or die;
}
die $USAGE unless
- GetOptions(
- \%opt,
- 'version|V',
- 'verbose|v+',
- 'help|h',
- 'man',
- 'output=s',
- 'simple',
- 'showperf=i',
- 'perflimit=i',
- 'showtime=i',
- 'timeout|t=i',
- 'test',
- 'symlinks',
- 'debugoutput=s',
- 'no-check_postgresrc',
-
- 'action=s',
- 'warning=s',
- 'critical=s',
- 'include=s@',
- 'exclude=s@',
- 'includeuser=s@',
- 'excludeuser=s@',
-
- 'host|dbhost|H|dbhost1|H1=s@',
- 'port|dbport|p|port1|dbport1|p1=s@',
- 'dbname|db|dbname1|db1=s@',
- 'dbuser|u|dbuser1|u1=s@',
- 'dbpass|dbpass1=s@',
- 'dbservice|dbservice1=s@',
-
- 'host2|dbhost2|H2=s@',
- 'port2|dbport2|p2=s@',
- 'dbname2|db2=s@',
- 'dbuser2|u2=s@',
- 'dbpass2=s@',
- 'dbservice2=s@',
-
- 'PSQL=s',
-
- 'tempdir=s',
- 'get_method=s',
- 'language=s',
- 'mrtg=s', ## used by MRTG checks only
- 'logfile=s', ## used by check_logfile only
- 'queryname=s', ## used by query_runtime only
- 'query=s', ## used by custom_query only
- 'valtype=s', ## used by custom_query only
- 'reverse', ## used by custom_query only
- 'repinfo=s', ## used by replicate_row only
- 'noidle', ## used by backends only
- 'datadir=s', ## used by checkpoint only
- 'schema=s', ## used by slony_status only
- )
- and keys %opt
- and ! @ARGV;
+ GetOptions(
+ \%opt,
+ 'version|V',
+ 'verbose|v+',
+ 'help|h',
+ 'man',
+ 'output=s',
+ 'simple',
+ 'showperf=i',
+ 'perflimit=i',
+ 'showtime=i',
+ 'timeout|t=i',
+ 'test',
+ 'symlinks',
+ 'debugoutput=s',
+ 'no-check_postgresrc',
+
+ 'action=s',
+ 'warning=s',
+ 'critical=s',
+ 'include=s@',
+ 'exclude=s@',
+ 'includeuser=s@',
+ 'excludeuser=s@',
+
+ 'host|dbhost|H|dbhost1|H1=s@',
+ 'port|dbport|p|port1|dbport1|p1=s@',
+ 'dbname|db|dbname1|db1=s@',
+ 'dbuser|u|dbuser1|u1=s@',
+ 'dbpass|dbpass1=s@',
+ 'dbservice|dbservice1=s@',
+
+ 'host2|dbhost2|H2=s@',
+ 'port2|dbport2|p2=s@',
+ 'dbname2|db2=s@',
+ 'dbuser2|u2=s@',
+ 'dbpass2=s@',
+ 'dbservice2=s@',
+
+ 'PSQL=s',
+
+ 'tempdir=s',
+ 'get_method=s',
+ 'language=s',
+ 'mrtg=s', ## used by MRTG checks only
+ 'logfile=s', ## used by check_logfile only
+ 'queryname=s', ## used by query_runtime only
+ 'query=s', ## used by custom_query only
+ 'valtype=s', ## used by custom_query only
+ 'reverse', ## used by custom_query only
+ 'repinfo=s', ## used by replicate_row only
+ 'noidle', ## used by backends only
+ 'datadir=s', ## used by checkpoint only
+ 'schema=s', ## used by slony_status only
+ )
+ and keys %opt
+ and ! @ARGV;
if ( $opt{man} ) {
- require Pod::Usage;
- Pod::Usage::pod2usage({-verbose => 2});
- exit;
+ require Pod::Usage;
+ Pod::Usage::pod2usage({-verbose => 2});
+ exit;
}
## Put multi-val options from check_postgresrc in place, only if no command-line args!
for my $mv (keys %tempopt) {
- $opt{$mv} ||= delete $tempopt{$mv};
+ $opt{$mv} ||= delete $tempopt{$mv};
}
our $VERBOSE = $opt{verbose} || 0;
## Allow the optimization of the get_methods list by an argument
if ($opt{get_method}) {
- my $found = 0;
- for my $meth (@get_methods) {
- if ($meth =~ /^$opt{get_method}/io) {
- @get_methods = ($meth);
- $found = 1;
- last;
- }
- }
- if (!$found) {
- print "Unknown value for get_method: $opt{get_method}\n";
- print "Valid choices are:\n";
- print (join "\n" => map { s/(\w+).*/$1/; $_ } @get_methods);
- print "\n";
- exit;
- }
+ my $found = 0;
+ for my $meth (@get_methods) {
+ if ($meth =~ /^$opt{get_method}/io) {
+ @get_methods = ($meth);
+ $found = 1;
+ last;
+ }
+ }
+ if (!$found) {
+ print "Unknown value for get_method: $opt{get_method}\n";
+ print "Valid choices are:\n";
+ print (join "\n" => map { s/(\w+).*/$1/; $_ } @get_methods);
+ print "\n";
+ exit;
+ }
}
## Allow the language to be changed by an explicit option
if ($opt{language}) {
- $lang = substr($opt{language},0,2);
+ $lang = substr($opt{language},0,2);
}
## Output the actual string returned by psql in the normal output
## then fall back to the default.
if (!$OUTPUT) {
- my $dir = getcwd;
- if ($dir =~ /(nagios|mrtg|simple|cacti)/io) {
- $OUTPUT = lc $1;
- }
- elsif ($opt{simple}) {
- $OUTPUT = 'simple';
- }
- else {
- $OUTPUT = $DEFAULT_OUTPUT;
- }
+ my $dir = getcwd;
+ if ($dir =~ /(nagios|mrtg|simple|cacti)/io) {
+ $OUTPUT = lc $1;
+ }
+ elsif ($opt{simple}) {
+ $OUTPUT = 'simple';
+ }
+ else {
+ $OUTPUT = $DEFAULT_OUTPUT;
+ }
}
## Extract transforms from the output
$opt{transform} = '';
if ($OUTPUT =~ /\b(kb|mb|gb|tb|eb)\b/) {
- $opt{transform} = uc $1;
+ $opt{transform} = uc $1;
}
if ($OUTPUT =~ /(nagios|mrtg|simple|cacti)/io) {
- $OUTPUT = lc $1;
+ $OUTPUT = lc $1;
}
## Check for a valid output setting
if ($OUTPUT ne 'nagios' and $OUTPUT ne 'mrtg' and $OUTPUT ne 'simple' and $OUTPUT ne 'cacti') {
- die msgn('opt-output-invalid');
+ die msgn('opt-output-invalid');
}
our $MRTG = ($OUTPUT eq 'mrtg' or $OUTPUT eq 'simple') ? 1 : 0;
## See if we need to invoke something based on our name
our $action = $opt{action} || '';
if ($ME =~ /check_postgres_(\w+)/) {
- $action = $1;
+ $action = $1;
}
$VERBOSE >= 3 and warn Dumper \%opt;
if ($opt{version}) {
- print qq{$ME2 version $VERSION\n};
- exit 0;
+ print qq{$ME2 version $VERSION\n};
+ exit 0;
}
## Quick hash to put normal action information in one place:
our $action_usage = '';
our $longname = 1;
for (keys %$action_info) {
- $longname = length($_) if length($_) > $longname;
+ $longname = length($_) if length($_) > $longname;
}
for (sort keys %$action_info) {
- $action_usage .= sprintf " %-*s - %s\n", 2+$longname, $_, $action_info->{$_}[1];
+ $action_usage .= sprintf " %-*s - %s\n", 2+$longname, $_, $action_info->{$_}[1];
}
if ($opt{help}) {
- print qq{Usage: $ME2 <options>
+ print qq{Usage: $ME2 <options>
Run various tests against one or more Postgres databases.
Returns with an exit code of 0 (success), 1 (warning), 2 (critical), or 3 (unknown)
This is version $VERSION.
};
- exit 0;
+ exit 0;
}
build_symlinks() if $opt{symlinks};
## Die if Time::HiRes is needed but not found
if ($opt{showtime}) {
- eval {
- require Time::HiRes;
- import Time::HiRes qw/gettimeofday tv_interval sleep/;
- };
- if ($@) {
- die msg('no-time-hires');
- }
+ eval {
+ require Time::HiRes;
+ import Time::HiRes qw/gettimeofday tv_interval sleep/;
+ };
+ if ($@) {
+ die msg('no-time-hires');
+ }
}
## We don't (usually) want to die, but want a graceful Nagios-like exit instead
sub ndie {
- eval { File::Temp::cleanup(); };
- my $msg = shift;
- chomp $msg;
- print "ERROR: $msg\n";
- exit 3;
+ eval { File::Temp::cleanup(); };
+ my $msg = shift;
+ chomp $msg;
+ print "ERROR: $msg\n";
+ exit 3;
}
sub msg { ## no critic
- my $name = shift || '?';
-
- my $msg = '';
-
- if (exists $msg{$lang}{$name}) {
- $msg = $msg{$lang}{$name};
- }
- elsif (exists $msg{'en'}{$name}) {
- $msg = $msg{'en'}{$name};
- }
- else {
- my $line = (caller)[2];
- die qq{Invalid message "$name" from line $line\n};
- }
-
- my $x=1;
- {
- my $val = $_[$x-1];
- $val = '?' if ! defined $val;
- last unless $msg =~ s/\$$x/$val/g;
- $x++;
- redo;
- }
- return $msg;
+ my $name = shift || '?';
+
+ my $msg = '';
+
+ if (exists $msg{$lang}{$name}) {
+ $msg = $msg{$lang}{$name};
+ }
+ elsif (exists $msg{'en'}{$name}) {
+ $msg = $msg{'en'}{$name};
+ }
+ else {
+ my $line = (caller)[2];
+ die qq{Invalid message "$name" from line $line\n};
+ }
+
+ my $x=1;
+ {
+ my $val = $_[$x-1];
+ $val = '?' if ! defined $val;
+ last unless $msg =~ s/\$$x/$val/g;
+ $x++;
+ redo;
+ }
+ return $msg;
} ## end of msg
sub msgn { ## no critic
- return msg(@_) . "\n";
+ return msg(@_) . "\n";
}
sub msg_en {
- my $name = shift || '?';
+ my $name = shift || '?';
- return $msg{'en'}{$name};
+ return $msg{'en'}{$name};
} ## end of msg_en
## Everything from here on out needs psql, so find and verify a working version:
if ($NO_PSQL_OPTION) {
- delete $opt{PSQL} and ndie msg('opt-psql-restrict');
+ delete $opt{PSQL} and ndie msg('opt-psql-restrict');
}
if (! defined $PSQL or ! length $PSQL) {
- if (exists $opt{PSQL}) {
- $PSQL = $opt{PSQL};
- $PSQL =~ m{^/[\w\d\/]*psql$} or ndie msg('opt-psql-badpath');
- -e $PSQL or ndie msg('opt-psql-noexist', $PSQL);
- }
- else {
- my $psql = $ENV{PGBINDIR} ? "$ENV{PGBINDIR}/psql" : 'psql';
- chomp($PSQL = qx{which $psql});
- $PSQL or ndie msg('opt-psql-nofind');
- }
+ if (exists $opt{PSQL}) {
+ $PSQL = $opt{PSQL};
+ $PSQL =~ m{^/[\w\d\/]*psql$} or ndie msg('opt-psql-badpath');
+ -e $PSQL or ndie msg('opt-psql-noexist', $PSQL);
+ }
+ else {
+ my $psql = $ENV{PGBINDIR} ? "$ENV{PGBINDIR}/psql" : 'psql';
+ chomp($PSQL = qx{which $psql});
+ $PSQL or ndie msg('opt-psql-nofind');
+ }
}
-x $PSQL or ndie msg('opt-psql-noexec', $PSQL);
$res = qx{$PSQL --version};
sub add_response {
- my ($type,$msg) = @_;
-
- $db->{host} ||= '';
-
- if (defined $opt{dbname2} and defined $opt{dbname2}->[0] and length $opt{dbname2}->[0]
- and $opt{dbname}->[0] ne $opt{dbname2}->[0]) {
- $db->{dbname} .= " => $opt{dbname2}->[0]";
- }
- if (defined $opt{host2} and defined $opt{host2}->[0] and length $opt{host2}->[0]
- and $opt{host}->[0] ne $opt{host2}->[0]) {
- $db->{host} .= " => $opt{host2}->[0]";
- }
- if (defined $opt{port2} and defined $opt{port2}->[0] and length $opt{port2}->[0]
- and $opt{port}->[0] ne $opt{port2}->[0]) {
- $db->{port} .= " => $opt{port2}->[0]) ";
- }
- if ($nohost) {
- push @{$type->{''}} => [$msg, length $nohost > 1 ? $nohost : ''];
- return;
- }
-
- my $dbservice = $db->{dbservice};
- my $dbname = $db->{dbname};
- my $header = sprintf q{%s%s%s},
- $action_info->{$action}[0] ? '' : (defined $dbservice and length $dbservice) ?
- qq{service=$dbservice } : qq{DB "$dbname" },
- $db->{host} eq '<none>' ? '' : qq{(host:$db->{host}) },
- defined $db->{port} ? ($db->{port} eq $opt{defaultport} ? '' : qq{(port=$db->{port}) }) : '';
- $header =~ s/\s+$//;
- my $perf = ($opt{showtime} and $db->{totaltime}) ? "time=$db->{totaltime}" : '';
- if ($db->{perf}) {
- $perf .= " $db->{perf}";
- }
- push @{$type->{$header}} => [$msg,$perf];
-
- return;
+ my ($type,$msg) = @_;
+
+ $db->{host} ||= '';
+
+ if (defined $opt{dbname2} and defined $opt{dbname2}->[0] and length $opt{dbname2}->[0]
+ and $opt{dbname}->[0] ne $opt{dbname2}->[0]) {
+ $db->{dbname} .= " => $opt{dbname2}->[0]";
+ }
+ if (defined $opt{host2} and defined $opt{host2}->[0] and length $opt{host2}->[0]
+ and $opt{host}->[0] ne $opt{host2}->[0]) {
+ $db->{host} .= " => $opt{host2}->[0]";
+ }
+ if (defined $opt{port2} and defined $opt{port2}->[0] and length $opt{port2}->[0]
+ and $opt{port}->[0] ne $opt{port2}->[0]) {
+ $db->{port} .= " => $opt{port2}->[0]) ";
+ }
+ if ($nohost) {
+ push @{$type->{''}} => [$msg, length $nohost > 1 ? $nohost : ''];
+ return;
+ }
+
+ my $dbservice = $db->{dbservice};
+ my $dbname = $db->{dbname};
+ my $header = sprintf q{%s%s%s},
+ $action_info->{$action}[0] ? '' : (defined $dbservice and length $dbservice) ?
+ qq{service=$dbservice } : qq{DB "$dbname" },
+ $db->{host} eq '<none>' ? '' : qq{(host:$db->{host}) },
+ defined $db->{port} ? ($db->{port} eq $opt{defaultport} ? '' : qq{(port=$db->{port}) }) : '';
+ $header =~ s/\s+$//;
+ my $perf = ($opt{showtime} and $db->{totaltime}) ? "time=$db->{totaltime}" : '';
+ if ($db->{perf}) {
+ $perf .= " $db->{perf}";
+ }
+ push @{$type->{$header}} => [$msg,$perf];
+
+ return;
} ## end of add_response
sub add_unknown {
- my $msg = shift || $db->{error};
- $msg =~ s/[\r\n]\s*/\\n /g;
- $msg =~ s/\|/<PIPE>/g if $opt{showperf};
- add_response \%unknown, $msg;
+ my $msg = shift || $db->{error};
+ $msg =~ s/[\r\n]\s*/\\n /g;
+ $msg =~ s/\|/<PIPE>/g if $opt{showperf};
+ add_response \%unknown, $msg;
}
sub add_critical {
- add_response \%critical, shift;
+ add_response \%critical, shift;
}
sub add_warning {
- add_response \%warning, shift;
+ add_response \%warning, shift;
}
sub add_ok {
- add_response \%ok, shift;
+ add_response \%ok, shift;
}
sub do_mrtg {
- ## Hashref of info to pass out for MRTG or stat
- my $arg = shift;
- my $one = $arg->{one} || 0;
- my $two = $arg->{two} || 0;
- if ($SIMPLE) {
- $one = $two if (length $two and $two > $one);
- if ($opt{transform} eq 'KB' and $one =~ /^\d+$/) {
- $one = int $one/(1024);
- }
- if ($opt{transform} eq 'MB' and $one =~ /^\d+$/) {
- $one = int $one/(1024*1024);
- }
- elsif ($opt{transform} eq 'GB' and $one =~ /^\d+$/) {
- $one = int $one/(1024*1024*1024);
- }
- elsif ($opt{transform} eq 'TB' and $one =~ /^\d+$/) {
- $one = int $one/(1024*1024*1024*1024);
- }
- elsif ($opt{transform} eq 'EB' and $one =~ /^\d+$/) {
- $one = int $one/(1024*1024*1024*1024*1024);
- }
- print "$one\n";
- }
- else {
- my $uptime = $arg->{uptime} || '';
- my $message = $arg->{msg} || '';
- print "$one\n$two\n$uptime\n$message\n";
- }
- exit 0;
+ ## Hashref of info to pass out for MRTG or stat
+ my $arg = shift;
+ my $one = $arg->{one} || 0;
+ my $two = $arg->{two} || 0;
+ if ($SIMPLE) {
+ $one = $two if (length $two and $two > $one);
+ if ($opt{transform} eq 'KB' and $one =~ /^\d+$/) {
+ $one = int $one/(1024);
+ }
+ if ($opt{transform} eq 'MB' and $one =~ /^\d+$/) {
+ $one = int $one/(1024*1024);
+ }
+ elsif ($opt{transform} eq 'GB' and $one =~ /^\d+$/) {
+ $one = int $one/(1024*1024*1024);
+ }
+ elsif ($opt{transform} eq 'TB' and $one =~ /^\d+$/) {
+ $one = int $one/(1024*1024*1024*1024);
+ }
+ elsif ($opt{transform} eq 'EB' and $one =~ /^\d+$/) {
+ $one = int $one/(1024*1024*1024*1024*1024);
+ }
+ print "$one\n";
+ }
+ else {
+ my $uptime = $arg->{uptime} || '';
+ my $message = $arg->{msg} || '';
+ print "$one\n$two\n$uptime\n$message\n";
+ }
+ exit 0;
}
sub bad_mrtg {
- my $msg = shift;
- $ERROR and ndie $ERROR;
- warn msgn('mrtg-fail', $action, $msg);
- exit 3;
+ my $msg = shift;
+ $ERROR and ndie $ERROR;
+ warn msgn('mrtg-fail', $action, $msg);
+ exit 3;
}
sub do_mrtg_stats {
- ## Show the two highest items for mrtg stats hash
-
- my $msg = shift || msg('unknown-error');
-
- keys %stats or bad_mrtg($msg);
- my ($one,$two) = ('','');
- for (sort { $stats{$b} <=> $stats{$a} } keys %stats) {
- if ($one eq '') {
- $one = $stats{$_};
- $msg = exists $statsmsg{$_} ? $statsmsg{$_} : "DB: $_";
- next;
- }
- $two = $stats{$_};
- last;
- }
- do_mrtg({one => $one, two => $two, msg => $msg});
+ ## Show the two highest items for mrtg stats hash
+
+ my $msg = shift || msg('unknown-error');
+
+ keys %stats or bad_mrtg($msg);
+ my ($one,$two) = ('','');
+ for (sort { $stats{$b} <=> $stats{$a} } keys %stats) {
+ if ($one eq '') {
+ $one = $stats{$_};
+ $msg = exists $statsmsg{$_} ? $statsmsg{$_} : "DB: $_";
+ next;
+ }
+ $two = $stats{$_};
+ last;
+ }
+ do_mrtg({one => $one, two => $two, msg => $msg});
}
sub finishup {
- ## Final output
- ## These are meant to be compact and terse: sometimes messages go to pagers
-
- $MRTG and do_mrtg_stats();
-
- $action =~ s/^\s*(\S+)\s*$/$1/;
- my $service = sprintf "%s$action", $FANCYNAME ? 'postgres_' : '';
- if (keys %critical or keys %warning or keys %ok or keys %unknown) {
- printf '%s ', $YELLNAME ? uc $service : $service;
- }
-
- sub dumpresult {
- my ($type,$info) = @_;
- my $SEP = ' * ';
- ## Are we showing DEBUG_INFO?
- my $showdebug = 0;
- if ($DEBUGOUTPUT) {
- $showdebug = 1 if $DEBUGOUTPUT =~ /a/io
- or ($DEBUGOUTPUT =~ /c/io and $type eq 'c')
- or ($DEBUGOUTPUT =~ /w/io and $type eq 'w')
- or ($DEBUGOUTPUT =~ /o/io and $type eq 'o')
- or ($DEBUGOUTPUT =~ /u/io and $type eq 'u');
- }
- for (sort keys %$info) {
- printf "$_ %s%s ",
- $showdebug ? "[DEBUG: $DEBUG_INFO] " : '',
- join $SEP => map { $_->[0] } @{$info->{$_}};
- }
- if ($opt{showperf}) {
- print '| ';
- for (sort keys %$info) {
- my $m = sprintf '%s ', join $SEP => map { $_->[1] } @{$info->{$_}};
- if ($VERBOSE) {
- $m =~ s/ /\n/g;
- }
- print $m;
- }
- }
- print "\n";
- }
-
- if (keys %critical) {
- print 'CRITICAL: ';
- dumpresult(c => \%critical);
- exit 2;
- }
- if (keys %warning) {
- print 'WARNING: ';
- dumpresult(w => \%warning);
- exit 1;
- }
- if (keys %ok) {
- print 'OK: ';
- dumpresult(o => \%ok);
- exit 0;
- }
- if (keys %unknown) {
- print 'UNKNOWN: ';
- dumpresult(u => \%unknown);
- exit 3;
- }
-
- die $USAGE;
+ ## Final output
+ ## These are meant to be compact and terse: sometimes messages go to pagers
+
+ $MRTG and do_mrtg_stats();
+
+ $action =~ s/^\s*(\S+)\s*$/$1/;
+ my $service = sprintf "%s$action", $FANCYNAME ? 'postgres_' : '';
+ if (keys %critical or keys %warning or keys %ok or keys %unknown) {
+ printf '%s ', $YELLNAME ? uc $service : $service;
+ }
+
+ sub dumpresult {
+ my ($type,$info) = @_;
+ my $SEP = ' * ';
+ ## Are we showing DEBUG_INFO?
+ my $showdebug = 0;
+ if ($DEBUGOUTPUT) {
+ $showdebug = 1 if $DEBUGOUTPUT =~ /a/io
+ or ($DEBUGOUTPUT =~ /c/io and $type eq 'c')
+ or ($DEBUGOUTPUT =~ /w/io and $type eq 'w')
+ or ($DEBUGOUTPUT =~ /o/io and $type eq 'o')
+ or ($DEBUGOUTPUT =~ /u/io and $type eq 'u');
+ }
+ for (sort keys %$info) {
+ printf "$_ %s%s ",
+ $showdebug ? "[DEBUG: $DEBUG_INFO] " : '',
+ join $SEP => map { $_->[0] } @{$info->{$_}};
+ }
+ if ($opt{showperf}) {
+ print '| ';
+ for (sort keys %$info) {
+ my $m = sprintf '%s ', join $SEP => map { $_->[1] } @{$info->{$_}};
+ if ($VERBOSE) {
+ $m =~ s/ /\n/g;
+ }
+ print $m;
+ }
+ }
+ print "\n";
+ }
+
+ if (keys %critical) {
+ print 'CRITICAL: ';
+ dumpresult(c => \%critical);
+ exit 2;
+ }
+ if (keys %warning) {
+ print 'WARNING: ';
+ dumpresult(w => \%warning);
+ exit 1;
+ }
+ if (keys %ok) {
+ print 'OK: ';
+ dumpresult(o => \%ok);
+ exit 0;
+ }
+ if (keys %unknown) {
+ print 'UNKNOWN: ';
+ dumpresult(u => \%unknown);
+ exit 3;
+ }
+
+ die $USAGE;
} ## end of finishup
## If in test mode, verify that we can run each requested action
our %testaction = (
- autovac_freeze => 'VERSION: 8.2',
- last_vacuum => 'ON: stats_row_level(<8.3) VERSION: 8.2',
- last_analyze => 'ON: stats_row_level(<8.3) VERSION: 8.2',
- last_autovacuum => 'ON: stats_row_level(<8.3) VERSION: 8.2',
- last_autoanalyze => 'ON: stats_row_level(<8.3) VERSION: 8.2',
- prepared_txns => 'VERSION: 8.1',
- database_size => 'VERSION: 8.1',
- disabled_triggers => 'VERSION: 8.1',
- relation_size => 'VERSION: 8.1',
- sequence => 'VERSION: 8.1',
- table_size => 'VERSION: 8.1',
- index_size => 'VERSION: 8.1',
- query_time => 'ON: stats_command_string(<8.3) VERSION: 8.0',
- txn_idle => 'ON: stats_command_string(<8.3) VERSION: 8.0',
- txn_time => 'VERSION: 8.3',
- wal_files => 'VERSION: 8.1',
- fsm_pages => 'VERSION: 8.2 MAX: 8.3',
- fsm_relations => 'VERSION: 8.2 MAX: 8.3',
+ autovac_freeze => 'VERSION: 8.2',
+ last_vacuum => 'ON: stats_row_level(<8.3) VERSION: 8.2',
+ last_analyze => 'ON: stats_row_level(<8.3) VERSION: 8.2',
+ last_autovacuum => 'ON: stats_row_level(<8.3) VERSION: 8.2',
+ last_autoanalyze => 'ON: stats_row_level(<8.3) VERSION: 8.2',
+ prepared_txns => 'VERSION: 8.1',
+ database_size => 'VERSION: 8.1',
+ disabled_triggers => 'VERSION: 8.1',
+ relation_size => 'VERSION: 8.1',
+ sequence => 'VERSION: 8.1',
+ table_size => 'VERSION: 8.1',
+ index_size => 'VERSION: 8.1',
+ query_time => 'ON: stats_command_string(<8.3) VERSION: 8.0',
+ txn_idle => 'ON: stats_command_string(<8.3) VERSION: 8.0',
+ txn_time => 'VERSION: 8.3',
+ wal_files => 'VERSION: 8.1',
+ fsm_pages => 'VERSION: 8.2 MAX: 8.3',
+ fsm_relations => 'VERSION: 8.2 MAX: 8.3',
);
if ($opt{test}) {
- print msgn('testmode-start');
- my $info = run_command('SELECT name, setting FROM pg_settings');
- my %set; ## port, host, name, user
- for my $db (@{$info->{db}}) {
- if (exists $db->{fail}) {
- (my $err = $db->{error}) =~ s/\s*\n\s*/ \| /g;
- print msgn('testmode-fail', $db->{pname}, $err);
- next;
- }
- print msgn('testmode-ok', $db->{pname});
- for (split /\n/ => $db->{slurp}) {
- while (/(\S+)\s*\|\s*(.+)\s*/sg) { ## no critic (ProhibitUnusedCapture)
- $set{$db->{pname}}{$1} = $2;
- }
- }
- }
- for my $ac (split /\s+/ => $action) {
- my $limit = $testaction{lc $ac};
- next if ! defined $limit;
-
- if ($limit =~ /VERSION: ((\d+)\.(\d+))/) {
- my ($rver,$rmaj,$rmin) = ($1,$2,$3);
- for my $db (@{$info->{db}}) {
- next unless exists $db->{ok};
- if ($set{$db->{pname}}{server_version} !~ /((\d+)\.(\d+))/) {
- print msgn('testmode-nover', $db->{pname});
- next;
- }
- my ($sver,$smaj,$smin) = ($1,$2,$3);
- if ($smaj < $rmaj or ($smaj==$rmaj and $smin < $rmin)) {
- print msgn('testmode-norun', $ac, $db->{pname}, $rver, $sver);
- }
- $db->{version} = $sver;
- }
- }
-
- if ($limit =~ /MAX: ((\d+)\.(\d+))/) {
- my ($rver,$rmaj,$rmin) = ($1,$2,$3);
- for my $db (@{$info->{db}}) {
- next unless exists $db->{ok};
- if ($set{$db->{pname}}{server_version} !~ /((\d+)\.(\d+))/) {
- print msgn('testmode-nover', $db->{pname});
- next;
- }
- my ($sver,$smaj,$smin) = ($1,$2,$3);
- if ($smaj > $rmaj) {
- print msgn('testmode-norun', $ac, $db->{pname}, $rver, $sver);
- }
- }
- }
-
- while ($limit =~ /\bON: (\w+)(?:\(([<>=])(\d+\.\d+)\))?/g) {
- my ($setting,$op,$ver) = ($1,$2||'',$3||0);
- for my $db (@{$info->{db}}) {
- next unless exists $db->{ok};
- if ($ver) {
- next if $op eq '<' and $db->{version} >= $ver;
- next if $op eq '>' and $db->{version} <= $ver;
- next if $op eq '=' and $db->{version} != $ver;
- }
- my $val = $set{$db->{pname}}{$setting};
- if ($val ne 'on') {
- print msgn('testmode-noset', $ac, $db->{pname}, $setting);
- }
- }
- }
- }
- print msgn('testmode-end');
- exit 0;
+ print msgn('testmode-start');
+ my $info = run_command('SELECT name, setting FROM pg_settings');
+ my %set; ## port, host, name, user
+ for my $db (@{$info->{db}}) {
+ if (exists $db->{fail}) {
+ (my $err = $db->{error}) =~ s/\s*\n\s*/ \| /g;
+ print msgn('testmode-fail', $db->{pname}, $err);
+ next;
+ }
+ print msgn('testmode-ok', $db->{pname});
+ for (split /\n/ => $db->{slurp}) {
+ while (/(\S+)\s*\|\s*(.+)\s*/sg) { ## no critic (ProhibitUnusedCapture)
+ $set{$db->{pname}}{$1} = $2;
+ }
+ }
+ }
+ for my $ac (split /\s+/ => $action) {
+ my $limit = $testaction{lc $ac};
+ next if ! defined $limit;
+
+ if ($limit =~ /VERSION: ((\d+)\.(\d+))/) {
+ my ($rver,$rmaj,$rmin) = ($1,$2,$3);
+ for my $db (@{$info->{db}}) {
+ next unless exists $db->{ok};
+ if ($set{$db->{pname}}{server_version} !~ /((\d+)\.(\d+))/) {
+ print msgn('testmode-nover', $db->{pname});
+ next;
+ }
+ my ($sver,$smaj,$smin) = ($1,$2,$3);
+ if ($smaj < $rmaj or ($smaj==$rmaj and $smin < $rmin)) {
+ print msgn('testmode-norun', $ac, $db->{pname}, $rver, $sver);
+ }
+ $db->{version} = $sver;
+ }
+ }
+
+ if ($limit =~ /MAX: ((\d+)\.(\d+))/) {
+ my ($rver,$rmaj,$rmin) = ($1,$2,$3);
+ for my $db (@{$info->{db}}) {
+ next unless exists $db->{ok};
+ if ($set{$db->{pname}}{server_version} !~ /((\d+)\.(\d+))/) {
+ print msgn('testmode-nover', $db->{pname});
+ next;
+ }
+ my ($sver,$smaj,$smin) = ($1,$2,$3);
+ if ($smaj > $rmaj) {
+ print msgn('testmode-norun', $ac, $db->{pname}, $rver, $sver);
+ }
+ }
+ }
+
+ while ($limit =~ /\bON: (\w+)(?:\(([<>=])(\d+\.\d+)\))?/g) {
+ my ($setting,$op,$ver) = ($1,$2||'',$3||0);
+ for my $db (@{$info->{db}}) {
+ next unless exists $db->{ok};
+ if ($ver) {
+ next if $op eq '<' and $db->{version} >= $ver;
+ next if $op eq '>' and $db->{version} <= $ver;
+ next if $op eq '=' and $db->{version} != $ver;
+ }
+ my $val = $set{$db->{pname}}{$setting};
+ if ($val ne 'on') {
+ print msgn('testmode-noset', $ac, $db->{pname}, $setting);
+ }
+ }
+ }
+ }
+ print msgn('testmode-end');
+ exit 0;
}
## Expand the list of included/excluded users into a standard format
our $USERWHERECLAUSE = '';
if ($opt{includeuser}) {
- my %userlist;
- for my $user (@{$opt{includeuser}}) {
- for my $u2 (split /,/ => $user) {
- $userlist{$u2}++;
- }
- }
- my $safename;
- if (1 == keys %userlist) {
- ($safename = each %userlist) =~ s/'/''/g;
- $USERWHERECLAUSE = " AND usename = '$safename'";
- }
- else {
- $USERWHERECLAUSE = ' AND usename IN (';
- for my $user (sort keys %userlist) {
- ($safename = $user) =~ s/'/''/g;
- $USERWHERECLAUSE .= "'$safename',";
- }
- chop $USERWHERECLAUSE;
- $USERWHERECLAUSE .= ')';
- }
+ my %userlist;
+ for my $user (@{$opt{includeuser}}) {
+ for my $u2 (split /,/ => $user) {
+ $userlist{$u2}++;
+ }
+ }
+ my $safename;
+ if (1 == keys %userlist) {
+ ($safename = each %userlist) =~ s/'/''/g;
+ $USERWHERECLAUSE = " AND usename = '$safename'";
+ }
+ else {
+ $USERWHERECLAUSE = ' AND usename IN (';
+ for my $user (sort keys %userlist) {
+ ($safename = $user) =~ s/'/''/g;
+ $USERWHERECLAUSE .= "'$safename',";
+ }
+ chop $USERWHERECLAUSE;
+ $USERWHERECLAUSE .= ')';
+ }
}
elsif ($opt{excludeuser}) {
- my %userlist;
- for my $user (@{$opt{excludeuser}}) {
- for my $u2 (split /,/ => $user) {
- $userlist{$u2}++;
- }
- }
- my $safename;
- if (1 == keys %userlist) {
- ($safename = each %userlist) =~ s/'/''/g;
- $USERWHERECLAUSE = " AND usename <> '$safename'";
- }
- else {
- $USERWHERECLAUSE = ' AND usename NOT IN (';
- for my $user (sort keys %userlist) {
- ($safename = $user) =~ s/'/''/g;
- $USERWHERECLAUSE .= "'$safename',";
- }
- chop $USERWHERECLAUSE;
- $USERWHERECLAUSE .= ')';
- }
+ my %userlist;
+ for my $user (@{$opt{excludeuser}}) {
+ for my $u2 (split /,/ => $user) {
+ $userlist{$u2}++;
+ }
+ }
+ my $safename;
+ if (1 == keys %userlist) {
+ ($safename = each %userlist) =~ s/'/''/g;
+ $USERWHERECLAUSE = " AND usename <> '$safename'";
+ }
+ else {
+ $USERWHERECLAUSE = ' AND usename NOT IN (';
+ for my $user (sort keys %userlist) {
+ ($safename = $user) =~ s/'/''/g;
+ $USERWHERECLAUSE .= "'$safename',";
+ }
+ chop $USERWHERECLAUSE;
+ $USERWHERECLAUSE .= ')';
+ }
}
## Check number of connections, compare to max_connections
sub build_symlinks {
- ## Create symlinks to most actions
- $ME =~ /postgres/
- or die msgn('symlink-name');
-
- my $force = $action =~ /force/ ? 1 : 0;
- for my $action (sort keys %$action_info) {
- my $space = ' ' x ($longname - length $action);
- my $file = "check_postgres_$action";
- if (-l $file) {
- if (!$force) {
- my $source = readlink $file;
- print msgn('symlink-done', $file, $space, $source);
- next;
- }
- print msg('symlink-unlink', $file, $space);
- unlink $file or die msgn('symlink-fail1', $file, $!);
- }
- elsif (-e $file) {
- print msgn('symlink-exists', $file, $space);
- next;
- }
-
- if (symlink $0, $file) {
- print msgn('symlink-create', $file);
- }
- else {
- print msgn('symlink-fail2', $file, $ME, $!);
- }
- }
-
- exit 0;
+ ## Create symlinks to most actions
+ $ME =~ /postgres/
+ or die msgn('symlink-name');
+
+ my $force = $action =~ /force/ ? 1 : 0;
+ for my $action (sort keys %$action_info) {
+ my $space = ' ' x ($longname - length $action);
+ my $file = "check_postgres_$action";
+ if (-l $file) {
+ if (!$force) {
+ my $source = readlink $file;
+ print msgn('symlink-done', $file, $space, $source);
+ next;
+ }
+ print msg('symlink-unlink', $file, $space);
+ unlink $file or die msgn('symlink-fail1', $file, $!);
+ }
+ elsif (-e $file) {
+ print msgn('symlink-exists', $file, $space);
+ next;
+ }
+
+ if (symlink $0, $file) {
+ print msgn('symlink-create', $file);
+ }
+ else {
+ print msgn('symlink-fail2', $file, $ME, $!);
+ }
+ }
+
+ exit 0;
} ## end of build_symlinks
sub pretty_size {
- ## Transform number of bytes to a SI display similar to Postgres' format
+ ## Transform number of bytes to a SI display similar to Postgres' format
- my $bytes = shift;
- my $rounded = shift || 0;
+ my $bytes = shift;
+ my $rounded = shift || 0;
- return "$bytes bytes" if $bytes < 10240;
+ return "$bytes bytes" if $bytes < 10240;
- my @unit = qw/kB MB GB TB PB EB YB ZB/;
+ my @unit = qw/kB MB GB TB PB EB YB ZB/;
- for my $p (1..@unit) {
- if ($bytes <= 1024**$p) {
- $bytes /= (1024**($p-1));
- return $rounded ?
- sprintf ('%d %s', $bytes, $unit[$p-2]) :
- sprintf ('%.2f %s', $bytes, $unit[$p-2]);
- }
- }
+ for my $p (1..@unit) {
+ if ($bytes <= 1024**$p) {
+ $bytes /= (1024**($p-1));
+ return $rounded ?
+ sprintf ('%d %s', $bytes, $unit[$p-2]) :
+ sprintf ('%.2f %s', $bytes, $unit[$p-2]);
+ }
+ }
- return $bytes;
+ return $bytes;
} ## end of pretty_size
sub pretty_time {
- ## Transform number of seconds to a more human-readable format
- ## First argument is number of seconds
- ## Second optional arg is highest transform: s,m,h,d,w
- ## If uppercase, it indicates to "round that one out"
-
- my $sec = shift;
- my $tweak = shift || '';
-
- ## Just seconds (< 2:00)
- if ($sec < 120 or $tweak =~ /s/) {
- return sprintf "$sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
- }
-
- ## Minutes and seconds (< 60:00)
- if ($sec < 60*60 or $tweak =~ /m/) {
- my $min = int $sec / 60;
- $sec %= 60;
- my $ret = sprintf "$min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
- $sec and $tweak !~ /S/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
- return $ret;
- }
-
- ## Hours, minutes, and seconds (< 48:00:00)
- if ($sec < 60*60*24*2 or $tweak =~ /h/) {
- my $hour = int $sec / (60*60);
- $sec -= ($hour*60*60);
- my $min = int $sec / 60;
- $sec -= ($min*60);
- my $ret = sprintf "$hour %s", $hour==1 ? msg('time-hour') : msg('time-hours');
- $min and $tweak !~ /M/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
- $sec and $tweak !~ /[SM]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
- return $ret;
- }
-
- ## Days, hours, minutes, and seconds (< 28 days)
- if ($sec < 60*60*24*28 or $tweak =~ /d/) {
- my $day = int $sec / (60*60*24);
- $sec -= ($day*60*60*24);
- my $our = int $sec / (60*60);
- $sec -= ($our*60*60);
- my $min = int $sec / 60;
- $sec -= ($min*60);
- my $ret = sprintf "$day %s", $day==1 ? msg('time-day') : msg('time-days');
- $our and $tweak !~ /H/ and $ret .= sprintf " $our %s", $our==1 ? msg('time-hour') : msg('time-hours');
- $min and $tweak !~ /[HM]/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
- $sec and $tweak !~ /[HMS]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
- return $ret;
- }
-
- ## Weeks, days, hours, minutes, and seconds (< 28 days)
- my $week = int $sec / (60*60*24*7);
- $sec -= ($week*60*60*24*7);
- my $day = int $sec / (60*60*24);
- $sec -= ($day*60*60*24);
- my $our = int $sec / (60*60);
- $sec -= ($our*60*60);
- my $min = int $sec / 60;
- $sec -= ($min*60);
- my $ret = sprintf "$week %s", $week==1 ? msg('time-week') : msg('time-weeks');
- $day and $tweak !~ /D/ and $ret .= sprintf " $day %s", $day==1 ? msg('time-day') : msg('time-days');
- $our and $tweak !~ /[DH]/ and $ret .= sprintf " $our %s", $our==1 ? msg('time-hour') : msg('time-hours');
- $min and $tweak !~ /[DHM]/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
- $sec and $tweak !~ /[DHMS]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
- return $ret;
+ ## Transform number of seconds to a more human-readable format
+ ## First argument is number of seconds
+ ## Second optional arg is highest transform: s,m,h,d,w
+ ## If uppercase, it indicates to "round that one out"
+
+ my $sec = shift;
+ my $tweak = shift || '';
+
+ ## Just seconds (< 2:00)
+ if ($sec < 120 or $tweak =~ /s/) {
+ return sprintf "$sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+ }
+
+ ## Minutes and seconds (< 60:00)
+ if ($sec < 60*60 or $tweak =~ /m/) {
+ my $min = int $sec / 60;
+ $sec %= 60;
+ my $ret = sprintf "$min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
+ $sec and $tweak !~ /S/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+ return $ret;
+ }
+
+ ## Hours, minutes, and seconds (< 48:00:00)
+ if ($sec < 60*60*24*2 or $tweak =~ /h/) {
+ my $hour = int $sec / (60*60);
+ $sec -= ($hour*60*60);
+ my $min = int $sec / 60;
+ $sec -= ($min*60);
+ my $ret = sprintf "$hour %s", $hour==1 ? msg('time-hour') : msg('time-hours');
+ $min and $tweak !~ /M/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
+ $sec and $tweak !~ /[SM]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+ return $ret;
+ }
+
+ ## Days, hours, minutes, and seconds (< 28 days)
+ if ($sec < 60*60*24*28 or $tweak =~ /d/) {
+ my $day = int $sec / (60*60*24);
+ $sec -= ($day*60*60*24);
+ my $our = int $sec / (60*60);
+ $sec -= ($our*60*60);
+ my $min = int $sec / 60;
+ $sec -= ($min*60);
+ my $ret = sprintf "$day %s", $day==1 ? msg('time-day') : msg('time-days');
+ $our and $tweak !~ /H/ and $ret .= sprintf " $our %s", $our==1 ? msg('time-hour') : msg('time-hours');
+ $min and $tweak !~ /[HM]/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
+ $sec and $tweak !~ /[HMS]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+ return $ret;
+ }
+
+ ## Weeks, days, hours, minutes, and seconds (< 28 days)
+ my $week = int $sec / (60*60*24*7);
+ $sec -= ($week*60*60*24*7);
+ my $day = int $sec / (60*60*24);
+ $sec -= ($day*60*60*24);
+ my $our = int $sec / (60*60);
+ $sec -= ($our*60*60);
+ my $min = int $sec / 60;
+ $sec -= ($min*60);
+ my $ret = sprintf "$week %s", $week==1 ? msg('time-week') : msg('time-weeks');
+ $day and $tweak !~ /D/ and $ret .= sprintf " $day %s", $day==1 ? msg('time-day') : msg('time-days');
+ $our and $tweak !~ /[DH]/ and $ret .= sprintf " $our %s", $our==1 ? msg('time-hour') : msg('time-hours');
+ $min and $tweak !~ /[DHM]/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
+ $sec and $tweak !~ /[DHMS]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+ return $ret;
} ## end of pretty_time
sub run_command {
- ## Run a command string against each of our databases using psql
- ## Optional args in a hashref:
- ## "failok" - don't report if we failed
- ## "fatalregex" - allow this FATAL regex through
- ## "target" - use this targetlist instead of generating one
- ## "timeout" - change the timeout from the default of $opt{timeout}
- ## "regex" - the query must match this or we throw an error
- ## "emptyok" - it's okay to not match any rows at all
- ## "version" - alternate versions for different versions
- ## "dbnumber" - connect with an alternate set of params, e.g. port2 dbname2
-
- my $string = shift || '';
- my $arg = shift || {};
- my $info = { command => $string, db => [], hosts => 0 };
-
- $VERBOSE >= 3 and warn qq{Starting run_command with: $string\n};
-
- my (%host,$passfile,$passfh,$tempdir,$tempfile,$tempfh,$errorfile,$errfh);
- my $offset = -1;
-
- ## Build a list of all databases to connect to.
- ## Number is determined by host, port, and db arguments
- ## Multi-args are grouped together: host, port, dbuser, dbpass
- ## Grouped are kept together for first pass
- ## The final arg in a group is passed on
- ##
- ## Examples:
- ## --host=a,b --port=5433 --db=c
- ## Connects twice to port 5433, using database c, to hosts a and b
- ## a-5433-c b-5433-c
- ##
- ## --host=a,b --port=5433 --db=c,d
- ## Connects four times: a-5433-c a-5433-d b-5433-c b-5433-d
- ##
- ## --host=a,b --host=foo --port=1234 --port=5433 --db=e,f
- ## Connects six times: a-1234-e a-1234-f b-1234-e b-1234-f foo-5433-e foo-5433-f
- ##
- ## --host=a,b --host=x --port=5432,5433 --dbuser=alice --dbuser=bob -db=baz
- ## Connects three times: a-5432-alice-baz b-5433-alice-baz x-5433-bob-baz
-
- ## The final list of targets:
- my @target;
-
- ## Default connection options
- my $conn =
- {
- host => [$ENV{PGHOST} || '<none>'],
- port => [$ENV{PGPORT} || $opt{defaultport}],
- dbname => [$ENV{PGDATABASE} || $opt{defaultdb}],
- dbuser => [$ENV{PGUSER} || $arg->{dbuser} || $opt{defaultuser}],
- dbpass => [$ENV{PGPASSWORD} || ''],
- dbservice => [''],
- };
-
- ## Don't set any default values if a service is being used
- if (defined $opt{dbservice} and defined $opt{dbservice}->[0] and length $opt{dbservice}->[0]) {
- $conn->{dbname} = [];
- $conn->{port} = [];
- $conn->{dbuser} = [];
- }
- my $gbin = 0;
- GROUP: {
- ## This level controls a "group" of targets
-
- ## If we were passed in a target, use that and move on
- if (exists $arg->{target}) {
- ## Make a copy, in case we are passed in a ref
- my $newtarget;
- for my $key (keys %$conn) {
- $newtarget->{$key} = exists $arg->{target}{$key} ? $arg->{target}{$key} : $conn->{$key};
- }
- push @target, $newtarget;
- last GROUP;
- }
-
- my %group;
- my $foundgroup = 0;
- for my $v (keys %$conn) {
- my $vname = $v;
- ## Something new?
- if ($arg->{dbnumber} and $arg->{dbnumber} ne '1') {
- $v .= "$arg->{dbnumber}";
- }
- if (defined $opt{$v}->[$gbin]) {
- my $new = $opt{$v}->[$gbin];
- $new =~ s/\s+//g unless $vname eq 'dbservice' or $vname eq 'host';
- ## Set this as the new default
- $conn->{$vname} = [split /,/ => $new];
- $foundgroup = 1;
- }
- $group{$vname} = $conn->{$vname};
- }
-
- last GROUP if ! $foundgroup and @target;
-
- $gbin++;
-
- ## Now break the newly created group into individual targets
- my $tbin = 0;
- TARGET: {
- my $foundtarget = 0;
- my %temptarget;
- for my $g (keys %group) {
- if (defined $group{$g}->[$tbin]) {
- $conn->{$g} = [$group{$g}->[$tbin]];
- $foundtarget = 1;
- }
- $temptarget{$g} = $conn->{$g}[0];
- }
-
- ## Leave if nothing new
- last TARGET if ! $foundtarget;
-
- ## Add to our master list
- push @target, \%temptarget;
-
- $tbin++;
- redo TARGET;
- } ## end TARGET
-
- last GROUP if ! $foundgroup;
- redo GROUP;
- } ## end GROUP
-
- if (! @target) {
- ndie msg('runcommand-nodb');
- }
-
- ## Create a temp file to store our results
- my @tempdirargs = (CLEANUP => 1);
- if ($opt{tempdir}) {
- push @tempdirargs => 'DIR', $opt{tempdir};
- }
- $tempdir = tempdir(@tempdirargs);
- ($tempfh,$tempfile) = tempfile('check_postgres_psql.XXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
-
- ## Create another one to catch any errors
- ($errfh,$errorfile) = tempfile('check_postgres_psql_stderr.XXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
-
- for $db (@target) {
-
- ## Just to keep things clean:
- truncate $tempfh, 0;
- truncate $errfh, 0;
-
- ## Store this target in the global target list
- push @{$info->{db}}, $db;
-
- my @args = ('-q', '-t');
- if (defined $db->{dbservice} and length $db->{dbservice}) { ## XX Check for simple names
- $db->{pname} = "service=$db->{dbservice}";
- $ENV{PGSERVICE} = $db->{dbservice};
- }
- else {
- $db->{pname} = "port=$db->{port} host=$db->{host} db=$db->{dbname} user=$db->{dbuser}";
- }
- defined $db->{dbname} and push @args, '-d', $db->{dbname};
- defined $db->{dbuser} and push @args, '-U', $db->{dbuser};
- defined $db->{port} and push @args => '-p', $db->{port};
- if ($db->{host} ne '<none>') {
- push @args => '-h', $db->{host};
- $host{$db->{host}}++; ## For the overall count
- }
-
- if (defined $db->{dbpass} and length $db->{dbpass}) {
- ## Make a custom PGPASSFILE. Far better to simply use your own .pgpass of course
- ($passfh,$passfile) = tempfile('check_postgres.XXXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
- $VERBOSE >= 3 and warn msgn('runcommand-pgpass', $passfile);
- $ENV{PGPASSFILE} = $passfile;
- printf $passfh "%s:%s:%s:%s:%s\n",
- $db->{host} eq '<none>' ? '*' : $db->{host}, $db->{port}, $db->{dbname}, $db->{dbuser}, $db->{dbpass};
- close $passfh or ndie msg('file-noclose', $passfile, $!);
- }
-
- push @args, '-o', $tempfile;
- push @args => '-x';
-
- ## If we've got different SQL, use this first run to simply grab the version
- ## Then we'll use that info to pick the real query
- if ($arg->{version}) {
- if (!$db->{version}) {
- $arg->{versiononly} = 1;
- $arg->{oldstring} = $string;
- $string = 'SELECT version()';
- }
- else {
- $string = $arg->{oldstring} || $arg->{string};
- for my $row (@{$arg->{version}}) {
- if ($row !~ s/^([<>]?)(\d+\.\d+)\s+//) {
- ndie msg('die-badversion', $row);
- }
- my ($mod,$ver) = ($1||'',$2);
- if ($mod eq '>' and $db->{version} > $ver) {
- $string = $row;
- last;
- }
- if ($mod eq '<' and $db->{version} < $ver) {
- $string = $row;
- last;
- }
- if ($mod eq '' and $db->{version} eq $ver) {
- $string = $row;
- }
- }
- delete $arg->{version};
- $info->{command} = $string;
- }
- }
-
- local $SIG{ALRM} = sub { die 'Timed out' };
- my $timeout = $arg->{timeout} || $opt{timeout};
- my $dbtimeout = $timeout * 1000;
- alarm 0;
-
- if ($action ne 'pgbouncer_checksum') {
- $string = "BEGIN;SET statement_timeout=$dbtimeout;COMMIT;$string";
- }
-
- push @args, '-c', $string;
-
- $VERBOSE >= 3 and warn Dumper \@args;
-
- my $start = $opt{showtime} ? [gettimeofday()] : 0;
- open my $oldstderr, '>&', \*STDERR or ndie msg('runcommand-nodupe');
- open STDERR, '>', $errorfile or ndie msg('runcommand-noerr');
- eval {
- alarm $timeout;
- $res = system $PSQL => @args;
- };
- my $err = $@;
- alarm 0;
- open STDERR, '>&', $oldstderr or ndie msg('runcommand-noerr');
- close $oldstderr or ndie msg('file-noclose', 'STDERR copy', $!);
- if ($err) {
- if ($err =~ /Timed out/) {
- ndie msg('runcommand-timeout', $timeout);
- }
- else {
- ndie msg('runcommand-err');
- }
- }
-
- $db->{totaltime} = sprintf '%.2f', $opt{showtime} ? tv_interval($start) : 0;
-
- if ($res) {
- $db->{fail} = $res;
- $VERBOSE >= 3 and !$arg->{failok} and warn msgn('runcommand-nosys', $res);
- seek $errfh, 0, 0;
- {
- local $/;
- $db->{error} = <$errfh> || '';
- $db->{error} =~ s/\s*$//;
- $db->{error} =~ s/^psql: //;
- $ERROR = $db->{error};
- }
-
- if ($db->{error} =~ /FATAL/) {
- if (exists $arg->{fatalregex} and $db->{error} =~ /$arg->{fatalregex}/) {
- $info->{fatalregex} = $db->{error};
- next;
- }
- else {
- ndie "$db->{error}";
- }
- }
-
- elsif ($db->{error} =~ /statement timeout/) {
- ndie msg('runcommand-timeout', $timeout);
- }
-
- if (!$db->{ok} and !$arg->{failok} and !$arg->{noverify}) {
-
- ## Check if problem is due to backend being too old for this check
- verify_version();
-
- if (exists $db->{error}) {
- ndie $db->{error};
- }
-
- add_unknown;
- ## Remove it from the returned hash
- pop @{$info->{db}};
- }
- }
- else {
- seek $tempfh, 0, 0;
- {
- local $/;
- $db->{slurp} = <$tempfh>;
- }
- $db->{ok} = 1;
-
- ## Unfortunately, psql outputs "(No rows)" even with -t and -x
- $db->{slurp} = '' if index($db->{slurp},'(')==0;
-
- ## Allow an empty query (no matching rows) if requested
- if ($arg->{emptyok} and $db->{slurp} =~ /^\s*$/o) {
- $arg->{emptyok2} = 1;
- }
- ## If we just want a version, grab it and redo
- if ($arg->{versiononly}) {
- if ($db->{error}) {
- ndie $db->{error};
- }
- if ($db->{slurp} !~ /PostgreSQL (\d+\.\d+)/) {
- ndie msg('die-badversion', $db->{slurp});
- }
- $db->{version} = $1;
- $db->{ok} = 0;
- delete $arg->{versiononly};
- ## Remove this from the returned hash
- pop @{$info->{db}};
- redo;
- }
-
- ## If we were provided with a regex, check and bail if it fails
- if ($arg->{regex} and ! $arg->{emptyok2}) {
- if ($db->{slurp} !~ $arg->{regex}) {
- ## Check if problem is due to backend being too old for this check
-
- verify_version();
-
- add_unknown msg('invalid-query', $db->{slurp});
-
- finishup();
- exit 0;
- }
- }
-
- ## Transform psql output into an arrayref of hashes
- my @stuff;
- my $num = 0;
- my $lastval;
- for my $line (split /\n/ => $db->{slurp}) {
- if (index($line,'-')==0) {
- $num++;
- next;
- }
- if ($line =~ /^(\w+)\s+\| (.*)/) {
- $stuff[$num]{$1} = $2;
- $lastval = $1;
- }
- elsif ($line =~ /^QUERY PLAN\s+\| (.*)/) {
- $stuff[$num]{queryplan} = $1;
- $lastval = 'queryplan';
- }
- elsif ($line =~ /^\s+: (.*)/) {
- $stuff[$num]{$lastval} .= "\n$1";
- }
- else {
- ### XXX msg these
- warn "Could not parse psql output!\n";
- warn "Please report these details to check_postgres\@bucardo.org:\n";
- my $cline = (caller)[2];
- my $args = join ' ' => @args;
- warn "Version: $VERSION\n";
- warn "Action: $action\n";
- warn "Calling line: $cline\n";
- warn "Output: $line\n";
- warn "Command: $PSQL $args\n";
- exit 1;
- }
- }
- $db->{slurp} = \@stuff;
-
- } ## end valid system call
-
-
- } ## end each database
-
- close $errfh or ndie msg('file-noclose', $errorfile, $!);
- close $tempfh or ndie msg('file-noclose', $tempfile, $!);
-
- eval { File::Temp::cleanup(); };
-
- $info->{hosts} = keys %host;
-
- $VERBOSE >= 3 and warn Dumper $info;
-
- if ($DEBUGOUTPUT) {
- if (defined $info->{db} and defined $info->{db}[0]{slurp}) {
- $DEBUG_INFO = $info->{db}[0]{slurp};
- $DEBUG_INFO =~ s/\n/\\n/g;
- $DEBUG_INFO =~ s/\|/<SEP>/g;
- }
- }
-
- return $info;
+ ## Run a command string against each of our databases using psql
+ ## Optional args in a hashref:
+ ## "failok" - don't report if we failed
+ ## "fatalregex" - allow this FATAL regex through
+ ## "target" - use this targetlist instead of generating one
+ ## "timeout" - change the timeout from the default of $opt{timeout}
+ ## "regex" - the query must match this or we throw an error
+ ## "emptyok" - it's okay to not match any rows at all
+ ## "version" - alternate versions for different versions
+ ## "dbnumber" - connect with an alternate set of params, e.g. port2 dbname2
+
+ my $string = shift || '';
+ my $arg = shift || {};
+ my $info = { command => $string, db => [], hosts => 0 };
+
+ $VERBOSE >= 3 and warn qq{Starting run_command with: $string\n};
+
+ my (%host,$passfile,$passfh,$tempdir,$tempfile,$tempfh,$errorfile,$errfh);
+ my $offset = -1;
+
+ ## Build a list of all databases to connect to.
+ ## Number is determined by host, port, and db arguments
+ ## Multi-args are grouped together: host, port, dbuser, dbpass
+ ## Grouped are kept together for first pass
+ ## The final arg in a group is passed on
+ ##
+ ## Examples:
+ ## --host=a,b --port=5433 --db=c
+ ## Connects twice to port 5433, using database c, to hosts a and b
+ ## a-5433-c b-5433-c
+ ##
+ ## --host=a,b --port=5433 --db=c,d
+ ## Connects four times: a-5433-c a-5433-d b-5433-c b-5433-d
+ ##
+ ## --host=a,b --host=foo --port=1234 --port=5433 --db=e,f
+ ## Connects six times: a-1234-e a-1234-f b-1234-e b-1234-f foo-5433-e foo-5433-f
+ ##
+ ## --host=a,b --host=x --port=5432,5433 --dbuser=alice --dbuser=bob -db=baz
+ ## Connects three times: a-5432-alice-baz b-5433-alice-baz x-5433-bob-baz
+
+ ## The final list of targets:
+ my @target;
+
+ ## Default connection options
+ my $conn =
+ {
+ host => [$ENV{PGHOST} || '<none>'],
+ port => [$ENV{PGPORT} || $opt{defaultport}],
+ dbname => [$ENV{PGDATABASE} || $opt{defaultdb}],
+ dbuser => [$ENV{PGUSER} || $arg->{dbuser} || $opt{defaultuser}],
+ dbpass => [$ENV{PGPASSWORD} || ''],
+ dbservice => [''],
+ };
+
+ ## Don't set any default values if a service is being used
+ if (defined $opt{dbservice} and defined $opt{dbservice}->[0] and length $opt{dbservice}->[0]) {
+ $conn->{dbname} = [];
+ $conn->{port} = [];
+ $conn->{dbuser} = [];
+ }
+ my $gbin = 0;
+ GROUP: {
+ ## This level controls a "group" of targets
+
+ ## If we were passed in a target, use that and move on
+ if (exists $arg->{target}) {
+ ## Make a copy, in case we are passed in a ref
+ my $newtarget;
+ for my $key (keys %$conn) {
+ $newtarget->{$key} = exists $arg->{target}{$key} ? $arg->{target}{$key} : $conn->{$key};
+ }
+ push @target, $newtarget;
+ last GROUP;
+ }
+
+ my %group;
+ my $foundgroup = 0;
+ for my $v (keys %$conn) {
+ my $vname = $v;
+ ## Something new?
+ if ($arg->{dbnumber} and $arg->{dbnumber} ne '1') {
+ $v .= "$arg->{dbnumber}";
+ }
+ if (defined $opt{$v}->[$gbin]) {
+ my $new = $opt{$v}->[$gbin];
+ $new =~ s/\s+//g unless $vname eq 'dbservice' or $vname eq 'host';
+ ## Set this as the new default
+ $conn->{$vname} = [split /,/ => $new];
+ $foundgroup = 1;
+ }
+ $group{$vname} = $conn->{$vname};
+ }
+
+ last GROUP if ! $foundgroup and @target;
+
+ $gbin++;
+
+ ## Now break the newly created group into individual targets
+ my $tbin = 0;
+ TARGET: {
+ my $foundtarget = 0;
+ my %temptarget;
+ for my $g (keys %group) {
+ if (defined $group{$g}->[$tbin]) {
+ $conn->{$g} = [$group{$g}->[$tbin]];
+ $foundtarget = 1;
+ }
+ $temptarget{$g} = $conn->{$g}[0];
+ }
+
+ ## Leave if nothing new
+ last TARGET if ! $foundtarget;
+
+ ## Add to our master list
+ push @target, \%temptarget;
+
+ $tbin++;
+ redo TARGET;
+ } ## end TARGET
+
+ last GROUP if ! $foundgroup;
+ redo GROUP;
+ } ## end GROUP
+
+ if (! @target) {
+ ndie msg('runcommand-nodb');
+ }
+
+ ## Create a temp file to store our results
+ my @tempdirargs = (CLEANUP => 1);
+ if ($opt{tempdir}) {
+ push @tempdirargs => 'DIR', $opt{tempdir};
+ }
+ $tempdir = tempdir(@tempdirargs);
+ ($tempfh,$tempfile) = tempfile('check_postgres_psql.XXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
+
+ ## Create another one to catch any errors
+ ($errfh,$errorfile) = tempfile('check_postgres_psql_stderr.XXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
+
+ for $db (@target) {
+
+ ## Just to keep things clean:
+ truncate $tempfh, 0;
+ truncate $errfh, 0;
+
+ ## Store this target in the global target list
+ push @{$info->{db}}, $db;
+
+ my @args = ('-q', '-t');
+ if (defined $db->{dbservice} and length $db->{dbservice}) { ## XX Check for simple names
+ $db->{pname} = "service=$db->{dbservice}";
+ $ENV{PGSERVICE} = $db->{dbservice};
+ }
+ else {
+ $db->{pname} = "port=$db->{port} host=$db->{host} db=$db->{dbname} user=$db->{dbuser}";
+ }
+ defined $db->{dbname} and push @args, '-d', $db->{dbname};
+ defined $db->{dbuser} and push @args, '-U', $db->{dbuser};
+ defined $db->{port} and push @args => '-p', $db->{port};
+ if ($db->{host} ne '<none>') {
+ push @args => '-h', $db->{host};
+ $host{$db->{host}}++; ## For the overall count
+ }
+
+ if (defined $db->{dbpass} and length $db->{dbpass}) {
+ ## Make a custom PGPASSFILE. Far better to simply use your own .pgpass of course
+ ($passfh,$passfile) = tempfile('check_postgres.XXXXXXXX', SUFFIX => '.tmp', DIR => $tempdir);
+ $VERBOSE >= 3 and warn msgn('runcommand-pgpass', $passfile);
+ $ENV{PGPASSFILE} = $passfile;
+ printf $passfh "%s:%s:%s:%s:%s\n",
+ $db->{host} eq '<none>' ? '*' : $db->{host}, $db->{port}, $db->{dbname}, $db->{dbuser}, $db->{dbpass};
+ close $passfh or ndie msg('file-noclose', $passfile, $!);
+ }
+
+ push @args, '-o', $tempfile;
+ push @args => '-x';
+
+ ## If we've got different SQL, use this first run to simply grab the version
+ ## Then we'll use that info to pick the real query
+ if ($arg->{version}) {
+ if (!$db->{version}) {
+ $arg->{versiononly} = 1;
+ $arg->{oldstring} = $string;
+ $string = 'SELECT version()';
+ }
+ else {
+ $string = $arg->{oldstring} || $arg->{string};
+ for my $row (@{$arg->{version}}) {
+ if ($row !~ s/^([<>]?)(\d+\.\d+)\s+//) {
+ ndie msg('die-badversion', $row);
+ }
+ my ($mod,$ver) = ($1||'',$2);
+ if ($mod eq '>' and $db->{version} > $ver) {
+ $string = $row;
+ last;
+ }
+ if ($mod eq '<' and $db->{version} < $ver) {
+ $string = $row;
+ last;
+ }
+ if ($mod eq '' and $db->{version} eq $ver) {
+ $string = $row;
+ }
+ }
+ delete $arg->{version};
+ $info->{command} = $string;
+ }
+ }
+
+ local $SIG{ALRM} = sub { die 'Timed out' };
+ my $timeout = $arg->{timeout} || $opt{timeout};
+ my $dbtimeout = $timeout * 1000;
+ alarm 0;
+
+ if ($action ne 'pgbouncer_checksum') {
+ $string = "BEGIN;SET statement_timeout=$dbtimeout;COMMIT;$string";
+ }
+
+ push @args, '-c', $string;
+
+ $VERBOSE >= 3 and warn Dumper \@args;
+
+ my $start = $opt{showtime} ? [gettimeofday()] : 0;
+ open my $oldstderr, '>&', \*STDERR or ndie msg('runcommand-nodupe');
+ open STDERR, '>', $errorfile or ndie msg('runcommand-noerr');
+ eval {
+ alarm $timeout;
+ $res = system $PSQL => @args;
+ };
+ my $err = $@;
+ alarm 0;
+ open STDERR, '>&', $oldstderr or ndie msg('runcommand-noerr');
+ close $oldstderr or ndie msg('file-noclose', 'STDERR copy', $!);
+ if ($err) {
+ if ($err =~ /Timed out/) {
+ ndie msg('runcommand-timeout', $timeout);
+ }
+ else {
+ ndie msg('runcommand-err');
+ }
+ }
+
+ $db->{totaltime} = sprintf '%.2f', $opt{showtime} ? tv_interval($start) : 0;
+
+ if ($res) {
+ $db->{fail} = $res;
+ $VERBOSE >= 3 and !$arg->{failok} and warn msgn('runcommand-nosys', $res);
+ seek $errfh, 0, 0;
+ {
+ local $/;
+ $db->{error} = <$errfh> || '';
+ $db->{error} =~ s/\s*$//;
+ $db->{error} =~ s/^psql: //;
+ $ERROR = $db->{error};
+ }
+
+ if ($db->{error} =~ /FATAL/) {
+ if (exists $arg->{fatalregex} and $db->{error} =~ /$arg->{fatalregex}/) {
+ $info->{fatalregex} = $db->{error};
+ next;
+ }
+ else {
+ ndie "$db->{error}";
+ }
+ }
+
+ elsif ($db->{error} =~ /statement timeout/) {
+ ndie msg('runcommand-timeout', $timeout);
+ }
+
+ if (!$db->{ok} and !$arg->{failok} and !$arg->{noverify}) {
+
+ ## Check if problem is due to backend being too old for this check
+ verify_version();
+
+ if (exists $db->{error}) {
+ ndie $db->{error};
+ }
+
+ add_unknown;
+ ## Remove it from the returned hash
+ pop @{$info->{db}};
+ }
+ }
+ else {
+ seek $tempfh, 0, 0;
+ {
+ local $/;
+ $db->{slurp} = <$tempfh>;
+ }
+ $db->{ok} = 1;
+
+ ## Unfortunately, psql outputs "(No rows)" even with -t and -x
+ $db->{slurp} = '' if index($db->{slurp},'(')==0;
+
+ ## Allow an empty query (no matching rows) if requested
+ if ($arg->{emptyok} and $db->{slurp} =~ /^\s*$/o) {
+ $arg->{emptyok2} = 1;
+ }
+ ## If we just want a version, grab it and redo
+ if ($arg->{versiononly}) {
+ if ($db->{error}) {
+ ndie $db->{error};
+ }
+ if ($db->{slurp} !~ /PostgreSQL (\d+\.\d+)/) {
+ ndie msg('die-badversion', $db->{slurp});
+ }
+ $db->{version} = $1;
+ $db->{ok} = 0;
+ delete $arg->{versiononly};
+ ## Remove this from the returned hash
+ pop @{$info->{db}};
+ redo;
+ }
+
+ ## If we were provided with a regex, check and bail if it fails
+ if ($arg->{regex} and ! $arg->{emptyok2}) {
+ if ($db->{slurp} !~ $arg->{regex}) {
+ ## Check if problem is due to backend being too old for this check
+
+ verify_version();
+
+ add_unknown msg('invalid-query', $db->{slurp});
+
+ finishup();
+ exit 0;
+ }
+ }
+
+ ## Transform psql output into an arrayref of hashes
+ my @stuff;
+ my $num = 0;
+ my $lastval;
+ for my $line (split /\n/ => $db->{slurp}) {
+ if (index($line,'-')==0) {
+ $num++;
+ next;
+ }
+ if ($line =~ /^(\w+)\s+\| (.*)/) {
+ $stuff[$num]{$1} = $2;
+ $lastval = $1;
+ }
+ elsif ($line =~ /^QUERY PLAN\s+\| (.*)/) {
+ $stuff[$num]{queryplan} = $1;
+ $lastval = 'queryplan';
+ }
+ elsif ($line =~ /^\s+: (.*)/) {
+ $stuff[$num]{$lastval} .= "\n$1";
+ }
+ else {
+ ### XXX msg these
+ warn "Could not parse psql output!\n";
+ warn "Please report these details to check_postgres\@bucardo.org:\n";
+ my $cline = (caller)[2];
+ my $args = join ' ' => @args;
+ warn "Version: $VERSION\n";
+ warn "Action: $action\n";
+ warn "Calling line: $cline\n";
+ warn "Output: $line\n";
+ warn "Command: $PSQL $args\n";
+ exit 1;
+ }
+ }
+ $db->{slurp} = \@stuff;
+
+ } ## end valid system call
+
+
+ } ## end each database
+
+ close $errfh or ndie msg('file-noclose', $errorfile, $!);
+ close $tempfh or ndie msg('file-noclose', $tempfile, $!);
+
+ eval { File::Temp::cleanup(); };
+
+ $info->{hosts} = keys %host;
+
+ $VERBOSE >= 3 and warn Dumper $info;
+
+ if ($DEBUGOUTPUT) {
+ if (defined $info->{db} and defined $info->{db}[0]{slurp}) {
+ $DEBUG_INFO = $info->{db}[0]{slurp};
+ $DEBUG_INFO =~ s/\n/\\n/g;
+ $DEBUG_INFO =~ s/\|/<SEP>/g;
+ }
+ }
+
+ return $info;
} ## end of run_command
sub verify_version {
- ## Check if the backend can handle the current action
- my $limit = $testaction{lc $action} || '';
-
- my $versiononly = shift || 0;
-
- return if ! $limit and ! $versiononly;
-
- ## We almost always need the version, so just grab it for any limitation
- $SQL = q{SELECT setting FROM pg_settings WHERE name = 'server_version'};
- my $oldslurp = $db->{slurp} || '';
- my $info = run_command($SQL, {noverify => 1});
- if (defined $info->{db}[0]
- and exists $info->{db}[0]{error}
- and defined $info->{db}[0]{error}
- ) {
- ndie $info->{db}[0]{error};
- }
-
- if (!defined $info->{db}[0] or $info->{db}[0]{slurp}[0]{setting} !~ /((\d+)\.(\d+))/) {
- ndie msg('die-badversion', $SQL);
- }
- my ($sver,$smaj,$smin) = ($1,$2,$3);
-
- if ($versiononly) {
- return $sver;
- }
-
- if ($limit =~ /VERSION: ((\d+)\.(\d+))/) {
- my ($rver,$rmaj,$rmin) = ($1,$2,$3);
- if ($smaj < $rmaj or ($smaj==$rmaj and $smin < $rmin)) {
- ndie msg('die-action-version', $action, $rver, $sver);
- }
- }
-
- while ($limit =~ /\bON: (\w+)(?:\(([<>=])(\d+\.\d+)\))?/g) {
- my ($setting,$op,$ver) = ($1,$2||'',$3||0);
- if ($ver) {
- next if $op eq '<' and $sver >= $ver;
- next if $op eq '>' and $sver <= $ver;
- next if $op eq '=' and $sver != $ver;
- }
-
- $SQL = qq{SELECT setting FROM pg_settings WHERE name = '$setting'};
- my $info2 = run_command($SQL);
- if (!defined $info2->{db}[0]) {
- ndie msg('die-nosetting', $setting);
- }
- my $val = $info2->{db}[0]{slurp};
- if ($val !~ /^\s*on\b/) {
- ndie msg('die-noset', $action, $setting);
- }
- }
-
- $db->{slurp} = $oldslurp;
- return;
+ ## Check if the backend can handle the current action
+ my $limit = $testaction{lc $action} || '';
+
+ my $versiononly = shift || 0;
+
+ return if ! $limit and ! $versiononly;
+
+ ## We almost always need the version, so just grab it for any limitation
+ $SQL = q{SELECT setting FROM pg_settings WHERE name = 'server_version'};
+ my $oldslurp = $db->{slurp} || '';
+ my $info = run_command($SQL, {noverify => 1});
+ if (defined $info->{db}[0]
+ and exists $info->{db}[0]{error}
+ and defined $info->{db}[0]{error}
+ ) {
+ ndie $info->{db}[0]{error};
+ }
+
+ if (!defined $info->{db}[0] or $info->{db}[0]{slurp}[0]{setting} !~ /((\d+)\.(\d+))/) {
+ ndie msg('die-badversion', $SQL);
+ }
+ my ($sver,$smaj,$smin) = ($1,$2,$3);
+
+ if ($versiononly) {
+ return $sver;
+ }
+
+ if ($limit =~ /VERSION: ((\d+)\.(\d+))/) {
+ my ($rver,$rmaj,$rmin) = ($1,$2,$3);
+ if ($smaj < $rmaj or ($smaj==$rmaj and $smin < $rmin)) {
+ ndie msg('die-action-version', $action, $rver, $sver);
+ }
+ }
+
+ while ($limit =~ /\bON: (\w+)(?:\(([<>=])(\d+\.\d+)\))?/g) {
+ my ($setting,$op,$ver) = ($1,$2||'',$3||0);
+ if ($ver) {
+ next if $op eq '<' and $sver >= $ver;
+ next if $op eq '>' and $sver <= $ver;
+ next if $op eq '=' and $sver != $ver;
+ }
+
+ $SQL = qq{SELECT setting FROM pg_settings WHERE name = '$setting'};
+ my $info2 = run_command($SQL);
+ if (!defined $info2->{db}[0]) {
+ ndie msg('die-nosetting', $setting);
+ }
+ my $val = $info2->{db}[0]{slurp};
+ if ($val !~ /^\s*on\b/) {
+ ndie msg('die-noset', $action, $setting);
+ }
+ }
+
+ $db->{slurp} = $oldslurp;
+ return;
} ## end of verify_version
sub size_in_bytes { ## no critic (RequireArgUnpacking)
- ## Given a number and a unit, return the number of bytes.
- ## Defaults to bytes
+ ## Given a number and a unit, return the number of bytes.
+ ## Defaults to bytes
- my ($val,$unit) = ($_[0],lc substr($_[1]||'s',0,1));
- return $val * ($unit eq 'b' ? 1 : $unit eq 'k' ? 1024 : $unit eq 'm' ? 1024**2 :
- $unit eq 'g' ? 1024**3 : $unit eq 't' ? 1024**4 :
- $unit eq 'p' ? 1024**5 : $unit eq 'e' ? 1024**6 :
- $unit eq 'z' ? 1024**7 : 1);
+ my ($val,$unit) = ($_[0],lc substr($_[1]||'s',0,1));
+ return $val * ($unit eq 'b' ? 1 : $unit eq 'k' ? 1024 : $unit eq 'm' ? 1024**2 :
+ $unit eq 'g' ? 1024**3 : $unit eq 't' ? 1024**4 :
+ $unit eq 'p' ? 1024**5 : $unit eq 'e' ? 1024**6 :
+ $unit eq 'z' ? 1024**7 : 1);
} ## end of size_in_bytes
sub size_in_seconds {
- my ($string,$type) = @_;
-
- return '' if ! length $string;
- if ($string !~ $timere) {
- ndie msg('die-badtime', $type, substr($type,0,1));
- }
- my ($val,$unit) = ($1,lc substr($2||'s',0,1));
- my $tempval = sprintf '%.9f', $val * (
- $unit eq 's' ? 1 :
- $unit eq 'm' ? 60 :
- $unit eq 'h' ? 3600 :
- $unit eq 'd' ? 86400 :
- $unit eq 'w' ? 604800 :
- $unit eq 'y' ? 31536000 :
- ndie msg('die-badtime', $type, substr($type,0,1))
- );
- $tempval =~ s/0+$//;
- $tempval = int $tempval if $tempval =~ /\.$/;
- return $tempval;
+ my ($string,$type) = @_;
+
+ return '' if ! length $string;
+ if ($string !~ $timere) {
+ ndie msg('die-badtime', $type, substr($type,0,1));
+ }
+ my ($val,$unit) = ($1,lc substr($2||'s',0,1));
+ my $tempval = sprintf '%.9f', $val * (
+ $unit eq 's' ? 1 :
+ $unit eq 'm' ? 60 :
+ $unit eq 'h' ? 3600 :
+ $unit eq 'd' ? 86400 :
+ $unit eq 'w' ? 604800 :
+ $unit eq 'y' ? 31536000 :
+ ndie msg('die-badtime', $type, substr($type,0,1))
+ );
+ $tempval =~ s/0+$//;
+ $tempval = int $tempval if $tempval =~ /\.$/;
+ return $tempval;
} ## end of size_in_seconds
sub skip_item {
- ## Determine if something should be skipped due to inclusion/exclusion options
- ## Exclusion checked first: inclusion can pull it back in.
- my $name = shift;
- my $schema = shift || '';
-
- my $stat = 0;
- ## Is this excluded?
- if (defined $opt{exclude}) {
- $stat = 1;
- for (@{$opt{exclude}}) {
- for my $ex (split /\s*,\s*/o => $_) {
- if ($ex =~ s/\.$//) {
- if ($ex =~ s/^~//) {
- ($stat += 2 and last) if $schema =~ /$ex/;
- }
- else {
- ($stat += 2 and last) if $schema eq $ex;
- }
- }
- elsif ($ex =~ s/^~//) {
- ($stat += 2 and last) if $name =~ /$ex/;
- }
- else {
- ($stat += 2 and last) if $name eq $ex;
- }
- }
- }
- }
- if (defined $opt{include}) {
- $stat += 4;
- for (@{$opt{include}}) {
- for my $in (split /\s*,\s*/o => $_) {
- if ($in =~ s/\.$//) {
- if ($in =~ s/^~//) {
- ($stat += 8 and last) if $schema =~ /$in/;
- }
- else {
- ($stat += 8 and last) if $schema eq $in;
- }
- }
- elsif ($in =~ s/^~//) {
- ($stat += 8 and last) if $name =~ /$in/;
- }
- else {
- ($stat += 8 and last) if $name eq $in;
- }
- }
- }
- }
-
- ## Easiest to state the cases when we DO skip:
- return 1 if
- 3 == $stat ## exclude matched, no inclusion checking
- or 4 == $stat ## include check only, no match
- or 7 == $stat; ## exclude match, no inclusion match
-
- return 0;
+ ## Determine if something should be skipped due to inclusion/exclusion options
+ ## Exclusion checked first: inclusion can pull it back in.
+ my $name = shift;
+ my $schema = shift || '';
+
+ my $stat = 0;
+ ## Is this excluded?
+ if (defined $opt{exclude}) {
+ $stat = 1;
+ for (@{$opt{exclude}}) {
+ for my $ex (split /\s*,\s*/o => $_) {
+ if ($ex =~ s/\.$//) {
+ if ($ex =~ s/^~//) {
+ ($stat += 2 and last) if $schema =~ /$ex/;
+ }
+ else {
+ ($stat += 2 and last) if $schema eq $ex;
+ }
+ }
+ elsif ($ex =~ s/^~//) {
+ ($stat += 2 and last) if $name =~ /$ex/;
+ }
+ else {
+ ($stat += 2 and last) if $name eq $ex;
+ }
+ }
+ }
+ }
+ if (defined $opt{include}) {
+ $stat += 4;
+ for (@{$opt{include}}) {
+ for my $in (split /\s*,\s*/o => $_) {
+ if ($in =~ s/\.$//) {
+ if ($in =~ s/^~//) {
+ ($stat += 8 and last) if $schema =~ /$in/;
+ }
+ else {
+ ($stat += 8 and last) if $schema eq $in;
+ }
+ }
+ elsif ($in =~ s/^~//) {
+ ($stat += 8 and last) if $name =~ /$in/;
+ }
+ else {
+ ($stat += 8 and last) if $name eq $in;
+ }
+ }
+ }
+ }
+
+ ## Easiest to state the cases when we DO skip:
+ return 1 if
+ 3 == $stat ## exclude matched, no inclusion checking
+ or 4 == $stat ## include check only, no match
+ or 7 == $stat; ## exclude match, no inclusion match
+
+ return 0;
} ## end of skip_item
sub validate_range {
- ## Valid that warning and critical are set correctly.
- ## Returns new values of both
-
- my $arg = shift;
- defined $arg and ref $arg eq 'HASH' or ndie qq{validate_range must be called with a hashref\n};
-
- return ('','') if $MRTG and !$arg->{forcemrtg};
-
- my $type = $arg->{type} or ndie qq{validate_range must be provided a 'type'\n};
-
- ## The 'default default' is an empty string, which should fail all mandatory tests
- ## We only set the 'arg' default if neither option is provided.
- my $warning = exists $opt{warning} ? $opt{warning} :
- exists $opt{critical} ? '' : $arg->{default_warning} || '';
- my $critical = exists $opt{critical} ? $opt{critical} :
- exists $opt{warning} ? '' : $arg->{default_critical} || '';
-
- if ('string' eq $type) {
- ## Don't use this unless you have to
- }
- elsif ('seconds' eq $type) {
- if (length $warning) {
- if ($warning !~ $timesecre) {
- ndie msg('range-seconds', 'warning');
- }
- $warning = $1;
- }
- if (length $critical) {
- if ($critical !~ $timesecre) {
- ndie msg('range-seconds', 'critical')
- }
- $critical = $1;
- if (length $warning and $warning > $critical) {
- ndie msg('range-warnbigtime', $warning, $critical);
- }
- }
- }
- elsif ('time' eq $type) {
- $critical = size_in_seconds($critical, 'critical');
- $warning = size_in_seconds($warning, 'warning');
- if (! length $critical and ! length $warning) {
- ndie msg('range-notime');
- }
- if (length $warning and length $critical and $warning > $critical) {
- ndie msg('range-warnbigtime', $warning, $critical);
- }
- }
- elsif ('version' eq $type) {
- my $msg = msg('range-version');
- if (length $warning and $warning !~ /^\d+\.\d+\.?[\d\w]*$/) {
- ndie msg('range-badversion', 'warning', $msg);
- }
- if (length $critical and $critical !~ /^\d+\.\d+\.?[\d\w]*$/) {
- ndie msg('range-badversion', 'critical', $msg);
- }
- if (! length $critical and ! length $warning) {
- ndie msg('range-noopt-orboth');
- }
- }
- elsif ('size' eq $type) {
- if (length $critical) {
- if ($critical !~ $sizere) {
- ndie msg('range-badsize', 'critical');
- }
- $critical = size_in_bytes($1,$2);
- }
- if (length $warning) {
- if ($warning !~ $sizere) {
- ndie msg('range-badsize', 'warning');
- }
- $warning = size_in_bytes($1,$2);
- if (length $critical and $warning > $critical) {
- ndie msg('range-warnbigsize', $warning, $critical);
- }
- }
- elsif (!length $critical) {
- ndie msg('range-nosize');
- }
- }
- elsif ($type =~ /integer/) {
- $warning =~ s/_//g;
- if (length $warning and $warning !~ /^\d+$/) {
- ndie $type =~ /positive/ ? msg('range-int-pos', 'warning') : msg('range-int', 'warning');
- }
- elsif (length $warning && $type =~ /positive/ && $warning <= 0) {
- ndie msg('range-int-pos', 'warning');
- }
-
- $critical =~ s/_//g;
- if (length $critical and $critical !~ /^\d+$/) {
- ndie $type =~ /positive/ ? msg('range-int-pos', 'critical') : msg('range-int', 'critical');
- }
- elsif (length $critical && $type =~ /positive/ && $critical <= 0) {
- ndie msg('range-int-pos', 'critical');
- }
-
- if (length $warning
- and length $critical
- and (
- ($opt{reverse} and $warning < $critical)
- or
- (!$opt{reverse} and $warning > $critical)
- )
- ) {
- ndie msg('range-warnbig');
- }
- }
- elsif ('restringex' eq $type) {
- if (! length $critical and ! length $warning) {
- ndie msg('range-noopt-one');
- }
- if (length $critical and length $warning) {
- ndie msg('range-noopt-only');
- }
- my $string = length $critical ? $critical : $warning;
- my $regex = ($string =~ s/^~//) ? '~' : '=';
- $string =~ /^\w+$/ or ndie msg('invalid-option');
- }
- elsif ('percent' eq $type) {
- if (length $critical) {
- if ($critical !~ /^\d+\%$/) {
- ndie msg('range-badpercent', 'critical');
- }
- }
- if (length $warning) {
- if ($warning !~ /^\d+\%$/) {
- ndie msg('range-badpercent', 'warning');
- }
- }
- }
- elsif ('size or percent' eq $type) {
- if (length $critical) {
- if ($critical =~ $sizere) {
- $critical = size_in_bytes($1,$2);
- }
- elsif ($critical !~ /^\d+\%$/) {
- ndie msg('range-badpercsize', 'critical');
- }
- }
- if (length $warning) {
- if ($warning =~ $sizere) {
- $warning = size_in_bytes($1,$2);
- }
- elsif ($warning !~ /^\d+\%$/) {
- ndie msg('range-badpercsize', 'warning');
- }
- }
- elsif (! length $critical) {
- ndie msg('range-noopt-size');
- }
- }
- elsif ('checksum' eq $type) {
- if (length $critical and $critical !~ $checksumre and $critical ne '0') {
- ndie msg('range-badcs', 'critical');
- }
- if (length $warning and $warning !~ $checksumre) {
- ndie msg('range-badcs', 'warning');
- }
- }
- elsif ('multival' eq $type) { ## Simple number, or foo=#;bar=#
- ## Note: only used for check_locks
- my %err;
- while ($critical =~ /(\w+)\s*=\s*(\d+)/gi) {
- my ($name,$val) = (lc $1,$2);
- $name =~ s/lock$//;
- $err{$name} = $val;
- }
- if (keys %err) {
- $critical = \%err;
- }
- elsif (length $critical and $critical =~ /^(\d+)$/) {
- $err{total} = $1;
- $critical = \%err;
- }
- elsif (length $critical) {
- ndie msg('range-badlock', 'critical');
- }
- my %warn;
- while ($warning =~ /(\w+)\s*=\s*(\d+)/gi) {
- my ($name,$val) = (lc $1,$2);
- $name =~ s/lock$//;
- $warn{$name} = $val;
- }
- if (keys %warn) {
- $warning = \%warn;
- }
- elsif (length $warning and $warning =~ /^(\d+)$/) {
- $warn{total} = $1;
- $warning = \%warn;
- }
- elsif (length $warning) {
- ndie msg('range-badlock', 'warning');
- }
- }
- elsif ('cacti' eq $type) { ## Takes no args, just dumps data
- if (length $warning or length $critical) {
- ndie msg('range-cactionly');
- }
- }
- else {
- ndie msg('range-badtype', $type);
- }
-
- if ($arg->{both}) {
- if (! length $warning or ! length $critical) {
- ndie msg('range-noopt-both');
- }
- }
- if ($arg->{leastone}) {
- if (! length $warning and ! length $critical) {
- ndie msg('range-noopt-one');
- }
- }
- elsif ($arg->{onlyone}) {
- if (length $warning and length $critical) {
- ndie msg('range-noopt-only');
- }
- if (! length $warning and ! length $critical) {
- ndie msg('range-noopt-one');
- }
- }
-
- return ($warning,$critical);
+ ## Valid that warning and critical are set correctly.
+ ## Returns new values of both
+
+ my $arg = shift;
+ defined $arg and ref $arg eq 'HASH' or ndie qq{validate_range must be called with a hashref\n};
+
+ return ('','') if $MRTG and !$arg->{forcemrtg};
+
+ my $type = $arg->{type} or ndie qq{validate_range must be provided a 'type'\n};
+
+ ## The 'default default' is an empty string, which should fail all mandatory tests
+ ## We only set the 'arg' default if neither option is provided.
+ my $warning = exists $opt{warning} ? $opt{warning} :
+ exists $opt{critical} ? '' : $arg->{default_warning} || '';
+ my $critical = exists $opt{critical} ? $opt{critical} :
+ exists $opt{warning} ? '' : $arg->{default_critical} || '';
+
+ if ('string' eq $type) {
+ ## Don't use this unless you have to
+ }
+ elsif ('seconds' eq $type) {
+ if (length $warning) {
+ if ($warning !~ $timesecre) {
+ ndie msg('range-seconds', 'warning');
+ }
+ $warning = $1;
+ }
+ if (length $critical) {
+ if ($critical !~ $timesecre) {
+ ndie msg('range-seconds', 'critical')
+ }
+ $critical = $1;
+ if (length $warning and $warning > $critical) {
+ ndie msg('range-warnbigtime', $warning, $critical);
+ }
+ }
+ }
+ elsif ('time' eq $type) {
+ $critical = size_in_seconds($critical, 'critical');
+ $warning = size_in_seconds($warning, 'warning');
+ if (! length $critical and ! length $warning) {
+ ndie msg('range-notime');
+ }
+ if (length $warning and length $critical and $warning > $critical) {
+ ndie msg('range-warnbigtime', $warning, $critical);
+ }
+ }
+ elsif ('version' eq $type) {
+ my $msg = msg('range-version');
+ if (length $warning and $warning !~ /^\d+\.\d+\.?[\d\w]*$/) {
+ ndie msg('range-badversion', 'warning', $msg);
+ }
+ if (length $critical and $critical !~ /^\d+\.\d+\.?[\d\w]*$/) {
+ ndie msg('range-badversion', 'critical', $msg);
+ }
+ if (! length $critical and ! length $warning) {
+ ndie msg('range-noopt-orboth');
+ }
+ }
+ elsif ('size' eq $type) {
+ if (length $critical) {
+ if ($critical !~ $sizere) {
+ ndie msg('range-badsize', 'critical');
+ }
+ $critical = size_in_bytes($1,$2);
+ }
+ if (length $warning) {
+ if ($warning !~ $sizere) {
+ ndie msg('range-badsize', 'warning');
+ }
+ $warning = size_in_bytes($1,$2);
+ if (length $critical and $warning > $critical) {
+ ndie msg('range-warnbigsize', $warning, $critical);
+ }
+ }
+ elsif (!length $critical) {
+ ndie msg('range-nosize');
+ }
+ }
+ elsif ($type =~ /integer/) {
+ $warning =~ s/_//g;
+ if (length $warning and $warning !~ /^\d+$/) {
+ ndie $type =~ /positive/ ? msg('range-int-pos', 'warning') : msg('range-int', 'warning');
+ }
+ elsif (length $warning && $type =~ /positive/ && $warning <= 0) {
+ ndie msg('range-int-pos', 'warning');
+ }
+
+ $critical =~ s/_//g;
+ if (length $critical and $critical !~ /^\d+$/) {
+ ndie $type =~ /positive/ ? msg('range-int-pos', 'critical') : msg('range-int', 'critical');
+ }
+ elsif (length $critical && $type =~ /positive/ && $critical <= 0) {
+ ndie msg('range-int-pos', 'critical');
+ }
+
+ if (length $warning
+ and length $critical
+ and (
+ ($opt{reverse} and $warning < $critical)
+ or
+ (!$opt{reverse} and $warning > $critical)
+ )
+ ) {
+ ndie msg('range-warnbig');
+ }
+ }
+ elsif ('restringex' eq $type) {
+ if (! length $critical and ! length $warning) {
+ ndie msg('range-noopt-one');
+ }
+ if (length $critical and length $warning) {
+ ndie msg('range-noopt-only');
+ }
+ my $string = length $critical ? $critical : $warning;
+ my $regex = ($string =~ s/^~//) ? '~' : '=';
+ $string =~ /^\w+$/ or ndie msg('invalid-option');
+ }
+ elsif ('percent' eq $type) {
+ if (length $critical) {
+ if ($critical !~ /^\d+\%$/) {
+ ndie msg('range-badpercent', 'critical');
+ }
+ }
+ if (length $warning) {
+ if ($warning !~ /^\d+\%$/) {
+ ndie msg('range-badpercent', 'warning');
+ }
+ }
+ }
+ elsif ('size or percent' eq $type) {
+ if (length $critical) {
+ if ($critical =~ $sizere) {
+ $critical = size_in_bytes($1,$2);
+ }
+ elsif ($critical !~ /^\d+\%$/) {
+ ndie msg('range-badpercsize', 'critical');
+ }
+ }
+ if (length $warning) {
+ if ($warning =~ $sizere) {
+ $warning = size_in_bytes($1,$2);
+ }
+ elsif ($warning !~ /^\d+\%$/) {
+ ndie msg('range-badpercsize', 'warning');
+ }
+ }
+ elsif (! length $critical) {
+ ndie msg('range-noopt-size');
+ }
+ }
+ elsif ('checksum' eq $type) {
+ if (length $critical and $critical !~ $checksumre and $critical ne '0') {
+ ndie msg('range-badcs', 'critical');
+ }
+ if (length $warning and $warning !~ $checksumre) {
+ ndie msg('range-badcs', 'warning');
+ }
+ }
+ elsif ('multival' eq $type) { ## Simple number, or foo=#;bar=#
+ ## Note: only used for check_locks
+ my %err;
+ while ($critical =~ /(\w+)\s*=\s*(\d+)/gi) {
+ my ($name,$val) = (lc $1,$2);
+ $name =~ s/lock$//;
+ $err{$name} = $val;
+ }
+ if (keys %err) {
+ $critical = \%err;
+ }
+ elsif (length $critical and $critical =~ /^(\d+)$/) {
+ $err{total} = $1;
+ $critical = \%err;
+ }
+ elsif (length $critical) {
+ ndie msg('range-badlock', 'critical');
+ }
+ my %warn;
+ while ($warning =~ /(\w+)\s*=\s*(\d+)/gi) {
+ my ($name,$val) = (lc $1,$2);
+ $name =~ s/lock$//;
+ $warn{$name} = $val;
+ }
+ if (keys %warn) {
+ $warning = \%warn;
+ }
+ elsif (length $warning and $warning =~ /^(\d+)$/) {
+ $warn{total} = $1;
+ $warning = \%warn;
+ }
+ elsif (length $warning) {
+ ndie msg('range-badlock', 'warning');
+ }
+ }
+ elsif ('cacti' eq $type) { ## Takes no args, just dumps data
+ if (length $warning or length $critical) {
+ ndie msg('range-cactionly');
+ }
+ }
+ else {
+ ndie msg('range-badtype', $type);
+ }
+
+ if ($arg->{both}) {
+ if (! length $warning or ! length $critical) {
+ ndie msg('range-noopt-both');
+ }
+ }
+ if ($arg->{leastone}) {
+ if (! length $warning and ! length $critical) {
+ ndie msg('range-noopt-one');
+ }
+ }
+ elsif ($arg->{onlyone}) {
+ if (length $warning and length $critical) {
+ ndie msg('range-noopt-only');
+ }
+ if (! length $warning and ! length $critical) {
+ ndie msg('range-noopt-one');
+ }
+ }
+
+ return ($warning,$critical);
} ## end of validate_range
sub check_autovac_freeze {
- ## Check how close all databases are to autovacuum_freeze_max_age
- ## Supports: Nagios, MRTG
- ## It makes no sense to run this more than once on the same cluster
- ## Warning and criticals are percentages
- ## Can also ignore databases with exclude, and limit with include
+ ## Check how close all databases are to autovacuum_freeze_max_age
+ ## Supports: Nagios, MRTG
+ ## It makes no sense to run this more than once on the same cluster
+ ## Warning and criticals are percentages
+ ## Can also ignore databases with exclude, and limit with include
- my ($warning, $critical) = validate_range
- ({
- type => 'percent',
- default_warning => '90%',
- default_critical => '95%',
- forcemrtg => 1,
- });
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'percent',
+ default_warning => '90%',
+ default_critical => '95%',
+ forcemrtg => 1,
+ });
- (my $w = $warning) =~ s/\D//;
- (my $c = $critical) =~ s/\D//;
+ (my $w = $warning) =~ s/\D//;
+ (my $c = $critical) =~ s/\D//;
- my $SQL = q{SELECT freez, txns, ROUND(100*(txns/freez::float)) AS perc, datname}.
- q{ FROM (SELECT foo.freez::int, age(datfrozenxid) AS txns, datname}.
- q{ FROM pg_database d JOIN (SELECT setting AS freez FROM pg_settings WHERE name = 'autovacuum_freeze_max_age') AS foo}.
- q{ ON (true) WHERE d.datallowconn) AS foo2 ORDER BY 3 DESC, 4 ASC};
+ my $SQL = q{SELECT freez, txns, ROUND(100*(txns/freez::float)) AS perc, datname}.
+ q{ FROM (SELECT foo.freez::int, age(datfrozenxid) AS txns, datname}.
+ q{ FROM pg_database d JOIN (SELECT setting AS freez FROM pg_settings WHERE name = 'autovacuum_freeze_max_age') AS foo}.
+ q{ ON (true) WHERE d.datallowconn) AS foo2 ORDER BY 3 DESC, 4 ASC};
- my $info = run_command($SQL, {regex => qr{\w+} } );
+ my $info = run_command($SQL, {regex => qr{\w+} } );
- $db = $info->{db}[0];
+ $db = $info->{db}[0];
- my (@crit,@warn,@ok);
- my ($maxp,$maxt,$maxdb) = (0,0,''); ## used by MRTG only
+ my (@crit,@warn,@ok);
+ my ($maxp,$maxt,$maxdb) = (0,0,''); ## used by MRTG only
SLURP: for my $r (@{$db->{slurp}}) {
- next SLURP if skip_item($r->{datname});
-
- if ($MRTG) {
- if ($r->{perc} > $maxp) {
- $maxdb = $r->{datname};
- $maxp = $r->{perc};
- }
- elsif ($r->{perc} == $maxp) {
- $maxdb .= sprintf '%s%s', (length $maxdb ? ' | ' : ''), $r->{datname};
- }
- $maxt = $r->{tnxs} if $r->{txns} > $maxt;
- next SLURP;
- }
-
- my $msg = "'$r->{datname}'=$r->{perc}\%;$w;$c";
- $db->{perf} .= " $msg";
- if (length $critical and $r->{perc} >= $c) {
- push @crit => $msg;
- }
- elsif (length $warning and $r->{perc} >= $w) {
- push @warn => $msg;
- }
- else {
- push @ok => $msg;
- }
- }
- if ($MRTG) {
- do_mrtg({one => $maxp, two => $maxt, msg => $maxdb});
- }
- if (@crit) {
- add_critical join ' ' => @crit;
- }
- elsif (@warn) {
- add_warning join ' ' => @warn;
- }
- else {
- add_ok join ' ' => @ok;
- }
-
- return;
+ next SLURP if skip_item($r->{datname});
+
+ if ($MRTG) {
+ if ($r->{perc} > $maxp) {
+ $maxdb = $r->{datname};
+ $maxp = $r->{perc};
+ }
+ elsif ($r->{perc} == $maxp) {
+ $maxdb .= sprintf '%s%s', (length $maxdb ? ' | ' : ''), $r->{datname};
+ }
+ $maxt = $r->{tnxs} if $r->{txns} > $maxt;
+ next SLURP;
+ }
+
+ my $msg = "'$r->{datname}'=$r->{perc}\%;$w;$c";
+ $db->{perf} .= " $msg";
+ if (length $critical and $r->{perc} >= $c) {
+ push @crit => $msg;
+ }
+ elsif (length $warning and $r->{perc} >= $w) {
+ push @warn => $msg;
+ }
+ else {
+ push @ok => $msg;
+ }
+ }
+ if ($MRTG) {
+ do_mrtg({one => $maxp, two => $maxt, msg => $maxdb});
+ }
+ if (@crit) {
+ add_critical join ' ' => @crit;
+ }
+ elsif (@warn) {
+ add_warning join ' ' => @warn;
+ }
+ else {
+ add_ok join ' ' => @ok;
+ }
+
+ return;
} ## end of check_autovac_freeze
sub check_backends {
- ## Check the number of connections
- ## Supports: Nagios, MRTG
- ## It makes no sense to run this more than once on the same cluster
- ## Need to be superuser, else only your queries will be visible
- ## Warning and criticals can take three forms:
- ## critical = 12 -- complain if there are 12 or more connections
- ## critical = 95% -- complain if >= 95% of available connections are used
- ## critical = -5 -- complain if there are only 5 or fewer connection slots left
- ## The former two options only work with simple numbers - no percentage or negative
- ## Can also ignore databases with exclude, and limit with include
-
- my $warning = $opt{warning} || '90%';
- my $critical = $opt{critical} || '95%';
- my $noidle = $opt{noidle} || 0;
-
- ## If only critical was used, remove the default warning
- if ($opt{critical} and !$opt{warning}) {
- $warning = $critical;
- }
-
- my $validre = qr{^(\-?)(\d+)(\%?)$};
- if ($critical !~ $validre) {
- ndie msg('backends-users', 'Critical');
- }
- my ($e1,$e2,$e3) = ($1,$2,$3);
- if ($warning !~ $validre) {
- ndie msg('backends-users', 'Warning');
- }
- my ($w1,$w2,$w3) = ($1,$2,$3);
-
- ## If number is greater, all else is same, and not minus
- if ($w2 > $e2 and $w1 eq $e1 and $w3 eq $e3 and $w1 eq '') {
- ndie msg('range-warnbig');
- }
- ## If number is less, all else is same, and minus
- if ($w2 < $e2 and $w1 eq $e1 and $w3 eq $e3 and $w1 eq '-') {
- ndie msg('range-warnsmall');
- }
- if (($w1 and $w3) or ($e1 and $e3)) {
- ndie msg('range-neg-percent');
- }
-
- my $MAXSQL = q{SELECT setting AS mc FROM pg_settings WHERE name = 'max_connections'};
-
- my $NOIDLE = $noidle ? q{WHERE current_query <> '<IDLE>'} : '';
- $SQL = qq{
+ ## Check the number of connections
+ ## Supports: Nagios, MRTG
+ ## It makes no sense to run this more than once on the same cluster
+ ## Need to be superuser, else only your queries will be visible
+ ## Warning and criticals can take three forms:
+ ## critical = 12 -- complain if there are 12 or more connections
+ ## critical = 95% -- complain if >= 95% of available connections are used
+ ## critical = -5 -- complain if there are only 5 or fewer connection slots left
+ ## The former two options only work with simple numbers - no percentage or negative
+ ## Can also ignore databases with exclude, and limit with include
+
+ my $warning = $opt{warning} || '90%';
+ my $critical = $opt{critical} || '95%';
+ my $noidle = $opt{noidle} || 0;
+
+ ## If only critical was used, remove the default warning
+ if ($opt{critical} and !$opt{warning}) {
+ $warning = $critical;
+ }
+
+ my $validre = qr{^(\-?)(\d+)(\%?)$};
+ if ($critical !~ $validre) {
+ ndie msg('backends-users', 'Critical');
+ }
+ my ($e1,$e2,$e3) = ($1,$2,$3);
+ if ($warning !~ $validre) {
+ ndie msg('backends-users', 'Warning');
+ }
+ my ($w1,$w2,$w3) = ($1,$2,$3);
+
+ ## If number is greater, all else is same, and not minus
+ if ($w2 > $e2 and $w1 eq $e1 and $w3 eq $e3 and $w1 eq '') {
+ ndie msg('range-warnbig');
+ }
+ ## If number is less, all else is same, and minus
+ if ($w2 < $e2 and $w1 eq $e1 and $w3 eq $e3 and $w1 eq '-') {
+ ndie msg('range-warnsmall');
+ }
+ if (($w1 and $w3) or ($e1 and $e3)) {
+ ndie msg('range-neg-percent');
+ }
+
+ my $MAXSQL = q{SELECT setting AS mc FROM pg_settings WHERE name = 'max_connections'};
+
+ my $NOIDLE = $noidle ? q{WHERE current_query <> '<IDLE>'} : '';
+ $SQL = qq{
SELECT COUNT(datid) AS current,
($MAXSQL) AS mc,
d.datname
GROUP BY 2,3
ORDER BY datname
};
- my $info = run_command($SQL, {regex => qr{\d+}, fatalregex => 'too many clients' } );
-
- $db = $info->{db}[0];
-
- ## If we cannot connect because of too many clients, we treat as a critical error
- if (exists $info->{fatalregex}) {
- my $regmsg = msg('backends-po');
- my $regmsg2 = msg_en('backends-po');
- if ($info->{fatalregex} =~ /$regmsg/ or $info->{fatalregex} =~ /$regmsg2/) {
- add_critical msg('backends-fatal');
- return;
- }
- }
-
- ## There may be no entries returned if we catch pg_stat_activity at the right
- ## moment in older versions of Postgres
- if (! defined $db) {
- $info = run_command($MAXSQL, {regex => qr[\d] } );
- $db = $info->{db}[0];
- if (!defined $db->{slurp} or $db->{slurp} !~ /(\d+)/) {
- undef %unknown;
- add_unknown msg('backends-nomax');
- return;
- }
- my $limit = $1;
- if ($MRTG) {
- do_mrtg({one => 1, msg => msg('backends-mrtg', $db->{dbname}, $limit)});
- }
- my $percent = (int 1/$limit*100) || 1;
- add_ok msg('backends-msg', 1, $limit, $percent);
- return;
- }
-
- my $total = 0;
- my $grandtotal = @{$db->{slurp}};
-
- ## If no max_connections, something is wrong
- if ($db->{slurp}[0]{mc} !~ /\d/) {
- add_unknown msg('backends-nomax');
- return;
- }
- my $limit = $db->{slurp}[0]{mc};
-
- for my $r (@{$db->{slurp}}) {
-
- ## Always want perf to show all
- my $nwarn=$w2;
- my $ncrit=$e2;
- if ($e1) {
- $ncrit = $limit-$e2;
- }
- elsif ($e3) {
- $ncrit = (int $e2*$limit/100);
- }
- if ($w1) {
- $nwarn = $limit-$w2;
- }
- elsif ($w3) {
- $nwarn = (int $w2*$limit/100)
- }
- $db->{perf} .= " '$r->{datname}'=$r->{current};$nwarn;$ncrit;0;$limit";
-
- if (! skip_item($r->{datname})) {
- $total += $r->{current};
- }
- }
-
- if ($MRTG) {
- $stats{$db->{dbname}} = $total;
- $statsmsg{$db->{dbname}} = msg('backends-mrtg', $db->{dbname}, $limit);
- return;
- }
-
- if (!$total) {
- if ($grandtotal) {
- ## We assume that exclude/include rules are correct, and we simply had no entries
- ## at all in the specific databases we wanted
- add_ok msg('backends-oknone');
- }
- else {
- add_unknown msg('no-match-db');
- }
- return;
- }
-
- my $percent = (int $total / $limit*100) || 1;
- my $msg = msg('backends-msg', $total, $limit, $percent);
- my $ok = 1;
- if ($e1) { ## minus
- $ok = 0 if $limit-$total >= $e2;
- }
- elsif ($e3) { ## percent
- my $nowpercent = $total/$limit*100;
- $ok = 0 if $nowpercent >= $e2;
- }
- else { ## raw number
- $ok = 0 if $total >= $e2;
- }
- if (!$ok) {
- add_critical $msg;
- return;
- }
-
- if ($w1) {
- $ok = 0 if $limit-$total >= $w2;
- }
- elsif ($w3) {
- my $nowpercent = $total/$limit*100;
- $ok = 0 if $nowpercent >= $w2;
- }
- else {
- $ok = 0 if $total >= $w2;
- }
- if (!$ok) {
- add_warning $msg;
- return;
- }
-
- add_ok $msg;
-
- return;
+ my $info = run_command($SQL, {regex => qr{\d+}, fatalregex => 'too many clients' } );
+
+ $db = $info->{db}[0];
+
+ ## If we cannot connect because of too many clients, we treat as a critical error
+ if (exists $info->{fatalregex}) {
+ my $regmsg = msg('backends-po');
+ my $regmsg2 = msg_en('backends-po');
+ if ($info->{fatalregex} =~ /$regmsg/ or $info->{fatalregex} =~ /$regmsg2/) {
+ add_critical msg('backends-fatal');
+ return;
+ }
+ }
+
+ ## There may be no entries returned if we catch pg_stat_activity at the right
+ ## moment in older versions of Postgres
+ if (! defined $db) {
+ $info = run_command($MAXSQL, {regex => qr[\d] } );
+ $db = $info->{db}[0];
+ if (!defined $db->{slurp} or $db->{slurp} !~ /(\d+)/) {
+ undef %unknown;
+ add_unknown msg('backends-nomax');
+ return;
+ }
+ my $limit = $1;
+ if ($MRTG) {
+ do_mrtg({one => 1, msg => msg('backends-mrtg', $db->{dbname}, $limit)});
+ }
+ my $percent = (int 1/$limit*100) || 1;
+ add_ok msg('backends-msg', 1, $limit, $percent);
+ return;
+ }
+
+ my $total = 0;
+ my $grandtotal = @{$db->{slurp}};
+
+ ## If no max_connections, something is wrong
+ if ($db->{slurp}[0]{mc} !~ /\d/) {
+ add_unknown msg('backends-nomax');
+ return;
+ }
+ my $limit = $db->{slurp}[0]{mc};
+
+ for my $r (@{$db->{slurp}}) {
+
+ ## Always want perf to show all
+ my $nwarn=$w2;
+ my $ncrit=$e2;
+ if ($e1) {
+ $ncrit = $limit-$e2;
+ }
+ elsif ($e3) {
+ $ncrit = (int $e2*$limit/100);
+ }
+ if ($w1) {
+ $nwarn = $limit-$w2;
+ }
+ elsif ($w3) {
+ $nwarn = (int $w2*$limit/100)
+ }
+ $db->{perf} .= " '$r->{datname}'=$r->{current};$nwarn;$ncrit;0;$limit";
+
+ if (! skip_item($r->{datname})) {
+ $total += $r->{current};
+ }
+ }
+
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $total;
+ $statsmsg{$db->{dbname}} = msg('backends-mrtg', $db->{dbname}, $limit);
+ return;
+ }
+
+ if (!$total) {
+ if ($grandtotal) {
+ ## We assume that exclude/include rules are correct, and we simply had no entries
+ ## at all in the specific databases we wanted
+ add_ok msg('backends-oknone');
+ }
+ else {
+ add_unknown msg('no-match-db');
+ }
+ return;
+ }
+
+ my $percent = (int $total / $limit*100) || 1;
+ my $msg = msg('backends-msg', $total, $limit, $percent);
+ my $ok = 1;
+ if ($e1) { ## minus
+ $ok = 0 if $limit-$total >= $e2;
+ }
+ elsif ($e3) { ## percent
+ my $nowpercent = $total/$limit*100;
+ $ok = 0 if $nowpercent >= $e2;
+ }
+ else { ## raw number
+ $ok = 0 if $total >= $e2;
+ }
+ if (!$ok) {
+ add_critical $msg;
+ return;
+ }
+
+ if ($w1) {
+ $ok = 0 if $limit-$total >= $w2;
+ }
+ elsif ($w3) {
+ my $nowpercent = $total/$limit*100;
+ $ok = 0 if $nowpercent >= $w2;
+ }
+ else {
+ $ok = 0 if $total >= $w2;
+ }
+ if (!$ok) {
+ add_warning $msg;
+ return;
+ }
+
+ add_ok $msg;
+
+ return;
} ## end of check_backends
sub check_bloat {
- ## Check how bloated the tables and indexes are
- ## Supports: Nagios, MRTG
- ## NOTE! This check depends on ANALYZE being run regularly
- ## Also requires stats collection to be on
- ## This action may be very slow on large databases
- ## By default, checks all relations
- ## Can check specific one(s) with include; can ignore some with exclude
- ## Begin name with a '~' to make it a regular expression
- ## Warning and critical are in sizes, defaults to bytes
- ## Valid units: b, k, m, g, t, e
- ## All above may be written as plural or with a trailing 'b'
- ## Example: --critical="25 GB" --include="mylargetable"
- ## Can also specify percentages
-
- ## Don't bother with tables or indexes unless they have at least this many bloated pages
- my $MINPAGES = 0;
- my $MINIPAGES = 10;
-
- my $LIMIT = 10;
- if ($opt{perflimit}) {
- $LIMIT = $opt{perflimit};
- }
-
- my ($warning, $critical) = validate_range
- ({
- type => 'size or percent',
- default_warning => '1 GB',
- default_critical => '5 GB',
- });
-
- ## This was fun to write
- $SQL = q{
+ ## Check how bloated the tables and indexes are
+ ## Supports: Nagios, MRTG
+ ## NOTE! This check depends on ANALYZE being run regularly
+ ## Also requires stats collection to be on
+ ## This action may be very slow on large databases
+ ## By default, checks all relations
+ ## Can check specific one(s) with include; can ignore some with exclude
+ ## Begin name with a '~' to make it a regular expression
+ ## Warning and critical are in sizes, defaults to bytes
+ ## Valid units: b, k, m, g, t, e
+ ## All above may be written as plural or with a trailing 'b'
+ ## Example: --critical="25 GB" --include="mylargetable"
+ ## Can also specify percentages
+
+ ## Don't bother with tables or indexes unless they have at least this many bloated pages
+ my $MINPAGES = 0;
+ my $MINIPAGES = 10;
+
+ my $LIMIT = 10;
+ if ($opt{perflimit}) {
+ $LIMIT = $opt{perflimit};
+ }
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'size or percent',
+ default_warning => '1 GB',
+ default_critical => '5 GB',
+ });
+
+ ## This was fun to write
+ $SQL = q{
SELECT
current_database() AS db, schemaname, tablename, reltuples::bigint AS tups, relpages::bigint AS pages, otta,
ROUND(CASE WHEN otta=0 THEN 0.0 ELSE sml.relpages/otta::numeric END,1) AS tbloat,
) AS sml
};
- if (! defined $opt{include} and ! defined $opt{exclude}) {
- $SQL .= " WHERE sml.relpages - otta > $MINPAGES OR ipages - iotta > $MINIPAGES";
- $SQL .= " ORDER BY wastedbytes DESC LIMIT $LIMIT";
- }
- else {
- $SQL .= ' ORDER BY wastedbytes DESC';
- }
-
- if ($psql_version <= 7.4) {
- $SQL =~ s/BLOCK_SIZE/(SELECT 8192) AS bs/;
- }
- else {
- $SQL =~ s/BLOCK_SIZE/(SELECT current_setting('block_size')::numeric) AS bs/;
- }
-
- my $info = run_command($SQL);
-
- if (defined $info->{db}[0] and exists $info->{db}[0]{error}) {
- ndie $info->{db}[0]{error};
- }
-
- my %seenit;
- for $db (@{$info->{db}}) {
- if ($db->{slurp}[0] !~ /\w+/o) {
- add_ok msg('bloat-nomin') unless $MRTG;
- return;
- }
- ## Not a 'regex' to run_command as we need to check the above first.
- if ($db->{slurp}[0] !~ /\d+/) {
- add_unknown msg('invalid-query', $db->{slurp}) unless $MRTG;
- return;
- }
-
- my $max = -1;
- my $maxmsg = '?';
-
- for my $r (@{$db->{slurp}}) {
-
- my ($dbname,$schema,$table,$tups,$pages,$otta,$bloat,$wp,$wb,$ws) = @$r{
- qw/ db schemaname tablename tups pages otta tbloat wastedpages wastedbytes wastedsize/};
- my ($index,$irows,$ipages,$iotta,$ibloat,$iwp,$iwb,$iws) = @$r{
- qw/ iname irows ipages iotta ibloat wastedipgaes wastedibytes wastedisize/};
-
- next if skip_item($table, $schema);
-
- ## Made it past the exclusions
- $max = -2 if $max == -1;
-
- for my $v (values %$r) {
- $v =~ s/\| (\d+) bytes/'| ' . pretty_size($1,1)/ge;
- }
-
- ## Do the table first if we haven't seen it
- if (! $seenit{"$dbname.$schema.$table"}++) {
- $db->{perf} .= " $schema.$table=$wb";
- my $msg = msg('bloat-table', $dbname, $schema, $table, $tups, $pages, $otta, $bloat, $wb, $ws);
- my $ok = 1;
- my $perbloat = $bloat * 100;
-
- if ($MRTG) {
- $stats{table}{"DB=$dbname TABLE=$schema.$table"} = [$wb, $bloat];
- next;
- }
- if (length $critical) {
- if (index($critical,'%')>=0) {
- (my $critical2 = $critical) =~ s/\%//;
- if ($perbloat >= $critical2) {
- add_critical $msg;
- $ok = 0;
- }
- }
- elsif ($wb >= $critical) {
- add_critical $msg;
- $ok = 0;
- }
- }
-
- if (length $warning and $ok) {
- if (index($warning,'%')>=0) {
- (my $warning2 = $warning) =~ s/\%//;
- if ($perbloat >= $warning2) {
- add_warning $msg;
- $ok = 0;
- }
- }
- elsif ($wb >= $warning) {
- add_warning $msg;
- $ok = 0;
- }
- }
- ($max = $wb, $maxmsg = $msg) if $wb > $max and $ok;
- }
-
- ## Now the index, if it exists
- if ($index ne '?') {
- $db->{perf} .= " $index=$iwb" if $iwb;
- my $msg = msg('bloat-index', $dbname, $index, $irows, $ipages, $iotta, $ibloat, $iwb, $iws);
- my $ok = 1;
- my $iperbloat = $ibloat * 100;
-
- if ($MRTG) {
- $stats{index}{"DB=$dbname INDEX=$index"} = [$iwb, $ibloat];
- next;
- }
- if (length $critical) {
- if (index($critical,'%')>=0) {
- (my $critical2 = $critical) =~ s/\%//;
- if ($iperbloat >= $critical2) {
- add_critical $msg;
- $ok = 0;
- }
- }
- elsif ($iwb >= $critical) {
- add_critical $msg;
- $ok = 0;
- }
- }
-
- if (length $warning and $ok) {
- if (index($warning,'%')>=0) {
- (my $warning2 = $warning) =~ s/\%//;
- if ($iperbloat >= $warning2) {
- add_warning $msg;
- $ok = 0;
- }
- }
- elsif ($iwb >= $warning) {
- add_warning $msg;
- $ok = 0;
- }
- }
-
- ($max = $iwb, $maxmsg = $msg) if $iwb > $max and $ok;
- }
- }
- if ($max == -1) {
- add_unknown msg('no-match-rel');
- }
- elsif ($max != -1) {
- add_ok $maxmsg;
- }
- }
-
- if ($MRTG) {
- keys %stats or bad_mrtg(msg('unknown-error'));
- ## We are going to report the highest wasted bytes for table and index
- my ($one,$two,$msg) = ('','');
- ## Can also sort by ratio
- my $sortby = exists $opt{mrtg} and $opt{mrtg} eq 'ratio' ? 1 : 0;
- for (sort { $stats{table}{$b}->[$sortby] <=> $stats{table}{$a}->[$sortby] } keys %{$stats{table}}) {
- $one = $stats{table}{$_}->[$sortby];
- $msg = $_;
- last;
- }
- for (sort { $stats{index}{$b}->[$sortby] <=> $stats{index}{$a}->[$sortby] } keys %{$stats{index}}) {
- $two = $stats{index}{$_}->[$sortby];
- $msg .= " $_";
- last;
- }
- do_mrtg({one => $one, two => $two, msg => $msg});
- }
-
- return;
+ if (! defined $opt{include} and ! defined $opt{exclude}) {
+ $SQL .= " WHERE sml.relpages - otta > $MINPAGES OR ipages - iotta > $MINIPAGES";
+ $SQL .= " ORDER BY wastedbytes DESC LIMIT $LIMIT";
+ }
+ else {
+ $SQL .= ' ORDER BY wastedbytes DESC';
+ }
+
+ if ($psql_version <= 7.4) {
+ $SQL =~ s/BLOCK_SIZE/(SELECT 8192) AS bs/;
+ }
+ else {
+ $SQL =~ s/BLOCK_SIZE/(SELECT current_setting('block_size')::numeric) AS bs/;
+ }
+
+ my $info = run_command($SQL);
+
+ if (defined $info->{db}[0] and exists $info->{db}[0]{error}) {
+ ndie $info->{db}[0]{error};
+ }
+
+ my %seenit;
+ for $db (@{$info->{db}}) {
+ if ($db->{slurp}[0] !~ /\w+/o) {
+ add_ok msg('bloat-nomin') unless $MRTG;
+ return;
+ }
+ ## Not a 'regex' to run_command as we need to check the above first.
+ if ($db->{slurp}[0] !~ /\d+/) {
+ add_unknown msg('invalid-query', $db->{slurp}) unless $MRTG;
+ return;
+ }
+
+ my $max = -1;
+ my $maxmsg = '?';
+
+ for my $r (@{$db->{slurp}}) {
+
+ my ($dbname,$schema,$table,$tups,$pages,$otta,$bloat,$wp,$wb,$ws) = @$r{
+ qw/ db schemaname tablename tups pages otta tbloat wastedpages wastedbytes wastedsize/};
+ my ($index,$irows,$ipages,$iotta,$ibloat,$iwp,$iwb,$iws) = @$r{
+ qw/ iname irows ipages iotta ibloat wastedipgaes wastedibytes wastedisize/};
+
+ next if skip_item($table, $schema);
+
+ ## Made it past the exclusions
+ $max = -2 if $max == -1;
+
+ for my $v (values %$r) {
+ $v =~ s/\| (\d+) bytes/'| ' . pretty_size($1,1)/ge;
+ }
+
+ ## Do the table first if we haven't seen it
+ if (! $seenit{"$dbname.$schema.$table"}++) {
+ $db->{perf} .= " $schema.$table=$wb";
+ my $msg = msg('bloat-table', $dbname, $schema, $table, $tups, $pages, $otta, $bloat, $wb, $ws);
+ my $ok = 1;
+ my $perbloat = $bloat * 100;
+
+ if ($MRTG) {
+ $stats{table}{"DB=$dbname TABLE=$schema.$table"} = [$wb, $bloat];
+ next;
+ }
+ if (length $critical) {
+ if (index($critical,'%')>=0) {
+ (my $critical2 = $critical) =~ s/\%//;
+ if ($perbloat >= $critical2) {
+ add_critical $msg;
+ $ok = 0;
+ }
+ }
+ elsif ($wb >= $critical) {
+ add_critical $msg;
+ $ok = 0;
+ }
+ }
+
+ if (length $warning and $ok) {
+ if (index($warning,'%')>=0) {
+ (my $warning2 = $warning) =~ s/\%//;
+ if ($perbloat >= $warning2) {
+ add_warning $msg;
+ $ok = 0;
+ }
+ }
+ elsif ($wb >= $warning) {
+ add_warning $msg;
+ $ok = 0;
+ }
+ }
+ ($max = $wb, $maxmsg = $msg) if $wb > $max and $ok;
+ }
+
+ ## Now the index, if it exists
+ if ($index ne '?') {
+ $db->{perf} .= " $index=$iwb" if $iwb;
+ my $msg = msg('bloat-index', $dbname, $index, $irows, $ipages, $iotta, $ibloat, $iwb, $iws);
+ my $ok = 1;
+ my $iperbloat = $ibloat * 100;
+
+ if ($MRTG) {
+ $stats{index}{"DB=$dbname INDEX=$index"} = [$iwb, $ibloat];
+ next;
+ }
+ if (length $critical) {
+ if (index($critical,'%')>=0) {
+ (my $critical2 = $critical) =~ s/\%//;
+ if ($iperbloat >= $critical2) {
+ add_critical $msg;
+ $ok = 0;
+ }
+ }
+ elsif ($iwb >= $critical) {
+ add_critical $msg;
+ $ok = 0;
+ }
+ }
+
+ if (length $warning and $ok) {
+ if (index($warning,'%')>=0) {
+ (my $warning2 = $warning) =~ s/\%//;
+ if ($iperbloat >= $warning2) {
+ add_warning $msg;
+ $ok = 0;
+ }
+ }
+ elsif ($iwb >= $warning) {
+ add_warning $msg;
+ $ok = 0;
+ }
+ }
+
+ ($max = $iwb, $maxmsg = $msg) if $iwb > $max and $ok;
+ }
+ }
+ if ($max == -1) {
+ add_unknown msg('no-match-rel');
+ }
+ elsif ($max != -1) {
+ add_ok $maxmsg;
+ }
+ }
+
+ if ($MRTG) {
+ keys %stats or bad_mrtg(msg('unknown-error'));
+ ## We are going to report the highest wasted bytes for table and index
+ my ($one,$two,$msg) = ('','');
+ ## Can also sort by ratio
+ my $sortby = exists $opt{mrtg} and $opt{mrtg} eq 'ratio' ? 1 : 0;
+ for (sort { $stats{table}{$b}->[$sortby] <=> $stats{table}{$a}->[$sortby] } keys %{$stats{table}}) {
+ $one = $stats{table}{$_}->[$sortby];
+ $msg = $_;
+ last;
+ }
+ for (sort { $stats{index}{$b}->[$sortby] <=> $stats{index}{$a}->[$sortby] } keys %{$stats{index}}) {
+ $two = $stats{index}{$_}->[$sortby];
+ $msg .= " $_";
+ last;
+ }
+ do_mrtg({one => $one, two => $two, msg => $msg});
+ }
+
+ return;
} ## end of check_bloat
sub check_checkpoint {
- ## Checks how long in seconds since the last checkpoint on a WAL slave
- ## Supports: Nagios, MRTG
- ## Warning and critical are seconds
- ## Requires $ENV{PGDATA} or --datadir
-
- my ($warning, $critical) = validate_range
- ({
- type => 'time',
- leastone => 1,
- forcemrtg => 1,
- });
-
- ## Find the data directory, make sure it exists
- my $dir = $opt{datadir} || $ENV{PGDATA};
-
- if (!defined $dir or ! length $dir) {
- ndie msg('checkpoint-nodir');
- }
-
- if (! -d $dir) {
- ndie msg('checkpoint-baddir', $dir);
- }
-
- $db->{host} = '<none>';
-
- ## Run pg_controldata, grab the time
- my $pgc
- = $ENV{PGCONTROLDATA} ? $ENV{PGCONTROLDATA}
- : $ENV{PGBINDIR} ? "$ENV{PGBINDIR}/pg_controldata"
- : 'pg_controldata';
- $COM = qq{$pgc "$dir"};
- eval {
- $res = qx{$COM 2>&1};
- };
- if ($@) {
- ndie msg('checkpoint-nosys', $@);
- }
-
- ## If the path is echoed back, we most likely have an invalid data dir
- if ($res =~ /$dir/) {
- ndie msg('checkpoint-baddir2', $dir);
- }
-
- if ($res =~ /WARNING: Calculated CRC checksum/) {
- ndie msg('checkpoint-badver');
- }
- if ($res !~ /^pg_control.+\d+/) {
- ndie msg('checkpoint-badver2');
- }
-
- my $regex = msg('checkpoint-po');
- if ($res !~ /$regex\s*(.+)/) { ## no critic (ProhibitUnusedCapture)
- ## Just in case, check the English one as well
- $regex = msg_en('checkpoint-po');
- if ($res !~ /$regex\s*(.+)/) {
- ndie msg('checkpoint-noregex', $dir);
- }
- }
- my $last = $1;
-
- ## Convert to number of seconds
- eval {
- require Date::Parse;
- import Date::Parse;
- };
- if ($@) {
- ndie msg('checkpoint-nodp');
- }
- my $dt = str2time($last);
- if ($dt !~ /^\d+$/) {
- ndie msg('checkpoint-noparse', $last);
- }
- my $diff = $db->{perf} = time - $dt;
- my $msg = $diff==1 ? msg('checkpoint-ok') : msg('checkpoint-ok2', $diff);
-
- if ($MRTG) {
- do_mrtg({one => $diff, msg => $msg});
- }
-
- if (length $critical and $diff >= $critical) {
- add_critical $msg;
- return;
- }
-
- if (length $warning and $diff >= $warning) {
- add_warning $msg;
- return;
- }
-
- add_ok $msg;
-
- return;
+ ## Checks how long in seconds since the last checkpoint on a WAL slave
+ ## Supports: Nagios, MRTG
+ ## Warning and critical are seconds
+ ## Requires $ENV{PGDATA} or --datadir
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'time',
+ leastone => 1,
+ forcemrtg => 1,
+ });
+
+ ## Find the data directory, make sure it exists
+ my $dir = $opt{datadir} || $ENV{PGDATA};
+
+ if (!defined $dir or ! length $dir) {
+ ndie msg('checkpoint-nodir');
+ }
+
+ if (! -d $dir) {
+ ndie msg('checkpoint-baddir', $dir);
+ }
+
+ $db->{host} = '<none>';
+
+ ## Run pg_controldata, grab the time
+ my $pgc
+ = $ENV{PGCONTROLDATA} ? $ENV{PGCONTROLDATA}
+ : $ENV{PGBINDIR} ? "$ENV{PGBINDIR}/pg_controldata"
+ : 'pg_controldata';
+ $COM = qq{$pgc "$dir"};
+ eval {
+ $res = qx{$COM 2>&1};
+ };
+ if ($@) {
+ ndie msg('checkpoint-nosys', $@);
+ }
+
+ ## If the path is echoed back, we most likely have an invalid data dir
+ if ($res =~ /$dir/) {
+ ndie msg('checkpoint-baddir2', $dir);
+ }
+
+ if ($res =~ /WARNING: Calculated CRC checksum/) {
+ ndie msg('checkpoint-badver');
+ }
+ if ($res !~ /^pg_control.+\d+/) {
+ ndie msg('checkpoint-badver2');
+ }
+
+ my $regex = msg('checkpoint-po');
+ if ($res !~ /$regex\s*(.+)/) { ## no critic (ProhibitUnusedCapture)
+ ## Just in case, check the English one as well
+ $regex = msg_en('checkpoint-po');
+ if ($res !~ /$regex\s*(.+)/) {
+ ndie msg('checkpoint-noregex', $dir);
+ }
+ }
+ my $last = $1;
+
+ ## Convert to number of seconds
+ eval {
+ require Date::Parse;
+ import Date::Parse;
+ };
+ if ($@) {
+ ndie msg('checkpoint-nodp');
+ }
+ my $dt = str2time($last);
+ if ($dt !~ /^\d+$/) {
+ ndie msg('checkpoint-noparse', $last);
+ }
+ my $diff = $db->{perf} = time - $dt;
+ my $msg = $diff==1 ? msg('checkpoint-ok') : msg('checkpoint-ok2', $diff);
+
+ if ($MRTG) {
+ do_mrtg({one => $diff, msg => $msg});
+ }
+
+ if (length $critical and $diff >= $critical) {
+ add_critical $msg;
+ return;
+ }
+
+ if (length $warning and $diff >= $warning) {
+ add_warning $msg;
+ return;
+ }
+
+ add_ok $msg;
+
+ return;
} ## end of check_checkpoint
sub check_connection {
- ## Check the connection, get the connection time and version
- ## No comparisons made: warning and critical are not allowed
- ## Suports: Nagios, MRTG
+ ## Check the connection, get the connection time and version
+ ## No comparisons made: warning and critical are not allowed
+ ## Suports: Nagios, MRTG
- if ($opt{warning} or $opt{critical}) {
- ndie msg('range-none');
- }
+ if ($opt{warning} or $opt{critical}) {
+ ndie msg('range-none');
+ }
- my $info = run_command('SELECT version() AS v');
+ my $info = run_command('SELECT version() AS v');
- $db = $info->{db}[0];
+ $db = $info->{db}[0];
- my $ver = ($db->{slurp}[0]{v} =~ /PostgreSQL (\d+\.\d+\S+)/o) ? $1 : '';
+ my $ver = ($db->{slurp}[0]{v} =~ /PostgreSQL (\d+\.\d+\S+)/o) ? $1 : '';
- $MRTG and do_mrtg({one => $ver ? 1 : 0});
+ $MRTG and do_mrtg({one => $ver ? 1 : 0});
- if ($ver) {
- add_ok msg('version', $ver);
- }
- else {
- add_unknown msg('invalid-query', $db->{slurp}[0]{v});
- }
+ if ($ver) {
+ add_ok msg('version', $ver);
+ }
+ else {
+ add_unknown msg('invalid-query', $db->{slurp}[0]{v});
+ }
- return;
+ return;
} ## end of check_connection
sub check_custom_query {
- ## Run a user-supplied query, then parse the results
- ## If you end up using this to make a useful query, consider making it
- ## into a specific action and sending in a patch!
- ## valtype must be one of: string, time, size, integer
+ ## Run a user-supplied query, then parse the results
+ ## If you end up using this to make a useful query, consider making it
+ ## into a specific action and sending in a patch!
+ ## valtype must be one of: string, time, size, integer
- my $valtype = $opt{valtype} || 'integer';
+ my $valtype = $opt{valtype} || 'integer';
- my ($warning, $critical) = validate_range({type => $valtype, leastone => 1});
+ my ($warning, $critical) = validate_range({type => $valtype, leastone => 1});
- my $query = $opt{query} or ndie msg('custom-nostring');
+ my $query = $opt{query} or ndie msg('custom-nostring');
- my $reverse = $opt{reverse} || 0;
+ my $reverse = $opt{reverse} || 0;
- my $info = run_command($query);
+ my $info = run_command($query);
- for $db (@{$info->{db}}) {
+ for $db (@{$info->{db}}) {
- if (! @{$db->{slurp}}) {
- add_unknown msg('custom-norows');
- next;
- }
+ if (! @{$db->{slurp}}) {
+ add_unknown msg('custom-norows');
+ next;
+ }
- my $goodrow = 0;
+ my $goodrow = 0;
- for my $r (@{$db->{slurp}}) {
- my ($data,$msg) = ($r->{result}, $r->{data}||'');
- $goodrow++;
- $db->{perf} .= " $msg";
- my $gotmatch = 0;
- if (length $critical) {
- if (($valtype eq 'string' and $data eq $critical)
- or
- ($reverse ? $data <= $critical : $data >= $critical)) { ## covers integer, time, size
- add_critical "$data";
- $gotmatch = 1;
- }
- }
+ for my $r (@{$db->{slurp}}) {
+ my ($data,$msg) = ($r->{result}, $r->{data}||'');
+ $goodrow++;
+ $db->{perf} .= " $msg";
+ my $gotmatch = 0;
+ if (length $critical) {
+ if (($valtype eq 'string' and $data eq $critical)
+ or
+ ($reverse ? $data <= $critical : $data >= $critical)) { ## covers integer, time, size
+ add_critical "$data";
+ $gotmatch = 1;
+ }
+ }
- if (length $warning and ! $gotmatch) {
- if (($valtype eq 'string' and $data eq $warning)
- or
- ($reverse ? $data <= $warning : $data >= $warning)) {
- add_warning "$data";
- $gotmatch = 1;
- }
- }
+ if (length $warning and ! $gotmatch) {
+ if (($valtype eq 'string' and $data eq $warning)
+ or
+ ($reverse ? $data <= $warning : $data >= $warning)) {
+ add_warning "$data";
+ $gotmatch = 1;
+ }
+ }
- if (! $gotmatch) {
- add_ok "$data";
- }
+ if (! $gotmatch) {
+ add_ok "$data";
+ }
- } ## end each row returned
+ } ## end each row returned
- if (!$goodrow) {
- add_unknown msg('custom-invalid');
- }
- }
+ if (!$goodrow) {
+ add_unknown msg('custom-invalid');
+ }
+ }
- return;
+ return;
} ## end of check_custom_query
sub check_database_size {
- ## Check the size of one or more databases
- ## Supports: Nagios, MRTG
- ## mrtg reports the largest two databases
- ## By default, checks all databases
- ## Can check specific one(s) with include
- ## Can ignore some with exclude
- ## Warning and critical are bytes
- ## Valid units: b, k, m, g, t, e
- ## All above may be written as plural or with a trailing 'b'
- ## Limit to a specific user (db owner) with the includeuser option
- ## Exclude users with the excludeuser option
+ ## Check the size of one or more databases
+ ## Supports: Nagios, MRTG
+ ## mrtg reports the largest two databases
+ ## By default, checks all databases
+ ## Can check specific one(s) with include
+ ## Can ignore some with exclude
+ ## Warning and critical are bytes
+ ## Valid units: b, k, m, g, t, e
+ ## All above may be written as plural or with a trailing 'b'
+ ## Limit to a specific user (db owner) with the includeuser option
+ ## Exclude users with the excludeuser option
- my ($warning, $critical) = validate_range({type => 'size'});
+ my ($warning, $critical) = validate_range({type => 'size'});
- $USERWHERECLAUSE =~ s/AND/WHERE/;
+ $USERWHERECLAUSE =~ s/AND/WHERE/;
- $SQL = qq{
+ $SQL = qq{
SELECT pg_database_size(d.oid) AS dsize,
pg_size_pretty(pg_database_size(d.oid)) AS pdsize,
datname,
FROM pg_database d
JOIN pg_user u ON (u.usesysid=d.datdba)$USERWHERECLAUSE
};
- if ($opt{perflimit}) {
- $SQL .= " ORDER BY 1 DESC LIMIT $opt{perflimit}";
- }
-
- my $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1, } );
- my $found = 0;
-
- for $db (@{$info->{db}}) {
- my $max = -1;
- $found = 1;
- my %s;
- for my $r (@{$db->{slurp}}) {
-
- next if skip_item($r->{datname});
-
- if ($r->{dsize} >= $max) {
- $max = $r->{dsize};
- }
- $s{$r->{datname}} = [$r->{dsize},$r->{pdsize}];
- }
-
- if ($MRTG) {
- $stats{$db->{dbname}} = $max;
- next;
- }
- if ($max < 0) {
- $stats{$db->{dbname}} = 0;
- if ($USERWHERECLAUSE) {
- add_ok msg('no-match-user');
- }
- else {
- add_unknown msg('no-match-db');
- }
- next;
- }
-
- my $msg = '';
- for (sort {$s{$b}[0] <=> $s{$a}[0] or $a cmp $b } keys %s) {
- $msg .= "$_: $s{$_}[0] ($s{$_}[1]) ";
- $db->{perf} .= " $_=$s{$_}[0]";
- }
- if (length $critical and $max >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $max >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
-
- ## If no results, probably a version problem
- if (!$found and keys %unknown) {
- (my $first) = values %unknown;
- if ($first->[0][0] =~ /pg_database_size/) {
- ndie msg('dbsize-version');
- }
- }
-
- return;
+ if ($opt{perflimit}) {
+ $SQL .= " ORDER BY 1 DESC LIMIT $opt{perflimit}";
+ }
+
+ my $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1, } );
+ my $found = 0;
+
+ for $db (@{$info->{db}}) {
+ my $max = -1;
+ $found = 1;
+ my %s;
+ for my $r (@{$db->{slurp}}) {
+
+ next if skip_item($r->{datname});
+
+ if ($r->{dsize} >= $max) {
+ $max = $r->{dsize};
+ }
+ $s{$r->{datname}} = [$r->{dsize},$r->{pdsize}];
+ }
+
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $max;
+ next;
+ }
+ if ($max < 0) {
+ $stats{$db->{dbname}} = 0;
+ if ($USERWHERECLAUSE) {
+ add_ok msg('no-match-user');
+ }
+ else {
+ add_unknown msg('no-match-db');
+ }
+ next;
+ }
+
+ my $msg = '';
+ for (sort {$s{$b}[0] <=> $s{$a}[0] or $a cmp $b } keys %s) {
+ $msg .= "$_: $s{$_}[0] ($s{$_}[1]) ";
+ $db->{perf} .= " $_=$s{$_}[0]";
+ }
+ if (length $critical and $max >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $max >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+
+ ## If no results, probably a version problem
+ if (!$found and keys %unknown) {
+ (my $first) = values %unknown;
+ if ($first->[0][0] =~ /pg_database_size/) {
+ ndie msg('dbsize-version');
+ }
+ }
+
+ return;
} ## end of check_database_size
sub show_dbstats {
- ## Returns values from the pg_stat_database view
- ## Supports: Cacti
- ## Assumes psql and target are the same version for the 8.3 check
+ ## Returns values from the pg_stat_database view
+ ## Supports: Cacti
+ ## Assumes psql and target are the same version for the 8.3 check
- my ($warning, $critical) = validate_range
- ({
- type => 'cacti',
- });
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'cacti',
+ });
- my $SQL = q{SELECT datname,
+ my $SQL = q{SELECT datname,
numbackends AS backends,xact_commit AS commits,xact_rollback AS rollbacks,
blks_read AS read, blks_hit AS hit};
- if ($opt{dbname}) {
- $SQL .= q{
+ if ($opt{dbname}) {
+ $SQL .= q{
,(SELECT SUM(idx_scan) FROM pg_stat_user_indexes) AS idxscan
,COALESCE((SELECT SUM(idx_tup_read) FROM pg_stat_user_indexes),0) AS idxtupread
,COALESCE((SELECT SUM(idx_tup_fetch) FROM pg_stat_user_indexes),0) AS idxtupfetch
,COALESCE((SELECT SUM(seq_scan) FROM pg_stat_user_tables),0) AS seqscan
,COALESCE((SELECT SUM(seq_tup_read) FROM pg_stat_user_tables),0) AS seqtupread
};
- }
- $SQL .= q{ FROM pg_stat_database};
- (my $SQL2 = $SQL) =~ s/AS seq_tup_read/AS seq_tup_read, tup_returned AS ret, tup_fetched AS fetch, tup_inserted AS ins, tup_updated AS upd, tup_deleted AS del/;
+ }
+ $SQL .= q{ FROM pg_stat_database};
+ (my $SQL2 = $SQL) =~ s/AS seq_tup_read/AS seq_tup_read, tup_returned AS ret, tup_fetched AS fetch, tup_inserted AS ins, tup_updated AS upd, tup_deleted AS del/;
- my $info = run_command($SQL, {regex => qr{\w}, version => [ ">8.2 $SQL2" ] } );
+ my $info = run_command($SQL, {regex => qr{\w}, version => [ ">8.2 $SQL2" ] } );
- for $db (@{$info->{db}}) {
- ROW: for my $r (@{$db->{slurp}}) {
+ for $db (@{$info->{db}}) {
+ ROW: for my $r (@{$db->{slurp}}) {
- my $dbname = $r->{datname};
+ my $dbname = $r->{datname};
- next ROW if skip_item($dbname);
+ next ROW if skip_item($dbname);
- ## If dbnames were specififed, use those for filtering as well
- if (@{$opt{dbname}}) {
- my $keepit = 0;
- for my $drow (@{$opt{dbname}}) {
- for my $d (split /,/ => $drow) {
- $d eq $dbname and $keepit = 1;
- }
- }
- next ROW unless $keepit;
- }
+ ## If dbnames were specififed, use those for filtering as well
+ if (@{$opt{dbname}}) {
+ my $keepit = 0;
+ for my $drow (@{$opt{dbname}}) {
+ for my $d (split /,/ => $drow) {
+ $d eq $dbname and $keepit = 1;
+ }
+ }
+ next ROW unless $keepit;
+ }
- my $msg = '';
- for my $col (qw/
+ my $msg = '';
+ for my $col (qw/
backends commits rollbacks
read hit
idxscan idxtupread idxtupfetch idxblksread idxblkshit
seqscan seqtupread
ret fetch ins upd del/) {
- $msg .= "$col:";
- $msg .= (exists $r->{$col} and length $r->{$col}) ? $r->{$col} : 0;
- $msg .= ' ';
- }
- print "${msg}dbname:$dbname\n";
- }
- }
+ $msg .= "$col:";
+ $msg .= (exists $r->{$col} and length $r->{$col}) ? $r->{$col} : 0;
+ $msg .= ' ';
+ }
+ print "${msg}dbname:$dbname\n";
+ }
+ }
- exit 0;
+ exit 0;
} ## end of show_dbstats
sub check_disabled_triggers {
- ## Checks how many disabled triggers are in the database
- ## Supports: Nagios, MRTG
- ## Warning and critical are integers, defaults to 1
+ ## Checks how many disabled triggers are in the database
+ ## Supports: Nagios, MRTG
+ ## Warning and critical are integers, defaults to 1
- my ($warning, $critical) = validate_range
- ({
- type => 'positive integer',
- default_warning => 1,
- default_critical => 1,
- forcemrtg => 1,
- });
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'positive integer',
+ default_warning => 1,
+ default_critical => 1,
+ forcemrtg => 1,
+ });
- $SQL = q{
+ $SQL = q{
SELECT tgrelid::regclass AS tname, tgname, tgenabled
FROM pg_trigger
WHERE tgenabled IS NOT TRUE ORDER BY tgname
};
- my $SQL83 = q{
+ my $SQL83 = q{
SELECT tgrelid::regclass AS tname, tgname, tgenabled
FROM pg_trigger
WHERE tgenabled = 'D' ORDER BY tgname
};
- my $SQLOLD = q{SELECT 'FAIL' AS fail};
+ my $SQLOLD = q{SELECT 'FAIL' AS fail};
- my $info = run_command($SQL, { version => [ ">8.2 $SQL83", "<8.1 $SQLOLD" ] } );
+ my $info = run_command($SQL, { version => [ ">8.2 $SQL83", "<8.1 $SQLOLD" ] } );
- if (exists $info->{db}[0]{fail}) {
- ndie msg('die-action-version', $action, '8.1', $db->{version});
- }
+ if (exists $info->{db}[0]{fail}) {
+ ndie msg('die-action-version', $action, '8.1', $db->{version});
+ }
- my $count = 0;
- my $dislis = '';
- for $db (@{$info->{db}}) {
+ my $count = 0;
+ my $dislis = '';
+ for $db (@{$info->{db}}) {
- ROW: for my $r (@{$db->{slurp}}) {
- $count++;
- $dislis .= " $r->{tname}=>$r->{tgname}";
- }
- $MRTG and do_mrtg({one => $count});
+ ROW: for my $r (@{$db->{slurp}}) {
+ $count++;
+ $dislis .= " $r->{tname}=>$r->{tgname}";
+ }
+ $MRTG and do_mrtg({one => $count});
- my $msg = msg('trigger-msg', "$count$dislis");
+ my $msg = msg('trigger-msg', "$count$dislis");
- if ($critical and $count >= $critical) {
- add_critical $msg;
- }
- elsif ($warning and $count >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
+ if ($critical and $count >= $critical) {
+ add_critical $msg;
+ }
+ elsif ($warning and $count >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
- return;
+ return;
} ## end of check_disabled_triggers
sub check_disk_space {
- ## Check the available disk space used by postgres
- ## Supports: Nagios, MRTG
- ## Requires the executable "/bin/df"
- ## Must run as a superuser in the database (to examine 'data_directory' setting)
- ## Critical and warning are maximum size, or percentages
- ## Example: --critical="40 GB"
- ## NOTE: Needs to run on the same system (for now)
- ## XXX Allow custom ssh commands for remote df and the like
-
- my ($warning, $critical) = validate_range
- ({
- type => 'size or percent',
- default_warning => '90%',
- default_critical => '95%',
- });
-
- -x '/bin/df' or ndie msg('diskspace-nodf');
-
- ## Figure out where everything is.
- $SQL = q{
+ ## Check the available disk space used by postgres
+ ## Supports: Nagios, MRTG
+ ## Requires the executable "/bin/df"
+ ## Must run as a superuser in the database (to examine 'data_directory' setting)
+ ## Critical and warning are maximum size, or percentages
+ ## Example: --critical="40 GB"
+ ## NOTE: Needs to run on the same system (for now)
+ ## XXX Allow custom ssh commands for remote df and the like
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'size or percent',
+ default_warning => '90%',
+ default_critical => '95%',
+ });
+
+ -x '/bin/df' or ndie msg('diskspace-nodf');
+
+ ## Figure out where everything is.
+ $SQL = q{
SELECT 'S' AS syn, name AS nn, setting AS val
FROM pg_settings
WHERE name = 'data_directory'
WHERE spclocation <> ''
};
- my $info = run_command($SQL);
-
- my %dir; ## 1 = normal 2 = been checked -1 = does not exist
- my %seenfs;
- for $db (@{$info->{db}}) {
- my %i;
- for my $r (@{$db->{slurp}}) {
- $i{$r->{syn}}{$r->{nn}} = $r->{val};
- }
- if (! exists $i{S}{data_directory}) {
- add_unknown msg('diskspace-nodata');
- next;
- }
- my ($datadir,$logdir) = ($i{S}{data_directory},$i{S}{log_directory}||'');
-
- if (!exists $dir{$datadir}) {
- if (! -d $datadir) {
- add_unknown msg('diskspace-nodir', $datadir);
- $dir{$datadir} = -1;
- next;
- }
- $dir{$datadir} = 1;
-
- ## Check if the WAL files are on a separate disk
- my $xlog = "$datadir/pg_xlog";
- if (-l $xlog) {
- my $linkdir = readlink($xlog);
- $dir{$linkdir} = 1 if ! exists $dir{$linkdir};
- }
- }
-
- ## Check log_directory: relative or absolute
- if (length $logdir) {
- if ($logdir =~ /^\w/) { ## relative, check only if symlinked
- $logdir = "$datadir/$logdir";
- if (-l $logdir) {
- my $linkdir = readlink($logdir);
- $dir{$linkdir} = 1 if ! exists $dir{$linkdir};
- }
- }
- else { ## absolute, always check
- if ($logdir ne $datadir and ! exists $dir{$logdir}) {
- $dir{$logdir} = 1;
- }
- }
- }
-
- ## Check all tablespaces
- for my $tsname (keys %{$i{T}}) {
- my $tsdir = $i{T}{$tsname};
- $dir{$tsdir} = 1 if ! exists $dir{$tsdir};
- }
-
- my $gotone = 0;
- for my $dir (keys %dir) {
- next if $dir{$dir} != 1;
-
- $dir{$dir} = 1;
-
- $COM = qq{/bin/df -kP "$dir" 2>&1};
- $res = qx{$COM};
-
- if ($res !~ /^.+\n(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\%\s+(\S+)/) {
- ndie msg('diskspace-fail', $COM, $res);
- }
- my ($fs,$total,$used,$avail,$percent,$mount) = ($1,$2*1024,$3*1024,$4*1024,$5,$6);
-
- ## If we've already done this one, skip it
- next if $seenfs{$fs}++;
-
- next if skip_item($fs);
-
- if ($MRTG) {
- $stats{$fs} = [$total,$used,$avail,$percent];
- next;
- }
-
- $gotone = 1;
-
- ## Rather than make another call with -h, do it ourselves
- my $prettyused = pretty_size($used);
- my $prettytotal = pretty_size($total);
-
- my $msg = msg('diskspace-msg', $fs, $mount, $prettyused, $prettytotal, $percent);
-
- $db->{perf} = "$fs=$used";
-
- my $ok = 1;
- if (length $critical) {
- if (index($critical,'%')>=0) {
- (my $critical2 = $critical) =~ s/\%//;
- if ($percent >= $critical2) {
- add_critical $msg;
- $ok = 0;
- }
- }
- elsif ($used >= $critical) {
- add_critical $msg;
- $ok = 0;
- }
- }
- if (length $warning and $ok) {
- if (index($warning,'%')>=0) {
- (my $warning2 = $warning) =~ s/\%//;
- if ($percent >= $warning2) {
- add_warning $msg;
- $ok = 0;
- }
- }
- elsif ($used >= $warning) {
- add_warning $msg;
- $ok = 0;
- }
- }
- if ($ok) {
- add_ok $msg;
- }
- } ## end each dir
-
- next if $MRTG;
-
- if (!$gotone) {
- add_unknown msg('no-match-fs');
- }
- }
-
- if ($MRTG) {
- keys %stats or bad_mrtg(msg('unknown-error'));
- ## Get the highest by total size or percent (total, used, avail, percent)
- ## We default to 'available'
- my $sortby = exists $opt{mrtg}
- ? $opt{mrtg} eq 'total' ? 0
- : $opt{mrtg} eq 'used' ? 1
- : $opt{mrtg} eq 'avail' ? 2
- : $opt{mrtg} eq 'percent' ? 3 : 2 : 2;
- my ($one,$two,$msg) = ('','','');
- for (sort { $stats{$b}->[$sortby] <=> $stats{$a}->[$sortby] } keys %stats) {
- if ($one eq '') {
- $one = $stats{$_}->[$sortby];
- $msg = $_;
- next;
- }
- $two = $stats{$_}->[$sortby];
- last;
- }
- do_mrtg({one => $one, two => $two, msg => $msg});
- }
-
- return;
+ my $info = run_command($SQL);
+
+ my %dir; ## 1 = normal 2 = been checked -1 = does not exist
+ my %seenfs;
+ for $db (@{$info->{db}}) {
+ my %i;
+ for my $r (@{$db->{slurp}}) {
+ $i{$r->{syn}}{$r->{nn}} = $r->{val};
+ }
+ if (! exists $i{S}{data_directory}) {
+ add_unknown msg('diskspace-nodata');
+ next;
+ }
+ my ($datadir,$logdir) = ($i{S}{data_directory},$i{S}{log_directory}||'');
+
+ if (!exists $dir{$datadir}) {
+ if (! -d $datadir) {
+ add_unknown msg('diskspace-nodir', $datadir);
+ $dir{$datadir} = -1;
+ next;
+ }
+ $dir{$datadir} = 1;
+
+ ## Check if the WAL files are on a separate disk
+ my $xlog = "$datadir/pg_xlog";
+ if (-l $xlog) {
+ my $linkdir = readlink($xlog);
+ $dir{$linkdir} = 1 if ! exists $dir{$linkdir};
+ }
+ }
+
+ ## Check log_directory: relative or absolute
+ if (length $logdir) {
+ if ($logdir =~ /^\w/) { ## relative, check only if symlinked
+ $logdir = "$datadir/$logdir";
+ if (-l $logdir) {
+ my $linkdir = readlink($logdir);
+ $dir{$linkdir} = 1 if ! exists $dir{$linkdir};
+ }
+ }
+ else { ## absolute, always check
+ if ($logdir ne $datadir and ! exists $dir{$logdir}) {
+ $dir{$logdir} = 1;
+ }
+ }
+ }
+
+ ## Check all tablespaces
+ for my $tsname (keys %{$i{T}}) {
+ my $tsdir = $i{T}{$tsname};
+ $dir{$tsdir} = 1 if ! exists $dir{$tsdir};
+ }
+
+ my $gotone = 0;
+ for my $dir (keys %dir) {
+ next if $dir{$dir} != 1;
+
+ $dir{$dir} = 1;
+
+ $COM = qq{/bin/df -kP "$dir" 2>&1};
+ $res = qx{$COM};
+
+ if ($res !~ /^.+\n(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\%\s+(\S+)/) {
+ ndie msg('diskspace-fail', $COM, $res);
+ }
+ my ($fs,$total,$used,$avail,$percent,$mount) = ($1,$2*1024,$3*1024,$4*1024,$5,$6);
+
+ ## If we've already done this one, skip it
+ next if $seenfs{$fs}++;
+
+ next if skip_item($fs);
+
+ if ($MRTG) {
+ $stats{$fs} = [$total,$used,$avail,$percent];
+ next;
+ }
+
+ $gotone = 1;
+
+ ## Rather than make another call with -h, do it ourselves
+ my $prettyused = pretty_size($used);
+ my $prettytotal = pretty_size($total);
+
+ my $msg = msg('diskspace-msg', $fs, $mount, $prettyused, $prettytotal, $percent);
+
+ $db->{perf} = "$fs=$used";
+
+ my $ok = 1;
+ if (length $critical) {
+ if (index($critical,'%')>=0) {
+ (my $critical2 = $critical) =~ s/\%//;
+ if ($percent >= $critical2) {
+ add_critical $msg;
+ $ok = 0;
+ }
+ }
+ elsif ($used >= $critical) {
+ add_critical $msg;
+ $ok = 0;
+ }
+ }
+ if (length $warning and $ok) {
+ if (index($warning,'%')>=0) {
+ (my $warning2 = $warning) =~ s/\%//;
+ if ($percent >= $warning2) {
+ add_warning $msg;
+ $ok = 0;
+ }
+ }
+ elsif ($used >= $warning) {
+ add_warning $msg;
+ $ok = 0;
+ }
+ }
+ if ($ok) {
+ add_ok $msg;
+ }
+ } ## end each dir
+
+ next if $MRTG;
+
+ if (!$gotone) {
+ add_unknown msg('no-match-fs');
+ }
+ }
+
+ if ($MRTG) {
+ keys %stats or bad_mrtg(msg('unknown-error'));
+ ## Get the highest by total size or percent (total, used, avail, percent)
+ ## We default to 'available'
+ my $sortby = exists $opt{mrtg}
+ ? $opt{mrtg} eq 'total' ? 0
+ : $opt{mrtg} eq 'used' ? 1
+ : $opt{mrtg} eq 'avail' ? 2
+ : $opt{mrtg} eq 'percent' ? 3 : 2 : 2;
+ my ($one,$two,$msg) = ('','','');
+ for (sort { $stats{$b}->[$sortby] <=> $stats{$a}->[$sortby] } keys %stats) {
+ if ($one eq '') {
+ $one = $stats{$_}->[$sortby];
+ $msg = $_;
+ next;
+ }
+ $two = $stats{$_}->[$sortby];
+ last;
+ }
+ do_mrtg({one => $one, two => $two, msg => $msg});
+ }
+
+ return;
} ## end of check_disk_space
sub check_fsm_pages {
- ## Check on the percentage of free space map pages in use
- ## Supports: Nagios, MRTG
- ## Must run as superuser
- ## Requires pg_freespacemap contrib module
- ## Critical and warning are a percentage of max_fsm_pages
- ## Example: --critical=95
-
- my ($warning, $critical) = validate_range
- ({
- type => 'percent',
- default_warning => '85%',
- default_critical => '95%',
- });
-
- (my $w = $warning) =~ s/\D//;
- (my $c = $critical) =~ s/\D//;
- my $SQL = qq{
+ ## Check on the percentage of free space map pages in use
+ ## Supports: Nagios, MRTG
+ ## Must run as superuser
+ ## Requires pg_freespacemap contrib module
+ ## Critical and warning are a percentage of max_fsm_pages
+ ## Example: --critical=95
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'percent',
+ default_warning => '85%',
+ default_critical => '95%',
+ });
+
+ (my $w = $warning) =~ s/\D//;
+ (my $c = $critical) =~ s/\D//;
+ my $SQL = qq{
SELECT pages, maxx, ROUND(100*(pages/maxx)) AS percent
FROM
(SELECT (sumrequests+numrels)*chunkpages AS pages
COUNT(relfilenode) AS numrels, 16 AS chunkpages FROM pg_freespacemap_relations) AS foo) AS foo2,
(SELECT setting::NUMERIC AS maxx FROM pg_settings WHERE name = 'max_fsm_pages') AS foo3
};
- my $SQLNOOP = q{SELECT 'FAIL' AS fail};
+ my $SQLNOOP = q{SELECT 'FAIL' AS fail};
- my $info = run_command($SQL, { version => [ ">8.3 $SQLNOOP" ] } );
+ my $info = run_command($SQL, { version => [ ">8.3 $SQLNOOP" ] } );
- if (exists $info->{db}[0]{fail}) {
- add_unknown msg('fsm-page-highver');
- return;
- }
+ if (exists $info->{db}[0]{fail}) {
+ add_unknown msg('fsm-page-highver');
+ return;
+ }
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- my ($pages,$max,$percent) = ($r->{pages}||0,$r->{maxx},$r->{percent}||0);
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ my ($pages,$max,$percent) = ($r->{pages}||0,$r->{maxx},$r->{percent}||0);
- $MRTG and do_mrtg({one => $percent, two => $pages});
+ $MRTG and do_mrtg({one => $percent, two => $pages});
- my $msg = msg('fsm-page-msg', $pages, $max, $percent);
+ my $msg = msg('fsm-page-msg', $pages, $max, $percent);
- if (length $critical and $percent >= $c) {
- add_critical $msg;
- }
- elsif (length $warning and $percent >= $w) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
- }
+ if (length $critical and $percent >= $c) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $percent >= $w) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+ }
- return;
+ return;
} ## end of check_fsm_pages
sub check_fsm_relations {
- ## Check on the % of free space map relations in use
- ## Supports: Nagios, MRTG
- ## Must run as superuser
- ## Requires pg_freespacemap contrib module
- ## Critical and warning are a percentage of max_fsm_relations
- ## Example: --critical=95
+ ## Check on the % of free space map relations in use
+ ## Supports: Nagios, MRTG
+ ## Must run as superuser
+ ## Requires pg_freespacemap contrib module
+ ## Critical and warning are a percentage of max_fsm_relations
+ ## Example: --critical=95
- my ($warning, $critical) = validate_range
- ({
- type => 'percent',
- default_warning => '85%',
- default_critical => '95%',
- });
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'percent',
+ default_warning => '85%',
+ default_critical => '95%',
+ });
- (my $w = $warning) =~ s/\D//;
- (my $c = $critical) =~ s/\D//;
+ (my $w = $warning) =~ s/\D//;
+ (my $c = $critical) =~ s/\D//;
- my $SQL = qq{
+ my $SQL = qq{
SELECT maxx, cur, ROUND(100*(cur/maxx)) AS percent
FROM (SELECT
(SELECT COUNT(*) FROM pg_freespacemap_relations) AS cur,
(SELECT setting::NUMERIC FROM pg_settings WHERE name='max_fsm_relations') AS maxx) x
};
- my $SQLNOOP = q{SELECT 'FAIL' AS fail};
+ my $SQLNOOP = q{SELECT 'FAIL' AS fail};
- my $info = run_command($SQL, { version => [ ">8.3 $SQLNOOP" ] } );
+ my $info = run_command($SQL, { version => [ ">8.3 $SQLNOOP" ] } );
- if (exists $info->{db}[0]{fail}) {
- add_unknown msg('fsm-rel-highver');
- return;
- }
+ if (exists $info->{db}[0]{fail}) {
+ add_unknown msg('fsm-rel-highver');
+ return;
+ }
- for $db (@{$info->{db}}) {
+ for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- my ($max,$cur,$percent) = ($r->{maxx},$r->{cur},$r->{percent});
+ for my $r (@{$db->{slurp}}) {
+ my ($max,$cur,$percent) = ($r->{maxx},$r->{cur},$r->{percent});
- $MRTG and do_mrtg({one => $percent, two => $cur});
+ $MRTG and do_mrtg({one => $percent, two => $cur});
- my $msg = msg('fsm-rel-msg', $cur, $max, $percent);
+ my $msg = msg('fsm-rel-msg', $cur, $max, $percent);
- if (length $critical and $percent >= $c) {
- add_critical $msg;
- }
- elsif (length $warning and $percent >= $w) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
+ if (length $critical and $percent >= $c) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $percent >= $w) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
- }
+ }
- return;
+ return;
} ## end of check_fsm_relations
sub check_last_analyze {
- my $auto = shift || '';
- return check_last_vacuum_analyze('analyze', $auto);
+ my $auto = shift || '';
+ return check_last_vacuum_analyze('analyze', $auto);
}
sub check_last_vacuum {
- my $auto = shift || '';
- return check_last_vacuum_analyze('vacuum', $auto);
+ my $auto = shift || '';
+ return check_last_vacuum_analyze('vacuum', $auto);
}
sub check_last_vacuum_analyze {
- my $type = shift || 'vacuum';
- my $auto = shift || 0;
-
- ## Check the last time things were vacuumed or analyzed
- ## Supports: Nagios, MRTG
- ## NOTE: stats_row_level must be set to on in your database (if version 8.2)
- ## By default, reports on the oldest value in the database
- ## Can exclude and include tables
- ## Warning and critical are times, default to seconds
- ## Valid units: s[econd], m[inute], h[our], d[ay]
- ## All above may be written as plural as well (e.g. "2 hours")
- ## Limit to a specific user (relation owner) with the includeuser option
- ## Exclude users with the excludeuser option
- ## Example:
- ## --exclude=~pg_ --include=pg_class,pg_attribute
-
- my ($warning, $critical) = validate_range
- ({
- type => 'time',
- default_warning => '1 day',
- default_critical => '2 days',
- });
-
- my $criteria = $auto ?
- qq{pg_stat_get_last_auto${type}_time(c.oid)}
- : qq{GREATEST(pg_stat_get_last_${type}_time(c.oid), pg_stat_get_last_auto${type}_time(c.oid))};
-
- ## Do include/exclude earlier for large pg_classes?
- $SQL = qq{
+ my $type = shift || 'vacuum';
+ my $auto = shift || 0;
+
+ ## Check the last time things were vacuumed or analyzed
+ ## Supports: Nagios, MRTG
+ ## NOTE: stats_row_level must be set to on in your database (if version 8.2)
+ ## By default, reports on the oldest value in the database
+ ## Can exclude and include tables
+ ## Warning and critical are times, default to seconds
+ ## Valid units: s[econd], m[inute], h[our], d[ay]
+ ## All above may be written as plural as well (e.g. "2 hours")
+ ## Limit to a specific user (relation owner) with the includeuser option
+ ## Exclude users with the excludeuser option
+ ## Example:
+ ## --exclude=~pg_ --include=pg_class,pg_attribute
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'time',
+ default_warning => '1 day',
+ default_critical => '2 days',
+ });
+
+ my $criteria = $auto ?
+ qq{pg_stat_get_last_auto${type}_time(c.oid)}
+ : qq{GREATEST(pg_stat_get_last_${type}_time(c.oid), pg_stat_get_last_auto${type}_time(c.oid))};
+
+ ## Do include/exclude earlier for large pg_classes?
+ $SQL = qq{
SELECT current_database() AS datname, nspname AS sname, relname AS tname,
CASE WHEN v IS NULL THEN -1 ELSE round(extract(epoch FROM now()-v)) END AS ltime,
CASE WHEN v IS NULL THEN '?' ELSE TO_CHAR(v, '$SHOWTIME') END AS ptime
AND n.nspname <> 'information_schema'
ORDER BY 3) AS foo
};
- if ($opt{perflimit}) {
- $SQL .= ' ORDER BY 3 DESC';
- }
-
- if ($USERWHERECLAUSE) {
- $SQL =~ s/ WHERE/, pg_user u WHERE u.usesysid=c.relowner$USERWHERECLAUSE AND/;
- }
-
- my $info = run_command($SQL, { regex => qr{\w}, emptyok => 1 } );
-
- for $db (@{$info->{db}}) {
-
- if (! @{$db->{slurp}} and $USERWHERECLAUSE) {
- $stats{$db->{dbname}} = 0;
- add_ok msg('no-match-user');
- return;
- }
-
- ## -1 means no tables found at all
- ## -2 means exclusion rules took effect
- ## -3 means no tables were ever vacuumed/analyzed
- my $maxtime = -1;
- my $maxptime = '?';
- my ($minrel,$maxrel) = ('?','?'); ## no critic
- my $mintime = 0; ## used for MRTG only
- my $count = 0;
- ROW: for my $r (@{$db->{slurp}}) {
- my ($dbname,$schema,$name,$time,$ptime) = @$r{qw/ datname sname tname ltime ptime/};
- $maxtime = -3 if $maxtime == -1;
- if (skip_item($name, $schema)) {
- $maxtime = -2 if $maxtime < 1;
- next ROW;
- }
- $db->{perf} .= " $dbname.$schema.$name=${time}s;$warning;$critical" if $time >= 0;
- if ($time > $maxtime) {
- $maxtime = $time;
- $maxrel = "$schema.$name";
- $maxptime = $ptime;
- }
- if ($time > 0 and ($time < $mintime or !$mintime)) {
- $mintime = $time;
- $minrel = "$schema.$name";
- }
- if ($opt{perflimit}) {
- last if ++$count >= $opt{perflimit};
- }
- }
- if ($MRTG) {
- $stats{$db->{dbname}} = $mintime;
- $statsmsg{$db->{dbname}} = msg('vac-msg', $db->{dbname}, $minrel);
- return;
- }
-
- if ($maxtime == -2) {
- add_unknown msg('no-match-table');
- }
- elsif ($maxtime < 0) {
- add_unknown $type eq 'vacuum' ? msg('vac-nomatch-v') : msg('vac-nomatch-a');
- }
- else {
- my $showtime = pretty_time($maxtime, 'S');
- my $msg = "$maxrel: $maxptime ($showtime)";
- if ($critical and $maxtime >= $critical) {
- add_critical $msg;
- }
- elsif ($warning and $maxtime >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
- }
-
- return;
+ if ($opt{perflimit}) {
+ $SQL .= ' ORDER BY 3 DESC';
+ }
+
+ if ($USERWHERECLAUSE) {
+ $SQL =~ s/ WHERE/, pg_user u WHERE u.usesysid=c.relowner$USERWHERECLAUSE AND/;
+ }
+
+ my $info = run_command($SQL, { regex => qr{\w}, emptyok => 1 } );
+
+ for $db (@{$info->{db}}) {
+
+ if (! @{$db->{slurp}} and $USERWHERECLAUSE) {
+ $stats{$db->{dbname}} = 0;
+ add_ok msg('no-match-user');
+ return;
+ }
+
+ ## -1 means no tables found at all
+ ## -2 means exclusion rules took effect
+ ## -3 means no tables were ever vacuumed/analyzed
+ my $maxtime = -1;
+ my $maxptime = '?';
+ my ($minrel,$maxrel) = ('?','?'); ## no critic
+ my $mintime = 0; ## used for MRTG only
+ my $count = 0;
+ ROW: for my $r (@{$db->{slurp}}) {
+ my ($dbname,$schema,$name,$time,$ptime) = @$r{qw/ datname sname tname ltime ptime/};
+ $maxtime = -3 if $maxtime == -1;
+ if (skip_item($name, $schema)) {
+ $maxtime = -2 if $maxtime < 1;
+ next ROW;
+ }
+ $db->{perf} .= " $dbname.$schema.$name=${time}s;$warning;$critical" if $time >= 0;
+ if ($time > $maxtime) {
+ $maxtime = $time;
+ $maxrel = "$schema.$name";
+ $maxptime = $ptime;
+ }
+ if ($time > 0 and ($time < $mintime or !$mintime)) {
+ $mintime = $time;
+ $minrel = "$schema.$name";
+ }
+ if ($opt{perflimit}) {
+ last if ++$count >= $opt{perflimit};
+ }
+ }
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $mintime;
+ $statsmsg{$db->{dbname}} = msg('vac-msg', $db->{dbname}, $minrel);
+ return;
+ }
+
+ if ($maxtime == -2) {
+ add_unknown msg('no-match-table');
+ }
+ elsif ($maxtime < 0) {
+ add_unknown $type eq 'vacuum' ? msg('vac-nomatch-v') : msg('vac-nomatch-a');
+ }
+ else {
+ my $showtime = pretty_time($maxtime, 'S');
+ my $msg = "$maxrel: $maxptime ($showtime)";
+ if ($critical and $maxtime >= $critical) {
+ add_critical $msg;
+ }
+ elsif ($warning and $maxtime >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+ }
+
+ return;
} ## end of check_last_vacuum_analyze
sub check_listener {
- ## Check for a specific listener
- ## Supports: Nagios, MRTG
- ## Critical and warning are simple strings, or regex if starts with a ~
- ## Example: --critical="~bucardo"
-
- if ($MRTG and exists $opt{mrtg}) {
- $opt{critical} = $opt{mrtg};
- }
-
- my ($warning, $critical) = validate_range({type => 'restringex', forcemrtg => 1});
-
- my $string = length $critical ? $critical : $warning;
- my $regex = ($string =~ s/^~//) ? '~' : '=';
-
- $SQL = "SELECT count(*) AS c FROM pg_listener WHERE relname $regex '$string'";
- my $info = run_command($SQL);
-
- for $db (@{$info->{db}}) {
- if ($db->{slurp}[0]{c} !~ /(\d+)/) {
- add_unknown msg('invalid-query', $db->{slurp});
- next;
- }
- my $count = $1;
- if ($MRTG) {
- do_mrtg({one => $count});
- }
- $db->{perf} .= msg('listener-count', $count);
- my $msg = msg('listener-msg', $count);
- if ($count >= 1) {
- add_ok $msg;
- }
- elsif ($critical) {
- add_critical $msg;
- }
- else {
- add_warning $msg;
- }
- }
- return;
+ ## Check for a specific listener
+ ## Supports: Nagios, MRTG
+ ## Critical and warning are simple strings, or regex if starts with a ~
+ ## Example: --critical="~bucardo"
+
+ if ($MRTG and exists $opt{mrtg}) {
+ $opt{critical} = $opt{mrtg};
+ }
+
+ my ($warning, $critical) = validate_range({type => 'restringex', forcemrtg => 1});
+
+ my $string = length $critical ? $critical : $warning;
+ my $regex = ($string =~ s/^~//) ? '~' : '=';
+
+ $SQL = "SELECT count(*) AS c FROM pg_listener WHERE relname $regex '$string'";
+ my $info = run_command($SQL);
+
+ for $db (@{$info->{db}}) {
+ if ($db->{slurp}[0]{c} !~ /(\d+)/) {
+ add_unknown msg('invalid-query', $db->{slurp});
+ next;
+ }
+ my $count = $1;
+ if ($MRTG) {
+ do_mrtg({one => $count});
+ }
+ $db->{perf} .= msg('listener-count', $count);
+ my $msg = msg('listener-msg', $count);
+ if ($count >= 1) {
+ add_ok $msg;
+ }
+ elsif ($critical) {
+ add_critical $msg;
+ }
+ else {
+ add_warning $msg;
+ }
+ }
+ return;
} ## end of check_listener
sub check_locks {
- ## Check the number of locks
- ## Supports: Nagios, MRTG
- ## By default, checks all databases
- ## Can check specific databases with include
- ## Can ignore databases with exclude
- ## Warning and critical are either simple numbers, or more complex:
- ## Use locktype=number;locktype2=number
- ## The locktype can be "total", "waiting", or the name of a lock
- ## Lock names are case-insensitive, and do not need the "lock" at the end.
- ## Example: --warning=100 --critical="total=200;exclusive=20;waiting=5"
-
- my ($warning, $critical) = validate_range
- ({
- type => 'multival',
- default_warning => 100,
- default_critical => 150,
- });
-
- $SQL = q{SELECT granted, mode, datname FROM pg_locks l JOIN pg_database d ON (d.oid=l.database)};
- my $info = run_command($SQL, { regex => qr[\s*\w+\s*\|\s*] });
-
- # Locks are counted globally not by db.
- # add a limit by db ? (--critical='foodb.total=30 foodb.exclusive=3 postgres.total=3')
- # end remove the -db option ?
- # we output for each db, following the specific warning and critical :
- # time=00.1 foodb.exclusive=2;;3 foodb.total=10;;30 postgres.exclusive=0;;3 postgres.total=1;;3
- for $db (@{$info->{db}}) {
- my $gotone = 0;
- my %dblock;
- my %totallock = (total => 0);
- ROW: for my $r (@{$db->{slurp}}) {
- my ($granted,$mode,$dbname) = ($r->{granted}, lc $r->{mode}, $r->{datname});
- next ROW if skip_item($dbname);
- $gotone = 1;
- $mode =~ s{lock$}{};
- $dblock{$dbname}{total}++;
- $dblock{$dbname}{$mode}++;
- $dblock{$dbname}{waiting}++ if $granted ne 't';
- }
- # Compute total, add hash key for critical and warning specific check
- for my $k (keys %dblock) {
- if ($warning) {
- for my $l (keys %{$warning}) {
- $dblock{$k}{$l} = 0 if ! exists $dblock{$k}{$l};
- }
- }
- if ($critical) {
- for my $l (keys %{$critical}) {
- $dblock{$k}{$l} = 0 if ! exists $dblock{$k}{$l};
- }
- }
- for my $m (keys %{$dblock{$k}}){
- $totallock{$m} += $dblock{$k}{$m};
- }
- }
-
- if ($MRTG) {
- $stats{$db->{dbname}} = $totallock{total};
- next;
- }
-
- # Nagios perfdata output
- for my $dbname (sort keys %dblock) {
- for my $type (sort keys %{ $dblock{$dbname} }) {
- next if ((! $critical or ! exists $critical->{$type})
- and (!$warning or ! exists $warning->{$type}));
- $db->{perf} .= " '$dbname.$type'=$dblock{$dbname}{$type};";
- if ($warning and exists $warning->{$type}) {
- $db->{perf} .= $warning->{$type};
- }
- $db->{perf} .= ';';
- if ($critical and $critical->{$type}) {
- $db->{perf} .= $critical->{$type};
- }
- }
- }
-
- if (!$gotone) {
- add_unknown msg('no-match-db');
- next;
- }
-
- ## If not specific errors, just use the total
- my $ok = 1;
- for my $type (keys %totallock) {
- if ($critical and exists $critical->{$type} and $totallock{$type} >= $critical->{$type}) {
- ($type eq 'total')
- ? add_critical msg('locks-msg2', $totallock{total})
- : add_critical msg('locks-msg', $type, $totallock{$type});
- $ok = 0;
- }
- if ($warning and exists $warning->{$type} and $totallock{$type} >= $warning->{$type}) {
- ($type eq 'total')
- ? add_warning msg('locks-msg2', $totallock{total})
- : add_warning msg('locks-msg', $type, $totallock{$type});
- $ok = 0;
- }
- }
- if ($ok) {
- my %show;
- if (!keys %critical and !keys %warning) {
- $show{total} = 1;
- }
- for my $type (keys %critical) {
- $show{$type} = 1;
- }
- for my $type (keys %warning) {
- $show{$type} = 1;
- }
- my $msg = '';
- for (sort keys %show) {
- $msg .= sprintf "$_=%d ", $totallock{$_} || 0;
- }
- add_ok $msg;
- }
- }
-
- return;
+ ## Check the number of locks
+ ## Supports: Nagios, MRTG
+ ## By default, checks all databases
+ ## Can check specific databases with include
+ ## Can ignore databases with exclude
+ ## Warning and critical are either simple numbers, or more complex:
+ ## Use locktype=number;locktype2=number
+ ## The locktype can be "total", "waiting", or the name of a lock
+ ## Lock names are case-insensitive, and do not need the "lock" at the end.
+ ## Example: --warning=100 --critical="total=200;exclusive=20;waiting=5"
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'multival',
+ default_warning => 100,
+ default_critical => 150,
+ });
+
+ $SQL = q{SELECT granted, mode, datname FROM pg_locks l JOIN pg_database d ON (d.oid=l.database)};
+ my $info = run_command($SQL, { regex => qr[\s*\w+\s*\|\s*] });
+
+ # Locks are counted globally not by db.
+ # add a limit by db ? (--critical='foodb.total=30 foodb.exclusive=3 postgres.total=3')
+ # end remove the -db option ?
+ # we output for each db, following the specific warning and critical :
+ # time=00.1 foodb.exclusive=2;;3 foodb.total=10;;30 postgres.exclusive=0;;3 postgres.total=1;;3
+ for $db (@{$info->{db}}) {
+ my $gotone = 0;
+ my %dblock;
+ my %totallock = (total => 0);
+ ROW: for my $r (@{$db->{slurp}}) {
+ my ($granted,$mode,$dbname) = ($r->{granted}, lc $r->{mode}, $r->{datname});
+ next ROW if skip_item($dbname);
+ $gotone = 1;
+ $mode =~ s{lock$}{};
+ $dblock{$dbname}{total}++;
+ $dblock{$dbname}{$mode}++;
+ $dblock{$dbname}{waiting}++ if $granted ne 't';
+ }
+ # Compute total, add hash key for critical and warning specific check
+ for my $k (keys %dblock) {
+ if ($warning) {
+ for my $l (keys %{$warning}) {
+ $dblock{$k}{$l} = 0 if ! exists $dblock{$k}{$l};
+ }
+ }
+ if ($critical) {
+ for my $l (keys %{$critical}) {
+ $dblock{$k}{$l} = 0 if ! exists $dblock{$k}{$l};
+ }
+ }
+ for my $m (keys %{$dblock{$k}}){
+ $totallock{$m} += $dblock{$k}{$m};
+ }
+ }
+
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $totallock{total};
+ next;
+ }
+
+ # Nagios perfdata output
+ for my $dbname (sort keys %dblock) {
+ for my $type (sort keys %{ $dblock{$dbname} }) {
+ next if ((! $critical or ! exists $critical->{$type})
+ and (!$warning or ! exists $warning->{$type}));
+ $db->{perf} .= " '$dbname.$type'=$dblock{$dbname}{$type};";
+ if ($warning and exists $warning->{$type}) {
+ $db->{perf} .= $warning->{$type};
+ }
+ $db->{perf} .= ';';
+ if ($critical and $critical->{$type}) {
+ $db->{perf} .= $critical->{$type};
+ }
+ }
+ }
+
+ if (!$gotone) {
+ add_unknown msg('no-match-db');
+ next;
+ }
+
+ ## If not specific errors, just use the total
+ my $ok = 1;
+ for my $type (keys %totallock) {
+ if ($critical and exists $critical->{$type} and $totallock{$type} >= $critical->{$type}) {
+ ($type eq 'total')
+ ? add_critical msg('locks-msg2', $totallock{total})
+ : add_critical msg('locks-msg', $type, $totallock{$type});
+ $ok = 0;
+ }
+ if ($warning and exists $warning->{$type} and $totallock{$type} >= $warning->{$type}) {
+ ($type eq 'total')
+ ? add_warning msg('locks-msg2', $totallock{total})
+ : add_warning msg('locks-msg', $type, $totallock{$type});
+ $ok = 0;
+ }
+ }
+ if ($ok) {
+ my %show;
+ if (!keys %critical and !keys %warning) {
+ $show{total} = 1;
+ }
+ for my $type (keys %critical) {
+ $show{$type} = 1;
+ }
+ for my $type (keys %warning) {
+ $show{$type} = 1;
+ }
+ my $msg = '';
+ for (sort keys %show) {
+ $msg .= sprintf "$_=%d ", $totallock{$_} || 0;
+ }
+ add_ok $msg;
+ }
+ }
+
+ return;
} ## end of check_locks
sub check_logfile {
- ## Make sure the logfile is getting written to
- ## Supports: Nagios, MRTG
- ## Especially useful for syslog redirectors
- ## Should be run on the system housing the logs
- ## Optional argument "logfile" tells where the logfile is
- ## Allows for some conversion characters.
- ## Example: --logfile="/syslog/%Y-m%-d%/H%/postgres.log"
- ## Critical and warning are not used: it's either ok or critical.
+ ## Make sure the logfile is getting written to
+ ## Supports: Nagios, MRTG
+ ## Especially useful for syslog redirectors
+ ## Should be run on the system housing the logs
+ ## Optional argument "logfile" tells where the logfile is
+ ## Allows for some conversion characters.
+ ## Example: --logfile="/syslog/%Y-m%-d%/H%/postgres.log"
+ ## Critical and warning are not used: it's either ok or critical.
- my $critwarn = $opt{warning} ? 0 : 1;
+ my $critwarn = $opt{warning} ? 0 : 1;
- $SQL = q{
+ $SQL = q{
SELECT name, CASE WHEN length(setting)<1 THEN '?' ELSE setting END AS s
FROM pg_settings
WHERE name IN ('log_destination','log_directory','log_filename','redirect_stderr','syslog_facility')
ORDER BY name
};
- my $logfilere = qr{^[\w_\s\/%\-\.]+$};
- if (exists $opt{logfile} and $opt{logfile} !~ $logfilere) {
- ndie msg('logfile-opt-bad');
- }
-
- my $info = run_command($SQL);
- $VERBOSE >= 3 and warn Dumper $info;
-
- for $db (@{$info->{db}}) {
- my $i;
- for my $r (@{$db->{slurp}}) {
- $i->{$r->{name}} = $r->{s} || '?';
- }
- for my $word (qw{ log_destination log_directory log_filename redirect_stderr syslog_facility }) {
- $i->{$word} = '?' if ! exists $i->{$word};
- }
-
- ## Figure out what we think the log file will be
- my $logfile ='';
- if (exists $opt{logfile} and $opt{logfile} =~ /\w/) {
- $logfile = $opt{logfile};
- }
- else {
- if ($i->{log_destination} eq 'syslog') {
- ## We'll make a best effort to figure out where it is. Using the --logfile option is preferred.
- $logfile = '/var/log/messages';
- if (open my $cfh, '<', '/etc/syslog.conf') {
- while (<$cfh>) {
- if (/\b$i->{syslog_facility}\.(?!none).+?([\w\/]+)$/i) {
- $logfile = $1;
- }
- }
- }
- if (!$logfile or ! -e $logfile) {
- ndie msg('logfile-syslog', $i->{syslog_facility});
- }
- }
- elsif ($i->{log_destination} eq 'stderr') {
- if ($i->{redirect_stderr} ne 'yes') {
- ndie msg('logfile-stderr');
- }
- }
- }
-
- ## We now have a logfile (or a template)..parse it into pieces.
- ## We need at least hour, day, month, year
- my @t = localtime;
- my ($H,$d,$m,$Y) = (sprintf ('%02d',$t[2]),sprintf('%02d',$t[3]),sprintf('%02d',$t[4]+1),$t[5]+1900);
- if ($logfile !~ $logfilere) {
- ndie msg('logfile-bad',$logfile);
- }
- $logfile =~ s/%%/~~/g;
- $logfile =~ s/%Y/$Y/g;
- $logfile =~ s/%m/$m/g;
- $logfile =~ s/%d/$d/g;
- $logfile =~ s/%H/$H/g;
-
- $VERBOSE >= 3 and warn msg('logfile-debug2', $logfile);
-
- if (! -e $logfile) {
- my $msg = msg('logfile-dne', $logfile);
- $MRTG and ndie $msg;
- if ($critwarn) {
- add_unknown $msg;
- }
- else {
- add_warning $msg;
- }
- next;
- }
- my $logfh;
- unless (open $logfh, '<', $logfile) {
- add_unknown msg('logfile-openfail', $logfile, $!);
- next;
- }
- seek($logfh, 0, 2) or ndie msg('logfile-seekfail', $logfile, $!);
-
- ## Throw a custom error string.
- ## We do the number first as old versions only show part of the string.
- my $random_number = int rand(999999999999);
- my $funky = sprintf "check_postgres_logfile_error_$random_number $ME DB=$db->{dbname} PID=$$ Time=%s",
- scalar localtime;
-
- ## Cause an error on just this target
- delete @{$db}{qw(ok slurp totaltime)};
- my $badinfo = run_command("$funky", {failok => 1, target => $db} );
-
- my $MAXSLEEPTIME = $opt{timeout} || 20;
- my $SLEEP = 1;
- my $found = 0;
- LOGWAIT: {
- sleep $SLEEP;
- seek $logfh, 0, 1 or ndie msg('logfile-seekfail', $logfile, $!);
- while (<$logfh>) {
- if (/logfile_error_$random_number/) { ## Some logs break things up, so we don't use funky
- $found = 1;
- last LOGWAIT;
- }
- }
- $MAXSLEEPTIME -= $SLEEP;
- redo if $MAXSLEEPTIME > 0;
- my $msg = msg('logfile-fail', $logfile);
- $MRTG and do_mrtg({one => 0, msg => $msg});
- if ($critwarn) {
- add_critical $msg;
- }
- else {
- add_warning $msg;
- }
- }
- close $logfh or ndie msg('file-noclose', $logfile, $!);
-
- if ($found == 1) {
- $MRTG and do_mrtg({one => 1});
- add_ok msg('logfile-ok', $logfile);
- }
- }
- return;
+ my $logfilere = qr{^[\w_\s\/%\-\.]+$};
+ if (exists $opt{logfile} and $opt{logfile} !~ $logfilere) {
+ ndie msg('logfile-opt-bad');
+ }
+
+ my $info = run_command($SQL);
+ $VERBOSE >= 3 and warn Dumper $info;
+
+ for $db (@{$info->{db}}) {
+ my $i;
+ for my $r (@{$db->{slurp}}) {
+ $i->{$r->{name}} = $r->{s} || '?';
+ }
+ for my $word (qw{ log_destination log_directory log_filename redirect_stderr syslog_facility }) {
+ $i->{$word} = '?' if ! exists $i->{$word};
+ }
+
+ ## Figure out what we think the log file will be
+ my $logfile ='';
+ if (exists $opt{logfile} and $opt{logfile} =~ /\w/) {
+ $logfile = $opt{logfile};
+ }
+ else {
+ if ($i->{log_destination} eq 'syslog') {
+ ## We'll make a best effort to figure out where it is. Using the --logfile option is preferred.
+ $logfile = '/var/log/messages';
+ if (open my $cfh, '<', '/etc/syslog.conf') {
+ while (<$cfh>) {
+ if (/\b$i->{syslog_facility}\.(?!none).+?([\w\/]+)$/i) {
+ $logfile = $1;
+ }
+ }
+ }
+ if (!$logfile or ! -e $logfile) {
+ ndie msg('logfile-syslog', $i->{syslog_facility});
+ }
+ }
+ elsif ($i->{log_destination} eq 'stderr') {
+ if ($i->{redirect_stderr} ne 'yes') {
+ ndie msg('logfile-stderr');
+ }
+ }
+ }
+
+ ## We now have a logfile (or a template)..parse it into pieces.
+ ## We need at least hour, day, month, year
+ my @t = localtime;
+ my ($H,$d,$m,$Y) = (sprintf ('%02d',$t[2]),sprintf('%02d',$t[3]),sprintf('%02d',$t[4]+1),$t[5]+1900);
+ if ($logfile !~ $logfilere) {
+ ndie msg('logfile-bad',$logfile);
+ }
+ $logfile =~ s/%%/~~/g;
+ $logfile =~ s/%Y/$Y/g;
+ $logfile =~ s/%m/$m/g;
+ $logfile =~ s/%d/$d/g;
+ $logfile =~ s/%H/$H/g;
+
+ $VERBOSE >= 3 and warn msg('logfile-debug2', $logfile);
+
+ if (! -e $logfile) {
+ my $msg = msg('logfile-dne', $logfile);
+ $MRTG and ndie $msg;
+ if ($critwarn) {
+ add_unknown $msg;
+ }
+ else {
+ add_warning $msg;
+ }
+ next;
+ }
+ my $logfh;
+ unless (open $logfh, '<', $logfile) {
+ add_unknown msg('logfile-openfail', $logfile, $!);
+ next;
+ }
+ seek($logfh, 0, 2) or ndie msg('logfile-seekfail', $logfile, $!);
+
+ ## Throw a custom error string.
+ ## We do the number first as old versions only show part of the string.
+ my $random_number = int rand(999999999999);
+ my $funky = sprintf "check_postgres_logfile_error_$random_number $ME DB=$db->{dbname} PID=$$ Time=%s",
+ scalar localtime;
+
+ ## Cause an error on just this target
+ delete @{$db}{qw(ok slurp totaltime)};
+ my $badinfo = run_command("$funky", {failok => 1, target => $db} );
+
+ my $MAXSLEEPTIME = $opt{timeout} || 20;
+ my $SLEEP = 1;
+ my $found = 0;
+ LOGWAIT: {
+ sleep $SLEEP;
+ seek $logfh, 0, 1 or ndie msg('logfile-seekfail', $logfile, $!);
+ while (<$logfh>) {
+ if (/logfile_error_$random_number/) { ## Some logs break things up, so we don't use funky
+ $found = 1;
+ last LOGWAIT;
+ }
+ }
+ $MAXSLEEPTIME -= $SLEEP;
+ redo if $MAXSLEEPTIME > 0;
+ my $msg = msg('logfile-fail', $logfile);
+ $MRTG and do_mrtg({one => 0, msg => $msg});
+ if ($critwarn) {
+ add_critical $msg;
+ }
+ else {
+ add_warning $msg;
+ }
+ }
+ close $logfh or ndie msg('file-noclose', $logfile, $!);
+
+ if ($found == 1) {
+ $MRTG and do_mrtg({one => 1});
+ add_ok msg('logfile-ok', $logfile);
+ }
+ }
+ return;
} ## end of check_logfile
sub check_new_version_bc {
- ## Check if a new version of Bucardo is available
-
- my $site = 'bucardo.org';
- my $path = 'bucardo/latest_version.txt';
- my $url = "http://$site/$path";
- my ($newver,$maj,$rev,$message) = ('','','','');
- my $versionre = qr{((\d+\.\d+)\.(\d+))\s+(.+)};
-
- for my $meth (@get_methods) {
- eval {
- my $COM = "$meth $url";
- $VERBOSE >= 1 and warn "TRYING: $COM\n";
- my $info = qx{$COM 2>/dev/null};
- if ($info =~ $versionre) {
- ($newver,$maj,$rev,$message) = ($1,$2,$3,$4);
- }
- $VERBOSE >=1 and warn "SET version to $newver\n";
- };
- last if length $newver;
- }
-
- if (! length $newver) {
- add_unknown msg('new-bc-fail');
- return;
- }
-
- my $BCVERSION = '?';
- eval {
- $BCVERSION = qx{bucardo_ctl --version 2>&1};
- };
- if ($@ or !$BCVERSION) {
- add_unknown msg('new-bc-badver');
- return;
- }
-
- if ($BCVERSION !~ s/.*((\d+\.\d+)\.(\d+)).*/$1/s) {
- add_unknown msg('new-bc-fail');
- return;
- }
- my ($cmaj,$crev) = ($2,$3);
-
- if ($newver eq $BCVERSION) {
- add_ok msg('new-bc-ok', $newver);
- return;
- }
-
- $nohost = $message;
- if ($cmaj eq $maj) {
- add_critical msg('new-bc-warn', $newver, $BCVERSION);
- }
- else {
- add_warning msg('new-bc-warn', $newver, $BCVERSION);
- }
- return;
+ ## Check if a new version of Bucardo is available
+
+ my $site = 'bucardo.org';
+ my $path = 'bucardo/latest_version.txt';
+ my $url = "http://$site/$path";
+ my ($newver,$maj,$rev,$message) = ('','','','');
+ my $versionre = qr{((\d+\.\d+)\.(\d+))\s+(.+)};
+
+ for my $meth (@get_methods) {
+ eval {
+ my $COM = "$meth $url";
+ $VERBOSE >= 1 and warn "TRYING: $COM\n";
+ my $info = qx{$COM 2>/dev/null};
+ if ($info =~ $versionre) {
+ ($newver,$maj,$rev,$message) = ($1,$2,$3,$4);
+ }
+ $VERBOSE >=1 and warn "SET version to $newver\n";
+ };
+ last if length $newver;
+ }
+
+ if (! length $newver) {
+ add_unknown msg('new-bc-fail');
+ return;
+ }
+
+ my $BCVERSION = '?';
+ eval {
+ $BCVERSION = qx{bucardo_ctl --version 2>&1};
+ };
+ if ($@ or !$BCVERSION) {
+ add_unknown msg('new-bc-badver');
+ return;
+ }
+
+ if ($BCVERSION !~ s/.*((\d+\.\d+)\.(\d+)).*/$1/s) {
+ add_unknown msg('new-bc-fail');
+ return;
+ }
+ my ($cmaj,$crev) = ($2,$3);
+
+ if ($newver eq $BCVERSION) {
+ add_ok msg('new-bc-ok', $newver);
+ return;
+ }
+
+ $nohost = $message;
+ if ($cmaj eq $maj) {
+ add_critical msg('new-bc-warn', $newver, $BCVERSION);
+ }
+ else {
+ add_warning msg('new-bc-warn', $newver, $BCVERSION);
+ }
+ return;
} ## end of check_new_version_bc
sub check_new_version_cp {
- ## Check if a new version of check_postgres.pl is available
- ## You probably don't want to run this one every five minutes. :)
-
- my $site = 'bucardo.org';
- my $path = 'check_postgres/latest_version.txt';
- my $url = "http://$site/$path";
- my ($newver,$maj,$rev,$message) = ('','','','');
- my $versionre = qr{((\d+\.\d+)\.(\d+))\s+(.+)};
-
- for my $meth (@get_methods) {
- eval {
- my $COM = "$meth $url";
- $VERBOSE >= 1 and warn "TRYING: $COM\n";
- my $info = qx{$COM 2>/dev/null};
- if ($info =~ $versionre) {
- ($newver,$maj,$rev,$message) = ($1,$2,$3,$4);
- }
- $VERBOSE >=1 and warn "SET version to $newver\n";
- };
- last if length $newver;
- }
-
- if (! length $newver) {
- add_unknown msg('new-cp-fail');
- return;
- }
-
- if ($newver eq $VERSION) {
- add_ok msg('new-cp-ok', $newver);
- return;
- }
-
- if ($VERSION !~ /(\d+\.\d+)\.(\d+)/) {
- add_unknown msg('new-cp-fail');
- return;
- }
-
- $nohost = $message;
- my ($cmaj,$crev) = ($1,$2);
- if ($cmaj eq $maj) {
- add_warning msg('new-cp-warn', $newver, $VERSION);
- }
- else {
- add_critical msg('new-cp-warn', $newver, $VERSION);
- }
- return;
+ ## Check if a new version of check_postgres.pl is available
+ ## You probably don't want to run this one every five minutes. :)
+
+ my $site = 'bucardo.org';
+ my $path = 'check_postgres/latest_version.txt';
+ my $url = "http://$site/$path";
+ my ($newver,$maj,$rev,$message) = ('','','','');
+ my $versionre = qr{((\d+\.\d+)\.(\d+))\s+(.+)};
+
+ for my $meth (@get_methods) {
+ eval {
+ my $COM = "$meth $url";
+ $VERBOSE >= 1 and warn "TRYING: $COM\n";
+ my $info = qx{$COM 2>/dev/null};
+ if ($info =~ $versionre) {
+ ($newver,$maj,$rev,$message) = ($1,$2,$3,$4);
+ }
+ $VERBOSE >=1 and warn "SET version to $newver\n";
+ };
+ last if length $newver;
+ }
+
+ if (! length $newver) {
+ add_unknown msg('new-cp-fail');
+ return;
+ }
+
+ if ($newver eq $VERSION) {
+ add_ok msg('new-cp-ok', $newver);
+ return;
+ }
+
+ if ($VERSION !~ /(\d+\.\d+)\.(\d+)/) {
+ add_unknown msg('new-cp-fail');
+ return;
+ }
+
+ $nohost = $message;
+ my ($cmaj,$crev) = ($1,$2);
+ if ($cmaj eq $maj) {
+ add_warning msg('new-cp-warn', $newver, $VERSION);
+ }
+ else {
+ add_critical msg('new-cp-warn', $newver, $VERSION);
+ }
+ return;
} ## end of check_new_version_cp
sub check_new_version_pg {
- ## Check if a new version of Postgres is available
- ## Note that we only check the revision
- ## This also depends highly on the web page at postgresql.org not changing format
-
- my $url = 'http://www.postgresql.org/versions.rss';
- my $versionre = qr{<title>(\d+)\.(\d+)\.(\d+)</title>};
-
- my %newver;
- for my $meth (@get_methods) {
- eval {
- my $COM = "$meth $url";
- $VERBOSE >= 1 and warn "TRYING: $COM\n";
- my $info = qx{$COM 2>/dev/null};
- while ($info =~ /$versionre/g) {
- my ($maj,$min,$rev) = ($1,$2,$3);
- $newver{"$maj.$min"} = $rev;
- }
- };
- last if %newver;
- }
-
- my $info = run_command('SELECT version() AS version');
-
- $db = $info->{db}[0];
-
- if ($db->{slurp}[0]{version} !~ /PostgreSQL (\S+)/o) { ## no critic (ProhibitUnusedCapture)
- add_unknown msg('invalid-query', $db->{slurp});
- return;
- }
-
- my $currver = $1;
- if ($currver !~ /(\d+\.\d+)\.(\d+)/) {
- add_unknown msg('new-pg-badver', $currver);
- return;
- }
-
- my ($ver,$rev) = ($1,$2);
- if (! exists $newver{$ver}) {
- add_unknown msg('new-pg-badver2', $ver);
- return;
- }
-
- my $newrev = $newver{$ver};
- if ($newrev > $rev) {
- add_warning msg('new-pg-big', "$ver.$newrev", $currver);
- }
- elsif ($newrev < $rev) {
- add_critical msg('new-pg-small', "$ver.$newrev", $currver);
- }
- else {
- add_ok msg('new-pg-match', $currver);
- }
-
- return;
+ ## Check if a new version of Postgres is available
+ ## Note that we only check the revision
+ ## This also depends highly on the web page at postgresql.org not changing format
+
+ my $url = 'http://www.postgresql.org/versions.rss';
+ my $versionre = qr{<title>(\d+)\.(\d+)\.(\d+)</title>};
+
+ my %newver;
+ for my $meth (@get_methods) {
+ eval {
+ my $COM = "$meth $url";
+ $VERBOSE >= 1 and warn "TRYING: $COM\n";
+ my $info = qx{$COM 2>/dev/null};
+ while ($info =~ /$versionre/g) {
+ my ($maj,$min,$rev) = ($1,$2,$3);
+ $newver{"$maj.$min"} = $rev;
+ }
+ };
+ last if %newver;
+ }
+
+ my $info = run_command('SELECT version() AS version');
+
+ $db = $info->{db}[0];
+
+ if ($db->{slurp}[0]{version} !~ /PostgreSQL (\S+)/o) { ## no critic (ProhibitUnusedCapture)
+ add_unknown msg('invalid-query', $db->{slurp});
+ return;
+ }
+
+ my $currver = $1;
+ if ($currver !~ /(\d+\.\d+)\.(\d+)/) {
+ add_unknown msg('new-pg-badver', $currver);
+ return;
+ }
+
+ my ($ver,$rev) = ($1,$2);
+ if (! exists $newver{$ver}) {
+ add_unknown msg('new-pg-badver2', $ver);
+ return;
+ }
+
+ my $newrev = $newver{$ver};
+ if ($newrev > $rev) {
+ add_warning msg('new-pg-big', "$ver.$newrev", $currver);
+ }
+ elsif ($newrev < $rev) {
+ add_critical msg('new-pg-small', "$ver.$newrev", $currver);
+ }
+ else {
+ add_ok msg('new-pg-match', $currver);
+ }
+
+ return;
} ## end of check_new_version_pg
sub check_pg_stat_activity {
- ## Common function to run various actions against the pg_stat_activity view
- ## Actions: txn_idle, txn_time, query_time
- ## Supports: Nagios, MRTG
- ## It makes no sense to run this more than once on the same cluster
- ## Warning and critical are time limits - defaults to seconds
- ## Valid units: s[econd], m[inute], h[our], d[ay]
- ## All above may be written as plural as well (e.g. "2 hours")
- ## Can also ignore databases with exclude and limit with include
- ## Limit to a specific user with the includeuser option
- ## Exclude users with the excludeuser option
-
- my $arg = shift || {};
-
- my ($warning, $critical) = validate_range
- ({
- type => 'time',
- default_warning => $arg->{default_warning},
- default_critical => $arg->{default_critical},
- });
-
- ## Grab information from the pg_stat_activity table
- ## Since we clobber old info on a qtime "tie", use an ORDER BY
- $SQL = qq{
+ ## Common function to run various actions against the pg_stat_activity view
+ ## Actions: txn_idle, txn_time, query_time
+ ## Supports: Nagios, MRTG
+ ## It makes no sense to run this more than once on the same cluster
+ ## Warning and critical are time limits - defaults to seconds
+ ## Valid units: s[econd], m[inute], h[our], d[ay]
+ ## All above may be written as plural as well (e.g. "2 hours")
+ ## Can also ignore databases with exclude and limit with include
+ ## Limit to a specific user with the includeuser option
+ ## Exclude users with the excludeuser option
+
+ my $arg = shift || {};
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'time',
+ default_warning => $arg->{default_warning},
+ default_critical => $arg->{default_critical},
+ });
+
+ ## Grab information from the pg_stat_activity table
+ ## Since we clobber old info on a qtime "tie", use an ORDER BY
+ $SQL = qq{
SELECT
xact_start,
SUBSTR(current_query,0,100) AS current_query,
ORDER BY xact_start, procpid DESC
};
- my $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1 } );
-
- ## Default values for information gathered
- my ($maxact, $maxtime, $client_addr, $client_port, $procpid, $username, $maxdb, $maxq) =
- ('?',0,'?','?','?','?','?','?');
-
- for $db (@{$info->{db}}) {
-
- ## Parse the psql output and gather stats from the winning row
- ## Read in and parse the psql output
- my $skipped = 0;
- ROW: for my $r (@{$db->{slurp}}) {
-
- ## Apply --exclude and --include arguments to the database name
- if (skip_item($r->{datname})) {
- $skipped++;
- next ROW;
- }
-
- ## Detect cases where pg_stat_activity is not fully populated
- if ($r->{xact_start} !~ /\d/o) {
- ## Perhaps this is a non-superuser?
- if ($r->{current_query} =~ /insufficient/) {
- add_unknown msg('psa-nosuper');
- }
- ## Perhaps stats_command_string / track_activities is off?
- elsif ($r->{current_query} =~ /disabled/) {
- add_unknown msg('psa-disabled');
- }
- ## Something else is going on
- else {
- add_unknown msg('psa-noxact');
- }
- return;
- }
-
- ## Assign stats if we have a new winner
- if ($r->{qtime} >= $maxtime) {
- $maxact = $r->{xact_start};
- $client_addr = $r->{client_addr};
- $client_port = $r->{client_port};
- $procpid = $r->{procpid};
- $maxtime = $r->{qtime};
- $maxdb = $r->{datname};
- $username = $r->{usename};
- $maxq = $r->{current_query};
- }
- }
-
- ## We don't really care why things matches as far as the final output
- ## But it's nice to report what we can
- if ($maxdb eq '?') {
- $MRTG and do_mrtg({one => 0, msg => 'No rows'});
- $db->{perf} = "0;$warning;$critical";
-
- if ($skipped) {
- add_ok msg('psa-skipped', $skipped);
- }
- else {
- add_ok msg('psa-nomatches');
- }
- return;
- }
-
- ## Details on who the offender was
- my $whodunit = sprintf q{%s:%s %s:%s%s%s %s:%s},
- msg('database'),
- $maxdb,
- msg('PID'),
- $procpid,
- $client_port < 1 ? '' : (sprintf ' %s:%s', msg('port'), $client_port),
- $client_addr eq '' ? '' : (sprintf ' %s:%s', msg('address'), $client_addr),
- msg('username'),
- $username;
-
- my $details = '';
- if ($VERBOSE >= 1 and $maxtime > 0) { ## >0 so we don't report ourselves
- $maxq =~ s/\n/\\n/g;
- $details = " " . msg('Query', $maxq);
- }
-
- $MRTG and do_mrtg({one => $maxtime, msg => "$whodunit$details"});
-
- $db->{perf} .= sprintf q{'%s'=%s;%s;%s},
- $whodunit,
- $maxtime,
- $warning,
- $critical;
-
- my $m = $action eq 'query_time' ? msg('qtime-msg', $maxtime)
- : $action eq 'txn_time' ? msg('txntime-msg', $maxtime)
- : $action eq 'txn_idle' ? msg('txnidle-msg', $maxtime)
- : die "Unkown action: $action\n";
- my $msg = sprintf '%s (%s)%s', $m, $whodunit, $details;
-
- if (length $critical and $maxtime >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $maxtime >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
-
- return;
+ my $info = run_command($SQL, { regex => qr{\d+}, emptyok => 1 } );
+
+ ## Default values for information gathered
+ my ($maxact, $maxtime, $client_addr, $client_port, $procpid, $username, $maxdb, $maxq) =
+ ('?',0,'?','?','?','?','?','?');
+
+ for $db (@{$info->{db}}) {
+
+ ## Parse the psql output and gather stats from the winning row
+ ## Read in and parse the psql output
+ my $skipped = 0;
+ ROW: for my $r (@{$db->{slurp}}) {
+
+ ## Apply --exclude and --include arguments to the database name
+ if (skip_item($r->{datname})) {
+ $skipped++;
+ next ROW;
+ }
+
+ ## Detect cases where pg_stat_activity is not fully populated
+ if ($r->{xact_start} !~ /\d/o) {
+ ## Perhaps this is a non-superuser?
+ if ($r->{current_query} =~ /insufficient/) {
+ add_unknown msg('psa-nosuper');
+ }
+ ## Perhaps stats_command_string / track_activities is off?
+ elsif ($r->{current_query} =~ /disabled/) {
+ add_unknown msg('psa-disabled');
+ }
+ ## Something else is going on
+ else {
+ add_unknown msg('psa-noxact');
+ }
+ return;
+ }
+
+ ## Assign stats if we have a new winner
+ if ($r->{qtime} >= $maxtime) {
+ $maxact = $r->{xact_start};
+ $client_addr = $r->{client_addr};
+ $client_port = $r->{client_port};
+ $procpid = $r->{procpid};
+ $maxtime = $r->{qtime};
+ $maxdb = $r->{datname};
+ $username = $r->{usename};
+ $maxq = $r->{current_query};
+ }
+ }
+
+ ## We don't really care why things matches as far as the final output
+ ## But it's nice to report what we can
+ if ($maxdb eq '?') {
+ $MRTG and do_mrtg({one => 0, msg => 'No rows'});
+ $db->{perf} = "0;$warning;$critical";
+
+ if ($skipped) {
+ add_ok msg('psa-skipped', $skipped);
+ }
+ else {
+ add_ok msg('psa-nomatches');
+ }
+ return;
+ }
+
+ ## Details on who the offender was
+ my $whodunit = sprintf q{%s:%s %s:%s%s%s %s:%s},
+ msg('database'),
+ $maxdb,
+ msg('PID'),
+ $procpid,
+ $client_port < 1 ? '' : (sprintf ' %s:%s', msg('port'), $client_port),
+ $client_addr eq '' ? '' : (sprintf ' %s:%s', msg('address'), $client_addr),
+ msg('username'),
+ $username;
+
+ my $details = '';
+ if ($VERBOSE >= 1 and $maxtime > 0) { ## >0 so we don't report ourselves
+ $maxq =~ s/\n/\\n/g;
+ $details = " " . msg('Query', $maxq);
+ }
+
+ $MRTG and do_mrtg({one => $maxtime, msg => "$whodunit$details"});
+
+ $db->{perf} .= sprintf q{'%s'=%s;%s;%s},
+ $whodunit,
+ $maxtime,
+ $warning,
+ $critical;
+
+ my $m = $action eq 'query_time' ? msg('qtime-msg', $maxtime)
+ : $action eq 'txn_time' ? msg('txntime-msg', $maxtime)
+ : $action eq 'txn_idle' ? msg('txnidle-msg', $maxtime)
+ : die "Unkown action: $action\n";
+ my $msg = sprintf '%s (%s)%s', $m, $whodunit, $details;
+
+ if (length $critical and $maxtime >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $maxtime >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+
+ return;
} ## end of check_pg_stat_activity
sub check_pgbouncer_checksum {
- ## Verify the checksum of all pgbouncer settings
- ## Supports: Nagios, MRTG
- ## Not that the connection will be done on the pgbouncer database
- ## One of warning or critical must be given (but not both)
- ## It should run one time to find out the expected checksum
- ## You can use --critical="0" to find out the checksum
- ## You can include or exclude settings as well
- ## Example:
- ## check_postgres_pgbouncer_checksum --critical="4e7ba68eb88915d3d1a36b2009da4acd"
-
- my ($warning, $critical) = validate_range({type => 'checksum', onlyone => 1});
-
- eval {
- require Digest::MD5;
- };
- if ($@) {
- ndie msg('checksum-nomd');
- }
-
- $SQL = 'SHOW CONFIG';
- my $info = run_command($SQL, { regex => qr[log_pooler_errors] });
-
- $db = $info->{db};
-
- my $newstring = '';
- for my $r (@{$db->{slurp}}) {
- my $key = $r->{key};
- next if skip_item($key);
- $newstring .= "$r->{key} = $r->{value}\n";
- }
-
- if (! length $newstring) {
- add_unknown msg('no-match-set');
- }
-
- my $checksum = Digest::MD5::md5_hex($newstring);
-
- my $msg = msg('checksum-msg', $checksum);
- if ($MRTG) {
- $opt{mrtg} or ndie msg('checksum-nomrtg');
- do_mrtg({one => $opt{mrtg} eq $checksum ? 1 : 0, msg => $checksum});
- }
- if ($critical and $critical ne $checksum) {
- add_critical $msg;
- }
- elsif ($warning and $warning ne $checksum) {
- add_warning $msg;
- }
- elsif (!$critical and !$warning) {
- add_unknown $msg;
- }
- else {
- add_ok $msg;
- }
-
- return;
+ ## Verify the checksum of all pgbouncer settings
+ ## Supports: Nagios, MRTG
+ ## Not that the connection will be done on the pgbouncer database
+ ## One of warning or critical must be given (but not both)
+ ## It should run one time to find out the expected checksum
+ ## You can use --critical="0" to find out the checksum
+ ## You can include or exclude settings as well
+ ## Example:
+ ## check_postgres_pgbouncer_checksum --critical="4e7ba68eb88915d3d1a36b2009da4acd"
+
+ my ($warning, $critical) = validate_range({type => 'checksum', onlyone => 1});
+
+ eval {
+ require Digest::MD5;
+ };
+ if ($@) {
+ ndie msg('checksum-nomd');
+ }
+
+ $SQL = 'SHOW CONFIG';
+ my $info = run_command($SQL, { regex => qr[log_pooler_errors] });
+
+ $db = $info->{db};
+
+ my $newstring = '';
+ for my $r (@{$db->{slurp}}) {
+ my $key = $r->{key};
+ next if skip_item($key);
+ $newstring .= "$r->{key} = $r->{value}\n";
+ }
+
+ if (! length $newstring) {
+ add_unknown msg('no-match-set');
+ }
+
+ my $checksum = Digest::MD5::md5_hex($newstring);
+
+ my $msg = msg('checksum-msg', $checksum);
+ if ($MRTG) {
+ $opt{mrtg} or ndie msg('checksum-nomrtg');
+ do_mrtg({one => $opt{mrtg} eq $checksum ? 1 : 0, msg => $checksum});
+ }
+ if ($critical and $critical ne $checksum) {
+ add_critical $msg;
+ }
+ elsif ($warning and $warning ne $checksum) {
+ add_warning $msg;
+ }
+ elsif (!$critical and !$warning) {
+ add_unknown $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+
+ return;
} ## end of check_pgbouncer_checksum
sub check_prepared_txns {
- ## Checks age of prepared transactions
- ## Most installations probably want no prepared_transactions
- ## Supports: Nagios, MRTG
+ ## Checks age of prepared transactions
+ ## Most installations probably want no prepared_transactions
+ ## Supports: Nagios, MRTG
- my ($warning, $critical) = validate_range
- ({
- type => 'seconds',
- default_warning => '1',
- default_critical => '30',
- });
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'seconds',
+ default_warning => '1',
+ default_critical => '30',
+ });
- my $SQL = q{
+ my $SQL = q{
SELECT database, ROUND(EXTRACT(epoch FROM now()-prepared)) AS age, prepared
FROM pg_prepared_xacts
ORDER BY prepared ASC
};
- my $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } );
-
- my $msg = msg('preptxn-none');
- my $found = 0;
- for $db (@{$info->{db}}) {
- my (@crit,@warn,@ok);
- my ($maxage,$maxdb) = (0,''); ## used by MRTG only
- ROW: for my $r (@{$db->{slurp}}) {
- my ($dbname,$age,$date) = ($r->{database},$r->{age},$r->{prepared});
- $found = 1 if ! $found;
- next ROW if skip_item($dbname);
- $found = 2;
- if ($MRTG) {
- if ($age > $maxage) {
- $maxdb = $dbname;
- $maxage = $age;
- }
- elsif ($age == $maxage) {
- $maxdb .= sprintf "%s$dbname", length $maxdb ? ' | ' : '';
- }
- next;
- }
-
- $msg = "$dbname=$date ($age)";
- $db->{perf} .= " $msg";
- if (length $critical and $age >= $critical) {
- push @crit => $msg;
- }
- elsif (length $warning and $age >= $warning) {
- push @warn => $msg;
- }
- else {
- push @ok => $msg;
- }
- }
- if ($MRTG) {
- do_mrtg({one => $maxage, msg => $maxdb});
- }
- elsif (0 == $found) {
- add_ok msg('preptxn-none');
- }
- elsif (1 == $found) {
- add_unknown msg('no-match-db');
- }
- elsif (@crit) {
- add_critical join ' ' => @crit;
- }
- elsif (@warn) {
- add_warning join ' ' => @warn;
- }
- else {
- add_ok join ' ' => @ok;
- }
- }
-
- return;
+ my $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } );
+
+ my $msg = msg('preptxn-none');
+ my $found = 0;
+ for $db (@{$info->{db}}) {
+ my (@crit,@warn,@ok);
+ my ($maxage,$maxdb) = (0,''); ## used by MRTG only
+ ROW: for my $r (@{$db->{slurp}}) {
+ my ($dbname,$age,$date) = ($r->{database},$r->{age},$r->{prepared});
+ $found = 1 if ! $found;
+ next ROW if skip_item($dbname);
+ $found = 2;
+ if ($MRTG) {
+ if ($age > $maxage) {
+ $maxdb = $dbname;
+ $maxage = $age;
+ }
+ elsif ($age == $maxage) {
+ $maxdb .= sprintf "%s$dbname", length $maxdb ? ' | ' : '';
+ }
+ next;
+ }
+
+ $msg = "$dbname=$date ($age)";
+ $db->{perf} .= " $msg";
+ if (length $critical and $age >= $critical) {
+ push @crit => $msg;
+ }
+ elsif (length $warning and $age >= $warning) {
+ push @warn => $msg;
+ }
+ else {
+ push @ok => $msg;
+ }
+ }
+ if ($MRTG) {
+ do_mrtg({one => $maxage, msg => $maxdb});
+ }
+ elsif (0 == $found) {
+ add_ok msg('preptxn-none');
+ }
+ elsif (1 == $found) {
+ add_unknown msg('no-match-db');
+ }
+ elsif (@crit) {
+ add_critical join ' ' => @crit;
+ }
+ elsif (@warn) {
+ add_warning join ' ' => @warn;
+ }
+ else {
+ add_ok join ' ' => @ok;
+ }
+ }
+
+ return;
} ## end of check_prepared_txns
sub check_query_runtime {
- ## Make sure a known query runs at least as fast as we think it should
- ## Supports: Nagios, MRTG
- ## Warning and critical are time limits, defaulting to seconds
- ## Valid units: s[econd], m[inute], h[our], d[ay]
- ## Does a "EXPLAIN ANALYZE SELECT COUNT(1) FROM xyz"
- ## where xyz is given by the option --queryname
- ## This could also be a table or a function, or course, but must be a
- ## single word. If a function, it must be empty (with "()")
- ## Examples:
- ## --warning="100s" --critical="120s" --queryname="speedtest1"
- ## --warning="5min" --critical="15min" --queryname="speedtest()"
-
- my ($warning, $critical) = validate_range({type => 'time'});
-
- my $queryname = $opt{queryname} || '';
-
- if ($queryname !~ /^[\w\_\.]+(?:\(\))?$/) {
- ndie msg('runtime-badname');
- }
-
- $SQL = "EXPLAIN ANALYZE SELECT COUNT(1) FROM $queryname";
- my $info = run_command($SQL);
-
- for $db (@{$info->{db}}) {
- if (! exists $db->{slurp}[0]{queryplan}) {
- add_unknown msg('invalid-query', $db->{slurp});
- next;
- }
- my $totalms = -1;
- for my $r (@{$db->{slurp}}) {
- if ($r->{queryplan} =~ / (\d+\.\d+) ms/) {
- $totalms = $1;
- }
- }
- my $totalseconds = sprintf '%.2f', $totalms / 1000.0;
- if ($MRTG) {
- $stats{$db->{dbname}} = $totalseconds;
- next;
- }
- $db->{perf} = " qtime=$totalseconds";
- my $msg = msg('runtime-msg', $totalseconds);
- if (length $critical and $totalseconds >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $totalseconds >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
-
- $MRTG and do_mrtg_stats(msg('runtime-badmrtg'));
-
- return;
+ ## Make sure a known query runs at least as fast as we think it should
+ ## Supports: Nagios, MRTG
+ ## Warning and critical are time limits, defaulting to seconds
+ ## Valid units: s[econd], m[inute], h[our], d[ay]
+ ## Does a "EXPLAIN ANALYZE SELECT COUNT(1) FROM xyz"
+ ## where xyz is given by the option --queryname
+ ## This could also be a table or a function, or course, but must be a
+ ## single word. If a function, it must be empty (with "()")
+ ## Examples:
+ ## --warning="100s" --critical="120s" --queryname="speedtest1"
+ ## --warning="5min" --critical="15min" --queryname="speedtest()"
+
+ my ($warning, $critical) = validate_range({type => 'time'});
+
+ my $queryname = $opt{queryname} || '';
+
+ if ($queryname !~ /^[\w\_\.]+(?:\(\))?$/) {
+ ndie msg('runtime-badname');
+ }
+
+ $SQL = "EXPLAIN ANALYZE SELECT COUNT(1) FROM $queryname";
+ my $info = run_command($SQL);
+
+ for $db (@{$info->{db}}) {
+ if (! exists $db->{slurp}[0]{queryplan}) {
+ add_unknown msg('invalid-query', $db->{slurp});
+ next;
+ }
+ my $totalms = -1;
+ for my $r (@{$db->{slurp}}) {
+ if ($r->{queryplan} =~ / (\d+\.\d+) ms/) {
+ $totalms = $1;
+ }
+ }
+ my $totalseconds = sprintf '%.2f', $totalms / 1000.0;
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $totalseconds;
+ next;
+ }
+ $db->{perf} = " qtime=$totalseconds";
+ my $msg = msg('runtime-msg', $totalseconds);
+ if (length $critical and $totalseconds >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $totalseconds >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+
+ $MRTG and do_mrtg_stats(msg('runtime-badmrtg'));
+
+ return;
} ## end of check_query_runtime
sub check_query_time {
- ## Check the length of running queries
+ ## Check the length of running queries
- return check_pg_stat_activity(
- {
- default_warning => '2 minutes',
- default_critical => '5 minutes',
- whereclause => q{current_query <> '<IDLE>'},
- offsetcol => q{query_start},
- });
+ return check_pg_stat_activity(
+ {
+ default_warning => '2 minutes',
+ default_critical => '5 minutes',
+ whereclause => q{current_query <> '<IDLE>'},
+ offsetcol => q{query_start},
+ });
} ## end of check_query_time
sub check_relation_size {
- my $relkind = shift || 'relation';
+ my $relkind = shift || 'relation';
- ## Check the size of one or more relations
- ## Supports: Nagios, MRTG
- ## By default, checks all relations
- ## Can check specific one(s) with include
- ## Can ignore some with exclude
- ## Warning and critical are bytes
- ## Valid units: b, k, m, g, t, e
- ## All above may be written as plural or with a trailing 'g'
- ## Limit to a specific user (relation owner) with the includeuser option
- ## Exclude users with the excludeuser option
+ ## Check the size of one or more relations
+ ## Supports: Nagios, MRTG
+ ## By default, checks all relations
+ ## Can check specific one(s) with include
+ ## Can ignore some with exclude
+ ## Warning and critical are bytes
+ ## Valid units: b, k, m, g, t, e
+ ## All above may be written as plural or with a trailing 'g'
+ ## Limit to a specific user (relation owner) with the includeuser option
+ ## Exclude users with the excludeuser option
- my ($warning, $critical) = validate_range({type => 'size'});
+ my ($warning, $critical) = validate_range({type => 'size'});
- $SQL = sprintf q{
+ $SQL = sprintf q{
SELECT pg_relation_size(c.oid) AS rsize,
pg_size_pretty(pg_relation_size(c.oid)) AS psize,
relkind, relname, nspname
FROM pg_class c, pg_namespace n WHERE (relkind = %s) AND n.oid = c.relnamespace
},
- $relkind eq 'table' ? q{'r'}
- : $relkind eq 'index' ? q{'i'}
- : q{'r' OR relkind = 'i'};
-
- if ($opt{perflimit}) {
- $SQL .= " ORDER BY 1 DESC LIMIT $opt{perflimit}";
- }
-
- if ($USERWHERECLAUSE) {
- $SQL =~ s/ WHERE/, pg_user u WHERE u.usesysid=c.relowner$USERWHERECLAUSE AND/;
- }
-
- my $info = run_command($SQL, {emptyok => 1});
-
- my $found = 0;
- for $db (@{$info->{db}}) {
-
- $found = 1;
- if ($db->{slurp}[0]{rsize} !~ /\d/ and $USERWHERECLAUSE) {
- $stats{$db->{dbname}} = 0;
- add_ok msg('no-match-user');
- next;
- }
-
- my ($max,$pmax,$kmax,$nmax,$smax) = (-1,0,0,'?','?');
-
- ROW: for my $r (@{$db->{slurp}}) {
- my ($size,$psize,$kind,$name,$schema) = @$r{qw/ rsize psize relkind relname nspname/};
-
- next ROW if skip_item($name, $schema);
-
- $db->{perf} .= sprintf "%s%s$name=$size",
- $VERBOSE==1 ? "\n" : ' ',
- $kind eq 'r' ? "$schema." : '';
- ($max=$size, $pmax=$psize, $kmax=$kind, $nmax=$name, $smax=$schema) if $size > $max;
- }
- if ($max < 0) {
- add_unknown msg('no-match-rel');
- next;
- }
- if ($MRTG) {
- $stats{$db->{dbname}} = $max;
- $statsmsg{$db->{dbname}} = sprintf "DB: $db->{dbname} %s %s$nmax",
- $kmax eq 'i' ? 'INDEX:' : 'TABLE:', $kmax eq 'i' ? '' : "$smax.";
- next;
- }
-
- my $msg;
- if ($relkind eq 'relation') {
- if ($kmax eq 'r') {
- $msg = msg('relsize-msg-relt', "$smax.$nmax", $pmax);
- }
- else {
- $msg = msg('relsize-msg-reli', $nmax, $pmax);
- }
- }
- elsif ($relkind eq 'table') {
- $msg = msg('relsize-msg-tab', "$smax.$nmax", $pmax);
- }
- else {
- $msg = msg('relsize-msg-ind', $nmax, $pmax);
- }
- if (length $critical and $max >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $max >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
-
- return;
+ $relkind eq 'table' ? q{'r'}
+ : $relkind eq 'index' ? q{'i'}
+ : q{'r' OR relkind = 'i'};
+
+ if ($opt{perflimit}) {
+ $SQL .= " ORDER BY 1 DESC LIMIT $opt{perflimit}";
+ }
+
+ if ($USERWHERECLAUSE) {
+ $SQL =~ s/ WHERE/, pg_user u WHERE u.usesysid=c.relowner$USERWHERECLAUSE AND/;
+ }
+
+ my $info = run_command($SQL, {emptyok => 1});
+
+ my $found = 0;
+ for $db (@{$info->{db}}) {
+
+ $found = 1;
+ if ($db->{slurp}[0]{rsize} !~ /\d/ and $USERWHERECLAUSE) {
+ $stats{$db->{dbname}} = 0;
+ add_ok msg('no-match-user');
+ next;
+ }
+
+ my ($max,$pmax,$kmax,$nmax,$smax) = (-1,0,0,'?','?');
+
+ ROW: for my $r (@{$db->{slurp}}) {
+ my ($size,$psize,$kind,$name,$schema) = @$r{qw/ rsize psize relkind relname nspname/};
+
+ next ROW if skip_item($name, $schema);
+
+ $db->{perf} .= sprintf "%s%s$name=$size",
+ $VERBOSE==1 ? "\n" : ' ',
+ $kind eq 'r' ? "$schema." : '';
+ ($max=$size, $pmax=$psize, $kmax=$kind, $nmax=$name, $smax=$schema) if $size > $max;
+ }
+ if ($max < 0) {
+ add_unknown msg('no-match-rel');
+ next;
+ }
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $max;
+ $statsmsg{$db->{dbname}} = sprintf "DB: $db->{dbname} %s %s$nmax",
+ $kmax eq 'i' ? 'INDEX:' : 'TABLE:', $kmax eq 'i' ? '' : "$smax.";
+ next;
+ }
+
+ my $msg;
+ if ($relkind eq 'relation') {
+ if ($kmax eq 'r') {
+ $msg = msg('relsize-msg-relt', "$smax.$nmax", $pmax);
+ }
+ else {
+ $msg = msg('relsize-msg-reli', $nmax, $pmax);
+ }
+ }
+ elsif ($relkind eq 'table') {
+ $msg = msg('relsize-msg-tab', "$smax.$nmax", $pmax);
+ }
+ else {
+ $msg = msg('relsize-msg-ind', $nmax, $pmax);
+ }
+ if (length $critical and $max >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $max >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+
+ return;
} ## end of check_relation_size
sub check_table_size {
- return check_relation_size('table');
+ return check_relation_size('table');
}
sub check_index_size {
- return check_relation_size('index');
+ return check_relation_size('index');
}
sub check_replicate_row {
- ## Make an update on one server, make sure it propogates to others
- ## Supports: Nagios, MRTG
- ## Warning and critical are time to replicate to all slaves
-
- my ($warning, $critical) = validate_range({type => 'time', leastone => 1, forcemrtg => 1});
-
- if ($warning and $critical and $warning > $critical) {
- ndie msg('range-warnbig');
- }
-
- if (!$opt{repinfo}) {
- ndie msg('rep-noarg');
- }
- my @repinfo = split /,/ => ($opt{repinfo} || '');
- if ($#repinfo != 5) {
- ndie msg('rep-badarg');
- }
- my ($table,$pk,$id,$col,$val1,$val2) = (@repinfo);
-
- ## Quote everything, just to be safe (e.g. columns named 'desc')
- $table = qq{"$table"};
- $pk = qq{"$pk"};
- $col = qq{"$col"};
-
- if ($val1 eq $val2) {
- ndie msg('rep-duh');
- }
-
- $SQL = qq{UPDATE $table SET $col = 'X' WHERE $pk = '$id'};
- (my $update1 = $SQL) =~ s/X/$val1/;
- (my $update2 = $SQL) =~ s/X/$val2/;
- my $select = qq{SELECT $col AS c FROM $table WHERE $pk = '$id'};
-
- ## Are they the same on both sides? Must be yes, or we error out
-
- ## We assume this is a single server
- my $info1 = run_command($select);
- ## Squirrel away the $db setting for later
- my $sourcedb = $info1->{db}[0];
- if (!defined $sourcedb) {
- ndie msg('rep-norow', "$table.$col");
- }
- my $value1 = $info1->{db}[0]{slurp}[0]{c};
-
- my $info2 = run_command($select, { dbnumber => 2 });
- my $slave = 0;
- for my $d (@{$info2->{db}}) {
- $slave++;
- my $value2 = $d->{slurp}[0]{c};
- if ($value1 ne $value2) {
- ndie msg('rep-notsame');
- }
- }
- my $numslaves = $slave;
- if ($numslaves < 1) {
- ndie msg('rep-noslaves');
- }
-
- my ($update,$newval);
- if ($value1 eq $val1) {
- $update = $update2;
- $newval = $val2;
- }
- elsif ($value1 eq $val2) {
- $update = $update1;
- $newval = $val1;
- }
- else {
- ndie msg('rep-wrongvals', $value1, $val1, $val2);
- }
-
- $info1 = run_command($update, { failok => 1 } );
-
- ## Make sure the update worked
- if (! defined $info1->{db}[0]) {
- ndie msg('rep-sourcefail');
- }
-
- my $err = $info1->{db}[0]{error} || '';
- if ($err) {
- $err =~ s/ERROR://; ## e.g. Slony read-only
- ndie $err;
- }
-
- ## Start the clock
- my $starttime = time();
-
- ## Loop until we get a match, check each in turn
- my %slave;
- my $time = 0;
- LOOP: {
- $info2 = run_command($select, { dbnumber => 2 } );
- ## Reset for final output
- $db = $sourcedb;
-
- $slave = 0;
- for my $d (@{$info2->{db}}) {
- $slave++;
- next if exists $slave{$slave};
- my $value2 = $d->{slurp}[0]{c};
- $time = $db->{totaltime} = time - $starttime;
- if ($value2 eq $newval) {
- $slave{$slave} = $time;
- next;
- }
- if ($warning and $time > $warning) {
- $MRTG and do_mrtg({one => 0, msg => $time});
- add_warning msg('rep-fail', $slave);
- return;
- }
- elsif ($critical and $time > $critical) {
- $MRTG and do_mrtg({one => 0, msg => $time});
- add_critical msg('rep-fail', $slave);
- return;
- }
- }
- ## Did they all match?
- my $k = keys %slave;
- if (keys %slave >= $numslaves) {
- $MRTG and do_mrtg({one => $time});
- add_ok msg('rep-ok');
- return;
- }
- sleep 1;
- redo;
- }
-
- $MRTG and ndie msg('rep-timeout', $time);
- add_unknown msg('rep-unknown');
- return;
+ ## Make an update on one server, make sure it propogates to others
+ ## Supports: Nagios, MRTG
+ ## Warning and critical are time to replicate to all slaves
+
+ my ($warning, $critical) = validate_range({type => 'time', leastone => 1, forcemrtg => 1});
+
+ if ($warning and $critical and $warning > $critical) {
+ ndie msg('range-warnbig');
+ }
+
+ if (!$opt{repinfo}) {
+ ndie msg('rep-noarg');
+ }
+ my @repinfo = split /,/ => ($opt{repinfo} || '');
+ if ($#repinfo != 5) {
+ ndie msg('rep-badarg');
+ }
+ my ($table,$pk,$id,$col,$val1,$val2) = (@repinfo);
+
+ ## Quote everything, just to be safe (e.g. columns named 'desc')
+ $table = qq{"$table"};
+ $pk = qq{"$pk"};
+ $col = qq{"$col"};
+
+ if ($val1 eq $val2) {
+ ndie msg('rep-duh');
+ }
+
+ $SQL = qq{UPDATE $table SET $col = 'X' WHERE $pk = '$id'};
+ (my $update1 = $SQL) =~ s/X/$val1/;
+ (my $update2 = $SQL) =~ s/X/$val2/;
+ my $select = qq{SELECT $col AS c FROM $table WHERE $pk = '$id'};
+
+ ## Are they the same on both sides? Must be yes, or we error out
+
+ ## We assume this is a single server
+ my $info1 = run_command($select);
+ ## Squirrel away the $db setting for later
+ my $sourcedb = $info1->{db}[0];
+ if (!defined $sourcedb) {
+ ndie msg('rep-norow', "$table.$col");
+ }
+ my $value1 = $info1->{db}[0]{slurp}[0]{c};
+
+ my $info2 = run_command($select, { dbnumber => 2 });
+ my $slave = 0;
+ for my $d (@{$info2->{db}}) {
+ $slave++;
+ my $value2 = $d->{slurp}[0]{c};
+ if ($value1 ne $value2) {
+ ndie msg('rep-notsame');
+ }
+ }
+ my $numslaves = $slave;
+ if ($numslaves < 1) {
+ ndie msg('rep-noslaves');
+ }
+
+ my ($update,$newval);
+ if ($value1 eq $val1) {
+ $update = $update2;
+ $newval = $val2;
+ }
+ elsif ($value1 eq $val2) {
+ $update = $update1;
+ $newval = $val1;
+ }
+ else {
+ ndie msg('rep-wrongvals', $value1, $val1, $val2);
+ }
+
+ $info1 = run_command($update, { failok => 1 } );
+
+ ## Make sure the update worked
+ if (! defined $info1->{db}[0]) {
+ ndie msg('rep-sourcefail');
+ }
+
+ my $err = $info1->{db}[0]{error} || '';
+ if ($err) {
+ $err =~ s/ERROR://; ## e.g. Slony read-only
+ ndie $err;
+ }
+
+ ## Start the clock
+ my $starttime = time();
+
+ ## Loop until we get a match, check each in turn
+ my %slave;
+ my $time = 0;
+ LOOP: {
+ $info2 = run_command($select, { dbnumber => 2 } );
+ ## Reset for final output
+ $db = $sourcedb;
+
+ $slave = 0;
+ for my $d (@{$info2->{db}}) {
+ $slave++;
+ next if exists $slave{$slave};
+ my $value2 = $d->{slurp}[0]{c};
+ $time = $db->{totaltime} = time - $starttime;
+ if ($value2 eq $newval) {
+ $slave{$slave} = $time;
+ next;
+ }
+ if ($warning and $time > $warning) {
+ $MRTG and do_mrtg({one => 0, msg => $time});
+ add_warning msg('rep-fail', $slave);
+ return;
+ }
+ elsif ($critical and $time > $critical) {
+ $MRTG and do_mrtg({one => 0, msg => $time});
+ add_critical msg('rep-fail', $slave);
+ return;
+ }
+ }
+ ## Did they all match?
+ my $k = keys %slave;
+ if (keys %slave >= $numslaves) {
+ $MRTG and do_mrtg({one => $time});
+ add_ok msg('rep-ok');
+ return;
+ }
+ sleep 1;
+ redo;
+ }
+
+ $MRTG and ndie msg('rep-timeout', $time);
+ add_unknown msg('rep-unknown');
+ return;
} ## end of check_replicate_row
sub check_same_schema {
- ## Verify that all relations inside two databases are the same
- ## Supports: Nagios
- ## Include and exclude should be supported
- ## Warning and critical are not used as normal
- ## Warning is used to do filtering
-
- ## Check for filtering rules
- my %filter;
- if (exists $opt{warning} and length $opt{warning}) {
- for my $phrase (split /[\s,]+/ => $opt{warning}) {
- for my $type (qw/schema user table view index sequence constraint trigger function perm language owner/) {
- if ($phrase =~ /^no${type}s?$/i) {
- $filter{"no${type}s"} = 1;
- }
- elsif ($phrase =~ /^no$type=(.+)/i) {
- push @{$filter{"no${type}_regex"}} => $1;
- }
- }
- if ($phrase =~ /^noposition$/io) { ## no critic (ProhibitFixedStringMatches)
- $filter{noposition} = 1;
- }
- if ($phrase =~ /^nofuncbody$/io) { ## no critic (ProhibitFixedStringMatches)
- $filter{nofuncbody} = 1;
- }
- }
- $VERBOSE >= 3 and warn Dumper \%filter;
- }
-
- my (%thing,$info);
-
- ## Do some synchronizations: assume db "1" is the default for "2" unless explicitly set
- for my $setting (qw/ host port dbname dbuser dbpass dbservice /) {
- my $two = "${setting}2";
- if (exists $opt{$setting} and ! exists $opt{$two}) {
- $opt{$two} = $opt{$setting};
- }
- }
-
- my $saved_db;
- for my $x (1..2) {
-
- ## Get a list of all users
- if (! exists $filter{nousers}) {
- $SQL = q{
+ ## Verify that all relations inside two databases are the same
+ ## Supports: Nagios
+ ## Include and exclude should be supported
+ ## Warning and critical are not used as normal
+ ## Warning is used to do filtering
+
+ ## Check for filtering rules
+ my %filter;
+ if (exists $opt{warning} and length $opt{warning}) {
+ for my $phrase (split /[\s,]+/ => $opt{warning}) {
+ for my $type (qw/schema user table view index sequence constraint trigger function perm language owner/) {
+ if ($phrase =~ /^no${type}s?$/i) {
+ $filter{"no${type}s"} = 1;
+ }
+ elsif ($phrase =~ /^no$type=(.+)/i) {
+ push @{$filter{"no${type}_regex"}} => $1;
+ }
+ }
+ if ($phrase =~ /^noposition$/io) { ## no critic (ProhibitFixedStringMatches)
+ $filter{noposition} = 1;
+ }
+ if ($phrase =~ /^nofuncbody$/io) { ## no critic (ProhibitFixedStringMatches)
+ $filter{nofuncbody} = 1;
+ }
+ }
+ $VERBOSE >= 3 and warn Dumper \%filter;
+ }
+
+ my (%thing,$info);
+
+ ## Do some synchronizations: assume db "1" is the default for "2" unless explicitly set
+ for my $setting (qw/ host port dbname dbuser dbpass dbservice /) {
+ my $two = "${setting}2";
+ if (exists $opt{$setting} and ! exists $opt{$two}) {
+ $opt{$two} = $opt{$setting};
+ }
+ }
+
+ my $saved_db;
+ for my $x (1..2) {
+
+ ## Get a list of all users
+ if (! exists $filter{nousers}) {
+ $SQL = q{
SELECT usesysid, quote_ident(usename) AS usename, usecreatedb, usesuper
FROM pg_user
};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- $thing{$x}{users}{$r->{usename}} = {
- oid=>$r->{usesysid},
- createdb=>$r->{usecreatedb},
- superuser=>$r->{usesuper}
- };
- $thing{$x}{useroid}{$r->{usesysid}} = $r->{usename};
- }
- }
- }
-
- ## Get a list of all schemas (aka namespaces)
- if (! exists $filter{noschemas}) {
- $SQL = q{
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ $thing{$x}{users}{$r->{usename}} = {
+ oid=>$r->{usesysid},
+ createdb=>$r->{usecreatedb},
+ superuser=>$r->{usesuper}
+ };
+ $thing{$x}{useroid}{$r->{usesysid}} = $r->{usename};
+ }
+ }
+ }
+
+ ## Get a list of all schemas (aka namespaces)
+ if (! exists $filter{noschemas}) {
+ $SQL = q{
SELECT quote_ident(nspname) AS nspname, n.oid, quote_ident(usename) AS usename, nspacl
FROM pg_namespace n
JOIN pg_user u ON (u.usesysid = n.nspowner)
WHERE nspname !~ '^pg_t'
};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- $thing{$x}{schemas}{$r->{nspname}} = {
- oid => $r->{oid},
- owner => $r->{usename},
- acl => (exists $filter{noperms} or !$r->{nspacl}) ? '(none)' : $r->{nspacl},
- };
- }
- }
- }
-
- ## Get a list of all relations
- if (! exists $filter{notables}) {
- $SQL = q{
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ $thing{$x}{schemas}{$r->{nspname}} = {
+ oid => $r->{oid},
+ owner => $r->{usename},
+ acl => (exists $filter{noperms} or !$r->{nspacl}) ? '(none)' : $r->{nspacl},
+ };
+ }
+ }
+ }
+
+ ## Get a list of all relations
+ if (! exists $filter{notables}) {
+ $SQL = q{
SELECT relkind, quote_ident(nspname) AS nspname, quote_ident(relname) AS relname,
quote_ident(usename) AS usename, relacl,
CASE WHEN relkind = 'v' THEN pg_get_viewdef(c.oid) ELSE '' END AS viewdef
JOIN pg_user u ON (u.usesysid = c.relowner)
WHERE nspname !~ '^pg_t'
};
- exists $filter{notriggers} and $SQL .= q{ AND relkind <> 'r'};
- exists $filter{noviews} and $SQL .= q{ AND relkind <> 'v'};
- exists $filter{noindexes} and $SQL .= q{ AND relkind <> 'i'};
- exists $filter{nosequences} and $SQL .= q{ AND relkind <> 'S'};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- my ($kind,$schema,$name,$owner,$acl,$def) = @$r{
- qw/ relkind nspname relname usename relacl viewdef /};
- $acl = '(none)' if exists $filter{noperms};
- if ($kind eq 'r') {
- $thing{$x}{tables}{"$schema.$name"} =
- {
- schema=>$schema, table=>$name, owner=>$owner, acl=>$acl||'(none)' };
- }
- elsif ($kind eq 'v') {
- $thing{$x}{views}{"$schema.$name"} =
- {
- schema=>$schema, table=>$name, owner=>$owner, acl=>$acl||'(none)', def=>$def };
- }
- elsif ($kind eq 'i') {
- $thing{$x}{indexes}{"$schema.$name"} =
- {
- schema=>$schema, table=>$name, owner=>$owner, acl=>$acl||'(none)' };
- }
- elsif ($kind eq 'S') {
- $thing{$x}{sequences}{"$schema.$name"} =
- {
- schema=>$schema, table=>$name, owner=>$owner, acl=>$acl||'(none)' };
- }
- }
- }
- }
-
- ## Get a list of all types
- $SQL = q{SELECT typname, oid FROM pg_type};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- $thing{$x}{type}{$r->{oid}} = $r->{typname};
- }
- $saved_db = $db if ! defined $saved_db;
- }
-
- ## Get a list of all triggers
- if (! exists $filter{notriggers}) {
- $SQL = q{
+ exists $filter{notriggers} and $SQL .= q{ AND relkind <> 'r'};
+ exists $filter{noviews} and $SQL .= q{ AND relkind <> 'v'};
+ exists $filter{noindexes} and $SQL .= q{ AND relkind <> 'i'};
+ exists $filter{nosequences} and $SQL .= q{ AND relkind <> 'S'};
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ my ($kind,$schema,$name,$owner,$acl,$def) = @$r{
+ qw/ relkind nspname relname usename relacl viewdef /};
+ $acl = '(none)' if exists $filter{noperms};
+ if ($kind eq 'r') {
+ $thing{$x}{tables}{"$schema.$name"} =
+ {
+ schema=>$schema, table=>$name, owner=>$owner, acl=>$acl||'(none)' };
+ }
+ elsif ($kind eq 'v') {
+ $thing{$x}{views}{"$schema.$name"} =
+ {
+ schema=>$schema, table=>$name, owner=>$owner, acl=>$acl||'(none)', def=>$def };
+ }
+ elsif ($kind eq 'i') {
+ $thing{$x}{indexes}{"$schema.$name"} =
+ {
+ schema=>$schema, table=>$name, owner=>$owner, acl=>$acl||'(none)' };
+ }
+ elsif ($kind eq 'S') {
+ $thing{$x}{sequences}{"$schema.$name"} =
+ {
+ schema=>$schema, table=>$name, owner=>$owner, acl=>$acl||'(none)' };
+ }
+ }
+ }
+ }
+
+ ## Get a list of all types
+ $SQL = q{SELECT typname, oid FROM pg_type};
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ $thing{$x}{type}{$r->{oid}} = $r->{typname};
+ }
+ $saved_db = $db if ! defined $saved_db;
+ }
+
+ ## Get a list of all triggers
+ if (! exists $filter{notriggers}) {
+ $SQL = q{
SELECT tgname, quote_ident(relname) AS relname, proname, proargtypes
FROM pg_trigger
JOIN pg_class c ON (c.oid = tgrelid)
JOIN pg_proc p ON (p.oid = tgfoid)
WHERE NOT tgisconstraint
}; ## constraints checked separately
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- my ($name,$table,$func,$args) = @$r{qw/ tgname relname proname proargtypes /};
- $args =~ s/(\d+)/$thing{$x}{type}{$1}/g;
- $args =~ s/^\s*(.*)\s*$/($1)/;
- $thing{$x}{triggers}{$name} = { table=>$table, func=>$func, args=>$args };
- }
- }
- }
-
- ## Get a list of all columns
- ## We'll use information_schema for this one
- $SQL = q{
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ my ($name,$table,$func,$args) = @$r{qw/ tgname relname proname proargtypes /};
+ $args =~ s/(\d+)/$thing{$x}{type}{$1}/g;
+ $args =~ s/^\s*(.*)\s*$/($1)/;
+ $thing{$x}{triggers}{$name} = { table=>$table, func=>$func, args=>$args };
+ }
+ }
+ }
+
+ ## Get a list of all columns
+ ## We'll use information_schema for this one
+ $SQL = q{
SELECT table_schema AS ts, table_name AS tn, column_name AS cn, ordinal_position AS op,
COALESCE(column_default, '(none)') AS df,
is_nullable AS in, data_type AS dt,
FROM information_schema.columns
ORDER BY table_schema, table_name, ordinal_position, column_name
};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- my $oldrelation = '';
- my $col = 0;
- my $position;
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
-
- my ($schema,$table) = @$r{qw/ ts tn /};
-
- ## If this is a new relation, reset the column numbering
- if ($oldrelation ne "$schema.$table") {
- $oldrelation = "$schema.$table";
- $col = 1;
- }
-
- ## Rather than use ordinal_position directly, count the live columns
- $position = $col++;
-
- $thing{$x}{columns}{"$schema.$table"}{$r->{cn}} = {
- schema => $schema,
- table => $table,
- name => $r->{cn},
- position => exists $filter{noposition} ? 0 : $position,
- default => $r->{df},
- nullable => $r->{in},
- type => $r->{dt},
- length => $r->{ml},
- precision => $r->{np},
- scale => $r->{ns},
- };
- }
- }
-
- ## Get a list of all constraints
- ## We'll use information_schema for this one too
- if (! exists $filter{noconstraints}) {
- $SQL = q{
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ my $oldrelation = '';
+ my $col = 0;
+ my $position;
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+
+ my ($schema,$table) = @$r{qw/ ts tn /};
+
+ ## If this is a new relation, reset the column numbering
+ if ($oldrelation ne "$schema.$table") {
+ $oldrelation = "$schema.$table";
+ $col = 1;
+ }
+
+ ## Rather than use ordinal_position directly, count the live columns
+ $position = $col++;
+
+ $thing{$x}{columns}{"$schema.$table"}{$r->{cn}} = {
+ schema => $schema,
+ table => $table,
+ name => $r->{cn},
+ position => exists $filter{noposition} ? 0 : $position,
+ default => $r->{df},
+ nullable => $r->{in},
+ type => $r->{dt},
+ length => $r->{ml},
+ precision => $r->{np},
+ scale => $r->{ns},
+ };
+ }
+ }
+
+ ## Get a list of all constraints
+ ## We'll use information_schema for this one too
+ if (! exists $filter{noconstraints}) {
+ $SQL = q{
SELECT constraint_schema AS cs, constraint_name AS cn, table_schema AS ts, table_name AS tn
FROM information_schema.constraint_table_usage
};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- my ($ichi,$ni,$san,$shi) = @$r{qw/ cs cn ts tn/};
-
- ## No sense in grabbing "generic" constraints
- next if $ni =~ /^\$\d+$/o;
-
- $thing{$x}{constraints}{"$ichi.$ni"} = "$san.$shi";
- }
- }
- $SQL = <<'SQL'; # cribbed from information_schema.constraint_column_usage
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ my ($ichi,$ni,$san,$shi) = @$r{qw/ cs cn ts tn/};
+
+ ## No sense in grabbing "generic" constraints
+ next if $ni =~ /^\$\d+$/o;
+
+ $thing{$x}{constraints}{"$ichi.$ni"} = "$san.$shi";
+ }
+ }
+ $SQL = <<'SQL'; # cribbed from information_schema.constraint_column_usage
SELECT current_database()::information_schema.sql_identifier AS cd,
x.tblschema::information_schema.sql_identifier AS tschema,
x.tblname::information_schema.sql_identifier AS tname,
x(tblschema, tblname, tblowner, colname, cstrschema, cstrname, constrdef)
WHERE pg_has_role(x.tblowner, 'USAGE'::text)
SQL
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- my ($cschema,$cname,$tschema,$tname,$ccol,$cdef) = @$r{
- qw/cschema cname tschema tname ccol cdef/};
- ## No sense in grabbing "generic" constraints
- if ($cname !~ /^\$\d+$/o) {
- if (exists $thing{$x}{colconstraints}{"$cschema.$cname"}) {
- my @oldcols = split / / => $thing{$x}{colconstraints}{"$cschema.$cname"}->[1];
- push @oldcols => $ccol;
- $ccol = join ' ' => sort @oldcols;
- }
- $thing{$x}{colconstraints}{"$cschema.$cname"} = ["$tschema.$tname", $ccol, $cdef];
- }
- }
- }
- }
-
- ## Get a list of all functions
- if (! exists $filter{nofunctions}) {
- $SQL = q{
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ my ($cschema,$cname,$tschema,$tname,$ccol,$cdef) = @$r{
+ qw/cschema cname tschema tname ccol cdef/};
+ ## No sense in grabbing "generic" constraints
+ if ($cname !~ /^\$\d+$/o) {
+ if (exists $thing{$x}{colconstraints}{"$cschema.$cname"}) {
+ my @oldcols = split / / => $thing{$x}{colconstraints}{"$cschema.$cname"}->[1];
+ push @oldcols => $ccol;
+ $ccol = join ' ' => sort @oldcols;
+ }
+ $thing{$x}{colconstraints}{"$cschema.$cname"} = ["$tschema.$tname", $ccol, $cdef];
+ }
+ }
+ }
+ }
+
+ ## Get a list of all functions
+ if (! exists $filter{nofunctions}) {
+ $SQL = q{
SELECT quote_ident(nspname) AS nspname, quote_ident(proname) AS proname, proargtypes, md5(prosrc) AS md,
proisstrict, proretset, provolatile
FROM pg_proc
JOIN pg_namespace n ON (n.oid = pronamespace)
};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- my ($schema,$name,$args,$md5,$isstrict,$retset,$volatile) = @$r{
- qw/ nspname proname proargtypes md proisstrict proretset provolatile /};
- $args =~ s/ /,/g;
- $args =~ s/(\d+)/$thing{$x}{type}{$1}/g;
- $args =~ s/^\s*(.*)\s*$/($1)/;
- $thing{$x}{functions}{"${schema}.${name}${args}"} = {
- md5 => $md5,
- isstrict => $isstrict,
- retset => $retset,
- volatile => $volatile,
- };
- }
- }
- }
-
- ## Get a list of all languages
- if (! exists $filter{nolanguages}) {
- $SQL = q{SELECT lanname FROM pg_language};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- $thing{$x}{language}{$r->{lanname}} = 1;
- }
- }
- }
-
-
- } ## end each database to query
-
- $db = $saved_db;
-
- ## Build a list of what has failed
- my %fail;
- my $failcount = 0;
-
- ## Compare users
-
- ## Any users on 1 but not 2?
- USER1:
- for my $user (sort keys %{$thing{1}{users}}) {
- next if exists $thing{2}{users}{$user};
-
- if (exists $filter{nouser_regex}) {
- for my $regex (@{$filter{nouser_regex}}) {
- next USER1 if $user =~ /$regex/;
- }
- }
-
- push @{$fail{users}{notexist}{1}} => $user;
- $failcount++;
- }
-
- ## Any users on 2 but not 1?
- USER2:
- for my $user (sort keys %{$thing{2}{users}}) {
-
- if (exists $filter{nouser_regex}) {
- for my $regex (@{$filter{nouser_regex}}) {
- next USER2 if $user =~ /$regex/;
- }
- }
-
- if (! exists $thing{1}{users}{$user}) {
- push @{$fail{users}{notexist}{2}} => $user;
- $failcount++;
- next;
- }
- ## Do the matching users have the same superpowers?
-
- if ($thing{1}{users}{$user}{createdb} ne $thing{2}{users}{$user}{createdb}) {
- push @{$fail{users}{createdb}{1}{$thing{1}{users}{$user}{createdb}}} => $user;
- $failcount++;
- }
-
- if ($thing{1}{users}{$user}{superuser} ne $thing{2}{users}{$user}{superuser}) {
- push @{$fail{users}{superuser}{1}{$thing{1}{users}{$user}{superuser}}} => $user;
- $failcount++;
- }
- }
-
- ## Compare schemas
-
- ## Any schemas on 1 but not 2?
- SCHEMA1:
- for my $name (sort keys %{$thing{1}{schemas}}) {
- next if exists $thing{2}{schemas}{$name};
-
- if (exists $filter{noschema_regex}) {
- for my $regex (@{$filter{noschema_regex}}) {
- next SCHEMA1 if $name =~ /$regex/;
- }
- }
-
- push @{$fail{schemas}{notexist}{1}} => $name;
- $failcount++;
- }
-
- ## Any schemas on 2 but not 1?
- SCHEMA2:
- for my $name (sort keys %{$thing{2}{schemas}}) {
-
- if (exists $filter{noschema_regex}) {
- for my $regex (@{$filter{noschema_regex}}) {
- next SCHEMA2 if $name =~ /$regex/;
- }
- }
-
- if (! exists $thing{1}{schemas}{$name}) {
- push @{$fail{schemas}{notexist}{2}} => $name;
- $failcount++;
- next;
- }
-
- ## Do the schemas have same owner and permissions?
- if (! exists $filter{noowners}) {
- if ($thing{1}{schemas}{$name}{owner} ne $thing{2}{schemas}{$name}{owner}) {
- push @{$fail{schemas}{diffowners}} =>
- [
- $name,
- $thing{1}{schemas}{$name}{owner},
- $thing{2}{schemas}{$name}{owner},
- ];
- $failcount++;
- }
- }
-
- if ($thing{1}{schemas}{$name}{acl} ne $thing{2}{schemas}{$name}{acl}) {
- push @{$fail{schemas}{diffacls}} =>
- [
- $name,
- $thing{1}{schemas}{$name}{acl},
- $thing{2}{schemas}{$name}{acl},
- ];
- $failcount++;
- }
-
- }
-
- ## Compare tables
-
- ## Any tables on 1 but not 2?
- ## We treat the name as a unified "schema.relname"
- TABLE1:
- for my $name (sort keys %{$thing{1}{tables}}) {
- next if exists $thing{2}{tables}{$name};
-
- ## If the schema does not exist, don't bother reporting it
- next if ! exists $thing{2}{schemas}{ $thing{1}{tables}{$name}{schema} };
-
- if (exists $filter{notable_regex}) {
- for my $regex (@{$filter{notable_regex}}) {
- next TABLE1 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next TABLE1 if $name =~ /$exclude/;
- }
-
- push @{$fail{tables}{notexist}{1}} => $name;
- $failcount++;
- }
-
- ## Any tables on 2 but not 1?
- TABLE2:
- for my $name (sort keys %{$thing{2}{tables}}) {
-
- if (exists $filter{notable_regex}) {
- for my $regex (@{$filter{notable_regex}}) {
- next TABLE2 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next TABLE2 if $name =~ /$exclude/;
- }
-
- if (! exists $thing{1}{tables}{$name}) {
- ## If the schema does not exist, don't bother reporting it
- if (exists $thing{1}{schemas}{ $thing{2}{tables}{$name}{schema} }) {
- push @{$fail{tables}{notexist}{2}} => $name;
- $failcount++;
- }
- next;
- }
-
- ## Do the tables have same owner and permissions?
- if (! exists $filter{noowners}) {
- if ($thing{1}{tables}{$name}{owner} ne $thing{2}{tables}{$name}{owner}) {
- push @{$fail{tables}{diffowners}} =>
- [
- $name,
- $thing{1}{tables}{$name}{owner},
- $thing{2}{tables}{$name}{owner},
- ];
- $failcount++;
- }
- }
-
- if ($thing{1}{tables}{$name}{acl} ne $thing{2}{tables}{$name}{acl}) {
- push @{$fail{tables}{diffacls}} =>
- [
- $name,
- $thing{1}{tables}{$name}{acl},
- $thing{2}{tables}{$name}{acl}
- ];
- $failcount++;
- }
-
- }
-
- ## Compare sequences
-
- ## Any sequences on 1 but not 2?
- ## We treat the name as a unified "schema.relname"
- SEQUENCE1:
- for my $name (sort keys %{$thing{1}{sequences}}) {
- next if exists $thing{2}{sequences}{$name};
-
- ## If the schema does not exist, don't bother reporting it
- next if ! exists $thing{2}{schemas}{ $thing{1}{sequences}{$name}{schema} };
-
- if (exists $filter{nosequence_regex}) {
- for my $regex (@{$filter{nosequence_regex}}) {
- next SEQUENCE1 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next SEQUENCE2 if $name =~ /$exclude/;
- }
-
- push @{$fail{sequences}{notexist}{1}} => $name;
- $failcount++;
- }
-
- ## Any sequences on 2 but not 1?
- SEQUENCE2:
- for my $name (sort keys %{$thing{2}{sequences}}) {
-
- if (exists $filter{nosequence_regex}) {
- for my $regex (@{$filter{nosequence_regex}}) {
- next SEQUENCE2 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next SEQUENCE2 if $name =~ /$exclude/;
- }
-
- if (! exists $thing{1}{sequences}{$name}) {
- ## If the schema does not exist, don't bother reporting it
- if (exists $thing{1}{schemas}{ $thing{2}{sequences}{$name}{schema} }) {
- push @{$fail{sequences}{notexist}{2}} => $name;
- $failcount++;
- }
- next;
- }
-
- ## Do the sequences have same owner and permissions?
- if (! exists $filter{noowners}) {
- if ($thing{1}{sequences}{$name}{owner} ne $thing{2}{sequences}{$name}{owner}) {
- push @{$fail{sequences}{diffowners}} =>
- [
- $name,
- $thing{1}{sequences}{$name}{owner},
- $thing{2}{sequences}{$name}{owner},
- ];
- $failcount++;
- }
- }
-
- if ($thing{1}{sequences}{$name}{acl} ne $thing{2}{sequences}{$name}{acl}) {
- push @{$fail{sequences}{diffacls}} =>
- [
- $name,
- $thing{1}{sequences}{$name}{acl},
- $thing{2}{sequences}{$name}{acl}
- ];
- $failcount++;
- }
- }
-
- ## Compare views
-
- ## Any views on 1 but not 2?
- ## We treat the name as a unified "schema.relname"
- VIEW1:
- for my $name (sort keys %{$thing{1}{views}}) {
- next if exists $thing{2}{views}{$name};
-
- ## If the schema does not exist, don't bother reporting it
- next if ! exists $thing{2}{schemas}{ $thing{1}{views}{$name}{schema} };
-
- if (exists $filter{noview_regex}) {
- for my $regex (@{$filter{noview_regex}}) {
- next VIEW1 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next VIEW1 if $name =~ /$exclude/;
- }
-
- push @{$fail{views}{notexist}{1}} => $name;
- $failcount++;
- }
-
- ## Any views on 2 but not 1?
- VIEW2:
- for my $name (sort keys %{$thing{2}{views}}) {
-
- if (exists $filter{noview_regex}) {
- for my $regex (@{$filter{noview_regex}}) {
- next VIEW2 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next VIEW2 if $name =~ /$exclude/;
- }
-
- if (! exists $thing{1}{views}{$name}) {
- ## If the schema does not exist, don't bother reporting it
- if (exists $thing{1}{schemas}{ $thing{2}{views}{$name}{schema} }) {
- push @{$fail{views}{notexist}{2}} => $name;
- $failcount++;
- }
- next;
- }
-
- ## Do the views have same owner and permissions?
- if (! exists $filter{noowners}) {
- if ($thing{1}{views}{$name}{owner} ne $thing{2}{views}{$name}{owner}) {
- push @{$fail{views}{diffowners}} =>
- [
- $name,
- $thing{1}{views}{$name}{owner},
- $thing{2}{views}{$name}{owner},
- ];
- $failcount++;
- }
- }
-
- if ($thing{1}{views}{$name}{acl} ne $thing{2}{views}{$name}{acl}) {
- push @{$fail{views}{diffacls}} =>
- [
- $name,
- $thing{1}{views}{$name}{acl},
- $thing{2}{views}{$name}{acl}
- ];
- $failcount++;
- }
-
- ## Do the views have same definitions?
- if ($thing{1}{views}{$name}{def} ne $thing{2}{views}{$name}{def}) {
- push @{$fail{views}{diffdef}} => $name;
- $failcount++;
- }
-
-
- }
-
- ## Compare triggers
-
- ## Any triggers on 1 but not 2?
- TRIGGER1:
- for my $name (sort keys %{$thing{1}{triggers}}) {
- next if exists $thing{2}{triggers}{$name};
- if (exists $filter{notrigger_regex}) {
- for my $regex (@{$filter{notrigger_regex}}) {
- next TRIGGER1 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next TRIGGER1 if $name =~ /$exclude/;
- }
-
- my $tabname = $thing{1}{triggers}{$name}->{table};
- push @{$fail{triggers}{notexist}{1}} => [$name,$tabname];
- $failcount++;
- }
-
- ## Any triggers on 2 but not 1?
- TRIGGER2:
- for my $name (sort keys %{$thing{2}{triggers}}) {
- if (! exists $thing{1}{triggers}{$name}) {
- if (exists $filter{notrigger_regex}) {
- for my $regex (@{$filter{notrigger_regex}}) {
- next TRIGGER2 if $name =~ /$regex/;
- }
- }
- my $tabname = $thing{2}{triggers}{$name}->{table};
- push @{$fail{triggers}{notexist}{2}} => [$name,$tabname];
- $failcount++;
- next;
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next TRIGGER2 if $name =~ /$exclude/;
- }
-
- ## Do the triggers call the same function?
- if (
- $thing{1}{triggers}{$name}{func} ne $thing{2}{triggers}{$name}{func}
- or $thing{1}{triggers}{$name}{args} ne $thing{2}{triggers}{$name}{args}
- ) {
- push @{$fail{triggers}{difffunc}} =>
- [$name,
- $thing{1}{triggers}{$name}{func} . $thing{1}{triggers}{$name}{args},
- $thing{2}{triggers}{$name}{func} . $thing{2}{triggers}{$name}{args},
- ];
- $failcount++;
- }
- }
-
- ## Compare columns
-
- ## Any columns on 1 but not 2, or 2 but not 1?
- COLUMN1:
- for my $name (sort keys %{$thing{1}{columns}}) {
- ## Skip any mismatched tables - already handled above
- next if ! exists $thing{2}{columns}{$name};
-
- for my $exclude (@{$opt{exclude}}) {
- next COLUMN1 if $name =~ /$exclude/;
- }
-
- my ($t1,$t2) = ($thing{1}{columns}{$name},$thing{2}{columns}{$name});
- for my $col (sort keys %$t1) {
- if (! exists $t2->{$col}) {
- push @{$fail{columns}{notexist}{1}} => [$name,$col];
- $failcount++;
- }
- }
- for my $col (sort keys %$t2) {
- if (! exists $t1->{$col}) {
- push @{$fail{columns}{notexist}{2}} => [$name,$col];
- $failcount++;
- next;
- }
- ## They exist, so dig deeper for differences. Done in two passes.
- my $newtype = 0;
- for my $var (qw/position type default nullable/) {
- if ($t1->{$col}{$var} ne $t2->{$col}{$var}) {
- $fail{columns}{diff}{$name}{$col}{$var} = [$t1->{$col}{$var}, $t2->{$col}{$var}];
- $failcount++;
- $newtype = 1 if $var eq 'type';
- }
- }
- ## Now the rest, with the caveat that we don't care about the rest if the type has changed
- if (!$newtype) {
- for my $var (qw/length precision scale/) {
- if ($t1->{$col}{$var} ne $t2->{$col}{$var}) {
- $fail{columns}{diff}{$name}{$col}{$var} = [$t1->{$col}{$var}, $t2->{$col}{$var}];
- $failcount++;
- }
- }
- }
- }
- }
-
- ## Compare constraints
-
- ## Table constraints - any exists on 1 but not 2?
- CONSTRAINT1:
- for my $name (sort keys %{$thing{1}{constraints}}) {
- next if exists $thing{2}{constraints}{$name};
-
- ## If the table does not exist, we don't report it
- next if ! exists $thing{2}{tables}{ $thing{1}{constraints}{$name} };
-
- if (exists $filter{noconstraint_regex}) {
- for my $regex (@{$filter{noconstraint_regex}}) {
- next CONSTRAINT1 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next CONSTRAINT1 if $name =~ /$exclude/;
- }
-
- push @{$fail{constraints}{notexist}{1}} => [$name, $thing{1}{constraints}{$name}];
- $failcount++;
- }
-
- ## Check exists on 2 but not 1, and make sure the schema/table matches
- CONSTRAINT2:
- for my $name (sort keys %{$thing{2}{constraints}}) {
-
- if (exists $filter{noconstraint_regex}) {
- for my $regex (@{$filter{noconstraint_regex}}) {
- next CONSTRAINT2 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next CONSTRAINT2 if $name =~ /$exclude/;
- }
-
- if (! exists $thing{1}{constraints}{$name}) {
-
- ## If the table does not exist, we don't report it
- if (exists $thing{1}{tables}{ $thing{2}{constraints}{$name} }) {
- push @{$fail{constraints}{notexist}{2}} => [$name, $thing{2}{constraints}{$name}];
- $failcount++;
- }
-
- next;
- }
- if ($thing{1}{constraints}{$name} ne $thing{2}{constraints}{$name}) {
- push @{$fail{constraints}{tablediff}} =>
- [
- $name,
- $thing{1}{constraints}{$name},
- $thing{2}{constraints}{$name},
- ];
- $failcount++;
- }
- }
-
- ## Column constraints - any exists on 1 but not 2?
- CONSTRAINT3:
- for my $name (sort keys %{$thing{1}{colconstraints}}) {
- next if exists $thing{2}{colconstraints}{$name};
-
- ## If the table does not exist, we don't report it
- my ($tname,$cname) = @{$thing{1}{colconstraints}{$name}};
- next if ! exists $thing{2}{tables}{$tname};
-
- if (exists $filter{noconstraint_regex}) {
- for my $regex (@{$filter{noconstraint_regex}}) {
- next CONSTRAINT3 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next CONSTRAINT3 if $name =~ /$exclude/;
- }
-
- push @{$fail{colconstraints}{notexist}{1}} => [$name, $tname, $cname];
- $failcount++;
- }
-
- ## Check exists on 2 but not 1, and make sure the schema/table/column matches
- CONSTRAINT4:
- for my $name (sort keys %{$thing{2}{colconstraints}}) {
-
- if (exists $filter{noconstraint_regex}) {
- for my $regex (@{$filter{noconstraint_regex}}) {
- next CONSTRAINT4 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next CONSTRAINT4 if $name =~ /$exclude/;
- }
-
- if (! exists $thing{1}{colconstraints}{$name}) {
-
- ## If the table does not exist, we don't report it
- my ($tname,$cname) = @{$thing{2}{colconstraints}{$name}};
- if (exists $thing{1}{tables}{ $tname }) {
- push @{$fail{colconstraints}{notexist}{2}} => [$name, $tname, $cname];
- $failcount++;
- }
- next;
- }
-
- ## Check for a difference in schema/table
- my ($tname1,$cname1,$cdef1) = @{$thing{1}{colconstraints}{$name}};
- my ($tname2,$cname2,$cdef2) = @{$thing{2}{colconstraints}{$name}};
- if ($tname1 ne $tname2) {
- push @{$fail{colconstraints}{tablediff}} =>
- [
- $name,
- $tname1,
- $tname2,
- ];
- $failcount++;
- }
- ## Check for a difference in schema/table/column
- elsif ($cname1 ne $cname2) {
- push @{$fail{colconstraints}{columndiff}} =>
- [
- $name,
- $tname1, $cname1,
- $tname2, $cname2,
- ];
- $failcount++;
- }
- ## Check for a difference in schema/table/column/definition
- elsif ($cdef1 ne $cdef2) {
- push @{$fail{colconstraints}{defdiff}} =>
- [
- $name,
- $tname1, $cname1, $cdef1,
- $tname2, $cname2, $cdef2,
- ];
- $failcount++;
- }
- }
-
- ## Compare languages
- for my $name (sort keys %{$thing{1}{language}}) {
- if (!exists $thing{2}{language}{$name}) {
- push @{$fail{language}{notexist}{1}} => $name;
- $failcount++;
- next;
- }
- }
- for my $name (sort keys %{$thing{2}{language}}) {
- if (!exists $thing{1}{language}{$name}) {
- push @{$fail{language}{notexist}{2}} => $name;
- $failcount++;
- next;
- }
- }
-
- ## Compare functions
-
- ## Functions on 1 but not 2?
- FUNCTION1:
- for my $name (sort keys %{$thing{1}{functions}}) {
- next if exists $thing{2}{functions}{$name};
-
- if (exists $filter{nofunction_regex}) {
- for my $regex (@{$filter{nofunction_regex}}) {
- next FUNCTION1 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next FUNCTION1 if $name =~ /$exclude/;
- }
-
- ## Skip if these are a side effect of having a language
- for my $l (@{$fail{language}{notexist}{1}}) {
- $l =~ s/u$//;
- next FUNCTION1 if
- $name eq "pg_catalog.${l}_call_handler()"
- or $name eq "pg_catalog.${l}_validator(oid)";
- }
-
- push @{$fail{functions}{notexist}{1}} => $name;
- $failcount++;
- }
-
- ## Functions on 2 but not 1 and check for identity
- FUNCTION2:
- for my $name (sort keys %{$thing{2}{functions}}) {
-
- if (exists $filter{nofunction_regex}) {
- for my $regex (@{$filter{nofunction_regex}}) {
- next FUNCTION2 if $name =~ /$regex/;
- }
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next FUNCTION2 if $name =~ /$exclude/;
- }
-
- ## Skip if these are a side effect of having a language
- for my $l (@{$fail{language}{notexist}{2}}) {
- $l =~ s/u$//;
- next FUNCTION2 if
- $name =~ "pg_catalog.${l}_call_handler()"
- or $name eq "pg_catalog.${l}_validator(oid)";
- }
-
- if (! exists $thing{1}{functions}{$name}) {
- push @{$fail{functions}{notexist}{2}} => $name;
- $failcount++;
- next;
- }
-
- ## Are the insides exactly the same
- if (! $filter{nofuncbody}) {
- if ($thing{1}{functions}{$name}{md5} ne $thing{2}{functions}{$name}{md5}) {
- push @{$fail{functions}{diffbody}}, $name;
- $failcount++;
- }
- }
-
- if (! $filter{nofuncstrict}) {
- if ($thing{1}{functions}{$name}{isstrict} ne $thing{2}{functions}{$name}{isstrict}) {
- push @{$fail{functions}{diffstrict}}, $name;
- $failcount++;
- }
- }
-
- if (! $filter{nofuncret}) {
- if ($thing{1}{functions}{$name}{retset} ne $thing{2}{functions}{$name}{retset}) {
- push @{$fail{functions}{diffretset}}, $name;
- $failcount++;
- }
- }
- if (! $filter{nofuncvol}) {
- if ($thing{1}{functions}{$name}{volatile} ne $thing{2}{functions}{$name}{volatile}) {
- push @{$fail{functions}{diffvol}}, $name;
- $failcount++;
- }
- }
- }
-
-
- ##
- ## Comparison is done, let's report the results
- ##
-
- if (! $failcount) {
- add_ok msg('same-matched');
- return;
- }
-
- ## Build a pretty message giving all the gory details
-
- $db->{perf} = '';
-
- ## User differences
- if (exists $fail{users}) {
- if (exists $fail{users}{notexist}) {
- if (exists $fail{users}{notexist}{1}) {
- $db->{perf} .= ' Users in 1 but not 2: ';
- $db->{perf} .= join ', ' => @{$fail{users}{notexist}{1}};
- $db->{perf} .= ' ';
- }
- if (exists $fail{users}{notexist}{2}) {
- $db->{perf} .= ' Users in 2 but not 1: ';
- $db->{perf} .= join ', ' => @{$fail{users}{notexist}{2}};
- $db->{perf} .= ' ';
- }
- }
- if (exists $fail{users}{createdb}) {
- if (exists $fail{users}{createdb}{1}) {
- if (exists $fail{users}{createdb}{1}{t}) {
- $db->{perf} .= ' Users with createdb on 1 but not 2: ';
- $db->{perf} .= join ', ' => @{$fail{users}{createdb}{1}{t}};
- $db->{perf} .= ' ';
- }
- if (exists $fail{users}{createdb}{1}{f}) {
- $db->{perf} .= ' Users with createdb on 2 but not 1: ';
- $db->{perf} .= join ', ' => @{$fail{users}{createdb}{1}{f}};
- $db->{perf} .= ' ';
- }
- }
- }
- if (exists $fail{users}{superuser}) {
- if (exists $fail{users}{superuser}{1}) {
- if (exists $fail{users}{superuser}{1}{t}) {
- $db->{perf} .= ' Users with superuser on 1 but not 2: ';
- $db->{perf} .= join ', ' => @{$fail{users}{superuser}{1}{t}};
- $db->{perf} .= ' ';
- }
- if (exists $fail{users}{superuser}{1}{f}) {
- $db->{perf} .= ' Users with superuser on 2 but not 1: ';
- $db->{perf} .= join ', ' => @{$fail{users}{superuser}{1}{f}};
- $db->{perf} .= ' ';
- }
- }
- }
- }
-
- ## Schema differences
- if (exists $fail{schemas}) {
- if (exists $fail{schemas}{notexist}) {
- if (exists $fail{schemas}{notexist}{1}) {
- for my $name (@{$fail{schemas}{notexist}{1}}) {
- $db->{perf} .= " Schema in 1 but not 2: $name ";
- }
- }
- if (exists $fail{schemas}{notexist}{2}) {
- for my $name (@{$fail{schemas}{notexist}{2}}) {
- $db->{perf} .= " Schema in 2 but not 1: $name ";
- }
- }
- }
- if (exists $fail{schemas}{diffowners}) {
- for my $item (@{$fail{schemas}{diffowners}}) {
- my ($name,$owner1,$owner2) = @$item;
- $db->{perf} .= qq{ Schema "$name" owned by "$owner1" on 1, but by "$owner2" on 2. };
- }
- }
- if (exists $fail{schemas}{diffacls}) {
- for my $item (@{$fail{schemas}{diffacls}}) {
- my ($name,$acl1,$acl2) = @$item;
- $db->{perf} .= qq{ Schema "$name" has $acl1 perms on 1, but $acl2 perms on 2. };
- }
- }
- }
-
- ## Table differences
- if (exists $fail{tables}) {
- if (exists $fail{tables}{notexist}) {
- if (exists $fail{tables}{notexist}{1}) {
- for my $name (@{$fail{tables}{notexist}{1}}) {
- $db->{perf} .= " Table in 1 but not 2: $name ";
- }
- }
- if (exists $fail{tables}{notexist}{2}) {
- for my $name (@{$fail{tables}{notexist}{2}}) {
- $db->{perf} .= " Table in 2 but not 1: $name ";
- }
- }
- }
- if (exists $fail{tables}{diffowners}) {
- for my $item (@{$fail{tables}{diffowners}}) {
- my ($name,$owner1,$owner2) = @$item;
- $db->{perf} .= qq{ Table "$name" owned by "$owner1" on 1, but by "$owner2" on 2. };
- }
- }
- if (exists $fail{tables}{diffacls}) {
- for my $item (@{$fail{tables}{diffacls}}) {
- my ($name,$acl1,$acl2) = @$item;
- $db->{perf} .= qq{ Table "$name" has $acl1 perms on 1, but $acl2 perms on 2. };
- }
- }
- }
-
- ## Sequence differences
- if (exists $fail{sequences}) {
- if (exists $fail{sequences}{notexist}) {
- if (exists $fail{sequences}{notexist}{1}) {
- for my $name (@{$fail{sequences}{notexist}{1}}) {
- $db->{perf} .= " Sequence in 1 but not 2: $name ";
- }
- }
- if (exists $fail{sequences}{notexist}{2}) {
- for my $name (@{$fail{sequences}{notexist}{2}}) {
- $db->{perf} .= " Sequence in 2 but not 1: $name ";
- }
- }
- }
- if (exists $fail{sequences}{diffowners}) {
- for my $item (@{$fail{sequences}{diffowners}}) {
- my ($name,$owner1,$owner2) = @$item;
- $db->{perf} .= qq{ Sequence "$name" owned by "$owner1" on 1, but by "$owner2" on 2. };
- }
- }
- if (exists $fail{sequences}{diffacls}) {
- for my $item (@{$fail{sequences}{diffacls}}) {
- my ($name,$acl1,$acl2) = @$item;
- $db->{perf} .= qq{ Sequence "$name" has $acl1 perms on 1, but $acl2 perms on 2. };
- }
- }
- }
-
- ## View differences
- if (exists $fail{views}) {
- if (exists $fail{views}{notexist}) {
- if (exists $fail{views}{notexist}{1}) {
- for my $name (@{$fail{views}{notexist}{1}}) {
- $db->{perf} .= " View in 1 but not 2: $name ";
- }
- }
- if (exists $fail{views}{notexist}{2}) {
- for my $name (@{$fail{views}{notexist}{2}}) {
- $db->{perf} .= " View in 2 but not 1: $name ";
- }
- }
- }
- if (exists $fail{views}{diffowners}) {
- for my $item (@{$fail{views}{diffowners}}) {
- my ($name,$owner1,$owner2) = @$item;
- $db->{perf} .= qq{ View "$name" owned by "$owner1" on 1, but by "$owner2" on 2. };
- }
- }
- if (exists $fail{views}{diffacls}) {
- for my $item (@{$fail{views}{diffacls}}) {
- my ($name,$acl1,$acl2) = @$item;
- $db->{perf} .= qq{ View "$name" has $acl1 perms on 1, but $acl2 perms on 2. };
- }
- }
- if (exists $fail{views}{diffdef}) {
- for my $item (@{$fail{views}{diffdef}}) {
- $db->{perf} .= qq{ View "$item" is different on 1 and 2. };
- }
- }
- }
-
- ## Trigger differences
- if (exists $fail{triggers}) {
- if (exists $fail{triggers}{notexist}) {
- if (exists $fail{triggers}{notexist}{1}) {
- for my $row (@{$fail{triggers}{notexist}{1}}) {
- my ($name,$tabname) = @$row;
- $db->{perf} .= " Trigger in 1 but not 2: $name (on $tabname) ";
- }
- }
- if (exists $fail{triggers}{notexist}{2}) {
- for my $row (@{$fail{triggers}{notexist}{2}}) {
- my ($name,$tabname) = @$row;
- $db->{perf} .= " Trigger in 2 but not 1: $name (on $tabname) ";
- }
- }
- }
- if (exists $fail{triggers}{difffunc}) {
- for my $item (@{$fail{triggers}{diffowners}}) {
- my ($name,$func1,$func2) = @$item;
- $db->{perf} .= qq{ Trigger "$name" calls function "$func1" on 1, but function "$func2" on 2. };
- }
- }
- }
-
- ## Column differences
- if (exists $fail{columns}) {
- if (exists $fail{columns}{notexist}) {
- if (exists $fail{columns}{notexist}{1}) {
- for my $row (@{$fail{columns}{notexist}{1}}) {
- my ($tname,$cname) = @$row;
- $db->{perf} .= qq{ Table "$tname" on 1 has column "$cname", but 2 does not. };
- }
- }
- if (exists $fail{columns}{notexist}{2}) {
- for my $row (@{$fail{columns}{notexist}{2}}) {
- my ($tname,$cname) = @$row;
- $db->{perf} .= qq{ Table "$tname" on 2 has column "$cname", but 1 does not. };
- }
- }
- }
- if (exists $fail{columns}{diff}) {
- for my $tname (sort keys %{$fail{columns}{diff}}) {
- for my $cname (sort keys %{$fail{columns}{diff}{$tname}}) {
- for my $var (sort keys %{$fail{columns}{diff}{$tname}{$cname}}) {
- my ($v1,$v2) = @{$fail{columns}{diff}{$tname}{$cname}{$var}};
- $db->{perf} .= qq{ Column "$cname" of "$tname": $var is $v1 on 1, but $v2 on 2. };
- }
- }
- }
- }
- }
-
- ## Constraint differences - table level
- ## Don't report things twice
- my %doublec;
- if (exists $fail{constraints}) {
- if (exists $fail{constraints}{notexist}) {
- if (exists $fail{constraints}{notexist}{1}) {
- for my $row (@{$fail{constraints}{notexist}{1}}) {
- my ($cname,$tname) = @$row;
- $db->{perf} .= qq{ Table "$tname" on 1 has constraint "$cname", but 2 does not. };
- $doublec{$cname}++;
- }
- }
- if (exists $fail{constraints}{notexist}{2}) {
- for my $row (@{$fail{constraints}{notexist}{2}}) {
- my ($cname,$tname) = @$row;
- $db->{perf} .= qq{ Table "$tname" on 2 has constraint "$cname", but 1 does not. };
- $doublec{$cname}++;
- }
- }
- }
- if (exists $fail{constraints}{tablediff}) {
- for my $row (@{$fail{constraints}{tablediff}}) {
- my ($cname,$t1,$t2) = @$row;
- $db->{perf} .= qq{ Constraint "$cname" is applied to "$t1" on 1, but to "$t2" on 2. };
- $doublec{$cname}++;
- }
- }
- }
-
- ## Constraint differences - column level
- if (exists $fail{colconstraints}) {
- if (exists $fail{colconstraints}{notexist}) {
- if (exists $fail{colconstraints}{notexist}{1}) {
- for my $row (@{$fail{colconstraints}{notexist}{1}}) {
- my ($name,$tname,$cname) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Table "$tname" on 1 has constraint "$name" on column "$cname", but 2 does not. };
- }
- else {
- $failcount--;
- }
- }
- }
- if (exists $fail{colconstraints}{notexist}{2}) {
- for my $row (@{$fail{colconstraints}{notexist}{2}}) {
- my ($name,$tname,$cname) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Table "$tname" on 2 has constraint "$name" on column "$cname", but 1 does not. };
- }
- else {
- $failcount--;
- }
- }
- }
- }
- if (exists $fail{colconstraints}{tablediff}) {
- for my $row (@{$fail{colconstraints}{tablediff}}) {
- my ($name,$t1,$t2) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Constraint "$name" is applied to "$t1" on 1, but to "$t2" on 2. };
- }
- else {
- $failcount--;
- }
- }
- }
- if (exists $fail{colconstraints}{columndiff}) {
- for my $row (@{$fail{colconstraints}{columndiff}}) {
- my ($name,$t1,$c1,$t2,$c2) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Constraint "$name" on 1 is applied to $t1.$c1, but to $t2.$c2 on 2. };
- }
- else {
- $failcount--;
- }
- }
- }
- if (exists $fail{colconstraints}{defdiff}) {
- for my $row (@{$fail{colconstraints}{defdiff}}) {
- my ($name,$t1,$c1,$d1,$t2,$c2,$d2) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Constraint "$name" on 1 differs from 2 ("$d1" vs. "$d2") };
- }
- else {
- $failcount--;
- }
- }
- }
- }
-
- ## Function differences
- if (exists $fail{functions}) {
- if (exists $fail{functions}{notexist}) {
- if (exists $fail{functions}{notexist}{1}) {
- for my $name (@{$fail{functions}{notexist}{1}}) {
- $db->{perf} .= " Function on 1 but not 2: $name ";
- }
- }
- if (exists $fail{functions}{notexist}{2}) {
- for my $name (@{$fail{functions}{notexist}{2}}) {
- $db->{perf} .= " Function on 2 but not 1: $name ";
- }
- }
- }
- if (exists $fail{functions}{diffbody}) {
- for my $name (sort @{$fail{functions}{diffbody}}) {
- $db->{perf} .= " Function body different on 1 than 2: $name ";
- }
- }
- if (exists $fail{functions}{diffstrict}) {
- for my $name (sort @{$fail{functions}{diffbody}}) {
- $db->{perf} .= " Function strictness different on 1 than 2: $name ";
- }
- }
- if (exists $fail{functions}{diffretset}) {
- for my $name (sort @{$fail{functions}{diffretset}}) {
- $db->{perf} .= " Function return-set different on 1 than 2: $name ";
- }
- }
- if (exists $fail{functions}{diffvol}) {
- for my $name (sort @{$fail{functions}{diffvol}}) {
- $db->{perf} .= " Function volatilitiy different on 1 than 2: $name ";
- }
- }
- }
-
- ## Language differences
- if (exists $fail{language}) {
- if (exists $fail{language}{notexist}) {
- if (exists $fail{language}{notexist}{1}) {
- for my $name (@{$fail{language}{notexist}{1}}) {
- $db->{perf} .= " Language on 1 but not 2: $name ";
- }
- }
- if (exists $fail{language}{notexist}{2}) {
- for my $name (@{$fail{language}{notexist}{2}}) {
- $db->{perf} .= " Language on 2 but not 1: $name ";
- }
- }
- }
- }
-
-
- add_critical msg('same-failed', $failcount);
-
- return;
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ my ($schema,$name,$args,$md5,$isstrict,$retset,$volatile) = @$r{
+ qw/ nspname proname proargtypes md proisstrict proretset provolatile /};
+ $args =~ s/ /,/g;
+ $args =~ s/(\d+)/$thing{$x}{type}{$1}/g;
+ $args =~ s/^\s*(.*)\s*$/($1)/;
+ $thing{$x}{functions}{"${schema}.${name}${args}"} = {
+ md5 => $md5,
+ isstrict => $isstrict,
+ retset => $retset,
+ volatile => $volatile,
+ };
+ }
+ }
+ }
+
+ ## Get a list of all languages
+ if (! exists $filter{nolanguages}) {
+ $SQL = q{SELECT lanname FROM pg_language};
+ $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
+ for $db (@{$info->{db}}) {
+ for my $r (@{$db->{slurp}}) {
+ $thing{$x}{language}{$r->{lanname}} = 1;
+ }
+ }
+ }
+
+
+ } ## end each database to query
+
+ $db = $saved_db;
+
+ ## Build a list of what has failed
+ my %fail;
+ my $failcount = 0;
+
+ ## Compare users
+
+ ## Any users on 1 but not 2?
+ USER1:
+ for my $user (sort keys %{$thing{1}{users}}) {
+ next if exists $thing{2}{users}{$user};
+
+ if (exists $filter{nouser_regex}) {
+ for my $regex (@{$filter{nouser_regex}}) {
+ next USER1 if $user =~ /$regex/;
+ }
+ }
+
+ push @{$fail{users}{notexist}{1}} => $user;
+ $failcount++;
+ }
+
+ ## Any users on 2 but not 1?
+ USER2:
+ for my $user (sort keys %{$thing{2}{users}}) {
+
+ if (exists $filter{nouser_regex}) {
+ for my $regex (@{$filter{nouser_regex}}) {
+ next USER2 if $user =~ /$regex/;
+ }
+ }
+
+ if (! exists $thing{1}{users}{$user}) {
+ push @{$fail{users}{notexist}{2}} => $user;
+ $failcount++;
+ next;
+ }
+ ## Do the matching users have the same superpowers?
+
+ if ($thing{1}{users}{$user}{createdb} ne $thing{2}{users}{$user}{createdb}) {
+ push @{$fail{users}{createdb}{1}{$thing{1}{users}{$user}{createdb}}} => $user;
+ $failcount++;
+ }
+
+ if ($thing{1}{users}{$user}{superuser} ne $thing{2}{users}{$user}{superuser}) {
+ push @{$fail{users}{superuser}{1}{$thing{1}{users}{$user}{superuser}}} => $user;
+ $failcount++;
+ }
+ }
+
+ ## Compare schemas
+
+ ## Any schemas on 1 but not 2?
+ SCHEMA1:
+ for my $name (sort keys %{$thing{1}{schemas}}) {
+ next if exists $thing{2}{schemas}{$name};
+
+ if (exists $filter{noschema_regex}) {
+ for my $regex (@{$filter{noschema_regex}}) {
+ next SCHEMA1 if $name =~ /$regex/;
+ }
+ }
+
+ push @{$fail{schemas}{notexist}{1}} => $name;
+ $failcount++;
+ }
+
+ ## Any schemas on 2 but not 1?
+ SCHEMA2:
+ for my $name (sort keys %{$thing{2}{schemas}}) {
+
+ if (exists $filter{noschema_regex}) {
+ for my $regex (@{$filter{noschema_regex}}) {
+ next SCHEMA2 if $name =~ /$regex/;
+ }
+ }
+
+ if (! exists $thing{1}{schemas}{$name}) {
+ push @{$fail{schemas}{notexist}{2}} => $name;
+ $failcount++;
+ next;
+ }
+
+ ## Do the schemas have same owner and permissions?
+ if (! exists $filter{noowners}) {
+ if ($thing{1}{schemas}{$name}{owner} ne $thing{2}{schemas}{$name}{owner}) {
+ push @{$fail{schemas}{diffowners}} =>
+ [
+ $name,
+ $thing{1}{schemas}{$name}{owner},
+ $thing{2}{schemas}{$name}{owner},
+ ];
+ $failcount++;
+ }
+ }
+
+ if ($thing{1}{schemas}{$name}{acl} ne $thing{2}{schemas}{$name}{acl}) {
+ push @{$fail{schemas}{diffacls}} =>
+ [
+ $name,
+ $thing{1}{schemas}{$name}{acl},
+ $thing{2}{schemas}{$name}{acl},
+ ];
+ $failcount++;
+ }
+
+ }
+
+ ## Compare tables
+
+ ## Any tables on 1 but not 2?
+ ## We treat the name as a unified "schema.relname"
+ TABLE1:
+ for my $name (sort keys %{$thing{1}{tables}}) {
+ next if exists $thing{2}{tables}{$name};
+
+ ## If the schema does not exist, don't bother reporting it
+ next if ! exists $thing{2}{schemas}{ $thing{1}{tables}{$name}{schema} };
+
+ if (exists $filter{notable_regex}) {
+ for my $regex (@{$filter{notable_regex}}) {
+ next TABLE1 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next TABLE1 if $name =~ /$exclude/;
+ }
+
+ push @{$fail{tables}{notexist}{1}} => $name;
+ $failcount++;
+ }
+
+ ## Any tables on 2 but not 1?
+ TABLE2:
+ for my $name (sort keys %{$thing{2}{tables}}) {
+
+ if (exists $filter{notable_regex}) {
+ for my $regex (@{$filter{notable_regex}}) {
+ next TABLE2 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next TABLE2 if $name =~ /$exclude/;
+ }
+
+ if (! exists $thing{1}{tables}{$name}) {
+ ## If the schema does not exist, don't bother reporting it
+ if (exists $thing{1}{schemas}{ $thing{2}{tables}{$name}{schema} }) {
+ push @{$fail{tables}{notexist}{2}} => $name;
+ $failcount++;
+ }
+ next;
+ }
+
+ ## Do the tables have same owner and permissions?
+ if (! exists $filter{noowners}) {
+ if ($thing{1}{tables}{$name}{owner} ne $thing{2}{tables}{$name}{owner}) {
+ push @{$fail{tables}{diffowners}} =>
+ [
+ $name,
+ $thing{1}{tables}{$name}{owner},
+ $thing{2}{tables}{$name}{owner},
+ ];
+ $failcount++;
+ }
+ }
+
+ if ($thing{1}{tables}{$name}{acl} ne $thing{2}{tables}{$name}{acl}) {
+ push @{$fail{tables}{diffacls}} =>
+ [
+ $name,
+ $thing{1}{tables}{$name}{acl},
+ $thing{2}{tables}{$name}{acl}
+ ];
+ $failcount++;
+ }
+
+ }
+
+ ## Compare sequences
+
+ ## Any sequences on 1 but not 2?
+ ## We treat the name as a unified "schema.relname"
+ SEQUENCE1:
+ for my $name (sort keys %{$thing{1}{sequences}}) {
+ next if exists $thing{2}{sequences}{$name};
+
+ ## If the schema does not exist, don't bother reporting it
+ next if ! exists $thing{2}{schemas}{ $thing{1}{sequences}{$name}{schema} };
+
+ if (exists $filter{nosequence_regex}) {
+ for my $regex (@{$filter{nosequence_regex}}) {
+ next SEQUENCE1 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next SEQUENCE2 if $name =~ /$exclude/;
+ }
+
+ push @{$fail{sequences}{notexist}{1}} => $name;
+ $failcount++;
+ }
+
+ ## Any sequences on 2 but not 1?
+ SEQUENCE2:
+ for my $name (sort keys %{$thing{2}{sequences}}) {
+
+ if (exists $filter{nosequence_regex}) {
+ for my $regex (@{$filter{nosequence_regex}}) {
+ next SEQUENCE2 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next SEQUENCE2 if $name =~ /$exclude/;
+ }
+
+ if (! exists $thing{1}{sequences}{$name}) {
+ ## If the schema does not exist, don't bother reporting it
+ if (exists $thing{1}{schemas}{ $thing{2}{sequences}{$name}{schema} }) {
+ push @{$fail{sequences}{notexist}{2}} => $name;
+ $failcount++;
+ }
+ next;
+ }
+
+ ## Do the sequences have same owner and permissions?
+ if (! exists $filter{noowners}) {
+ if ($thing{1}{sequences}{$name}{owner} ne $thing{2}{sequences}{$name}{owner}) {
+ push @{$fail{sequences}{diffowners}} =>
+ [
+ $name,
+ $thing{1}{sequences}{$name}{owner},
+ $thing{2}{sequences}{$name}{owner},
+ ];
+ $failcount++;
+ }
+ }
+
+ if ($thing{1}{sequences}{$name}{acl} ne $thing{2}{sequences}{$name}{acl}) {
+ push @{$fail{sequences}{diffacls}} =>
+ [
+ $name,
+ $thing{1}{sequences}{$name}{acl},
+ $thing{2}{sequences}{$name}{acl}
+ ];
+ $failcount++;
+ }
+ }
+
+ ## Compare views
+
+ ## Any views on 1 but not 2?
+ ## We treat the name as a unified "schema.relname"
+ VIEW1:
+ for my $name (sort keys %{$thing{1}{views}}) {
+ next if exists $thing{2}{views}{$name};
+
+ ## If the schema does not exist, don't bother reporting it
+ next if ! exists $thing{2}{schemas}{ $thing{1}{views}{$name}{schema} };
+
+ if (exists $filter{noview_regex}) {
+ for my $regex (@{$filter{noview_regex}}) {
+ next VIEW1 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next VIEW1 if $name =~ /$exclude/;
+ }
+
+ push @{$fail{views}{notexist}{1}} => $name;
+ $failcount++;
+ }
+
+ ## Any views on 2 but not 1?
+ VIEW2:
+ for my $name (sort keys %{$thing{2}{views}}) {
+
+ if (exists $filter{noview_regex}) {
+ for my $regex (@{$filter{noview_regex}}) {
+ next VIEW2 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next VIEW2 if $name =~ /$exclude/;
+ }
+
+ if (! exists $thing{1}{views}{$name}) {
+ ## If the schema does not exist, don't bother reporting it
+ if (exists $thing{1}{schemas}{ $thing{2}{views}{$name}{schema} }) {
+ push @{$fail{views}{notexist}{2}} => $name;
+ $failcount++;
+ }
+ next;
+ }
+
+ ## Do the views have same owner and permissions?
+ if (! exists $filter{noowners}) {
+ if ($thing{1}{views}{$name}{owner} ne $thing{2}{views}{$name}{owner}) {
+ push @{$fail{views}{diffowners}} =>
+ [
+ $name,
+ $thing{1}{views}{$name}{owner},
+ $thing{2}{views}{$name}{owner},
+ ];
+ $failcount++;
+ }
+ }
+
+ if ($thing{1}{views}{$name}{acl} ne $thing{2}{views}{$name}{acl}) {
+ push @{$fail{views}{diffacls}} =>
+ [
+ $name,
+ $thing{1}{views}{$name}{acl},
+ $thing{2}{views}{$name}{acl}
+ ];
+ $failcount++;
+ }
+
+ ## Do the views have same definitions?
+ if ($thing{1}{views}{$name}{def} ne $thing{2}{views}{$name}{def}) {
+ push @{$fail{views}{diffdef}} => $name;
+ $failcount++;
+ }
+
+
+ }
+
+ ## Compare triggers
+
+ ## Any triggers on 1 but not 2?
+ TRIGGER1:
+ for my $name (sort keys %{$thing{1}{triggers}}) {
+ next if exists $thing{2}{triggers}{$name};
+ if (exists $filter{notrigger_regex}) {
+ for my $regex (@{$filter{notrigger_regex}}) {
+ next TRIGGER1 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next TRIGGER1 if $name =~ /$exclude/;
+ }
+
+ my $tabname = $thing{1}{triggers}{$name}->{table};
+ push @{$fail{triggers}{notexist}{1}} => [$name,$tabname];
+ $failcount++;
+ }
+
+ ## Any triggers on 2 but not 1?
+ TRIGGER2:
+ for my $name (sort keys %{$thing{2}{triggers}}) {
+ if (! exists $thing{1}{triggers}{$name}) {
+ if (exists $filter{notrigger_regex}) {
+ for my $regex (@{$filter{notrigger_regex}}) {
+ next TRIGGER2 if $name =~ /$regex/;
+ }
+ }
+ my $tabname = $thing{2}{triggers}{$name}->{table};
+ push @{$fail{triggers}{notexist}{2}} => [$name,$tabname];
+ $failcount++;
+ next;
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next TRIGGER2 if $name =~ /$exclude/;
+ }
+
+ ## Do the triggers call the same function?
+ if (
+ $thing{1}{triggers}{$name}{func} ne $thing{2}{triggers}{$name}{func}
+ or $thing{1}{triggers}{$name}{args} ne $thing{2}{triggers}{$name}{args}
+ ) {
+ push @{$fail{triggers}{difffunc}} =>
+ [$name,
+ $thing{1}{triggers}{$name}{func} . $thing{1}{triggers}{$name}{args},
+ $thing{2}{triggers}{$name}{func} . $thing{2}{triggers}{$name}{args},
+ ];
+ $failcount++;
+ }
+ }
+
+ ## Compare columns
+
+ ## Any columns on 1 but not 2, or 2 but not 1?
+ COLUMN1:
+ for my $name (sort keys %{$thing{1}{columns}}) {
+ ## Skip any mismatched tables - already handled above
+ next if ! exists $thing{2}{columns}{$name};
+
+ for my $exclude (@{$opt{exclude}}) {
+ next COLUMN1 if $name =~ /$exclude/;
+ }
+
+ my ($t1,$t2) = ($thing{1}{columns}{$name},$thing{2}{columns}{$name});
+ for my $col (sort keys %$t1) {
+ if (! exists $t2->{$col}) {
+ push @{$fail{columns}{notexist}{1}} => [$name,$col];
+ $failcount++;
+ }
+ }
+ for my $col (sort keys %$t2) {
+ if (! exists $t1->{$col}) {
+ push @{$fail{columns}{notexist}{2}} => [$name,$col];
+ $failcount++;
+ next;
+ }
+ ## They exist, so dig deeper for differences. Done in two passes.
+ my $newtype = 0;
+ for my $var (qw/position type default nullable/) {
+ if ($t1->{$col}{$var} ne $t2->{$col}{$var}) {
+ $fail{columns}{diff}{$name}{$col}{$var} = [$t1->{$col}{$var}, $t2->{$col}{$var}];
+ $failcount++;
+ $newtype = 1 if $var eq 'type';
+ }
+ }
+ ## Now the rest, with the caveat that we don't care about the rest if the type has changed
+ if (!$newtype) {
+ for my $var (qw/length precision scale/) {
+ if ($t1->{$col}{$var} ne $t2->{$col}{$var}) {
+ $fail{columns}{diff}{$name}{$col}{$var} = [$t1->{$col}{$var}, $t2->{$col}{$var}];
+ $failcount++;
+ }
+ }
+ }
+ }
+ }
+
+ ## Compare constraints
+
+ ## Table constraints - any exists on 1 but not 2?
+ CONSTRAINT1:
+ for my $name (sort keys %{$thing{1}{constraints}}) {
+ next if exists $thing{2}{constraints}{$name};
+
+ ## If the table does not exist, we don't report it
+ next if ! exists $thing{2}{tables}{ $thing{1}{constraints}{$name} };
+
+ if (exists $filter{noconstraint_regex}) {
+ for my $regex (@{$filter{noconstraint_regex}}) {
+ next CONSTRAINT1 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next CONSTRAINT1 if $name =~ /$exclude/;
+ }
+
+ push @{$fail{constraints}{notexist}{1}} => [$name, $thing{1}{constraints}{$name}];
+ $failcount++;
+ }
+
+ ## Check exists on 2 but not 1, and make sure the schema/table matches
+ CONSTRAINT2:
+ for my $name (sort keys %{$thing{2}{constraints}}) {
+
+ if (exists $filter{noconstraint_regex}) {
+ for my $regex (@{$filter{noconstraint_regex}}) {
+ next CONSTRAINT2 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next CONSTRAINT2 if $name =~ /$exclude/;
+ }
+
+ if (! exists $thing{1}{constraints}{$name}) {
+
+ ## If the table does not exist, we don't report it
+ if (exists $thing{1}{tables}{ $thing{2}{constraints}{$name} }) {
+ push @{$fail{constraints}{notexist}{2}} => [$name, $thing{2}{constraints}{$name}];
+ $failcount++;
+ }
+
+ next;
+ }
+ if ($thing{1}{constraints}{$name} ne $thing{2}{constraints}{$name}) {
+ push @{$fail{constraints}{tablediff}} =>
+ [
+ $name,
+ $thing{1}{constraints}{$name},
+ $thing{2}{constraints}{$name},
+ ];
+ $failcount++;
+ }
+ }
+
+ ## Column constraints - any exists on 1 but not 2?
+ CONSTRAINT3:
+ for my $name (sort keys %{$thing{1}{colconstraints}}) {
+ next if exists $thing{2}{colconstraints}{$name};
+
+ ## If the table does not exist, we don't report it
+ my ($tname,$cname) = @{$thing{1}{colconstraints}{$name}};
+ next if ! exists $thing{2}{tables}{$tname};
+
+ if (exists $filter{noconstraint_regex}) {
+ for my $regex (@{$filter{noconstraint_regex}}) {
+ next CONSTRAINT3 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next CONSTRAINT3 if $name =~ /$exclude/;
+ }
+
+ push @{$fail{colconstraints}{notexist}{1}} => [$name, $tname, $cname];
+ $failcount++;
+ }
+
+ ## Check exists on 2 but not 1, and make sure the schema/table/column matches
+ CONSTRAINT4:
+ for my $name (sort keys %{$thing{2}{colconstraints}}) {
+
+ if (exists $filter{noconstraint_regex}) {
+ for my $regex (@{$filter{noconstraint_regex}}) {
+ next CONSTRAINT4 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next CONSTRAINT4 if $name =~ /$exclude/;
+ }
+
+ if (! exists $thing{1}{colconstraints}{$name}) {
+
+ ## If the table does not exist, we don't report it
+ my ($tname,$cname) = @{$thing{2}{colconstraints}{$name}};
+ if (exists $thing{1}{tables}{ $tname }) {
+ push @{$fail{colconstraints}{notexist}{2}} => [$name, $tname, $cname];
+ $failcount++;
+ }
+ next;
+ }
+
+ ## Check for a difference in schema/table
+ my ($tname1,$cname1,$cdef1) = @{$thing{1}{colconstraints}{$name}};
+ my ($tname2,$cname2,$cdef2) = @{$thing{2}{colconstraints}{$name}};
+ if ($tname1 ne $tname2) {
+ push @{$fail{colconstraints}{tablediff}} =>
+ [
+ $name,
+ $tname1,
+ $tname2,
+ ];
+ $failcount++;
+ }
+ ## Check for a difference in schema/table/column
+ elsif ($cname1 ne $cname2) {
+ push @{$fail{colconstraints}{columndiff}} =>
+ [
+ $name,
+ $tname1, $cname1,
+ $tname2, $cname2,
+ ];
+ $failcount++;
+ }
+ ## Check for a difference in schema/table/column/definition
+ elsif ($cdef1 ne $cdef2) {
+ push @{$fail{colconstraints}{defdiff}} =>
+ [
+ $name,
+ $tname1, $cname1, $cdef1,
+ $tname2, $cname2, $cdef2,
+ ];
+ $failcount++;
+ }
+ }
+
+ ## Compare languages
+ for my $name (sort keys %{$thing{1}{language}}) {
+ if (!exists $thing{2}{language}{$name}) {
+ push @{$fail{language}{notexist}{1}} => $name;
+ $failcount++;
+ next;
+ }
+ }
+ for my $name (sort keys %{$thing{2}{language}}) {
+ if (!exists $thing{1}{language}{$name}) {
+ push @{$fail{language}{notexist}{2}} => $name;
+ $failcount++;
+ next;
+ }
+ }
+
+ ## Compare functions
+
+ ## Functions on 1 but not 2?
+ FUNCTION1:
+ for my $name (sort keys %{$thing{1}{functions}}) {
+ next if exists $thing{2}{functions}{$name};
+
+ if (exists $filter{nofunction_regex}) {
+ for my $regex (@{$filter{nofunction_regex}}) {
+ next FUNCTION1 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next FUNCTION1 if $name =~ /$exclude/;
+ }
+
+ ## Skip if these are a side effect of having a language
+ for my $l (@{$fail{language}{notexist}{1}}) {
+ $l =~ s/u$//;
+ next FUNCTION1 if
+ $name eq "pg_catalog.${l}_call_handler()"
+ or $name eq "pg_catalog.${l}_validator(oid)";
+ }
+
+ push @{$fail{functions}{notexist}{1}} => $name;
+ $failcount++;
+ }
+
+ ## Functions on 2 but not 1 and check for identity
+ FUNCTION2:
+ for my $name (sort keys %{$thing{2}{functions}}) {
+
+ if (exists $filter{nofunction_regex}) {
+ for my $regex (@{$filter{nofunction_regex}}) {
+ next FUNCTION2 if $name =~ /$regex/;
+ }
+ }
+
+ for my $exclude (@{$opt{exclude}}) {
+ next FUNCTION2 if $name =~ /$exclude/;
+ }
+
+ ## Skip if these are a side effect of having a language
+ for my $l (@{$fail{language}{notexist}{2}}) {
+ $l =~ s/u$//;
+ next FUNCTION2 if
+ $name =~ "pg_catalog.${l}_call_handler()"
+ or $name eq "pg_catalog.${l}_validator(oid)";
+ }
+
+ if (! exists $thing{1}{functions}{$name}) {
+ push @{$fail{functions}{notexist}{2}} => $name;
+ $failcount++;
+ next;
+ }
+
+ ## Are the insides exactly the same
+ if (! $filter{nofuncbody}) {
+ if ($thing{1}{functions}{$name}{md5} ne $thing{2}{functions}{$name}{md5}) {
+ push @{$fail{functions}{diffbody}}, $name;
+ $failcount++;
+ }
+ }
+
+ if (! $filter{nofuncstrict}) {
+ if ($thing{1}{functions}{$name}{isstrict} ne $thing{2}{functions}{$name}{isstrict}) {
+ push @{$fail{functions}{diffstrict}}, $name;
+ $failcount++;
+ }
+ }
+
+ if (! $filter{nofuncret}) {
+ if ($thing{1}{functions}{$name}{retset} ne $thing{2}{functions}{$name}{retset}) {
+ push @{$fail{functions}{diffretset}}, $name;
+ $failcount++;
+ }
+ }
+ if (! $filter{nofuncvol}) {
+ if ($thing{1}{functions}{$name}{volatile} ne $thing{2}{functions}{$name}{volatile}) {
+ push @{$fail{functions}{diffvol}}, $name;
+ $failcount++;
+ }
+ }
+ }
+
+
+ ##
+ ## Comparison is done, let's report the results
+ ##
+
+ if (! $failcount) {
+ add_ok msg('same-matched');
+ return;
+ }
+
+ ## Build a pretty message giving all the gory details
+
+ $db->{perf} = '';
+
+ ## User differences
+ if (exists $fail{users}) {
+ if (exists $fail{users}{notexist}) {
+ if (exists $fail{users}{notexist}{1}) {
+ $db->{perf} .= ' Users in 1 but not 2: ';
+ $db->{perf} .= join ', ' => @{$fail{users}{notexist}{1}};
+ $db->{perf} .= ' ';
+ }
+ if (exists $fail{users}{notexist}{2}) {
+ $db->{perf} .= ' Users in 2 but not 1: ';
+ $db->{perf} .= join ', ' => @{$fail{users}{notexist}{2}};
+ $db->{perf} .= ' ';
+ }
+ }
+ if (exists $fail{users}{createdb}) {
+ if (exists $fail{users}{createdb}{1}) {
+ if (exists $fail{users}{createdb}{1}{t}) {
+ $db->{perf} .= ' Users with createdb on 1 but not 2: ';
+ $db->{perf} .= join ', ' => @{$fail{users}{createdb}{1}{t}};
+ $db->{perf} .= ' ';
+ }
+ if (exists $fail{users}{createdb}{1}{f}) {
+ $db->{perf} .= ' Users with createdb on 2 but not 1: ';
+ $db->{perf} .= join ', ' => @{$fail{users}{createdb}{1}{f}};
+ $db->{perf} .= ' ';
+ }
+ }
+ }
+ if (exists $fail{users}{superuser}) {
+ if (exists $fail{users}{superuser}{1}) {
+ if (exists $fail{users}{superuser}{1}{t}) {
+ $db->{perf} .= ' Users with superuser on 1 but not 2: ';
+ $db->{perf} .= join ', ' => @{$fail{users}{superuser}{1}{t}};
+ $db->{perf} .= ' ';
+ }
+ if (exists $fail{users}{superuser}{1}{f}) {
+ $db->{perf} .= ' Users with superuser on 2 but not 1: ';
+ $db->{perf} .= join ', ' => @{$fail{users}{superuser}{1}{f}};
+ $db->{perf} .= ' ';
+ }
+ }
+ }
+ }
+
+ ## Schema differences
+ if (exists $fail{schemas}) {
+ if (exists $fail{schemas}{notexist}) {
+ if (exists $fail{schemas}{notexist}{1}) {
+ for my $name (@{$fail{schemas}{notexist}{1}}) {
+ $db->{perf} .= " Schema in 1 but not 2: $name ";
+ }
+ }
+ if (exists $fail{schemas}{notexist}{2}) {
+ for my $name (@{$fail{schemas}{notexist}{2}}) {
+ $db->{perf} .= " Schema in 2 but not 1: $name ";
+ }
+ }
+ }
+ if (exists $fail{schemas}{diffowners}) {
+ for my $item (@{$fail{schemas}{diffowners}}) {
+ my ($name,$owner1,$owner2) = @$item;
+ $db->{perf} .= qq{ Schema "$name" owned by "$owner1" on 1, but by "$owner2" on 2. };
+ }
+ }
+ if (exists $fail{schemas}{diffacls}) {
+ for my $item (@{$fail{schemas}{diffacls}}) {
+ my ($name,$acl1,$acl2) = @$item;
+ $db->{perf} .= qq{ Schema "$name" has $acl1 perms on 1, but $acl2 perms on 2. };
+ }
+ }
+ }
+
+ ## Table differences
+ if (exists $fail{tables}) {
+ if (exists $fail{tables}{notexist}) {
+ if (exists $fail{tables}{notexist}{1}) {
+ for my $name (@{$fail{tables}{notexist}{1}}) {
+ $db->{perf} .= " Table in 1 but not 2: $name ";
+ }
+ }
+ if (exists $fail{tables}{notexist}{2}) {
+ for my $name (@{$fail{tables}{notexist}{2}}) {
+ $db->{perf} .= " Table in 2 but not 1: $name ";
+ }
+ }
+ }
+ if (exists $fail{tables}{diffowners}) {
+ for my $item (@{$fail{tables}{diffowners}}) {
+ my ($name,$owner1,$owner2) = @$item;
+ $db->{perf} .= qq{ Table "$name" owned by "$owner1" on 1, but by "$owner2" on 2. };
+ }
+ }
+ if (exists $fail{tables}{diffacls}) {
+ for my $item (@{$fail{tables}{diffacls}}) {
+ my ($name,$acl1,$acl2) = @$item;
+ $db->{perf} .= qq{ Table "$name" has $acl1 perms on 1, but $acl2 perms on 2. };
+ }
+ }
+ }
+
+ ## Sequence differences
+ if (exists $fail{sequences}) {
+ if (exists $fail{sequences}{notexist}) {
+ if (exists $fail{sequences}{notexist}{1}) {
+ for my $name (@{$fail{sequences}{notexist}{1}}) {
+ $db->{perf} .= " Sequence in 1 but not 2: $name ";
+ }
+ }
+ if (exists $fail{sequences}{notexist}{2}) {
+ for my $name (@{$fail{sequences}{notexist}{2}}) {
+ $db->{perf} .= " Sequence in 2 but not 1: $name ";
+ }
+ }
+ }
+ if (exists $fail{sequences}{diffowners}) {
+ for my $item (@{$fail{sequences}{diffowners}}) {
+ my ($name,$owner1,$owner2) = @$item;
+ $db->{perf} .= qq{ Sequence "$name" owned by "$owner1" on 1, but by "$owner2" on 2. };
+ }
+ }
+ if (exists $fail{sequences}{diffacls}) {
+ for my $item (@{$fail{sequences}{diffacls}}) {
+ my ($name,$acl1,$acl2) = @$item;
+ $db->{perf} .= qq{ Sequence "$name" has $acl1 perms on 1, but $acl2 perms on 2. };
+ }
+ }
+ }
+
+ ## View differences
+ if (exists $fail{views}) {
+ if (exists $fail{views}{notexist}) {
+ if (exists $fail{views}{notexist}{1}) {
+ for my $name (@{$fail{views}{notexist}{1}}) {
+ $db->{perf} .= " View in 1 but not 2: $name ";
+ }
+ }
+ if (exists $fail{views}{notexist}{2}) {
+ for my $name (@{$fail{views}{notexist}{2}}) {
+ $db->{perf} .= " View in 2 but not 1: $name ";
+ }
+ }
+ }
+ if (exists $fail{views}{diffowners}) {
+ for my $item (@{$fail{views}{diffowners}}) {
+ my ($name,$owner1,$owner2) = @$item;
+ $db->{perf} .= qq{ View "$name" owned by "$owner1" on 1, but by "$owner2" on 2. };
+ }
+ }
+ if (exists $fail{views}{diffacls}) {
+ for my $item (@{$fail{views}{diffacls}}) {
+ my ($name,$acl1,$acl2) = @$item;
+ $db->{perf} .= qq{ View "$name" has $acl1 perms on 1, but $acl2 perms on 2. };
+ }
+ }
+ if (exists $fail{views}{diffdef}) {
+ for my $item (@{$fail{views}{diffdef}}) {
+ $db->{perf} .= qq{ View "$item" is different on 1 and 2. };
+ }
+ }
+ }
+
+ ## Trigger differences
+ if (exists $fail{triggers}) {
+ if (exists $fail{triggers}{notexist}) {
+ if (exists $fail{triggers}{notexist}{1}) {
+ for my $row (@{$fail{triggers}{notexist}{1}}) {
+ my ($name,$tabname) = @$row;
+ $db->{perf} .= " Trigger in 1 but not 2: $name (on $tabname) ";
+ }
+ }
+ if (exists $fail{triggers}{notexist}{2}) {
+ for my $row (@{$fail{triggers}{notexist}{2}}) {
+ my ($name,$tabname) = @$row;
+ $db->{perf} .= " Trigger in 2 but not 1: $name (on $tabname) ";
+ }
+ }
+ }
+ if (exists $fail{triggers}{difffunc}) {
+ for my $item (@{$fail{triggers}{diffowners}}) {
+ my ($name,$func1,$func2) = @$item;
+ $db->{perf} .= qq{ Trigger "$name" calls function "$func1" on 1, but function "$func2" on 2. };
+ }
+ }
+ }
+
+ ## Column differences
+ if (exists $fail{columns}) {
+ if (exists $fail{columns}{notexist}) {
+ if (exists $fail{columns}{notexist}{1}) {
+ for my $row (@{$fail{columns}{notexist}{1}}) {
+ my ($tname,$cname) = @$row;
+ $db->{perf} .= qq{ Table "$tname" on 1 has column "$cname", but 2 does not. };
+ }
+ }
+ if (exists $fail{columns}{notexist}{2}) {
+ for my $row (@{$fail{columns}{notexist}{2}}) {
+ my ($tname,$cname) = @$row;
+ $db->{perf} .= qq{ Table "$tname" on 2 has column "$cname", but 1 does not. };
+ }
+ }
+ }
+ if (exists $fail{columns}{diff}) {
+ for my $tname (sort keys %{$fail{columns}{diff}}) {
+ for my $cname (sort keys %{$fail{columns}{diff}{$tname}}) {
+ for my $var (sort keys %{$fail{columns}{diff}{$tname}{$cname}}) {
+ my ($v1,$v2) = @{$fail{columns}{diff}{$tname}{$cname}{$var}};
+ $db->{perf} .= qq{ Column "$cname" of "$tname": $var is $v1 on 1, but $v2 on 2. };
+ }
+ }
+ }
+ }
+ }
+
+ ## Constraint differences - table level
+ ## Don't report things twice
+ my %doublec;
+ if (exists $fail{constraints}) {
+ if (exists $fail{constraints}{notexist}) {
+ if (exists $fail{constraints}{notexist}{1}) {
+ for my $row (@{$fail{constraints}{notexist}{1}}) {
+ my ($cname,$tname) = @$row;
+ $db->{perf} .= qq{ Table "$tname" on 1 has constraint "$cname", but 2 does not. };
+ $doublec{$cname}++;
+ }
+ }
+ if (exists $fail{constraints}{notexist}{2}) {
+ for my $row (@{$fail{constraints}{notexist}{2}}) {
+ my ($cname,$tname) = @$row;
+ $db->{perf} .= qq{ Table "$tname" on 2 has constraint "$cname", but 1 does not. };
+ $doublec{$cname}++;
+ }
+ }
+ }
+ if (exists $fail{constraints}{tablediff}) {
+ for my $row (@{$fail{constraints}{tablediff}}) {
+ my ($cname,$t1,$t2) = @$row;
+ $db->{perf} .= qq{ Constraint "$cname" is applied to "$t1" on 1, but to "$t2" on 2. };
+ $doublec{$cname}++;
+ }
+ }
+ }
+
+ ## Constraint differences - column level
+ if (exists $fail{colconstraints}) {
+ if (exists $fail{colconstraints}{notexist}) {
+ if (exists $fail{colconstraints}{notexist}{1}) {
+ for my $row (@{$fail{colconstraints}{notexist}{1}}) {
+ my ($name,$tname,$cname) = @$row;
+ if (! exists $doublec{$name}) {
+ $db->{perf} .= qq{ Table "$tname" on 1 has constraint "$name" on column "$cname", but 2 does not. };
+ }
+ else {
+ $failcount--;
+ }
+ }
+ }
+ if (exists $fail{colconstraints}{notexist}{2}) {
+ for my $row (@{$fail{colconstraints}{notexist}{2}}) {
+ my ($name,$tname,$cname) = @$row;
+ if (! exists $doublec{$name}) {
+ $db->{perf} .= qq{ Table "$tname" on 2 has constraint "$name" on column "$cname", but 1 does not. };
+ }
+ else {
+ $failcount--;
+ }
+ }
+ }
+ }
+ if (exists $fail{colconstraints}{tablediff}) {
+ for my $row (@{$fail{colconstraints}{tablediff}}) {
+ my ($name,$t1,$t2) = @$row;
+ if (! exists $doublec{$name}) {
+ $db->{perf} .= qq{ Constraint "$name" is applied to "$t1" on 1, but to "$t2" on 2. };
+ }
+ else {
+ $failcount--;
+ }
+ }
+ }
+ if (exists $fail{colconstraints}{columndiff}) {
+ for my $row (@{$fail{colconstraints}{columndiff}}) {
+ my ($name,$t1,$c1,$t2,$c2) = @$row;
+ if (! exists $doublec{$name}) {
+ $db->{perf} .= qq{ Constraint "$name" on 1 is applied to $t1.$c1, but to $t2.$c2 on 2. };
+ }
+ else {
+ $failcount--;
+ }
+ }
+ }
+ if (exists $fail{colconstraints}{defdiff}) {
+ for my $row (@{$fail{colconstraints}{defdiff}}) {
+ my ($name,$t1,$c1,$d1,$t2,$c2,$d2) = @$row;
+ if (! exists $doublec{$name}) {
+ $db->{perf} .= qq{ Constraint "$name" on 1 differs from 2 ("$d1" vs. "$d2") };
+ }
+ else {
+ $failcount--;
+ }
+ }
+ }
+ }
+
+ ## Function differences
+ if (exists $fail{functions}) {
+ if (exists $fail{functions}{notexist}) {
+ if (exists $fail{functions}{notexist}{1}) {
+ for my $name (@{$fail{functions}{notexist}{1}}) {
+ $db->{perf} .= " Function on 1 but not 2: $name ";
+ }
+ }
+ if (exists $fail{functions}{notexist}{2}) {
+ for my $name (@{$fail{functions}{notexist}{2}}) {
+ $db->{perf} .= " Function on 2 but not 1: $name ";
+ }
+ }
+ }
+ if (exists $fail{functions}{diffbody}) {
+ for my $name (sort @{$fail{functions}{diffbody}}) {
+ $db->{perf} .= " Function body different on 1 than 2: $name ";
+ }
+ }
+ if (exists $fail{functions}{diffstrict}) {
+ for my $name (sort @{$fail{functions}{diffbody}}) {
+ $db->{perf} .= " Function strictness different on 1 than 2: $name ";
+ }
+ }
+ if (exists $fail{functions}{diffretset}) {
+ for my $name (sort @{$fail{functions}{diffretset}}) {
+ $db->{perf} .= " Function return-set different on 1 than 2: $name ";
+ }
+ }
+ if (exists $fail{functions}{diffvol}) {
+ for my $name (sort @{$fail{functions}{diffvol}}) {
+ $db->{perf} .= " Function volatilitiy different on 1 than 2: $name ";
+ }
+ }
+ }
+
+ ## Language differences
+ if (exists $fail{language}) {
+ if (exists $fail{language}{notexist}) {
+ if (exists $fail{language}{notexist}{1}) {
+ for my $name (@{$fail{language}{notexist}{1}}) {
+ $db->{perf} .= " Language on 1 but not 2: $name ";
+ }
+ }
+ if (exists $fail{language}{notexist}{2}) {
+ for my $name (@{$fail{language}{notexist}{2}}) {
+ $db->{perf} .= " Language on 2 but not 1: $name ";
+ }
+ }
+ }
+ }
+
+
+ add_critical msg('same-failed', $failcount);
+
+ return;
} ## end of check_same_schema
sub check_sequence {
- ## Checks how many values are left in sequences
- ## Supports: Nagios, MRTG
- ## Warning and critical are percentages
- ## Can exclude and include sequences
+ ## Checks how many values are left in sequences
+ ## Supports: Nagios, MRTG
+ ## Warning and critical are percentages
+ ## Can exclude and include sequences
- my ($warning, $critical) = validate_range
- ({
- type => 'percent',
- default_warning => '85%',
- default_critical => '95%',
- forcemrtg => 1,
- });
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'percent',
+ default_warning => '85%',
+ default_critical => '95%',
+ forcemrtg => 1,
+ });
- (my $w = $warning) =~ s/\D//;
- (my $c = $critical) =~ s/\D//;
+ (my $w = $warning) =~ s/\D//;
+ (my $c = $critical) =~ s/\D//;
- ## Gather up all sequence names
- my $SQL = q{
+ ## Gather up all sequence names
+ my $SQL = q{
SELECT DISTINCT ON (nspname, seqname) nspname, seqname,
quote_ident(nspname) || '.' || quote_ident(seqname) AS safename, typname
-- sequences by column dependency
ORDER BY nspname, seqname, typname
};
- my $info = run_command($SQL, {regex => qr{\w}, emptyok => 1} );
-
- my $MAXINT2 = 32767;
- my $MAXINT4 = 2147483647;
- my $MAXINT8 = 9223372036854775807;
-
- my $limit = 0;
-
- for $db (@{$info->{db}}) {
- my (@crit,@warn,@ok);
- my $maxp = 0;
- my %seqinfo;
- my %seqperf;
- my $multidb = @{$info->{db}} > 1 ? "$db->{dbname}." : '';
- for my $r (@{$db->{slurp}}) {
- my ($schema, $seq, $seqname, $typename) = @$r{qw/ nspname seqname safename typname /};
- next if skip_item($seq);
- my $maxValue = $typename eq 'int2' ? $MAXINT2 : $typename eq 'int4' ? $MAXINT4 : $MAXINT8;
- $SQL = qq{
+ my $info = run_command($SQL, {regex => qr{\w}, emptyok => 1} );
+
+ my $MAXINT2 = 32767;
+ my $MAXINT4 = 2147483647;
+ my $MAXINT8 = 9223372036854775807;
+
+ my $limit = 0;
+
+ for $db (@{$info->{db}}) {
+ my (@crit,@warn,@ok);
+ my $maxp = 0;
+ my %seqinfo;
+ my %seqperf;
+ my $multidb = @{$info->{db}} > 1 ? "$db->{dbname}." : '';
+ for my $r (@{$db->{slurp}}) {
+ my ($schema, $seq, $seqname, $typename) = @$r{qw/ nspname seqname safename typname /};
+ next if skip_item($seq);
+ my $maxValue = $typename eq 'int2' ? $MAXINT2 : $typename eq 'int4' ? $MAXINT4 : $MAXINT8;
+ $SQL = qq{
SELECT last_value, slots, used, ROUND(used/slots*100) AS percent,
CASE WHEN slots < used THEN 0 ELSE slots - used END AS numleft
FROM (
FROM $seqname) foo
};
- my $seqinfo = run_command($SQL, { target => $db });
- my $r2 = $seqinfo->{db}[0]{slurp}[0];
- my ($last, $slots, $used, $percent, $left) = @$r2{qw/ last_value slots used percent numleft / };
- if (! defined $last) {
- ndie msg('seq-die', $seqname);
- }
- my $msg = msg('seq-msg', $seqname, $percent, $left);
- $seqperf{$percent}{$seqname} = [$left, " $multidb$seqname=$percent|$slots|$used|$left"];
- if ($percent >= $maxp) {
- $maxp = $percent;
- if (! exists $opt{perflimit} or $limit++ < $opt{perflimit}) {
- push @{$seqinfo{$percent}} => $MRTG ? [$seqname,$percent,$slots,$used,$left] : $msg;
- }
- }
- next if $MRTG;
-
- if (length $critical and $percent >= $c) {
- push @crit => $msg;
- }
- elsif (length $warning and $percent >= $w) {
- push @warn => $msg;
- }
- }
- if ($MRTG) {
- my $msg = join ' | ' => map { $_->[0] } @{$seqinfo{$maxp}};
- do_mrtg({one => $maxp, msg => $msg});
- }
- $limit = 0;
- PERF: for my $val (sort { $b <=> $a } keys %seqperf) {
- for my $seq (sort { $seqperf{$val}{$a}->[0] <=> $seqperf{$val}{$b}->[0] or $a cmp $b } keys %{$seqperf{$val}}) {
- last PERF if exists $opt{perflimit} and $limit++ >= $opt{perflimit};
- $db->{perf} .= $seqperf{$val}{$seq}->[1];
- }
- }
-
- if (@crit) {
- add_critical join ' ' => @crit;
- }
- elsif (@warn) {
- add_warning join ' ' => @warn;
- }
- else {
- if (keys %seqinfo) {
- add_ok join ' ' => @{$seqinfo{$maxp}};
- }
- else {
- add_ok msg('seq-none');
- }
- }
- }
-
- return;
+ my $seqinfo = run_command($SQL, { target => $db });
+ my $r2 = $seqinfo->{db}[0]{slurp}[0];
+ my ($last, $slots, $used, $percent, $left) = @$r2{qw/ last_value slots used percent numleft / };
+ if (! defined $last) {
+ ndie msg('seq-die', $seqname);
+ }
+ my $msg = msg('seq-msg', $seqname, $percent, $left);
+ $seqperf{$percent}{$seqname} = [$left, " $multidb$seqname=$percent|$slots|$used|$left"];
+ if ($percent >= $maxp) {
+ $maxp = $percent;
+ if (! exists $opt{perflimit} or $limit++ < $opt{perflimit}) {
+ push @{$seqinfo{$percent}} => $MRTG ? [$seqname,$percent,$slots,$used,$left] : $msg;
+ }
+ }
+ next if $MRTG;
+
+ if (length $critical and $percent >= $c) {
+ push @crit => $msg;
+ }
+ elsif (length $warning and $percent >= $w) {
+ push @warn => $msg;
+ }
+ }
+ if ($MRTG) {
+ my $msg = join ' | ' => map { $_->[0] } @{$seqinfo{$maxp}};
+ do_mrtg({one => $maxp, msg => $msg});
+ }
+ $limit = 0;
+ PERF: for my $val (sort { $b <=> $a } keys %seqperf) {
+ for my $seq (sort { $seqperf{$val}{$a}->[0] <=> $seqperf{$val}{$b}->[0] or $a cmp $b } keys %{$seqperf{$val}}) {
+ last PERF if exists $opt{perflimit} and $limit++ >= $opt{perflimit};
+ $db->{perf} .= $seqperf{$val}{$seq}->[1];
+ }
+ }
+
+ if (@crit) {
+ add_critical join ' ' => @crit;
+ }
+ elsif (@warn) {
+ add_warning join ' ' => @warn;
+ }
+ else {
+ if (keys %seqinfo) {
+ add_ok join ' ' => @{$seqinfo{$maxp}};
+ }
+ else {
+ add_ok msg('seq-none');
+ }
+ }
+ }
+
+ return;
} ## end of check_sequence
sub check_settings_checksum {
- ## Verify the checksum of all settings
- ## Supports: Nagios, MRTG
- ## Not that this will vary from user to user due to ALTER USER
- ## and because superusers see additional settings
- ## One of warning or critical must be given (but not both)
- ## It should run one time to find out the expected checksum
- ## You can use --critical="0" to find out the checksum
- ## You can include or exclude settings as well
- ## Example:
- ## check_postgres_settings_checksum --critical="4e7ba68eb88915d3d1a36b2009da4acd"
-
- my ($warning, $critical) = validate_range({type => 'checksum', onlyone => 1});
-
- eval {
- require Digest::MD5;
- };
- if ($@) {
- ndie msg('checksum-nomd');
- }
-
- $SQL = 'SELECT name, setting FROM pg_settings ORDER BY name';
- my $info = run_command($SQL, { regex => qr[client_encoding] });
-
- for $db (@{$info->{db}}) {
-
- my $newstring = '';
- for my $r (@{$db->{slurp}}) {
- next SLURP if skip_item($r->{name});
- $newstring .= "$r->{name} $r->{setting}\n";
- }
- if (! length $newstring) {
- add_unknown msg('no-match-set');
- }
-
- my $checksum = Digest::MD5::md5_hex($newstring);
-
- my $msg = msg('checksum-msg', $checksum);
- if ($MRTG) {
- $opt{mrtg} or ndie msg('checksum-nomrtg');
- do_mrtg({one => $opt{mrtg} eq $checksum ? 1 : 0, msg => $checksum});
- }
- if ($critical and $critical ne $checksum) {
- add_critical $msg;
- }
- elsif ($warning and $warning ne $checksum) {
- add_warning $msg;
- }
- elsif (!$critical and !$warning) {
- add_unknown $msg;
- }
- else {
- add_ok $msg;
- }
- }
-
- return;
+ ## Verify the checksum of all settings
+ ## Supports: Nagios, MRTG
+ ## Not that this will vary from user to user due to ALTER USER
+ ## and because superusers see additional settings
+ ## One of warning or critical must be given (but not both)
+ ## It should run one time to find out the expected checksum
+ ## You can use --critical="0" to find out the checksum
+ ## You can include or exclude settings as well
+ ## Example:
+ ## check_postgres_settings_checksum --critical="4e7ba68eb88915d3d1a36b2009da4acd"
+
+ my ($warning, $critical) = validate_range({type => 'checksum', onlyone => 1});
+
+ eval {
+ require Digest::MD5;
+ };
+ if ($@) {
+ ndie msg('checksum-nomd');
+ }
+
+ $SQL = 'SELECT name, setting FROM pg_settings ORDER BY name';
+ my $info = run_command($SQL, { regex => qr[client_encoding] });
+
+ for $db (@{$info->{db}}) {
+
+ my $newstring = '';
+ for my $r (@{$db->{slurp}}) {
+ next SLURP if skip_item($r->{name});
+ $newstring .= "$r->{name} $r->{setting}\n";
+ }
+ if (! length $newstring) {
+ add_unknown msg('no-match-set');
+ }
+
+ my $checksum = Digest::MD5::md5_hex($newstring);
+
+ my $msg = msg('checksum-msg', $checksum);
+ if ($MRTG) {
+ $opt{mrtg} or ndie msg('checksum-nomrtg');
+ do_mrtg({one => $opt{mrtg} eq $checksum ? 1 : 0, msg => $checksum});
+ }
+ if ($critical and $critical ne $checksum) {
+ add_critical $msg;
+ }
+ elsif ($warning and $warning ne $checksum) {
+ add_warning $msg;
+ }
+ elsif (!$critical and !$warning) {
+ add_unknown $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+
+ return;
} ## end of check_settings_checksum
sub check_slony_status {
- ## Checks the sl_status table
- ## Returns unknown if sl_status is not found
- ## Returns critical is status is not "good"
- ## Otherwise, returns based on time-based warning and critical options
- ## Supports: Nagios, MRTG
-
- my ($warning, $critical) = validate_range
- ({
- type => 'time',
- default_warning => '60',
- default_critical => '300',
- });
-
- my $schema = $opt{schema} || '';
-
- if (!$schema) {
- $SQL = q{SELECT quote_ident(nspname) AS nspname FROM pg_namespace WHERE oid = }.
- q{(SELECT relnamespace FROM pg_class WHERE relkind = 'v' AND relname = 'sl_status' LIMIT 1)};
- my $res = run_command($SQL);
- if (! defined $res->{db}[0]{slurp}[0]{nspname}) {
- add_unknown msg('slony-noschema');
- return;
- }
- $schema = $res->{db}[0]{slurp}[0]{nspname};
- }
-
- my $SQL =
+ ## Checks the sl_status table
+ ## Returns unknown if sl_status is not found
+ ## Returns critical is status is not "good"
+ ## Otherwise, returns based on time-based warning and critical options
+ ## Supports: Nagios, MRTG
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'time',
+ default_warning => '60',
+ default_critical => '300',
+ });
+
+ my $schema = $opt{schema} || '';
+
+ if (!$schema) {
+ $SQL = q{SELECT quote_ident(nspname) AS nspname FROM pg_namespace WHERE oid = }.
+ q{(SELECT relnamespace FROM pg_class WHERE relkind = 'v' AND relname = 'sl_status' LIMIT 1)};
+ my $res = run_command($SQL);
+ if (! defined $res->{db}[0]{slurp}[0]{nspname}) {
+ add_unknown msg('slony-noschema');
+ return;
+ }
+ $schema = $res->{db}[0]{slurp}[0]{nspname};
+ }
+
+ my $SQL =
qq{SELECT
ROUND(EXTRACT(epoch FROM st_lag_time)) AS lagtime,
st_origin,
JOIN $schema.sl_node n1 ON (n1.no_id=st_origin)
JOIN $schema.sl_node n2 ON (n2.no_id=st_received)};
- my $info = run_command($SQL);
- $db = $info->{db}[0];
- if (! defined $db->{slurp}[0]{lagtime}) {
- add_unknown msg('slony-nonumber');
- return;
- }
- my $maxlagtime = 0;
- my @perf;
- for my $r (@{$db->{slurp}}) {
- if (! defined $r->{lagtime}) {
- add_unknown msg('slony-noparse');
- }
- my ($lag,$from,$to,$dbname,$fromc,$toc) = @$r{qw/ lagtime st_origin st_received cd com1 com2/};
- $maxlagtime = $lag if $lag > $maxlagtime;
- push @perf => [
- $lag,
- $from,
- qq{'$dbname Node $from($fromc) -> Node $to($toc)'=$lag;$warning;$critical},
- ];
- }
- $db->{perf} = join "\n" => map { $_->[2] } sort { $b->[0]<=>$a->[0] or $a->[1]<=>$b->[1] } @perf;
- if ($MRTG) {
- do_mrtg({one => $maxlagtime});
- return;
- }
- my $msg = msg('slony-lagtime', $maxlagtime);
- $msg .= sprintf ' (%s)', pretty_time($maxlagtime, $maxlagtime > 500 ? 'S' : '');
- if (length $critical and $maxlagtime >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $maxlagtime >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
-
- return;
+ my $info = run_command($SQL);
+ $db = $info->{db}[0];
+ if (! defined $db->{slurp}[0]{lagtime}) {
+ add_unknown msg('slony-nonumber');
+ return;
+ }
+ my $maxlagtime = 0;
+ my @perf;
+ for my $r (@{$db->{slurp}}) {
+ if (! defined $r->{lagtime}) {
+ add_unknown msg('slony-noparse');
+ }
+ my ($lag,$from,$to,$dbname,$fromc,$toc) = @$r{qw/ lagtime st_origin st_received cd com1 com2/};
+ $maxlagtime = $lag if $lag > $maxlagtime;
+ push @perf => [
+ $lag,
+ $from,
+ qq{'$dbname Node $from($fromc) -> Node $to($toc)'=$lag;$warning;$critical},
+ ];
+ }
+ $db->{perf} = join "\n" => map { $_->[2] } sort { $b->[0]<=>$a->[0] or $a->[1]<=>$b->[1] } @perf;
+ if ($MRTG) {
+ do_mrtg({one => $maxlagtime});
+ return;
+ }
+ my $msg = msg('slony-lagtime', $maxlagtime);
+ $msg .= sprintf ' (%s)', pretty_time($maxlagtime, $maxlagtime > 500 ? 'S' : '');
+ if (length $critical and $maxlagtime >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $maxlagtime >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+
+ return;
} ## end of check_slony_status
sub check_timesync {
- ## Compare local time to the database time
- ## Supports: Nagios, MRTG
- ## Warning and critical are given in number of seconds difference
-
- my ($warning,$critical) = validate_range
- ({
- type => 'seconds',
- default_warning => 2,
- default_critical => 5,
- });
-
- $SQL = q{SELECT round(extract(epoch FROM now())) AS epok, TO_CHAR(now(),'YYYY-MM-DD HH24:MI:SS') AS pretti};
- my $info = run_command($SQL);
- my $localepoch = time;
- my @l = localtime;
-
- for $db (@{$info->{db}}) {
- my ($pgepoch,$pgpretty) = @{$db->{slurp}->[0]}{qw/ epok pretti /};
-
- my $diff = abs($pgepoch - $localepoch);
- if ($MRTG) {
- $stats{$db->{dbname}} = $diff;
- next;
- }
- $db->{perf} = msg('timesync-diff', $diff);
- my $localpretty = sprintf '%d-%02d-%02d %02d:%02d:%02d', $l[5]+1900, $l[4]+1, $l[3],$l[2],$l[1],$l[0];
- my $msg = msg('timesync-msg', $diff, $pgpretty, $localpretty);
-
- if (length $critical and $diff >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $diff >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
- return;
+ ## Compare local time to the database time
+ ## Supports: Nagios, MRTG
+ ## Warning and critical are given in number of seconds difference
+
+ my ($warning,$critical) = validate_range
+ ({
+ type => 'seconds',
+ default_warning => 2,
+ default_critical => 5,
+ });
+
+ $SQL = q{SELECT round(extract(epoch FROM now())) AS epok, TO_CHAR(now(),'YYYY-MM-DD HH24:MI:SS') AS pretti};
+ my $info = run_command($SQL);
+ my $localepoch = time;
+ my @l = localtime;
+
+ for $db (@{$info->{db}}) {
+ my ($pgepoch,$pgpretty) = @{$db->{slurp}->[0]}{qw/ epok pretti /};
+
+ my $diff = abs($pgepoch - $localepoch);
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $diff;
+ next;
+ }
+ $db->{perf} = msg('timesync-diff', $diff);
+ my $localpretty = sprintf '%d-%02d-%02d %02d:%02d:%02d', $l[5]+1900, $l[4]+1, $l[3],$l[2],$l[1],$l[0];
+ my $msg = msg('timesync-msg', $diff, $pgpretty, $localpretty);
+
+ if (length $critical and $diff >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $diff >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+ return;
} ## end of check_timesync
sub check_txn_idle {
- ## Check the length of "idle in transaction" connections
- ## Supports: Nagios, MRTG
- ## It makes no sense to run this more than once on the same cluster
- ## Warning and critical are time limits - defaults to seconds
- ## Valid units: s[econd], m[inute], h[our], d[ay]
- ## All above may be written as plural as well (e.g. "2 hours")
- ## Can also ignore databases with exclude and limit with include
- ## Limit to a specific user with the includeuser option
- ## Exclude users with the excludeuser option
-
- my ($warning, $critical) = validate_range
- ({
- type => 'time',
- });
-
-
- $SQL = q{SELECT datname, max(COALESCE(ROUND(EXTRACT(epoch FROM now()-query_start)),0)) AS maxx }.
- qq{FROM pg_stat_activity WHERE current_query = '<IDLE> in transaction'$USERWHERECLAUSE GROUP BY 1};
-
- my $info = run_command($SQL, { emptyok => 1 } );
-
- my $found = 0;
- for $db (@{$info->{db}}) {
- my $max = -1;
- for my $r (@{$db->{slurp}}) {
- $found++;
- my ($dbname,$current) = ($r->{datname}, int $r->{maxx});
- next if skip_item($dbname);
- $max = $current if $current > $max;
- }
- if ($MRTG) {
- $stats{$db->{dbname}} = $max < 0 ? 0 : $max;
- next;
- }
- $db->{perf} .= msg('maxtime', $max);
- if ($max < 0) {
- add_unknown msg('txnidle-none');
- next;
- }
-
- my $msg = msg('txnidle-msg', $max);
- if (length $critical and $max >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $max >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
-
- ## If no results, let's be paranoid and check their settings
- if (!$found) {
- if ($USERWHERECLAUSE) {
- add_ok msg('no-match-user');
- }
- verify_version();
- }
-
- return;
+ ## Check the length of "idle in transaction" connections
+ ## Supports: Nagios, MRTG
+ ## It makes no sense to run this more than once on the same cluster
+ ## Warning and critical are time limits - defaults to seconds
+ ## Valid units: s[econd], m[inute], h[our], d[ay]
+ ## All above may be written as plural as well (e.g. "2 hours")
+ ## Can also ignore databases with exclude and limit with include
+ ## Limit to a specific user with the includeuser option
+ ## Exclude users with the excludeuser option
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'time',
+ });
+
+
+ $SQL = q{SELECT datname, max(COALESCE(ROUND(EXTRACT(epoch FROM now()-query_start)),0)) AS maxx }.
+ qq{FROM pg_stat_activity WHERE current_query = '<IDLE> in transaction'$USERWHERECLAUSE GROUP BY 1};
+
+ my $info = run_command($SQL, { emptyok => 1 } );
+
+ my $found = 0;
+ for $db (@{$info->{db}}) {
+ my $max = -1;
+ for my $r (@{$db->{slurp}}) {
+ $found++;
+ my ($dbname,$current) = ($r->{datname}, int $r->{maxx});
+ next if skip_item($dbname);
+ $max = $current if $current > $max;
+ }
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $max < 0 ? 0 : $max;
+ next;
+ }
+ $db->{perf} .= msg('maxtime', $max);
+ if ($max < 0) {
+ add_unknown msg('txnidle-none');
+ next;
+ }
+
+ my $msg = msg('txnidle-msg', $max);
+ if (length $critical and $max >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $max >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+
+ ## If no results, let's be paranoid and check their settings
+ if (!$found) {
+ if ($USERWHERECLAUSE) {
+ add_ok msg('no-match-user');
+ }
+ verify_version();
+ }
+
+ return;
} ## end of check_txn_idle
sub check_txn_time {
- ## Check the length of running transactions
- ## Supports: Nagios, MRTG
- ## It makes no sense to run this more than once on the same cluster
- ## Warning and critical are time limits - defaults to seconds
- ## Valid units: s[econd], m[inute], h[our], d[ay]
- ## All above may be written as plural as well (e.g. "2 hours")
- ## Can also ignore databases with exclude and limit with include
- ## Limit to a specific user with the includeuser option
- ## Exclude users with the excludeuser option
-
- my ($warning, $critical) = validate_range
- ({
- type => 'time',
- });
-
- $SQL = qq{
+ ## Check the length of running transactions
+ ## Supports: Nagios, MRTG
+ ## It makes no sense to run this more than once on the same cluster
+ ## Warning and critical are time limits - defaults to seconds
+ ## Valid units: s[econd], m[inute], h[our], d[ay]
+ ## All above may be written as plural as well (e.g. "2 hours")
+ ## Can also ignore databases with exclude and limit with include
+ ## Limit to a specific user with the includeuser option
+ ## Exclude users with the excludeuser option
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'time',
+ });
+
+ $SQL = qq{
SELECT
client_addr,
client_port,
WHERE xact_start IS NOT NULL $USERWHERECLAUSE
};
- my $info = run_command($SQL, { regex => qr{\d+ \|\s+\s+}, emptyok => 1 } );
-
- $db = $info->{db}[0];
- my $slurp = $db->{slurp};
-
- if (! exists $db->{ok}) {
- ndie msg('txntime-fail');
- }
-
- if ($slurp !~ /\w/ and $USERWHERECLAUSE) {
- $stats{$db->{dbname}} = 0;
- add_ok msg('no-match-user');
- return;
- }
-
- ## Default values for information gathered
- my ($client_addr, $client_port, $procpid, $username, $maxtime, $maxdb) = ('0.0.0.0', 0, '?', 0, 0, '?');
-
- ## Read in and parse the psql output
- for my $r (@{$db->{slurp}}) {
- my ($add,$port,$pid,$time,$dbname,$user) = @$r{qw/ client_addr client_port procpid username maxtime maxdb /};
- next if skip_item($dbname);
-
- if ($time >= $maxtime) {
- $maxtime = $time;
- $maxdb = $dbname;
- $client_addr = $add;
- $client_port = $port;
- $procpid = $pid;
- $username = $user;
- }
- }
-
- ## Use of skip_item means we may have no matches
- if ($maxdb eq '?') {
- if ($USERWHERECLAUSE) { ## needed?
- add_unknown msg('tttt-nomatch');
- }
- else {
- add_ok msg('txntime-none');
- }
- return;
- }
-
- ## Details on who the offender was
- my $whodunit = sprintf q{%s:%s %s:%s%s%s %s:%s},
- msg('database'),
- $maxdb,
- msg('PID'),
- $procpid,
- $client_port < 1 ? '' : (sprintf ' %s:%s', msg('port'), $client_port),
- $client_addr eq '' ? '' : (sprintf ' %s:%s', msg('address'), $client_addr),
- msg('username'),
- $username;
-
- $MRTG and do_mrtg({one => $maxtime, msg => $whodunit});
-
- $db->{perf} .= sprintf q{'%s'=%s;%s;%s},
- $whodunit,
- $maxtime,
- $warning,
- $critical;
-
- my $msg = sprintf '%s (%s)', msg('qtime-msg', $maxtime), $whodunit;
-
- if (length $critical and $maxtime >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $maxtime >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
-
- return;
+ my $info = run_command($SQL, { regex => qr{\d+ \|\s+\s+}, emptyok => 1 } );
+
+ $db = $info->{db}[0];
+ my $slurp = $db->{slurp};
+
+ if (! exists $db->{ok}) {
+ ndie msg('txntime-fail');
+ }
+
+ if ($slurp !~ /\w/ and $USERWHERECLAUSE) {
+ $stats{$db->{dbname}} = 0;
+ add_ok msg('no-match-user');
+ return;
+ }
+
+ ## Default values for information gathered
+ my ($client_addr, $client_port, $procpid, $username, $maxtime, $maxdb) = ('0.0.0.0', 0, '?', 0, 0, '?');
+
+ ## Read in and parse the psql output
+ for my $r (@{$db->{slurp}}) {
+ my ($add,$port,$pid,$time,$dbname,$user) = @$r{qw/ client_addr client_port procpid username maxtime maxdb /};
+ next if skip_item($dbname);
+
+ if ($time >= $maxtime) {
+ $maxtime = $time;
+ $maxdb = $dbname;
+ $client_addr = $add;
+ $client_port = $port;
+ $procpid = $pid;
+ $username = $user;
+ }
+ }
+
+ ## Use of skip_item means we may have no matches
+ if ($maxdb eq '?') {
+ if ($USERWHERECLAUSE) { ## needed?
+ add_unknown msg('tttt-nomatch');
+ }
+ else {
+ add_ok msg('txntime-none');
+ }
+ return;
+ }
+
+ ## Details on who the offender was
+ my $whodunit = sprintf q{%s:%s %s:%s%s%s %s:%s},
+ msg('database'),
+ $maxdb,
+ msg('PID'),
+ $procpid,
+ $client_port < 1 ? '' : (sprintf ' %s:%s', msg('port'), $client_port),
+ $client_addr eq '' ? '' : (sprintf ' %s:%s', msg('address'), $client_addr),
+ msg('username'),
+ $username;
+
+ $MRTG and do_mrtg({one => $maxtime, msg => $whodunit});
+
+ $db->{perf} .= sprintf q{'%s'=%s;%s;%s},
+ $whodunit,
+ $maxtime,
+ $warning,
+ $critical;
+
+ my $msg = sprintf '%s (%s)', msg('qtime-msg', $maxtime), $whodunit;
+
+ if (length $critical and $maxtime >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $maxtime >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+
+ return;
} ## end of check_txn_time
sub check_txn_wraparound {
- ## Check how close to transaction wraparound we are on all databases
- ## Supports: Nagios, MRTG
- ## Warning and critical are the number of transactions performed
- ## Thus, anything *over* that number will trip the alert
- ## See: http://www.postgresql.org/docs/current/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
- ## It makes no sense to run this more than once on the same cluster
-
- my ($warning, $critical) = validate_range
- ({
- type => 'positive integer',
- default_warning => 1_300_000_000,
- default_critical => 1_400_000_000,
- });
-
- if ($warning and $warning >= 2_000_000_000) {
- ndie msg('txnwrap-wbig');
- }
- if ($critical and $critical >= 2_000_000_000) {
- ndie msg('txnwrap-cbig');
- }
-
- $SQL = q{SELECT datname, age(datfrozenxid) AS age FROM pg_database WHERE datallowconn ORDER BY 1, 2};
- my $info = run_command($SQL, { regex => qr[\w+\s+\|\s+\d+] } );
-
- my ($mrtgmax,$mrtgmsg) = (0,'?');
- for $db (@{$info->{db}}) {
- my ($max,$msg) = (0,'?');
- for my $r (@{$db->{slurp}}) {
- my ($dbname,$dbtxns) = ($r->{datname},$r->{age});
- $db->{perf} .= " '$dbname'=$dbtxns;";
- $db->{perf} .= $warning if length $warning;
- $db->{perf} .= ';';
- $db->{perf} .= $critical if length $critical;
- $db->{perf} .= ';0;2000000000';
- next SLURP if skip_item($dbname);
- if ($dbtxns > $max) {
- $max = $dbtxns;
- $msg = qq{$dbname: $dbtxns};
- if ($dbtxns > $mrtgmax) {
- $mrtgmax = $dbtxns;
- $mrtgmsg = "DB: $dbname";
- }
- }
- }
- if (length $critical and $max >= $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $max >= $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
- $MRTG and do_mrtg({one => $mrtgmax, msg => $mrtgmsg});
-
- return;
+ ## Check how close to transaction wraparound we are on all databases
+ ## Supports: Nagios, MRTG
+ ## Warning and critical are the number of transactions performed
+ ## Thus, anything *over* that number will trip the alert
+ ## See: http://www.postgresql.org/docs/current/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
+ ## It makes no sense to run this more than once on the same cluster
+
+ my ($warning, $critical) = validate_range
+ ({
+ type => 'positive integer',
+ default_warning => 1_300_000_000,
+ default_critical => 1_400_000_000,
+ });
+
+ if ($warning and $warning >= 2_000_000_000) {
+ ndie msg('txnwrap-wbig');
+ }
+ if ($critical and $critical >= 2_000_000_000) {
+ ndie msg('txnwrap-cbig');
+ }
+
+ $SQL = q{SELECT datname, age(datfrozenxid) AS age FROM pg_database WHERE datallowconn ORDER BY 1, 2};
+ my $info = run_command($SQL, { regex => qr[\w+\s+\|\s+\d+] } );
+
+ my ($mrtgmax,$mrtgmsg) = (0,'?');
+ for $db (@{$info->{db}}) {
+ my ($max,$msg) = (0,'?');
+ for my $r (@{$db->{slurp}}) {
+ my ($dbname,$dbtxns) = ($r->{datname},$r->{age});
+ $db->{perf} .= " '$dbname'=$dbtxns;";
+ $db->{perf} .= $warning if length $warning;
+ $db->{perf} .= ';';
+ $db->{perf} .= $critical if length $critical;
+ $db->{perf} .= ';0;2000000000';
+ next SLURP if skip_item($dbname);
+ if ($dbtxns > $max) {
+ $max = $dbtxns;
+ $msg = qq{$dbname: $dbtxns};
+ if ($dbtxns > $mrtgmax) {
+ $mrtgmax = $dbtxns;
+ $mrtgmsg = "DB: $dbname";
+ }
+ }
+ }
+ if (length $critical and $max >= $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $max >= $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+ $MRTG and do_mrtg({one => $mrtgmax, msg => $mrtgmsg});
+
+ return;
} ## end of check_txn_wraparound
sub check_version {
- ## Compare version with what we think it should be
- ## Supports: Nagios, MRTG
- ## Warning and critical are the major and minor (e.g. 8.3)
- ## or the major, minor, and revision (e.g. 8.2.4 or even 8.3beta4)
-
- if ($MRTG) {
- if (!exists $opt{mrtg} or $opt{mrtg} !~ /^\d+\.\d+/) {
- ndie msg('version-badmrtg');
- }
- if ($opt{mrtg} =~ /^\d+\.\d+$/) {
- $opt{critical} = $opt{mrtg};
- }
- else {
- $opt{warning} = $opt{mrtg};
- }
- }
-
- my ($warning, $critical) = validate_range({type => 'version', forcemrtg => 1});
-
- my ($warnfull, $critfull) = (($warning =~ /^\d+\.\d+$/ ? 0 : 1),($critical =~ /^\d+\.\d+$/ ? 0 : 1));
-
- my $info = run_command('SELECT version() AS version');
-
- for $db (@{$info->{db}}) {
- my $row = $db->{slurp}[0];
- if ($row->{version} !~ /PostgreSQL ((\d+\.\d+)(\w+|\.\d+))/o) {
- add_unknown msg('invalid-query', $row->{version});
- next;
- }
- my ($full,$version,$revision) = ($1,$2,$3||'?');
- $revision =~ s/^\.//;
-
- my $ok = 1;
-
- if (length $critical) {
- if (($critfull and $critical ne $full)
- or (!$critfull and $critical ne $version)) {
- $MRTG and do_mrtg({one => 0, msg => $full});
- add_critical msg('version-fail', $full, $critical);
- $ok = 0;
- }
- }
- elsif (length $warning) {
- if (($warnfull and $warning ne $full)
- or (!$warnfull and $warning ne $version)) {
- $MRTG and do_mrtg({one => 0, msg => $full});
- add_warning msg('version-fail', $full, $warning);
- $ok = 0;
- }
- }
- if ($ok) {
- $MRTG and do_mrtg({one => 1, msg => $full});
- add_ok msg('version-ok', $full);
- }
- }
-
- return;
+ ## Compare version with what we think it should be
+ ## Supports: Nagios, MRTG
+ ## Warning and critical are the major and minor (e.g. 8.3)
+ ## or the major, minor, and revision (e.g. 8.2.4 or even 8.3beta4)
+
+ if ($MRTG) {
+ if (!exists $opt{mrtg} or $opt{mrtg} !~ /^\d+\.\d+/) {
+ ndie msg('version-badmrtg');
+ }
+ if ($opt{mrtg} =~ /^\d+\.\d+$/) {
+ $opt{critical} = $opt{mrtg};
+ }
+ else {
+ $opt{warning} = $opt{mrtg};
+ }
+ }
+
+ my ($warning, $critical) = validate_range({type => 'version', forcemrtg => 1});
+
+ my ($warnfull, $critfull) = (($warning =~ /^\d+\.\d+$/ ? 0 : 1),($critical =~ /^\d+\.\d+$/ ? 0 : 1));
+
+ my $info = run_command('SELECT version() AS version');
+
+ for $db (@{$info->{db}}) {
+ my $row = $db->{slurp}[0];
+ if ($row->{version} !~ /PostgreSQL ((\d+\.\d+)(\w+|\.\d+))/o) {
+ add_unknown msg('invalid-query', $row->{version});
+ next;
+ }
+ my ($full,$version,$revision) = ($1,$2,$3||'?');
+ $revision =~ s/^\.//;
+
+ my $ok = 1;
+
+ if (length $critical) {
+ if (($critfull and $critical ne $full)
+ or (!$critfull and $critical ne $version)) {
+ $MRTG and do_mrtg({one => 0, msg => $full});
+ add_critical msg('version-fail', $full, $critical);
+ $ok = 0;
+ }
+ }
+ elsif (length $warning) {
+ if (($warnfull and $warning ne $full)
+ or (!$warnfull and $warning ne $version)) {
+ $MRTG and do_mrtg({one => 0, msg => $full});
+ add_warning msg('version-fail', $full, $warning);
+ $ok = 0;
+ }
+ }
+ if ($ok) {
+ $MRTG and do_mrtg({one => 1, msg => $full});
+ add_ok msg('version-ok', $full);
+ }
+ }
+
+ return;
} ## end of check_version
sub check_wal_files {
- ## Check on the number of WAL files in use
- ## Supports: Nagios, MRTG
- ## Must run as a superuser
- ## Critical and warning are the number of files
- ## Example: --critical=40
-
- my ($warning, $critical) = validate_range({type => 'integer', leastone => 1});
-
- ## Figure out where the pg_xlog directory is
- $SQL = q{SELECT count(*) AS count FROM pg_ls_dir('pg_xlog') WHERE pg_ls_dir ~ E'^[0-9A-F]{24}$'}; ## no critic (RequireInterpolationOfMetachars)
-
- my $info = run_command($SQL, {regex => qr[\d] });
-
- my $found = 0;
- for $db (@{$info->{db}}) {
- my $r = $db->{slurp}[0];
- my $numfiles = $r->{count};
- if ($MRTG) {
- $stats{$db->{dbname}} = $numfiles;
- $statsmsg{$db->{dbname}} = '';
- next;
- }
- my $msg = qq{$numfiles};
- $db->{perf} .= " '$db->{host}'=$numfiles;$warning;$critical";
- if (length $critical and $numfiles > $critical) {
- add_critical $msg;
- }
- elsif (length $warning and $numfiles > $warning) {
- add_warning $msg;
- }
- else {
- add_ok $msg;
- }
- }
-
- return;
+ ## Check on the number of WAL files in use
+ ## Supports: Nagios, MRTG
+ ## Must run as a superuser
+ ## Critical and warning are the number of files
+ ## Example: --critical=40
+
+ my ($warning, $critical) = validate_range({type => 'integer', leastone => 1});
+
+ ## Figure out where the pg_xlog directory is
+ $SQL = q{SELECT count(*) AS count FROM pg_ls_dir('pg_xlog') WHERE pg_ls_dir ~ E'^[0-9A-F]{24}$'}; ## no critic (RequireInterpolationOfMetachars)
+
+ my $info = run_command($SQL, {regex => qr[\d] });
+
+ my $found = 0;
+ for $db (@{$info->{db}}) {
+ my $r = $db->{slurp}[0];
+ my $numfiles = $r->{count};
+ if ($MRTG) {
+ $stats{$db->{dbname}} = $numfiles;
+ $statsmsg{$db->{dbname}} = '';
+ next;
+ }
+ my $msg = qq{$numfiles};
+ $db->{perf} .= " '$db->{host}'=$numfiles;$warning;$critical";
+ if (length $critical and $numfiles > $critical) {
+ add_critical $msg;
+ }
+ elsif (length $warning and $numfiles > $warning) {
+ add_warning $msg;
+ }
+ else {
+ add_ok $msg;
+ }
+ }
+
+ return;
} ## end of check_wal_files