package Mogstored::ChildProcess::DiskUsage;
use strict;
use base 'Mogstored::ChildProcess';
my $docroot;
sub pre_exec_init {
my $class = shift;
$SIG{TERM} = 'DEFAULT'; # override custom one from earlier
$ENV{MOG_DOCROOT} = Perlbal->service('mogstored')->{docroot};
}
sub run {
$docroot = $ENV{MOG_DOCROOT};
die "\$ENV{MOG_DOCROOT} not set" unless $docroot;
die "\$ENV{MOG_DOCROOT} not set to a directory" unless -d $docroot;
# (runs in exec'd child process)
$0 = "mogstored [diskusage]";
select((select(STDOUT), $|++)[0]);
my $start_ppid = getppid();
# Discover whether or not we have GNU df.
my $gnu_df = '';
`df -P / 2>/dev/null >/dev/null`;
if ($? eq 0) {
$gnu_df = '-P';
}
while (1) {
look_at_disk_usage($gnu_df);
sleep 10;
# shut ourselves down if our parent mogstored
# has gone away.
my $ppid = getppid();
exit(0) unless $ppid == $start_ppid && kill(0,$ppid);
}
}
sub look_at_disk_usage {
my $err = sub { warn "$_[0]\n"; };
my $path = $ENV{MOG_DOCROOT};
$path =~ s!/$!!;
my $gnu_df = shift;
# find all devices below us
my @devnum;
if (opendir(D, $path)) {
@devnum = grep { /^dev\d+$/ } readdir(D);
closedir(D);
} else {
return $err->("Failed to open $path: $!");
}
foreach my $devnum (@devnum) {
my $rval = `df $gnu_df -l -k $path/$devnum`;
my $uperK = ($rval =~ /512-blocks/i) ? 2 : 1; # units per kB
foreach my $l (split /\r?\n/, $rval) {
next unless $l =~ /^(.+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)\s+(.+)$/;
my ($dev, $total, $used, $avail, $useper, $disk) = ($1, $2, $3, $4, $5, $6);
unless ($disk =~ m!$devnum/?$!) {
$disk = "$path/$devnum";
}
# FIXME: We're stupidly throwing away the 'avail' value here.
# This causes mogilefs to run aground when used with ext
# partitions using reserved space. Drop the reserved space from
# the total, and in the future add available to the device table
# and just use that.
$total = $used + $avail;
# create string to print
my $now = time;
my $output = {
time => time(),
device => $dev, # /dev/sdh1
total => int($total / $uperK), # integer: total KiB blocks
used => int($used / $uperK), # integer: used KiB blocks
available => int($avail / $uperK), # integer: available KiB blocks
'use' => $useper, # "45%"
disk => $disk, # mount point of disk (/var/mogdata/dev8), or path if not a mount
};
if ($ENV{MOG_DEV_USAGE_VIA_DU}) {
my $size = `du -k -c -s $path/$devnum`;
if ($size =~ /^(\d+)/) {
$output->{used} = $1;
}
}
# size of old file we'll be overwriting in place (we'll want
# to pad with newlines/spaces, before we truncate it, for
# minimizing races)
my $ufile = "$disk/usage";
my $old_size = (-s $ufile) || 0;
my $mode = $old_size ? "+<" : ">";
# string we'll be writing
my $new_data = "";
foreach (sort keys %$output) {
$new_data .= "$_: $output->{$_}\n";
}
my $new_size = length $new_data;
my $pad_len = $old_size > $new_size ? ($old_size - $new_size) : 0;
$new_data .= "\n" x $pad_len;
# write the file, all at once (with padding) then remove padding
my $rv = open(my $fh, $mode, $ufile);
unless ($rv) {
$err->("Unable to open '$ufile' for writing: $!");
next;
}
unless (syswrite($fh, $new_data)) {
close($fh);
$err->("Error writing to '$ufile': $!");
next;
}
truncate($fh, $new_size) if $pad_len;
close($fh);
}
}
}
1;