Add the hitratio action
authorGuillaume Lelarge <[email protected]>
Mon, 27 Jun 2011 14:15:54 +0000 (16:15 +0200)
committerGuillaume Lelarge <[email protected]>
Mon, 27 Jun 2011 14:16:56 +0000 (16:16 +0200)
This new action allows a user to track the hit ratio, ie the difference
between cache and disk reads, in each database.

check_postgres.pl
t/02_database_hitratio.t [new file with mode: 0644]

index 7da96ed1d3ec8bf12f2b3fe326fe57b560893d61..d745e857208c21cb6c234482bb18f9d9021878b6 100755 (executable)
@@ -923,6 +923,7 @@ our $action_info = {
  connection          => [0, 'Simple connection check.'],
  custom_query        => [0, 'Run a custom query.'],
  database_commitratio   => [0, 'Report if the commitratio of a database is too low.'],
+ database_hitratio   => [0, 'Report if the hitratio of a database is too low.'],
  database_size       => [0, 'Report if a database is too big.'],
  dbstats             => [1, 'Returns stats from pg_stat_database: Cacti output only'],
  disabled_triggers   => [0, 'Check if any triggers are disabled'],
@@ -1575,6 +1576,9 @@ check_connection() if $action eq 'connection';
 ## Check the commitratio of one or more databases
 check_database_commitratio() if $action eq 'database_commitratio';
 
+## Check the hitratio of one or more databases
+check_database_hitratio() if $action eq 'database_hitratio';
+
 ## Check the size of one or more databases
 check_database_size() if $action eq 'database_size';
 
@@ -3638,6 +3642,96 @@ $USERWHERECLAUSE
 } ## end of check_database_commitratio
 
 
+sub check_database_hitratio {
+
+    ## Check the hitratio 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 criticals are percentages
+    ## Limit to a specific user (db owner) with the includeuser option
+    ## Exclude users with the excludeuser option
+
+    my ($warning, $critical) = validate_range({type => 'percent'});
+
+    $SQL = qq{
+SELECT
+  round(100.*sd.blks_hit/(sd.blks_read+sd.blks_hit), 2) AS dhitratio,
+  d.datname,
+  u.usename
+FROM pg_stat_database sd
+JOIN pg_database d ON (d.oid=sd.datid)
+JOIN pg_user u ON (u.usesysid=d.datdba)
+WHERE sd.blks_read+sd.blks_hit<>0
+$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 $min = 101;
+        $found = 1;
+        my %s;
+        for my $r (@{$db->{slurp}}) {
+
+            next if skip_item($r->{datname});
+
+            if ($r->{dhitratio} <= $min) {
+                $min = $r->{dhitratio};
+            }
+            $s{$r->{datname}} = $r->{dhitratio};
+        }
+
+        if ($MRTG) {
+            do_mrtg({one => $min, msg => "DB: $db->{dbname}"});
+        }
+        if ($min > 100) {
+            $stats{$db->{dbname}} = 0;
+            if ($USERWHERECLAUSE) {
+                add_ok msg('no-match-user');
+            }
+            else {
+                add_unknown msg('no-match-db');
+            }
+            next;
+        }
+
+        my $msg = '';
+        for (reverse sort {$s{$b} <=> $s{$a} or $a cmp $b } keys %s) {
+            $msg .= "$_: $s{$_} ";
+            $db->{perf} .= sprintf ' %s=%s;%s;%s',
+                perfname($_), $s{$_}, $warning, $critical;
+        }
+        if (length $critical and $min <= $critical) {
+            add_critical $msg;
+        }
+        elsif (length $warning and $min <= $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_hitratio
+
+
 sub check_database_size {
 
     ## Check the size of one or more databases
@@ -8315,6 +8409,29 @@ Example: Warn if any database on host flagg is less than 90% in commitratio, and
 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<database_hitratio>
+
+(C<symlink: check_postgres_database_hitratio>) Checks the hit 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</"BASIC FILTERING"> 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</"USER NAME FILTERING"> section for more details.
+
+The warning and critical options should be specified as percentages. There are not
+defaults for this action: the warning and critical must be specified. The warning value
+cannot be greater than the critical value. The output returns all databases sorted by
+hitratio, smallest first.
+
+Example: Warn if any database on host flagg is less than 90% in hitratio, and critical if less then 80%.
+
+  check_postgres_database_hitratio --host=flagg --warning='90%' --critical='80%'
+
+For MRTG output, returns the percentage of the database with the smallest hitratio on the first line, 
+and the name of the database on the fourth line.
+    
 =head2 B<database_size>
 
 (C<symlink: check_postgres_database_size>) Checks the size of all databases and complains when they are too big. 
diff --git a/t/02_database_hitratio.t b/t/02_database_hitratio.t
new file mode 100644 (file)
index 0000000..5fcd986
--- /dev/null
@@ -0,0 +1,85 @@
+#!perl
+
+## Test the "database_size" action
+
+use 5.006;
+use strict;
+use warnings;
+use Data::Dumper;
+use Test::More tests => 23;
+use lib 't','.';
+use CP_Testing;
+
+use vars qw/$dbh $dbh2 $SQL $count $host $t $result $user/;
+
+my $cp = CP_Testing->new({default_action => 'database_hitratio'});
+
+$dbh = $cp->test_database_handle();
+
+my $S = q{Action 'database_hitratio'};
+my $label = 'POSTGRES_DATABASE_HITRATIO';
+
+$cp->drop_all_tables();
+
+$t=qq{$S returned expected text when warning level is specified in percentages};
+like ($cp->run('-w 0%'), qr{^$label OK:}, $t);
+
+$t=qq{$S returned expected text when warning level is specified in percentages};
+like ($cp->run('-w 100%'), qr{^$label WARNING:}, $t);
+
+$t=qq{$S returned expected text when critical level is specified};
+like ($cp->run('-c 0%'), qr{^$label OK:}, $t);
+
+$t=qq{$S returned expected text when warning level and critical level are specified};
+like ($cp->run('-w 0% -c 0%'), qr{^$label OK:}, $t);
+
+$t=qq{$S fails when called with an invalid option};
+like ($cp->run('foobar=12'), qr{^\s*Usage:}, $t);
+
+$t=qq{$S fails when called with an invalid warning option};
+like ($cp->run('-w felz'),     qr{^ERROR: Invalid 'warning' option: must be a percentage}, $t);
+like ($cp->run('-w 23%%'),     qr{^ERROR: Invalid 'warning' option: must be a percentage}, $t);
+
+$t=qq{$S fails when called with an invalid critical option};
+like ($cp->run('-c felz'),     qr{^ERROR: Invalid 'critical' option: must be a percentage}, $t);
+like ($cp->run('-c 23%%'),     qr{^ERROR: Invalid 'critical' option: must be a percentage}, $t);
+
+$t=qq{$S fails when the warning or critical percentages is negative};
+like ($cp->run('-w -10%'), qr{^ERROR: Invalid 'warning' option: must be a percentage}, $t);
+like ($cp->run('-c -20%'), qr{^ERROR: Invalid 'critical' option: must be a percentage}, $t);
+
+$t=qq{$S with includeuser option returns nothing};
+like ($cp->run('--includeuser mycatbeda -w 10%'), qr{^$label OK:.+ }, $t);
+
+$t=qq{$S has critical option trump the warning option};
+like ($cp->run('-w 100% -c 100%'), qr{^$label CRITICAL}, $t);
+like ($cp->run('--critical=100% --warning=99%'), qr{^$label CRITICAL}, $t);
+
+$t=qq{$S returns correct MRTG output when no rows found};
+like ($cp->run('--output=MRTG -w 10% --includeuser nosuchuser'), qr{^101}, $t);
+
+$t=qq{$S returns correct MRTG output when rows found};
+like ($cp->run('--output=MRTG -w 10%'), qr{\d+\n0\n\nDB: postgres\n}s, $t);
+
+$t=qq{$S works when include forces no matches};
+like ($cp->run('-w 1% --include blargy'), qr{^$label UNKNOWN: .+No matching databases}, $t);
+
+$t=qq{$S works when include has valid database};
+like ($cp->run('-w 1% --include=postgres'), qr{$label OK: .+postgres}, $t);
+
+$t=qq{$S works when exclude excludes nothing};
+like ($cp->run('-w 90% --exclude=foobar'), qr{$label OK: DB "postgres"}, $t);
+
+$t=qq{$S works when include and exclude make a match};
+like ($cp->run('-w 5% --exclude=postgres --include=postgres'), qr{$label OK: DB "postgres"}, $t);
+
+$t=qq{$S works when exclude and include make a match};
+like ($cp->run('-w 5% --include=postgres --exclude=postgres'), qr{$label OK: DB "postgres"}, $t);
+
+$t=qq{$S returned correct performance data with include};
+like ($cp->run('-w 5% --include=postgres'), qr{ \| time=\d\.\d\ds postgres=\d+}, $t);
+
+$t=qq{$S with includeuser option returns nothing};
+like ($cp->run('--includeuser postgres --includeuser mycatbeda -w 10%'), qr{No matching entries found due to user exclusion}, $t);
+
+exit;