#!@@PERL@@
# hp2000_ - Munin plugin for HP P2000 StorageWorks
# Copyright (C) 2012 Redpill Linpro AS
#
# Author: Trygve Vea <tv@redpill-linpro.com>
#
# 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Graph stats from the HP P2000 StorageWorks unit
#
# Parameters supported:
#
#     config
#
# Configurable variables
#
#     env.host     - http-host
#     env.secret   - username_password
#     env.aspect   - vdisk-statistics, controller-statistics or disk-statistics,
#                    volume-statistics.  If not specified, the plugin will attempt
#                    to detect the aspect from the symlink-name using regex -
#                    (/vdisk/, /controller/, /volume/ and /disk/)
#
# Suggested config:
#
#   [hp2000_san1_*]
#   host_name san1
#   env.host 10.0.0.1
#   env.secret username_password
#
# Then create the following symlinks, pointing to the plugin:
#
#  hp2000_san1_vdisk
#  hp2000_san1_disk
#  hp2000_san1_controller
#  hp2000_san1_volume
#
# And direct your munin.conf to the node configured with this plugin, using
# node 'san1'.
#
#    TODO:
#
#       * Graph sensor-data
#       * Graph disk errors
#       * Graph cache hit vs. misses in one single descriptive graph
#       * Set thresholds
#       * Add useful descriptions
#
# Magic markers:
#%# family=contrib

use LWP::UserAgent;
use Digest::MD5 qw(md5_hex);
use XML::LibXML;
use Munin::Plugin;

need_multigraph();

my $cmd = 'vdisk-statistics'; # Default.

if ( $0 =~ /vdisk/ ) { $cmd = 'vdisk-statistics'; }
elsif ( $0 =~ /controller/ ) { $cmd = 'controller-statistics'; }
elsif ( $0 =~ /volume/ ) { $cmd = 'volume-statistics'; }
elsif ( $0 =~ /disk/ ) { $cmd = 'disk-statistics'; }

if ( exists $ENV{'aspect'} ) { $cmd = $ENV{'aspect'}; }
my $host = $ENV{'host'};
my $secret = $ENV{'secret'};

my @plugins = ('vdisk-statistics', 'controller-statistics', 'disk-statistics', 'volume-statistics');
my @graph_parameters = ('title','total','order','scale','vlabel','args', 'category');
my @field_parameters = ('graph', 'min', 'max', 'draw', 'cdef', 'warning', 'colour', 'info', 'type');

my %aspects = (
    'volume-statistics' => {
        'volume_bandwidth' => {
            'title' => 'Bandwidth',
            'category' => 'volume',
            'vlabel' => '(- reads / + writes) bytes per second',
            'min' => 0,
            'values' => {
                'data-read-numeric' => {
                    'short' => 'br',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'read bps',
                    'min' => 0,
                    'graph' => 'no',
                },
                'data-written-numeric' => {
                    'short' => 'bw',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'bps',
                    'min' => 0,
                    'negative' => 'br',
                }
            }
        },
        'volume_iops' => {
            'title' => 'I/O Operations',
            'category' => 'volume',
            'vlabel' => '(- reads / + writes) ops per second',
            'min' => 0,
            'values' => {
                'number-of-reads' => {
                    'short' => 'ior',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'reads',
                    'min' => 0,
                    'graph' => 'no'
                },
                'number-of-writes' => {
                    'short' => 'iow',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'iops',
                    'min' => 0,
                    'negative' => 'ior'
                }
            }
        },
        'volume_cache_hits' => {
            'title' => 'Cache hits',
            'category' => 'volume',
            'vlabel' => '(- reads / + writes) hits per second',
            'min' => 0,
            'values' => {
                'read-cache-hits' => {
                    'short' => 'rch',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'reads',
                    'min' => 0,
                    'graph' => 'no'
                },
                'write-cache-hits' => {
                    'short' => 'wch',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'cache hits',
                    'min' => 0,
                    'negative' => 'rch'
                }
            }
        },
        'volume_cache_misses' => {
            'title' => 'Cache misses',
            'category' => 'volume',
            'vlabel' => '(- reads / + writes) misses per second',
            'min' => 0,
            'values' => {
                'read-cache-misses' => {
                    'short' => 'rcm',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'reads',
                    'min' => 0,
                    'graph' => 'no'
                },
                'write-cache-misses' => {
                    'short' => 'wcm',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'cache misses',
                    'min' => 0,
                    'negative' => 'rcm'
                }
            }
        },
        'volume_small_destages' => {
            'title' => 'Small Destages',
            'category' => 'volume',
            'vlabel' => 'destages per second',
            'min' => 0,
            'values' => {
                'small-destages' => {
                    'short' => 'destages',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'destages',
                    'min' => 0,
                }
            }
        },
        'volume_stripe_destages' => {
            'title' => 'Full Stripe Write Destages',
            'category' => 'volume',
            'vlabel' => 'destages per second',
            'min' => 0,
            'values' => {
                'full-stripe-write-destages' => {
                    'short' => 'destages',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'destages',
                    'min' => 0,
                }
            }
        },
        'volume_readahead' => {
            'title' => 'Read-Ahead Operations',
            'category' => 'volume',
            'vlabel' => 'ops per second',
            'min' => 0,
            'values' => {
                'read-ahead-operations' => {
                    'short' => 'ops',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'ops',
                    'min' => 0,
                }
            }
        },
    },
    'vdisk-statistics' => {
        'vdisk_bandwidth' => {
            'title' => 'Bandwidth',
            'category' => 'vdisk',
            'vlabel' => '(- reads / + writes) bytes per second',
            'min' => 0,
            'values' => {
                'data-read-numeric' => {
                    'short' => 'br',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'read bps',
                    'min' => 0,
                    'graph' => 'no',
                },
                'data-written-numeric' => {
                    'short' => 'bw',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'bps',
                    'min' => 0,
                    'negative' => 'br',
                }
            }
        },
        'vdisk_iops' => {
            'title' => 'I/O Operations',
            'category' => 'vdisk',
            'vlabel' => '(- reads / + writes) ops per second',
            'min' => 0,
            'values' => {
                'number-of-reads' => {
                    'short' => 'ior',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'reads',
                    'min' => 0,
                    'graph' => 'no'
                },
                'number-of-writes' => {
                    'short' => 'iow',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'iops',
                    'min' => 0,
                    'negative' => 'ior'
                }
            }
        }
    },
    'controller-statistics' => {
        'cpu_load' => {
            'title' => 'CPU Usage',
            'category' => 'CPU',
            'vlabel' => 'usage in %',
            'min' => 0,
            'values' => {
                'cpu-load' => {
                    'short' => 'cpu',
                    'type' => 'GAUGE',
                    'draw' => 'LINE1',
                    'label' => '% load',
                    'min' => 0,
                },
            }
        },
        'uptime' => {
            'title' => 'Controller Uptime',
            'category' => 'System',
            'vlabel' => 'seconds up',
            'min' => 0,
            'values' => {
                'power-on-time' => {
                    'short' => 'uptime',
                    'type' => 'GAUGE',
                    'draw' => 'LINE1',
                    'label' => 'uptime',
                    'min' => 0,
                },
            }
        },
        'controller_iops' => {
            'title' => 'Controller I/O Operations',
            'category' => 'Controller',
            'vlabel' => '(- reads / + writes) ops per second',
            'min' => 0,
            'values' => {
                'number-of-reads' => {
                    'short' => 'ior',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'reads',
                    'min' => 0,
                    'graph' => 'no'
                },
                'number-of-writes' => {
                    'short' => 'iow',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'iops',
                    'min' => 0,
                    'negative' => 'ior'
                }
            }
        },
        'controller_bandwidth' => {
            'title' => 'Bandwidth',
            'category' => 'Controller',
            'vlabel' => '(- reads / + writes) bytes per second',
            'min' => 0,
            'values' => {
                'data-read-numeric' => {
                    'short' => 'br',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'read bytes',
                    'min' => 0,
                    'graph' => 'no'
                },
                'data-written-numeric' => {
                    'short' => 'bw',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'write bytes',
                    'min' => 0,
                    'negative' => 'br'
                }
            }
        },
        'controller_cache_hits' => {
            'title' => 'Cache hits',
            'category' => 'Cache',
            'vlabel' => '(- reads / + writes) hits per second',
            'min' => 0,
            'values' => {
                'read-cache-hits' => {
                    'short' => 'rch',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'reads',
                    'min' => 0,
                    'graph' => 'no'
                },
                'write-cache-hits' => {
                    'short' => 'wch',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'cache hits',
                    'min' => 0,
                    'negative' => 'rch'
                }
            }
        },
        'controller_cache_misses' => {
            'title' => 'Cache misses',
            'category' => 'Cache',
            'vlabel' => '(- reads / + writes) misses per second',
            'min' => 0,
            'values' => {
                'read-cache-misses' => {
                    'short' => 'rcm',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'reads',
                    'min' => 0,
                    'graph' => 'no'
                },
                'write-cache-misses' => {
                    'short' => 'wcm',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'cache misses',
                    'min' => 0,
                    'negative' => 'rcm'
                }
            }
        },
    },
    'disk-statistics' => {
        'disk_bandwidth' => {
            'title' => 'Bandwidth',
            'category' => 'disk',
            'vlabel' => '(- reads / + writes) bytes per second',
            'min' => 0,
            'values' => {
                'data-read-numeric' => {
                    'short' => 'br',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'read bytes',
                    'min' => 0,
                    'graph' => 'no'
                },
                'data-written-numeric' => {
                    'short' => 'bw',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'bytes',
                    'min' => 0,
                    'negative' => 'br'
                }
            }
        },
        'disk_iops' => {
            'title' => 'Disk I/O Operations',
            'category' => 'disk',
            'vlabel' => '(- reads / + writes) ops per second',
            'min' => 0,
            'values' => {
                'number-of-reads' => {
                    'short' => 'ior',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'reads',
                    'min' => 0,
                    'graph' => 'no'
                },
                'number-of-writes' => {
                    'short' => 'iow',
                    'type' => 'DERIVE',
                    'draw' => 'LINE1',
                    'label' => 'iops',
                    'min' => 0,
                    'negative' => 'ior'
                }
            }
        },

    }
);


# Authenticate with the controller, get session key.
my $md5_hash = md5_hex( $secret );

$ua = LWP::UserAgent->new;
$url = "http://$host/api/login/" . $md5_hash;
$req = HTTP::Request->new(GET => $url);
$res = $ua->request($req);

# Parse the XML content using LibXML to obtain the session key
my $parser  = XML::LibXML->new();
my $doc     = $parser->parse_string( $res->content );
my $root    = $doc->getDocumentElement;
my @objects = $root->getElementsByTagName('OBJECT');
my @props   = $objects[0]->getElementsByTagName('PROPERTY');
my $sessionKey;

foreach my $prop ( @props ) {
     my $name = $prop->getAttribute('name');
     # print "Property = " . $name . "\n";
     if( $name eq 'response' ) {
         $sessionKey = $prop->textContent;
     }
}

# Got $sessionKey, use in subsequent requests.

$url = "http://$host/api/show/$cmd";

$ua = LWP::UserAgent->new;
$req = HTTP::Request->new(GET => $url);
$req->header('sessionKey' => $sessionKey );
$req->header('dataType' => 'ipa' );
$res = $ua->request($req);

#print $res->content;

$doc     = $parser->parse_string( $res->content );
$root    = $doc->getDocumentElement;
@objects = $root->getElementsByTagName('OBJECT');

foreach my $obj ( @objects ) {
    next unless ( $obj->getAttribute('basetype') eq $cmd );
    my @splitname;
    my $name;

    @props = $obj->getElementsByTagName('PROPERTY');

    foreach my $prop ( @props ) {
        my $attr = $prop->getAttribute('name');

        if ( $attr eq 'name' or $attr eq 'durable-id' or $attr eq 'sensor-name' or $attr eq 'volume-name' ){
            $name = $prop->textContent();
            $name =~ s/-/_/g;
            if ( length($name) > 12 ) {
                $name = "v".substr($name, length($name) - 11, 11);
            }

            @splitname = split(/\./, $name);

            if ( $splitname[0] ne $name ) {
                $datas{'nodes'}{$splitname[0]} = 1;
                $datas{'subnodes'}{$name} = 1;
            }
            else {
                $datas{'nodes'}{$name} = 1;
            }
        }
        else {
            if ( $splitname[0] ne $name ) {
                $datas{'stats'}{$cmd}{$attr}{$splitname[0]} += $prop->textContent();
                $datas{'substats'}{$cmd}{$attr}{$name} += $prop->textContent();
            } else {
                $datas{'stats'}{$cmd}{$attr}{$name} += $prop->textContent();
            }
        }
    }
}

foreach $key (sort keys %{$aspects{$cmd}}) {
    my $val;
    print "multigraph $key\n";
    if ( $ARGV[0] eq 'config' ) {
        foreach (@graph_parameters) {
            if (!defined($aspects{$cmd}{$key}{$_})) {
                next;
            }
            print "graph_$_ $aspects{$cmd}{$key}{$_}\n";
        }
        print "\n";
        foreach $val (sort keys %{$aspects{$cmd}{$key}{'values'}}) {

            foreach $name (sort keys %{$datas{'stats'}{$cmd}{$val}}) {
                my $basename = $aspects{$cmd}{$key}{'values'}{$val}{'short'}."_"."$name";

                print "$basename.label $name\n";

                if ( defined($aspects{$cmd}{$key}{'values'}{$val}{'negative'}) ) {
                    print "$basename.negative $aspects{$cmd}{$key}{'values'}{$val}{'negative'}_$name\n";
                }

                foreach (@field_parameters) {
                    if (!defined($aspects{$cmd}{$key}{'values'}{$val}{$_})) {
                        next;
                    }
                    print "$basename.$_ $aspects{$cmd}{$key}{'values'}{$val}{$_}\n";
                }
            }
        }
        print "\n";
    }
    elsif ( $ARGV[0] eq 'debug' ) {
        foreach $key (sort keys %datas) {
            print "$key = $datas{$key}\n";
        }
    }
    elsif ( $ARGV[0] eq 'autoconf' ) {
        print "no (needs manual config)\n";
        exit 0;
    }
    else {
        foreach $val (sort keys %{$aspects{$cmd}{$key}{'values'}}) {
            foreach $name (sort keys %{$datas{'stats'}{$cmd}{$val}}) {
                print $aspects{$cmd}{$key}{'values'}{$val}{'short'}."_"."$name.value $datas{'stats'}{$cmd}{$val}{$name}\n";
            }
        }
        print "\n";
    }

    if ( $cmd ne 'disk-statistics' ) {
        foreach $name (sort keys %{$datas{'nodes'}}) {

            print "multigraph $key.$name\n";

            if ( $ARGV[0] eq 'config' ) {
                foreach (@graph_parameters) {
                    if (!defined($aspects{$cmd}{$key}{$_})) {
                        next;
                    }

                    if ( $_ eq 'title' ) {
                        print "graph_$_ $aspects{$cmd}{$key}{$_}: $name\n";
                        next;
                    }
                    print "graph_$_ $aspects{$cmd}{$key}{$_}\n";
                }

                print "\n";

                foreach $val (sort keys %{$aspects{$cmd}{$key}{'values'}}) {

                    my $basename = $aspects{$cmd}{$key}{'values'}{$val}{'short'};

                    print "$basename.label $aspects{$cmd}{$key}{'values'}{$val}{'label'}\n";

                    if ( defined($aspects{$cmd}{$key}{'values'}{$val}{'negative'}) ) {
                        print "$basename.negative $aspects{$cmd}{$key}{'values'}{$val}{'negative'}\n";
                    }

                    foreach (@field_parameters) {
                        if (!defined($aspects{$cmd}{$key}{'values'}{$val}{$_})) {
                            next;
                        }
                        print "$basename.$_ $aspects{$cmd}{$key}{'values'}{$val}{$_}\n";
                    }
                }
                print "\n";
            }
            else {
                foreach $val (sort keys %{$aspects{$cmd}{$key}{'values'}}) {
                    print $aspects{$cmd}{$key}{'values'}{$val}{'short'}.".value $datas{'stats'}{$cmd}{$val}{$name}\n";
                }
                print "\n";
            }
        }
    } else {
        # Enclosure graphs
        foreach $name (sort keys %{$datas{'nodes'}}) {

            print "multigraph $key.$name\n";

            if ( $ARGV[0] eq 'config' ) {
                foreach (@graph_parameters) {
                    if (!defined($aspects{$cmd}{$key}{$_})) {
                        next;
                    }

                    if ( $_ eq 'title' ) {
                        print "graph_$_ $aspects{$cmd}{$key}{$_}: Enclosure $name\n";
                        next;
                    }
                    print "graph_$_ $aspects{$cmd}{$key}{$_}\n";
                }

                print "\n";


                foreach $disk (sort keys %{$datas{'subnodes'}}) {
                    my @diskname = split(/\./, $disk);
                    if ( $diskname[0] ne $name ) {
                        next;
                    }

                    foreach $val (sort keys %{$aspects{$cmd}{$key}{'values'}}) {

                        my $basename = "disk_".$diskname[1]."_".$aspects{$cmd}{$key}{'values'}{$val}{'short'};

                        print "$basename.label d$diskname[1]\n";

                        if ( defined($aspects{$cmd}{$key}{'values'}{$val}{'negative'}) ) {
                            print "$basename.negative disk_".$diskname[1]."_$aspects{$cmd}{$key}{'values'}{$val}{'negative'}\n";
                        }

                        foreach (@field_parameters) {
                            if (!defined($aspects{$cmd}{$key}{'values'}{$val}{$_})) {
                                next;
                            }
                            print "$basename.$_ $aspects{$cmd}{$key}{'values'}{$val}{$_}\n";
                        }
                    }
                }
                print "\n";
            }
            else {
                foreach $disk (sort keys %{$datas{'subnodes'}}) {
                    my @diskname = split(/\./, $disk);
                    if ( $diskname[0] ne $name ) {
                        next;
                    }
                    foreach $val (sort keys %{$aspects{$cmd}{$key}{'values'}}) {
                        print "disk_".$diskname[1]."_".$aspects{$cmd}{$key}{'values'}{$val}{'short'}.".value $datas{'substats'}{$cmd}{$val}{$disk}\n";
                    }
                }
                print "\n";
            }
        }

        # Subgraphs on disk level
        foreach $name (sort keys %{$datas{'subnodes'}}) {
            my $graphname = $name;
            $graphname =~ s/\./\.d/;

            print "multigraph $key.$graphname\n";

            if ( $ARGV[0] eq 'config' ) {
                foreach (@graph_parameters) {
                    if (!defined($aspects{$cmd}{$key}{$_})) {
                        next;
                    }

                    if ( $_ eq 'title' ) {
                        print "graph_$_ $aspects{$cmd}{$key}{$_}: $name\n";
                        next;
                    }
                    print "graph_$_ $aspects{$cmd}{$key}{$_}\n";
                }

                print "\n";

                foreach $val (sort keys %{$aspects{$cmd}{$key}{'values'}}) {

                    my $basename = $aspects{$cmd}{$key}{'values'}{$val}{'short'};

                    print "$basename.label $aspects{$cmd}{$key}{'values'}{$val}{'label'}\n";

                    if ( defined($aspects{$cmd}{$key}{'values'}{$val}{'negative'}) ) {
                        print "$basename.negative $aspects{$cmd}{$key}{'values'}{$val}{'negative'}\n";
                    }

                    foreach (@field_parameters) {
                        if (!defined($aspects{$cmd}{$key}{'values'}{$val}{$_})) {
                            next;
                        }
                        print "$basename.$_ $aspects{$cmd}{$key}{'values'}{$val}{$_}\n";
                    }
                }
                print "\n";
            }
            else {
                foreach $val (sort keys %{$aspects{$cmd}{$key}{'values'}}) {
                    print $aspects{$cmd}{$key}{'values'}{$val}{'short'}.".value $datas{'substats'}{$cmd}{$val}{$name}\n";
                }
                print "\n";
            }
        }

    }
}
