#!/usr/bin/perl
# 
# ***** BEGIN LICENSE BLOCK *****
# Zimbra Collaboration Suite Server
# Copyright (C) 2009, 2010, 2013, 2014, 2015, 2016 Synacor, Inc.
#
# 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,
# version 2 of the License.
#
# 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 <https://www.gnu.org/licenses/>.
# ***** END LICENSE BLOCK *****
# 
use strict;
use warnings;
use lib qw(/opt/zimbra/common/lib/perl5);
use RRDs;
use DBI;
use Zimbra::Mon::Zmstat;
use Getopt::Std;

Zimbra::Mon::Zmstat::getLocalConfig();
my $logger_directory     = $Zimbra::Mon::Zmstat::LC{'logger_data_directory'};
my $logger_rrd_directory = "$logger_directory/rrds";
my $compressed_row_limit = 500;
my $primary_resolution   = 30;

sub gethostid($$) {
    my ($dbh, $hostname) = @_;
    my $sth = $dbh->prepare("SELECT id FROM hosts WHERE zm_hostname = ?");
    $sth->bind_param(1, $hostname);
    $sth->execute;
    my $rows = $sth->fetchall_arrayref([0]);
    die $sth->err if $sth->err;
    $sth->finish;
    die "$hostname not found\n" if @$rows == 0;
    return [ map $_->[0], @$rows ];
}

sub getdatastructure($$$) {
    my ($dbh, $file, $hostid) = @_;
    my $ref = undef;
    if (@$hostid > 0) {
    	my $ps = join(',', ('?') x @$hostid);
        my $sth = $dbh->prepare(qq{
            SELECT col_name, col_name_19, col_num, rrd_file, host_id FROM rrds
            WHERE csv_file = ? AND host_id IN ($ps)
        });
        my @params = ($file, @$hostid);

        $sth->execute(@params);
        die $sth->err if $sth->err;
        $ref = $sth->fetchall_hashref('col_name');
    } else {
        my $sth = $dbh->prepare(q{
            SELECT col_name, col_name_19, col_num, rrd_file, zm_hostname, host_id
            FROM rrds join hosts ON hosts.id = rrds.host_id
            WHERE csv_file = ?
        });
        $sth->bind_param(1, $file);
        $sth->execute;
        die $sth->err if $sth->err;
        my $data = $sth->fetchall_arrayref({});
        my %hash;
        foreach my $row (@$data) {
            my $host = $row->{'zm_hostname'};
            my $colname = $row->{'col_name'};
            $hash{$host} = {} if (!exists $hash{$host});
            $hash{$host}->{'host_id'} = $row->{'host_id'};
            $hash{$host}->{$colname} = {};
            $hash{$host}->{$colname}->{'col_name_19'} = $row->{'col_name_19'};
            $hash{$host}->{$colname}->{'col_num'}     = $row->{'col_num'};
            $hash{$host}->{$colname}->{'col_unit'}    = $row->{'col_unit'};
            $hash{$host}->{$colname}->{'rrd_file'}    = $row->{'rrd_file'};
        };
        $ref = \%hash;
    }
    keys %$ref > 0 ? $ref : undef;
}

sub get_rrd_file($$) {
    my ($hostid, $rrd_file) = @_;
    sprintf "%s/%d-%d.rrd", $logger_rrd_directory, $hostid, $rrd_file;
}

sub get_rra_info($) {
    my ($sample_file) = @_;

    my $info = RRDs::info($sample_file);

    my $rra_pattern = 'rra[%d].pdp_per_row';
    my $index = 0;
    my $rra_not_found = 0;
    my @rra_resolution;
    for ($index = 0; !$rra_not_found; $index++) {
        my $rra_rec = sprintf $rra_pattern, $index;
        $rra_not_found = !exists($info->{$rra_rec});
        if (!$rra_not_found) {
            push(@rra_resolution, $info->{$rra_rec} * 30);
        }
    }
    @rra_resolution;
}

sub dump_data($$$$$) {
    my ($hostid, $structure, $start, $end, $compress, $resolution) = @_;
    my %col19col;
    my @start;
    my @end;
    my @res;
    @start = ("-s", $start) if ($start);
    @end   = ("-e", $end)   if ($end);
    @res   = ("-r", $resolution) if ($resolution);

    my %files;

    foreach my $column (keys %$structure) {
    	next if $column eq 'host_id';
    	
        $col19col{$structure->{$column}->{'col_name_19'}} = $column;
        $files{$structure->{$column}->{'rrd_file'}} = 1;
    }
    my @columns;
    my @rows;
    my ($rrd_start, $rrd_step);
    foreach my $file (keys %files) {
        my $rrd = get_rrd_file($hostid, $file);
        next if ! -f $rrd;
        my ($names, $data);
        ($rrd_start, $rrd_step, $names, $data)  =
                RRDs::fetch($rrd, @start, @end, @res, 'AVERAGE');
        if (@rows == 0) {
            @rows = @$data;
        } else {
            for (my $i = 0; $i < @$data; $i++) {
                next if ($i > $#rows);
                push(@{$rows[$i]}, @{$data->[$i]});
            }
        }
        push(@columns, @$names);
        my $err = RRDs::error();
        die $err if $err;
    }

    if ($compress && @rows > $compressed_row_limit) {
        my @resolutions = get_rra_info(get_rrd_file($hostid, (keys %files)[0]));
        my $first = $rrd_start;
        my $last = $rrd_start + (scalar(@rows) * $rrd_step);
        my $delta = $last - $first;
        my $resolution = $rrd_step;
        my $r = scalar @rows;
        for (my $i = 0; $i < @resolutions && $r > $compressed_row_limit; $i++) {
            $resolution = $resolutions[$i];
            $r = int($delta / $resolutions[$i]);
        }
        $first = $first - ($first % $resolution);
        $last  = $last  - ($last  % $resolution);
        &dump_data($hostid, $structure, $first, $last, 0, $resolution);
        return;
    }
    @columns = map { $col19col{$_} } @columns;
    unshift @columns, 'timestamp';
    print join(",", @columns) . "\n";
    my $ts = $rrd_start;

    foreach my $row (@rows) {
        my @r = map { defined($_) ? $_ : '' } @$row;
        unshift @r, $ts;
        $ts += $rrd_step;
        print join(",", @r) . "\n";
    }
}

sub list_files($$$) {
    my ($dbh, $host, $csv) = @_;
    my $sth;
    if (defined($host) && defined($csv)) {
        $sth = $dbh->prepare(q{
            SELECT DISTINCT(rrds.col_name), col_unit FROM rrds JOIN hosts
            ON rrds.host_id = hosts.id
            LEFT OUTER JOIN rrd_column_type
            ON rrds.col_name = rrd_column_type.col_name
               AND rrds.csv_file = rrd_column_type.csv_file
            WHERE zm_hostname = ? AND rrds.csv_file = ?
        });
        $sth->bind_param(1, $host);
        $sth->bind_param(2, $csv);
    } elsif (defined($host)) {
        $sth = $dbh->prepare(q{
            SELECT DISTINCT(csv_file) FROM rrds JOIN hosts
            ON rrds.host_id = hosts.id
            WHERE zm_hostname = ?
        });
        $sth->bind_param(1, $host);
    } else {
        $sth = $dbh->prepare("SELECT DISTINCT(csv_file) FROM rrds");
    }
    $sth->execute;
    die $sth->err if $sth->err;
    my $ref = $sth->fetchall_arrayref([0,1]);
    foreach my $row (@$ref) {
    	my $line = defined($row->[1]) ? $row->[0] . " :: " . $row->[1] : $row->[0];
        print $line . "\n";
    }
    exit;
}
sub list_hosts($) {
    my $dbh = shift @_;
    my $sth = $dbh->prepare("SELECT DISTINCT(zm_hostname) FROM hosts");
    $sth->execute;
    die $sth->err if $sth->err;
    my $ref = $sth->fetchall_arrayref([0]);
    foreach my $row (@$ref) {
        print $row->[0] . "\n";
    }
    exit;
}
sub run() {
    my %options;
    my $hostid = [];
    getopts('f:h:s:e:lnc', \%options);

    my $filename = $options{f};
    my $start    = $options{s};
    my $end      = $options{e};

    my $dbh = DBI->connect(
            "dbi:SQLite:dbname=$logger_directory/logger.sqlitedb", "", "");

    list_files($dbh, $options{h}, $options{f}) if exists $options{l};
    list_hosts($dbh) if exists $options{n};

    usage() if (!exists $options{f});

    $hostid = gethostid($dbh, $options{h}) if (exists $options{h});
    my $structure = getdatastructure($dbh, $filename, $hostid);
    die "no structure found for $filename" if (!$structure);
    if (@$hostid == 0) {
        foreach my $host (keys %$structure) {
            print "\nHost: $host\n\n";
            dump_data($structure->{$host}->{'host_id'}, $structure->{$host}, $start, $end, $options{c});
        }
    } else {
    	my $somekey = (keys(%$structure))[0];
        dump_data($structure->{$somekey}->{'host_id'}, $structure, $start, $end, $options{c});
    }
    $dbh->disconnect;
}

sub usage() {
    print STDERR <<'EOF';

Usage: zmrrdfetch -f group [-h hostname] [-s time] [-e time] [-l] [-n]
    -f        - group name to retrieve stats for
    -h        - hostname to filter returned stats from -f
    -s        - start time, rrd syntax
    -e        - end time, rrd syntax
    -l        - list groups
                with -h - lists groups for specified host
                with -h and -f - list columns for specified host
    -n        - list hostnames
    -c        - reduce results; try to reduce resultset less than 500 rows
EOF
    exit 1;
}

run();
