[virt-tools-list] [PATCH 1/2] Connection: Introduce class for handling statistics.
Simon Kobyda
skobyda at redhat.com
Fri Oct 5 14:54:25 UTC 2018
Class will work above connection. Right know, it is possible to
provide stats for domains only, but could be expanded.
To retrieve stats, you can use get_vm_stats(vm).
This class uses new virConnectGetAllDomainStats call, which reduces
number of calls needed to poll stats.
Stats are refreshed with every connection tick.
Signed-off-by: Simon Kobyda <skobyda at redhat.com>
---
virtManager/connection.py | 316 ++++++++++++++++++++++++++++++++++++++
1 file changed, 316 insertions(+)
diff --git a/virtManager/connection.py b/virtManager/connection.py
index 7cadd97f..bdfb04e0 100644
--- a/virtManager/connection.py
+++ b/virtManager/connection.py
@@ -144,6 +144,317 @@ class _ObjectList(vmmGObject):
return self._objects[:]
+class _StatsManager(vmmGObject):
+ """
+ Class for polling statistics
+ """
+ def __init__(self):
+ vmmGObject.__init__(self)
+ self._newStatsDict = {}
+ self._all_stats_supported = True
+
+ def _cleanup(self):
+ self._newStatsDict = {}
+
+ @staticmethod
+ def _sample_cpu_stats_helper(vm, stats):
+ state = 0
+ guestcpus = 0
+ cpuTimeAbs = 0
+ if stats:
+ state = stats.get("state.state", 0)
+ if not (state in [libvirt.VIR_DOMAIN_SHUTOFF,
+ libvirt.VIR_DOMAIN_CRASHED]):
+ guestcpus = stats.get("vcpu.current", 0)
+ cpuTimeAbs = stats.get("cpu.time", 0)
+ else:
+ info = vm.get_backend().info()
+ state = info[0]
+ if not (state in [libvirt.VIR_DOMAIN_SHUTOFF,
+ libvirt.VIR_DOMAIN_CRASHED]):
+ guestcpus = info[3]
+ cpuTimeAbs = info[4]
+
+ return state, guestcpus, cpuTimeAbs
+
+ def _sample_cpu_stats(self, now, vm, stats = None):
+ if not vm.enable_cpu_stats:
+ return 0, 0, 0, 0
+
+ prevCpuTime = 0
+ prevTimestamp = 0
+ cpuTime = 0
+ cpuTimeAbs = 0
+ pcentHostCpu = 0
+ pcentGuestCpu = 0
+
+ if len(vm.stats) > 0:
+ prevTimestamp = vm.stats[0]["timestamp"]
+ prevCpuTime = vm.stats[0]["cpuTimeAbs"]
+
+ state, guestcpus, cpuTimeAbs = self._sample_cpu_stats_helper(vm, stats)
+ cpuTime = cpuTimeAbs - prevCpuTime
+
+ if state not in [libvirt.VIR_DOMAIN_SHUTOFF,
+ libvirt.VIR_DOMAIN_CRASHED]:
+ hostcpus = vm.conn.host_active_processor_count()
+
+ pcentbase = (((cpuTime) * 100.0) /
+ ((now - prevTimestamp) * 1000.0 * 1000.0 * 1000.0))
+ pcentHostCpu = pcentbase / hostcpus
+ # Under RHEL-5.9 using a XEN HV guestcpus can be 0 during shutdown
+ # so play safe and check it.
+ pcentGuestCpu = guestcpus > 0 and pcentbase / guestcpus or 0
+
+ pcentHostCpu = max(0.0, min(100.0, pcentHostCpu))
+ pcentGuestCpu = max(0.0, min(100.0, pcentGuestCpu))
+
+ return cpuTime, cpuTimeAbs, pcentHostCpu, pcentGuestCpu
+
+ @staticmethod
+ def _sample_network_traffic_helper(vm, stats, i, dev = None):
+ rx = 0
+ tx = 0
+ if stats:
+ rx = stats.get("net." + str(i) + ".rx.bytes", 0)
+ tx = stats.get("net." + str(i) + ".tx.bytes", 0)
+ else:
+ try:
+ io = vm.get_backend().interfaceStats(dev)
+ if io:
+ rx = io[0]
+ tx = io[4]
+ except libvirt.libvirtError as err:
+ if util.is_error_nosupport(err):
+ logging.debug("Net stats not supported: %s", err)
+ vm.stats_net_supported = False
+ else:
+ logging.error("Error reading net stats for "
+ "'%s' dev '%s': %s",
+ vm.get_name(), dev, err)
+ if vm.is_active():
+ logging.debug("Adding %s to skip list", dev)
+ vm.stats_net_skip.append(dev)
+ else:
+ logging.debug("Aren't running, don't add to skiplist")
+
+ return rx, tx
+
+ def _sample_network_traffic(self, vm, stats = None):
+ rx = 0
+ tx = 0
+ if (not vm.stats_net_supported or
+ not vm.enable_net_poll or
+ not vm.is_active()):
+ vm.stats_net_skip = []
+ return rx, tx
+
+ for i, netdev in enumerate(vm.get_interface_devices_norefresh()):
+ dev = netdev.target_dev
+ if not dev:
+ continue
+
+ if dev in vm.stats_net_skip:
+ continue
+
+ devrx, devtx = self._sample_network_traffic_helper(vm, stats, i, dev)
+ rx += devrx
+ tx += devtx
+
+ return rx, tx
+
+ @staticmethod
+ def _sample_disk_io_helper(vm, stats, i, dev = None):
+ rd = 0
+ wr = 0
+ if stats:
+ rd = stats.get("block." + str(i) + ".rd.bytes", 0)
+ wr = stats.get("block." + str(i) + ".wr.bytes", 0)
+ else:
+ try:
+ io = vm.get_backend().blockStats(dev)
+ if io:
+ rd = io[1]
+ wr = io[3]
+ except libvirt.libvirtError as err:
+ if util.is_error_nosupport(err):
+ logging.debug("Disk stats not supported: %s", err)
+ vm.stats_disk_supported = False
+ else:
+ logging.error("Error reading disk stats for "
+ "'%s' dev '%s': %s",
+ vm.get_name(), dev, err)
+ if vm.is_active():
+ logging.debug("Adding %s to skip list", dev)
+ vm.stats_disk_skip.append(dev)
+ else:
+ logging.debug("Aren't running, don't add to skiplist")
+
+ return rd, wr
+
+ def _sample_disk_io(self, vm, stats = None):
+ rd = 0
+ wr = 0
+ if (not vm.stats_disk_supported or
+ not vm.enable_disk_poll or
+ not vm.is_active()):
+ vm.stats_disk_skip = []
+ return rd, wr
+
+ # Some drivers support this method for getting all usage at once
+ if not vm.summary_disk_stats_skip:
+ rd, wr = self._sample_disk_io_helper(vm, stats, 0)
+ return rd, wr
+
+ # did not work, iterate over all disks
+ for i, disk in enumerate(vm.get_disk_devices_norefresh()):
+ dev = disk.target
+ if not dev:
+ continue
+
+ if dev in vm.stats_disk_skip:
+ continue
+
+ diskrd, diskwr = self._sample_disk_io_helper(vm, stats, i, dev)
+ rd += diskrd
+ wr += diskwr
+
+ return rd, wr
+
+ @staticmethod
+ def _set_mem_stats_period(vm):
+ # QEMU requires to explicitly enable memory stats polling per VM
+ # if we want fine grained memory stats
+ if not vm.conn.check_support(
+ vm.conn.SUPPORT_CONN_MEM_STATS_PERIOD):
+ return
+
+ # Only works for virtio balloon
+ if not any([b for b in vm.get_xmlobj().devices.memballoon if
+ b.model == "virtio"]):
+ return
+
+ try:
+ secs = 5
+ vm.get_backend().setMemoryStatsPeriod(secs,
+ libvirt.VIR_DOMAIN_AFFECT_LIVE)
+ except Exception as e:
+ logging.debug("Error setting memstats period: %s", e)
+
+ @staticmethod
+ def _sample_mem_stats_helper(vm, stats):
+ if stats:
+ # @stats are available if new API call is supported
+ totalmem = stats.get("balloon.current", 0)
+ curmem = stats.get("balloon.rss", 0)
+ else:
+ # Legacy call
+ try:
+ stats = vm.get_backend().memoryStats()
+ totalmem = stats.get("actual", 0)
+ curmem = stats.get("rss", 0)
+
+ if "unused" in stats:
+ curmem = max(0, totalmem - stats.get("unused", totalmem))
+ except libvirt.libvirtError as err:
+ logging.error("Error reading mem stats: %s", err)
+
+ return totalmem, curmem
+
+ def _sample_mem_stats(self, vm, stats = None):
+ if (not vm.mem_stats_supported or
+ not vm.enable_mem_stats or
+ not vm.is_active()):
+ vm.mem_stats_period_is_set = False
+ return 0, 0
+
+ if vm.mem_stats_period_is_set is False:
+ self._set_mem_stats_period(vm)
+ vm.mem_stats_period_is_set = True
+
+ totalmem, curmem = self._sample_mem_stats_helper(vm, stats)
+
+ if "unused" in stats:
+ curmem = max(0, totalmem - stats.get("unused", totalmem))
+
+ pcentCurrMem = (curmem / float(totalmem)) * 100
+ pcentCurrMem = max(0.0, min(pcentCurrMem, 100.0))
+
+ return pcentCurrMem, curmem
+
+ def _get_all_stats(self, con):
+ stats = []
+ try:
+ stats = con.get_backend().getAllDomainStats(
+ libvirt.VIR_DOMAIN_STATS_STATE |
+ libvirt.VIR_DOMAIN_STATS_CPU_TOTAL |
+ libvirt.VIR_DOMAIN_STATS_VCPU |
+ libvirt.VIR_DOMAIN_STATS_BALLOON |
+ libvirt.VIR_DOMAIN_STATS_BLOCK |
+ libvirt.VIR_DOMAIN_STATS_INTERFACE,
+ 0)
+ except libvirt.libvirtError as err:
+ if util.is_error_nosupport(err):
+ logging.debug("Method getAllDomainStats() not supported: %s", err)
+ self._all_stats_supported = False
+ else:
+ logging.error("Error loading statistics: %s", err)
+ return stats
+
+ def refresh_vms_stats(self, con, vm_list):
+ for vm in vm_list:
+ stats = None
+ now = time.time()
+ if self._all_stats_supported:
+ stats = self._get_all_stats(con)
+ if stats:
+ # using new API
+ for domstat in stats:
+ if vm.get_backend().UUID() == domstat[0].UUID():
+ cpuTime, cpuTimeAbs, pcentHostCpu, pcentGuestCpu = \
+ self._sample_cpu_stats(now, vm, domstat[1])
+ pcentCurrMem, curmem = self._sample_mem_stats(vm, domstat[1])
+ rdBytes, wrBytes = self._sample_disk_io(vm, domstat[1])
+ rxBytes, txBytes = self._sample_network_traffic(vm, domstat[1])
+ # this if statement is true only once, so we can break out
+ # of the cycle
+ break
+ else:
+ # legacy method of gathering stats
+ cpuTime, cpuTimeAbs, pcentHostCpu, pcentGuestCpu = \
+ self._sample_cpu_stats(now, vm)
+ pcentCurrMem, curmem = self._sample_mem_stats(vm)
+ rdBytes, wrBytes = self._sample_disk_io(vm)
+ rxBytes, txBytes = self._sample_network_traffic(vm)
+
+ newStats = {
+ "timestamp": now,
+ "cpuTime": cpuTime,
+ "cpuTimeAbs": cpuTimeAbs,
+ "cpuHostPercent": pcentHostCpu,
+ "cpuGuestPercent": pcentGuestCpu,
+ "curmem": curmem,
+ "currMemPercent": pcentCurrMem,
+ "diskRdKiB": rdBytes // 1024,
+ "diskWrKiB": wrBytes // 1024,
+ "netRxKiB": rxBytes // 1024,
+ "netTxKiB": txBytes // 1024,
+ }
+
+ self._newStatsDict[vm] = newStats
+
+ def get_vm_stats(self, vm):
+ if not self._all_stats_supported:
+ return None
+
+ # this should happen only during initialization of vm when
+ # caches are primed
+ if not self._newStatsDict.get(vm):
+ self.refresh_vms_stats(vm.conn, [vm])
+
+ return self._newStatsDict.get(vm)
+
+
class vmmConnection(vmmGObject):
__gsignals__ = {
"vm-added": (vmmGObject.RUN_FIRST, None, [str]),
@@ -200,6 +511,7 @@ class vmmConnection(vmmGObject):
self._xml_flags = {}
self._objects = _ObjectList()
+ self.statsmanager = _StatsManager()
self._stats = []
self._hostinfo = None
@@ -1332,6 +1644,10 @@ class vmmConnection(vmmGObject):
initial_poll, pollvm, pollnet, pollpool, polliface, pollnodedev)
self.idle_add(self._gone_object_signals, gone_objects)
+ # if stats_update:
+ self.statsmanager.refresh_vms_stats(self,
+ [o for o in preexisting_objects if o.reports_stats()])
+
# Only tick() pre-existing objects, since new objects will be
# initialized asynchronously and tick() would be redundant
for obj in preexisting_objects:
--
2.17.1
More information about the virt-tools-list
mailing list