pcp
[Top] [All Lists]

PCP Libvirt PMDA

To: pcp developers <pcp@xxxxxxxxxxx>
Subject: PCP Libvirt PMDA
From: Marko Myllynen <myllynen@xxxxxxxxxx>
Date: Fri, 1 Jul 2016 10:49:33 +0300
Delivered-to: pcp@xxxxxxxxxxx
Organization: Red Hat
Reply-to: Marko Myllynen <myllynen@xxxxxxxxxx>
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.8.0
Hi,

There was discussion on IRC yesterday wrt PCP and Libvirt metrics. I
investigated the situation a bit and it turns out writing a PMDA for
Libvirt looks pretty straightforward.

Below is an example how it could be done (obviously not yet ready for
merging). There are few metrics of different types, VMs coming and
going are always identified by the same internal/external ID, and the
PMDA handles libvirt becoming un/available.

The next steps, if someone is interested to pursue this further, would
probably be evaluating the metrics to report (or perhaps to come up
with a scheme how to report everything available automagically) and to
see how those perf event metrics mentioned yesterday could be fetched.

I'm providing the needed files inline for easier copy-pasting.


# Install:

#! /bin/sh
#
# Copyright (c) 2016 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.
# 
# Install the libvirt PMDA
#

. $PCP_DIR/etc/pcp.env
. $PCP_SHARE_DIR/lib/pmdaproc.sh

iam=libvirt
python_opt=true
daemon_opt=false
forced_restart=true

pmdaSetup
pmdaInstall
exit 0


# Remove:

#! /bin/sh
#
# Copyright (c) 2016 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.
# 
# Remove the libirt PMDA
#

. $PCP_DIR/etc/pcp.env
. $PCP_SHARE_DIR/lib/pmdaproc.sh

iam=libvirt

pmdaSetup
pmdaRemove
exit 0


# pmdalibvirt.python

#!/usr/bin/pcp python
# pylint: disable=line-too-long,bad-continuation,too-many-public-methods
#
# Copyright (C) 2016 Marko Myllynen <myllynen@xxxxxxxxxx>
#
# 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.
#
""" PCP Libvirt Performance Metrics Domain Agent """

import libvirt
import libxml2

from ctypes import c_int

from pcp.pmapi import pmUnits
from pcp.pmda import PMDA, pmdaIndom, pmdaMetric
from cpmapi import PM_TYPE_32, PM_TYPE_U32, PM_TYPE_64, PM_TYPE_U64
from cpmapi import PM_TYPE_FLOAT, PM_TYPE_DOUBLE, PM_TYPE_STRING
from cpmapi import PM_SEM_COUNTER, PM_SEM_INSTANT, PM_SEM_DISCRETE
from cpmapi import PM_ERR_INST, PM_ERR_NOTCONN, PM_ERR_PMID

class LibvirtPMDA(PMDA):
    """ PCP Libvirt PMDA """
    def __init__(self, name, domain):
        """ Initializer """
        PMDA.__init__(self, name, domain)

        self.connect_pmcd()
        self.conn = self.connect_libvirt()

        self.doms = None

        self.vm_indom = self.indom(0)
        self.vm_cluster = 0
        self.vm_metrics = [
                            [ 'name',           PM_TYPE_STRING ],
                            [ 'uuid',           PM_TYPE_STRING ],
                            [ 'title',          PM_TYPE_STRING ],
                            [ 'description',    PM_TYPE_STRING ],
                            [ 'vcpu',           PM_TYPE_U32    ],
                          ]
        self.vm_insts = pmdaIndom(self.vm_indom, [])
        self.add_indom(self.vm_insts)

        self.add_metric(name + '.' + self.vm_metrics[0][0], 
pmdaMetric(self.pmid(self.vm_cluster, 0),
                self.vm_metrics[0][1], self.vm_indom, PM_SEM_DISCRETE,
                pmUnits(0, 0, 0, 0, 0, 0)), 'VM name', 'VM name')
        self.add_metric(name + '.' + self.vm_metrics[1][0], 
pmdaMetric(self.pmid(self.vm_cluster, 1),
                self.vm_metrics[1][1], self.vm_indom, PM_SEM_DISCRETE,
                pmUnits(0, 0, 0, 0, 0, 0)), 'VM UUID', 'VM UUID')
        self.add_metric(name + '.' + self.vm_metrics[2][0], 
pmdaMetric(self.pmid(self.vm_cluster, 2),
                self.vm_metrics[2][1], self.vm_indom, PM_SEM_DISCRETE,
                pmUnits(0, 0, 0, 0, 0, 0)), 'VM title', 'VM title')
        self.add_metric(name + '.' + self.vm_metrics[3][0], 
pmdaMetric(self.pmid(self.vm_cluster, 3),
                self.vm_metrics[3][1], self.vm_indom, PM_SEM_DISCRETE,
                pmUnits(0, 0, 0, 0, 0, 0)), 'VM desc', 'VM desc')
        self.add_metric(name + '.' + self.vm_metrics[4][0], 
pmdaMetric(self.pmid(self.vm_cluster, 4),
                self.vm_metrics[4][1], self.vm_indom, PM_SEM_INSTANT,
                pmUnits(0, 0, 0, 0, 0, 0)), 'VM vCPUs', 'VM vCPUs')

        self.set_fetch(self.libvirt_fetch)
        self.set_fetch_callback(self.libvirt_fetch_callback)

    def connect_libvirt(self):
        """ Connect to libvirt """
        conn = None
        try:
            conn = libvirt.openReadOnly(None)
        except libvirt.libvirtError as error:
            self.log("Failed to connect to the hypervisor: %s" % error)
        return conn

    def libvirt_fetch(self):
        """ Fetch """
        if not self.conn:
            self.conn = self.connect_libvirt()
            if not self.conn:
                self.replace_indom(self.vm_indom, {"0":c_int(1)})
                return
        try:
            self.doms = 
self.conn.listAllDomains(libvirt.VIR_CONNECT_LIST_DOMAINS_ACTIVE)
        except libvirt.libvirtError as error:
            self.log("Failed to list domains: %s" % error)
            self.conn = None
            return

        insts = {}
        for dom in self.doms:
            ctx = libxml2.parseDoc(dom.XMLDesc()).xpathNewContext()
            uuid = ctx.xpathEval("string(/domain/uuid)")
            insts[uuid] = c_int(1)
        self.vm_insts.set_instances(self.vm_indom, insts)
        self.replace_indom(self.vm_indom, insts)

    def libvirt_fetch_callback(self, cluster, item, inst):
        """ Fetch callback """
        if not self.conn:
            return [PM_ERR_NOTCONN, 0]
        if cluster == self.vm_cluster:
            for dom in self.doms:
                ctx = libxml2.parseDoc(dom.XMLDesc()).xpathNewContext()
                uuid = self.vm_insts.inst_name_lookup(inst)
                if uuid != ctx.xpathEval("string(/domain/uuid)"):
                    continue
                try:
                    path = "string(/domain/" + self.vm_metrics[item][0] + ")"
                    value = ctx.xpathEval(path)
                    if self.vm_metrics[item][1] == PM_TYPE_FLOAT or \
                       self.vm_metrics[item][1] == PM_TYPE_DOUBLE:
                        value = float(value)
                    elif self.vm_metrics[item][1] != PM_TYPE_STRING:
                        value = int(value)
                    return [value, 1]
                except:
                    return [PM_ERR_INST, 0]

        return [PM_ERR_PMID, 0]

if __name__ == '__main__':
    LibvirtPMDA('libvirt', 491).run()

Cheers,

-- 
Marko Myllynen

<Prev in Thread] Current Thread [Next in Thread>