diff --git a/virtManager/domain.py b/virtManager/domain.py index 9f8c893ee..266697bd2 100644 --- a/virtManager/domain.py +++ b/virtManager/domain.py @@ -211,14 +211,6 @@ class vmmDomain(vmmLibvirtObject): self.cloning = False - self.stats = [] - self._stats_rates = { - "diskRdRate": 10.0, - "diskWrRate": 10.0, - "netTxRate": 10.0, - "netRxRate": 10.0, - } - self._install_abort = False self._id = None self._uuid = None @@ -232,10 +224,6 @@ class vmmDomain(vmmLibvirtObject): self.managedsave_supported = False self._domain_state_supported = False - self.stats_net_skip = [] - self.stats_disk_skip = [] - self.summary_disk_stats_skip = False - self.inspection = vmmInspectionData() def _cleanup(self): @@ -258,7 +246,8 @@ class vmmDomain(vmmLibvirtObject): # Prime caches info = self._backend.info() self._refresh_status(newstatus=info[0]) - self._tick_stats() + # XXX _tick_stats + # self._tick_stats() self.has_managed_save() self.snapshots_supported() @@ -1512,95 +1501,52 @@ class vmmDomain(vmmLibvirtObject): # Don't schedule any conn update, migrate dialog handles it for us - ################# - # Stats helpers # - ################# - - def _get_cur_rate(self, what): - if len(self.stats) > 1: - ret = (float(self.stats[0][what] - - self.stats[1][what]) / - float(self.stats[0]["timestamp"] - - self.stats[1]["timestamp"])) - else: - ret = 0.0 - return max(ret, 0, 0) # avoid negative values at poweroff - - def _set_max_rate(self, record, what): - if record[what] > self._stats_rates[what]: - self._stats_rates[what] = record[what] - def _get_max_rate(self, name1, name2): - return float(max(self._stats_rates[name1], self._stats_rates[name2])) - - def _get_record_helper(self, record_name): - if len(self.stats) == 0: - return 0 - return self.stats[0][record_name] - - def _vector_helper(self, record_name, limit, ceil=100.0): - vector = [] - statslen = self.config.get_stats_history_length() + 1 - if limit is not None: - statslen = min(statslen, limit) - - for i in range(statslen): - if i < len(self.stats): - vector.append(self.stats[i][record_name] / ceil) - else: - vector.append(0) - - return vector - - def _in_out_vector_helper(self, name1, name2, limit, ceil): - if ceil is None: - ceil = self._get_max_rate(name1, name2) - - return (self._vector_helper(name1, limit, ceil=ceil), - self._vector_helper(name2, limit, ceil=ceil)) - - ################### # Stats accessors # ################### + def _get_stats(self): + return self.conn.statsmanager.get_vm_statslist(self) def stats_memory(self): - return self._get_record_helper("curmem") + return self._get_stats().get_record("curmem") def cpu_time(self): - return self._get_record_helper("cpuTime") + return self._get_stats().get_record("cpuTime") def host_cpu_time_percentage(self): - return self._get_record_helper("cpuHostPercent") + return self._get_stats().get_record("cpuHostPercent") def guest_cpu_time_percentage(self): - return self._get_record_helper("cpuGuestPercent") + return self._get_stats().get_record("cpuGuestPercent") def network_rx_rate(self): - return self._get_record_helper("netRxRate") + return self._get_stats().get_record("netRxRate") def network_tx_rate(self): - return self._get_record_helper("netTxRate") + return self._get_stats().get_record("netTxRate") def disk_read_rate(self): - return self._get_record_helper("diskRdRate") + return self._get_stats().get_record("diskRdRate") def disk_write_rate(self): - return self._get_record_helper("diskWrRate") + return self._get_stats().get_record("diskWrRate") def network_traffic_rate(self): return self.network_tx_rate() + self.network_rx_rate() def network_traffic_max_rate(self): - return self._get_max_rate("netRxRate", "netTxRate") + stats = self._get_stats() + return max(stats.netRxMaxRate, stats.netTxMaxRate) def disk_io_rate(self): return self.disk_read_rate() + self.disk_write_rate() def disk_io_max_rate(self): - return self._get_max_rate("diskRdRate", "diskWrRate") + stats = self._get_stats() + return max(stats.diskRdMaxRate, stats.diskWrMaxRate) def host_cpu_time_vector(self, limit=None): - return self._vector_helper("cpuHostPercent", limit) + return self._get_stats().get_vector("cpuHostPercent", limit) def guest_cpu_time_vector(self, limit=None): - return self._vector_helper("cpuGuestPercent", limit) + return self._get_stats().get_vector("cpuGuestPercent", limit) def stats_memory_vector(self, limit=None): - return self._vector_helper("currMemPercent", limit) + return self._get_stats().get_vector("currMemPercent", limit) def network_traffic_vectors(self, limit=None, ceil=None): - return self._in_out_vector_helper( - "netRxRate", "netTxRate", limit, ceil) + return self._get_stats().get_in_out_vector( + "netRxRate", "netTxRate", limit, ceil) def disk_io_vectors(self, limit=None, ceil=None): - return self._in_out_vector_helper( - "diskRdRate", "diskWrRate", limit, ceil) + return self._get_stats().get_in_out_vector( + "diskRdRate", "diskWrRate", limit, ceil) ################### @@ -1727,26 +1673,12 @@ class vmmDomain(vmmLibvirtObject): dosignal = self._refresh_status(newstatus=info[0], cansignal=False) if stats_update: - self._tick_stats() + self.conn.statsmanager.refresh_vm_stats(self) if dosignal: self.idle_emit("state-changed") if stats_update: self.idle_emit("resources-sampled") - def _tick_stats(self): - expected = self.config.get_stats_history_length() - current = len(self.stats) - if current > expected: - del self.stats[expected:current] - - newStats = self.conn.statsmanager.refresh_vm_stats(self) - - for r in ["diskRd", "diskWr", "netRx", "netTx"]: - newStats[r + "Rate"] = self._get_cur_rate(r + "KiB") - self._set_max_rate(newStats, r + "Rate") - - self.stats.insert(0, newStats) - ######################## # Libvirt domain class # diff --git a/virtManager/statsmanager.py b/virtManager/statsmanager.py index 441de50a9..0175d8e21 100644 --- a/virtManager/statsmanager.py +++ b/virtManager/statsmanager.py @@ -14,12 +14,115 @@ from virtinst import util from .baseclass import vmmGObject +class _VMStatsRecord(object): + """ + Tracks a set of VM stats for a single timestamp + """ + def __init__(self, timestamp, + cpuTime, cpuTimeAbs, + cpuHostPercent, cpuGuestPercent, + curmem, currMemPercent, + diskRdBytes, diskWrBytes, + netRxBytes, netTxBytes): + self.timestamp = timestamp + self.cpuTime = cpuTime + self.cpuTimeAbs = cpuTimeAbs + self.cpuHostPercent = cpuHostPercent + self.cpuGuestPercent = cpuGuestPercent + self.curmem = curmem + self.currMemPercent = currMemPercent + self.diskRdKiB = diskRdBytes // 1024 + self.diskWrKiB = diskWrBytes // 1024 + self.netRxKiB = netRxBytes // 1024 + self.netTxKiB = netTxBytes // 1024 + + # These are set in _VMStatsList.append_stats + self.diskRdRate = None + self.diskWrRate = None + self.netRxRate = None + self.netTxRate = None + + +class _VMStatsList(vmmGObject): + """ + Tracks a list of VMStatsRecords for a single VM + """ + def __init__(self): + vmmGObject.__init__(self) + self._stats = [] + + self.diskRdMaxRate = 10.0 + self.diskWrMaxRate = 10.0 + self.netRxMaxRate = 10.0 + self.netTxMaxRate = 10.0 + + self.mem_stats_period_is_set = False + self.stats_disk_skip = [] + self.stats_net_skip = [] + + def _cleanup(self): + pass + + def append_stats(self, newstats): + expected = self.config.get_stats_history_length() + current = len(self._stats) + if current > expected: + del(self._stats[expected:current]) + + def _calculate_rate(record_name): + ret = 0.0 + if self._stats: + oldstats = self._stats[0] + ratediff = (getattr(newstats, record_name) - + getattr(oldstats, record_name)) + timediff = newstats.timestamp - oldstats.timestamp + ret = float(ratediff) / float(timediff) + return max(ret, 0.0) + + newstats.diskRdRate = _calculate_rate("diskRdKiB") + newstats.diskWrRate = _calculate_rate("diskWrKiB") + newstats.netRxRate = _calculate_rate("netRxKiB") + newstats.netTxRate = _calculate_rate("netTxKiB") + + self.diskRdMaxRate = max(newstats.diskRdRate, self.diskRdMaxRate) + self.diskWrMaxRate = max(newstats.diskWrRate, self.diskWrMaxRate) + self.netRxMaxRate = max(newstats.netRxRate, self.netRxMaxRate) + self.netTxMaxRate = max(newstats.netTxRate, self.netTxMaxRate) + + self._stats.insert(0, newstats) + + def get_record(self, record_name): + if not self._stats: + return 0 + return getattr(self._stats[0], record_name) + + def get_vector(self, record_name, limit, ceil=100.0): + vector = [] + statslen = self.config.get_stats_history_length() + 1 + if limit is not None: + statslen = min(statslen, limit) + + for i in range(statslen): + if i < len(self._stats): + vector.append(getattr(self._stats[i], record_name) / ceil) + else: + vector.append(0) + return vector + + def get_in_out_vector(self, name1, name2, limit, ceil): + if ceil is None: + ceil = max(self.get_record(name1), self.get_record(name2), 10.0) + return (self.get_vector(name1, limit, ceil=ceil), + self.get_vector(name2, limit, ceil=ceil)) + + class vmmStatsManager(vmmGObject): """ Class for polling statistics """ def __init__(self): vmmGObject.__init__(self) + self._vm_stats = {} self._latest_all_stats = [] self._all_stats_supported = True self._enable_mem_stats = False @@ -31,7 +134,6 @@ class vmmStatsManager(vmmGObject): self._disk_stats_supported = True self._disk_stats_lxc_supported = True self._mem_stats_supported = True - self._mem_stats_period_is_set = False self._on_config_sample_network_traffic_changed() self._on_config_sample_disk_io_changed() @@ -80,15 +182,11 @@ class vmmStatsManager(vmmGObject): if not self._enable_cpu_stats: return 0, 0, 0, 0 - prevCpuTime = 0 - prevTimestamp = 0 cpuTime = 0 - pcentHostCpu = 0 - pcentGuestCpu = 0 - - if len(vm.stats) > 0: - prevTimestamp = vm.stats[0]["timestamp"] - prevCpuTime = vm.stats[0]["cpuTimeAbs"] + cpuHostPercent = 0 + cpuGuestPercent = 0 + prevTimestamp = self.get_vm_statslist(vm).get_record("timestamp") + prevCpuTime = self.get_vm_statslist(vm).get_record("cpuTimeAbs") if allstats: state = allstats.get("state.state", 0) @@ -109,15 +207,15 @@ class vmmStatsManager(vmmGObject): pcentbase = (((cpuTime) * 100.0) / ((now - prevTimestamp) * 1000.0 * 1000.0 * 1000.0)) - pcentHostCpu = pcentbase / hostcpus + cpuHostPercent = 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 + cpuGuestPercent = guestcpus > 0 and pcentbase / guestcpus or 0 - pcentHostCpu = max(0.0, min(100.0, pcentHostCpu)) - pcentGuestCpu = max(0.0, min(100.0, pcentGuestCpu)) + cpuHostPercent = max(0.0, min(100.0, cpuHostPercent)) + cpuGuestPercent = max(0.0, min(100.0, cpuGuestPercent)) - return cpuTime, cpuTimeAbs, pcentHostCpu, pcentGuestCpu + return cpuTime, cpuTimeAbs, cpuHostPercent, cpuGuestPercent ###################### @@ -125,6 +223,7 @@ class vmmStatsManager(vmmGObject): ###################### def _old_net_stats_helper(self, vm, dev): + statslist = self.get_vm_statslist(vm) try: io = vm.get_backend().interfaceStats(dev) if io: @@ -141,7 +240,7 @@ class vmmStatsManager(vmmGObject): vm.get_name(), dev, err) if vm.is_active(): logging.debug("Adding %s to skip list", dev) - vm.stats_net_skip.append(dev) + statslist.stats_net_skip.append(dev) else: logging.debug("Aren't running, don't add to skiplist") @@ -150,10 +249,11 @@ class vmmStatsManager(vmmGObject): def _sample_net_stats(self, vm, allstats): rx = 0 tx = 0 + statslist = self.get_vm_statslist(vm) if (not self._net_stats_supported or not self._enable_net_stats or not vm.is_active()): - vm.stats_net_skip = [] + statslist.stats_net_skip = [] return rx, tx if allstats: @@ -168,7 +268,7 @@ class vmmStatsManager(vmmGObject): dev = iface.target_dev if not dev: continue - if dev in vm.stats_net_skip: + if dev in statslist.stats_net_skip: continue devrx, devtx = self._old_net_stats_helper(vm, dev) @@ -183,6 +283,7 @@ class vmmStatsManager(vmmGObject): ####################### def _old_disk_stats_helper(self, vm, dev): + statslist = self.get_vm_statslist(vm) try: io = vm.get_backend().blockStats(dev) if io: @@ -199,7 +300,7 @@ class vmmStatsManager(vmmGObject): vm.get_name(), dev, err) if vm.is_active(): logging.debug("Adding %s to skip list", dev) - vm.stats_disk_skip.append(dev) + statslist.stats_disk_skip.append(dev) else: logging.debug("Aren't running, don't add to skiplist") @@ -208,10 +309,11 @@ class vmmStatsManager(vmmGObject): def _sample_disk_stats(self, vm, allstats): rd = 0 wr = 0 + statslist = self.get_vm_statslist(vm) if (not self._disk_stats_supported or not self._enable_disk_stats or not vm.is_active()): - vm.stats_disk_skip = [] + statslist.stats_disk_skip = [] return rd, wr if allstats: @@ -238,7 +340,7 @@ class vmmStatsManager(vmmGObject): dev = disk.target if not dev: continue - if dev in vm.stats_disk_skip: + if dev in statslist.stats_disk_skip: continue diskrd, diskwr = self._old_disk_stats_helper(vm, dev) @@ -288,15 +390,16 @@ class vmmStatsManager(vmmGObject): return totalmem, curmem def _sample_mem_stats(self, vm, allstats): + statslist = self.get_vm_statslist(vm) if (not self._mem_stats_supported or not self._enable_mem_stats or not vm.is_active()): - self._mem_stats_period_is_set = False + statslist.mem_stats_period_is_set = False return 0, 0 - if self._mem_stats_period_is_set is False: + if statslist.mem_stats_period_is_set is False: self._set_mem_stats_period(vm) - self._mem_stats_period_is_set = True + statslist.mem_stats_period_is_set = True if allstats: totalmem = allstats.get("balloon.current", 1) @@ -305,15 +408,15 @@ class vmmStatsManager(vmmGObject): else: totalmem, curmem = self._old_mem_stats_helper(vm) - pcentCurrMem = (curmem / float(totalmem)) * 100 - pcentCurrMem = max(0.0, min(pcentCurrMem, 100.0)) + currMemPercent = (curmem / float(totalmem)) * 100 + currMemPercent = max(0.0, min(currMemPercent, 100.0)) - return pcentCurrMem, curmem + return currMemPercent, curmem - ##################### - # allstats handling # - ##################### + #################### + # alltats handling # + #################### def _get_all_stats(self, conn): if not self._all_stats_supported: @@ -337,8 +440,13 @@ class vmmStatsManager(vmmGObject): logging.debug("Error call getAllDomainStats(): %s", err) return stats + + ############## + # Public API # + ############## + def refresh_vm_stats(self, vm): - now = time.time() + timestamp = time.time() domallstats = None for _domstat in self._latest_all_stats: @@ -346,27 +454,24 @@ class vmmStatsManager(vmmGObject): domallstats = _domstat[1] break - cpuTime, cpuTimeAbs, pcentHostCpu, pcentGuestCpu = \ - self._sample_cpu_stats(now, vm, domallstats) - pcentCurrMem, curmem = self._sample_mem_stats(vm, domallstats) - rdBytes, wrBytes = self._sample_disk_stats(vm, domallstats) - rxBytes, txBytes = self._sample_net_stats(vm, domallstats) + cpuTime, cpuTimeAbs, cpuHostPercent, cpuGuestPercent = \ + self._sample_cpu_stats(timestamp, vm, domallstats) + currMemPercent, curmem = self._sample_mem_stats(vm, domallstats) + diskRdBytes, diskWrBytes = self._sample_disk_stats(vm, domallstats) + netRxBytes, netTxBytes = self._sample_net_stats(vm, domallstats) - 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, - } - - return newStats + newstats = _VMStatsRecord( + timestamp, cpuTime, cpuTimeAbs, + cpuHostPercent, cpuGuestPercent, + curmem, currMemPercent, + diskRdBytes, diskWrBytes, + netRxBytes, netTxBytes) + self.get_vm_statslist(vm).append_stats(newstats) def cache_all_stats(self, conn): self._latest_all_stats = self._get_all_stats(conn) + + def get_vm_statslist(self, vm): + if vm.get_connkey() not in self._vm_stats: + self._vm_stats[vm.get_connkey()] = _VMStatsList() + return self._vm_stats[vm.get_connkey()]