#!/usr/bin/perl
#============================================================= -*-perl-*-
#
# BackupPC_rrdUpdate: Nightly pool graph update
#
# DESCRIPTION
#
#   Usage: BackupPC_rrdUpdate
#
# AUTHORS
#   Alexander Moisseev  <moiseev@mezonplus.ru>
#   Craig Barratt       <cbarratt@users.sourceforge.net>
#
# COPYRIGHT
#   Copyright (C) 2013  Alexander Moisseev and Craig Barratt
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#========================================================================
#
# Version 4.4.0, released 20 Jun 2020.
#
# See http://backuppc.sourceforge.net.
#
#========================================================================

use strict;
no utf8;

use lib "/usr/share/backuppc/lib";
use BackupPC::Lib qw( :BPC_DT_ALL );
use File::Path;
use Data::Dumper;
our(%Info);

die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
my $TopDir = $bpc->TopDir();
my $BinDir = $bpc->BinDir();
my $LogDir = $bpc->LogDir();
my %Conf   = $bpc->Conf();

if ( !-x $Conf{RrdToolPath} ) {
    print("$Conf{RrdToolPath} is not a valid executable\n");
    exit(0);
}

my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
if ( $err ) {
    print("Can't connect to server ($err)\n");
    exit(1);
}
my $reply = $bpc->ServerMesg("status info");
$reply = $1 if ( $reply =~ /(.*)/s );
eval($reply);

my $Now     = time();
my $NowRnd0 = 86400 * int($Now / 86400);
my $NowRnd1 = $NowRnd0 + 86400;
my $RRDFile = "$LogDir/poolUsage.rrd";

if ( -f $RRDFile ) {
    my $lastUpdate = $bpc->cmdSystemOrEval([$Conf{RrdToolPath}, "last", $RRDFile]);
    if ( $? ) {
        print $lastUpdate;
        exit(1);
    } elsif ( $lastUpdate == $NowRnd1 ) {
        print "skipping repeated RRD update for the same day: last update time is $lastUpdate\n";
        exit(0);
    }
}

RRDUpdate();
RRDGraph(4);
RRDGraph(52);
exit(0);

#
# Feeds pool size data values into an RRD $RRDFile
#
sub RRDUpdate
{
    #
    # We store data in RRD with predicted time of next update.
    #
    if ( !-f $RRDFile ) {
        if ( (
            $err = $bpc->cmdSystemOrEval([
                $Conf{RrdToolPath},         "create",
                $RRDFile,                   "--step",
                "86400",                    "--start",
                "$NowRnd0 - 1d",            "DS:c3kB:GAUGE:172800:0:U",
                "DS:tpkB:GAUGE:172800:0:U", "DS:p3kB:GAUGE:172800:0:U",
                "DS:p4kB:GAUGE:172800:0:U", "DS:c4kB:GAUGE:172800:0:U",
                "RRA:LAST:0:1:732"
            ])
        ) ) {
            print "$err";
        }
    }

    #
    # Calculate pool size totals prior to poolng and compression
    #
    my $sizeTot;
    foreach my $host ( sort(keys(%{$bpc->HostInfoRead()})) ) {
        my @Backups = $bpc->BackupInfoRead($host);
        for ( my $i = 0 ; $i < @Backups ; $i++ ) {
            $sizeTot += $Backups[$i]{size} || 0;
        }
    }
    $sizeTot = $sizeTot / 1024;

    if ( (
        $err = $bpc->cmdSystemOrEval([
            $Conf{RrdToolPath},
            "update",
            $RRDFile,
            $NowRnd1 . ":"
              . $Info{"cpoolKb"} . ":"
              . $sizeTot . ":"
              . $Info{"poolKb"} . ":"
              . $Info{"pool4Kb"} . ":"
              . $Info{"cpool4Kb"}
        ])
    ) ) {
        print "$err";
    } else {
        printf("RRD updated: date %s; cpoolKb %f; total %f; poolKb %f; pool4Kb %f; cpool4Kb %f\n",
            $NowRnd1, $Info{"cpoolKb"}, $sizeTot, $Info{"poolKb"}, $Info{"pool4Kb"}, $Info{"cpool4Kb"});
    }
}

#
# Generate pool size RRDtool graph image
#
sub RRDGraph
{
    my($weeks) = @_;
    my($fdOut);
    my @poolMax;

    #
    # Get each pool max value from RRD
    #
    $bpc->cmdSystemOrEvalLong(
        [
            $Conf{RrdToolPath},            "graphv",
            "-",                           "--end=$NowRnd1",
            "--start=end-${weeks}w",       "DEF:p3kB=$RRDFile:p3kB:LAST",
            "DEF:c3kB=$RRDFile:c3kB:LAST", "DEF:p4kB=$RRDFile:p4kB:LAST",
            "DEF:c4kB=$RRDFile:c4kB:LAST", "PRINT:p3kB:MAX:%lf",
            "PRINT:c3kB:MAX:%lf",          "PRINT:p4kB:MAX:%lf",
            "PRINT:c4kB:MAX:%lf",
        ],
        sub {
            if ( $_[0] =~ /^print\[([0-3])\] = "([.,0-9]+)"$/ ) {
                $poolMax[$1] = $2 unless ( $2 == 0 );
            }
        },
        1,
        undef
    );

    my $poolSizeGraph = [
        "$Conf{RrdToolPath}",          "graph",
        "-",                           "--end=$NowRnd1",
        "--start=end-${weeks}w",       "--title=BackupPC Pool Size (${weeks} weeks)",
        "--vertical-label=",           "--width=600",
        "--height=100",                "--rigid",
        "--alt-autoscale-max",         "--base=1024",
        "--logarithmic",               "--units=si",
        "--color",                     "BACK#FFFFFF",
        "--slope-mode",                "--imgformat=PNG",
        "--font",                      "TITLE:10:",
        "--font",                      "AXIS:8:",
        "--font",                      "LEGEND:8:",
        "--font",                      "UNIT:8:",
        "--font-render-mode",          "mono",
        "DEF:p3kB=$RRDFile:p3kB:LAST", "DEF:c3kB=$RRDFile:c3kB:LAST",
        "DEF:p4kB=$RRDFile:p4kB:LAST", "DEF:c4kB=$RRDFile:c4kB:LAST",
        "DEF:tpkB=$RRDFile:tpkB:LAST", "CDEF:p3B=p3kB,1024,*",
        "CDEF:c3B=c3kB,1024,*",        "CDEF:p4B=p4kB,1024,*",
        "CDEF:c4B=c4kB,1024,*",        "CDEF:tpB=tpkB,1024,*"
    ];

    #
    # Draw stacked pool graphs (and legend entry) for non empty pools
    #
    push(@$poolSizeGraph,
        'AREA:p3B#FFADA7:Pool  V3 in bytes               ',
        'GPRINT:p3B:LAST:Current\\:%8.2lf %s',
        'GPRINT:p3B:AVERAGE:Average\\:%8.2lf %s',
        'GPRINT:p3B:MAX:Maximum\\:%8.2lf %s\n')
      if $poolMax[0];

    push(@$poolSizeGraph,
        'AREA:c3B#FFDBA7:CPool V3 in bytes               :STACK',
        'GPRINT:c3B:LAST:Current\\:%8.2lf %s',
        'GPRINT:c3B:AVERAGE:Average\\:%8.2lf %s',
        'GPRINT:c3B:MAX:Maximum\\:%8.2lf %s\n')
      if $poolMax[1];

    push(@$poolSizeGraph,
        'AREA:p4B#97E7A5:Pool  V4 in bytes               :STACK',
        'GPRINT:p4B:LAST:Current\\:%8.2lf %s',
        'GPRINT:p4B:AVERAGE:Average\\:%8.2lf %s',
        'GPRINT:p4B:MAX:Maximum\\:%8.2lf %s\n')
      if $poolMax[2];

    push(@$poolSizeGraph,
        'AREA:c4B#95B8DB:CPool V4 in bytes               :STACK',
        'GPRINT:c4B:LAST:Current\\:%8.2lf %s',
        'GPRINT:c4B:AVERAGE:Average\\:%8.2lf %s',
        'GPRINT:c4B:MAX:Maximum\\:%8.2lf %s\n')
      if $poolMax[3];

    #
    # Draw total line graph
    #
    push(@$poolSizeGraph,
        'LINE1:tpB#FF0000:Prior to pooling and compression',
        'GPRINT:tpB:LAST:Current\\:%8.2lf %s',
        'GPRINT:tpB:AVERAGE:Average\\:%8.2lf %s',
        'GPRINT:tpB:MAX:Maximum\\:%8.2lf %s\\n');

    if ( !open($fdOut, ">", "$LogDir/poolUsage$weeks.png") ) {
        print("Can't open/create $LogDir/poolUsage$weeks.png\n");
        return;
    }
    $bpc->cmdSystemOrEvalLong($poolSizeGraph, sub { print $fdOut $_[0] }, 1, undef);
    close($fdOut);
}
