#
# Copyright (c) 2011 SGI.
# All rights reserved.
#
# 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.
#

use strict;
use warnings;
use PCP::PMDA;

use vars qw( $pmda @all_ops @common_ops @v3only_ops @v4_ops @v41_ops);

# The instance domain is 0 for all nfs client stats
our  $nfsclient_indom = 0;

# mountstats or other file to use
our $MOUNTSTATS_PATH = "/proc/self/mountstats";

# Configuration files for overriding the location of mountstats, mostly for testing purposes
for my $file (pmda_config('PCP_PMDAS_DIR') . '/nfsclient/nfsclient.conf', 'nfsclient.conf') {
        eval `cat $file` unless ! -f $file;
}

# Check env variable for mountstats file to use
if ( defined $ENV{"NFSCLIENT_MOUNTSTATS_PATH"} ) {
	$MOUNTSTATS_PATH = $ENV{"NFSCLIENT_MOUNTSTATS_PATH"}
}

our $noval = PM_ERR_APPVERSION;

# The stats hash is keyed on the 'mount' string (the mountpoint string), e.g.
# '/home'.  The value is a hash ref, and that hash contains all of
# the stats data keyed on pmid_name
our %h = ();

#
# Parse /proc/self/mountstats and store the stats, taking one pass through
# the file.
#
sub nfsclient_parse_proc_mountstats {
	# mountstats output has a section for each mounted filesystems on the
	# system.  Each section starts with a line like:  'device X mounted on
	# Y with fstype Z'.  Some filesystems (like nfs) print additional
	# output that we need to capture.
	open STATS, '<', $MOUNTSTATS_PATH ||
		( $pmda->err("pmdanfsclient failed to open $MOUNTSTATS_PATH: $!") &&
		die "Can't open $MOUNTSTATS_PATH: $!\n") ;

	my $export;
	my $nfsinst;
	while (<STATS>) {
		my $line = $_;

		# does this line represent a mount?
		if ($line =~ /^device (\S*) mounted on (\S*) with fstype.*/) {
			$export = $1;
			my $mtpt = $2;
			$nfsinst = $mtpt;

			# is it an nfs mount?
			if ($line =~ /.*nfs(4)? statvers=.*/) {
				$h{$nfsinst} = {}; # {} is an anonymous hash ref
				$h{$nfsinst}->{'nfsclient.export'} = $export;
				$h{$nfsinst}->{'nfsclient.mountpoint'} = $mtpt; 
			} else {
				# the following lines aren't nfs so undef
				# $nfsinst so that they are ignored below
				undef $nfsinst;
				next;
			}
		}

		# skip lines that do not belong to nfs mounts
		if (not defined $nfsinst) {
			next;
		}

		# opts
		if ($line =~ /^\topts:\t(.*)$/) {

			# Need to differentiate between options that are not available based on nfs vers, vs options that are not set and therefore default values ?
			# Prepopulate based on this constraint
			# Try to stay away from the "no" names
			# Guess reasonable defaults based on mapping below
			#
			$h{$nfsinst}->{'nfsclient.options.readmode'} = "rw";
			$h{$nfsinst}->{'nfsclient.options.sync'} = 0;
			$h{$nfsinst}->{'nfsclient.options.atime'} = 1;
			$h{$nfsinst}->{'nfsclient.options.diratime'} = 1;
			$h{$nfsinst}->{'nfsclient.options.vers'} = "3";
			$h{$nfsinst}->{'nfsclient.options.rsize'} = 0;
			$h{$nfsinst}->{'nfsclient.options.wsize'} = 0;
			$h{$nfsinst}->{'nfsclient.options.bsize'} = 0;
			$h{$nfsinst}->{'nfsclient.options.namlen'} = 0;
			$h{$nfsinst}->{'nfsclient.options.acregmin'} = 0;
			$h{$nfsinst}->{'nfsclient.options.acregmax'} = 0;
			$h{$nfsinst}->{'nfsclient.options.acdirmin'} = 0;
			$h{$nfsinst}->{'nfsclient.options.acdirmax'} = 0;
			$h{$nfsinst}->{'nfsclient.options.recovery'} = "hard";
			$h{$nfsinst}->{'nfsclient.options.posix'} = 0;
			$h{$nfsinst}->{'nfsclient.options.cto'} = 1;
			$h{$nfsinst}->{'nfsclient.options.ac'} = 1;
			$h{$nfsinst}->{'nfsclient.options.lock'} = 1;
			$h{$nfsinst}->{'nfsclient.options.acl'} = 1;
			$h{$nfsinst}->{'nfsclient.options.rdirplus'} = 1;
			$h{$nfsinst}->{'nfsclient.options.sharecache'} = 1;
			$h{$nfsinst}->{'nfsclient.options.resvport'} = 1;
			$h{$nfsinst}->{'nfsclient.options.proto'} = "tcp";
			$h{$nfsinst}->{'nfsclient.options.port'} = 0;
			$h{$nfsinst}->{'nfsclient.options.timeo'} = 600;
			$h{$nfsinst}->{'nfsclient.options.retrans'} = 3;
			$h{$nfsinst}->{'nfsclient.options.sec'} = "sys";

			# NFS 3 only options but can be NULL also
			$h{$nfsinst}->{'nfsclient.options.mountaddr'} = "unspecified";
			$h{$nfsinst}->{'nfsclient.options.mountvers'} = 0;
			$h{$nfsinst}->{'nfsclient.options.mountport'} = 0;
			$h{$nfsinst}->{'nfsclient.options.mountproto'} = "";

			# NFS 4 only
			$h{$nfsinst}->{'nfsclient.options.clientaddr'} = "";

			# All
			$h{$nfsinst}->{'nfsclient.options.fsc'} = 0;
			$h{$nfsinst}->{'nfsclient.options.migration'} = 0;
			$h{$nfsinst}->{'nfsclient.options.lookupcache'} = "";
			$h{$nfsinst}->{'nfsclient.options.local_lock'} = "none";

			$h{$nfsinst}->{'nfsclient.options.string'} = $1; # Keep this???

			my @mountopts = split(',', $1);

			foreach my $mountopt (@mountopts) {
            			chomp $mountopt;
				# Is there a better way to do this ??
				# Started with a regex for the full line but got ugly
				#
				if( $mountopt =~ /^(ro|rw)$/ ){ 
					$h{$nfsinst}->{'nfsclient.options.readmode'} = "$1";
				} elsif ( $mountopt =~ /^(sync)$/ ){
					$h{$nfsinst}->{'nfsclient.options.sync'} = 1;
				} elsif ( $mountopt =~ /^(noatime)$/ ){
					$h{$nfsinst}->{'nfsclient.options.atime'} = 0;
				} elsif ( $mountopt =~ /^(nodirtime)$/ ){
					$h{$nfsinst}->{'nfsclient.options.diratime'} = 0;
				} elsif ( $mountopt =~ /^vers=([2-4](\.[0-9])?)/ ){
					$h{$nfsinst}->{'nfsclient.options.vers'} = "$1";
				} elsif ( $mountopt =~ /^rsize=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.rsize'} = $1;
				} elsif ( $mountopt =~ /^wsize=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.wsize'} = $1;
				} elsif ( $mountopt =~ /^bsize=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.bsize'} = $1;
				} elsif ( $mountopt =~ /^namlen=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.namlen'} = $1;
				} elsif ( $mountopt =~ /^acregmin=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.acregmin'} = $1;
				} elsif ( $mountopt =~ /^acregmax=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.acregmax'} = $1;
				} elsif ( $mountopt =~ /^acdirmin=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.acdirmin'} = $1;
				} elsif ( $mountopt =~ /^acdirmax=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.acdirmax'} = $1;
				} elsif ( $mountopt =~ /^(soft|hard)$/ ){
					$h{$nfsinst}->{'nfsclient.options.recovery'} = "$1";
				} elsif ( $mountopt =~ /^(posix)$/ ){
					$h{$nfsinst}->{'nfsclient.options.posix'} = 1;
				} elsif ( $mountopt =~ /^(nocto)$/ ){
					$h{$nfsinst}->{'nfsclient.options.cto'} = 0;
				} elsif ( $mountopt =~ /^(noac)$/ ){
					$h{$nfsinst}->{'nfsclient.options.ac'} = 0;
				} elsif ( $mountopt =~ /^(nolock)$/ ){
					$h{$nfsinst}->{'nfsclient.options.lock'} = 0;
				} elsif ( $mountopt =~ /^(noacl)$/ ){
					$h{$nfsinst}->{'nfsclient.options.acl'} = 0;
				} elsif ( $mountopt =~ /^(nordirplus)$/ ){
					$h{$nfsinst}->{'nfsclient.options.rdirplus'} = 0;
				} elsif ( $mountopt =~ /^(nosharecache)$/ ){
					$h{$nfsinst}->{'nfsclient.options.sharecache'} = 0;
				} elsif ( $mountopt =~ /^(noresvport)$/ ){
					$h{$nfsinst}->{'nfsclient.options.resvport'} = 0;
				} elsif ( $mountopt =~ /^proto=(tcp|udp|rdma)$/ ){
					$h{$nfsinst}->{'nfsclient.options.proto'} = "$1";
				} elsif ( $mountopt =~ /^port=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.port'} = $1;
				} elsif ( $mountopt =~ /^timeo=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.timeo'} = $1;
				} elsif ( $mountopt =~ /^retrans=(\d+)$/ ){
					$h{$nfsinst}->{'nfsclient.options.retrans'} = $1;
				} elsif ( $mountopt =~ /^sec=(null|sys|krb5|krb5i|krb5p|lkey|lkeyi|lkeyp|spkm|spkmi|spkmp|unknown)$/ ){
					$h{$nfsinst}->{'nfsclient.options.sec'} = "$1";
				} elsif ( $mountopt =~ /^mountaddr=(.*)$/ ){ # NFS3 only
					$h{$nfsinst}->{'nfsclient.options.mountaddr'} = "$1";
				} elsif ( $mountopt =~ /^mountvers=(\d+)$/ ){ # NFS3 only
					$h{$nfsinst}->{'nfsclient.options.mountvers'} = $1;
				} elsif ( $mountopt =~ /^mountport=(\d+)$/ ){ # NFS3 only
					$h{$nfsinst}->{'nfsclient.options.mountport'} = $1;
				} elsif ( $mountopt =~ /^mountproto=(udp|tcp|auto|udp6|tcp6)$/ ){ # NFS3 only
					$h{$nfsinst}->{'nfsclient.options.mountproto'} = "$1"
				} elsif ( $mountopt =~ /^clientaddr=(.*)$/ ){ # NFS4 only
					$h{$nfsinst}->{'nfsclient.options.clientaddr'} = "$1";
				} elsif ( $mountopt =~ /^(fsc)$/ ){
					$h{$nfsinst}->{'nfsclient.options.fsc'} = 1;
				} elsif ( $mountopt =~ /^(migration)$/ ){
					$h{$nfsinst}->{'nfsclient.options.migration'} = 1;
				} elsif ( $mountopt =~ /^lookupcache=(none|pos)$/ ){
					$h{$nfsinst}->{'nfsclient.options.lookupcache'} = "$1";
				} elsif ( $mountopt =~ /^local_lock=(none|all|flock|posix)$/ ){
					$h{$nfsinst}->{'nfsclient.options.local_lock'} = "$1";
				}
        		}
		}
		#age
		if ($line =~ /^\tage:\t(.*)$/) {
                        $h{$nfsinst}->{'nfsclient.age'} = $1;
                }
		#caps
		if ($line =~ /^\tcaps:\t(.*)$/) {
                        $h{$nfsinst}->{'nfsclient.capabilities'} = $1;
                }
		#sec
		if ($line =~ /^\tsec:\t(.*)$/) {
                        $h{$nfsinst}->{'nfsclient.security'} = $1;
                }
		# events
		if ($line =~ /^\tevents:\t(.*)$/) {
			($h{$nfsinst}->{'nfsclient.events.inoderevalidate'},
			 $h{$nfsinst}->{'nfsclient.events.dentryrevalidate'},
			 $h{$nfsinst}->{'nfsclient.events.datainvalidate'},
			 $h{$nfsinst}->{'nfsclient.events.attrinvalidate'},
			 $h{$nfsinst}->{'nfsclient.events.vfsopen'},
			 $h{$nfsinst}->{'nfsclient.events.vfslookup'},
			 $h{$nfsinst}->{'nfsclient.events.vfsaccess'},
			 $h{$nfsinst}->{'nfsclient.events.vfsupdatepage'},
			 $h{$nfsinst}->{'nfsclient.events.vfsreadpage'},
			 $h{$nfsinst}->{'nfsclient.events.vfsreadpages'},
			 $h{$nfsinst}->{'nfsclient.events.vfswritepage'},
			 $h{$nfsinst}->{'nfsclient.events.vfswritepages'},
			 $h{$nfsinst}->{'nfsclient.events.vfsgetdents'},
			 $h{$nfsinst}->{'nfsclient.events.vfssetattr'},
			 $h{$nfsinst}->{'nfsclient.events.vfsflush'},
			 $h{$nfsinst}->{'nfsclient.events.vfsfsync'},
			 $h{$nfsinst}->{'nfsclient.events.vfslock'},
			 $h{$nfsinst}->{'nfsclient.events.vfsrelease'},
			 $h{$nfsinst}->{'nfsclient.events.congestionwait'},
			 $h{$nfsinst}->{'nfsclient.events.setattrtrunc'},
			 $h{$nfsinst}->{'nfsclient.events.extendwrite'},
			 $h{$nfsinst}->{'nfsclient.events.sillyrename'},
			 $h{$nfsinst}->{'nfsclient.events.shortread'},
			 $h{$nfsinst}->{'nfsclient.events.shortwrite'},
			 $h{$nfsinst}->{'nfsclient.events.delay'}) =
				split(/ /, $1);
		}

		# bytes
		if ($line =~ /\tbytes:\t(.*)$/) {
			($h{$nfsinst}->{'nfsclient.bytes.read.normal'},
			 $h{$nfsinst}->{'nfsclient.bytes.write.normal'},
			 $h{$nfsinst}->{'nfsclient.bytes.read.direct'},
			 $h{$nfsinst}->{'nfsclient.bytes.write.direct'},
			 $h{$nfsinst}->{'nfsclient.bytes.read.server'},
			 $h{$nfsinst}->{'nfsclient.bytes.write.server'},
			 $h{$nfsinst}->{'nfsclient.pages.read'},
			 $h{$nfsinst}->{'nfsclient.pages.write'}) =
				split(/ /, $1);
		}

		# xprt
		if ($line =~ /\txprt:\t(.*)$/) {
			my @stats = split(/ /, $1);
			my $xprt_prot = shift(@stats);

			if( $xprt_prot eq "tcp"){

				($h{$nfsinst}->{'nfsclient.xprt.srcport'},
				 $h{$nfsinst}->{'nfsclient.xprt.bind_count'},
				 $h{$nfsinst}->{'nfsclient.xprt.connect_count'},
				 $h{$nfsinst}->{'nfsclient.xprt.connect_time'},
				 $h{$nfsinst}->{'nfsclient.xprt.idle_time'},
				 $h{$nfsinst}->{'nfsclient.xprt.sends'},
				 $h{$nfsinst}->{'nfsclient.xprt.recvs'},
				 $h{$nfsinst}->{'nfsclient.xprt.bad_xids'},
				 $h{$nfsinst}->{'nfsclient.xprt.req_u'},
				 $h{$nfsinst}->{'nfsclient.xprt.bklog_u'}) =
					@stats;

				 # Unused RDMA Elements
                                 $h{$nfsinst}->{'nfsclient.xprt.read_chunks'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.write_chunks'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.reply_chunks'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.total_rdma_req'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.total_rdma_rep'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.pullup'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.fixup'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.hardway'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.failed_marshal'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.bad_reply'} = 0;

			} elsif ( $xprt_prot eq "udp"){
				($h{$nfsinst}->{'nfsclient.xprt.srcport'},
                                 $h{$nfsinst}->{'nfsclient.xprt.bind_count'},
                                 $h{$nfsinst}->{'nfsclient.xprt.sends'},
                                 $h{$nfsinst}->{'nfsclient.xprt.recvs'},
                                 $h{$nfsinst}->{'nfsclient.xprt.bad_xids'},
                                 $h{$nfsinst}->{'nfsclient.xprt.req_u'},
                                 $h{$nfsinst}->{'nfsclient.xprt.bklog_u'}) =
                                        @stats;

				 # Unused TCP elements
				 $h{$nfsinst}->{'nfsclient.xprt.connect_count'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.connect_time'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.idle_time'} = 0;

				 # Unused RDMA Elements
				 $h{$nfsinst}->{'nfsclient.xprt.read_chunks'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.write_chunks'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.reply_chunks'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.total_rdma_req'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.total_rdma_rep'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.pullup'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.fixup'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.hardway'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.failed_marshal'} = 0;
                                 $h{$nfsinst}->{'nfsclient.xprt.bad_reply'} = 0;

			} elsif ( $xprt_prot eq "rdma"){
				($h{$nfsinst}->{'nfsclient.xprt.srcport'},
                                 $h{$nfsinst}->{'nfsclient.xprt.bind_count'},
                                 $h{$nfsinst}->{'nfsclient.xprt.connect_count'},
                                 $h{$nfsinst}->{'nfsclient.xprt.connect_time'},
                                 $h{$nfsinst}->{'nfsclient.xprt.idle_time'},
                                 $h{$nfsinst}->{'nfsclient.xprt.sends'},
                                 $h{$nfsinst}->{'nfsclient.xprt.recvs'},
                                 $h{$nfsinst}->{'nfsclient.xprt.bad_xids'},
                                 $h{$nfsinst}->{'nfsclient.xprt.req_u'},
                                 $h{$nfsinst}->{'nfsclient.xprt.bklog_u'},
				 $h{$nfsinst}->{'nfsclient.xprt.read_chunks'},
				 $h{$nfsinst}->{'nfsclient.xprt.write_chunks'},
				 $h{$nfsinst}->{'nfsclient.xprt.reply_chunks'},
				 $h{$nfsinst}->{'nfsclient.xprt.total_rdma_req'},
				 $h{$nfsinst}->{'nfsclient.xprt.total_rdma_rep'},
				 $h{$nfsinst}->{'nfsclient.xprt.pullup'},
				 $h{$nfsinst}->{'nfsclient.xprt.fixup'},
				 $h{$nfsinst}->{'nfsclient.xprt.hardway'},
				 $h{$nfsinst}->{'nfsclient.xprt.failed_marshal'},
				 $h{$nfsinst}->{'nfsclient.xprt.bad_reply'}) =
                                        @stats;

			} else {
				next;
			}
		}

		# per-op statistics
		# pre-populate all possible ops from v3, v4, v4.1 and set to noval
		# ops defined below
		for my $opname (@all_ops) {
			$h{$nfsinst}->{"nfsclient.ops.$opname.ops"} = $noval;
                        $h{$nfsinst}->{"nfsclient.ops.$opname.ntrans"} = $noval;
                        $h{$nfsinst}->{"nfsclient.ops.$opname.timeouts"} = $noval;
                        $h{$nfsinst}->{"nfsclient.ops.$opname.bytes_sent"} = $noval;
                        $h{$nfsinst}->{"nfsclient.ops.$opname.bytes_recv"} = $noval;
                        $h{$nfsinst}->{"nfsclient.ops.$opname.queue"} = $noval;
                        $h{$nfsinst}->{"nfsclient.ops.$opname.rtt"} = $noval;
                        $h{$nfsinst}->{"nfsclinet.ops.$opname.execute"} = $noval;
		}

		if ($line =~ /\tper-op statistics$/) {
			# We'll do these a bit differently since they are not
			# all on the same line.  Just loop until we don't match
			# anymore.
			# v4 ops can have underscore
			while (<STATS> =~
	/^\s*([A-Z_]*): (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*)$/) {
				my $op_name = "\L$1";
				($h{$nfsinst}->{"nfsclient.ops.$op_name.ops"},
				 $h{$nfsinst}->{"nfsclient.ops.$op_name.ntrans"},
				 $h{$nfsinst}->{"nfsclient.ops.$op_name.timeouts"},
				 $h{$nfsinst}->{"nfsclient.ops.$op_name.bytes_sent"},
				 $h{$nfsinst}->{"nfsclient.ops.$op_name.bytes_recv"},
				 $h{$nfsinst}->{"nfsclient.ops.$op_name.queue"},
				 $h{$nfsinst}->{"nfsclient.ops.$op_name.rtt"},
				 $h{$nfsinst}->{"nfsclinet.ops.$op_name.execute"}) =
					($2, $3, $4, $5, $6, $7, $8, $9);
			}
		}
	}

	close STATS;
}

#
# fetch is called once by pcp for each refresh and then the fetch callback is
# called to query each statistic individually
#
sub nfsclient_fetch {
	nfsclient_parse_proc_mountstats();

	our $pmda->replace_indom($nfsclient_indom, \%h);
}

sub nfsclient_fetch_callback {
	my ($cluster, $item, $inst) = @_; 

	my $lookup = pmda_inst_lookup($nfsclient_indom, $inst);
	return (PM_ERR_INST, 0) unless defined($lookup);

	my $pmid_name = pmda_pmid_name($cluster, $item)
               or die "Unknown metric name: cluster $cluster item $item\n";

	return ($lookup->{$pmid_name}, 1);

}

# the PCP::PMDA->new line is parsed by the check_domain rule of the PMDA build
# process, so there are special requirements:  no comments, the domain has to
# be a bare number.
#
our $pmda = PCP::PMDA->new('nfsclient', 62);

# metrics go here, with full descriptions

# general - cluster 0
$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_STRING, $nfsclient_indom,
		PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
		'nfsclient.export',
		'Export',
		'');
$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_STRING, $nfsclient_indom,
		PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
		'nfsclient.mountpoint',
		'Mount Point',
		'');

# opts - cluster 1
#
# Options in order:
# NULL is not present
# from super.c in nfs_show_mount_options
#
# ro | rw
# sync | NULL
# noatime | NULL
# nodiratime | NULL
# vers=([2-4](\.[0-9])?)
# rsize=%u
# wsize=%u
# bsize=%u | NULL
# namlen=%u
# acregmin=%u | NULL
# acregmax=%u | NULL
# acdirmin=%u | NULL
# acdirmax=%u | NULL
# soft | hard
# posix | NULL
# nocto | NULL
# noac | NULL
# nolock | NULL
# noacl | NULL
# nordirplus | NULL
# nosharecache | NULL
# noresvport | NULL
# proto=(tcp|udp|rdma)
# port=%u | NULL
# timeo=%lu
# retrans=%u
# sec=(null|sys|krb5|krb5i|krb5p|lkey|lkeyi|lkeyp|spkm|spkmi|spkmp|unknown)   # null and unknown are those exact literal strings

# NFS 3 only
# Below block may not exist at all if NFS_MOUNT_LEGACY_INTERFACE
# mountaddr="ip4addr|ip6addr|unspecified"
# mountvers=%u | NULL
# mountport=%u | NULL
# mountproto=(udp|tcp|auto|udp6|tcp6|NULL)    # NULL if showdefaults is false

# NFS 4 Only
# clientaddr=(ip4address|ip6address)

# fsc | NULL
# migration | NULL
# lookupcache=(none|pos) | NULL
# local_lock=(none|all|flock|posix)

# TODO
# deprecated after 2.6.25, do we care about these ?
# intr|nointr

$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.string',
                'Full Options String',
                '');
$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.readmode',
                'rw or ro mount mode',
                '');
$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.sync',
                'boolean sync mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,4), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.atime',
                'boolean atime mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,5), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.diratime',
                'boolean diratime mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,6), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.vers',
                'nfs version',
                '');
$pmda->add_metric(pmda_pmid(1,7), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.rsize',
                'rsize mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,8), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.wsize',
                'wsize mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,9), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.bsize',
                'bsize mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,10), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.namlen',
                'namlen mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,11), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.acregmin',
                'acregmin mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,12), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.acregmax',
                'acregmax mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,13), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.acdirmin',
                'acdirmin mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,14), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.acdirmax',
                'acdirmax mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,15), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.recovery',
                'hard or soft recovery behavior',
                '');
$pmda->add_metric(pmda_pmid(1,16), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.posix',
                'boolean posix mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,17), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.cto',
                'boolean cto mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,18), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.ac',
                'boolean ac mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,19), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.lock',
                'boolean lock mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,20), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.acl',
                'boolean acl mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,21), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.rdirplus',
                'boolean rdirplus mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,22), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.sharecache',
                'boolean sharecache mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,23), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.resvport',
                'boolean resvport mount option used',
                '');
$pmda->add_metric(pmda_pmid(1,24), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.proto',
                'nfs protocol: udp|tcp|rdma',
                '');
$pmda->add_metric(pmda_pmid(1,25), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.port',
                'port mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,26), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.timeo',
                'timeo mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,27), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.retrans',
                'retrans mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,28), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.sec',
                'sec mount parameter',
                '');
# NFS3 only
$pmda->add_metric(pmda_pmid(1,29), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.mountaddr',
                'mountaddr mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,30), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.mountvers',
                'mountvers mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,31), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.mountport',
                'mountport mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,32), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.mountproto',
                'mountproto mount parameter',
                '');
# NFS4 only
$pmda->add_metric(pmda_pmid(1,33), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.clientaddr',
                'clientaddr mount parameter',
                '');
# All
$pmda->add_metric(pmda_pmid(1,34), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.fsc',
                'fsc mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,35), PM_TYPE_U32, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.migration',
                'migration mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,36), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.lookupcache',
                'lookupcache mount parameter',
                '');
$pmda->add_metric(pmda_pmid(1,37), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.options.local_lock',
                'local_lock mount parameter',
                '');

# age - cluster 2
$pmda->add_metric(pmda_pmid(2,1), pmda_ulong, $nfsclient_indom,
                  PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
                  'nfsclient.age',
                  'Age in Seconds',
                  '');
# caps - cluster 3 - Any use in parsing this ?
$pmda->add_metric(pmda_pmid(3,1), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.capabilities',
                'Capabilities',
                '');

# sec - cluster 4 - Any use in parsing this ?
$pmda->add_metric(pmda_pmid(4,1), PM_TYPE_STRING, $nfsclient_indom,
                PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
                'nfsclient.security',
                'Security Flavor',
                '');

# events - cluster 5
$pmda->add_metric(pmda_pmid(5,1), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.inoderevalidate',
		  'NFSIOS_INODEREVALIDATE',
'incremented in __nfs_revalidate_inode whenever an inode is revalidated');

$pmda->add_metric(pmda_pmid(5,2), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.dentryrevalidate',
		  'NFSIOS_DENTRYREVALIDATE',
'incremented in nfs_lookup_revalidate whenever a dentry is revalidated');

$pmda->add_metric(pmda_pmid(5,3), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.datainvalidate',
		  'NFSIOS_DATAINVALIDATE',
'incremented in nfs_invalidate_mapping_nolock when data cache for an inode ' .
'is invalidated');

$pmda->add_metric(pmda_pmid(5,4), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.attrinvalidate',
		  'NFSIOS_ATTRINVALIDATE',
'incremented in nfs_zap_caches_locked and nfs_update_inode when an the ' .
'attribute cache for an inode has been invalidated');

$pmda->add_metric(pmda_pmid(5,5), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsopen',
		  'NFSIOS_VFSOPEN',
'incremented in nfs_file_open and nfs_opendir whenever a file or directory ' .
'is opened');

$pmda->add_metric(pmda_pmid(5,6), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfslookup',
		  'NFSIOS_VFSLOOKUP',
'incremented in nfs_lookup on every lookup');

$pmda->add_metric(pmda_pmid(5,7), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsaccess',
		  '', '');

$pmda->add_metric(pmda_pmid(5,8), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsupdatepage',
		  '', '');

$pmda->add_metric(pmda_pmid(5,9), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsreadpage',
		  '', '');

$pmda->add_metric(pmda_pmid(5,10), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsreadpages',
		  '', '');

$pmda->add_metric(pmda_pmid(5,11), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfswritepage',
		  '', '');

$pmda->add_metric(pmda_pmid(5,12), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfswritepages',
		  '', '');

$pmda->add_metric(pmda_pmid(5,13), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsgetdents',
		  '', '');

$pmda->add_metric(pmda_pmid(5,14), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfssetattr',
		  '', '');

$pmda->add_metric(pmda_pmid(5,15), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsflush',
		  '', '');

$pmda->add_metric(pmda_pmid(5,16), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsfsync',
		  '', '');

$pmda->add_metric(pmda_pmid(5,17), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfslock',
		  '', '');

$pmda->add_metric(pmda_pmid(5,18), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.vfsrelease',
		  '', '');

$pmda->add_metric(pmda_pmid(5,19), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.congestionwait',
		  '', '');

$pmda->add_metric(pmda_pmid(5,20), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.setattrtrunc',
		  '', '');

$pmda->add_metric(pmda_pmid(5,21), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.extendwrite',
		  '', '');

$pmda->add_metric(pmda_pmid(5,22), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.sillyrename',
		  '', '');

$pmda->add_metric(pmda_pmid(5,23), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.shortread',
		  '', '');

$pmda->add_metric(pmda_pmid(5,24), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.shortwrite',
		  '', '');

$pmda->add_metric(pmda_pmid(5,25), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.events.delay',
		  '', '');

# bytes - cluster 6
$pmda->add_metric(pmda_pmid(6,1), pmda_ulong, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
		  'nfsclient.bytes.read.normal',
		  'NFSIOS_NORMALREADBYTES',
'the number of bytes read by applications via the read system call interface');

$pmda->add_metric(pmda_pmid(6,2), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
		  'nfsclient.bytes.write.normal',
		  'NFSIOS_NORMALWRITTENBYTES',
'the number of bytes written by applications via the write system call ' .
'interface');

$pmda->add_metric(pmda_pmid(6,3), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
		  'nfsclient.bytes.read.direct',
		  'NFSIOS_DIRECTREADBYTES',
'the number of bytes read by applications from files opened with the ' .
'O_DIRECT flag');

$pmda->add_metric(pmda_pmid(6,4), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
		  'nfsclient.bytes.write.direct',
		  'NFSIOS_DIRECTWRITTENBYTES',
'the number of bytes written by applications to files opened with the ' .
'O_DIRECT flag');

$pmda->add_metric(pmda_pmid(6,5), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
		  'nfsclient.bytes.read.server',
		  'NFSIOS_SERVERREADBYTES',
'the number of bytes read from the nfs server by the nfs client via nfs ' .
'read requests');

$pmda->add_metric(pmda_pmid(6,6), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
		  'nfsclient.bytes.write.server',
		  'NFSIOS_SERVERWRITTENBYTES',
'the number of bytes written to the nfs server by the nfs client via nfs ' .
'write requests');

$pmda->add_metric(pmda_pmid(6,7), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.pages.read',
		  'NFSIOS_READPAGES',
'the number of pages read via nfs_readpage or nfs_readpages');

$pmda->add_metric(pmda_pmid(6,8), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.pages.write',
		  'NFSIOS_WRITEPAGES',
'the number of pages written via nfs_writepage or nfs_writepages');

# xprt - cluster 7
#
# We have three possible transports:  udp, tcp, rdma.
# Fill in 0 for ones that dont apply.  Types for RDMA from net/sunrpc/xprtrdma/transport.c : xprt_rdma_print_stats
#
$pmda->add_metric(pmda_pmid(7,1), PM_TYPE_STRING, $nfsclient_indom,
		  PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
		  'nfsclient.xprt.srcport',
		  'tcp source port',
'source port on the client');

$pmda->add_metric(pmda_pmid(7,2), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.xprt.bind_count',
		  'count of rpcbind get_port calls',
'incremented in rpcb_getport_async: \"obtain the port for a given RPC ' .
'service on a given host\"');

$pmda->add_metric(pmda_pmid(7,3), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.xprt.connect_count',
		  'count of tcp connects',
'incremented in xs_tcp_finish_connecting and xprt_connect_status.  This is ' .
'a count of the number of tcp (re)connects (only of which is active at a ' .
'time) for this mount.');

$pmda->add_metric(pmda_pmid(7,4), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.xprt.connect_time',
		  'jiffies waiting for connect',
'Summed in xprt_connect_status, it is stored and printed in jiffies.  This ' .
'the sum of all connection attempts: how long was spent waiting to connect.');

$pmda->add_metric(pmda_pmid(7,5), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
		  'nfsclient.xprt.idle_time',
		  'transport idle time',
'How long has it been since the transport has been used.  Stored and ' .
'calculated in jiffies and printed in seconds.');

$pmda->add_metric(pmda_pmid(7,6), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.xprt.sends',
                  'count of tcp transmits',
'Incremented in xprt_transmit upon transmit completion of each rpc.');

$pmda->add_metric(pmda_pmid(7,7), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.xprt.recvs',
		  'count of tcp receives',
'Incremented in xprt_complete_rqst when reply processing is complete.');

$pmda->add_metric(pmda_pmid(7,8), PM_TYPE_U32, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.xprt.bad_xids',
		  'count of bad transaction identifiers',
'When processing an rpc reply it is necessary to look up the original ' .
'rpc_rqst using xprt_lookup_rqst.  If the rpc_rqst that prompted the reply ' .
'cannot be found on the transport bad_xids is incremented.');

$pmda->add_metric(pmda_pmid(7,9), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.xprt.req_u',
                  'average requests on the wire',
'FIXME:  The comment in struct stat says: \"average requests on the wire\", ' .
'but the actual calculation in xprt_transmit is: ' .
'xprt->stat.req_u += xprt->stat.sends - xprt->stat.recvs;\n' .
'This stat may be broken.');

$pmda->add_metric(pmda_pmid(7,10), PM_TYPE_U64, $nfsclient_indom,
		  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
		  'nfsclient.xprt.backlog_u',
		  'backlog queue utilization',
'FIXME: here is the calculation in xprt_transmit:  ' .
'xprt->stat.bklog_u += xprt->backlog.qlen;\n ' .
'qlen is incremented in __rpc_add_wait_queue and decremented in ' .
'__rpc_remove_wait_queue.');

$pmda->add_metric(pmda_pmid(7,11), pmda_ulong, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.read_chunks',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,12), pmda_ulong, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.write_chunks',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,13), pmda_ulong, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.reply_chunks',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,14), PM_TYPE_U64, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.total_rdma_req',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,15), PM_TYPE_U64, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.total_rdma_rep',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,16), PM_TYPE_U64, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.pullup',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,17), PM_TYPE_U64, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.fixup',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,18), pmda_ulong, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.hardway',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,19), pmda_ulong, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.failed_marshal',
                  '',
		  '');

$pmda->add_metric(pmda_pmid(7,20), pmda_ulong, $nfsclient_indom,
                  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
                  'nfsclient.xprt.bad_reply',
                  '',
		  '');

# ops - cluster 8
#
# Different versions have different ops.  We just List them all and fill in
# only what is appropriate for this version. Non matching ones will return noval
# from above.

# ops available in v3, v4 and v4.1
#
our @common_ops = ('null', 'getattr', 'setattr', 'lookup', 'access', 'readlink',
           'read', 'write', 'create', 'symlink', 'remove',
           'rename', 'link', 'readdir',
	   'fsinfo', 'pathconf', 'commit');

# ops only in v3
#
our @v3only_ops = ('fsstat', 'mkdir', 'mknod', 'readdirplus', 'rmdir');

# ops ADDED in v4, some v3 ops are invalid : fsstat, mkdir, mknod, readdirplus, rmdir 
#
our @v4_ops = ('close', 'create_session', 'delegreturn', 'destroy_session', 'exchange_id',
	   'fs_locations', 'getacl', 'getdeviceinfo', 'get_lease_time', 'layoutcommit',
	   'layoutget', 'layoutreturn', 'lock', 'lockt', 'locku', 'lookup_root',
	   'open', 'open_confirm', 'open_downgrade', 'open_noattr', 'reclaim_complete',
	   'release_lockowner', 'renew', 'secinfo', 'sequence', 'server_caps', 'setacl',
	   'setclientid', 'setclientid_confirm', 'statfs');

# ops ADDED in v4.1, same v3 ops removed as in v4, all v4 ops should exist
#
our @v41_ops = ('bind_conn_to_session', 'destroy_clientid', 'free_stateid', 'getdevicelist',
	     'secinfo_no_name', 'test_stateid');

our @all_ops = ( @common_ops, @v3only_ops, @v4_ops, @v41_ops );

my $item = 1;
for my $op_name (@all_ops) {
	$pmda->add_metric(pmda_pmid(8, $item++), pmda_ulong, $nfsclient_indom,
			  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
			  "nfsclient.ops.$op_name.ops",
			  'count of operations',
'rpc count for this op, only bumped in rpc_count_iostats');

	$pmda->add_metric(pmda_pmid(8, $item++), pmda_ulong, $nfsclient_indom,
			  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
			  "nfsclient.ops.$op_name.ntrans",
			  'count of transmissions',
'there can be more than one transmission per rpc');

	$pmda->add_metric(pmda_pmid(8, $item++), pmda_ulong, $nfsclient_indom,
			  PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
			  "nfsclient.ops.$op_name.timeouts",
			  'count of major timeouts',
'XXX fill me in');

	$pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
			  PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
			  "nfsclient.ops.$op_name.bytes_sent",
			  'count of bytes out',
'How many bytes are sent for this procedure type.  This indicates how much ' .
'load this procedure is putting on the network.  It includes the RPC and ULP' .
'headers, and the request payload');

	$pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
			  PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
			  "nfsclient.ops.$op_name.bytes_recv",
			  'count of bytes in',
'How many bytes are received for this procedure type.  This indicates how ' .
'much load this procedure is putting on the network.  It includes RPC and ' .
'ULP headers, and the request payload');

	$pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
			  PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
			  "nfsclient.ops.$op_name.queue",
			  'milliseconds queued for transmit',
'The length of time an RPC request waits in queue before transmission in ' .
' milliseconds.');

	$pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
			  PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
			  "nfsclient.ops.$op_name.rtt",
			  'milliseconds for rpc round trip time',
'The network + server latency of the request in milliseconds.');

	$pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
			  PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
			  "nfsclient.ops.$op_name.execute",
			  'milliseconds for rpc execution',
'The total time the request spent from init to release in milliseconds.');
}

&nfsclient_parse_proc_mountstats;

$nfsclient_indom = $pmda->add_indom($nfsclient_indom, {}, '', '');

$pmda->set_fetch(\&nfsclient_fetch);
$pmda->set_fetch_callback(\&nfsclient_fetch_callback);

$pmda->run;

=pod

=head1 NAME

pmdanfsclient - nfs client statistics performance metrics domain agent (PMDA)

=head1 DESCRIPTION

B<pmdanfsclient> is a Performance Metrics Domain Agent (PMDA) which exports
metric values from the /proc/self/mountstats interface to provide information
on nfs mounts.

=head1 INSTALLATION

If you want access to the names and values for the nfsclient performance
metrics, do the following as root:

        # cd $PCP_PMDAS_DIR/nfsclient
        # ./Install

If you want to undo the installation, do the following as root:

        # cd $PCP_PMDAS_DIR/nfsclient
        # ./Remove

B<pmdanfsclient> is launched by pmcd(1) and should never be executed
directly.  The Install and Remove scripts notify pmcd(1) when
the agent is installed or removed.

=head1 FILES

=over

=item $PCP_PMDAS_DIR/nfsclient/Install

installation script for the B<pmdanfsclient> agent

=item $PCP_PMDAS_DIR/nfsclient/Remove

undo installation script for the B<pmdanfsclient> agent

=item $PCP_LOG_DIR/pmcd/nfsclient.log

default log file for error messages from B<pmdanfsclient>

=back

=head1 SEE ALSO

pmcd(1) and nfs(5) and mountstats(8).

