''' Python implementation of the "rpm" 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. # from subprocess import Popen, PIPE, STDOUT from threading import Thread from posixpath import getmtime import rpm from ctypes import c_int, POINTER, cast import cpmapi as c_api from pcp.pmda import PMDA, pmdaMetric, pmdaIndom from pcp.pmapi import pmUnits, pmContext as PCP class setup_indom(Thread): ''' Thread to create an indom whose components are the current rpm package list ''' def __init__(self, pmda, name): Thread.__init__(self) self.pmda = pmda self.name = name def run(self): self.pmda.log("setup_indom") package_list = self.pmda.rpm_handle.dbMatch() rpms = {} i = 0 replace_indom = False # Get current package list for package in package_list: self.pmda.log(package['name'] + " " + str(i)) if replace_indom == False and package not in rpms: replace_indom = True rpms[package['name']] = c_int(i) i += 1 # Rebuild if a package was added or a package was removed if replace_indom == True or len(rpms) != len(self.pmda.rpms_by_n): self.pmda.replace_indom(self.pmda.source_indom, rpms) for (k, v) in rpms.iteritems(): nv = int (v.value) self.pmda.rpms_by_n[nv] = k class RpmPMDA(PMDA): ''' A Performance Metrics Domain Agent for rpm metrics. ''' source = {} state = {} # rpm.numfetch properties numfetch = 0 oldfetch = -1 rpm_metrics = ( ['.name', 0, 1, "RPMTAG_NAME"], \ ['.source', 0, 2, "RPMTAG_SOURCERPM"], \ ['.size', 0, 3, "RPMTAG_SIZE"], \ ['.description', 0, 4, "RPMTAG_DESCRIPTION"], \ ['.arch', 0, 5, "RPMTAG_ARCH"], \ ['.version', 0, 6, "RPMTAG_VERSION"], \ ['.dirnames', 0, 7, "RPMTAG_DIRNAMES"], \ ['.group', 0, 8, "RPMTAG_GROUP"], \ ['.list', 0, 9, "RPMTAG_FILENAMES"], \ ['.obsoletes', 0, 10, "RPMTAG_OBSOLETES"], \ ['.provides', 0, 11, "RPMTAG_PROVIDES"], \ ['.requires', 0, 12, "RPMTAG_REQUIRES"], \ ['.basenames', 0, 13, "RPMTAG_BASENAMES"], \ ['.buildtime', 0, 14, "RPMTAG_BUILDTIME"], \ ['.file.class', 9, 1, "RPMTAG_FILECLASS"], \ ['.file.linktos', 9, 2, "RPMTAG_FILELINKTOS"], \ ['.file.names', 9, 3, "RPMTAG_FILENAMES"], \ ['.file.sizes', 9, 4, "RPMTAG_FILESIZES"] \ ) def rpm_instance(self, serial): ''' Called once per instance request; to check if package list has changed. ''' self.log("instance update for %d" % serial) # Wait on setup_indom thread if it is actively updating the indom if self.updating_indom == True: self.indom_thread.join() self.updating_indom = False # If the rpm database has changed then reload the package list rpmdb_mtime = getmtime("/var/lib/rpm/Packages") if (self.rpmdb_mtime != rpmdb_mtime): self.rpmdb_mtime = rpmdb_mtime self.updating_indom = True self.indom_thread.start() def rpm_fetch(self): ''' Called once per fetch PDU, before callbacks ''' self.log("rpm_fetch") self.numfetch += 1 def rpm_fetch_callback(self, cluster, item, inst): ''' Main fetch callback Get metric rpm.cluster.item for instance inst; e.g. get the filename list (.list) for the python instance ''' self.log("rpm_fetch_callback for %d/%d/%d" % (cluster, item, inst)) qtype = "" # Get the metric rpm.cluster.item for m in self.rpm_metrics: if (cluster == m[1] and item == m[2]): qtype = m[3] if len(qtype) == 0: return [c_api.PM_ERR_PMID, 0] voidp = self.inst_lookup(self.source_indom, inst) if (voidp == None): return [c_api.PM_ERR_INST, 0] valuep = cast(voidp, POINTER(c_int)) rpm_name = self.rpms_by_n[valuep.contents.value] # Get the metric for this inst package_list = self.rpm_handle.dbMatch('name', rpm_name) for package in package_list: metric_value = str(package[eval("rpm." + qtype)]) return [metric_value, 1] def __init__(self, name, domain): PMDA.__init__(self, name, domain) self.configfile = PCP.pmGetConfig('PCP_PMDAS_DIR') self.configfile += '/' + name + '/' + name + '.conf' self.source_indom = self.indom(0) self.add_indom(pmdaIndom(self.source_indom, self.source)) self.rpm_handle = rpm.TransactionSet() self.rpmdb_mtime = getmtime("/var/lib/rpm/Packages") self.rpms_by_n = {} self.indom_thread = setup_indom(self, name) self.indom_thread.start() self.updating_indom = True self.log(str(len(self.rpms_by_n))) for m in self.rpm_metrics: self.add_metric(name + m[0], pmdaMetric(self.pmid(m[1], m[2]), \ c_api.PM_TYPE_STRING, self.source_indom, \ c_api.PM_SEM_INSTANT, pmUnits(0, 0, 0, 0, 0, 0))) self.set_fetch(self.rpm_fetch) self.set_instance(self.rpm_instance) self.set_fetch_callback(self.rpm_fetch_callback) self.set_user(PCP.pmGetConfig('PCP_USER')) if __name__ == '__main__': RpmPMDA('rpm', 255).run()