summaryrefslogtreecommitdiff
path: root/src/pmdas/simple
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmdas/simple')
-rw-r--r--src/pmdas/simple/GNUmakefile46
-rw-r--r--src/pmdas/simple/GNUmakefile.install53
-rw-r--r--src/pmdas/simple/Install53
-rw-r--r--src/pmdas/simple/README67
-rw-r--r--src/pmdas/simple/Remove25
-rw-r--r--src/pmdas/simple/help82
-rw-r--r--src/pmdas/simple/pmdasimple.perl158
-rw-r--r--src/pmdas/simple/pmdasimple.python244
-rw-r--r--src/pmdas/simple/pmns27
-rw-r--r--src/pmdas/simple/root10
-rw-r--r--src/pmdas/simple/simple.c517
-rw-r--r--src/pmdas/simple/simple.conf1
12 files changed, 1283 insertions, 0 deletions
diff --git a/src/pmdas/simple/GNUmakefile b/src/pmdas/simple/GNUmakefile
new file mode 100644
index 0000000..a0c50bb
--- /dev/null
+++ b/src/pmdas/simple/GNUmakefile
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2012-2013 Red Hat.
+# Copyright (c) 2000,2003,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
+
+CFILES = simple.c
+CMDTARGET = pmdasimple$(EXECSUFFIX)
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = -I.
+DFILES = README help
+SCRIPTS = pmdasimple.perl pmdasimple.python
+LSRCFILES = Install Remove pmns root $(DFILES) $(SCRIPTS) \
+ simple.conf GNUmakefile.install
+
+IAM = simple
+DOMAIN = SIMPLE
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h *.o $(IAM).log pmda$(IAM) pmda_$(IAM).so
+
+default_pcp default: domain.h $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 GNUmakefile.install $(PMDADIR)/Makefile
+ $(INSTALL) -m 644 root pmns domain.h simple.conf $(PMDADIR)
+ $(INSTALL) -m 644 $(CFILES) $(DFILES) $(SCRIPTS) $(PMDADIR)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/simple/GNUmakefile.install b/src/pmdas/simple/GNUmakefile.install
new file mode 100644
index 0000000..2df8bb4
--- /dev/null
+++ b/src/pmdas/simple/GNUmakefile.install
@@ -0,0 +1,53 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2000,2003,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.
+#
+
+SHELL = sh
+
+ifdef PCP_CONF
+include $(PCP_CONF)
+else
+PCP_DIR = $(shell echo $$PCP_DIR)
+include $(PCP_DIR)/etc/pcp.conf
+endif
+include $(PCP_INC_DIR)/builddefs
+
+# remove -Lpath and -Ipath options from builddefs CFLAGS value
+#
+PCP_LIBS =
+TMP := $(CFLAGS:-I%=)
+ifdef PCP_DIR
+# put -Ipath and -Lpath back but use paths for run-time environment
+#
+CFLAGS = $(TMP) -I$(PCP_INC_DIR)/..
+LDFLAGS = -L$(PCP_LIB_DIR)
+else
+CFLAGS = $(TMP)
+endif
+
+IAM = simple
+CFILES = $(IAM).c
+
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+CMDTARGET = pmda$(IAM)
+TARGETS = $(LIBTARGET) $(CMDTARGET)
+
+LLDLIBS = -lpcp_pmda -lpcp $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS)
+LDIRT = *.log help.dir help.pag
+
+default: $(TARGETS)
+
+install: default
+
+include $(PCP_INC_DIR)/buildrules
diff --git a/src/pmdas/simple/Install b/src/pmdas/simple/Install
new file mode 100644
index 0000000..0d3ead3
--- /dev/null
+++ b/src/pmdas/simple/Install
@@ -0,0 +1,53 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 1997 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.
+#
+# Install the simple PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=simple
+pmda_interface=2
+forced_restart=false
+
+dso_opt=true
+perl_opt=true
+python_opt=true
+socket_opt=true
+socket_inet_def=2078
+
+# Set up the simple PMDA (domain 253) InDom cache
+#
+domain=`sed -n <domain.h -e '/define SIMPLE/{
+s/[ ]*$//
+s/.*[ ]//
+p
+}'`
+if [ -z "$domain" ]
+then
+ echo "Arrgh ... cannot extract domain number from domain.h"
+ exit 1
+fi
+if [ -d $PCP_VAR_DIR/config/pmda ]
+then
+ touch $PCP_VAR_DIR/config/pmda/$domain.1
+ chown $PCP_USER:$PCP_GROUP $PCP_VAR_DIR/config/pmda/$domain.1
+ chmod 644 $PCP_VAR_DIR/config/pmda/$domain.1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/simple/README b/src/pmdas/simple/README
new file mode 100644
index 0000000..d30dc63
--- /dev/null
+++ b/src/pmdas/simple/README
@@ -0,0 +1,67 @@
+Simple PMDA
+===========
+
+This PMDA is an example, that illustrates how a simple PMDA might be
+constructed.
+
+Although the metrics supported as simple, the framework is quite general,
+and could be extended to implement a much more complex PMDA.
+
+Note:
+ This PMDA may be remade from source and hence requires IDO (or
+ more specifically a C compiler) to be installed.
+
+ Uses of make(1) may fail (without removing or clobbering files)
+ if the C compiler cannot be found. This is most likely to
+ happen when running the PMDA ./Install script.
+
+ The only remedial action is to install the C compiler, or
+ hand-craft changes to the Makefile.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT simple
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/simple
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ You will be prompted to choose either a daemon implementation
+ or a DSO implementation of the PMDA, and in the case of the daemon
+ variant to select an IPC method -- everything else is automated
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/simple
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/simple.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/simple/Remove b/src/pmdas/simple/Remove
new file mode 100644
index 0000000..44c4d1e
--- /dev/null
+++ b/src/pmdas/simple/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 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.
+#
+# Remove the simple PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=simple
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/simple/help b/src/pmdas/simple/help
new file mode 100644
index 0000000..e31ad0e
--- /dev/null
+++ b/src/pmdas/simple/help
@@ -0,0 +1,82 @@
+#
+# Copyright (c) 2000-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.
+#
+# simple PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ SIMPLE.0 Instance domain "colour" for simple PMDA
+Universally 3 instances, "red" (0), "green" (1) and "blue" (3).
+
+@ SIMPLE.1 Dynamic instance domain "time" for simple PMDA
+An instance domain which is computed on-the-fly for exporting current time
+information. Refer to the help text for simple.now for a more complete
+explanation.
+
+@ simple.numfetch Number of pmFetch operations.
+The cumulative number of pmFetch operations directed to the "simple"
+PMDA.
+
+This counter may be modified with pmstore(1).
+
+@ simple.color Metrics which increment with each fetch
+This metric has 3 instances, designated "red", "green" and "blue".
+
+The value of the metric is monotonic increasing in the range 0 to
+255, then back to 0. The different instances have different starting
+values, namely 0 (red), 100 (green) and 200 (blue).
+
+The metric values my be altered using pmstore(1).
+
+@ simple.time.user Time agent has spent executing user code
+The time in seconds that the CPU has spent executing user code for the
+agent.
+
+@ simple.time.sys Time agent has spent executing system code
+The time in seconds that the CPU has spent executing system code for
+the agent.
+
+@ simple.now Time of day with a configurable instance domain
+The value reflects the current time of day through a dynamically
+reconfigurable instance domain. On each metric value fetch request,
+the agent checks to see whether the configuration file in
+$PCP_PMDAS_DIR/simple/simple.conf has been modified - if it has then
+the file is re-parsed and the instance domain for this metric is again
+constructed according to its contents.
+
+This configuration file contains a single line of comma-separated time
+tokens from this set:
+ "sec" (seconds after the minute),
+ "min" (minutes after the hour),
+ "hour" (hour since midnight).
+
+An example configuration file could be: sec,min,hour
+and in this case the simple.now metric would export values
+for the three instances "sec", "min" and "hour" corresponding
+respectively to the components seconds, minutes and hours of the
+current time of day.
+
+The instance domain reflects each token present in the file, and the
+values reflect the time at which the PMDA processes the fetch.
diff --git a/src/pmdas/simple/pmdasimple.perl b/src/pmdas/simple/pmdasimple.perl
new file mode 100644
index 0000000..f6d2da4
--- /dev/null
+++ b/src/pmdas/simple/pmdasimple.perl
@@ -0,0 +1,158 @@
+#
+# Copyright (c) 2012 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.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+use vars qw( $pmda $red $green $blue $user $system );
+my ( $numfetch, $oldfetch ) = ( 0, -1 );
+my ( $color_indom, $now_indom ) = ( 0, 1 );
+my ( $red, $green, $blue ) = ( 0, 100, 200 );
+
+# simple.now instance domain stuff...
+my $simple_config = pmda_config('PCP_PMDAS_DIR') . '/simple/simple.conf';
+my %timeslices;
+my $file_error = 0;
+
+sub simple_instance # called once per ``instance request'' pdu
+{
+ &simple_timenow_check;
+}
+
+sub simple_fetch # called once per ``fetch'' pdu, before callbacks
+{
+ $numfetch++;
+ &simple_timenow_check;
+}
+
+sub simple_fetch_callback # must return array of value,status
+{
+ my ($cluster, $item, $inst) = @_;
+
+ return (PM_ERR_INST, 0) unless ( $inst == PM_IN_NULL
+ || ($cluster == 0 && $item == 1)
+ || ($cluster == 2 && $item == 4) );
+ if ($cluster == 0) {
+ if ($item == 0) { return ($numfetch, 1); }
+ elsif ($item == 1) {
+ if ($inst == 0) { return ($red = ($red+1) % 255, 1); }
+ elsif ($inst == 1) { return ($green = ($green+1) % 255, 1); }
+ elsif ($inst == 2) { return ($blue = ($blue+1) % 255, 1); }
+ else { return (PM_ERR_INST, 0); }
+ } else { return (PM_ERR_PMID, 0); }
+ }
+ elsif ($cluster == 1) {
+ if ($oldfetch < $numfetch) { # get current values, if needed
+ ($user, $system, undef, undef) = times;
+ $oldfetch = $numfetch;
+ }
+ if ($item == 2) { return ($user, 1); }
+ elsif ($item == 3) { return ($system, 1); }
+ else { return (PM_ERR_PMID, 0); }
+ }
+ elsif ($cluster == 2 && $item == 4) {
+ my $value = pmda_inst_lookup($now_indom, $inst);
+ return (PM_ERR_INST, 0) unless defined($value);
+ return ($value, 1);
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+sub simple_store_callback # must return a single value (scalar context)
+{
+ my ($cluster, $item, $inst, $val) = @_;
+ my $sts = 0;
+
+ if ($cluster == 0) {
+ if ($item == 0) {
+ if ($val < 0) { $val = 0; $sts = PM_ERR_SIGN; }
+ $numfetch = $val;
+ }
+ elsif ($item == 1) {
+ if ($val < 0) { $sts = PM_ERR_SIGN; $val = 0; }
+ elsif ($val > 255) { $sts = PM_ERR_CONV; $val = 255; }
+
+ if ($inst == 0) { $red = $val; }
+ elsif ($inst == 1) { $green = $val; }
+ elsif ($inst == 2) { $blue = $val; }
+ else { $sts = PM_ERR_INST; }
+ }
+ else { $sts = PM_ERR_PMID; }
+ return $sts;
+ }
+ elsif ( ($cluster == 1 && ($item == 2 || $item == 3))
+ || ($cluster == 2 && $item == 4) ) {
+ return PM_ERR_PERMISSION;
+ }
+ return PM_ERR_PMID;
+}
+
+sub simple_timenow_check
+{
+ %timeslices = ();
+
+ if (open(CONFIG, $simple_config)) {
+ my %values;
+
+ ($values{'sec'}, $values{'min'}, $values{'hour'},
+ undef,undef,undef,undef,undef) = localtime;
+ $_ = <CONFIG>;
+ chomp; # avoid possible \n on last field
+ foreach my $spec (split(/,/)) {
+ $timeslices{$spec} = $values{$spec};
+ }
+ close CONFIG;
+ $file_error = 0;
+ }
+ else {
+ unless ($file_error == $!) {
+ $pmda->log("read failed on $simple_config: $!");
+ $file_error = $!;
+ }
+ }
+ $pmda->replace_indom( $now_indom, \%timeslices);
+}
+
+$pmda = PCP::PMDA->new('simple', 253);
+$pmda->connect_pmcd;
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'simple.numfetch', '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_32, $color_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'simple.color', '', '');
+$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_DOUBLE, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'simple.time.user', '', '');
+$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_DOUBLE, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'simple.time.sys', '', '');
+$pmda->add_metric(pmda_pmid(2,4), PM_TYPE_U32, $now_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'simple.now', '', '');
+
+$pmda->add_indom($color_indom, [0 => 'red', 1 => 'green', 2 => 'blue'], '', '');
+$now_indom = $pmda->add_indom($now_indom, {}, '', ''); # initialized on-the-fly
+$pmda->set_fetch( \&simple_fetch );
+$pmda->set_instance( \&simple_instance );
+$pmda->set_fetch_callback( \&simple_fetch_callback );
+$pmda->set_store_callback( \&simple_store_callback );
+
+$pmda->set_user('pcp');
+&simple_timenow_check;
+$pmda->run;
diff --git a/src/pmdas/simple/pmdasimple.python b/src/pmdas/simple/pmdasimple.python
new file mode 100644
index 0000000..5d0295a
--- /dev/null
+++ b/src/pmdas/simple/pmdasimple.python
@@ -0,0 +1,244 @@
+'''
+Python implementation of the "simple" Performance Metrics Domain Agent.
+'''
+#
+# Copyright (c) 2013 Red Hat.
+#
+# 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.
+#
+
+import os
+import time
+import ctypes
+from ctypes import c_int, POINTER, cast
+import cpmapi as c_api
+from pcp.pmda import PMDA, pmdaMetric, pmdaIndom, pmdaInstid
+from pcp.pmapi import pmUnits, pmContext as PCP
+
+class SimplePMDA(PMDA):
+ '''
+ A simple Performance Metrics Domain Agent with very simple metrics.
+ Install it and make basic use of it, as follows:
+
+ # $PCP_PMDAS_DIR/simple/Install
+ [select python option]
+ $ pminfo -fmdtT simple
+
+ Then experiment with the simple.conf file (see simple.now metrics).
+ '''
+
+ # simple.color instance domain
+ red = 0
+ green = 100
+ blue = 200
+ colors = [pmdaInstid(0, 'red'),
+ pmdaInstid(1, 'green'),
+ pmdaInstid(2, 'blue')]
+
+ # simple.now instance domain
+ configfile = ''
+ timeslices = {}
+ times = ()
+
+ # simple.numfetch properties
+ numfetch = 0
+ oldfetch = -1
+
+
+ def simple_instance(self, serial):
+ ''' Called once per "instance request" PDU '''
+ # self.log("instance update for %d" % serial)
+ if (serial == 1):
+ self.simple_timenow_check()
+
+
+ def simple_fetch(self):
+ ''' Called once per "fetch" PDU, before callbacks '''
+ self.numfetch += 1
+ self.simple_timenow_check()
+
+ def simple_fetch_color_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for color cluster
+ Helper for the fetch callback
+ '''
+ if (item == 0):
+ if (inst != c_api.PM_IN_NULL):
+ return [c_api.PM_ERR_INST, 0]
+ return [self.numfetch, 1]
+ elif (item == 1):
+ if (inst == 0):
+ self.red = (self.red + 1) % 255
+ return [self.red, 1]
+ elif (inst == 1):
+ self.green = (self.green + 1) % 255
+ return [self.green, 1]
+ elif (inst == 2):
+ self.blue = (self.blue + 1) % 255
+ return [self.blue, 1]
+ else:
+ return [c_api.PM_ERR_INST, 0]
+ else:
+ return [c_api.PM_ERR_PMID, 0]
+
+ def simple_fetch_times_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for times cluster
+ Helper for the fetch callback
+ '''
+ if (inst != c_api.PM_IN_NULL):
+ return [c_api.PM_ERR_INST, 0]
+ if (self.oldfetch < self.numfetch): # get current values, if needed
+ self.times = os.times()
+ self.oldfetch = self.numfetch
+ if (item == 2):
+ return [self.times[0], 1]
+ elif (item == 3):
+ return [self.times[1], 1]
+ return [c_api.PM_ERR_PMID, 0]
+
+ def simple_fetch_now_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for "now" cluster
+ Helper for the fetch callback
+ '''
+ if (item == 4):
+ voidp = self.inst_lookup(self.now_indom, inst)
+ if (voidp == None):
+ return [c_api.PM_ERR_INST, 0]
+ valuep = cast(voidp, POINTER(c_int))
+ return [valuep.contents.value, 1]
+ return [c_api.PM_ERR_PMID, 0]
+
+ def simple_fetch_callback(self, cluster, item, inst):
+ '''
+ Main fetch callback, defers to helpers for each cluster.
+ Returns a list of value,status (single pair) for requested pmid/inst
+ '''
+ # self.log("fetch callback for %d.%d[%d]" % (cluster, item, inst))
+ if (cluster == 0):
+ return self.simple_fetch_color_callback(item, inst)
+ elif (cluster == 1):
+ return self.simple_fetch_times_callback(item, inst)
+ elif (cluster == 2):
+ return self.simple_fetch_now_callback(item, inst)
+ return [c_api.PM_ERR_PMID, 0]
+
+
+ def simple_store_count_callback(self, val):
+ ''' Helper for the store callback, handles simple.numfetch '''
+ sts = 0
+ if (val < 0):
+ sts = c_api.PM_ERR_SIGN
+ val = 0
+ self.numfetch = val
+ return sts
+
+ def simple_store_color_callback(self, val, inst):
+ ''' Helper for the store callback, handles simple.color '''
+ sts = 0
+ if (val < 0):
+ sts = c_api.PM_ERR_SIGN
+ val = 0
+ elif (val > 255):
+ sts = c_api.PM_ERR_CONV
+ val = 255
+
+ if (inst == 0):
+ self.red = val
+ elif (inst == 1):
+ self.green = val
+ elif (inst == 2):
+ self.blue = val
+ else:
+ sts = c_api.PM_ERR_INST
+ return sts
+
+ def simple_store_callback(self, cluster, item, inst, val):
+ '''
+ Store callback, executed when a request to write to a metric happens
+ Defers to helpers for each storable metric. Returns a single value.
+ '''
+ if (cluster == 0):
+ if (item == 0):
+ return self.simple_store_count_callback(val)
+ elif (item == 1):
+ return self.simple_store_color_callback(val, inst)
+ else:
+ return c_api.PM_ERR_PMID
+ elif ((cluster == 1 and (item == 2 or item == 3))
+ or (cluster == 2 and item == 4)):
+ return c_api.PM_ERR_PERMISSION
+ return c_api.PM_ERR_PMID
+
+
+ def simple_timenow_check(self):
+ '''
+ Read our configuration file and update instance domain
+ '''
+ self.timeslices.clear()
+ try:
+ cfg = open(self.configfile)
+ fields = time.localtime()
+ values = {'sec': c_int(fields[5]),
+ 'min': c_int(fields[4]),
+ 'hour': c_int(fields[3])}
+ config = cfg.readline().strip()
+ for key in config.split(','):
+ if (key != '' and values[key] != None):
+ self.timeslices[key] = values[key]
+ finally:
+ cfg.close()
+ self.replace_indom(self.now_indom, self.timeslices)
+
+
+ def __init__(self, name, domain):
+ PMDA.__init__(self, name, domain)
+
+ self.configfile = PCP.pmGetConfig('PCP_PMDAS_DIR')
+ self.configfile += '/' + name + '/' + name + '.conf'
+
+ self.connect_pmcd();
+
+ self.color_indom = self.indom(0)
+ self.add_indom(pmdaIndom(self.color_indom, self.colors))
+
+ self.now_indom = self.indom(1)
+ self.add_indom(pmdaIndom(self.now_indom, self.timeslices))
+
+ self.add_metric(name + '.numfetch', pmdaMetric(self.pmid(0, 0),
+ c_api.PM_TYPE_U32, c_api.PM_INDOM_NULL, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+ self.add_metric(name + '.color', pmdaMetric(self.pmid(0, 1),
+ c_api.PM_TYPE_32, self.color_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+ self.add_metric(name + '.time.user', pmdaMetric(self.pmid(1, 2),
+ c_api.PM_TYPE_DOUBLE, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 1, 0, 0, c_api.PM_TIME_SEC, 0)))
+ self.add_metric(name + '.time.sys', pmdaMetric(self.pmid(1, 3),
+ c_api.PM_TYPE_DOUBLE, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 1, 0, 0, c_api.PM_TIME_SEC, 0)))
+ self.add_metric(name + '.now', pmdaMetric(self.pmid(2, 4),
+ c_api.PM_TYPE_U32, self.now_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+
+ self.set_fetch(self.simple_fetch)
+ self.set_instance(self.simple_instance)
+ self.set_fetch_callback(self.simple_fetch_callback)
+ self.set_store_callback(self.simple_store_callback)
+ self.set_user(PCP.pmGetConfig('PCP_USER'))
+ self.simple_timenow_check()
+
+
+if __name__ == '__main__':
+
+ SimplePMDA('simple', 253).run()
+
diff --git a/src/pmdas/simple/pmns b/src/pmdas/simple/pmns
new file mode 100644
index 0000000..ab987f4
--- /dev/null
+++ b/src/pmdas/simple/pmns
@@ -0,0 +1,27 @@
+/*
+ * Metrics for simple PMDA
+ *
+ * Copyright (c) 2000-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.
+ */
+
+simple {
+ numfetch SIMPLE:0:0
+ color SIMPLE:0:1
+ time
+ now SIMPLE:2:4
+}
+
+simple.time {
+ user SIMPLE:1:2
+ sys SIMPLE:1:3
+}
diff --git a/src/pmdas/simple/root b/src/pmdas/simple/root
new file mode 100644
index 0000000..d139152
--- /dev/null
+++ b/src/pmdas/simple/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { simple }
+
+#include "pmns"
+
diff --git a/src/pmdas/simple/simple.c b/src/pmdas/simple/simple.c
new file mode 100644
index 0000000..d0eca16
--- /dev/null
+++ b/src/pmdas/simple/simple.c
@@ -0,0 +1,517 @@
+/*
+ * Simple, configurable PMDA
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995,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 <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "domain.h"
+#include <sys/stat.h>
+
+/*
+ * Simple PMDA
+ *
+ * This PMDA is a sample that illustrates how a simple PMDA might be
+ * constructed using libpcp_pmda.
+ *
+ * Although the metrics supported are simple, the framework is quite general,
+ * and could be extended to implement a much more complex PMDA.
+ *
+ * Metrics
+ * simple.numfetch - number of fetches from this PMDA,
+ * may be re-set using pmStore
+ * simple.colors - 3 instances ("red", "green" and "blue")
+ * of a "saw-tooth" sequence
+ * simple.time.user - time in seconds spent executing user code
+ * simple.time.sys - time in seconds spent executing system code
+ * simple.now - current time of day across a dynamically
+ * re-configurable instance domain.
+ */
+
+/*
+ * list of instances
+ */
+
+static pmdaInstid color[] = {
+ { 0, "red" }, { 1, "green" }, { 2, "blue" }
+};
+
+/*
+ * instance domains
+ * COLOR_INDOM uses the classical indomtab[] method
+ * NOW_INDOM uses the more recent pmdaCache methods, but also appears in
+ * indomtab[] so that the initialization of the pmInDom and the pmDescs
+ * in metrictab[] is completed by pmdaInit
+ */
+
+static pmdaIndom indomtab[] = {
+#define COLOR_INDOM 0 /* serial number for "color" instance domain */
+ { COLOR_INDOM, sizeof(color)/sizeof(color[0]), color },
+#define NOW_INDOM 1 /* serial number for "now" instance domain */
+ { NOW_INDOM, 0, NULL },
+};
+
+/* this is merely a convenience */
+static pmInDom *now_indom = &indomtab[NOW_INDOM].it_indom;
+
+/*
+ * All metrics supported in this PMDA - one table entry for each.
+ * The 4th field specifies the serial number of the instance domain
+ * for the metric, and must be either PM_INDOM_NULL (denoting a
+ * metric that only ever has a single value), or the serial number
+ * of one of the instance domains declared in the instance domain table
+ * (i.e. in indomtab, above).
+ */
+
+static pmdaMetric metrictab[] = {
+/* numfetch */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* color */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_32, COLOR_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* time.user */
+ { NULL,
+ { PMDA_PMID(1,2), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) }, },
+/* time.sys */
+ { NULL,
+ { PMDA_PMID(1,3), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) }, },
+/* now */
+ { NULL,
+ { PMDA_PMID(2,4), PM_TYPE_U32, NOW_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+
+static int numfetch = 0; /* number of pmFetch operations */
+static int red = 0; /* current red value */
+static int green = 100; /* current green value */
+static int blue = 200; /* current blue value */
+static int isDSO = 1; /* =0 I am a daemon */
+static char *username;
+
+/* data and function prototypes for dynamic instance domain handling */
+static struct timeslice {
+ int tm_field;
+ int inst_id;
+ char *tm_name;
+} timeslices[] = {
+ { 0, 1, "sec" }, { 0, 60, "min" }, { 0, 3600, "hour" }
+};
+static int num_timeslices = sizeof(timeslices)/sizeof(timeslices[0]);
+
+#define SIMPLE_BUFSIZE 256
+static struct stat file_change; /* has time of last configuration change */
+static void simple_timenow_clear(void);
+static void simple_timenow_init(void);
+static void simple_timenow_refresh(void);
+static void simple_timenow_check(void);
+
+static char mypath[MAXPATHLEN];
+
+/* command line option handling - both short and long options */
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_TEXT("\nExactly one of the following options may appear:"),
+ PMDAOPT_INET,
+ PMDAOPT_PIPE,
+ PMDAOPT_UNIX,
+ PMDAOPT_IPV6,
+ PMDA_OPTIONS_END
+};
+static pmdaOptions opts = {
+ .short_options = "D:d:i:l:pu:U:6:?",
+ .long_options = longopts,
+};
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+simple_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ int sts;
+ static int oldfetch;
+ static double usr, sys;
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (inst != PM_IN_NULL &&
+ !(idp->cluster == 0 && idp->item == 1) &&
+ !(idp->cluster == 2 && idp->item == 4))
+ return PM_ERR_INST;
+
+ if (idp->cluster == 0) {
+ if (idp->item == 0) { /* simple.numfetch */
+ atom->l = numfetch;
+ }
+ else if (idp->item == 1) { /* simple.color */
+ switch (inst) {
+ case 0: /* red */
+ red = (red + 1) % 256;
+ atom->l = red;
+ break;
+ case 1: /* green */
+ green = (green + 1) % 256;
+ atom->l = green;
+ break;
+ case 2: /* blue */
+ blue = (blue + 1) % 256;
+ atom->l = blue;
+ break;
+ default:
+ return PM_ERR_INST;
+ }
+ }
+ else
+ return PM_ERR_PMID;
+ }
+ else if (idp->cluster == 1) { /* simple.time */
+ if (oldfetch < numfetch) {
+ __pmProcessRunTimes(&usr, &sys);
+ oldfetch = numfetch;
+ }
+ if (idp->item == 2) /* simple.time.user */
+ atom->d = usr;
+ else if (idp->item == 3) /* simple.time.sys */
+ atom->d = sys;
+ else
+ return PM_ERR_PMID;
+ }
+ else if (idp->cluster == 2) {
+ if (idp->item == 4) { /* simple.now */
+ struct timeslice *tsp;
+ if ((sts = pmdaCacheLookup(*now_indom, inst, NULL, (void *)&tsp)) != PMDA_CACHE_ACTIVE) {
+ if (sts < 0)
+ __pmNotifyErr(LOG_ERR, "pmdaCacheLookup failed: inst=%d: %s", inst, pmErrStr(sts));
+ return PM_ERR_INST;
+ }
+ atom->l = tsp->tm_field;
+ }
+ else
+ return PM_ERR_PMID;
+ }
+ else
+ return PM_ERR_PMID;
+
+ return 0;
+}
+
+/*
+ * wrapper for pmdaFetch which increments the fetch count and checks for
+ * a change to the NOW instance domain.
+ *
+ * This routine is called once for each pmFetch(3) operation, so is a
+ * good place to do once-per-fetch functions, such as value caching or
+ * instance domain evaluation (as we do in simple_timenow_check).
+ */
+static int
+simple_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ numfetch++;
+ simple_timenow_check();
+ simple_timenow_refresh();
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * wrapper for pmdaInstance which we need to ensure is called with the
+ * _current_ contents of the NOW instance domain.
+ */
+static int
+simple_instance(pmInDom indom, int foo, char *bar, __pmInResult **iresp, pmdaExt *pmda)
+{
+ simple_timenow_check();
+ return pmdaInstance(indom, foo, bar, iresp, pmda);
+}
+
+/*
+ * Re-evaluate the NOW instance domain.
+ *
+ * Refer to the help text for simple.now for an explanation of how
+ * this indom can be modified, or just read the code ...
+ */
+static void
+simple_timenow_check(void)
+{
+ struct stat statbuf;
+ static int last_error = 0;
+ int sep = __pmPathSeparator();
+
+ /* stat the file & check modification time has changed */
+ snprintf(mypath, sizeof(mypath), "%s%c" "simple" "%c" "simple.conf",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ if (stat(mypath, &statbuf) == -1) {
+ if (oserror() != last_error) {
+ last_error = oserror();
+ __pmNotifyErr(LOG_ERR, "stat failed on %s: %s\n",
+ mypath, pmErrStr(-last_error));
+ }
+ simple_timenow_clear();
+ }
+ else {
+ last_error = 0;
+#if defined(HAVE_ST_MTIME_WITH_E)
+ if (statbuf.st_mtime != file_change.st_mtime) {
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ if (statbuf.st_mtimespec.tv_sec != file_change.st_mtimespec.tv_sec ||
+ statbuf.st_mtimespec.tv_nsec != file_change.st_mtimespec.tv_nsec) {
+#else
+ if (statbuf.st_mtim.tv_sec != file_change.st_mtim.tv_sec ||
+ statbuf.st_mtim.tv_nsec != file_change.st_mtim.tv_nsec) {
+#endif
+ simple_timenow_clear();
+ simple_timenow_init();
+ file_change = statbuf;
+ }
+ }
+}
+
+/*
+ * get values for time.now metric instances
+ */
+static void
+simple_timenow_refresh(void)
+{
+ time_t t = time(NULL);
+ struct tm *tptr;
+
+ tptr = localtime(&t);
+ timeslices[0].tm_field = tptr->tm_sec;
+ timeslices[1].tm_field = tptr->tm_min;
+ timeslices[2].tm_field = tptr->tm_hour;
+}
+
+/*
+ * clear the time.now metric instance domain
+ */
+static void
+simple_timenow_clear(void)
+{
+ int sts;
+
+ sts = pmdaCacheOp(*now_indom, PMDA_CACHE_INACTIVE);
+ if (sts < 0)
+ __pmNotifyErr(LOG_ERR, "pmdaCacheOp(INACTIVE) failed: indom=%s: %s",
+ pmInDomStr(*now_indom), pmErrStr(sts));
+#ifdef DESPERATE
+ __pmdaCacheDump(stderr, *now_indom, 1);
+#endif
+}
+
+/*
+ * parse the configuration file for the time.now metric instance domain
+ */
+static void
+simple_timenow_init(void)
+{
+ int i;
+ int sts;
+ int sep = __pmPathSeparator();
+ FILE *fp;
+ char *p, *q;
+ char buf[SIMPLE_BUFSIZE];
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "simple" "%c" "simple.conf",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ if ((fp = fopen(mypath, "r")) == NULL) {
+ __pmNotifyErr(LOG_ERR, "fopen on %s failed: %s\n",
+ mypath, pmErrStr(-oserror()));
+ return;
+ }
+ if ((p = fgets(&buf[0], SIMPLE_BUFSIZE, fp)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "fgets on %s found no data\n", mypath);
+ fclose(fp);
+ return;
+ }
+ if ((q = strchr(p, '\n')) != NULL)
+ *q = '\0'; /* remove eol character */
+
+ q = strtok(p, ","); /* and refresh using the updated file */
+ while (q != NULL) {
+ for (i = 0; i < num_timeslices; i++) {
+ if (strcmp(timeslices[i].tm_name, q) == 0) {
+ sts = pmdaCacheStore(*now_indom, PMDA_CACHE_ADD, q, &timeslices[i]);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "pmdaCacheStore failed: %s", pmErrStr(sts));
+ fclose(fp);
+ return;
+ }
+ break;
+ }
+ }
+ if (i == num_timeslices)
+ __pmNotifyErr(LOG_WARNING, "ignoring \"%s\" in %s", q, mypath);
+ q = strtok(NULL, ",");
+ }
+#ifdef DESPERATE
+ __pmdaCacheDump(stderr, *now_indom, 1);
+#endif
+ if (pmdaCacheOp(*now_indom, PMDA_CACHE_SIZE_ACTIVE) < 1)
+ __pmNotifyErr(LOG_WARNING, "\"timenow\" instance domain is empty");
+
+ fclose(fp);
+}
+
+/*
+ * support the storage of a value into the number of fetches count
+ */
+static int
+simple_store(pmResult *result, pmdaExt *pmda)
+{
+ int i;
+ int j;
+ int val;
+ int sts = 0;
+ pmValueSet *vsp = NULL;
+ __pmID_int *pmidp = NULL;
+
+ /* a store request may affect multiple metrics at once */
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+
+ if (pmidp->cluster == 0) { /* all storable metrics are cluster 0 */
+
+ switch (pmidp->item) {
+ case 0: /* simple.numfetch */
+ val = vsp->vlist[0].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ val = 0;
+ }
+ numfetch = val;
+ break;
+
+ case 1: /* simple.color */
+ /* a store request may affect multiple instances at once */
+ for (j = 0; j < vsp->numval && sts == 0; j++) {
+
+ val = vsp->vlist[j].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ val = 0;
+ }
+ if (val > 255) {
+ sts = PM_ERR_CONV;
+ val = 255;
+ }
+
+ switch (vsp->vlist[j].inst) {
+ case 0: /* red */
+ red = val;
+ break;
+ case 1: /* green */
+ green = val;
+ break;
+ case 2: /* blue */
+ blue = val;
+ break;
+ default:
+ sts = PM_ERR_INST;
+ }
+ }
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else if ((pmidp->cluster == 1 &&
+ (pmidp->item == 2 || pmidp->item == 3)) ||
+ (pmidp->cluster == 2 && pmidp->item == 4)) {
+ sts = PM_ERR_PERMISSION;
+ break;
+ }
+ else {
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ return sts;
+}
+
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+simple_init(pmdaInterface *dp)
+{
+ if (isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "simple" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_2, "simple DSO", mypath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.any.fetch = simple_fetch;
+ dp->version.any.store = simple_store;
+ dp->version.any.instance = simple_instance;
+
+ pmdaSetFetchCallBack(dp, simple_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab,
+ sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "simple" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_2, pmProgname, SIMPLE,
+ "simple.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+ pmdaConnect(&dispatch);
+ simple_init(&dispatch);
+ simple_timenow_check();
+ pmdaMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/simple/simple.conf b/src/pmdas/simple/simple.conf
new file mode 100644
index 0000000..a1927dc
--- /dev/null
+++ b/src/pmdas/simple/simple.conf
@@ -0,0 +1 @@
+sec,min,hour