Estimate disk usage.pl
Aus VDR Wiki
Version vom 26. Oktober 2008, 00:51 Uhr von 85.178.233.215 (Diskussion)
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.
$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 );
}