Created
March 24, 2016 13:37
-
-
Save frgaudet/3dec0cad061d64c3bee3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# coding=utf-8 | |
# | |
# 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 3 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. | |
# | |
# <http://www.gnu.org/licenses/>. | |
# | |
# Based on diamond collector (https://github.com/python-diamond/Diamond) | |
# | |
# F-Gaudet 2016 | |
import time | |
import logging | |
import platform | |
import sys | |
import os | |
from prettytable import PrettyTable | |
try: | |
from xml.etree import ElementTree | |
except ImportError: | |
import cElementTree as ElementTree | |
try: | |
import libvirt | |
except ImportError: | |
libvirt = None | |
def write_pidfile_or_die(path_to_pidfile): | |
if os.path.exists(path_to_pidfile): | |
pid = int(open(path_to_pidfile).read()) | |
if is_running(pid): | |
print("Sorry, found a pidfile! Process {0} is still running.".format(pid)) | |
sys.exit(1) | |
else: | |
os.remove(path_to_pidfile) | |
open(path_to_pidfile, 'w+').write(str(os.getpid())) | |
return path_to_pidfile | |
def is_running(pid): | |
try: | |
os.kill(pid, 0) | |
except OSError: | |
return | |
else: | |
return pid | |
class LibvirtKVMCollector(object): | |
def __init__(self): | |
self.blockStats = { | |
'read_reqs': 0, | |
'read_bytes': 1, | |
'write_reqs': 2, | |
'write_bytes': 3 | |
} | |
self.vifStats = { | |
'rx_bytes': 0, | |
'rx_packets': 1, | |
'rx_errors': 2, | |
'rx_drops': 3, | |
'tx_bytes': 4, | |
'tx_packets': 5, | |
'tx_errors': 6, | |
'tx_drops': 7 | |
} | |
self.config = ({ | |
'sort_by_uuid': False, | |
'uri': 'qemu:///system', | |
'cpu_absolute': False | |
}) | |
self.logger = logging.getLogger(__name__) | |
self.handler = logging.StreamHandler() | |
self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
self.handler.setFormatter(self.formatter) | |
self.logger.addHandler(self.handler) | |
self.logger.setLevel(logging.DEBUG) | |
if libvirt is None: | |
self.logger.error('Unable to import libvirt') | |
exit (1) | |
self.pp = PrettyTable(["Instance","Item","Value"]) | |
self.pp.align["Instance"] = "l" | |
self.pp.padding_width = 1 | |
def get_devices(self, dom, type): | |
devices = [] | |
# Create a XML tree from the domain XML description. | |
tree = ElementTree.fromstring(dom.XMLDesc(0)) | |
for target in tree.findall("devices/%s/target" % type): | |
dev = target.get("dev") | |
if not dev in devices: | |
devices.append(dev) | |
return devices | |
def get_disk_devices(self, dom): | |
return self.get_devices(dom, 'disk') | |
def get_network_devices(self, dom): | |
return self.get_devices(dom, 'interface') | |
def publish(self, item, value, instance): | |
self.pp.add_row([instance, item, value]) | |
def printpp(self): | |
print self.pp | |
def report_cpu_metric(self, value0, value1): | |
return (100 * ((float(value1)-float(value0)) / 1000000000)) | |
def collect(self): | |
conn = libvirt.openReadOnly(self.config['uri']) | |
for dom in [conn.lookupByID(n) for n in conn.listDomainsID()]: | |
if self.config['sort_by_uuid'] is True: | |
name = dom.UUIDString() | |
else: | |
name = dom.name() | |
# CPU stats | |
vcpus0 = dom.getCPUStats(True, 0) | |
# Take 2 samples in order to convert CPU Time to % | |
time.sleep(1) | |
vcpus1 = dom.getCPUStats(True, 0) | |
totalcpu = 0 | |
idx = 0 | |
for vcpu in vcpus0: | |
cputime0 = vcpu['cpu_time'] | |
cputime1 = vcpus1[idx]['cpu_time'] | |
if self.config['cpu_absolute'] is True: | |
self.publish('cpu.%s.time' % idx , cputime1, name) | |
else: | |
self.publish('cpu.%s.usage' % idx , self.report_cpu_metric(cputime0,cputime1), name) | |
idx += 1 | |
totalcpu += cputime1 | |
self.publish('cpu.total.time', totalcpu, name) | |
# Disk stats | |
disks = self.get_disk_devices(dom) | |
accum = {} | |
for stat in self.blockStats.keys(): | |
accum[stat] = 0 | |
for disk in disks: | |
stats = dom.blockStats(disk) | |
for stat in self.blockStats.keys(): | |
idx = self.blockStats[stat] | |
val = stats[idx] | |
accum[stat] += val | |
self.publish('block.%s.%s' % (disk, stat), val, | |
instance=name) | |
for stat in self.blockStats.keys(): | |
self.publish('block.total.%s' % stat, accum[stat], instance=name) | |
# Network stats | |
vifs = self.get_network_devices(dom) | |
accum = {} | |
for stat in self.vifStats.keys(): | |
accum[stat] = 0 | |
for vif in vifs: | |
stats = dom.interfaceStats(vif) | |
for stat in self.vifStats.keys(): | |
idx = self.vifStats[stat] | |
val = stats[idx] | |
accum[stat] += val | |
self.publish('net.%s.%s' % (vif, stat), val, | |
instance=name) | |
for stat in self.vifStats.keys(): | |
self.publish('net.total.%s' % stat, accum[stat], | |
instance=name) | |
# Memory stats | |
mem = dom.memoryStats() | |
self.publish('memory.nominal', mem['actual'] * 1024, | |
instance=name) | |
self.publish('memory.rss', mem['rss'] * 1024, instance=name) | |
if __name__ == '__main__': | |
pid_path = "%s/kvmMonitoringLock" % os.path.dirname('/tmp/') | |
write_pidfile_or_die(str(pid_path)) | |
try: | |
a = LibvirtKVMCollector() | |
a.collect() | |
a.printpp() | |
except KeyboardInterrupt: | |
sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment