summaryrefslogtreecommitdiff
path: root/src/perl/PMDA
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2014-10-26 12:33:50 +0400
committerIgor Pashev <pashev.igor@gmail.com>2014-10-26 12:33:50 +0400
commit47e6e7c84f008a53061e661f31ae96629bc694ef (patch)
tree648a07f3b5b9d67ce19b0fd72e8caa1175c98f1a /src/perl/PMDA
downloadpcp-debian/3.9.10.tar.gz
Debian 3.9.10debian/3.9.10debian
Diffstat (limited to 'src/perl/PMDA')
-rw-r--r--src/perl/PMDA/Changes101
-rw-r--r--src/perl/PMDA/GNUmakefile67
-rw-r--r--src/perl/PMDA/MANIFEST11
-rw-r--r--src/perl/PMDA/Makefile.PL49
-rw-r--r--src/perl/PMDA/PMDA.pm495
-rw-r--r--src/perl/PMDA/PMDA.xs1212
-rw-r--r--src/perl/PMDA/cvalue.c155
-rw-r--r--src/perl/PMDA/local.c468
-rw-r--r--src/perl/PMDA/local.h80
-rw-r--r--src/perl/PMDA/test.pl93
-rw-r--r--src/perl/PMDA/typemap27
11 files changed, 2758 insertions, 0 deletions
diff --git a/src/perl/PMDA/Changes b/src/perl/PMDA/Changes
new file mode 100644
index 0000000..0f49bd5
--- /dev/null
+++ b/src/perl/PMDA/Changes
@@ -0,0 +1,101 @@
+Revision history for Perl extension PCP::PMDA.
+
+1.15 Thu Sep 5 11:22:34 EST 2013
+ - Fix a subtle reference counting problem in the hash
+ indom code.
+ - Consistently use unsigned type to index the indom
+ table.
+
+1.14 Tue Jul 10 11:40:14 EST 2012
+ - Add support for hash-based indom handling via PMDA
+ cache interfaces in libpcp_pmda. This introduces a
+ routine for looking up opaque PMDA data from instid
+ (pmda_inst_lookup) suitable for fetch callback use.
+ - Fix pmda_inst_name interface used by dtsrun PMDA -
+ using an incorrect value for indom lookup. This is
+ now consistent with the other instance interfaces,
+ and dtsrun PMDA (not widely used) has been fixed.
+ - Fix instance interface arguments - no current PMDA
+ makes use of this, but if one did (pmdaoracle will)
+ it would be given the internal indom identifier and
+ not the handle given to all other indom interfaces.
+ - Remove unused parameters to internal preinstance()
+ and prefetch() routines, no visible effect for any
+ PMDAs but the wrapper code becomes more readable.
+ - Fix help file handling which (for current gcc/perl
+ combinations at least) was using potentially-freed
+ memory.
+
+1.13 Thu Oct 20 11:50:18 EST 2011
+ - Add an interface to allow a PMDA to drop privileges.
+ - Fix pmda_long and pmda_ulong 32-bit detection.
+
+1.12 Fri Jul 22 11:15:41 EST 2011
+ - Ensure local fetch and instance wrapper routines
+ are always called. Otherwise refresh interface
+ doesn't work, and pmns_refresh may not be called
+ in all situations where it should be.
+
+1.11 Wed May 11 21:04:09 EST 2011
+ - Make file tail non-blocking so it can be used for
+ named pipes as well as regular files.
+
+1.10 Thu Nov 25 03:30:18 EST 2010
+ - Convert to using dynamic namespace interfaces.
+ - Added metric and indom table clearing interfaces.
+
+1.09 Sun Aug 1 09:04:38 EST 2010
+ - Fix memory leak in fetch routine string handling.
+
+1.08 Thu Nov 19 10:37:26 EST 2009
+ - Fix typo on name export for pmda_inst_name.
+ - Add a fast path lookup in pmda_inst_name for direct
+ instance identifier to offset case (common).
+ - When tailing logfiles, seek to end initially so we
+ don't spend potentially copious amounts of time on
+ scanning the entire log file - we only want events
+ that happen after PMDA startup to be counted too.
+
+1.07 Fri Aug 21 09:27:51 EST 2009
+ - Add helper routines to determine native long sizes.
+
+1.06 Wed Jul 22 12:49:43 EST 2009
+ - Ensure the process runs in its own process group,
+ and block SIGTERM in atexit handler. This resolves
+ a regression in the 1.05 fix for reaping children.
+
+1.05 Tue Jul 8 16:19:49 EST 2009
+ - Explicitly free all local temporaries immediately
+ for routines that call Perl from C (fixes memleak).
+ - Fix an off-by-one when handling long input lines.
+ - Terminate any children started by the PMDA at exit.
+
+1.04 Mon Jul 6 11:08:20 EST 2009
+ - Rewrote PMNS file generation to use libpcp routines.
+
+1.03 Wed Jun 10 14:53:05 EST 2009
+ - Incorporated Win32 build changes (paths, etc).
+ - Remove (unneeded) use of hsearch and <search.h>.
+
+1.02 Tue Jun 2 17:16:25 EST 2009
+ - Implemented log file rotation and host reconnect handling.
+ - Generally improved the file "tail" mode of operation.
+
+1.01 Fri Feb 13 17:33:31 EST 2009
+ - Added simple instance name lookup routine.
+
+1.00 Thu Aug 20 08:48:14 EST 2008
+ - Added several API components, 1st stable version.
+
+0.04 Mon Feb 25 15:01:33 EST 2008
+ - Smaller, but still incompatible, API refinements.
+
+0.03 Sun Feb 24 09:06:39 EST 2008
+ - API changes, moved existing Perl PMDAs out into PCP.
+
+0.02 Wed Feb 20 16:38:38 EST 2008
+ - port forward to Perl API changes (circa Perl 5.6).
+
+0.01 Mon Sep 20 09:01:16 EST 1999
+ - original version; created by h2xs 1.18
+
diff --git a/src/perl/PMDA/GNUmakefile b/src/perl/PMDA/GNUmakefile
new file mode 100644
index 0000000..98c6826
--- /dev/null
+++ b/src/perl/PMDA/GNUmakefile
@@ -0,0 +1,67 @@
+#!gmake
+#
+# Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+# Copyright (c) 2004 Silicon Graphics, Inc. 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.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+PERLMOD = PMDA.pm
+INTERFACE = PMDA.xs local.h local.c typemap
+TESTCODE = test.pl cvalue.c
+PERLDOCS = Changes MANIFEST
+LSRCFILES = Makefile.PL $(PERLDOCS) $(PERLMOD) $(INTERFACE) $(TESTCODE)
+
+LPKGDIRT = PCP-PMDA-* MYMETA.yml MYMETA.json
+LDIRT = Makefile COPYING PMDA.bs PMDA.c PMDA.o cvalue pm_to_blib blib \
+ Makefile.old local.o $(LPKGDIRT)
+
+default: dist
+
+MAKEMAKER_OPTIONS = INSTALLDIRS=$(PERL_INSTALLDIRS) INSTALLVENDORMAN3DIR=$(PCP_MAN_DIR)/man3
+INSTALLER_OPTIONS = DESTDIR=$$DIST_ROOT
+
+ifeq ($(TARGET_OS),mingw)
+PERLMAKE = dmake.exe
+else
+PERLMAKE = $(MAKE)
+endif
+
+PMDA.o: Makefile PMDA.xs
+ $(PERLMAKE) -f Makefile
+
+Makefile: COPYING Makefile.PL
+ $(call PERL_MAKE_MAKEFILE)
+
+COPYING:
+ $(LN_S) $(TOPDIR)/COPYING COPYING
+
+test dist: PMDA.o
+ rm -rf $(LPKGDIRT)
+ $(PERLMAKE) -f Makefile $@
+
+include $(BUILDRULES)
+
+install: default
+ifneq "$(PACKAGE_DISTRIBUTION)" "debian"
+ $(call PERL_GET_FILELIST,$(TOPDIR)/perl-pcp-pmda.list,PMDA)
+endif
+
+install_perl:
+ $(PERLMAKE) -f Makefile pure_install $(INSTALLER_OPTIONS)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/perl/PMDA/MANIFEST b/src/perl/PMDA/MANIFEST
new file mode 100644
index 0000000..b4749b6
--- /dev/null
+++ b/src/perl/PMDA/MANIFEST
@@ -0,0 +1,11 @@
+Changes
+COPYING
+cvalue.c
+Makefile.PL
+MANIFEST
+PMDA.pm
+PMDA.xs
+local.h
+local.c
+test.pl
+typemap
diff --git a/src/perl/PMDA/Makefile.PL b/src/perl/PMDA/Makefile.PL
new file mode 100644
index 0000000..1191a02
--- /dev/null
+++ b/src/perl/PMDA/Makefile.PL
@@ -0,0 +1,49 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+my $ldfrom;
+my $inc;
+my $libs;
+my $lddlflags;
+my $cccdlflags;
+
+if ($ENV{TARGET_OS} eq "mingw") {
+ $ldfrom = "-L$ENV{PCP_TOPDIR}/src/libpcp/src -L$ENV{PCP_TOPDIR}/src/libpcp_pmda/src -L$ENV{PCP_DIR}\\local\\bin -lpcp_pmda -lpcp local.o PMDA.o";
+ $inc = "-I$ENV{PCP_TOPDIR}/src/include/pcp -I/usr/include/pcp -I$ENV{PCP_DIR}\\include\\pcp -I$ENV{PCP_DIR}\\c\\include";
+ $libs = ["-L$ENV{PCP_DIR}\\local\\bin", '-lpcp_pmda', '-lpcp'];
+}
+else {
+ $ldfrom = "local.o PMDA.o";
+ $inc = "-I$ENV{PCP_TOPDIR}/src/include/pcp -I/usr/include/pcp";
+ $libs = ["-L$ENV{PCP_TOPDIR}/src/libpcp_pmda/src -L$ENV{PCP_TOPDIR}/src/libpcp/src -lpcp_pmda -lpcp"];
+}
+if ($ENV{TARGET_OS} eq "darwin") {
+ # standard ones, minus -arch ppc
+ $lddlflags = "-arch x86_64 -arch i386 -bundle -undefined dynamic_lookup";
+}
+else {
+ $lddlflags = "-shared \$(OPTIMIZE) \$(LDFLAGS)";
+}
+if ($ENV{TARGET_OS} eq "solaris") {
+ # for OpenSolaris Makefile ends up with -KPIC instead of -fPIC otherwise
+ $cccdlflags = "-fPIC"
+}
+
+WriteMakefile(
+ NAME => 'PCP::PMDA',
+ AUTHOR => 'Nathan Scott <nathans@debian.org>',
+ VERSION_FROM => 'PMDA.pm', # finds $VERSION
+ ABSTRACT_FROM => 'PMDA.pm', # retrieve abstract from module
+ C => ['local.c', 'PMDA.c'],
+ OPTIMIZE => '-g',
+ XSPROTOARG => '-noprototypes',
+ OBJECT => 'local.o PMDA.o',
+ DEFINE => '-DPERLIO_NOT_STDIO=0 -DPCP_VERSION -DPCP_DEBUG',
+ LDFROM => $ldfrom,
+ LDDLFLAGS => $lddlflags,
+ CCCDLFLAGS => $cccdlflags,
+ INC => $inc,
+ LIBS => $libs,
+ CC => $ENV{"CC"},
+ LD => $ENV{"CC"},
+);
diff --git a/src/perl/PMDA/PMDA.pm b/src/perl/PMDA/PMDA.pm
new file mode 100644
index 0000000..d6b1a52
--- /dev/null
+++ b/src/perl/PMDA/PMDA.pm
@@ -0,0 +1,495 @@
+package PCP::PMDA;
+
+use strict;
+use warnings;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
+
+require Exporter;
+require DynaLoader;
+
+@ISA = qw(Exporter DynaLoader);
+@EXPORT = qw(
+ pmda_pmid pmda_pmid_name pmda_pmid_text pmda_inst_name pmda_inst_lookup
+ pmda_units pmda_config pmda_uptime pmda_long pmda_ulong
+ PM_ID_NULL PM_INDOM_NULL PM_IN_NULL
+ PM_SPACE_BYTE PM_SPACE_KBYTE PM_SPACE_MBYTE PM_SPACE_GBYTE PM_SPACE_TBYTE
+ PM_TIME_NSEC PM_TIME_USEC PM_TIME_MSEC PM_TIME_SEC PM_TIME_MIN PM_TIME_HOUR
+ PM_COUNT_ONE
+ PM_TYPE_NOSUPPORT PM_TYPE_32 PM_TYPE_U32 PM_TYPE_64 PM_TYPE_U64
+ PM_TYPE_FLOAT PM_TYPE_DOUBLE PM_TYPE_STRING
+ PM_SEM_COUNTER PM_SEM_INSTANT PM_SEM_DISCRETE
+ PM_ERR_GENERIC PM_ERR_PMNS PM_ERR_NOPMNS PM_ERR_DUPPMNS PM_ERR_TEXT
+ PM_ERR_APPVERSION PM_ERR_VALUE PM_ERR_TIMEOUT
+ PM_ERR_NODATA PM_ERR_RESET PM_ERR_NAME PM_ERR_PMID
+ PM_ERR_INDOM PM_ERR_INST PM_ERR_UNIT PM_ERR_CONV PM_ERR_TRUNC
+ PM_ERR_SIGN PM_ERR_PROFILE PM_ERR_IPC PM_ERR_EOF
+ PM_ERR_NOTHOST PM_ERR_EOL PM_ERR_MODE PM_ERR_LABEL PM_ERR_LOGREC
+ PM_ERR_NOTARCHIVE PM_ERR_NOCONTEXT PM_ERR_PROFILESPEC PM_ERR_PMID_LOG
+ PM_ERR_INDOM_LOG PM_ERR_INST_LOG PM_ERR_NOPROFILE PM_ERR_NOAGENT
+ PM_ERR_PERMISSION PM_ERR_CONNLIMIT PM_ERR_AGAIN PM_ERR_ISCONN
+ PM_ERR_NOTCONN PM_ERR_NEEDPORT PM_ERR_NONLEAF
+ PM_ERR_PMDANOTREADY PM_ERR_PMDAREADY
+ PM_ERR_TOOSMALL PM_ERR_TOOBIG PM_ERR_NYI
+);
+@EXPORT_OK = qw();
+$VERSION = '1.15';
+
+# metric identification
+sub PM_ID_NULL { 0xffffffff; }
+sub PM_INDOM_NULL { 0xffffffff; }
+sub PM_IN_NULL { 0xffffffff; }
+
+# units - space scale
+sub PM_SPACE_BYTE { 0; } # bytes
+sub PM_SPACE_KBYTE { 1; } # kilobytes
+sub PM_SPACE_MBYTE { 2; } # megabytes
+sub PM_SPACE_GBYTE { 3; } # gigabytes
+sub PM_SPACE_TBYTE { 4; } # terabytes
+sub PM_SPACE_PBYTE { 5; } # petabytes
+sub PM_SPACE_EBYTE { 6; } # exabytes
+
+# units - time scale
+sub PM_TIME_NSEC { 0; } # nanoseconds
+sub PM_TIME_USEC { 1; } # microseconds
+sub PM_TIME_MSEC { 2; } # milliseconds
+sub PM_TIME_SEC { 3; } # seconds
+sub PM_TIME_MIN { 4; } # minutes
+sub PM_TIME_HOUR { 5; } # hours
+
+# units - count scale (for metrics such as count events, syscalls,
+# interrupts, etc - these are simply powers of ten and not enumerated here
+# (e.g. 6 for 10^6, or -3 for 10^-3).
+sub PM_COUNT_ONE { 0; } # 1
+
+# data type of metric values
+sub PM_TYPE_NOSUPPORT { 0xffffffff; } # not implemented in this version
+sub PM_TYPE_32 { 0; } # 32-bit signed integer
+sub PM_TYPE_U32 { 1; } # 32-bit unsigned integer
+sub PM_TYPE_64 { 2; } # 64-bit signed integer
+sub PM_TYPE_U64 { 3; } # 64-bit unsigned integer
+sub PM_TYPE_FLOAT { 4; } # 32-bit floating point
+sub PM_TYPE_DOUBLE { 5; } # 64-bit floating point
+sub PM_TYPE_STRING { 6; } # array of characters
+sub PM_TYPE_AGGREGATE { 7; } # arbitrary binary data (aggregate)
+sub PM_TYPE_AGGREGATE_STATIC { 8; } # static pointer to aggregate
+sub PM_TYPE_EVENT { 9; } # packed pmEventArray
+sub PM_TYPE_UNKNOWN { 255; }
+
+# semantics/interpretation of metric values
+sub PM_SEM_COUNTER { 1; } # cumulative counter (monotonic increasing)
+sub PM_SEM_INSTANT { 3; } # instantaneous value, continuous domain
+sub PM_SEM_DISCRETE { 4; } # instantaneous value, discrete domain
+
+# error codes
+sub PM_ERR_GENERIC { -12345; } # Generic error, already reported above
+sub PM_ERR_PMNS { -12346; } # Problems parsing PMNS definitions
+sub PM_ERR_NOPMNS { -12347; } # PMNS not accessible
+sub PM_ERR_DUPPMNS { -12348; } # Attempt to reload the PMNS
+sub PM_ERR_TEXT { -12349; } # Oneline or help text is not available
+sub PM_ERR_APPVERSION { -12350; } # Metric not supported by this version of monitored application
+sub PM_ERR_VALUE { -12351; } # Missing metric value(s)
+sub PM_ERR_TIMEOUT { -12353; } # Timeout waiting for a response from PMCD
+sub PM_ERR_NODATA { -12354; } # Empty archive log file
+sub PM_ERR_RESET { -12355; } # PMCD reset or configuration change
+sub PM_ERR_NAME { -12357; } # Unknown metric name
+sub PM_ERR_PMID { -12358; } # Unknown or illegal metric identifier
+sub PM_ERR_INDOM { -12359; } # Unknown or illegal instance domain identifier
+sub PM_ERR_INST { -12360; } # Unknown or illegal instance identifier
+sub PM_ERR_UNIT { -12361; } # Illegal pmUnits specification
+sub PM_ERR_CONV { -12362; } # Impossible value or scale conversion
+sub PM_ERR_TRUNC { -12363; } # Truncation in value conversion
+sub PM_ERR_SIGN { -12364; } # Negative value in conversion to unsigned
+sub PM_ERR_PROFILE { -12365; } # Explicit instance identifier(s) required
+sub PM_ERR_IPC { -12366; } # IPC protocol failure
+sub PM_ERR_EOF { -12368; } # IPC channel closed
+sub PM_ERR_NOTHOST { -12369; } # Operation requires context with host source of metrics
+sub PM_ERR_EOL { -12370; } # End of PCP archive log
+sub PM_ERR_MODE { -12371; } # Illegal mode specification
+sub PM_ERR_LABEL { -12372; } # Illegal label record at start of a PCP archive log file
+sub PM_ERR_LOGREC { -12373; } # Corrupted record in a PCP archive log
+sub PM_ERR_NOTARCHIVE { -12374; } # Operation requires context with archive source of metrics
+sub PM_ERR_NOCONTEXT { -12376; } # Attempt to use an illegal context
+sub PM_ERR_PROFILESPEC { -12377; } # NULL pmInDom with non-NULL instlist
+sub PM_ERR_PMID_LOG { -12378; } # Metric not defined in the PCP archive log
+sub PM_ERR_INDOM_LOG { -12379; } # Instance domain identifier not defined in the PCP archive log
+sub PM_ERR_INST_LOG { -12380; } # Instance identifier not defined in the PCP archive log
+sub PM_ERR_NOPROFILE { -12381; } # Missing profile - protocol botch
+sub PM_ERR_NOAGENT { -12386; } # No PMCD agent for domain of request
+sub PM_ERR_PERMISSION { -12387; } # No permission to perform requested operation
+
+sub PM_ERR_CONNLIMIT { -12388; } # PMCD connection limit for this host exceeded
+sub PM_ERR_AGAIN { -12389; } # Try again. Information not currently available
+sub PM_ERR_ISCONN { -12390; } # Already Connected
+sub PM_ERR_NOTCONN { -12391; } # Not Connected
+sub PM_ERR_NEEDPORT { -12392; } # A non-null port name is required
+sub PM_ERR_NONLEAF { -12394; } # Metric name is not a leaf in PMNS
+sub PM_ERR_PMDANOTREADY { -13394; } # PMDA is not yet ready to respond to requests
+sub PM_ERR_PMDAREADY { -13393; } # PMDA is now responsive to requests
+sub PM_ERR_TOOSMALL { -12443; } # Insufficient elements in list
+sub PM_ERR_TOOBIG { -12444; } # Result size exceeded
+sub PM_ERR_NYI { -21344; } # Functionality not yet implemented
+
+
+bootstrap PCP::PMDA $VERSION;
+
+1;
+__END__
+
+=head1 NAME
+
+PCP::PMDA - Perl extension for Performance Metrics Domain Agents
+
+=head1 SYNOPSIS
+
+ use PCP::PMDA;
+
+ $pmda = PCP::PMDA->new('myname', $MYDOMAIN);
+
+ $pmda->connect_pmcd;
+
+ $pmda->add_metric($pmid, $type, $indom, $sem, $units, 'name', '', '');
+ $pmda->add_indom($indom, [0 => 'white', 1 => 'black', ...], '', '');
+
+ $pmda->set_fetch(\&fetch_method);
+ $pmda->set_refresh(\&refresh_method);
+ $pmda->set_instance(\&instance_method);
+ $pmda->set_fetch_callback(\&fetch_callback_method);
+ $pmda->set_store_callback(\&store_callback_method);
+
+ $pmda->set_user('pcp');
+
+ $pmda->run;
+
+=head1 DESCRIPTION
+
+The PCP::PMDA Perl module contains the language bindings for
+building Performance Metric Domain Agents (PMDAs) using Perl.
+Each PMDA exports performance data for one specific domain, for
+example the operating system kernel, Cisco routers, a database,
+an application, etc.
+
+=head1 METHODS
+
+=over
+
+=item PCP::PMDA->new(name, domain)
+
+PCP::PMDA class constructor. I<name> is a string that becomes the
+name of the PMDA for messages and default prefix for the names of
+external files used by the PMDA. I<domain> is an integer domain
+number for the PMDA, usually from the register of domain numbers
+found in B<$PCP_VAR_DIR/pmns/stdpmid>.
+
+=item $pmda->run()
+
+Once all local setup is complete (i.e. instance domains and metrics
+are registered, callbacks registered - as discussed below) the PMDA
+must connect to B<pmcd>(1) to complete its initialisation and begin
+answering client requests for its metrics. This is the role performed
+by I<run>, and upon invoking it all interaction within the PMDA is
+done via callback routines (that is to say, under normal cicrumstances,
+the I<run> routine does not return).
+
+The behaviour of the I<run> method is different in the presence of
+either the B<PCP_PERL_PMNS> or B<PCP_PERL_DOMAIN> environment variables.
+These can be used to generate the namespace or domain number files,
+which are used as part of the PMDA installation process.
+
+=item $pmda->connect_pmcd()
+
+Allows the PMDA to set up the IPC channel to B<pmcd>(1) and complete
+the credentials handshake with B<pmcd>(1). If I<connect_pmcd> is not
+explicitly called the setup and handshake will be done when the
+I<run> method is called.
+
+The advantage of explicitly calling I<connect_pmcd> early in the life
+of the PMDA is that this reduces the risk of a fatal timeout during
+the credentials handshake, which may be an issue if the PMDA has
+considerable work to do, e.g. determining which metrics and
+instance domains are available, before calling I<run>.
+
+=item $pmda->add_indom(indom, insts, help, longhelp)
+
+Define a new instance domain. The instance domain identifier is
+I<indom>, which is an integer and unique across all instance domains
+for single PMDA.
+
+The instances of the instance domain are defined by I<insts> which
+can be specified as either a list or a hash.
+
+In list form, the contents of the list must provide consecutive pairs
+of identifier (a integer, unique across all instances in the instance
+domain) and external instance name (a string, must by unique up to the
+first space, if any, across all instances in the instance domain).
+For example:
+
+ @colours = [0 => 'red', 1 => 'green', 2 => 'blue'];
+
+In hash form, the external instance identifier (string) is used as the
+hash key. An arbitrary value can be stored along with the key (this
+value is often used as a convenient place to hold the latest value for
+each metric instance, for example).
+
+ %timeslices = ('sec' => 42, 'min' => \&min_func, 'hour' => '0');
+
+The I<help> and I<longhelp> strings are interpreted as the one-line and
+expanded help text to be used for this instance domain as further
+described in B<pmLookupInDomText>(3).
+
+Refer also to the B<replace_indom>() discussion below for further details
+about manipulating instance domains.
+
+=item $pmda->add_metric(pmid, type, indom, sem, units, name, help, longhelp)
+
+Define a new metric identified by the PMID I<pmid> and the full
+metric name I<name>.
+
+The metric's metadata is defined by I<type>, I<indom>, I<sem> and
+I<units> and these parameters are used to set up the I<pmDesc>
+structure as described in B<pmLookupDesc>(3).
+
+The I<help> and I<longhelp> strings are interpreted as the one-line
+and expanded help text to be used for the metric as further described
+in B<pmLookupText>(3).
+
+=item $pmda->replace_indom(index, insts)
+
+Whenever an instance domain identified by I<index>,
+previously registered using B<add_indom>(),
+changes in any way, this change must be reflected by replacing the
+existing mapping with a new one (I<insts>).
+
+The replacement mapping must be a hash if the instance domain
+was registered initially with B<add_indom>() as a hash, otherwise it must be
+a list.
+
+Refer to the earlier B<add_indom>() discussion concerning these two
+different types of instance domains definitions.
+
+=item $pmda->add_pipe(command, callback, data)
+
+Allow data to be injected into the PMDA using a B<pipe>(2).
+
+The given I<command> is run early in the life of the PMDA, and a pipe
+is formed between the PMDA and the I<command>. Line-oriented output
+is assumed (else truncation will occur), and on receipt of each line
+of text on the pipe, the I<callback> function will be called.
+
+The optional I<data> parameter can be used to specify extra data to
+pass into the I<callback> routine.
+
+=item $pmda->add_sock(hostname, port, callback, data)
+
+Create a B<socket>(2) connection to the I<hostname>, I<port> pair.
+Whenever data arrives (as above, a line-oriented protocol is best)
+the I<callback> function will be called.
+
+The optional I<data> parameter can be used to specify extra data to
+pass into the I<callback> routine.
+
+An opaque integer-sized identifier for the socket will be returned,
+which can later be used in calls to B<put_sock>() as discussed below.
+
+=item $pmda->put_sock(id, output)
+
+Write an I<output> string to the socket identified by I<id>, which
+must refer to a socket previously registered using B<add_sock>().
+
+=item $pmda->add_tail(filename, callback, data)
+
+Monitor the given I<filename> for the arrival of newly appended
+information. Line-oriented input is assumed (else truncation
+will occur), and on receipt of each line of text on the pipe,
+the I<callback> function will be called.
+
+The optional I<data> parameter can be used to specify extra data to
+pass into the I<callback> routine.
+
+This interface deals with the issue of the file being renamed (such
+as on daily log file rotation), and will attempt to automatically
+re-route information from the new log file if this occurs.
+
+=item $pmda->add_timer(timeout, callback, data)
+
+Registers a timer with the PMDA, such that on expiry of a I<timeout>
+a I<callback> routine will be called. This is a repeating timer.
+
+The optional I<data> parameter can be used to specify extra data to
+pass into the I<callback> routine.
+
+=item $pmda->err(message)
+
+Report a timestamped error message into the PMDA log file.
+
+=item $pmda->error(message)
+
+Report a timestamped error message into the PMDA log file.
+
+=item $pmda->log(message)
+
+Report a timestamped informational message into the PMDA log file.
+
+=item $pmda->set_fetch_callback(cb_function)
+
+Register a callback function akin to B<pmdaSetFetchCallBack>(3).
+
+=item $pmda->set_fetch(function)
+
+Register a fetch function, as used by B<pmdaInit>(3).
+
+=item $pmda->set_instance(function)
+
+Register an instance function, as used by B<pmdaInit>(3).
+
+=item $pmda->set_refresh(function)
+
+Register a refresh function, which will be called once per metric
+cluster, during the fetch operation. Only clusters being requested
+during this fetch will be refreshed, allowing selective metric value
+updates within the PMDA.
+
+=item $pmda->set_store_callback(cb_function)
+
+Register an store function, used indirectly by B<pmdaInit>(3).
+The I<cb_function> is called once for each metric/instance pair
+into which a B<pmStore>(3) is performed.
+
+=item $pmda->set_inet_socket(port)
+
+Specify the IPv4 socket I<port> to be used to communicate with B<pmcd>(1).
+
+=item $pmda->set_ipv6_socket(port)
+
+Specify the IPv6 socket I<port> to be used to communicate with B<pmcd>(1).
+
+=item $pmda->set_unix_socket(socket_name)
+
+Specify the filesystem I<socket_name> path to be used for communication
+with B<pmcd>(1).
+
+=item $pmda->set_user(username)
+
+Run the PMDA under the I<username> user account, instead of the
+default (root) user.
+
+=back
+
+=head1 HELPER METHODS
+
+=over
+
+=item pmda_pmid(cluster, item)
+
+Construct a Performance Metric Identifier (PMID) from the domain
+number (passed as an argument to the I<new> constructor), the
+I<cluster> (an integer in the range 0 to 2^12-1) and the
+I<item> (an integer in the range 0 to 2^10-1).
+
+Every performance metric exported from a PMDA must have a unique
+PMID.
+
+=item pmda_pmid_name(cluster, item)
+
+Perform a reverse metric identifier to name lookup - given the metric
+I<cluster> and I<item> numbers, returns the metric name string.
+
+=item pmda_pmid_text(cluster, item)
+
+Returns the one-line metric help text string - given the metric
+I<cluster> and I<item> numbers, returns the help text string.
+
+=item pmda_inst_name(index, instance)
+
+Perform a reverse instance identifier to instance name lookup
+for the instance domain identified by I<index>.
+Given the
+internal I<instance> identifier, returns the external instance name string.
+
+=item pmda_inst_lookup(index, instance)
+
+Given an internal I<instance> identifier (key) for the
+instance domain identified by I<index> with an associated indom hash,
+return the value associated with that key.
+The value can be any scalar value (this includes references, of course,
+so complex data structures can be referenced).
+
+=item pmda_units(dim_space, dim_time, dim_count, scale_space, scale_time, scale_count)
+
+Construct a B<pmUnits> structure suitable for registering a metrics metadata
+via B<add_metric>().
+
+=item pmda_config(name)
+
+Lookup the value for configuration variable I<name> from the
+I</etc/pcp.conf> file,
+using B<pmGetConfig>(3).
+
+=item pmda_uptime(now)
+
+Return a human-readable uptime string, based on I<now> seconds since the epoch.
+
+=item pmda_long()
+
+Return either PM_TYPE_32 or PM_TYPE_64 depending on the platform size for a
+signed long integer.
+
+=item pmda_ulong()
+
+Return either PM_TYPE_U32 or PM_TYPE_U64 depending on the platform size for an
+unsigned long integer.
+
+=back
+
+=head1 MACROS
+
+Most of the PM_* macros from the PCP C headers are available.
+
+For example the I<type> of a metric's value may be directly
+specified as one of
+B<PM_TYPE_32>, B<PM_TYPE_U32>, B<PM_TYPE_64>, B<PM_TYPE_U64>,
+B<PM_TYPE_FLOAT>, B<PM_TYPE_DOUBLE>, B<PM_TYPE_STRING> or
+B<PM_TYPE_NOSUPPORT>.
+
+=head1 DEBUGGING
+
+Perl PMDAs do not follow the B<-D> convention of other PCP applications
+for enabling run-time diagnostics and tracing. Rather the environment
+variable B<PCP_PERL_DEBUG> needs to be set to a string value matching
+the syntax accepted for the option value for B<-D> elsewhere, see
+B<__pmParseDebug>(3).
+
+This requires a little trickery. The B<pmcd>(1) configuration file
+(B<PCP_PMCDCONF_PATH> from I</etc/pcp.conf>) needs hand editing.
+This is best demonstrated by example.
+
+Replace this line
+
+ foo 242 pipe binary python /somepath/foo.py
+
+with
+
+ foo 242 pipe binary python \
+ sh -c "PCP_PERL_DEBUG=pdu,fetch /usr/bin/python /somepath/foo.py"
+
+=head1 SEE ALSO
+
+perl(1) and PCPIntro(1).
+
+The PCP mailing list pcp@mail.performancecopilot.org can be used for
+questions about this module.
+
+Further details can be found at http://www.performancecopilot.org/
+
+=head1 AUTHOR
+
+The Performance Co-Pilot development team.
+
+Copyright (C) 2014 Red Hat.
+Copyright (C) 2008-2010 Aconex.
+Copyright (C) 2004 Silicon Graphics, Inc.
+
+This library is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License, version 2 (see
+the "COPYING" file in the PCP source tree for further details).
+
+=cut
diff --git a/src/perl/PMDA/PMDA.xs b/src/perl/PMDA/PMDA.xs
new file mode 100644
index 0000000..a6d16b2
--- /dev/null
+++ b/src/perl/PMDA/PMDA.xs
@@ -0,0 +1,1212 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2008-2012 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. 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.
+ */
+
+#include "local.h"
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+static pmdaInterface dispatch;
+static pmdaMetric *metrictab;
+static int mtab_size;
+static __pmnsTree *pmns;
+static int need_refresh;
+static pmdaIndom *indomtab;
+static int itab_size;
+static int *clustertab;
+static int ctab_size;
+
+static HV *metric_names;
+static HV *metric_oneline;
+static HV *metric_helptext;
+static HV *indom_helptext;
+static HV *indom_oneline;
+
+static SV *fetch_func;
+static SV *refresh_func;
+static SV *instance_func;
+static SV *store_cb_func;
+static SV *fetch_cb_func;
+
+int
+clustertab_lookup(int cluster)
+{
+ int i, found = 0;
+
+ for (i = 0; i < ctab_size; i++) {
+ if (cluster == clustertab[i]) {
+ found = 1;
+ break;
+ }
+ }
+ return found;
+}
+
+void
+clustertab_replace(int index, int cluster)
+{
+ if (index >= 0 && index < ctab_size)
+ clustertab[index] = cluster;
+ else
+ warn("invalid cluster table replacement requested");
+}
+
+void
+clustertab_scratch()
+{
+ memset(clustertab, -1, sizeof(int) * ctab_size);
+}
+
+void
+clustertab_refresh(int index)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVuv(clustertab[index])));
+ PUTBACK;
+
+ perl_call_sv(refresh_func, G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+void
+refresh(int numpmid, pmID *pmidlist)
+{
+ int i, numclusters = 0;
+ __pmID_int *pmid;
+
+ /* Create list of affected clusters from pmidlist
+ * Note: we overwrite the initial cluster array here, to avoid
+ * allocating memory. The initial array contains all possible
+ * clusters whereas we (possibly) construct a subset here. We
+ * do not touch ctab_size at all, however, which lets us reuse
+ * the preallocated array space on every fetch.
+ */
+ clustertab_scratch();
+ for (i = 0; i < numpmid; i++) {
+ pmid = (__pmID_int *) &pmidlist[i];
+ if (clustertab_lookup(pmid->cluster) == 0)
+ clustertab_replace(numclusters++, pmid->cluster);
+ }
+
+ /* For each unique cluster, call the cluster refresh method */
+ for (i = 0; i < numclusters; i++)
+ clustertab_refresh(i);
+}
+
+void
+pmns_refresh(void)
+{
+ char *pmid, *next;
+ I32 idsize;
+ SV *metric;
+ int sts;
+
+ if (pmns)
+ __pmFreePMNS(pmns);
+
+ if ((sts = __pmNewPMNS(&pmns)) < 0)
+ croak("failed to create namespace root: %s", pmErrStr(sts));
+
+ hv_iterinit(metric_names);
+ while ((metric = hv_iternextsv(metric_names, &pmid, &idsize)) != NULL) {
+ unsigned int domain, cluster, item, id;
+
+ domain = strtoul(pmid, &next, 10);
+ cluster = strtoul(next+1, &next, 10);
+ item = strtoul(next+1, &next, 10);
+ id = pmid_build(domain, cluster, item);
+ if ((sts = __pmAddPMNSNode(pmns, id, SvPV_nolen(metric))) < 0)
+ croak("failed to add metric %s(%s) to namespace: %s",
+ SvPV_nolen(metric), pmIDStr(id), pmErrStr(sts));
+ }
+
+ pmdaTreeRebuildHash(pmns, mtab_size); /* for reverse (pmid->name) lookups */
+ need_refresh = 0;
+}
+
+int
+pmns_desc(pmID pmid, pmDesc *desc, pmdaExt *ep)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaDesc(pmid, desc, ep);
+}
+
+int
+pmns_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreePMID(pmns, name, pmid);
+}
+
+int
+pmns_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreeName(pmns, pmid, nameset);
+}
+
+int
+pmns_children(const char *name, int traverse, char ***kids, int **sts, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreeChildren(pmns, name, traverse, kids, sts);
+}
+
+void
+pmns_write(void)
+{
+ __pmnsNode *node;
+ char *pppenv = getenv("PCP_PERL_PMNS");
+ int root = pppenv ? strcmp(pppenv, "root") == 0 : 0;
+ char *prefix = root ? "\t" : "";
+
+ pmns_refresh();
+
+ if (root)
+ printf("root {\n");
+ for (node = pmns->root->first; node != NULL; node = node->next)
+ printf("%s%s\t%u:*:*\n", prefix, node->name, dispatch.domain);
+ if (root)
+ printf("}\n");
+}
+
+void
+domain_write(void)
+{
+ char *p, name[512] = { 0 };
+ int i, len = strlen(pmProgname);
+
+ if (len >= sizeof(name) - 1)
+ len = sizeof(name) - 2;
+ p = pmProgname;
+ if (strncmp(pmProgname, "pmda", 4) == 0)
+ p += 4;
+ for (i = 0; i < len; i++)
+ name[i] = toupper(p[i]);
+ printf("#define %s %u\n", name, dispatch.domain);
+}
+
+void
+prefetch(void)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ PUTBACK;
+
+ perl_call_sv(fetch_func, G_VOID|G_NOARGS);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+int
+fetch(int numpmid, pmID *pmidlist, pmResult **rp, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ if (fetch_func)
+ prefetch();
+ if (refresh_func)
+ refresh(numpmid, pmidlist);
+ return pmdaFetch(numpmid, pmidlist, rp, pmda);
+}
+
+int
+instance_index(pmInDom indom)
+{
+ int i;
+
+ for (i = 0; i < itab_size; i++)
+ if (indomtab[i].it_indom == indom)
+ return i;
+ return PM_INDOM_NULL;
+}
+
+void
+preinstance(pmInDom indom)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVuv(indom)));
+ PUTBACK;
+
+ perl_call_sv(instance_func, G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+int
+instance(pmInDom indom, int a, char *b, __pmInResult **rp, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ if (instance_func)
+ preinstance(instance_index(indom));
+ return pmdaInstance(indom, a, b, rp, pmda);
+}
+
+void
+timer_callback(int afid, void *data)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSViv(local_timer_get_cookie(afid))));
+ PUTBACK;
+
+ perl_call_sv(local_timer_get_callback(afid), G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+void
+input_callback(SV *input_cb_func, int data, char *string)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSViv(data)));
+ XPUSHs(sv_2mortal(newSVpv(string,0)));
+ PUTBACK;
+
+ perl_call_sv(input_cb_func, G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+int
+fetch_callback(pmdaMetric *metric, unsigned int inst, pmAtomValue *atom)
+{
+ dSP;
+ __pmID_int *pmid;
+ int sts;
+ STRLEN n_a; /* required by older Perl versions, used in POPpx */
+
+ ENTER;
+ SAVETMPS; /* allows us to tidy our perl stack changes later */
+
+ (void)n_a;
+ pmid = (__pmID_int *) &metric->m_desc.pmid;
+
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVuv(pmid->cluster)));
+ XPUSHs(sv_2mortal(newSVuv(pmid->item)));
+ XPUSHs(sv_2mortal(newSVuv(inst)));
+ PUTBACK;
+
+ sts = perl_call_sv(fetch_cb_func, G_ARRAY);
+ SPAGAIN; /* refresh local perl stack pointer after call */
+ if (sts != 2) {
+ croak("fetch CB error (returned %d values, expected 2)", sts);
+ sts = -EINVAL;
+ goto fetch_end;
+ }
+ sts = POPi; /* pop function return status */
+ if (sts < 0) {
+ goto fetch_end;
+ }
+ else if (sts == 0) {
+ sts = POPi;
+ goto fetch_end;
+ }
+
+ sts = PMDA_FETCH_STATIC;
+ switch (metric->m_desc.type) { /* pop result value */
+ case PM_TYPE_32: atom->l = POPi; break;
+ case PM_TYPE_U32: atom->ul = POPi; break;
+ case PM_TYPE_64: atom->ll = POPl; break;
+ case PM_TYPE_U64: atom->ull = POPl; break;
+ case PM_TYPE_FLOAT: atom->f = POPn; break;
+ case PM_TYPE_DOUBLE: atom->d = POPn; break;
+ case PM_TYPE_STRING: {
+ atom->cp = strdup(POPpx);
+ sts = PMDA_FETCH_DYNAMIC;
+ break;
+ }
+ }
+
+fetch_end:
+ PUTBACK;
+ FREETMPS;
+ LEAVE; /* fix up the perl stack, freeing anything we created */
+ return sts;
+}
+
+int
+store_callback(__pmID_int *pmid, unsigned int inst, pmAtomValue av, int type)
+{
+ dSP;
+ int sts;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVuv(pmid->cluster)));
+ XPUSHs(sv_2mortal(newSVuv(pmid->item)));
+ XPUSHs(sv_2mortal(newSVuv(inst)));
+ switch (type) {
+ case PM_TYPE_32: XPUSHs(sv_2mortal(newSViv(av.l))); break;
+ case PM_TYPE_U32: XPUSHs(sv_2mortal(newSVuv(av.ul))); break;
+ case PM_TYPE_64: XPUSHs(sv_2mortal(newSVuv(av.ll))); break;
+ case PM_TYPE_U64: XPUSHs(sv_2mortal(newSVuv(av.ull))); break;
+ case PM_TYPE_FLOAT: XPUSHs(sv_2mortal(newSVnv(av.f))); break;
+ case PM_TYPE_DOUBLE: XPUSHs(sv_2mortal(newSVnv(av.d))); break;
+ case PM_TYPE_STRING: XPUSHs(sv_2mortal(newSVpv(av.cp,0)));break;
+ }
+ PUTBACK;
+
+ sts = perl_call_sv(store_cb_func, G_SCALAR);
+ SPAGAIN; /* refresh local perl stack pointer after call */
+ if (sts != 1) {
+ croak("store CB error (returned %d values, expected 1)", sts);
+ sts = -EINVAL;
+ goto store_end;
+ }
+ sts = POPi; /* pop function return status */
+
+store_end:
+ PUTBACK;
+ FREETMPS;
+ LEAVE; /* fix up the perl stack, freeing anything we created */
+ return sts;
+}
+
+int
+store(pmResult *result, pmdaExt *pmda)
+{
+ int i, j;
+ int type;
+ int sts;
+ pmAtomValue av;
+ pmValueSet *vsp;
+ __pmID_int *pmid;
+
+ if (need_refresh)
+ pmns_refresh();
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmid = (__pmID_int *)&vsp->pmid;
+
+ /* need to find the type associated with this PMID */
+ for (j = 0; j < mtab_size; j++)
+ if (metrictab[j].m_desc.pmid == *(pmID *)pmid)
+ break;
+ if (j == mtab_size)
+ return PM_ERR_PMID;
+ type = metrictab[j].m_desc.type;
+
+ for (j = 0; j < vsp->numval; j++) {
+ sts = pmExtractValue(vsp->valfmt, &vsp->vlist[j],type, &av, type);
+ if (sts < 0)
+ return sts;
+ sts = store_callback(pmid, vsp->vlist[j].inst, av, type);
+ if (sts < 0)
+ return sts;
+ }
+ }
+ return 0;
+}
+
+int
+text(int ident, int type, char **buffer, pmdaExt *pmda)
+{
+ const char *hash;
+ int size;
+ SV **sv;
+
+ if (need_refresh)
+ pmns_refresh();
+
+ if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) {
+ hash = pmIDStr((pmID)ident);
+ size = strlen(hash);
+ if (type & PM_TEXT_ONELINE)
+ sv = hv_fetch(metric_oneline, hash, size, 0);
+ else
+ sv = hv_fetch(metric_helptext, hash, size, 0);
+ }
+ else {
+ hash = pmInDomStr((pmInDom)ident);
+ size = strlen(hash);
+ if (type & PM_TEXT_ONELINE)
+ sv = hv_fetch(indom_oneline, hash, size, 0);
+ else
+ sv = hv_fetch(indom_helptext, hash, size, 0);
+ }
+
+ if (sv && (*sv))
+ *buffer = SvPV_nolen(*sv);
+ return (*buffer == NULL) ? PM_ERR_TEXT : 0;
+}
+
+/*
+ * Converts Perl hash ref like {'foo' => \&data, 'boo' => \&data}
+ * into an instance structure (indom).
+ */
+static int
+update_hash_indom(SV *insts, pmInDom indom)
+{
+ int sts;
+ SV *data;
+ I32 instsize;
+ char *instance;
+ HV *ihash = (HV *) SvRV(insts);
+
+ sts = pmdaCacheOp(indom, PMDA_CACHE_INACTIVE);
+ if (sts < 0)
+ warn("pmda cache inactivation failed: %s", pmErrStr(sts));
+
+ hv_iterinit(ihash);
+ while ((data = hv_iternextsv(ihash, &instance, &instsize)) != NULL)
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, instance, SvREFCNT_inc(data));
+
+ sts = pmdaCacheOp(indom, PMDA_CACHE_SAVE);
+ if (sts < 0)
+ warn("pmda cache persistance failed: %s", pmErrStr(sts));
+
+ return 0;
+}
+
+/*
+ * Free all memory associated with a Perl list based indom
+ */
+static void
+release_list_indom(pmdaInstid *instances, int numinst)
+{
+ int i;
+
+ if (instances && numinst > 0) {
+ for (i = 0; i < numinst; i++)
+ free(instances[i].i_name); /* update_list_indom strdup */
+ free(instances); /* update_list_indom calloc */
+ }
+}
+
+/*
+ * Converts Perl list ref like [a => 'foo', b => 'boo'] into an indom.
+ */
+static int
+update_list_indom(SV *insts, pmdaInstid **set)
+{
+ int i, len;
+ SV **id;
+ SV **name;
+ AV *ilist = (AV *) SvRV(insts);
+ pmdaInstid *instances;
+
+ if ((len = av_len(ilist)) == -1) { /* empty */
+ *set = NULL;
+ return 0;
+ }
+ if (len++ % 2 == 0) {
+ warn("invalid instance list (length must be a multiple of 2)");
+ return -1;
+ }
+
+ len /= 2;
+ instances = (pmdaInstid *) calloc(len, sizeof(pmdaInstid));
+ if (instances == NULL) {
+ warn("insufficient memory for instance array");
+ return -1;
+ }
+ for (i = 0; i < len; i++) {
+ id = av_fetch(ilist,i*2,0);
+ name = av_fetch(ilist,i*2+1,0);
+ instances[i].i_inst = SvIV(*id);
+ instances[i].i_name = strdup(SvPV_nolen(*name));
+ if (instances[i].i_name == NULL) {
+ release_list_indom(instances, i);
+ warn("insufficient memory for instance array names");
+ return -1;
+ }
+ }
+ *set = instances;
+ return len;
+}
+
+/*
+ * Converts a Perl instance reference into a populated indom.
+ * This interface handles either the hash or list formats.
+ */
+static int
+update_indom(SV *insts, pmInDom indom, pmdaInstid **set)
+{
+ SV *rv = (SV *) SvRV(insts);
+ pmdaInstid *instances;
+
+ if (! SvROK(insts)) {
+ warn("expected a reference for instances argument");
+ return -1;
+ }
+ if (SvTYPE(rv) == SVt_PVAV)
+ return update_list_indom(insts, set);
+ if (SvTYPE(rv) == SVt_PVHV)
+ return update_hash_indom(insts, indom);
+ warn("instance argument is neither an array nor hash reference");
+ return -1;
+}
+
+
+MODULE = PCP::PMDA PACKAGE = PCP::PMDA
+
+
+pmdaInterface *
+new(CLASS,name,domain)
+ char * CLASS
+ char * name
+ int domain
+ PREINIT:
+ int sep;
+ char * p;
+ char * logfile;
+ char * pmdaname;
+ char helpfile[256];
+ CODE:
+ pmProgname = name;
+ RETVAL = &dispatch;
+ logfile = local_strdup_suffix(name, ".log");
+ pmdaname = local_strdup_prefix("pmda", name);
+ __pmSetProgname(pmdaname);
+ sep = __pmPathSeparator();
+ if ((p = getenv("PCP_PERL_DEBUG")) != NULL)
+ if ((pmDebug = __pmParseDebug(p)) < 0)
+ pmDebug = 0;
+#ifndef IS_MINGW
+ setsid();
+#endif
+ atexit(&local_atexit);
+ snprintf(helpfile, sizeof(helpfile), "%s%c%s%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, name, sep);
+ if (access(helpfile, R_OK) != 0) {
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmdaname, domain,
+ logfile, NULL);
+ dispatch.version.four.text = text;
+ }
+ else {
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmdaname, domain,
+ logfile, strdup(helpfile));
+ }
+ dispatch.version.four.fetch = fetch;
+ dispatch.version.four.instance = instance;
+ dispatch.version.four.desc = pmns_desc;
+ dispatch.version.four.pmid = pmns_pmid;
+ dispatch.version.four.name = pmns_name;
+ dispatch.version.four.children = pmns_children;
+
+ if (!getenv("PCP_PERL_PMNS") && !getenv("PCP_PERL_DOMAIN")) {
+ pmdaOpenLog(&dispatch);
+ }
+ metric_names = newHV();
+ metric_oneline = newHV();
+ metric_helptext = newHV();
+ indom_helptext = newHV();
+ indom_oneline = newHV();
+ OUTPUT:
+ RETVAL
+
+int
+pmda_pmid(cluster,item)
+ unsigned int cluster
+ unsigned int item
+ CODE:
+ RETVAL = pmid_build(dispatch.domain, cluster, item);
+ OUTPUT:
+ RETVAL
+
+SV *
+pmda_pmid_name(cluster,item)
+ unsigned int cluster
+ unsigned int item
+ PREINIT:
+ const char *name;
+ SV **rval;
+ CODE:
+ name = pmIDStr(pmid_build(dispatch.domain, cluster, item));
+ rval = hv_fetch(metric_names, name, strlen(name), 0);
+ if (!rval || !(*rval))
+ XSRETURN_UNDEF;
+ RETVAL = newSVsv(*rval);
+ OUTPUT:
+ RETVAL
+
+SV *
+pmda_pmid_text(cluster,item)
+ unsigned int cluster
+ unsigned int item
+ PREINIT:
+ const char *name;
+ SV **rval;
+ CODE:
+ name = pmIDStr(pmid_build(dispatch.domain, cluster, item));
+ rval = hv_fetch(metric_oneline, name, strlen(name), 0);
+ if (!rval || !(*rval))
+ XSRETURN_UNDEF;
+ RETVAL = newSVsv(*rval);
+ OUTPUT:
+ RETVAL
+
+SV *
+pmda_inst_name(index,instance)
+ unsigned int index
+ int instance
+ PREINIT:
+ int i;
+ pmdaIndom * p;
+ pmdaInstid *instp;
+ CODE:
+ if (index >= itab_size) /* is this a valid indom */
+ XSRETURN_UNDEF;
+ p = indomtab + index;
+ if (!p->it_set) /* was this indom previously setup via a hash? */
+ XSRETURN_UNDEF;
+
+ /* Optimistic (fast) direct lookup first, then iterate */
+ i = instance;
+ if (i > p->it_numinst || i < 0 || instance != p->it_set[i].i_inst) {
+ for (i = 0; i < p->it_numinst; i++)
+ if (instance == p->it_set[i].i_inst)
+ break;
+ if (i == p->it_numinst)
+ XSRETURN_UNDEF;
+ }
+ RETVAL = newSVpv(p->it_set[i].i_name,0);
+ OUTPUT:
+ RETVAL
+
+SV *
+pmda_inst_lookup(index,instance)
+ unsigned int index
+ int instance
+ PREINIT:
+ pmdaIndom * p;
+ SV * svp;
+ int i, sts;
+ CODE:
+ if (index >= itab_size) /* is this a valid indom */
+ XSRETURN_UNDEF;
+ p = indomtab + index;
+ if (p->it_set) /* was this indom previously setup via an array? */
+ XSRETURN_UNDEF;
+ sts = pmdaCacheLookup(p->it_indom, instance, NULL, (void *)&svp);
+ if (sts != PMDA_CACHE_ACTIVE)
+ XSRETURN_UNDEF;
+ RETVAL = SvREFCNT_inc(svp);
+ OUTPUT:
+ RETVAL
+
+int
+pmda_units(dim_space,dim_time,dim_count,scale_space,scale_time,scale_count)
+ unsigned int dim_space
+ unsigned int dim_time
+ unsigned int dim_count
+ unsigned int scale_space
+ unsigned int scale_time
+ unsigned int scale_count
+ PREINIT:
+ pmUnits units;
+ CODE:
+ units.pad = 0;
+ units.dimSpace = dim_space; units.scaleSpace = scale_space;
+ units.dimTime = dim_time; units.scaleTime = scale_time;
+ units.dimCount = dim_count; units.scaleCount = scale_count;
+ RETVAL = *(int *)(&units);
+ OUTPUT:
+ RETVAL
+
+char *
+pmda_config(name)
+ char * name
+ CODE:
+ RETVAL = pmGetConfig(name);
+ if (!RETVAL)
+ XSRETURN_UNDEF;
+ OUTPUT:
+ RETVAL
+
+char *
+pmda_uptime(now)
+ int now
+ PREINIT:
+ static char s[32];
+ size_t sz = sizeof(s);
+ int days, hours, mins, secs;
+ CODE:
+ days = now / (60 * 60 * 24);
+ now %= (60 * 60 * 24);
+ hours = now / (60 * 60);
+ now %= (60 * 60);
+ mins = now / 60;
+ now %= 60;
+ secs = now;
+
+ if (days > 1)
+ snprintf(s, sz, "%ddays %02d:%02d:%02d", days, hours, mins, secs);
+ else if (days == 1)
+ snprintf(s, sz, "%dday %02d:%02d:%02d", days, hours, mins, secs);
+ else
+ snprintf(s, sz, "%02d:%02d:%02d", hours, mins, secs);
+ RETVAL = s;
+ OUTPUT:
+ RETVAL
+
+int
+pmda_long()
+ CODE:
+ RETVAL = (sizeof(long) == 4) ? PM_TYPE_32 : PM_TYPE_64;
+ OUTPUT:
+ RETVAL
+
+int
+pmda_ulong()
+ CODE:
+ RETVAL = (sizeof(unsigned long) == 4) ? PM_TYPE_32 : PM_TYPE_64;
+ OUTPUT:
+ RETVAL
+
+
+void
+error(self,message)
+ pmdaInterface *self
+ char * message
+ CODE:
+ __pmNotifyErr(LOG_ERR, "%s", message);
+
+int
+set_user(self,username)
+ pmdaInterface *self
+ char * username
+ CODE:
+ RETVAL = __pmSetProcessIdentity(username);
+ OUTPUT:
+ RETVAL
+
+void
+set_fetch(self,function)
+ pmdaInterface *self
+ SV * function
+ CODE:
+ if (function != (SV *)NULL) {
+ fetch_func = newSVsv(function);
+ }
+
+void
+set_refresh(self,function)
+ pmdaInterface *self
+ SV * function
+ CODE:
+ if (function != (SV *)NULL) {
+ refresh_func = newSVsv(function);
+ }
+
+void
+set_instance(self,function)
+ pmdaInterface *self
+ SV * function
+ CODE:
+ if (function != (SV *)NULL) {
+ instance_func = newSVsv(function);
+ }
+
+void
+set_store_callback(self,cb_function)
+ pmdaInterface *self
+ SV * cb_function
+ CODE:
+ if (cb_function != (SV *)NULL) {
+ store_cb_func = newSVsv(cb_function);
+ self->version.four.store = store;
+ }
+
+void
+set_fetch_callback(self,cb_function)
+ pmdaInterface *self
+ SV * cb_function
+ CODE:
+ if (cb_function != (SV *)NULL) {
+ fetch_cb_func = newSVsv(cb_function);
+ pmdaSetFetchCallBack(self, fetch_callback);
+ }
+
+void
+set_inet_socket(self,port)
+ pmdaInterface *self
+ int port
+ CODE:
+ self->version.four.ext->e_io = pmdaInet;
+ self->version.four.ext->e_port = port;
+
+void
+set_ipv6_socket(self,port)
+ pmdaInterface *self
+ int port
+ CODE:
+ self->version.four.ext->e_io = pmdaIPv6;
+ self->version.four.ext->e_port = port;
+
+void
+set_unix_socket(self,socket_name)
+ pmdaInterface *self
+ char * socket_name
+ CODE:
+ self->version.four.ext->e_io = pmdaUnix;
+ self->version.four.ext->e_sockname = socket_name;
+
+void
+clear_metrics(self)
+ pmdaInterface *self
+ CODE:
+ need_refresh = 1;
+ if (clustertab)
+ free(clustertab);
+ ctab_size = 0;
+ if (metrictab)
+ free(metrictab);
+ mtab_size = 0;
+ clearHV(metric_names);
+ clearHV(metric_oneline);
+ clearHV(metric_helptext);
+
+void
+add_metric(self,pmid,type,indom,sem,units,name,help,longhelp)
+ pmdaInterface *self
+ int pmid
+ int type
+ int indom
+ int sem
+ int units
+ char * name
+ char * help
+ char * longhelp
+ PREINIT:
+ pmdaMetric * p;
+ __pmID_int * pmidp;
+ const char * hash;
+ int size;
+ CODE:
+ (void)self;
+ need_refresh = 1;
+ pmidp = (__pmID_int *)&pmid;
+ if (!clustertab_lookup(pmidp->cluster)) {
+ size = sizeof(int) * (ctab_size + 1);
+ clustertab = (int *)realloc(clustertab, size);
+ if (clustertab)
+ clustertab[ctab_size++] = pmidp->cluster;
+ else {
+ warn("unable to allocate memory for cluster table");
+ ctab_size = 0;
+ XSRETURN_UNDEF;
+ }
+ }
+
+ size = sizeof(pmdaMetric) * (mtab_size + 1);
+ metrictab = (pmdaMetric *)realloc(metrictab, size);
+ if (metrictab == NULL) {
+ warn("unable to allocate memory for metric table");
+ mtab_size = 0;
+ XSRETURN_UNDEF;
+ }
+
+ p = metrictab + mtab_size++;
+ p->m_user = NULL; p->m_desc.pmid = *(pmID *)&pmid;
+ p->m_desc.type = type; p->m_desc.indom = *(pmInDom *)&indom;
+ p->m_desc.sem = sem; p->m_desc.units = *(pmUnits *)&units;
+
+ hash = pmIDStr(pmid);
+ size = strlen(hash);
+ hv_store(metric_names, hash, size, newSVpv(name,0), 0);
+ if (help)
+ hv_store(metric_oneline, hash, size, newSVpv(help,0), 0);
+ if (longhelp)
+ hv_store(metric_helptext, hash, size, newSVpv(longhelp,0), 0);
+
+void
+clear_indoms(self)
+ pmdaInterface *self
+ CODE:
+ if (indomtab)
+ free(indomtab);
+ itab_size = 0;
+ if (metrictab)
+ free(metrictab);
+ mtab_size = 0;
+ clearHV(indom_oneline);
+ clearHV(indom_helptext);
+
+int
+add_indom(self,indom,insts,help,longhelp)
+ pmdaInterface * self
+ int indom
+ SV * insts
+ char * help
+ char * longhelp
+ PREINIT:
+ pmdaIndom * p;
+ const char * hash;
+ int sts, size;
+ CODE:
+ size = sizeof(pmdaIndom) * (itab_size + 1);
+ indomtab = (pmdaIndom *)realloc(indomtab, size);
+ if (indomtab == NULL) {
+ warn("unable to allocate memory for indom table");
+ itab_size = 0;
+ XSRETURN_UNDEF;
+ }
+
+ p = indomtab + itab_size;
+ memset(p, 0, sizeof(pmdaIndom));
+ p->it_indom = pmInDom_build(self->domain, indom);
+
+ sts = update_indom(insts, p->it_indom, &p->it_set);
+ if (sts < 0)
+ XSRETURN_UNDEF;
+ if (p->it_set)
+ p->it_numinst = sts;
+ RETVAL = itab_size++; /* used in calls to replace_indom() */
+
+ hash = pmInDomStr(indom);
+ size = strlen(hash);
+ if (help)
+ hv_store(indom_oneline, hash, size, newSVpv(help,0), 0);
+ if (longhelp)
+ hv_store(indom_helptext, hash, size, newSVpv(longhelp,0), 0);
+ OUTPUT:
+ RETVAL
+
+int
+replace_indom(self,index,insts)
+ pmdaInterface * self
+ unsigned int index
+ SV * insts
+ PREINIT:
+ pmdaIndom * p;
+ int sts;
+ CODE:
+ if (index >= itab_size) {
+ warn("attempt to replace non-existent instance domain");
+ XSRETURN_UNDEF;
+ }
+ else {
+ p = indomtab + index;
+ /* was this indom previously setup via an array? */
+ if (p->it_set)
+ release_list_indom(p->it_set, p->it_numinst);
+ sts = update_indom(insts, p->it_indom, &p->it_set);
+ if (sts < 0)
+ XSRETURN_UNDEF;
+ if (p->it_set)
+ p->it_numinst = sts;
+ RETVAL = sts;
+ }
+ OUTPUT:
+ RETVAL
+
+int
+add_timer(self,timeout,callback,data)
+ pmdaInterface * self
+ double timeout
+ SV * callback
+ int data
+ CODE:
+ if (getenv("PCP_PERL_PMNS") || getenv("PCP_PERL_DOMAIN") || !callback)
+ XSRETURN_UNDEF;
+ RETVAL = local_timer(timeout, newSVsv(callback), data);
+ OUTPUT:
+ RETVAL
+
+int
+add_pipe(self,command,callback,data)
+ pmdaInterface *self
+ char * command
+ SV * callback
+ int data
+ CODE:
+ if (getenv("PCP_PERL_PMNS") || getenv("PCP_PERL_DOMAIN") || !callback)
+ XSRETURN_UNDEF;
+ RETVAL = local_pipe(command, newSVsv(callback), data);
+ OUTPUT:
+ RETVAL
+
+int
+add_tail(self,filename,callback,data)
+ pmdaInterface *self
+ char * filename
+ SV * callback
+ int data
+ CODE:
+ if (getenv("PCP_PERL_PMNS") || getenv("PCP_PERL_DOMAIN") || !callback)
+ XSRETURN_UNDEF;
+ RETVAL = local_tail(filename, newSVsv(callback), data);
+ OUTPUT:
+ RETVAL
+
+int
+add_sock(self,hostname,port,callback,data)
+ pmdaInterface *self
+ char * hostname
+ int port
+ SV * callback
+ int data
+ CODE:
+ if (getenv("PCP_PERL_PMNS") || getenv("PCP_PERL_DOMAIN") || !callback)
+ XSRETURN_UNDEF;
+ RETVAL = local_sock(hostname, port, newSVsv(callback), data);
+ OUTPUT:
+ RETVAL
+
+int
+put_sock(self,id,output)
+ pmdaInterface *self
+ int id
+ char * output
+ CODE:
+ size_t length = strlen(output);
+ RETVAL = __pmWrite(local_files_get_descriptor(id), output, length);
+ OUTPUT:
+ RETVAL
+
+void
+log(self,message)
+ pmdaInterface *self
+ char * message
+ CODE:
+ __pmNotifyErr(LOG_INFO, "%s", message);
+
+void
+err(self,message)
+ pmdaInterface *self
+ char * message
+ CODE:
+ __pmNotifyErr(LOG_ERR, "%s", message);
+
+void
+connect_pmcd(self)
+ pmdaInterface *self
+ CODE:
+ /*
+ * Need to mimic the same special cases handled in run()
+ * that explicitly do NOT connect to pmcd and treat these
+ * as no-ops here
+ *
+ * Otherwise call pmdaConnet() to complete the PMDA's IPC
+ * channel setup and complete the connection handshake with
+ * pmcd.
+ */
+ if (getenv("PCP_PERL_PMNS") != NULL)
+ ;
+ else if (getenv("PCP_PERL_DOMAIN") != NULL)
+ ;
+ else {
+ /*
+ * On success pmdaConnect sets PMDA_EXT_CONNECTED in e_flags ...
+ * this used in the guard below to stop run() calling
+ * pmdaConnect() again.
+ */
+ pmdaConnect(self);
+ }
+
+void
+run(self)
+ pmdaInterface *self
+ CODE:
+ if (getenv("PCP_PERL_PMNS") != NULL)
+ pmns_write(); /* generate ascii namespace */
+ else if (getenv("PCP_PERL_DOMAIN") != NULL)
+ domain_write(); /* generate the domain header */
+ else { /* or normal operating mode ... */
+ pmns_refresh();
+ pmdaInit(self, indomtab, itab_size, metrictab, mtab_size);
+ if ((self->version.any.ext->e_flags & PMDA_EXT_CONNECTED) != PMDA_EXT_CONNECTED) {
+ /*
+ * connect_pmcd() not called before, so need pmdaConnect()
+ * here before falling into the PDU-driven mainloop
+ */
+ pmdaConnect(self);
+ }
+ local_pmdaMain(self);
+ }
+
+void
+debug_metric(self)
+ pmdaInterface *self
+ PREINIT:
+ int i;
+ CODE:
+ /* NB: debugging only (used in test.pl to verify state) */
+ fprintf(stderr, "metric table size = %d\n", mtab_size);
+ for (i = 0; i < mtab_size; i++) {
+ fprintf(stderr, "metric idx = %d\n\tpmid = %s\n\ttype = %u\n"
+ "\tindom= %d\n\tsem = %u\n\tunits= %u\n",
+ i, pmIDStr(metrictab[i].m_desc.pmid), metrictab[i].m_desc.type,
+ (int)metrictab[i].m_desc.indom, metrictab[i].m_desc.sem,
+ *(unsigned int *)&metrictab[i].m_desc.units);
+ }
+ (void)self;
+
+void
+debug_indom(self)
+ pmdaInterface *self
+ PREINIT:
+ int i,j;
+ CODE:
+ /* NB: debugging only (used in test.pl to verify state) */
+ fprintf(stderr, "indom table size = %d\n", itab_size);
+ for (i = 0; i < itab_size; i++) {
+ fprintf(stderr, "indom idx = %d\n\tindom = %d\n"
+ "\tninst = %u\n\tiptr = 0x%p\n",
+ i, *(int *)&indomtab[i].it_indom, indomtab[i].it_numinst,
+ indomtab[i].it_set);
+ for (j = 0; j < indomtab[i].it_numinst; j++) {
+ fprintf(stderr, "\t\tid=%d name=%s\n",
+ indomtab[i].it_set[j].i_inst, indomtab[i].it_set[j].i_name);
+ }
+ }
+ (void)self;
+
+void
+debug_init(self)
+ pmdaInterface *self
+ CODE:
+ /* NB: debugging only (used in test.pl to verify state) */
+ pmdaInit(self, indomtab, itab_size, metrictab, mtab_size);
+
diff --git a/src/perl/PMDA/cvalue.c b/src/perl/PMDA/cvalue.c
new file mode 100644
index 0000000..fe9723c
--- /dev/null
+++ b/src/perl/PMDA/cvalue.c
@@ -0,0 +1,155 @@
+/*
+ * Verifies that the information we see from PMDA.pm (perl) matches
+ * the local PCP installation (PMAPI) - used by test.pl: `make test`
+ *
+ * Copyright (c) 2004 Silicon Graphics, Inc. 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.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <values.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+
+void
+ids(void)
+{
+ printf("PMDA_PMID: 0,0 = %d\n", PMDA_PMID(0,0));
+ printf("PMDA_PMID: 1,1 = %d\n", PMDA_PMID(1,1));
+ printf("PMDA_PMID: 27,13 = %d\n", PMDA_PMID(27,13));
+ printf("PMDA_PMID: %d,0 = %d\n", MAXINT, PMDA_PMID(MAXINT,0));
+ printf("PMDA_PMID: 0,%d = %d\n", MAXINT, PMDA_PMID(0,MAXINT));
+ printf("PMDA_PMID: %d,%d = %d\n", MAXINT,MAXINT, PMDA_PMID(MAXINT,MAXINT));
+}
+
+void
+units(void)
+{
+ pmUnits forw;
+ pmUnits back;
+
+ forw.pad = back.pad = 0;
+ forw.dimSpace = back.scaleCount = 1;
+ forw.dimTime = back.scaleTime = 2;
+ forw.dimCount = back.scaleSpace = 3;
+ forw.scaleSpace = back.dimCount = 4;
+ forw.scaleTime = back.dimTime = 5;
+ forw.scaleCount = back.dimSpace = 6;
+ printf("pmUnits: 1,2,3,4,5,6 = %d\n", *(unsigned int *)&forw);
+ printf("pmUnits: 6,5,4,3,2,1 = %d\n", *(unsigned int *)&back);
+}
+
+void
+defines(void)
+{
+ printf("PM_ID_NULL=%u\n", PM_ID_NULL);
+ printf("PM_INDOM_NULL=%u\n", PM_INDOM_NULL);
+ printf("PM_IN_NULL=%u\n", PM_IN_NULL);
+
+ printf("PM_SPACE_BYTE=%u\n", PM_SPACE_BYTE);
+ printf("PM_SPACE_KBYTE=%u\n", PM_SPACE_KBYTE);
+ printf("PM_SPACE_MBYTE=%u\n", PM_SPACE_MBYTE);
+ printf("PM_SPACE_GBYTE=%u\n", PM_SPACE_GBYTE);
+ printf("PM_SPACE_TBYTE=%u\n", PM_SPACE_TBYTE);
+
+ printf("PM_TIME_NSEC=%u\n", PM_TIME_NSEC);
+ printf("PM_TIME_USEC=%u\n", PM_TIME_USEC);
+ printf("PM_TIME_MSEC=%u\n", PM_TIME_MSEC);
+ printf("PM_TIME_SEC=%u\n", PM_TIME_SEC);
+ printf("PM_TIME_MIN=%u\n", PM_TIME_MIN);
+ printf("PM_TIME_HOUR=%u\n", PM_TIME_HOUR);
+
+ printf("PM_TYPE_NOSUPPORT=%u\n", PM_TYPE_NOSUPPORT);
+ printf("PM_TYPE_32=%u\n", PM_TYPE_32);
+ printf("PM_TYPE_U32=%u\n", PM_TYPE_U32);
+ printf("PM_TYPE_64=%u\n", PM_TYPE_64);
+ printf("PM_TYPE_U64=%u\n", PM_TYPE_U64);
+ printf("PM_TYPE_FLOAT=%u\n", PM_TYPE_FLOAT);
+ printf("PM_TYPE_DOUBLE=%u\n", PM_TYPE_DOUBLE);
+ printf("PM_TYPE_STRING=%u\n", PM_TYPE_STRING);
+
+ printf("PM_SEM_COUNTER=%u\n", PM_SEM_COUNTER);
+ printf("PM_SEM_INSTANT=%u\n", PM_SEM_INSTANT);
+ printf("PM_SEM_DISCRETE=%u\n", PM_SEM_DISCRETE);
+
+ printf("PM_ERR_GENERIC=%d\n", PM_ERR_GENERIC);
+ printf("PM_ERR_PMNS=%d\n", PM_ERR_PMNS);
+ printf("PM_ERR_NOPMNS=%d\n", PM_ERR_NOPMNS);
+ printf("PM_ERR_DUPPMNS=%d\n", PM_ERR_DUPPMNS);
+ printf("PM_ERR_TEXT=%d\n", PM_ERR_TEXT);
+ printf("PM_ERR_APPVERSION=%d\n", PM_ERR_APPVERSION);
+ printf("PM_ERR_VALUE=%d\n", PM_ERR_VALUE);
+ printf("PM_ERR_TIMEOUT=%d\n", PM_ERR_TIMEOUT);
+ printf("PM_ERR_NODATA=%d\n", PM_ERR_NODATA);
+ printf("PM_ERR_RESET=%d\n", PM_ERR_RESET);
+ printf("PM_ERR_NAME=%d\n", PM_ERR_NAME);
+ printf("PM_ERR_PMID=%d\n", PM_ERR_PMID);
+ printf("PM_ERR_INDOM=%d\n", PM_ERR_INDOM);
+ printf("PM_ERR_INST=%d\n", PM_ERR_INST);
+ printf("PM_ERR_UNIT=%d\n", PM_ERR_UNIT);
+ printf("PM_ERR_CONV=%d\n", PM_ERR_CONV);
+ printf("PM_ERR_TRUNC=%d\n", PM_ERR_TRUNC);
+ printf("PM_ERR_SIGN=%d\n", PM_ERR_SIGN);
+ printf("PM_ERR_PROFILE=%d\n", PM_ERR_PROFILE);
+ printf("PM_ERR_IPC=%d\n", PM_ERR_IPC);
+ printf("PM_ERR_EOF=%d\n", PM_ERR_EOF);
+ printf("PM_ERR_NOTHOST=%d\n", PM_ERR_NOTHOST);
+ printf("PM_ERR_EOL=%d\n", PM_ERR_EOL);
+ printf("PM_ERR_MODE=%d\n", PM_ERR_MODE);
+ printf("PM_ERR_LABEL=%d\n", PM_ERR_LABEL);
+ printf("PM_ERR_LOGREC=%d\n", PM_ERR_LOGREC);
+ printf("PM_ERR_NOTARCHIVE=%d\n", PM_ERR_NOTARCHIVE);
+ printf("PM_ERR_LOGFILE=%d\n", PM_ERR_LOGFILE);
+ printf("PM_ERR_NOCONTEXT=%d\n", PM_ERR_NOCONTEXT);
+ printf("PM_ERR_PROFILESPEC=%d\n", PM_ERR_PROFILESPEC);
+ printf("PM_ERR_PMID_LOG=%d\n", PM_ERR_PMID_LOG);
+ printf("PM_ERR_INDOM_LOG=%d\n", PM_ERR_INDOM_LOG);
+ printf("PM_ERR_INST_LOG=%d\n", PM_ERR_INST_LOG);
+ printf("PM_ERR_NOPROFILE=%d\n", PM_ERR_NOPROFILE);
+ printf("PM_ERR_NOAGENT=%d\n", PM_ERR_NOAGENT);
+ printf("PM_ERR_PERMISSION=%d\n", PM_ERR_PERMISSION);
+ printf("PM_ERR_CONNLIMIT=%d\n", PM_ERR_CONNLIMIT);
+ printf("PM_ERR_AGAIN=%d\n", PM_ERR_AGAIN);
+ printf("PM_ERR_ISCONN=%d\n", PM_ERR_ISCONN);
+ printf("PM_ERR_NOTCONN=%d\n", PM_ERR_NOTCONN);
+ printf("PM_ERR_NEEDPORT=%d\n", PM_ERR_NEEDPORT);
+ printf("PM_ERR_NONLEAF=%d\n", PM_ERR_NONLEAF);
+ printf("PM_ERR_TYPE=%d\n", PM_ERR_TYPE);
+ printf("PM_ERR_TOOSMALL=%d\n", PM_ERR_TOOSMALL);
+ printf("PM_ERR_TOOBIG=%d\n", PM_ERR_TOOBIG);
+ printf("PM_ERR_PMDAREADY=%d\n", PM_ERR_PMDAREADY);
+ printf("PM_ERR_PMDANOTREADY=%d\n", PM_ERR_PMDANOTREADY);
+ printf("PM_ERR_NYI=%d\n", PM_ERR_NYI);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *use = "Error: must provide one argument - either 'd', 'i' or 'u'\n";
+ if (argc != 2) {
+ fputs(use, stderr);
+ return 1;
+ }
+ else if (argv[1][0] == 'd')
+ defines();
+ else if (argv[1][0] == 'i')
+ ids();
+ else if (argv[1][0] == 'u')
+ units();
+ else {
+ fputs(use, stderr);
+ fprintf(stderr, "(ouch!!!! that really hurt! who throws a '%s' anyway? --Austin)\n", argv[1]);
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/perl/PMDA/local.c b/src/perl/PMDA/local.c
new file mode 100644
index 0000000..03a67fa
--- /dev/null
+++ b/src/perl/PMDA/local.c
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2008-2011 Aconex. 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.
+ */
+
+#include "local.h"
+#include <dirent.h>
+#include <search.h>
+#include <sys/stat.h>
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+static timers_t *timers;
+static int ntimers;
+static files_t *files;
+static int nfiles;
+
+char *
+local_strdup_suffix(const char *string, const char *suffix)
+{
+ size_t length = strlen(string) + strlen(suffix) + 1;
+ char *result = malloc(length);
+
+ if (!result)
+ return result;
+ sprintf(result, "%s%s", string, suffix);
+ return result;
+}
+
+char *
+local_strdup_prefix(const char *prefix, const char *string)
+{
+ size_t length = strlen(prefix) + strlen(string) + 1;
+ char *result = malloc(length);
+
+ if (!result)
+ return result;
+ sprintf(result, "%s%s", prefix, string);
+ return result;
+}
+
+int
+local_timer(double timeout, scalar_t *callback, int cookie)
+{
+ int size = sizeof(*timers) * (ntimers + 1);
+ delta_t delta;
+
+ delta.tv_sec = (time_t)timeout;
+ delta.tv_usec = (long)((timeout - (double)delta.tv_sec) * 1000000.0);
+
+ if ((timers = realloc(timers, size)) == NULL)
+ __pmNoMem("timers resize", size, PM_FATAL_ERR);
+ timers[ntimers].id = -1; /* not yet registered */
+ timers[ntimers].delta = delta;
+ timers[ntimers].cookie = cookie;
+ timers[ntimers].callback = callback;
+ return ntimers++;
+}
+
+int
+local_timer_get_cookie(int id)
+{
+ int i;
+
+ for (i = 0; i < ntimers; i++)
+ if (timers[i].id == id)
+ return timers[i].cookie;
+ return -1;
+}
+
+scalar_t *
+local_timer_get_callback(int id)
+{
+ int i;
+
+ for (i = 0; i < ntimers; i++)
+ if (timers[i].id == id)
+ return timers[i].callback;
+ return NULL;
+}
+
+static int
+local_file(int type, int fd, scalar_t *callback, int cookie)
+{
+ int size = sizeof(*files) * (nfiles + 1);
+
+ if ((files = realloc(files, size)) == NULL)
+ __pmNoMem("files resize", size, PM_FATAL_ERR);
+ files[nfiles].type = type;
+ files[nfiles].fd = fd;
+ files[nfiles].cookie = cookie;
+ files[nfiles].callback = callback;
+ return nfiles++;
+}
+
+int
+local_pipe(char *pipe, scalar_t *callback, int cookie)
+{
+ FILE *fp = popen(pipe, "r");
+ int me;
+
+#if defined(HAVE_SIGPIPE)
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ if (!fp) {
+ __pmNotifyErr(LOG_ERR, "popen failed (%s): %s", pipe, osstrerror());
+ exit(1);
+ }
+ me = local_file(FILE_PIPE, fileno(fp), callback, cookie);
+ files[me].me.pipe.file = fp;
+ return fileno(fp);
+}
+
+int
+local_tail(char *file, scalar_t *callback, int cookie)
+{
+ int fd = open(file, O_RDONLY | O_NDELAY);
+ struct stat stats;
+ int me;
+
+ if (fd < 0) {
+ __pmNotifyErr(LOG_ERR, "open failed (%s): %s", file, osstrerror());
+ exit(1);
+ }
+ if (fstat(fd, &stats) < 0) {
+ __pmNotifyErr(LOG_ERR, "fstat failed (%s): %s", file, osstrerror());
+ exit(1);
+ }
+ lseek(fd, 0L, SEEK_END);
+ me = local_file(FILE_TAIL, fd, callback, cookie);
+ files[me].me.tail.path = strdup(file);
+ files[me].me.tail.dev = stats.st_dev;
+ files[me].me.tail.ino = stats.st_ino;
+ return me;
+}
+
+int
+local_sock(char *host, int port, scalar_t *callback, int cookie)
+{
+ __pmSockAddr *myaddr;
+ __pmHostEnt *servinfo = NULL;
+ void *enumIx;
+ int sts = -1;
+ int me, fd = -1;
+
+ if ((servinfo = __pmGetAddrInfo(host)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "__pmGetAddrInfo (%s): %s", host, netstrerror());
+ goto error;
+ }
+ /* Loop over the addresses resolved for this host name until one of them
+ connects. */
+ enumIx = NULL;
+ for (myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx);
+ myaddr != NULL;
+ myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx)) {
+ if (__pmSockAddrIsInet(myaddr))
+ fd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myaddr))
+ fd = __pmCreateIPv6Socket();
+ else {
+ __pmNotifyErr(LOG_ERR, "invalid address family: %d\n",
+ __pmSockAddrGetFamily(myaddr));
+ fd = -1;
+ }
+
+ if (fd < 0) {
+ __pmSockAddrFree(myaddr);
+ continue; /* Try the next address */
+ }
+
+ __pmSockAddrSetPort(myaddr, port);
+
+ sts = __pmConnect(fd, (void *)myaddr, __pmSockAddrSize());
+ __pmSockAddrFree(myaddr);
+ if (sts == 0)
+ break; /* Successful connection */
+
+ /* Try the next address */
+ __pmCloseSocket(fd);
+ fd = -1;
+ }
+ __pmHostEntFree(servinfo);
+
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "__pmConnect (%s): %s", host, netstrerror());
+ goto error;
+ }
+
+ me = local_file(FILE_SOCK, fd, callback, cookie);
+ files[me].me.sock.host = strdup(host);
+ files[me].me.sock.port = port;
+
+ return me;
+
+ error:
+ if (fd >= 0)
+ __pmCloseSocket(fd);
+ if (servinfo)
+ __pmHostEntFree(servinfo);
+ exit(1);
+}
+
+static char *
+local_filetype(int type)
+{
+ if (type == FILE_SOCK)
+ return "socket connection";
+ if (type == FILE_PIPE)
+ return "command pipe";
+ if (type == FILE_TAIL)
+ return "tailed file";
+ return NULL;
+}
+
+int
+local_files_get_descriptor(int id)
+{
+ if (id < 0 || id >= nfiles)
+ return -1;
+ return files[id].fd;
+}
+
+void
+local_atexit(void)
+{
+ while (ntimers > 0) {
+ --ntimers;
+ __pmAFunregister(timers[ntimers].id);
+ }
+ if (timers) {
+ free(timers);
+ timers = NULL;
+ }
+ while (nfiles > 0) {
+ --nfiles;
+ if (files[nfiles].type == FILE_PIPE)
+ pclose(files[nfiles].me.pipe.file);
+ if (files[nfiles].type == FILE_TAIL) {
+ close(files[nfiles].fd);
+ if (files[nfiles].me.tail.path)
+ free(files[nfiles].me.tail.path);
+ files[nfiles].me.tail.path = NULL;
+ }
+ if (files[nfiles].type == FILE_SOCK) {
+ __pmCloseSocket(files[nfiles].fd);
+ if (files[nfiles].me.sock.host)
+ free(files[nfiles].me.sock.host);
+ files[nfiles].me.sock.host = NULL;
+ }
+ }
+ if (files) {
+ free(files);
+ files = NULL;
+ }
+ /* take out any children we created */
+#ifdef HAVE_SIGNAL
+ signal(SIGTERM, SIG_IGN);
+#endif
+ __pmProcessTerminate((pid_t)0, 0);
+}
+
+static void
+local_log_rotated(files_t *file)
+{
+ struct stat stats;
+
+ if (stat(file->me.tail.path, &stats) < 0)
+ return;
+ if (stats.st_ino == file->me.tail.ino && stats.st_dev == file->me.tail.dev)
+ return;
+
+ close(file->fd);
+ file->fd = open(file->me.tail.path, O_RDONLY | O_NDELAY);
+ if (file->fd < 0) {
+ __pmNotifyErr(LOG_ERR, "open failed after log rotate (%s): %s",
+ file->me.tail.path, osstrerror());
+ return;
+ }
+ files->me.tail.dev = stats.st_dev;
+ files->me.tail.ino = stats.st_ino;
+}
+
+static void
+local_reconnector(files_t *file)
+{
+ __pmSockAddr *myaddr = NULL;
+ __pmHostEnt *servinfo = NULL;
+ int fd = -1;
+ int sts = -1;
+ void *enumIx;
+
+ if (file->fd >= 0) /* reconnect-needed flag */
+ goto done;
+ if ((servinfo = __pmGetAddrInfo(file->me.sock.host)) == NULL)
+ goto done;
+ /* Loop over the addresses resolved for this host name until one of them
+ connects. */
+ enumIx = NULL;
+ for (myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx);
+ myaddr != NULL;
+ myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx)) {
+ if (__pmSockAddrIsInet(myaddr))
+ fd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myaddr))
+ fd = __pmCreateIPv6Socket();
+ else {
+ __pmNotifyErr(LOG_ERR, "invalid address family: %d\n",
+ __pmSockAddrGetFamily(myaddr));
+ fd = -1;
+ }
+
+ if (fd < 0) {
+ __pmSockAddrFree(myaddr);
+ continue; /* Try the next address */
+ }
+
+ __pmSockAddrSetPort(myaddr, files->me.sock.port);
+ sts = __pmConnect(fd, (void *)myaddr, __pmSockAddrSize());
+ __pmSockAddrFree(myaddr);
+ if (sts == 0) /* good connection */
+ break;
+
+ /* Try the next address */
+ __pmCloseSocket(fd);
+ fd = -1;
+ }
+
+ if (fd >= 0)
+ files->fd = fd;
+
+done:
+ if (myaddr)
+ __pmSockAddrFree(myaddr);
+ if (servinfo)
+ __pmHostEntFree(servinfo);
+}
+
+static void
+local_connection(files_t *file)
+{
+ if (file->type == FILE_TAIL)
+ local_log_rotated(file);
+ else if (file->type == FILE_TAIL)
+ local_reconnector(file);
+}
+
+void
+local_pmdaMain(pmdaInterface *self)
+{
+ static char buffer[4096];
+ int pmcdfd, nready, nfds, i, j, count, fd, maxfd = -1;
+ __pmFdSet fds, readyfds;
+ ssize_t bytes;
+ size_t offset;
+ char *s, *p;
+
+ if ((pmcdfd = __pmdaInFd(self)) < 0)
+ exit(1);
+
+ for (i = 0; i < ntimers; i++)
+ timers[i].id = __pmAFregister(&timers[i].delta, &timers[i].cookie,
+ timer_callback);
+
+ /* custom PMDA main loop */
+ for (count = 0; ; count++) {
+ struct timeval timeout = { 1, 0 };
+
+ __pmFD_ZERO(&fds);
+ __pmFD_SET(pmcdfd, &fds);
+ for (i = 0; i < nfiles; i++) {
+ if (files[i].type == FILE_TAIL)
+ continue;
+ fd = files[i].fd;
+ __pmFD_SET(fd, &fds);
+ if (fd > maxfd)
+ maxfd = fd;
+ }
+ nfds = ((pmcdfd > maxfd) ? pmcdfd : maxfd) + 1;
+
+ __pmFD_COPY(&readyfds, &fds);
+ nready = __pmSelectRead(nfds, &readyfds, &timeout);
+ if (nready < 0) {
+ if (neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "select failed: %s\n",
+ netstrerror());
+ exit(1);
+ }
+ continue;
+ }
+
+ __pmAFblock();
+
+ if (__pmFD_ISSET(pmcdfd, &readyfds)) {
+ if (__pmdaMainPDU(self) < 0) {
+ __pmAFunblock();
+ exit(1);
+ }
+ }
+
+ for (i = 0; i < nfiles; i++) {
+ fd = files[i].fd;
+ /* check for log rotation or host reconnection needed */
+ if ((count % 10) == 0) /* but only once every 10 */
+ local_connection(&files[i]);
+ if (files[i].type != FILE_TAIL && !(__pmFD_ISSET(fd, &readyfds)))
+ continue;
+ offset = 0;
+multiread:
+ bytes = __pmRead(fd, buffer + offset, sizeof(buffer)-1 - offset);
+ if (bytes < 0) {
+ if ((files[i].type == FILE_TAIL) &&
+ (oserror() == EINTR) ||
+ (oserror() == EAGAIN) ||
+ (oserror() == EWOULDBLOCK))
+ continue;
+ if (files[i].type == FILE_SOCK) {
+ close(files[i].fd);
+ files[i].fd = -1;
+ continue;
+ }
+ __pmNotifyErr(LOG_ERR, "Data read error on %s: %s\n",
+ local_filetype(files[i].type), osstrerror());
+ exit(1);
+ }
+ if (bytes == 0) {
+ if (files[i].type == FILE_TAIL)
+ continue;
+ __pmNotifyErr(LOG_ERR, "No data to read - %s may be closed\n",
+ local_filetype(files[i].type));
+ exit(1);
+ }
+ buffer[sizeof(buffer)-1] = '\0';
+ for (s = p = buffer, j = 0;
+ *s != '\0' && j < sizeof(buffer)-1;
+ s++, j++) {
+ if (*s != '\n')
+ continue;
+ *s = '\0';
+ /*__pmNotifyErr(LOG_INFO, "Input callback: %s\n", p);*/
+ input_callback(files[i].callback, files[i].cookie, p);
+ p = s + 1;
+ }
+ if (files[i].type == FILE_TAIL) {
+ /* did we just do a full buffer read? */
+ if (p == buffer) {
+ __pmNotifyErr(LOG_ERR, "Ignoring long line: \"%s\"\n", p);
+ } else if (j == sizeof(buffer) - 1) {
+ offset = sizeof(buffer)-1 - (p - buffer);
+ memmove(buffer, p, offset);
+ goto multiread; /* read rest of line */
+ }
+ }
+ }
+
+ __pmAFunblock();
+ }
+}
diff --git a/src/perl/PMDA/local.h b/src/perl/PMDA/local.h
new file mode 100644
index 0000000..11812cd
--- /dev/null
+++ b/src/perl/PMDA/local.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2008-2010 Aconex. 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.
+ */
+#ifndef LOCAL_H
+#define LOCAL_H
+
+#include <pmapi.h>
+#include <impl.h>
+#include <pmda.h>
+
+typedef struct sv scalar_t;
+typedef struct timeval delta_t;
+
+typedef struct {
+ int id;
+ delta_t delta;
+ int cookie;
+ scalar_t *callback;
+} timers_t;
+
+typedef enum { FILE_PIPE, FILE_SOCK, FILE_TAIL } file_type_t;
+
+typedef struct {
+ FILE *file;
+} pipe_data_t;
+
+typedef struct {
+ char *path;
+ dev_t dev;
+ ino_t ino;
+} tail_data_t;
+
+typedef struct {
+ char *host;
+ int port;
+} sock_data_t;
+
+typedef struct {
+ int fd;
+ int type;
+ int cookie;
+ scalar_t *callback;
+ union {
+ pipe_data_t pipe;
+ tail_data_t tail;
+ sock_data_t sock;
+ } me;
+} files_t;
+
+extern void timer_callback(int, void *);
+extern void input_callback(scalar_t *, int, char *);
+
+extern char *local_strdup_suffix(const char *string, const char *suffix);
+extern char *local_strdup_prefix(const char *prefix, const char *string);
+
+extern int local_user(const char *username);
+
+extern int local_timer(double timeout, scalar_t *callback, int cookie);
+extern int local_timer_get_cookie(int id);
+extern scalar_t *local_timer_get_callback(int id);
+
+extern int local_pipe(char *pipe, scalar_t *callback, int cookie);
+extern int local_tail(char *file, scalar_t *callback, int cookie);
+extern int local_sock(char *host, int port, scalar_t *callback, int cookie);
+
+extern void local_atexit(void);
+extern int local_files_get_descriptor(int id);
+extern void local_pmdaMain(pmdaInterface *self);
+
+#endif /* LOCAL_H */
diff --git a/src/perl/PMDA/test.pl b/src/perl/PMDA/test.pl
new file mode 100644
index 0000000..a40443d
--- /dev/null
+++ b/src/perl/PMDA/test.pl
@@ -0,0 +1,93 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+#
+# Copyright (c) 2004 Silicon Graphics, Inc. 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.
+#
+
+######################### We start with some black magic to print on failure.
+
+BEGIN { $| = 1; print "1..4\n"; }
+END {print "not ok 1\n" unless $loaded;}
+use PCP::PMDA;
+$loaded = 1;
+print "ok 1\n";
+
+######################### End of black magic.
+
+use vars qw( $cvalue $perlvalue $failed $cases );
+
+`make -f Makefile cvalue`;
+
+# verify constants are defined and match their C counterparts
+# - assuming here that the header file matches our PMDA.pm
+# (d==define)
+#
+$failed = 0;
+$cases = 0;
+open(TEST, './cvalue d |') || die "cannot run test program 'cvalue'";
+while (<TEST>) {
+ /^(\w+)=(.*)$/;
+ $cvalue = $2;
+ $perlvalue = &$1;
+ unless ($perlvalue == $cvalue) {
+ print "$1: $perlvalue != $cvalue\n";
+ $failed++;
+ }
+ $cases++;
+}
+close TEST;
+if ($failed != 0) { print "not ok 2 (failed $failed of $cases cases)\n"; }
+else { print "ok 2\n"; }
+
+#########################
+
+# test data initialisation via the pmda_pmid macro (i==id)
+#
+$failed = 0;
+$cases = 0;
+open(TEST, './cvalue i |') || die "cannot run test program 'cvalue'";
+while (<TEST>) {
+ /^PMDA_PMID: (\d+),(\d+) = (.*)$/;
+ $cvalue = $3;
+ $perlvalue = pmda_pmid($1, $2);
+ unless ($perlvalue == $cvalue) {
+ print "$1,$2: $perlvalue != $cvalue\n";
+ $failed++;
+ }
+ $cases++;
+}
+close TEST;
+if ($failed != 0) { print "not ok 3 (failed $failed of $cases cases)\n"; }
+else { print "ok 3\n"; }
+
+# test data initialisation via the pmda_units macro (u==units)
+#
+$failed=0;
+$cases = 0;
+open(TEST, './cvalue u |') || die "cannot run test program 'cvalue'";
+while (<TEST>) {
+ /^pmUnits: {(\d+),}5(\d) = (.*)$/;
+ $cvalue = $7;
+ $perlvalue = pmda_units($1, $2, $3, $4, $5, $6);
+ unless ($perlvalue == $cvalue) {
+ print "$1,$2,$3,$4,$5,$6: $perlvalue != $cvalue\n";
+ $failed++;
+ }
+ $cases++;
+}
+close TEST;
+if ($failed != 0) { print "not ok 4 (failed $failed of $cases cases)\n"; }
+else { print "ok 4\n"; }
+
+
+#########################
diff --git a/src/perl/PMDA/typemap b/src/perl/PMDA/typemap
new file mode 100644
index 0000000..5846f97
--- /dev/null
+++ b/src/perl/PMDA/typemap
@@ -0,0 +1,27 @@
+######################################################################
+# INPUT/OUTPUT maps
+# O_OBJECT -> links an opaque C object to a blessed Perl object.
+#
+TYPEMAP
+pmdaInterface * O_OBJECT
+pmdaInstid * T_PTROBJ
+
+######################################################################
+OUTPUT
+
+O_OBJECT
+ sv_setref_pv( $arg, CLASS, (void*)$var );
+
+
+######################################################################
+INPUT
+
+O_OBJECT
+ if (sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG))
+ $var = ($type)SvIV((SV *)SvRV($arg));
+ else {
+ warn(\"${Package}::$func_name() -- $var is not a blessed SV reference\");
+ XSRETURN_UNDEF;
+ }
+
+######################################################################