#!/usr/bin/env perl

################################################################################
## disk tool
##
## Lists the GIDs of certain types of runs (pinned, expired pinned, archived;
## active) with an option to determine their on-disk sizes using 'du'.
##
################################################################################

use strict;
use warnings;

use Getopt::Long;

use lib $ENV{'NMI_LIB'} || "/nmi/lib";
use NmiConf;
use NmiTools;

# Does perl not have these as part of the language strictly to annoy me?
use constant { true => 1, false => 0 };

## Argument parsing.
my( $opt_help, $opt_debug, $opt_days, $opt_host, $opt_show_sizes, $opt_store_sizes, $opt_force_recalculate );
GetOptions( 'help'              => \$opt_help,
            'debug'             => \$opt_debug,
            'show-sizes'        => \$opt_show_sizes,
            'store-sizes'       => \$opt_store_sizes,
            'force-recalculate' => \$opt_force_recalculate,
            'host=s'            => \$opt_host,
            'nmiconf=s'         => \$main::opt_nmiconf
          );

if( defined( $opt_help ) ) {
    print_usage();
    exit 0;
    }

# This doesn't /have/ to be true, but it's much more convient for me.
if( $opt_store_sizes ) { $opt_show_sizes = true; }

my %conf = NmiConf::init( $main::opt_nmiconf );
use NmiDatabaseVariables;
require NmiDatabase;

## What were we asked to do?
my $command = shift( @ARGV );
if( ! defined( $command ) ) {
    print( STDERR "You must supply a command.\n" );
    print_usage();
    exit 0;
    }

my %dispatchTable = (
                'list-pinned-runs'      => \&list_pinned_runs,
                'list-expired-runs'     => \&list_expired_runs,
                'list-archived-runs'    => \&list_archived_runs,
                'list-cleaned-runs'     => \&list_cleaned_runs,
                'histogram'             => \&histogram,
                'update-run-sizes'      => \&update_run_sizes
                );
                
if( ! exists $dispatchTable{ $command } ) {
    print( STDERR "No such command '$command'.\n" );
    print_usage();
    exit 0;
    }

$dispatchTable{ $command }->( $opt_host );
    
## End of script.
exit 0;

sub print_usage {
    print( "Usage: $0 <options> <command>
    
Options may be unambigously abbreviated:
    --help         Print this help.
    --debug        Print debugging information.
    --nmiconf      Use the specified Metronome configuration file.
    --show-sizes   Prints size in megabytes (and sums) of each directory.
    --store-sizes  Stores shown sizes in database.  (Implies show-sizes.)
    --host         Limit run information to the given host.

Commands may be one of the following:
    list-pinned-runs    List runs whose pins have not expired.
    list-expired-runs   List runs whose pins have expired.
    list-archived-runs  List never-pinned runs (believed to be) still on disk.
    list-cleaned-runs   List never-pinned runs (believed to be) cleaned.
    list-active-runs    Not implemented.
                        (Not exclusive of the first three lists.)
    histogram           Display runs day-by-day (started and finished) over
                        the last week.
    update-run-sizes	Determine which runs have unknown sizes and find them.
    clean               Not implemented.\n" );
    }

########################################
## update_run_sizes()
########################################
sub update_run_sizes {
    my $host = $_[0];
    my $query = qq/
    
SELECT $NmiDatabaseVariables::DB_TABLE_RUN.*
 FROM $NmiDatabaseVariables::DB_TABLE_RUN LEFT OUTER JOIN run_sizes
 ON $NmiDatabaseVariables::DB_TABLE_RUN.runid = run_sizes.runid
 WHERE (
    ( archived_size IS NULL AND cleaned_size IS NULL )
 OR ( archived != 0 AND archived_size IS NULL )
 OR ( archived =0 AND cleaned_size IS NULL )
 )

/;
    my @rows = getRuns( $query, $host );
   
    # Set things up so that printGIDs() does what we want.
    $opt_show_sizes = true;
    $opt_store_sizes = true;
    $opt_force_recalculate = true;
    printGIDs( \@rows );
    }

########################################
## histogram()
########################################
sub histogram {
    my $host = $_[0];
    my $query = qq/SELECT * FROM $NmiDatabaseVariables::DB_TABLE_RUN WHERE /;
    
    my( @startsByDay, @endsByDay );
    
    # FIXME: accept as command-line flag?
    my $numberOfDays = 7;
    for( my $daysAgo = 1; $daysAgo <= $numberOfDays; ++$daysAgo ) {
        my $startSpec = qq/start > (NOW() - INTERVAL $daysAgo DAY) /;
        $startSpec .=  qq/AND start < (NOW() - INTERVAL ($daysAgo - 1) DAY) /;
        @{$startsByDay[$daysAgo]} = getRuns( $query . $startSpec, $host );
        
        my $endSpec = qq/finish > (NOW() - INTERVAL $daysAgo DAY)/;
        $endSpec .=  qq/AND finish < (NOW() - INTERVAL ($daysAgo - 1) DAY) /;
        @{$endsByDay[$daysAgo]} = getRuns( $query . $endSpec, $host );
        }
    
    # FIXME: respect 'show-sizes' flag.    
    print( "\tdaysAgo\t|\tstarted\t|\tended\n" ); 
    for( my $daysAgo = 1; $daysAgo <= $numberOfDays; ++$daysAgo ) {
        # Work around broken syntax in Perl.
        my @starts = @{$startsByDay[$daysAgo]};
        my @ends = @{$endsByDay[$daysAgo]};
        print( "\t" . $daysAgo . "\t|\t" . $#starts . "\t|\t" . $#ends . "\n" );
        }
    }
    
########################################
## list-pinned-runs()
########################################
sub list_pinned_runs {
    my $query = qq/SELECT * FROM $NmiDatabaseVariables::DB_TABLE_RUN WHERE archive_results_until >= NOW()/;
    my $host = $_[0];
    my @rows = getRuns( $query, $host );
    printGIDs( \@rows );
    }

########################################
## list-expired-runs()
########################################
sub list_expired_runs {
    my $query = qq/SELECT * FROM $NmiDatabaseVariables::DB_TABLE_RUN WHERE archive_results_until < NOW()/;
    my $host = $_[0];
    my @rows = getRuns( $query, $host );
    printGIDs( \@rows );
    }

########################################
## list-archived-runs()
########################################
sub list_archived_runs {
    # Archived runs /exclusive of/ pinned or expired runs.
    my $query = qq/SELECT * FROM $NmiDatabaseVariables::DB_TABLE_RUN WHERE archived != 0 AND archive_results_until IS NULL/;
    my $host = $_[0];
    my @rows = getRuns( $query, $host );
    printGIDs( \@rows );
    }
    
########################################
## list-cleaned-runs()
########################################
sub list_cleaned_runs {
    # Cleaned runs /exclusive of/ pinned or expired runs.
    my $query = qq/SELECT * FROM $NmiDatabaseVariables::DB_TABLE_RUN WHERE archived = 0 AND archive_results_until IS NULL/;
    my $host = $_[0];
    my @rows = getRuns( $query, $host );
    printGIDs( \@rows );
    }
    
########################################
## Utility functions.
########################################

sub printGIDs {
    my @rows = @{ $_[0] };
    
    if( ! $opt_show_sizes ) {
        foreach my $row (@rows) {
            print( $row->{ "filepath" } . "/" . $row->{ "gid" } . "\n" );
            }
        }
    else {
        my $sum = 0;
        foreach my $row (@rows) {
            # Grab the size from the database, if available.
            my $query =	qq/SELECT archived_size, cleaned_size FROM /;
            $query .= 	qq/run_sizes WHERE runid = $row->{ 'runid' }/;
            my @sizeRows = getQueryResults( $query );
          
            my $column;
            if( $row->{ "archived" } != 0 ) {
                $column = "archived_size";
                }
            else {
                $column = "cleaned_size";
                }
            my $size = $sizeRows[0]->{ $column };

            if( $opt_force_recalculate || ! defined( $size ) ) {
                # Arguably, we should store bytes, rather than kilobytes,
                # but I don't feel like running 'du' over the whole
                # filesystem again, and it's better to be explicit
                # about sizes, in case the default changes.
                my $command = qq#du -s -k $row->{ "filepath" }/$row->{ "gid" }#;
                if( $opt_debug ) { print( $command . "\n" ); }
                my $du = `$command`; print( $du );
                my $path; ( $size, $path ) = split( / |\t/, $du );
                }
            
            if( defined( $size ) ) { $sum += $size; }
            if( defined( $opt_store_sizes ) && defined( $size ) ) {
                my $runid = $row->{ "runid" };
                my $gid = $row->{ "gid" };

                # We'll use some MySQL-specific hackery to make my life easier.
                my $query = qq/INSERT INTO run_sizes SET runid = $runid, $column = $size /;
                $query .=   qq/ON DUPLICATE KEY UPDATE $column = $size/;
                if( $opt_debug ) { print( $query . "\n" ); }
                NmiDatabase::execute( $query );
                }
            }
        # 'du -s' actually reports kilobytes.
        print( ($sum / 1024.0) . "M\n" );
        }
    }    
    
sub getQueryResults {
    my $query = $_[0];
    my $handle = NmiDatabase::execute( $query );
    my @rows;
    while( my $row = $handle->fetchrow_hashref() ) {
        push( @rows, $row );
        }
    $handle->finish();
    return @rows;
    }

sub getRuns {
    my( $query, $host ) = @_;
    if( defined( $host ) ) {
        $query .= qq/ AND host = '$host'/;
        }
    if( $opt_debug ) {
        print( $query . "\n" );
        }
    return getQueryResults( $query );
    }
