From 440451a6a804e55e5b6784d92d0d9dbaa5ec5c41 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Mon, 3 Jul 2023 15:43:06 +0200 Subject: [PATCH 01/14] check for tables in part_config that are not partitioned tables --- check_postgres.pl | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index b2dd92c..4a85c12 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -204,7 +204,7 @@ package check_postgres; 'opt-psql-nover' => q{Could not determine psql version}, 'opt-psql-restrict' => q{Cannot use the --PGBINDIR or --PSQL option when NO_PSQL_OPTION is on}, 'partman-premake-ok' => q{All premade partitions are present}, - 'partman-conf-tbl' => q{misconfigured in partman.part_config}, + 'partman-conf-tbl' => q{misconfigured in partman.part_config or not a partitioned table}, 'partman-conf-mis' => q{missing table in partman.part_config}, 'pgagent-jobs-ok' => q{No failed jobs}, 'pgbouncer-pool' => q{Pool=$1 $2=$3}, @@ -6721,9 +6721,16 @@ sub check_partman_premake { partition_interval, EXTRACT(EPOCH FROM retention::interval) / EXTRACT(EPOCH FROM partition_interval::interval) AS configured_partitions FROM - partman.part_config) p -WHERE - configured_partitions < 1; + partman.part_config + JOIN + pg_class c + ON + c.relnamespace::regnamespace || '.' || c.relname = parent_table + WHERE + 4<1 + OR + c.relkind='r' +) p; }; $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); @@ -6737,7 +6744,8 @@ sub check_partman_premake { $found = 2; $msg = "$dbname=$parent_table " . msg('partman-conf-tbl'); - push @warn => $msg; + # we consider this as critical, because even run_maintenance_proc fails at all at least in some of these cases. + push @crit => $msg; }; }; From 1ae05bc1e1062b63995149c22bdcc8b2b0450344 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Tue, 8 Oct 2024 12:38:28 +0200 Subject: [PATCH 02/14] more fail-save date detection (for 5-digit years) --- check_postgres.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index 4a85c12..b1788fa 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -3435,7 +3435,7 @@ sub setup_target_databases { my $got_multiple = 0; for my $v (keys %$conn) { next if $v eq 'dbpass' or ! defined $opt{$v}[0]; - my $num = $opt{$v}->@*; + my $num = 0; # $opt{$v}->@*; if ($num > 1 or $opt{$v}[0] =~ /,/) { $got_multiple = 1; last; @@ -6760,7 +6760,7 @@ sub check_partman_premake { SELECT parent_table, date FROM ( SELECT i.inhparent::regclass as parent_table, - substring(pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid)::text FROM '%TO __#"_{10}#"%' FOR '#') as date, + substring(pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid)::text similar '%TO __#"[0-9]+-[0-9]{2}-[0-9]{2}#"%' escape '#') as date rank() OVER (PARTITION BY i.inhparent ORDER BY pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) DESC) FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid From 398ba7cd7b5ab84acbb51336e045888d14acf12b Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Tue, 8 Oct 2024 12:42:16 +0200 Subject: [PATCH 03/14] more fail-save date detection (for 5-digit years) --- check_postgres.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/check_postgres.pl b/check_postgres.pl index 66383bb..195d6a2 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -3435,7 +3435,7 @@ sub setup_target_databases { my $got_multiple = 0; for my $v (keys %$conn) { next if $v eq 'dbpass' or ! defined $opt{$v}[0]; - my $num = 0; # $opt{$v}->@*; + my $num = $opt{$v}->@*; if ($num > 1 or $opt{$v}[0] =~ /,/) { $got_multiple = 1; last; From beffd809ecf7390c5dcc705ca7b1ed9fe3455e5b Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Tue, 8 Oct 2024 13:04:25 +0200 Subject: [PATCH 04/14] typo --- check_postgres.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/check_postgres.pl b/check_postgres.pl index 195d6a2..8fbb06e 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -6760,7 +6760,7 @@ sub check_partman_premake { SELECT parent_table, date FROM ( SELECT i.inhparent::regclass as parent_table, - substring(pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid)::text similar '%TO __#"[0-9]+-[0-9]{2}-[0-9]{2}#"%' escape '#') as date + substring(pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid)::text similar '%TO __#"[0-9]+-[0-9]{2}-[0-9]{2}#"%' escape '#') as date, rank() OVER (PARTITION BY i.inhparent ORDER BY pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) DESC) FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid From 2c61a7b80ed177c9c7d8f473c2383b67bdc2e733 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Tue, 8 Oct 2024 14:32:14 +0200 Subject: [PATCH 05/14] check if the number of partitions is at least the number of configured premakes --- check_postgres.pl | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/check_postgres.pl b/check_postgres.pl index 8fbb06e..851a301 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -204,6 +204,7 @@ package check_postgres; 'opt-psql-nover' => q{Could not determine psql version}, 'opt-psql-restrict' => q{Cannot use the --PGBINDIR or --PSQL option when NO_PSQL_OPTION is on}, 'partman-premake-ok' => q{All premade partitions are present}, + 'partman-premake' => q{Not all premade partitions are present}, 'partman-conf-tbl' => q{misconfigured in partman.part_config or not a partitioned table}, 'partman-conf-mis' => q{missing table in partman.part_config}, 'pgagent-jobs-ok' => q{No failed jobs}, @@ -6749,6 +6750,41 @@ sub check_partman_premake { }; }; + # check, if the number of partitions is at least equal to premake + # retention is not taken into account + + $SQL = q{ +SELECT + current_database() as database, + a.parent_table, + a.count as partitions, + p.premake +FROM ( SELECT + inhparent::regclass::text as parent_table, + count(*) + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhrelid +WHERE c.relkind = 'r' + AND pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) != 'DEFAULT' +GROUP BY inhparent) a +JOIN partman.part_config p +ON p.parent_table=a.parent_table +WHERE a.count <= (p.premake ) + }; + + $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); + + for $db (@{$info->{db}}) { + my ($maxage,$maxdb) = (0,''); ## used by MRTG only + ROW: for my $r (@{$db->{slurp}}) { + my ($dbname,$parent_table,$partitions,$premake) = ($r->{database},$r->{parent_table},$r->{partitions},$r->{premake}); + $found = 1 if ! $found; + next ROW if skip_item($dbname); + $found = 2; + $msg = "$dbname=$parent_table " . msg('partman-premake') . " premake: $premake num partitions: $partitions"; + push @crit => $msg; + }; + }; $SQL = q{ SELECT From 6908d2123c4a0f2d04255872307f08ee8ffde369 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Tue, 8 Oct 2024 17:30:52 +0200 Subject: [PATCH 06/14] fix partition calculation --- check_postgres.pl | 73 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index 851a301..baa9856 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -6716,22 +6716,22 @@ sub check_partman_premake { current_database() as database, parent_table FROM ( - SELECT - parent_table, - retention, - partition_interval, - EXTRACT(EPOCH FROM retention::interval) / EXTRACT(EPOCH FROM partition_interval::interval) AS configured_partitions +SELECT + p.parent_table, + p.retention, + p.premake, + p.partition_interval, + (EXTRACT(EPOCH FROM p.retention::interval) / EXTRACT(EPOCH FROM p.partition_interval::interval))::int AS configured_partitions, +count(*) as num_partitions FROM - partman.part_config + partman.part_config p JOIN pg_class c ON - c.relnamespace::regnamespace || '.' || c.relname = parent_table - WHERE - 4<1 - OR - c.relkind='r' -) p; + c.relnamespace::regnamespace || '.' || c.relname = p.parent_table JOIN pg_inherits i + ON c.oid = i.inhparent + group by p.parent_table ) a +WHERE configured_partitions=0 }; $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); @@ -6744,13 +6744,59 @@ sub check_partman_premake { next ROW if skip_item($dbname); $found = 2; - $msg = "$dbname=$parent_table " . msg('partman-conf-tbl'); + $msg = "$dbname=$parent_table " . msg('partman-conf-tbl') . " configured_partitions=0"; # we consider this as critical, because even run_maintenance_proc fails at all at least in some of these cases. push @crit => $msg; }; }; + # check, if the number of partitions is at least configured_partitions + premake + # warning, if not, due to new partition sets + + $SQL = q{ + SELECT current_database() as database, + a.parent_table, + a.configured_partitions, + a.num_partitions, + a.premake + FROM ( + SELECT + p.parent_table, + p.retention, + p.premake, + p.partition_interval, + (EXTRACT(EPOCH FROM p.retention::interval) / EXTRACT(EPOCH FROM p.partition_interval::interval))::int AS configured_partitions, + count(*) as num_partitions + FROM + partman.part_config p + JOIN + pg_class c + ON + c.relnamespace::regnamespace || '.' || c.relname = p.parent_table JOIN pg_inherits i + ON c.oid = i.inhparent + group by p.parent_table ) a +WHERE num_partitions < configured_partitions+premake +-- configured_partitions=0 is handled by another check +AND configured_partitions>0 + }; + + $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); + + for $db (@{$info->{db}}) { + my ($maxage,$maxdb) = (0,''); ## used by MRTG only + ROW: for my $r (@{$db->{slurp}}) { + my ($dbname,$parent_table,$cpartitions,$npartitions,$premake) = ($r->{database},$r->{parent_table},$r->{configured_partitions},$r->{num_partitions},$r->{premake}); + $found = 1 if ! $found; + next ROW if skip_item($dbname); + $found = 2; + $msg = "$dbname=$parent_table " . msg('partman-premake') . " num partitions: $npartitions configured partitions: $cpartitions premake: $premake"; + push @warn => $msg; + push @crit => $msg if (@crit); + }; + }; + # check, if the number of partitions is at least equal to premake + # critical, if not # retention is not taken into account $SQL = q{ @@ -6786,6 +6832,7 @@ sub check_partman_premake { }; }; + $SQL = q{ SELECT current_database() as database, From 40cbd0442655224464af6b3f9d8405f2b6c302c3 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Tue, 8 Oct 2024 18:22:29 +0200 Subject: [PATCH 07/14] reorder tasks --- check_postgres.pl | 121 +++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index baa9856..0bf7cdb 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -6750,51 +6750,6 @@ sub check_partman_premake { }; }; - # check, if the number of partitions is at least configured_partitions + premake - # warning, if not, due to new partition sets - - $SQL = q{ - SELECT current_database() as database, - a.parent_table, - a.configured_partitions, - a.num_partitions, - a.premake - FROM ( - SELECT - p.parent_table, - p.retention, - p.premake, - p.partition_interval, - (EXTRACT(EPOCH FROM p.retention::interval) / EXTRACT(EPOCH FROM p.partition_interval::interval))::int AS configured_partitions, - count(*) as num_partitions - FROM - partman.part_config p - JOIN - pg_class c - ON - c.relnamespace::regnamespace || '.' || c.relname = p.parent_table JOIN pg_inherits i - ON c.oid = i.inhparent - group by p.parent_table ) a -WHERE num_partitions < configured_partitions+premake --- configured_partitions=0 is handled by another check -AND configured_partitions>0 - }; - - $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); - - for $db (@{$info->{db}}) { - my ($maxage,$maxdb) = (0,''); ## used by MRTG only - ROW: for my $r (@{$db->{slurp}}) { - my ($dbname,$parent_table,$cpartitions,$npartitions,$premake) = ($r->{database},$r->{parent_table},$r->{configured_partitions},$r->{num_partitions},$r->{premake}); - $found = 1 if ! $found; - next ROW if skip_item($dbname); - $found = 2; - $msg = "$dbname=$parent_table " . msg('partman-premake') . " num partitions: $npartitions configured partitions: $cpartitions premake: $premake"; - push @warn => $msg; - push @crit => $msg if (@crit); - }; - }; - # check, if the number of partitions is at least equal to premake # critical, if not # retention is not taken into account @@ -6891,21 +6846,67 @@ sub check_partman_premake { push @ok => $msg; } } - if (0 == $found) { - add_ok msg('partman-premake-ok'); - } - 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; - } + } + + # check, if the number of partitions is at least configured_partitions + premake + # warning, if not, due to new partition sets + + $SQL = q{ + SELECT current_database() as database, + a.parent_table, + a.configured_partitions, + a.num_partitions, + a.premake + FROM ( + SELECT + p.parent_table, + p.retention, + p.premake, + p.partition_interval, + (EXTRACT(EPOCH FROM p.retention::interval) / EXTRACT(EPOCH FROM p.partition_interval::interval))::int AS configured_partitions, + count(*) as num_partitions + FROM + partman.part_config p + JOIN + pg_class c + ON + c.relnamespace::regnamespace || '.' || c.relname = p.parent_table JOIN pg_inherits i + ON c.oid = i.inhparent + group by p.parent_table ) a +WHERE num_partitions < configured_partitions+premake +-- configured_partitions=0 is handled by another check +AND configured_partitions>0 + }; + + $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); + + for $db (@{$info->{db}}) { + my ($maxage,$maxdb) = (0,''); ## used by MRTG only + ROW: for my $r (@{$db->{slurp}}) { + my ($dbname,$parent_table,$cpartitions,$npartitions,$premake) = ($r->{database},$r->{parent_table},$r->{configured_partitions},$r->{num_partitions},$r->{premake}); + $found = 1 if ! $found; + next ROW if skip_item($dbname); + $found = 2; + $msg = "$dbname=$parent_table " . msg('partman-premake') . " num partitions: $npartitions configured partitions: $cpartitions premake: $premake"; + push @warn => $msg; + push @crit => $msg if (@crit); # we want to see this, if there is anything else critical + }; + }; + + if (0 == $found) { + add_ok msg('partman-premake-ok'); + } + 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; From c63e83adefaec8915d152dd33dc72abf4bc37b7f Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Wed, 9 Oct 2024 11:17:27 +0200 Subject: [PATCH 08/14] detect gaps in premade partitions --- check_postgres.pl | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index 0bf7cdb..444d458 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -6787,7 +6787,8 @@ sub check_partman_premake { }; }; - + # the highest partition boundarie must not be lower than the configured premake + # if the highest partition boundarie is higher than the configured premake, this is a gap indicator $SQL = q{ SELECT current_database() as database, @@ -6818,7 +6819,11 @@ sub check_partman_premake { ON a.parent_table::text = b.parent_table::text WHERE - b.date - a.date::date > 0 + -- the highest partition boundarie must not be lower than the configured premake + b.date > a.date::date +OR + -- if the highest partition boundarie is higher than the configured premake, this is a gap indicator + a.date::date - b.date > 100 ORDER BY 3, 2 DESC }; @@ -6832,14 +6837,19 @@ sub check_partman_premake { next ROW if skip_item($dbname); $found = 2; - $msg = "$dbname=$parent_table ($missing_days)"; - print "$msg"; + if ( $missing_days < 0 ){ + $msg = "$dbname=$parent_table ($missing_days) gap "; + + } else { + $msg = "$dbname=$parent_table ($missing_days) missing days "; + } + #print "$msg"; $db->{perf} .= sprintf ' %s=%sd;%s;%s', perfname($dbname), $missing_days, $warning, $critical; - if (length $critical and $missing_days >= $critical) { + if (length $critical and abs($missing_days) >= $critical) { push @crit => $msg; } - elsif (length $warning and $missing_days >= $warning) { + elsif (length $warning and abs($missing_days) >= $warning) { push @warn => $msg; } else { From 7213450152b98bd0cd8e6b59564bfb5add0bc42a Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Wed, 9 Oct 2024 14:35:11 +0200 Subject: [PATCH 09/14] count premade partitions more precise --- check_postgres.pl | 77 ++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index 444d458..1c3fbf6 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -6858,48 +6858,63 @@ sub check_partman_premake { } } - # check, if the number of partitions is at least configured_partitions + premake - # warning, if not, due to new partition sets + # check, if the number of premade partitions from now is not lower than the premake setting $SQL = q{ - SELECT current_database() as database, - a.parent_table, - a.configured_partitions, - a.num_partitions, - a.premake - FROM ( - SELECT - p.parent_table, - p.retention, - p.premake, - p.partition_interval, - (EXTRACT(EPOCH FROM p.retention::interval) / EXTRACT(EPOCH FROM p.partition_interval::interval))::int AS configured_partitions, - count(*) as num_partitions - FROM - partman.part_config p - JOIN - pg_class c - ON - c.relnamespace::regnamespace || '.' || c.relname = p.parent_table JOIN pg_inherits i - ON c.oid = i.inhparent - group by p.parent_table ) a -WHERE num_partitions < configured_partitions+premake --- configured_partitions=0 is handled by another check -AND configured_partitions>0 - }; + SELECT + current_database() as database, + a.parent_table, + b.premake, + b.premake-count(*) as missing_part +FROM +( +SELECT parent_table, date, prank +FROM ( SELECT + i.inhparent::regclass as parent_table, + substring(pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid)::text similar '%TO __#"[0-9]+-[0-9]{2}-[0-9]{2}#"%' escape '#') as date, + rank() OVER (PARTITION BY i.inhparent ORDER BY pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) DESC) as prank + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhrelid +WHERE c.relkind = 'r' + AND pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) != 'DEFAULT') p +) a +JOIN +( +SELECT + parent_table, + (now() + premake * partition_interval::interval)::date, premake +FROM + partman.part_config +) b +ON + a.parent_table::text = b.parent_table::text +WHERE a.prank<=b.premake +AND a.date::date > now() +GROUP BY database, a.parent_table, b.premake +HAVING count(*) < b.premake; + }; $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); for $db (@{$info->{db}}) { my ($maxage,$maxdb) = (0,''); ## used by MRTG only ROW: for my $r (@{$db->{slurp}}) { - my ($dbname,$parent_table,$cpartitions,$npartitions,$premake) = ($r->{database},$r->{parent_table},$r->{configured_partitions},$r->{num_partitions},$r->{premake}); + my ($dbname,$parent_table,$missing_part,$premake) = ($r->{database},$r->{parent_table},$r->{missing_part},$r->{premake}); $found = 1 if ! $found; next ROW if skip_item($dbname); $found = 2; - $msg = "$dbname=$parent_table " . msg('partman-premake') . " num partitions: $npartitions configured partitions: $cpartitions premake: $premake"; - push @warn => $msg; - push @crit => $msg if (@crit); # we want to see this, if there is anything else critical + $msg = "$dbname=$parent_table " . msg('partman-premake') . " missing partitions: $missing_part premake: $premake "; + + if (length $critical and abs($missing_part) >= $critical) { + push @crit => $msg; + } + elsif (length $warning and abs($missing_part) >= $warning) { + push @warn => $msg; + push @crit => $msg if (@crit); # we want to see this, if there is anything else critical + } + else { + push @ok => $msg; + }; }; }; From a1df533d2196b0f53018485b4b29a7b42397cb3c Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Mon, 14 Oct 2024 14:15:17 +0200 Subject: [PATCH 10/14] fix --alldb handling --- check_postgres.pl | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index 1c3fbf6..aaa9e43 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -3066,7 +3066,7 @@ sub run_command { next; } ## Likewise if we have specified "target" database info and this is not our choice - if ($arg->{target} and $arg->{target} != $db) { + if ($arg->{target} and $arg->{target} ne $db) { next; } @@ -6672,9 +6672,27 @@ sub check_partman_premake { default_critical => '3', }); + # check, if partman is installed in this DB + my $SQL = q{ + select current_database(), true from pg_extension where extname='pg_partman'; + }; + + my $info = run_command($SQL, {emptyok => 1 }); + my @localtargetlist; + for my $db (@{$info->{db}}) { + if ( exists $db->{slurp}[0]{bool} ) { + # push db to a local targetlist, otherwise checks fail due to missing part_conf + push @localtargetlist => $db->{slurp}[0]{current_database}; + } + } + if (!@localtargetlist) { + add_unknown msg('no-match-db'); + return 1; + } + # check missing Config for range partitioned tables - my $SQL = q{ + $SQL = q{ SELECT current_database() AS database, c.relnamespace::regnamespace || '.' || c.relname AS parent_table @@ -6693,7 +6711,8 @@ sub check_partman_premake { parent_table = c.relnamespace::regnamespace || '.' || c.relname); }; - my $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); + my (@crit,@warn,@ok); for $db (@{$info->{db}}) { @@ -6734,7 +6753,7 @@ sub check_partman_premake { WHERE configured_partitions=0 }; - $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); for $db (@{$info->{db}}) { my ($maxage,$maxdb) = (0,''); ## used by MRTG only @@ -6773,7 +6792,7 @@ sub check_partman_premake { WHERE a.count <= (p.premake ) }; - $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); for $db (@{$info->{db}}) { my ($maxage,$maxdb) = (0,''); ## used by MRTG only @@ -6827,7 +6846,7 @@ sub check_partman_premake { ORDER BY 3, 2 DESC }; - $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); for $db (@{$info->{db}}) { my ($maxage,$maxdb) = (0,''); ## used by MRTG only @@ -6894,7 +6913,7 @@ sub check_partman_premake { HAVING count(*) < b.premake; }; - $info = run_command($SQL, {regex => qr[\w+], emptyok => 1 } ); + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); for $db (@{$info->{db}}) { my ($maxage,$maxdb) = (0,''); ## used by MRTG only @@ -6970,7 +6989,6 @@ sub check_pgbouncer_checksum { next if skip_item($key); $newstring .= "$r->{key} = $r->{value}\n"; } - if (! length $newstring) { add_unknown msg('no-match-set'); } From 1ddf7cf8afe80e4be707a493ab8832906ee5e368 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Mon, 14 Oct 2024 14:27:19 +0200 Subject: [PATCH 11/14] fix --alldb handling --- check_postgres.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/check_postgres.pl b/check_postgres.pl index aaa9e43..de18190 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -6674,7 +6674,7 @@ sub check_partman_premake { # check, if partman is installed in this DB my $SQL = q{ - select current_database(), true from pg_extension where extname='pg_partman'; + select current_database(), true as bool from pg_extension where extname='pg_partman'; }; my $info = run_command($SQL, {emptyok => 1 }); From 2b4b39c2e9d25f2df7f481a115cd617d52faaee7 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Mon, 14 Oct 2024 16:34:37 +0200 Subject: [PATCH 12/14] fix --alldb handling --- check_postgres.pl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index de18190..4f08d0d 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -6674,15 +6674,15 @@ sub check_partman_premake { # check, if partman is installed in this DB my $SQL = q{ - select current_database(), true as bool from pg_extension where extname='pg_partman'; + select current_database(), (SELECT true as bool from pg_extension where extname='pg_partman'); }; my $info = run_command($SQL, {emptyok => 1 }); my @localtargetlist; for my $db (@{$info->{db}}) { - if ( exists $db->{slurp}[0]{bool} ) { + if ( $db->{slurp}[0]{bool} eq 't' ) { # push db to a local targetlist, otherwise checks fail due to missing part_conf - push @localtargetlist => $db->{slurp}[0]{current_database}; + push @localtargetlist => $db; } } if (!@localtargetlist) { @@ -6712,9 +6712,7 @@ sub check_partman_premake { }; $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); - my (@crit,@warn,@ok); - for $db (@{$info->{db}}) { my ($maxage,$maxdb) = (0,''); ## used by MRTG only ROW: for my $r (@{$db->{slurp}}) { @@ -6729,7 +6727,6 @@ sub check_partman_premake { }; # check Config Errors - $SQL = q{ SELECT current_database() as database, From 92312c337a40cbe5aeb3b44048787a3fa2406c09 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Tue, 10 Dec 2024 13:08:02 +0100 Subject: [PATCH 13/14] handle postgres < 14 and calculate dates more precisely --- check_postgres.pl | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/check_postgres.pl b/check_postgres.pl index 4f08d0d..b96ab2c 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -6803,6 +6803,10 @@ sub check_partman_premake { }; }; + # the following checks require postgres version 14+ + my $version = verify_version(); + if ($version >= 14.0) { + # the highest partition boundarie must not be lower than the configured premake # if the highest partition boundarie is higher than the configured premake, this is a gap indicator $SQL = q{ @@ -6828,7 +6832,20 @@ sub check_partman_premake { ( SELECT parent_table, - (now() + premake * partition_interval::interval)::date + CASE + WHEN partition_interval ilike '%year%' THEN + ((extract(year from now())||'-1-1')::timestamp + ((premake +2) * partition_interval::interval))::date + WHEN partition_interval ilike '%mon%' THEN + ((extract(year from now()) || '-' || extract(month from now()) ||'-1')::timestamp + ((premake +1) * partition_interval::interval))::date + WHEN partition_interval = '7 days' THEN + -- interval starts on Monday + ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from (now()::date-(((EXTRACT(DOW FROM now())-1) ||' days')::interval))))::timestamp + ((premake +1) * partition_interval::interval))::date + WHEN partition_interval ilike '%day' THEN + ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from now()))::timestamp + ((premake +1) * partition_interval::interval))::date + WHEN partition_interval ilike '%hour%' THEN + ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from now()) || ' ' || extract(hour from now()) || ':00:00')::timestamp + ((premake +2) * partition_interval::interval))::date + ELSE NULL + END as date FROM partman.part_config ) b @@ -6933,6 +6950,7 @@ sub check_partman_premake { }; }; }; + }; # end of version >= 14.0 if (0 == $found) { add_ok msg('partman-premake-ok'); From 7b7b31948ae8b82bac6343049fe9006b09c78e2f Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Tue, 4 Mar 2025 17:40:40 +0100 Subject: [PATCH 14/14] partman-premake gap detection re-written --- check_postgres.pl | 1305 ++++++++++++++++++++++++++------------------- 1 file changed, 758 insertions(+), 547 deletions(-) diff --git a/check_postgres.pl b/check_postgres.pl index b96ab2c..ba1a2a4 100755 --- a/check_postgres.pl +++ b/check_postgres.pl @@ -1528,7 +1528,7 @@ package check_postgres; n3.nspname AS procschema, p.proname AS procname, pg_get_triggerdef(t.oid) AS triggerdef, ( WITH nums AS (SELECT unnest(tgattr) AS poz) - SELECT string_agg(attname,',') FROM pg_attribute a JOIN nums ON + SELECT string_agg(attname,',') FROM pg_attribute a JOIN nums ON (nums.poz = a.attnum AND tgrelid = a.attrelid) ) AS trigger_columns FROM pg_trigger t @@ -6215,8 +6215,8 @@ sub check_lockwait { my $n = 0; for $db (@{$info->{db}}) { ROW: for my $r (@{$db->{slurp}}) { - my ($dbname,$blocked_pid,$blocked_user,$blocking_pid,$blocking_user,$waited_sec,$blocked_statement) - = ($r->{datname},$r->{blocked_pid}, $r->{blocked_user}, $r->{blocking_pid}, + my ($dbname,$blocked_pid,$blocked_user,$blocking_pid,$blocking_user,$waited_sec,$blocked_statement) + = ($r->{datname},$r->{blocked_pid}, $r->{blocked_user}, $r->{blocking_pid}, $r->{blocking_user},$r->{waited_sec},$r->{blocked_statement}); ## May be forcibly skipping this database via arguments @@ -6689,7 +6689,6 @@ sub check_partman_premake { add_unknown msg('no-match-db'); return 1; } - # check missing Config for range partitioned tables $SQL = q{ @@ -6726,7 +6725,7 @@ sub check_partman_premake { }; }; - # check Config Errors + # check Config Errors in time based partitions $SQL = q{ SELECT current_database() as database, @@ -6746,6 +6745,9 @@ sub check_partman_premake { ON c.relnamespace::regnamespace || '.' || c.relname = p.parent_table JOIN pg_inherits i ON c.oid = i.inhparent + WHERE + -- exclude Integer Intervals, casting them to ::interval fails + p.partition_interval !~ '^[0-9]*$' group by p.parent_table ) a WHERE configured_partitions=0 }; @@ -6769,24 +6771,27 @@ sub check_partman_premake { # check, if the number of partitions is at least equal to premake # critical, if not # retention is not taken into account + # integer ranges might have only 1 Partition after partition_data_proc $SQL = q{ SELECT current_database() as database, - a.parent_table, - a.count as partitions, - p.premake + a.parent_table, + a.count as partitions, + p.premake FROM ( SELECT - inhparent::regclass::text as parent_table, - count(*) + inhparent::regclass::text as parent_table, + count(*) FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid WHERE c.relkind = 'r' - AND pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) != 'DEFAULT' -GROUP BY inhparent) a -JOIN partman.part_config p -ON p.parent_table=a.parent_table -WHERE a.count <= (p.premake ) + AND pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) != 'DEFAULT' +GROUP BY inhparent) a +JOIN partman.part_config p +ON p.parent_table=a.parent_table +WHERE + ( a.count <= p.premake AND p.partition_interval !~ '^[0-9]*$' ) -- time ranges +OR ( a.count < 1 AND p.partition_interval ~ '^[0-9]*$' ) -- integer ranges can have only 1 Partition after partition_data_proc }; $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); @@ -6803,100 +6808,306 @@ sub check_partman_premake { }; }; - # the following checks require postgres version 14+ - my $version = verify_version(); - if ($version >= 14.0) { - - # the highest partition boundarie must not be lower than the configured premake - # if the highest partition boundarie is higher than the configured premake, this is a gap indicator - $SQL = q{ + # check integer/timerange mismatch in Config + $SQL = q{ + SELECT + current_database() AS database, + p.parent_table, + a.attname, + t.typname, + p.partition_interval AS interval +FROM + partman.part_config p + JOIN pg_class c ON p.parent_table = c.relnamespace::regnamespace || '.' || c.relname + JOIN pg_attribute a ON a.attrelid = c.oid + AND a.attname = p.control + JOIN pg_type t ON t.oid = a.atttypid +WHERE (t.typname LIKE 'date%' + OR t.typname LIKE 'time%') +AND p.partition_interval !~ '^\d+ (second|min|minute|hour|day|week|mon|month|year)s?$' +UNION ALL SELECT - current_database() as database, - a.parent_table, - b.date - a.date::date AS missing_days + current_database() AS database, + p.parent_table, + a.attname, + t.typname, + p.partition_interval FROM -( -SELECT parent_table, date -FROM ( SELECT - i.inhparent::regclass as parent_table, - substring(pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid)::text similar '%TO __#"[0-9]+-[0-9]{2}-[0-9]{2}#"%' escape '#') as date, - rank() OVER (PARTITION BY i.inhparent ORDER BY pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) DESC) - FROM pg_inherits i - JOIN pg_class c ON c.oid = i.inhrelid -WHERE c.relkind = 'r' - AND pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) != 'DEFAULT') p -WHERE - p.rank = 1 -) a -JOIN -( + partman.part_config p + JOIN pg_class c ON p.parent_table = c.relnamespace::regnamespace || '.' || c.relname + JOIN pg_attribute a ON a.attrelid = c.oid + AND a.attname = p.control + JOIN pg_type t ON t.oid = a.atttypid +WHERE (t.typname LIKE 'int%' + OR t.typname LIKE 'num%') +AND p.partition_interval !~ '^\d+$' +}; + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); + + for $db (@{$info->{db}}) { + my ($maxage,$maxdb) = (0,''); ## used by MRTG only + ROW: for my $r (@{$db->{slurp}}) { + my ($dbname,$parent_table,$attname,$typname,$interval) = ($r->{database},$r->{parent_table},$r->{attname},$r->{typname},$r->{interval}); + $found = 1 if ! $found; + next ROW if skip_item($dbname); + $found = 2; + $msg = "$dbname=$parent_table " . msg('partman-premake') . " key: $attname type $typname mismatches partition_interval $interval"; + push @crit => $msg; + }; + }; + + + # Integer Range Partitions + # check, if default Partitions are not empty + + $SQL = q{ SELECT - parent_table, - CASE - WHEN partition_interval ilike '%year%' THEN - ((extract(year from now())||'-1-1')::timestamp + ((premake +2) * partition_interval::interval))::date - WHEN partition_interval ilike '%mon%' THEN - ((extract(year from now()) || '-' || extract(month from now()) ||'-1')::timestamp + ((premake +1) * partition_interval::interval))::date - WHEN partition_interval = '7 days' THEN - -- interval starts on Monday - ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from (now()::date-(((EXTRACT(DOW FROM now())-1) ||' days')::interval))))::timestamp + ((premake +1) * partition_interval::interval))::date - WHEN partition_interval ilike '%day' THEN - ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from now()))::timestamp + ((premake +1) * partition_interval::interval))::date - WHEN partition_interval ilike '%hour%' THEN - ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from now()) || ' ' || extract(hour from now()) || ':00:00')::timestamp + ((premake +2) * partition_interval::interval))::date - ELSE NULL - END as date + current_database() AS database, + parent_table FROM partman.part_config -) b -ON - a.parent_table::text = b.parent_table::text WHERE - -- the highest partition boundarie must not be lower than the configured premake - b.date > a.date::date -OR - -- if the highest partition boundarie is higher than the configured premake, this is a gap indicator - a.date::date - b.date > 100 -ORDER BY 3, 2 DESC -}; + partition_interval ~ '^[0-9]*$' + AND pg_relation_size(parent_table || '_default') != 0; + }; $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); for $db (@{$info->{db}}) { my ($maxage,$maxdb) = (0,''); ## used by MRTG only ROW: for my $r (@{$db->{slurp}}) { - my ($dbname,$parent_table,$missing_days) = ($r->{database},$r->{parent_table},$r->{missing_days}); + my ($dbname,$parent_table) = ($r->{database},$r->{parent_table}); $found = 1 if ! $found; next ROW if skip_item($dbname); $found = 2; + $msg = "$dbname=$parent_table " . msg('partman-premake') . " int range default Partition is not empty"; + push @crit => $msg; + }; + }; - if ( $missing_days < 0 ){ - $msg = "$dbname=$parent_table ($missing_days) gap "; + $SQL = q{SELECT substring(extversion from '(\d+)\.') as partman_major_version from pg_extension where extname='pg_partman'}; + my $info2 = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 }); + if (!defined $info2->{db}[0]{slurp}[0]{partman_major_version}) { + ndie msg('die-nosetting', 'partman version'); + } + my $partm_ver = $info2->{db}[0]{slurp}[0]{partman_major_version}; + # the following checks require postgres version 14+ + my $version = verify_version(); + if ($version >= 14.0) { - } else { - $msg = "$dbname=$parent_table ($missing_days) missing days "; - } - #print "$msg"; - $db->{perf} .= sprintf ' %s=%sd;%s;%s', - perfname($dbname), $missing_days, $warning, $critical; - if (length $critical and abs($missing_days) >= $critical) { - push @crit => $msg; - } - elsif (length $warning and abs($missing_days) >= $warning) { - push @warn => $msg; + if ($partm_ver >= 5 ) { + + # calculate missing premake partitions + + # partman expects the $premake highest partitions to be empty + # otherwise premake kicks in + # seems to be version dependent on partman > 5 + # check premake-1 to avoid overlaps + $SQL = q{ + SELECT + current_database() AS database, + inhparent AS parent_table, + count(*) AS missing_parts + FROM ( + SELECT + n.nspname::text AS partition_schemaname, + c.relname::text AS partition_name, + h.inhparent::regclass, + pg_relation_size(c.oid), + row_number() OVER (PARTITION BY inhparent ORDER BY c.relname::text DESC) AS rn, + p.premake + FROM + pg_catalog.pg_inherits h + JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + JOIN partman.part_config p ON p.parent_table = h.inhparent::regclass::text + WHERE + p.partition_interval !~ '^\d+$' + AND pg_catalog.pg_get_expr(c.relpartbound, h.inhrelid) != 'DEFAULT' + ) a + WHERE + a.rn < a.premake - 1 + AND a.pg_relation_size > 0 + GROUP BY + inhparent + }; + + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); + + for $db (@{$info->{db}}) { + my ($maxage,$maxdb) = (0,''); ## used by MRTG only + ROW: for my $r (@{$db->{slurp}}) { + my ($dbname,$parent_table,$missing_parts) = ($r->{database},$r->{parent_table},$r->{missing_parts}); + $found = 1 if ! $found; + next ROW if skip_item($dbname); + $found = 2; + + $msg = "$dbname=$parent_table ($missing_parts) missing premake partitions "; + #print "$msg"; + $db->{perf} .= sprintf ' %s=%sd;%s;%s', + perfname($dbname), $missing_parts, $warning, $critical; + if (length $critical and abs($missing_parts) >= $critical) { + push @crit => $msg; + } + elsif (length $warning and abs($missing_parts) >= $warning) { + push @warn => $msg; + } + else { + push @ok => $msg; + } } - else { - push @ok => $msg; + } + + # end of Partman >= 5 + } else { + # Partman < 5 + # the highest partition boundarie must not be lower than the configured premake + # if the highest partition boundarie is higher than the configured premake, this is a gap indicator + $SQL = q{ + SELECT + current_database() as database, + a.parent_table, + b.date - a.date::date AS missing_days + FROM + ( + SELECT parent_table, date + FROM ( SELECT + i.inhparent::regclass as parent_table, + substring(pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid)::text similar '%TO __#"[0-9]+-[0-9]{2}-[0-9]{2}#"%' escape '#') as date, + rank() OVER (PARTITION BY i.inhparent ORDER BY pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) DESC) + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhrelid + WHERE c.relkind = 'r' + AND pg_catalog.pg_get_expr(c.relpartbound, i.inhrelid) != 'DEFAULT') p + WHERE + p.rank = 1 + ) a + JOIN + ( + SELECT + parent_table, + CASE + WHEN partition_interval ilike '%year%' THEN + ((extract(year from now())||'-1-1')::timestamp + ((premake +1) * partition_interval::interval))::date + WHEN partition_interval ilike '%mon%' THEN + ((extract(year from now()) || '-' || extract(month from now()) ||'-1')::timestamp + ((premake +1) * partition_interval::interval))::date + WHEN partition_interval = '7 days' THEN + -- interval starts on Monday + ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from (now()::date-(((EXTRACT(DOW FROM now())-1) ||' days')::interval))))::timestamp + ((premake +1) * partition_interval::interval))::date + WHEN partition_interval ilike '%day' THEN + ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from now()))::timestamp + ((premake +1) * partition_interval::interval))::date + WHEN partition_interval ilike '%hour%' THEN + ((extract(year from now()) || '-' || extract(month from now()) || '-' || extract(day from now()) || ' ' || extract(hour from now()) || ':00:00')::timestamp + ((premake +2) * partition_interval::interval))::date + ELSE NULL + END as date + FROM + partman.part_config + ) b + ON + a.parent_table::text = b.parent_table::text + WHERE + -- the highest partition boundarie must not be lower than the configured premake + b.date > a.date::date + OR + -- if the highest partition boundarie is higher than the configured premake, this is a gap indicator + a.date::date - b.date > 100 + ORDER BY 3, 2 DESC + }; + + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); + + for $db (@{$info->{db}}) { + my ($maxage,$maxdb) = (0,''); ## used by MRTG only + ROW: for my $r (@{$db->{slurp}}) { + my ($dbname,$parent_table,$missing_days) = ($r->{database},$r->{parent_table},$r->{missing_days}); + $found = 1 if ! $found; + next ROW if skip_item($dbname); + $found = 2; + + if ( $missing_days < 0 ){ + $msg = "$dbname=$parent_table ($missing_days) gap "; + + } else { + $msg = "$dbname=$parent_table ($missing_days) missing days "; + } + #print "$msg"; + $db->{perf} .= sprintf ' %s=%sd;%s;%s', + perfname($dbname), $missing_days, $warning, $critical; + if (length $critical and abs($missing_days) >= $critical) { + push @crit => $msg; + } + elsif (length $warning and abs($missing_days) >= $warning) { + push @warn => $msg; + } + else { + push @ok => $msg; + } } } - } + }; # End of Partman < 5 + + # calculate gaps + $SQL = q{ + with a as ( + SELECT + n.nspname::text AS partition_schemaname, + c.relname::text AS partition_name, + h.inhparent::regclass::text as parent + FROM + pg_catalog.pg_inherits h + JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + JOIN partman.part_config p ON p.parent_table = h.inhparent::regclass::text + WHERE + p.partition_interval !~ '^\d+$' + AND pg_catalog.pg_get_expr(c.relpartbound, h.inhrelid) != 'DEFAULT' + ) + SELECT + current_database() AS database, + b.partition_schemaname || '.' || b.parent as parent_table, count(*) ||' gap(s) total: ' ||sum(child_start_time - lag) as missing_parts + FROM + ( select + a.partition_schemaname, + a.parent, + LAG(child_end_time,1) OVER(PARTITION BY partition_schemaname, parent ORDER BY child_end_time), + child_start_time + FROM + a, + partman.show_partition_info(format('%s', a.partition_schemaname ||'.'|| a.partition_name ), p_parent_table := a.parent) ) b + WHERE + lag != child_start_time + GROUP BY 2; + }; + + $info = run_command($SQL, {target => @localtargetlist, regex => qr[\w+], emptyok => 1 } ); + + for $db (@{$info->{db}}) { + my ($maxage,$maxdb) = (0,''); ## used by MRTG only + ROW: for my $r (@{$db->{slurp}}) { + my ($dbname,$parent_table,$missing_parts) = ($r->{database},$r->{parent_table},$r->{missing_parts}); + $found = 1 if ! $found; + next ROW if skip_item($dbname); + $found = 2; + + $msg = "$dbname=$parent_table ($missing_parts) missing "; + #print "$msg"; + $db->{perf} .= sprintf ' %s=%sd;%s;%s', + perfname($dbname), $missing_parts, $warning, $critical; + if (length $missing_parts) { + push @crit => $msg; + } + else { + push @ok => $msg; + } + } + } # check, if the number of premade partitions from now is not lower than the premake setting $SQL = q{ SELECT current_database() as database, - a.parent_table, + a.parent_table, b.premake, b.premake-count(*) as missing_part FROM @@ -6921,9 +7132,9 @@ sub check_partman_premake { ) b ON a.parent_table::text = b.parent_table::text -WHERE a.prank<=b.premake -AND a.date::date > now() -GROUP BY database, a.parent_table, b.premake +WHERE a.prank<=b.premake +AND a.date::date > now() +GROUP BY database, a.parent_table, b.premake HAVING count(*) < b.premake; }; @@ -6950,7 +7161,7 @@ sub check_partman_premake { }; }; }; - }; # end of version >= 14.0 + }; # end of version >= 14.0 if (0 == $found) { add_ok msg('partman-premake-ok'); @@ -9638,28 +9849,28 @@ =head1 SYNOPSIS =head1 DESCRIPTION -check_postgres.pl is a Perl script that runs many different tests against -one or more Postgres databases. It uses the psql program to gather the -information, and outputs the results in one of three formats: Nagios, MRTG, +check_postgres.pl is a Perl script that runs many different tests against +one or more Postgres databases. It uses the psql program to gather the +information, and outputs the results in one of three formats: Nagios, MRTG, or simple. =head2 Output Modes -The output can be changed by use of the C<--output> option. The default output -is nagios, although this can be changed at the top of the script if you wish. The -current option choices are B, B, and B. To avoid having to -enter the output argument each time, the type of output is automatically set -if no --output argument is given, and if the current directory has one of the -output options in its name. For example, creating a directory named mrtg and -populating it with symlinks via the I<--symlinks> argument would ensure that +The output can be changed by use of the C<--output> option. The default output +is nagios, although this can be changed at the top of the script if you wish. The +current option choices are B, B, and B. To avoid having to +enter the output argument each time, the type of output is automatically set +if no --output argument is given, and if the current directory has one of the +output options in its name. For example, creating a directory named mrtg and +populating it with symlinks via the I<--symlinks> argument would ensure that any actions run from that directory will always default to an output of "mrtg" -As a shortcut for --output=simple, you can enter --simple, which also overrides +As a shortcut for --output=simple, you can enter --simple, which also overrides the directory naming trick. =head3 Nagios output -The default output format is for Nagios, which is a single line of information, along +The default output format is for Nagios, which is a single line of information, along with four specific exit codes: =over 2 @@ -9674,21 +9885,21 @@ =head3 Nagios output =back -The output line is one of the words above, a colon, and then a short description of what -was measured. Additional statistics information, as well as the total time the command -took, can be output as well: see the documentation on the arguments -I>, -I>, and +The output line is one of the words above, a colon, and then a short description of what +was measured. Additional statistics information, as well as the total time the command +took, can be output as well: see the documentation on the arguments +I>, +I>, and I>. =head3 MRTG output -The MRTG output is four lines, with the first line always giving a single number of importance. -When possible, this number represents an actual value such as a number of bytes, but it +The MRTG output is four lines, with the first line always giving a single number of importance. +When possible, this number represents an actual value such as a number of bytes, but it may also be a 1 or a 0 for actions that only return "true" or "false", such as check_postgres_version. -The second line is an additional stat and is only used for some actions. The third line indicates -an "uptime" and is not used. The fourth line is a description and usually indicates the name of -the database the stat from the first line was pulled from, but may be different depending on the +The second line is an additional stat and is only used for some actions. The third line indicates +an "uptime" and is not used. The fourth line is a description and usually indicates the name of +the database the stat from the first line was pulled from, but may be different depending on the action. Some actions accept an optional I<--mrtg> argument to further control the output. @@ -9697,18 +9908,18 @@ =head3 MRTG output =head3 Simple output -The simple output is simply a truncated version of the MRTG one, and simply returns the first number -and nothing else. This is very useful when you just want to check the state of something, regardless -of any threshold. You can transform the numeric output by appending KB, MB, GB, TB, or EB to the output +The simple output is simply a truncated version of the MRTG one, and simply returns the first number +and nothing else. This is very useful when you just want to check the state of something, regardless +of any threshold. You can transform the numeric output by appending KB, MB, GB, TB, or EB to the output argument, for example: --output=simple,MB =head3 Cacti output -The Cacti output consists of one or more items on the same line, with a simple name, a colon, and -then a number. At the moment, the only action with explicit Cacti output is 'dbstats', and using -the --output option is not needed in this case, as Cacti is the only output for this action. For many +The Cacti output consists of one or more items on the same line, with a simple name, a colon, and +then a number. At the moment, the only action with explicit Cacti output is 'dbstats', and using +the --output option is not needed in this case, as Cacti is the only output for this action. For many other actions, using --simple is enough to make Cacti happy. =head1 DATABASE CONNECTION OPTIONS @@ -9719,26 +9930,26 @@ =head1 DATABASE CONNECTION OPTIONS =item B<-H NAME> or B<--host=NAME> -Connect to the host indicated by NAME. Can be a comma-separated list of names. Multiple host arguments -are allowed. If no host is given, defaults to the C environment variable or no host at all +Connect to the host indicated by NAME. Can be a comma-separated list of names. Multiple host arguments +are allowed. If no host is given, defaults to the C environment variable or no host at all (which indicates using a local Unix socket). You may also use "--dbhost". =item B<-p PORT> or B<--port=PORT> -Connects using the specified PORT number. Can be a comma-separated list of port numbers, and multiple -port arguments are allowed. If no port number is given, defaults to the C environment variable. If +Connects using the specified PORT number. Can be a comma-separated list of port numbers, and multiple +port arguments are allowed. If no port number is given, defaults to the C environment variable. If that is not set, it defaults to 5432. You may also use "--dbport" =item B<-db NAME> or B<--dbname=NAME> -Specifies which database to connect to. Can be a comma-separated list of names, and multiple dbname -arguments are allowed. If no dbname option is provided, defaults to the C environment variable. +Specifies which database to connect to. Can be a comma-separated list of names, and multiple dbname +arguments are allowed. If no dbname option is provided, defaults to the C environment variable. If that is not set, it defaults to 'postgres' if psql is version 8 or greater, and 'template1' otherwise. =item B<-u USERNAME> or B<--dbuser=USERNAME> -The name of the database user to connect as. Can be a comma-separated list of usernames, and multiple -dbuser arguments are allowed. If this is not provided, it defaults to the C environment variable, otherwise +The name of the database user to connect as. Can be a comma-separated list of usernames, and multiple +dbuser arguments are allowed. If this is not provided, it defaults to the C environment variable, otherwise it defaults to 'postgres'. =item B<--dbpass=PASSWORD> @@ -9748,12 +9959,12 @@ =head1 DATABASE CONNECTION OPTIONS =item B<--dbservice=NAME> -The name of a service inside of the pg_service.conf file. Before version 9.0 of Postgres, this is -a global file, usually found in F. If you are using version 9.0 or higher of -Postgres, you can use the file ".pg_service.conf" in the home directory of the user running +The name of a service inside of the pg_service.conf file. Before version 9.0 of Postgres, this is +a global file, usually found in F. If you are using version 9.0 or higher of +Postgres, you can use the file ".pg_service.conf" in the home directory of the user running the script, e.g. nagios. -This file contains a simple list of connection options. You can also pass additional information +This file contains a simple list of connection options. You can also pass additional information when using this option such as --dbservice="maindatabase sslmode=require" The documentation for this file can be found at @@ -9769,7 +9980,7 @@ =head1 DATABASE CONNECTION OPTIONS =back The database connection options can be grouped: I<--host=a,b --host=c --port=1234 --port=3344> -would connect to a-1234, b-1234, and c-3344. Note that once set, an option +would connect to a-1234, b-1234, and c-3344. Note that once set, an option carries over until it is changed again. Examples: @@ -9797,23 +10008,23 @@ =head1 OTHER OPTIONS =item B<--action=NAME> -States what action we are running. Required unless using a symlinked file, +States what action we are running. Required unless using a symlinked file, in which case the name of the file is used to figure out the action. =item B<--warning=VAL or -w VAL> -Sets the threshold at which a warning alert is fired. The valid options for this +Sets the threshold at which a warning alert is fired. The valid options for this option depends on the action used. =item B<--critical=VAL or -c VAL> -Sets the threshold at which a critical alert is fired. The valid options for this +Sets the threshold at which a critical alert is fired. The valid options for this option depends on the action used. =item B<-t VAL> or B<--timeout=VAL> -Sets the timeout in seconds after which the script will abort whatever it is doing -and return an UNKNOWN status. The timeout is per Postgres cluster, not for the entire +Sets the timeout in seconds after which the script will abort whatever it is doing +and return an UNKNOWN status. The timeout is per Postgres cluster, not for the entire script. The default value is 10; the units are always in seconds. =item B<--assume-standby-mode> @@ -9840,7 +10051,7 @@ =head1 OTHER OPTIONS =item B<--assume-async> If specified, indicates that any replication between servers is asynchronous. -The option is only relevant for (C). +The option is only relevant for (C). Example: postgres@db$./check_postgres.pl --action=same_schema --assume-async --dbhost=star,line @@ -9859,28 +10070,28 @@ =head1 OTHER OPTIONS =item B<-v> or B<--verbose> -Set the verbosity level. Can call more than once to boost the level. Setting it to three -or higher (in other words, issuing C<-v -v -v>) turns on debugging information for this +Set the verbosity level. Can call more than once to boost the level. Setting it to three +or higher (in other words, issuing C<-v -v -v>) turns on debugging information for this program which is sent to stderr. =item B<--showperf=VAL> -Determines if we output additional performance data in standard Nagios format -(at end of string, after a pipe symbol, using name=value). +Determines if we output additional performance data in standard Nagios format +(at end of string, after a pipe symbol, using name=value). VAL should be 0 or 1. The default is 1. Only takes effect if using Nagios output mode. =item B<--perflimit=i> -Sets a limit as to how many items of interest are reported back when using the -I option. This only has an effect for actions that return a large -number of items, such as B. The default is 0, or no limit. Be -careful when using this with the I<--include> or I<--exclude> options, as -those restrictions are done I the query has been run, and thus your +Sets a limit as to how many items of interest are reported back when using the +I option. This only has an effect for actions that return a large +number of items, such as B. The default is 0, or no limit. Be +careful when using this with the I<--include> or I<--exclude> options, as +those restrictions are done I the query has been run, and thus your limit may not include the items you want. Only takes effect if using Nagios output mode. =item B<--showtime=VAL> -Determines if the time taken to run each query is shown in the output. VAL +Determines if the time taken to run each query is shown in the output. VAL should be 0 or 1. The default is 1. No effect unless I is on. Only takes effect if using Nagios output mode. @@ -9901,11 +10112,11 @@ =head1 OTHER OPTIONS =item B<--PSQL=PATH> I<(deprecated, this option may be removed in a future release!)> -Tells the script where to find the psql program. Useful if you have more than -one version of the psql executable on your system, or if there is no psql program -in your path. Note that this option is in all uppercase. By default, this option -is I. To enable it, you must change the C<$NO_PSQL_OPTION> near the -top of the script to 0. Avoid using this option if you can, and instead hard-code +Tells the script where to find the psql program. Useful if you have more than +one version of the psql executable on your system, or if there is no psql program +in your path. Note that this option is in all uppercase. By default, this option +is I. To enable it, you must change the C<$NO_PSQL_OPTION> near the +top of the script to 0. Avoid using this option if you can, and instead hard-code your psql location into the C<$PSQL> variable, also near the top of the script. =item B<--symlinks> @@ -9930,20 +10141,20 @@ =head1 OTHER OPTIONS =item B<--get_method=VAL> -Allows specification of the method used to fetch information for the C, -C, C, C, and C checks. -The following programs are tried, in order, to grab the information from the web: -GET, wget, fetch, curl, lynx, links. To force the use of just one (and thus remove the -overhead of trying all the others until one of those works), enter one of the names as -the argument to get_method. For example, a BSD box might enter the following line in +Allows specification of the method used to fetch information for the C, +C, C, C, and C checks. +The following programs are tried, in order, to grab the information from the web: +GET, wget, fetch, curl, lynx, links. To force the use of just one (and thus remove the +overhead of trying all the others until one of those works), enter one of the names as +the argument to get_method. For example, a BSD box might enter the following line in their C<.check_postgresrc> file: get_method=fetch =item B<--language=VAL> -Set the language to use for all output messages. Normally, this is detected by examining -the environment variables LC_ALL, LC_MESSAGES, and LANG, but setting this option +Set the language to use for all output messages. Normally, this is detected by examining +the environment variables LC_ALL, LC_MESSAGES, and LANG, but setting this option will override any such detection. =back @@ -9951,8 +10162,8 @@ =head1 OTHER OPTIONS =head1 ACTIONS -The action to be run is selected using the --action -flag, or by using a symlink to the main file that contains the name of the action +The action to be run is selected using the --action +flag, or by using a symlink to the main file that contains the name of the action inside of it. For example, to run the action "timesync", you may either issue: check_postgres.pl --action=timesync @@ -9961,30 +10172,30 @@ =head1 ACTIONS check_postgres_timesync -All the symlinks are created for you in the current directory +All the symlinks are created for you in the current directory if use the option --symlinks: perl check_postgres.pl --symlinks -If the file name already exists, it will not be overwritten. If the file exists +If the file name already exists, it will not be overwritten. If the file exists and is a symlink, you can force it to overwrite by using "--action=build_symlinks_force". -Most actions take a I<--warning> and a I<--critical> option, indicating at what -point we change from OK to WARNING, and what point we go to CRITICAL. Note that -because criticals are always checked first, setting the warning equal to the +Most actions take a I<--warning> and a I<--critical> option, indicating at what +point we change from OK to WARNING, and what point we go to CRITICAL. Note that +because criticals are always checked first, setting the warning equal to the critical is an effective way to turn warnings off and always give a critical. The current supported actions are: =head2 B -(C) Checks how many WAL files with extension F<.ready> -exist in the F directory (PostgreSQL 10 and later: F), which is found +(C) Checks how many WAL files with extension F<.ready> +exist in the F directory (PostgreSQL 10 and later: F), which is found off of your B. If the I<--lsfunc> option is not used then this action must be run as a superuser, in order to access the -contents of the F directory. The minimum version to use this action is -Postgres 8.1. The I<--warning> and I<--critical> options are simply the number of -F<.ready> files in the F directory. -Usually, these values should be low, turning on the archive mechanism, we usually want it to +contents of the F directory. The minimum version to use this action is +Postgres 8.1. The I<--warning> and I<--critical> options are simply the number of +F<.ready> files in the F directory. +Usually, these values should be low, turning on the archive mechanism, we usually want it to archive WAL files as fast as possible. If the archive command fail, number of WAL in your F directory will grow until @@ -10015,32 +10226,32 @@ =head2 B =head2 B -(C) Checks how close each database is to the Postgres B setting. This -action will only work for databases version 8.2 or higher. The I<--warning> and -I<--critical> options should be expressed as percentages. The 'age' of the transactions -in each database is compared to the autovacuum_freeze_max_age setting (200 million by default) -to generate a rounded percentage. The default values are B<90%> for the warning and B<95%> for -the critical. Databases can be filtered by use of the I<--include> and I<--exclude> options. +(C) Checks how close each database is to the Postgres B setting. This +action will only work for databases version 8.2 or higher. The I<--warning> and +I<--critical> options should be expressed as percentages. The 'age' of the transactions +in each database is compared to the autovacuum_freeze_max_age setting (200 million by default) +to generate a rounded percentage. The default values are B<90%> for the warning and B<95%> for +the critical. Databases can be filtered by use of the I<--include> and I<--exclude> options. See the L section for more details. Example 1: Give a warning when any databases on port 5432 are above 97% check_postgres_autovac_freeze --port=5432 --warning="97%" -For MRTG output, the highest overall percentage is reported on the first line, and the highest age is -reported on the second line. All databases which have the percentage from the first line are reported +For MRTG output, the highest overall percentage is reported on the first line, and the highest age is +reported on the second line. All databases which have the percentage from the first line are reported on the fourth line, separated by a pipe symbol. =head2 B -(C) Checks the current number of connections for one or more databases, and optionally -compares it to the maximum allowed, which is determined by the -Postgres configuration variable B. The I<--warning> and -I<--critical> options can take one of three forms. First, a simple number can be -given, which represents the number of connections at which the alert will be -given. This choice does not use the B setting. Second, the -percentage of available connections can be given. Third, a negative number can -be given which represents the number of connections left until B +(C) Checks the current number of connections for one or more databases, and optionally +compares it to the maximum allowed, which is determined by the +Postgres configuration variable B. The I<--warning> and +I<--critical> options can take one of three forms. First, a simple number can be +given, which represents the number of connections at which the alert will be +given. This choice does not use the B setting. Second, the +percentage of available connections can be given. Third, a negative number can +be given which represents the number of connections left until B is reached. The default values for I<--warning> and I<--critical> are '90%' and '95%'. You can also filter the databases by use of the I<--include> and I<--exclude> options. See the L section for more details. @@ -10057,7 +10268,7 @@ =head2 B check_postgres_backends --warning='75%' --critical='75%' --host=lancre,lancre2 -Example 3: Give a warning when there are only 10 more connection slots left on host plasmid, and a critical +Example 3: Give a warning when there are only 10 more connection slots left on host plasmid, and a critical when we have only 5 left. check_postgres_backends --warning=-10 --critical=-5 --host=plasmid @@ -10066,43 +10277,43 @@ =head2 B check_postgres_backends --dbhost=hong,kong --dbhost=fooey --dbport=5432 --dbport=5433 --warning=30 --critical=30 --exclude="~test" --include="pg_greatest,~prod" -For MRTG output, the number of connections is reported on the first line, and the fourth line gives the name of the database, -plus the current maximum_connections. If more than one database has been queried, the one with the highest number of +For MRTG output, the number of connections is reported on the first line, and the fourth line gives the name of the database, +plus the current maximum_connections. If more than one database has been queried, the one with the highest number of connections is output. =head2 B -(C) Checks the amount of bloat in tables and indexes. (Bloat is generally the amount -of dead unused space taken up in a table or index. This space is usually reclaimed -by use of the VACUUM command.) This action requires that stats collection be -enabled on the target databases, and requires that ANALYZE is run frequently. -The I<--include> and I<--exclude> options can be used to filter out which tables +(C) Checks the amount of bloat in tables and indexes. (Bloat is generally the amount +of dead unused space taken up in a table or index. This space is usually reclaimed +by use of the VACUUM command.) This action requires that stats collection be +enabled on the target databases, and requires that ANALYZE is run frequently. +The I<--include> and I<--exclude> options can be used to filter out which tables to look at. See the L section for more details. The I<--warning> and I<--critical> options can be specified as sizes, percents, or both. -Valid size units are bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes, -petabytes, and zettabytes. You can abbreviate all of those with the first letter. Items -without units are assumed to be 'bytes'. The default values are '1 GB' and '5 GB'. The value -represents the number of "wasted bytes", or the difference between what is actually +Valid size units are bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes, +petabytes, and zettabytes. You can abbreviate all of those with the first letter. Items +without units are assumed to be 'bytes'. The default values are '1 GB' and '5 GB'. The value +represents the number of "wasted bytes", or the difference between what is actually used by the table and index, and what we compute that it should be. -Note that this action has two hard-coded values to avoid false alarms on -smaller relations. Tables must have at least 10 pages, and indexes at least 15, -before they can be considered by this test. If you really want to adjust these -values, you can look for the variables I<$MINPAGES> and I<$MINIPAGES> at the top of the -C subroutine. These values are ignored if either I<--exclude> or +Note that this action has two hard-coded values to avoid false alarms on +smaller relations. Tables must have at least 10 pages, and indexes at least 15, +before they can be considered by this test. If you really want to adjust these +values, you can look for the variables I<$MINPAGES> and I<$MINIPAGES> at the top of the +C subroutine. These values are ignored if either I<--exclude> or I<--include> is used. -Only the top 10 most bloated relations are shown. You can change this number by +Only the top 10 most bloated relations are shown. You can change this number by using the I<--perflimit> option to set your own limit. -The schema named 'information_schema' is excluded from this test, as the only tables +The schema named 'information_schema' is excluded from this test, as the only tables it contains are small and do not change. -Please note that the values computed by this action are not precise, and -should be used as a guideline only. Great effort was made to estimate the -correct size of a table, but in the end it is only an estimate. The correct -index size is even more of a guess than the correct table size, but both +Please note that the values computed by this action are not precise, and +should be used as a guideline only. Great effort was made to estimate the +correct size of a table, but in the end it is only an estimate. The correct +index size is even more of a guess than the correct table size, but both should give a rough idea of how bloated things are. Example 1: Warn if any table on port 5432 is over 100 MB bloated, and critical if over 200 MB @@ -10127,20 +10338,20 @@ =head2 B check_postgres_bloat --port=5432 --warning='500 M or 40%' -For MRTG output, the first line gives the highest number of wasted bytes for the tables, and the -second line gives the highest number of wasted bytes for the indexes. The fourth line gives the database -name, table name, and index name information. If you want to output the bloat ratio instead (how many +For MRTG output, the first line gives the highest number of wasted bytes for the tables, and the +second line gives the highest number of wasted bytes for the indexes. The fourth line gives the database +name, table name, and index name information. If you want to output the bloat ratio instead (how many times larger the relation is compared to how large it should be), just pass in C<--mrtg=ratio>. =head2 B -(C) Determines how long since the last checkpoint has -been run. This must run on the same server as the database that is being checked (e.g. the -h -flag will not work). This check is meant to run on a "warm standby" server that is actively -processing shipped WAL files, and is meant to check that your warm standby is truly 'warm'. -The data directory must be set, either by the environment variable C, or passing -the C<--datadir> argument. It returns the number of seconds since the last checkpoint -was run, as determined by parsing the call to C. Because of this, the +(C) Determines how long since the last checkpoint has +been run. This must run on the same server as the database that is being checked (e.g. the -h +flag will not work). This check is meant to run on a "warm standby" server that is actively +processing shipped WAL files, and is meant to check that your warm standby is truly 'warm'. +The data directory must be set, either by the environment variable C, or passing +the C<--datadir> argument. It returns the number of seconds since the last checkpoint +was run, as determined by parsing the call to C. Because of this, the pg_controldata executable must be available in the current path. Alternatively, you can specify C as the directory that it lives in. It is also possible to use the special options I<--assume-prod> or @@ -10176,11 +10387,11 @@ =head2 B =head2 B (C) Checks the commit ratio of all databases and complains when they are too low. -There is no need to run this command more than once per database cluster. -Databases can be filtered with -the I<--include> and I<--exclude> options. See the L section -for more details. -They can also be filtered by the owner of the database with the +There is no need to run this command more than once per database cluster. +Databases can be filtered with +the I<--include> and I<--exclude> options. See the L section +for more details. +They can also be filtered by the owner of the database with the I<--includeuser> and I<--excludeuser> options. See the L section for more details. @@ -10193,7 +10404,7 @@ =head2 B check_postgres_database_commitratio --host=flagg --warning='90%' --critical='80%' -For MRTG output, returns the percentage of the database with the smallest commitratio on the first line, +For MRTG output, returns the percentage of the database with the smallest commitratio on the first line, and the name of the database on the fourth line. =head2 B @@ -10205,41 +10416,41 @@ =head2 B =head2 B -(C) Runs a custom query of your choosing, and parses the results. -The query itself is passed in through the C argument, and should be kept as simple as possible. -If at all possible, wrap it in a view or a function to keep things easier to manage. The query should -return one or two columns. It is required that one of the columns be named "result" and is the item -that will be checked against your warning and critical values. The second column is for the performance +(C) Runs a custom query of your choosing, and parses the results. +The query itself is passed in through the C argument, and should be kept as simple as possible. +If at all possible, wrap it in a view or a function to keep things easier to manage. The query should +return one or two columns. It is required that one of the columns be named "result" and is the item +that will be checked against your warning and critical values. The second column is for the performance data and any name can be used: this will be the 'value' inside the performance data section. -At least one warning or critical argument must be specified. What these are set to depends on the type of -query you are running. There are four types of custom_queries that can be run, specified by the C +At least one warning or critical argument must be specified. What these are set to depends on the type of +query you are running. There are four types of custom_queries that can be run, specified by the C argument. If none is specified, this action defaults to 'integer'. The four types are: B: -Does a simple integer comparison. The first column should be a simple integer, and the warning and +Does a simple integer comparison. The first column should be a simple integer, and the warning and critical values should be the same. B: -The warning and critical are strings, and are triggered only if the value in the first column matches +The warning and critical are strings, and are triggered only if the value in the first column matches it exactly. This is case-sensitive. B action requires the B module. -Some actions require access to external programs. If psql is not explicitly -specified, the command B> is used to find it. The program B> +Some actions require access to external programs. If psql is not explicitly +specified, the command B> is used to find it. The program B> is needed by the L action. =head1 DEVELOPMENT @@ -11588,12 +11799,12 @@ =head1 DEVELOPMENT =head1 MAILING LIST -Three mailing lists are available. For discussions about the program, bug reports, +Three mailing lists are available. For discussions about the program, bug reports, feature requests, and commit notices, send email to check_postgres@bucardo.org L -A low-volume list for announcement of new versions and important notices is the +A low-volume list for announcement of new versions and important notices is the 'check_postgres-announce' list: L @@ -11621,7 +11832,7 @@ =head1 HISTORY Add new action "pgbouncer_maxwait" (Ruslan Kabalin) [Github pull #59] - For the bloat check, add option to populate all known databases, + For the bloat check, add option to populate all known databases, as well as includsion and exclusion regexes. (Giles Westwood) [Github pull #86] Add Partman premake check (Jens Wilke) [Github pull #196] @@ -11938,7 +12149,7 @@ =head1 HISTORY Add cache-busting for the version-grabbing utilities. Fix problem with going to next method for new_version_pg (Greg Sabino Mullane, reported by Hywel Mallett in bug #65) - Allow /usr/local/etc as an alternative location for the + Allow /usr/local/etc as an alternative location for the check_postgresrc file (Hywel Mallett) Do not use tgisconstraint in same_schema if Postgres >= 9 (Guillaume Lelarge) @@ -12079,7 +12290,7 @@ =head1 HISTORY =item B (July 14, 2009) Quote dbname in perf output for the backends check. (Davide Abrigo) - Add 'fetch' as an alternative method for new_version checks, as this + Add 'fetch' as an alternative method for new_version checks, as this comes by default with FreeBSD. (Hywel Mallett) =item B (July 12, 2009) @@ -12088,7 +12299,7 @@ =head1 HISTORY Check and display the database for each match in the bloat check (Cédric Villemain) Handle 'too many connections' FATAL error in the backends check with a critical, rather than a generic error (Greg, idea by Jürgen Schulz-Brüssel) - Do not allow perflimit to interfere with exclusion rules in the vacuum and + Do not allow perflimit to interfere with exclusion rules in the vacuum and analyze tests. (Greg, bug reported by Jeff Frost) =item B (June 12, 2009) @@ -12144,7 +12355,7 @@ =head1 HISTORY =item B (February 4, 2009) - Do not require a connection argument, but use defaults and ENV variables when + Do not require a connection argument, but use defaults and ENV variables when possible: PGHOST, PGPORT, PGUSER, PGDATABASE. =item B (February 4, 2009) @@ -12394,7 +12605,7 @@ =head1 HISTORY =item B (April 2, 2008) Have 'wal_files' action use pg_ls_dir (idea by Robert Treat). - For last_vacuum and last_analyze, respect autovacuum effects, add separate + For last_vacuum and last_analyze, respect autovacuum effects, add separate autovacuum checks (ideas by Robert Treat). =item B (April 2, 2008) @@ -12485,24 +12696,24 @@ =head1 LICENSE AND COPYRIGHT Copyright 2007 - 2024 Greg Sabino Mullane . -Redistribution and use in source and binary forms, with or without +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =cut