From 30d91d80793392adb4c5b3890555662caa0031da Mon Sep 17 00:00:00 2001 From: "Frank Ch. Eigler" Date: Fri, 14 Jun 2013 23:38:56 -0400 Subject: PCP<->CIM bridge prototype v3 - contents properly built/packaged into openlmi-pcp subrpm - a cron.daily job conditionally (rarely) rebuilds the MOF/REG files based upon current PCP state - /usr/bin/openlmi-pcp-generate able to be run by hand, cron.daily job minimal - more run-time PCP error tolerance --- src/pcp/README | 52 ++++++++++++++ src/pcp/lmi/pcp/__init__.py | 25 +++++++ src/pcp/lmi/pcp/metric.py | 167 +++++++++++++++++++++++++++++++++++++++++++ src/pcp/openlmi-pcp-generate | 68 ++++++++++++++++++ src/pcp/openlmi-pcp.cron | 3 + src/pcp/setup.py | 19 +++++ 6 files changed, 334 insertions(+) create mode 100644 src/pcp/README create mode 100644 src/pcp/lmi/pcp/__init__.py create mode 100644 src/pcp/lmi/pcp/metric.py create mode 100644 src/pcp/openlmi-pcp-generate create mode 100644 src/pcp/openlmi-pcp.cron create mode 100644 src/pcp/setup.py (limited to 'src') diff --git a/src/pcp/README b/src/pcp/README new file mode 100644 index 0000000..1f5e225 --- /dev/null +++ b/src/pcp/README @@ -0,0 +1,52 @@ +CIM <-> PCP (http://oss.sgi.com/projects/pcp) bridge + + +The idea of the bridge is to create a suite of CIM classes (derived from +CIM_StatisticsData), each of which represents one PCP metric. There are +hundreds of these. (Since PCP metrics can come and go, the MOF and REG +files may be regenerated by a simple shell script at any with an enclosed +shell script.) + +Install PCP and start the pmcd service to start. We assume you're +already running OpenLMI / Pegasus with a client like YAWN and are far +more familiar with this WBEM business than the author. Install the +CIM <-> PCP bridge thusly: + + vi PCP_pmns2mofreg.sh # to fix the path to pcp-metric.py + sh PCP_pmns2mofreg.sh mof > PCP_Metric_PMNS.mof + sh PCP_pmns2mofreg.sh reg > PCP_Metric_PMNS.reg + openlmi-mof-register register *.mof *.reg + +For example, whereas PCP command line tools may show these sorts of results: + + % pminfo -dTf hinv.machine + + hinv.machine + Data Type: string InDom: PM_INDOM_NULL 0xffffffff + Semantics: discrete Units: none + Help: + machine name, IP35 if SGI SNIA, else simply linux + value "linux" + + % pminfo -dTf network.interface.total.bytes + network.interface.total.bytes + Data Type: 64-bit unsigned int InDom: 60.3 0xf000003 + Semantics: counter Units: byte + Help: + network total (in+out) bytes from /proc/net/dev per network interface + inst [0 or "eth0"] value 585236365 + inst [1 or "lo"] value 85894766 + +These same metrics would show up in the CIM namespace as classes + + PCP_Metric_hinv__machine +and PCP_Metric_network__interface__total__bytes + +with instances with InstanceIDs such as + + PCP:hinv.machine + PCP:network.interface.total.bytes:lo + PCP:network.interface.total.bytes:eth0 + +These currently supply a string-formatted value of the respective +metric measurement, an accurate StatisticTime, and other metadata. diff --git a/src/pcp/lmi/pcp/__init__.py b/src/pcp/lmi/pcp/__init__.py new file mode 100644 index 0000000..81de2e5 --- /dev/null +++ b/src/pcp/lmi/pcp/__init__.py @@ -0,0 +1,25 @@ +# PCP bridge Providers +# +# Copyright (C) 2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Frank Ch. Eigler +# + +""" +CIM providers for PCP metrics. +Part of OpenLMI project. +""" diff --git a/src/pcp/lmi/pcp/metric.py b/src/pcp/lmi/pcp/metric.py new file mode 100644 index 0000000..454f54a --- /dev/null +++ b/src/pcp/lmi/pcp/metric.py @@ -0,0 +1,167 @@ +"""Python Provider for PCP_Metric_* +Instruments the CIM class family PCP_Metric_* +""" + +from pywbem.cim_provider2 import CIMProvider2 +import pywbem +from pcp import pmapi +import cpmapi as c_api +import datetime + +context = None # persistent pcp context + + +# Since we serve a whole slew of PCP_Metric_** subclasses, we can't +# use a straight classname->provider-class dictionary. +# +#def get_providers(env): +# return {'PCP_Metric_****': PCP_MetricProvider} +# +# Instead, we implement the one-hop-higher proxy-level function calls, +# namely the IM_* functions at the bottom. + + +# Undo mangling done by PCP_pmns2mofreg.sh +def MOFname_to_PCPmetric (op): + assert (op.namespace == 'root/cimv2') + assert (op.classname[0:11] == 'PCP_Metric_') + return op.classname[11:].replace('__', '.') + + +# Search the given PM_ResultSet for a given instance number (if any); +# return formatted CIM of the value (or CIM None) +def PCP_CIMValueString (context, result, desc, inst): + for i in range (0,result.contents.get_numval(0)): + value = result.contents.get_vlist(0,i) + iv = value.inst + if (inst is not None and inst != iv): + continue + atom = context.pmExtractValue (result.contents.get_valfmt(0), + value, + desc.contents.type, + desc.contents.type) + atomValue = atom.dref(desc.contents.type) # nb: atomValue could be numeric etc. + return pywbem.CIMProperty(name='ValueString', + value=str(atomValue), # stringify it here + type='string') + + return pywbem.CIMProperty(name='ValueString', + value=None, + type='string') + + +def PCP_CIMStatisticTime (result): + dt = datetime.datetime.fromtimestamp(float(str(result.contents.timestamp))) + return pywbem.CIMDateTime(dt) + + +# generic payload generator, used for +# - iterating across instance domains (keys_only=1) +# - fetching metric values +# - fetching metric metadata (for those model/filter fields set) +def get_instance (env, op, model, keys_only): + metric = MOFname_to_PCPmetric (op) + global context + + try: + if (context == None): + context = pmapi.pmContext() # localhost or equivalent + context.pmReconnectContext() # in case it was nuked recently + except pmapi.pmErr, e: + context = None + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, "Unable to connect to local PMCD:" + str(e)) + + pmids = context.pmLookupName(metric) + pmid = pmids[0] + desc = context.pmLookupDesc(pmid) + + if ('InstanceID' in model): + selected_instanceid = model['InstanceID'] + else: + selected_instanceid = None + + model.path.update({'InstanceID':None}) + + if (not keys_only): + model['PMID'] = pywbem.Uint32(pmid) + model['ElementName'] = metric + # must not fail, or else we have no metric value data worth sharing + try: + results = context.pmFetch(pmids) + except pmapi.pmErr, e: + # fatal + raise pywbem.CIMError(pywbem.CIM_ERR_FAILED, "PCP pmFetch failed:" + str(e)) + # cannot fail + model['Units'] = context.pmUnitsStr(desc.contents.units) + model['Type'] = context.pmTypeStr(desc.contents.type) + # these may fail, but not fatally + try: + model['Caption'] = context.pmLookupText(pmid) + except pmapi.pmErr: + pass + try: + model['Description'] = context.pmLookupText(pmid, c_api.PM_TEXT_HELP) + except pmapi.pmErr: + pass + + try: + instL, nameL = context.pmGetInDom(desc) + for iL, nL in zip(instL, nameL): + new_instanceid = 'PCP:'+metric+':'+nL + if (selected_instanceid is None or + new_instanceid == selected_instanceid): + model['InstanceNumber'] = pywbem.Uint32(iL) + model['InstanceName'] = nL + model['InstanceID'] = new_instanceid + if (not keys_only): + model['StatisticTime'] = PCP_CIMStatisticTime (results) + model['ValueString'] = PCP_CIMValueString (context, results, desc, iL) + yield model + except pmapi.pmErr: # pmGetInDom is expected to fail for non-instance (PM_INDOM_NULL) metrics + new_instanceid = 'PCP:'+metric + if (selected_instanceid is None or + new_instanceid == selected_instanceid): + model['InstanceNumber'] = pywbem.CIMProperty(name='InstanceNumber', + value=None,type='uint32') + model['InstanceName'] = pywbem.CIMProperty(name='InstanceName', + value=None,type='string') + model['InstanceID'] = new_instanceid + if (not keys_only): + model['StatisticTime'] = PCP_CIMStatisticTime (results) + model['ValueString'] = PCP_CIMValueString (context, results, desc, None) + yield model + +# hooks for impersonating CIMProvider2 functions + + +def MI_enumInstanceNames (env, op): + model = pywbem.CIMInstance(classname = op.classname, path=op) + for x in get_instance (env, op, model, True): + yield x.path + +def MI_enumInstances (env, op, plist): + model = pywbem.CIMInstance(classname = op.classname, path=op) + return get_instance (env, op, model, False) + +def MI_getInstance (env, op, plist): + proplist = None + if plist is not None: + proplist = [s.lower() for s in propertyList] + proplist+= [s.lower() for s in op.keybindings.keys()] + model = pywbem.CIMInstance(classname=op.classname, path=op, + property_list=proplist) + model.update(model.path.keybindings) + for x in get_instance (env, op, model, False): + return x # XXX: first one + +def MI_createInstance (env, pinst): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + +def MI_modifyInstance (env, pinst, plist): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + +def MI_deleteInstance (env, piname): + raise pywbem.CIMError(pywbem.CIM_ERR_NOT_SUPPORTED) + +# See also extra MI_* functions for associations, etc.; +# cmpi-bindings.git swig/python/cmpi_pywbem_bindings.py diff --git a/src/pcp/openlmi-pcp-generate b/src/pcp/openlmi-pcp-generate new file mode 100644 index 0000000..8c53512 --- /dev/null +++ b/src/pcp/openlmi-pcp-generate @@ -0,0 +1,68 @@ +#! /bin/sh + +# This script refreshes WBEM/CIM MOF & REG files from the current PCP PMNS, +# if necessary, and reloads the new MOF/REGs into the CIMMON. +# +# The PCP PMNS changes infrequently (when the sysadmin manuall installs or +# removes pcp PMDA (agent) modules in /var/lib/pcp/pmdas/*). +# +# This script encodes the PCP->CIM metric name-mapping convention of replacing +# dots with double-underscores, which lmi/pcp/metric.py will dutifully undo. + +_LOCALSTATEDIR=/var +_DATADIR=/usr/share +NAME=openlmi-providers +PYTHON2_SITELIB=/usr/lib/python2.7/site-packages + +PCP_PMNS=$_LOCALSTATEDIR/lib/pcp/pmns/root +PCP_HOST=${1-localhost} # or local:// for pcp 3.9+ +BASEMOFFILE=$_DATADIR/$NAME/60_LMI_PCP.mof +MOFREGDIR=$_LOCALSTATEDIR/lib/$NAME +STAMPFILE=$MOFREGDIR/stamp +MOFFILE=$MOFREGDIR/60_LMI_PCP_PMNS.mof +REGFILE=$MOFREGDIR/60_LMI_PCP_PMNS.reg +PROVIDER=$PYTHON2_SITELIB/lmi/pcp/metric.py + +if [ ! -f $PROVIDER ]; then + echo "Cannot find $PROVIDER" 1>&2 + exit 1 +fi + +set -e + +echo Refreshing PCP_Metric CIMMON classes from current PCP PMNS + +# quick liveness test +pcp -h $PCP_HOST + +if [ -s $PCP_PMNS -a $PCP_PMNS -nt $STAMPFILE ]; then + if [ -f $MOFFILE -a -f $REGFILE ]; then + echo Unregistering $BASEMOFFILE + echo Unregistering previous $MOFFILE + echo Unregistering previous $REGFILE + openlmi-mof-register unregister $BASEMOFFILE $MOFFILE $REGFILE || : + fi + + echo Generating $MOFFILE + pminfo -h $PCP_HOST | sed -e 's,\.,__,g' | + awk '{print "class PCP_Metric_" $1 " : PCP_MetricValue { } ;" }' > $MOFFILE + + echo Generating $REGFILE + pminfo -h $PCP_HOST | sed -e 's,\.,__,g' | + awk '{print "[PCP_Metric_" $1 "]" + print " provider: '$PROVIDER'" + print " location: pyCmpiProvider" + print " type: instance" + print " namespace: root/cimv2" + print " group: pcp" + print ""}' > $REGFILE + + echo Registering $BASEMOFFILE + echo Registering new $MOFFILE + echo Registering new $REGFILE + openlmi-mof-register register $BASEMOFFILE $MOFFILE $REGFILE 2>&1 | # filter out two noise diagnostics + egrep -v 'Warning: the instance already exists.|In this implementation, that means it cannot be changed.' || : + touch $STAMPFILE +else + echo Doing nothing, $PCP_PMNS older than $STAMPFILE +fi diff --git a/src/pcp/openlmi-pcp.cron b/src/pcp/openlmi-pcp.cron new file mode 100644 index 0000000..a9691f0 --- /dev/null +++ b/src/pcp/openlmi-pcp.cron @@ -0,0 +1,3 @@ +#! /bin/sh + +openlmi-pcp-generate localhost >/dev/null 2>&1 diff --git a/src/pcp/setup.py b/src/pcp/setup.py new file mode 100644 index 0000000..9ffa320 --- /dev/null +++ b/src/pcp/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup +setup( + name='lmi-pcp', + description='PCP metric providers', + author='Frank Ch. Eigler', + author_email='fche@redhat.com', + url='https://fedorahosted.org/openlmi/', + version='0.1', + namespace_packages=['lmi'], + packages=[ + 'lmi.pcp'], + install_requires=['lmi', 'pcp'], + license="LGPLv2+", + classifiers=[ + 'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)', + 'Operating System :: POSIX :: Linux', + 'Topic :: System :: Systems Administration', + ] + ) -- cgit