Index: /trunk/tools/errors.pl
===================================================================
--- /trunk/tools/errors.pl	(revision 27146)
+++ /trunk/tools/errors.pl	(revision 27146)
@@ -0,0 +1,175 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use DBI;
+use constant DB_SOCKET => '/var/run/mysqld/mysqld.sock'; # Socket for mysql
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Nebulous::Client;
+
+
+my ($db_host, $db_name, $db_user, $db_pw); # Database details
+my ($label);                               # Label
+my ($stage);                               # Stage of interest
+my ($fault);                               # Fault code of interest
+my ($limit);                               # Limit for query
+
+GetOptions(
+           'dbhost=s' => \$db_host, # Database host name
+           'dbname=s' => \$db_name, # Database name
+           'dbuser=s' => \$db_user, # Database user
+           'dbpass=s' => \$db_pw, # Database p/w
+           'stage=s' => \$stage, # Stage
+           'label=s@' => \$label, # Label(s)
+           'fault=s' => \$fault, # Fault code
+           'limit=s' => \$limit, # Limit to query
+           ) or
+die "Unable to parse arguments.\n";
+die "Unknown option: @ARGV\n" if @ARGV;
+die "Required options: --dbhost --dbname --dbuser --dbpass --stage\n"
+    unless defined $db_host
+    and defined $db_name
+    and defined $db_user
+    and defined $db_pw
+    and defined $stage;
+
+# Database connection
+my $db = DBI->connect( "DBI:mysql:database=$db_name;host=$db_host;mysql_socket=" . DB_SOCKET(),
+                       $db_user,
+                       $db_pw,
+                       { RaiseError => 1, AutoCommit => 1 }
+                       ) or die "Unable to connect to database: $DBI::errstr";
+
+# Nebulous client
+my $neb = Nebulous::Client->new( proxy => $ENV{NEB_SERVER} ) or die "Unable to connect to Nebulous: $!\n";
+
+my $sql;                        # Query to run
+if ($stage eq "diff") {
+    $sql = "SELECT diff_id, skycell_id, path_base FROM diffRun JOIN diffSkyfile USING(diff_id) WHERE fault != 0";
+} else {
+    die "Unsupported stage: $stage\n";
+}
+
+my $where = "";                 # WHERE restriction
+$where .= " AND (label LIKE '" . join("' OR label LIKE '", @$label) . "')" if defined $label;
+$where .= " AND fault = $fault" if defined $fault;
+
+$sql .= $where if length $where > 0;
+$sql .= " ORDER BY rand() LIMIT $limit" if defined $limit;
+
+my $rows = $db->selectall_arrayref( $sql ) or die "Unable to execute SQL: $DBI::errstr";
+$db->disconnect;
+
+my %assertion;
+my %cannot_find_file;
+my %failed_real_name;
+my %failed_close;
+my %unable_access;
+my %statistics_file;
+my %unknown;
+my %bad;
+foreach my $row ( @$rows ) {
+    my $id1 = $$row[0];
+    my $id2 = $$row[1];
+    my $path = $$row[2];
+
+    my $name = "$id1.$id2";
+
+    unless (defined $path) {
+        $bad{$name} = "No path";
+        next;
+    }
+
+    my $log = "$path.log";
+
+    if ($log =~ /^neb:/) {
+        my $new = eval { $neb->find($log); };
+        $bad{$name} = $log unless $new;
+        $log = $new;
+    }
+
+    my $file;
+    open $file, $log;
+    my $found = 0;              # Found something interesting
+    while (<$file>) {
+        if (/Assertion .* failed/ or /Assertion failed in/) {
+            $assertion{$name} = $log;
+            $found = 1;
+            last;
+        }
+        if (/Cannot find file (\S+)/) {
+            $cannot_find_file{$name} = $1;
+            $found = 1;
+            last;
+        }
+        if (/Unable to access file (\S+)/) {
+            $unable_access{$name} = $1;
+            $found = 1;
+            last;
+        }
+        if (/failed to determine real name from template for (\S+)/) {
+            $failed_real_name{$name} = $1;
+            $found = 1;
+            last;
+        }
+        if (/failed to close (\S+)/) {
+            $failed_close{$name} = $1;
+            $found = 1;
+            last;
+        }
+        if (/Unable to open statistics file/) {
+            $statistics_file{$name} = $log;
+            $found = 1;
+            last;
+        }
+    }
+    close $file;
+    $unknown{$name} = $log unless $found;
+}
+
+print "\n";
+print "Total: " . (scalar @$rows) . "\n";
+print "\n";
+
+print "Assertion failures: " . ( scalar keys %assertion ) . "\n";
+foreach my $name (keys %assertion) {
+    print "$name: $assertion{$name}\n";
+}
+print "\n";
+
+print "'Cannot find file' errors: " . ( scalar keys %cannot_find_file ) . "\n";
+foreach my $name (keys %cannot_find_file) {
+    print "$name: $cannot_find_file{$name}\n";
+}
+print "\n";
+
+print "'Unable to access file' errors: " . ( scalar keys %unable_access ) . "\n";
+foreach my $name (keys %unable_access) {
+    print "$name: $unable_access{$name}\n";
+}
+print "\n";
+
+print "'Failed to determine real name' errors: " . ( scalar keys %failed_real_name ) . "\n";
+foreach my $name (keys %failed_real_name) {
+    print "$name: $failed_real_name{$name}\n";
+}
+print "\n";
+
+print "'Failed to close' errors: " . ( scalar keys %failed_close ) . "\n";
+foreach my $name (keys %failed_close) {
+    print "$name: $failed_close{$name}\n";
+}
+print "\n";
+
+print "Unknown errors: " . ( scalar keys %unknown ) . "\n";
+foreach my $name (keys %unknown) {
+    print "$name: $unknown{$name}\n";
+}
+print "\n";
+
+print "Bad logs: " . ( scalar keys %bad ) . "\n";
+foreach my $name (keys %bad) {
+    print "$name: $bad{$name}\n";
+}
+print "\n";
