Estimate disk usage.pl

Aus VDR Wiki
Wechseln zu: Navigation, Suche

Berechnung, wann der Plattenplatz voraussichtlich zur Neige gehen wird.

Das nachfolgende Skript habe ich auf meinem VDR Server in /etc/cron.hourly liegen. Es schreibt eine Nagios Statusmeldung raus.

Datei
$PATH/estimate_disk_usage.pl
#!/usr/bin/perl

use strict;
use warnings;

use Time::Local;
use Data::Dumper;

# VDR's video directory
my $vdrdir = "/scratch/vdr";
my $debug = 1;
my $backlogdir = "$vdrdir/.estimate_disk_usage_backlog";
my $fps = 25;
my $worst_case_ratio = 1.05;
my $nagios_status_file = "/var/spool/nagios-status/vdr_disk_usage_estimation";
my $warning_ndays = 1.0;
my $critical_ndays = 0.8;

if ( ! -d $backlogdir ) {
    system("install", "-d", "-m", "700", $backlogdir) and die $!;
}

# zuerst die Backlogs erstellen (wir speichern für jede aufgenommene
# Sendung deren mittlere Bitrate mitsamt dem Transponder-Code)

my $fh;
open ( $fh, "-|", "find", $vdrdir, "-iname", "index.vdr", "-mmin", "+60" ) or die $!;
while (<$fh>) {
    chomp;
    my $index_vdr_fn = $_;
    my ( $channel_id, $md5sum, $lmod ) = &get_t ( index_vdr => $index_vdr_fn );
    my ( $nframes, $bytesize, $mbitrate ) = &get_size ( index_vdr => $index_vdr_fn );
    my %data = (
        nframes    => $nframes,
        bytesize   => $bytesize,
        mbitrate   => $mbitrate,
        md5sum     => $md5sum,
        lmod       => $lmod,
        channel_id => $channel_id,
    );
    &store_bitrate ( %data );
}
close $fh or die $!;

# dann bestimmen wir die maximale Bitrate der letzten N Aufzeichnungen pro Transponder-Code
# aus den Backlogs
my $last_n_recs = 10;
my %max_mbitrate = ();
open ( $fh, "-|", "find", $backlogdir, "-mindepth", "1", "-maxdepth", "1", "-type", "d", "-printf", '%P\n' ) or die $!;
while (<$fh>) {
    chomp;
    my $channel_id = $_;
    if ( $debug ) {
        print "$channel_id\n";
    }
    my $max = 0;
    my $fh2;
    open ( $fh2, "find \"$backlogdir/$channel_id\" -mindepth 1 -maxdepth 1 -type f -printf \%P\\\\n | sort -n | tail -n $last_n_recs |" ) or die $!;
    while (<$fh2>) {
        chomp;
        my $timestamp = $_;
        if ( $debug ) {
            print "$timestamp\n";
        }
        my $fh3;
        open ( $fh3, "<", "$backlogdir/$channel_id/$timestamp" ) or die $!;
        chomp ( my $mbitrate = <$fh3> );
        close $fh3 or die $!;
        if ( $mbitrate > $max ) {
            $max = $mbitrate;
        }
    }
    close $fh2 or die $!;
    $max_mbitrate{$channel_id} = $max;
}
close $fh or die $!;

if ( $debug ) {
    print Dumper \%max_mbitrate;
}

# für den Fall, daß wir keine Erfahrungswerte bei einem speziellen Kanal haben, nehmen wir
# eine globale Bitrate
my $global_max_mbitrate = 0.1;
foreach my $r ( keys %max_mbitrate ) {
    if ( $max_mbitrate{$r} > $global_max_mbitrate ) {
        $global_max_mbitrate = $max_mbitrate{$r};
    }
}
if ( $debug ) {
    print "global max mbit rate = $global_max_mbitrate\n";
}

# Timer-Liste einlesen und parsen

my @timers = ();
open ( $fh, "-|", 'echo -e "lstt id\nquit" | netcat localhost 2001' ) or die $!;
while (<$fh>) {
    chomp;
    # 250-10 1:T-8468-258-14:2008-06-26:2350:0130:50:99:Aufgemerkt! Pelzig unterh�lt sich~Comedytalk Deutschland 2008:<epgsearch><channel>1 - ARD</channel><searchtimer>aufgemerkt pelzig</searchtimer><start>1214517000</start><stop>1214523000</stop><s-id>27</s-id><eventid>59954</eventid></epgsearch>
    my $timer = $_;
    if ( $timer =~ /^250.\d+\s+\d+:(.*?):(\d+)-(\d+)-(\d+):(\d{2,2})(\d{2,2}):(\d{2,2})(\d{2,2}):/ ) {
        my ( $channel_id, $y, $m, $d, $hhs, $mms, $hhe, $mme ) = ( $1, $2, $3, $4, $5, $6, $7, $8 );
        my $start = timelocal( 0, $mms, $hhs, $d, $m-1, $y-1900 );
        my $end = timelocal( 0, $mme, $hhe, $d, $m-1, $y-1900 );
        if ( $end < $start ) {
            $end += 86400;
        }
        if ( $end < $start ) {
            die;
        }
        if ( $debug ) {
            print "timer = $timer\n";
            print "channel_id = $channel_id\n";
            print "Date = " . localtime($start) . "\n";
        }
        my %timer = (
            start        => $start,
            channel_id   => $channel_id,
            end          => $end,
        );
        push @timers, \%timer;
    }
}
close $fh or die $!;

if ( $debug ) {
    print Dumper \@timers;
}

# und schließlich gehen wir sequentiell die Timer-Liste durch, um festzustellen, wann der
# Speicherplatz knapp werden wird.
# (bei gleichzeitig laufenden Timern/Aufzeichnungen gehen wir davon aus, daß es ausreichend ist,
# die geplanten Aufzeichnungen sequentiell nach ihren Aufzeichnungsstarts durchzugehen und den
# benötigten Plattenplatz sequentiell zu akkumulieren, anstatt die Timer parallel durch
# Partitionierung an den Überschneidungsstellen durchzugehen)

@timers = sort { $a->{start} <=> $b->{start} } @timers;

if ( $debug ) {
    print Dumper \@timers;
}

my $free = &get_free_diskspace();
my $disk_full_time = 0;

foreach my $t ( @timers ) {
    my $estimated_size_kb = ( $t->{end} - $t->{start} ) * $worst_case_ratio * &get_mbitrate($t->{channel_id}) * 1024 / 8;
    die if ($estimated_size_kb < 0);
    if ( $debug ) {
        print Dumper $t;
        print "estimated size = $estimated_size_kb kb\n";
    }
    if ( $free < $estimated_size_kb && !$disk_full_time ) {
        $disk_full_time = int($t->{start} + ($t->{end} - $t->{start}) * ($free/$estimated_size_kb));
        if ( $debug ) {
            print "disk full time = $disk_full_time\n";
        }
    }
    $free -= $estimated_size_kb;
}

my $delta = $disk_full_time - time();
if ( $debug ) {
    print "delta = $delta (local time = ".localtime(time()).", disk full time = ".localtime($disk_full_time)."\n";
}

if ( $delta < 0 ) {
    &write_nagios_status_file ( "0:est. time of disk full is inf, min. free disk space after all scheduled recordings is ".int($free/1024)." MB" );
}
elsif ( $delta <= $critical_ndays * 86400 ) {
    &write_nagios_status_file ( "2:est. time of disk full is ".localtime($disk_full_time).", min. free disk space after all scheduled recordings is ".int($free/1024)." MB" );
}
elsif ( $delta <= $warning_ndays * 86400 ) {
    &write_nagios_status_file ( "1:est. time of disk full is ".localtime($disk_full_time).", min. free disk space after all scheduled recordings is ".int($free/1024)." MB" );
}
else {
    &write_nagios_status_file ( "0:est. time of disk full is ".localtime($disk_full_time).", min. free disk space after all scheduled recordings is ".int($free/1024)." MB" );
}


if ( $debug ) {
    print "estimated free disk space after all scheduled recordings = $free kb\n";
}

exit 0;

sub write_nagios_status_file {
    my ( $errmsg ) = @_;
   
    if ( $debug ) {
        print "nagios msg = $errmsg\n";
    }
    
    my $fh;
    open ( $fh, ">", $nagios_status_file ) or die $!;
    print $fh "$errmsg\n";
    close $fh or die $!;
}

sub get_mbitrate {
    my ( $channel_id ) = @_;
    if ( exists $max_mbitrate{$channel_id} ) {
        return $max_mbitrate{$channel_id};
    }
    return $global_max_mbitrate;
}

sub get_free_diskspace {
    my $fh;
    my $free = -1;
    open ( $fh, "-|", "df", "-k", $vdrdir ) or die $!;
    while ( <$fh> ) {
        chomp;
        if ( /^\S+\s+\d+\s+\d+\s+(\d+)\s+/ ) {
            $free = $1;
        }
    }
    close $fh or die $!;
    if ( $free < 0 ) {
        die;
    }
    if ( $debug ) {
        print "free disk space = $free kb\n";
    }
    return $free;
}

sub store_bitrate {
    my ( %p ) = @_;
    my $dir = "$backlogdir/$p{channel_id}";
    system("install", "-d", "-m", "700", $dir) and die $!;
    my $fh;
    open ( $fh, ">", "$dir/$p{lmod}" ) or die $!;
    print $fh $p{mbitrate};
    close $fh or die $!;
}

sub get_size {
    my ( %p ) = @_;
    my $fn = $p{index_vdr};
    my $dirname = $fn;
    $dirname =~ s/index\.vdr$//;
    my $nframes = (stat $fn)[7] / 8;
    if ( $debug ) {
        print "get_size():nframes=\"$nframes\"\n";
        print "get_size():dirname=\"$dirname\"\n";
    }
    if ( $nframes < 1 ) {
        return ( 0, 0 );
    }
    my $fh;
    my $totalsize = 0;
    open ( $fh, "-|", "find", $dirname, "-type", "f" ) or die $!;
    while (<$fh>) {
        chomp;
        if ( /\/\d+\.vdr/i ) {
            $totalsize += (stat $_)[7];
        }
    }
    close $fh or die $!;
    my $mbitrate = ( 8 * $totalsize / ( $nframes / $fps ) ) / 1048576;
    if ( $debug ) {
        print "get_size():totalsize=\"$totalsize\"\n";
        print "get_size():bitrate=$mbitrate Mbit/s\n";
    }
    return ( $nframes, $totalsize, $mbitrate );
}

sub get_t {
    my ( %p ) = @_;
    my $fn = $p{index_vdr};
    $fn =~ s/index\.vdr$/info.vdr/i;
    my $fh;
    my $result = "";
    my $lmod = (stat $fn)[9];
    open ( $fh, "<", $fn ) or die $!;
    while ( <$fh> ) {
        chomp;
        if ( /^C\s+([^\s]+)\s*$/ ) {
            $result = $1;
        }
    }
    close $fh or die $!;
    if ( $debug ) {
        print "get_t()=\"$result\"\n";
    }
    if ( $result eq "" ) {
        die;
    }
    my $md5sum = `md5sum -b "$fn"`;
    die $! if $?;
    chomp ( $md5sum );
    $md5sum =~ s/\s+.*$//;
    return ( $result, $md5sum, $lmod );
}