#!/usr/bin/perl -w
#  Copyright (C) 2003  Stanislav Sinyagin
#  Copyright (C) 2003  Christian Schnidrig
#
#  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 2 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, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

# Stanislav Sinyagin <ssinyagin@k-open.com>

use strict;
use warnings;
BEGIN { require '/usr/share/torrus/conf_defaults/torrus-config.pl'; }

use Getopt::Long;

use Torrus::ConfigTree;
use Torrus::SiteConfig;
use Torrus::SchedulerInfo;
use Torrus::Log;

exit(1) unless Torrus::SiteConfig::verify();

my $tree;
my $report_config;
my $report_runtime;
my $clear_treestats;

my $help_needed;


my $ok = GetOptions('tree=s'   => \$tree,
                    'config'   => \$report_config,
                    'runtime'  => \$report_runtime,
                    'clear'    => \$clear_treestats,
                    'help'     => \$help_needed);

if( not $ok or
    not $tree or
    not ( $report_config or $report_runtime or $clear_treestats  ) or
    $help_needed or scalar(@ARGV) > 0 )
{
    print STDERR "Usage: $0 --tree=NAME [options...]\n",
    "Options:\n",
    "  --tree=NAME     tree name\n",
    "  --config        report scheduler configuration\n",
    "  --runtime       report scheduler runtime statistics\n",
    "  --clear         clear scheduler statistics for specific tree\n",
    "  --help          this help message\n";
    exit 1;
}


if( not Torrus::SiteConfig::treeExists( $tree ) )
{
    Error('Tree ' . $tree . ' does not exist');
    exit 1;
}


&Torrus::DB::setSafeSignalHandlers();

if( $clear_treestats )
{
    my $stats = new Torrus::SchedulerInfo( -Tree => $tree, -WriteAccess => 1 );
    $stats->clearAll();
    print STDERR "Statistics cleared for tree $tree\n";
    exit 0;
}

thickLine();
printf("Torrus version %s\n", '2.09');
printf("Datasources tree: %s\n", $tree);
printf("Date: %s\n\n", scalar( localtime( time() ) ) );

if( $report_config )
{
    my $config_tree = new Torrus::ConfigTree( -TreeName => $tree );
    if( not defined($config_tree) )
    {
        Error("Configuration is not ready");
        exit 1;
    }

    my $stats = { 'collectorLeaves' => {}, 'monitorLeaves' => 0 };
    
    collectStats( $config_tree, $stats );

    thickLine();
    printf("Scheduler configuration report\n\n");

    foreach my $instance ( sort {$a<=>$b} keys %{$stats->{'collectorLeaves'}} )
    {
        printf("Collector leaves for instance #%d: %d\n",
               $instance, 
               $stats->{'collectorLeaves'}{$instance});
    }
    
    printf("Total monitor leaves: %d\n\n", $stats->{'monitorLeaves'});

    printf("Scheduled leaves by type:\n");

    foreach my $type ( sort keys %{$stats->{'leavesPerType'}} )
    {
        printf("  %10s  %-10d\n", $type,
               $stats->{'leavesPerType'}{$type});
    }
    printf("\n");

    foreach my $instance ( sort {$a<=>$b} keys %{$stats->{'collectorLeaves'}} )
    {
        if( $stats->{'collectorLeaves'}{$instance} > 0 )
        {
            &Torrus::DB::checkInterrupted();
            
            printf("Collector execution timeline for instance #%d:\n",
                   $instance);
            reportTimeline( $stats->{'collectorSchedule'}{$instance} );
        }
    }

    if( $stats->{'monitorLeaves'} > 0 )
    {
        printf("Monitor execution timeline:\n");
        reportTimeline( $stats->{'monitorSchedule'} );
    }
}

if( $report_runtime )
{
    my @reportFormats =
        (
         { 'label'   => 'Running Time',
           'varname' => 'RunningTime' },
         
         { 'label'   => 'Late Start',
           'varname' => 'LateStart' },
         
         { 'label'   => 'Too Long',
           'varname' => 'TooLong' },

         { 'label'   => 'RRD Queue',
           'varname' => 'RRDQueue' },

         { 'label'   => 'Raw Queue',
           'varname' => 'RawQueue' }

         );

    my @counterFormats =
        (
         { 'label'   => 'running cycles passed',
           'varname' => 'NTimesRunningTime' },
         
         { 'label'   => 'late starts',
           'varname' => 'NTimesLateStart' },

         { 'label'   => 'too long runs',
           'varname' => 'NTimesTooLong' },

         { 'label'   => 'overrun periods',
           'varname' => 'CountOverrunPeriods' },

         { 'label'   => 'missed periods',
           'varname' => 'CountMissedPeriods' }
         );
    
    my $sInfo = new Torrus::SchedulerInfo( '-Tree' => $tree );
    exit(1) if not defined( $sInfo );

    my $stats = $sInfo->readStats();

    thickLine();
    printf("Scheduler runtime report\n\n");

    my $periodicTasks = {};
    foreach my $taskId ( keys %{$stats} )
    {
        my ($type, $taskName, $instance, $period, $offset) =
            split( ':', $taskId );
        if( $type eq 'P' )
        {
            $periodicTasks->{$taskName}{$instance}{$period}{$offset} = $taskId;
        }
    }
    
    foreach my $taskName ( sort keys %{$periodicTasks} )
    {
        foreach my $instance ( sort {$a<=>$b}
                               keys %{$periodicTasks->{$taskName}} )
        {
            foreach my $period
                ( sort {$a<=>$b}
                  keys %{$periodicTasks->{$taskName}{$instance}} )
            {
                foreach my $offset
                    ( sort {$a<=>$b}
                      keys %{$periodicTasks->{$taskName}{$instance}{$period}} )
                {
                    &Torrus::DB::checkInterrupted();
                    
                    my $taskId =
                        $periodicTasks->{$taskName}{$instance}{
                            $period}{$offset};
                    my $ref = $stats->{$taskId};
                
                    printf("Task: %s, Instance: %d, " .
                           "Period: %d seconds, Offset: %d seconds\n",
                           $taskName, $instance, $period, $offset);
                
                    foreach my $format ( @counterFormats )
                    {
                        if( defined( $ref->{$format->{'varname'}} ) )
                        {
                            printf("%5d %s\n",
                                   $ref->{$format->{'varname'}},
                                   $format->{'label'} );
                        }
                    }

                    thinLine();
                    printf("%-15s%-10s%-10s%-10s%-10s\n",
                           '', 'Min', 'Max', 'Average', 'Exp Average');

                    foreach my $format ( @reportFormats )
                    {
                        my $varname =  $format->{'varname'};
                        if( defined( $ref->{'Min' . $varname} ) )
                        {
                            printf("%-15s%-10d%-10d%-10.1f%-10.1f\n",
                                   $format->{'label'},
                                   $ref->{'Min' . $varname},
                                   $ref->{'Max' . $varname},
                                   $ref->{'Avg' . $varname},
                                   $ref->{'ExpAvg' . $varname});
                        }
                    }

                    thinLine();
                    printf("\n");
                }
            }
        }
    }
}

thickLine();
exit 0;


sub collectStats
{
    my $config_tree = shift;
    my $stats = shift;
    my $token = shift;

    if( not defined( $token ) )
    {
        $token = $config_tree->token('/');
    }

    my @children = $config_tree->getChildren( $token );

    foreach my $ctoken ( @children )
    {
        &Torrus::DB::checkInterrupted();
        
        if( $config_tree->isSubtree( $ctoken ) )
        {
            collectStats( $config_tree, $stats, $ctoken );
        }
        elsif( $config_tree->isLeaf( $ctoken ) )
        {
            if( $config_tree->getNodeParam( $ctoken, 'ds-type' )
                eq 'collector' )
            {
                my $instance =
                    $config_tree->getNodeParam
                    ( $ctoken, 'collector-instance' );
                
                $stats->{'collectorLeaves'}{$instance}++;
                
                my $type = 'c:' .
                    $config_tree->getNodeParam( $ctoken, 'collector-type' );

                my $period =
                    $config_tree->getNodeParam( $ctoken, 'collector-period' );
                $period = int( $period ); # make sure we're talking integers

                my $offset = $config_tree->
                    getNodeParam( $ctoken, 'collector-timeoffset' );
                
                $stats->{'leavesPerType'}{$type}++;
                $stats->{'collectorSchedule'}{$instance}{$period}{
                    $offset}{$type}++;
            }
            
            if( defined( $config_tree->getNodeParam( $ctoken, 'monitor' ) ) )
            {
                $stats->{'monitorLeaves'}++;
                my $type = 'monitor';

                my $period =
                    $config_tree->getNodeParam( $ctoken, 'monitor-period' );
                $period = int( $period ); # make sure we're talking integers

                my $offset = $config_tree->
                    getNodeParam( $ctoken, 'monitor-timeoffset' );
                $offset = int($offset) % $period;

                $stats->{'leavesPerType'}{$type}++;
                $stats->{'monitorSchedule'}{$period}{$offset}{$type}++;
            }
        }
    }
    return;
}


# caluclate and print the schedule
sub reportTimeline
{
    my $schedule = shift;
    
    # calculate the common period length (least common multiple)
    my $lcm = 0;
    foreach my $period ( keys %{$schedule} )
    {
        my $x = $period;
        my $y = $lcm;
        my $z;
        if( $y == 0 )
        {
            $lcm = $x;
        }
        else
        {
            if( $x < $y )
            {
                my $tmp = $y;
                $y = $x;
                $x = $tmp;
            }
            while( $y != 0 )
            {
                $z = $x % $y;
                $x = $y;
                $y = $z;
            }
            $lcm = $lcm * $period / $x;
        }
    }

    printf("Least common period: %d seconds\n", $lcm);

    # populate the common period
    my %cp;
    my $chunks = 0;
    foreach my $period ( keys %{$schedule} )
    {
        foreach my $offset ( keys %{$schedule->{$period}} )
        {
            $chunks++;
            foreach my $type ( keys %{$schedule->{$period}{$offset}} )
            {
                for( my $i = 0; $i < ($lcm / $period); $i++ )
                {
                    $cp{$i * $period + $offset}{'col'}{$type} +=
                        $schedule->{$period}{$offset}{$type};
                }
            }
        }
    }
    printf("Number of chunks: %d \n\n", $chunks );

    # calculate interval lengths

    my $previous;
    my $first;
    foreach my $time ( sort { $a <=> $b } keys %cp )
    {
        if( not defined($first) )
        {
            $first = $time;
        }
        else
        {
            $cp{$previous}{'endtime'} = $time;
        }
        $previous = $time;
    }
    $cp{$previous}{'endtime'} = $lcm + $first;

    # print results

    thinLine();
    printf("%-10s%-10s%-20s%-10s\n",
           'Offset', 'Interval', 'Type', 'Data');
    printf("%-10s%-10s%-20s%-10s\n",
           '(sec)', '(sec)', '', 'sources');
    thinLine();

    foreach my $time ( sort { $a <=> $b } keys %cp )
    {
        foreach my $type ( keys %{$cp{$time}{'col'}} )
        {
            printf("%-10d%-10d%-20s%-10d\n",
                   $time,
                   $cp{$time}{'endtime'} - $time,
                   $type,
                   $cp{$time}{'col'}{$type} );
        }
    }
    thinLine();
    printf("\n");
    return;
}


sub thickLine
{
    foreach my $i ( 1..75 )
    {
        print '=';
    }
    print "\n";
    return;
}

sub thinLine
{
    foreach my $i ( 1..70 )
    {
        print '-';
    }
    print "\n";
    return;
}





# Local Variables:
# mode: perl
# indent-tabs-mode: nil
# perl-indent-level: 4
# End:
