diff options
-rw-r--r-- | README | 7 | ||||
-rw-r--r-- | mof/60_LMI_PCP.mof | 44 | ||||
-rw-r--r-- | openlmi-providers.spec | 82 | ||||
-rw-r--r-- | src/pcp/README | 52 | ||||
-rw-r--r-- | src/pcp/lmi/pcp/__init__.py | 25 | ||||
-rw-r--r-- | src/pcp/lmi/pcp/metric.py | 167 | ||||
-rw-r--r-- | src/pcp/openlmi-pcp-generate | 68 | ||||
-rw-r--r-- | src/pcp/openlmi-pcp.cron | 3 | ||||
-rw-r--r-- | src/pcp/setup.py | 19 |
9 files changed, 465 insertions, 2 deletions
@@ -64,6 +64,11 @@ Following providers are part of this sub-project: This is a CIM interface for the RealmD daemon which allows for the Kerberos and Active Directory realms enrollment +* PCP + This is a CIM interface for the PCP (Performance Co-Pilot) daemon. Allows + reading of PCP metrics on the local host. + + ******************************************************************************* * Build Dependencies * ******************************************************************************* @@ -90,6 +95,8 @@ Provider specific dependencies: * RealmD - glib2-devel - dbus-devel +* PCP + - python-pcp ******************************************************************************* * Compilation and installation * diff --git a/mof/60_LMI_PCP.mof b/mof/60_LMI_PCP.mof new file mode 100644 index 0000000..8ba122b --- /dev/null +++ b/mof/60_LMI_PCP.mof @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * Stub MOF for the CIM<->PCP bridge. Further MOF/REG clauses that + * describe the PCP PMNS details may be generated periodically using + * pcp2cim.sh. + * + * 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 <fche@redhat.com> + */ +class PCP_MetricValue : CIM_StatisticalData +{ + [Description("PCP metric PMID")] + uint32 PMID; + + [Description("PMAPI indom instance number")] + uint32 InstanceNumber; + + [Description("PMAPI indom instance name")] + string InstanceName; + + [Description("The metric type, as returned by pmTypeStr(3)")] + string Type; + + [Description("The metric value, as rendered into string form by pmAtomStr() or pmPrintValue(3)")] + string ValueString; + + [Description("The metric units, as returned by pmUnitsStr(3)")] + string Units; +}; + diff --git a/openlmi-providers.spec b/openlmi-providers.spec index 6e6d8b7..2d181bf 100644 --- a/openlmi-providers.spec +++ b/openlmi-providers.spec @@ -1,6 +1,6 @@ Name: openlmi-providers Version: 0.0.25 -Release: 5%{?dist} +Release: 6%{?dist} Summary: Set of basic CIM providers License: LGPLv2+ @@ -177,6 +177,21 @@ Requires: %{name}%{?_isa} = %{version}-%{release} %description -n openlmi-indicationmanager-libs-devel %{summary}. +%package -n openlmi-pcp +Summary: pywbem providers for accessing PCP metrics +Requires: %{name} = %{version}-%{release} +BuildArch: noarch +Requires: python-setuptools +Requires: cmpi-bindings-pywbem +Requires: python-pcp + +%description -n openlmi-pcp +openlmi-pcp exposes metrics from a local PMCD (Performance Co-Pilot server) +to the CIMOM. They appear as potentially hundreds of MOF classes, e.g. +class "PCP_Metric_kernel__pernode__cpu__use", with instances for each PCP +metric instance, e.g. "node0". PCP metric values and metadata are transcribed +into strings on demand. + %prep %setup -q @@ -186,7 +201,7 @@ pushd %{_target_platform} %{cmake} .. popd -make %{?_smp_mflags} -C %{_target_platform} +make -k %{?_smp_mflags} -C %{_target_platform} pushd src/python %{__python} setup.py build @@ -195,6 +210,9 @@ popd # src/python pushd src/software %{__python} setup.py build popd # src/software +pushd src/pcp +%{__python} setup.py build +popd %install make install/fast DESTDIR=$RPM_BUILD_ROOT -C %{_target_platform} @@ -217,6 +235,21 @@ install -m 755 pycmpiLMI_Software-cimprovagt $RPM_BUILD_ROOT/%{_libexecdir}/pega popd # src/software cp mof/LMI_Software.reg $RPM_BUILD_ROOT/%{_datadir}/%{name}/ +# pcp +pushd src/pcp +%{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT +popd +cp -p src/pcp/openlmi-pcp-generate $RPM_BUILD_ROOT/%{_bindir}/openlmi-pcp-generate +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily +cp -p src/pcp/openlmi-pcp.cron $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily/openlmi-pcp +sed -i -e 's,^_LOCALSTATEDIR=.*,_LOCALSTATEDIR="%{_localstatedir}",' \ + -e 's,^_DATADIR=.*,_DATADIR="%{_datadir}",' \ + -e 's,^NAME=.*,NAME="%{name}",' \ + -e 's,^PYTHON2_SITELIB=.*,PYTHON2_SITELIB="%{python2_sitelib}",' \ + $RPM_BUILD_ROOT/%{_bindir}/openlmi-pcp-generate \ + $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily/openlmi-pcp +mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/lib/%{name} + %files %doc README COPYING @@ -290,6 +323,19 @@ cp mof/LMI_Software.reg $RPM_BUILD_ROOT/%{_datadir}/%{name}/ %{_datadir}/%{name}/70_LMI_SoftwareIndicationFilters.mof %{_datadir}/%{name}/LMI_Software.reg +%files -n openlmi-pcp +%doc README COPYING +%{_datadir}/%{name}/60_LMI_PCP.mof +%dir %{python_sitelib}/lmi/pcp +%{python_sitelib}/lmi/pcp/* +%{python_sitelib}/lmi_pcp-* +%attr(755, root, root) %{_bindir}/openlmi-pcp-generate +%attr(755, root, root) %{_sysconfdir}/cron.daily/openlmi-pcp +%dir %{_localstatedir}/lib/%{name} +%ghost %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.mof +%ghost %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.reg +%ghost %{_localstatedir}/lib/%{name}/stamp + %files -n openlmi-logicalfile %doc README COPYING %{_libdir}/cmpi/libcmpiLMI_LogicalFile.so @@ -435,6 +481,7 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi + %pre -n openlmi-hardware if [ "$1" -gt 1 ]; then %{_bindir}/openlmi-mof-register unregister \ @@ -443,6 +490,15 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi +%pre -n openlmi-pcp +if [ "$1" -gt 1 ]; then + %{_bindir}/openlmi-mof-register unregister \ + %{_datadir}/%{name}/60_LMI_PCP.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.reg \ + > /dev/null 2>&1 || :; +fi + %post -n openlmi-fan # Register Schema and Provider if [ "$1" -ge 1 ]; then @@ -518,6 +574,15 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi +%post -n openlmi-pcp +if [ "$1" -gt 1 ]; then + %{_bindir}/openlmi-mof-register register \ + %{_datadir}/%{name}/60_LMI_PCP.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.reg \ + > /dev/null 2>&1 || :; +fi + %preun -n openlmi-fan # Deregister only if not upgrading if [ "$1" -eq 0 ]; then @@ -585,6 +650,7 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi + %preun -n openlmi-hardware if [ "$1" -gt 1 ]; then %{_bindir}/openlmi-mof-register unregister \ @@ -593,7 +659,19 @@ if [ "$1" -gt 1 ]; then > /dev/null 2>&1 || :; fi +%preun -n openlmi-pcp +if [ "$1" -gt 1 ]; then + %{_bindir}/openlmi-mof-register unregister \ + %{_datadir}/%{name}/60_LMI_PCP.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.mof \ + %{_localstatedir}/lib/%{name}/60_LMI_PCP_PMNS.reg \ + > /dev/null 2>&1 || :; +fi + %changelog +* Mon Jul 08 2013 Frank Ch. Eigler <fche@redhat.com> 0.0.25-6 +- Added PCP provider in optional openlmi-pcp subrpm. + * Mon Jul 15 2013 Jan Synáček <jsynacek@redhat.com> - 0.0.25-5 - Added libselinux-devel to BuildRequires. 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 +<fche@redhat.com> + +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 <fche@redhat.com> +# + +""" +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', + ] + ) |