# -*-perl-*-

# Cricket: a configuration, polling and data display wrapper for RRD files
#
#    Copyright (C) 1998 Jeff R. Allen and WebTV Networks, 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; 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., 675 Mass Ave, Cambridge, MA 02139, USA.

#    This is still very early in development. It's designed to collect Perfmon
#    counters from Windows NT systems. Perfmon is similiar in many respects to
#    the way Cricket does things. It keeps a config file resident in memory
#    with all sorts of definitions from counter scaling, to what type of
#    counter it is. While it makes things convenient, it also requires a whole
#    bunch of disassembly of it's scheme to effectively put it into Cricket.
#
#    The math seems to be very wrong for some counters, which seems odd as all
#    the formulas were ripped straight from Microsoft's documentation on the
#    perflib SDK at:
#    http://msdn.microsoft.com/library/en-us/perfmon/hh/pdh/perfdata_6di9.asp
#
#    I'm continually developing this as we need a good solution in-house for
#    flexible historical trending of perfmon counters.
#
#    I realize how god-awful slow this is, and it's really Perfmon's fault.
#    Most counters are durational and require some sort of wait-time to get
#    two instances loaded to to comparisons/time based calculations. Also,
#    every time Perfmon is initiated on a system, you're required to get a
#    list of every counter on the system which is very time consuming. Blame
#    my employer, not me! I'm considering making this application threaded to
#    boost performance.
#
#    Bugs I'm currently aware of:
#       * Yes there are many missing counter types. This is being worked on.
#         I'd like to think the ones which are missing right now wouldn't be
#         commonly used anyway (PERF_AVERAGE_BULK) or wouldn't be relevant
#         to Cricket anyway (PERF_COUNTER_TEXT).
#       * I know PERF_RAW_FRACTION and PERF_RAW_BASE are supremely messed up
#         right now and I'm not sure why. I'm using the formulas Microsoft
#         defined on their web site (100*x/b) and it returns screwy results.
#       * On the same vein, this breaks the instance checker which Warn()s if
#         there are more than one matches for a selected counter.
#       * That's all I can think of right now. After spending over a week
#         working on this I think for the most part it works pretty well!
#
#    If you find any glaring bugs, omissions, or errors please feel free to
#    email me so I can fix it.
#
#    Thanks - Adam Meltzer <ameltzer@microsoft.com>

#    Minidoc:
#
#    Perfmon counters use the following syntax for input:
#    perfmon:hostname:object:counter:instance:special options
#
#    object is the top-level class such as "Processor" or "Memory". This is a
#    required entry.
#    counter is the "% Processor Time" or "% Committed Bytes in Use". This is
#    also a required entry.
#    instance is optional, but many counters have instances such as "_Total"
#    duration is optional, it defaults to 1 second. For many perfmon counters,
#    there is a required duration to get a time-based calculation.
#    special options is optional. Currently it accepts 'noscale' which turns
#    off scaling if perfmon so demands it.

use Common::Log;
use Common::Util;
use Win32::PerfLib;
use strict;

$main::gDSFetch{'perfmon'} = \&perfmonFetch;

sub perfmonFetch {
    my($dsList, $name, $target) = @_;
    my %pmonFetches;
    my $counter = {};
    my $rcounter = {};
    my(@results);

    foreach my $line (@{$dsList}) {
        my @components = split(/:/, $line);
        my ($index, $server, $myobject, $mycounter, $myinstance,$myduration,$myoptions);

        if($#components+1 < 3) {
            Error("Malformed datasource line: $line.");
            return();
        }

        $index      = shift(@components);
        $server     = shift(@components) || missing("host",$line);
        $myobject   = shift(@components) || 'System';
        $mycounter  = shift(@components) || 'System Up Time';
        $myinstance = shift(@components) || '';
        $myoptions  = shift(@components) || '';

        $pmonFetches{$index} = "$server:$myobject:$mycounter:$myinstance:$myoptions";
    }

    my($perflib);  my $count=0;  my $num_fetch=keys %pmonFetches;
    DSLOOP: while(my ($index, $ilRef) = each %pmonFetches) {
        my $pr1 = {};
        my $critical;
        my ($matches,$noscale) = int(0);
        my $value;
        my $fractop;
        my ($perfTimeOnly,$perfFreqOnly);

        my($server, $myobject, $mycounter, $myinstance, $myoptions) = split(/:/, $ilRef);

        $myobject =~ s/=/:/g;

        my @options = split(/,/, $myoptions);

        foreach my $op (@options) {
            if($op =~ /^noscale$/i) {
                $noscale = 1;
            }

            if($op =~ /^perftime$/i) {
                $perfTimeOnly = 'normal';
            }

            if($op =~ /^perftime100ns$/i) {
                $perfTimeOnly = '100ns';
            }
            if($op =~ /^perffreq$/i) {
                $perfFreqOnly = 1;
            }
            if($op =~ /^base$/i) {
                $fractop = 'base';
            }
            if($op =~ /^fraction$/i) {
                $fractop = 'fraction';
            }
        }

        if(!($counter->{$server})) {

            my $tempref = {};
            Win32::PerfLib::GetCounterNames($server, $tempref) or $critical = $!;
            $counter->{$server} = $tempref;
        }

        if( (!$counter->{$server}) || $counter->{$server} == 0) {
            Error("No counters were retrieved from $server! Not wasting any more time on this ds: $ilRef");
            push @results, "$index:U";
            next DSLOOP;
        }

        # Get a list of mappings from CounterName -> id
        if (! $rcounter->{$server}) {
            foreach my $k (sort keys %{$counter->{$server}}) {
                $rcounter->{$server}->{lc($counter->{$server}->{$k})} = $k;
            }
        }

        $myobject = lc($myobject);

        $perflib = new Win32::PerfLib($server) if ($count++ == 0);
	#if(!($perflib)) { return; }
        $perflib->GetObjectList($rcounter->{$server}->{$myobject}, $pr1);
        $perflib->Close() if ($count >= $num_fetch);

        if($perfTimeOnly) {
            if($perfTimeOnly eq 'normal') {
                Debug("perfTimeOnly is $perfTimeOnly $pr1->{'PerfTime'}");
                my $value = Common::Util::fixNum($pr1->{'PerfTime'});

                push @results, "$index:$value";
                next DSLOOP;
            } elsif ($perfTimeOnly eq '100ns') {
                Debug("perfTimeOnly is $perfTimeOnly $pr1->{'PerfTime100nSec'}");
                my $value = Common::Util::fixNum($pr1->{'PerfTime100nSec'});
                push @results, "$index:$value";
                next DSLOOP;
            }
        }

        if($perfFreqOnly) {
            Debug("perfFreqOnly is $perfFreqOnly $pr1->{'PerfFreq'}");
            my $value = sprintf("%0.20g", $pr1->{'PerfFreq'});
            push @results, "$index:$value";
            next DSLOOP;
        }

        foreach my $level2 (sort keys %{$pr1->{'Objects'}}) {
            my $gDenn = $pr1->{'PerfTime100nSec'};
            my $gDen  = $pr1->{'PerfTime'};
            my $gTb   = $pr1->{'PerfFreq'};
            my $oldvalue;

            if($pr1->{'Objects'}->{$level2}->{'NumInstances'} > 0) {
                foreach my $level4 (sort keys %{$pr1->{'Objects'}->{$level2}->{'Instances'}}) {
                    my $oldvalue;
                    my $ir1 = $pr1->{'Objects'}->{$level2}->{'Instances'}->{$level4};
                    foreach my $level5 (sort keys %{$ir1->{'Counters'}}) {
                        my $cr1 = $ir1->{'Counters'}->{$level5};
                        my $saneCounterName = $counter->{$server}->{$cr1->{'CounterNameTitleIndex'}};
                        my $saneInstanceName = $ir1->{'Name'};
                        if(lc($saneCounterName) eq lc($mycounter)) {
                            if(lc($saneInstanceName) eq lc($myinstance) || !defined $myinstance) {
                                $value = getCounter($cr1,$index,$ilRef,$noscale,$fractop);
                                if($value ne "skip") {
                                    $oldvalue = $value;
                                    $matches++;
                                } else {
                                    $value = $oldvalue;
                                }
                            }
                        }
                    }
                }
            }
            foreach my $level4 (sort keys %{$pr1->{'Objects'}->{$level2}->{'Counters'}}) {
                my $cr1 = $pr1->{'Objects'}->{$level2}->{'Counters'}->{$level4};
                my $saneCounterName = $counter->{$server}->{$cr1->{'CounterNameTitleIndex'}};

                if(lc($saneCounterName) eq lc($mycounter)) {
                    $value = getCounter($cr1,$index,$ilRef,$noscale,$fractop);
                    if($value ne "skip") {
                        $oldvalue = $value;
                        $matches++;
                    } else {
                        $value = $oldvalue;
                    }
                }
            }
        }

        if($matches > 1) {
            Debug("More than one matches for datasource line: $ilRef. Only last instance will be used!");
        }

        if($matches < 1) {
            Error("No matches for datasource line: $ilRef");
            push @results, "$index:U";
        } else {
            push @results, "$index:$value";
        }
    }
    return @results;
}

sub getCounter {
    my ($cr1,$index,$ilRef,$noscale,$fractop) = @_;
    my ($crap,$junk,$cn) = split(/:/, $ilRef);
    my $scaler = $cr1->{'DefaultScale'};
    my $ctype = Win32::PerfLib::GetCounterType($cr1->{'CounterType'});

    if(defined($fractop)) {
        return "skip" if($fractop eq "base" && $ctype =~ /FRACTION/);
        return "skip" if($fractop eq "fraction" && $ctype =~ /BASE/);
    }

    Debug("ds$index COUNTER_TYPE is $ctype");

    if(defined $fractop) {
        Debug("FractOp is set to: $fractop");
    }
    my $value = $cr1->{'Counter'};

    if(defined($noscale) && $noscale < 1) {
        $value = $value * (10**$scaler);
    }

    $value = Common::Util::fixNum($value);

    return $value;
}

sub missing {
    my ($missing,$line) = @_;
    Error("Missing perfmon $missing in datasource: $line");
    return();
}

1;

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