2018-10-05 16:54:25 +02:00
# Copyright (C) 2018 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
2018-10-09 16:27:25 -04:00
import re
2018-10-05 16:54:25 +02:00
import time
import libvirt
2019-06-16 21:12:39 -04:00
from virtinst import log
2019-06-16 22:19:17 -04:00
from . . baseclass import vmmGObject
2018-10-05 16:54:25 +02:00
2018-10-10 10:34:52 -04:00
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 )
2020-09-05 15:50:04 -04:00
if current > expected : # pragma: no cover
2022-12-13 10:51:14 -05:00
del self . _stats [ expected : current ]
2018-10-10 10:34:52 -04:00
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 ) :
return ( self . get_vector ( name1 , limit , ceil = ceil ) ,
self . get_vector ( name2 , limit , ceil = ceil ) )
2018-10-05 16:54:25 +02:00
class vmmStatsManager ( vmmGObject ) :
"""
Class for polling statistics
"""
def __init__ ( self ) :
vmmGObject . __init__ ( self )
2018-10-10 10:34:52 -04:00
self . _vm_stats = { }
2018-10-10 16:42:59 -04:00
self . _latest_all_stats = { }
2018-10-09 16:27:25 -04:00
2018-10-10 17:25:47 -04:00
self . _all_stats_supported = True
2018-10-09 16:27:25 -04:00
self . _net_stats_supported = True
self . _disk_stats_supported = True
self . _disk_stats_lxc_supported = True
self . _mem_stats_supported = True
2018-10-05 16:54:25 +02:00
def _cleanup ( self ) :
2020-09-05 15:50:04 -04:00
for statslist in self . _vm_stats . values ( ) :
statslist . cleanup ( )
2018-10-10 08:11:25 -04:00
self . _latest_all_stats = None
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
######################
# CPU stats handling #
######################
def _old_cpu_stats_helper ( self , vm ) :
info = vm . get_backend ( ) . info ( )
state = info [ 0 ]
guestcpus = info [ 3 ]
cpuTimeAbs = info [ 4 ]
2018-10-05 16:54:25 +02:00
return state , guestcpus , cpuTimeAbs
2018-10-10 16:42:59 -04:00
def _sample_cpu_stats ( self , vm , allstats ) :
timestamp = time . time ( )
2018-10-10 17:34:21 -04:00
if ( not vm . is_active ( ) or
not self . config . get_stats_enable_cpu_poll ( ) ) :
2018-10-10 16:42:59 -04:00
return 0 , 0 , 0 , 0 , timestamp
2018-10-05 16:54:25 +02:00
cpuTime = 0
2018-10-10 10:34:52 -04:00
cpuHostPercent = 0
cpuGuestPercent = 0
prevTimestamp = self . get_vm_statslist ( vm ) . get_record ( " timestamp " )
prevCpuTime = self . get_vm_statslist ( vm ) . get_record ( " cpuTimeAbs " )
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
if allstats :
state = allstats . get ( " state.state " , 0 )
guestcpus = allstats . get ( " vcpu.current " , 0 )
cpuTimeAbs = allstats . get ( " cpu.time " , 0 )
2018-10-10 16:42:59 -04:00
timestamp = allstats . get ( " virt-manager.timestamp " )
2018-10-09 16:27:25 -04:00
else :
state , guestcpus , cpuTimeAbs = self . _old_cpu_stats_helper ( vm )
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
is_offline = ( state in [ libvirt . VIR_DOMAIN_SHUTOFF ,
libvirt . VIR_DOMAIN_CRASHED ] )
if is_offline :
guestcpus = 0
cpuTimeAbs = 0
cpuTime = cpuTimeAbs - prevCpuTime
if not is_offline :
2018-10-05 16:54:25 +02:00
hostcpus = vm . conn . host_active_processor_count ( )
2018-10-10 16:42:59 -04:00
pcentbase = (
( ( cpuTime ) * 100.0 ) /
( ( timestamp - prevTimestamp ) * 1000.0 * 1000.0 * 1000.0 ) )
2018-10-10 10:34:52 -04:00
cpuHostPercent = pcentbase / hostcpus
2018-10-05 16:54:25 +02:00
# Under RHEL-5.9 using a XEN HV guestcpus can be 0 during shutdown
# so play safe and check it.
2018-10-10 10:34:52 -04:00
cpuGuestPercent = guestcpus > 0 and pcentbase / guestcpus or 0
2018-10-05 16:54:25 +02:00
2018-10-10 10:34:52 -04:00
cpuHostPercent = max ( 0.0 , min ( 100.0 , cpuHostPercent ) )
cpuGuestPercent = max ( 0.0 , min ( 100.0 , cpuGuestPercent ) )
2018-10-05 16:54:25 +02:00
2018-10-10 16:42:59 -04:00
return cpuTime , cpuTimeAbs , cpuHostPercent , cpuGuestPercent , timestamp
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
######################
# net stats handling #
######################
def _old_net_stats_helper ( self , vm , dev ) :
2018-10-10 10:34:52 -04:00
statslist = self . get_vm_statslist ( vm )
2018-10-09 16:27:25 -04:00
try :
io = vm . get_backend ( ) . interfaceStats ( dev )
if io :
rx = io [ 0 ]
tx = io [ 4 ]
return rx , tx
2020-09-05 15:50:04 -04:00
except libvirt . libvirtError as err : # pragma: no cover
2019-06-07 16:48:21 -04:00
if vm . conn . support . is_error_nosupport ( err ) :
2019-06-16 21:12:39 -04:00
log . debug ( " conn does not support interfaceStats " )
2018-10-09 16:27:25 -04:00
self . _net_stats_supported = False
return 0 , 0
2019-06-16 21:12:39 -04:00
log . debug ( " Error in interfaceStats for ' %s ' dev ' %s ' : %s " ,
2018-10-09 16:27:25 -04:00
vm . get_name ( ) , dev , err )
if vm . is_active ( ) :
2019-06-16 21:12:39 -04:00
log . debug ( " Adding %s to skip list " , dev )
2018-10-10 10:34:52 -04:00
statslist . stats_net_skip . append ( dev )
2018-10-09 16:27:25 -04:00
else :
2019-06-16 21:12:39 -04:00
log . debug ( " Aren ' t running, don ' t add to skiplist " )
2018-10-05 16:54:25 +02:00
2020-09-05 15:50:04 -04:00
return 0 , 0 # pragma: no cover
2018-10-09 16:27:25 -04:00
def _sample_net_stats ( self , vm , allstats ) :
2018-10-05 16:54:25 +02:00
rx = 0
tx = 0
2018-10-10 10:34:52 -04:00
statslist = self . get_vm_statslist ( vm )
2018-10-09 16:27:25 -04:00
if ( not self . _net_stats_supported or
2018-10-10 17:25:47 -04:00
not vm . is_active ( ) or
not self . config . get_stats_enable_net_poll ( ) ) :
2018-10-10 10:34:52 -04:00
statslist . stats_net_skip = [ ]
2018-10-05 16:54:25 +02:00
return rx , tx
2018-10-09 16:27:25 -04:00
if allstats :
2020-09-05 15:50:04 -04:00
for key in allstats . keys ( ) : # pragma: no cover
2018-10-09 16:27:25 -04:00
if re . match ( r " net.[0-9]+.rx.bytes " , key ) :
rx + = allstats [ key ]
if re . match ( r " net.[0-9]+.tx.bytes " , key ) :
tx + = allstats [ key ]
return rx , tx
for iface in vm . get_interface_devices_norefresh ( ) :
dev = iface . target_dev
2018-10-05 16:54:25 +02:00
if not dev :
2020-09-05 15:50:04 -04:00
continue # pragma: no cover
2018-10-10 10:34:52 -04:00
if dev in statslist . stats_net_skip :
2020-09-05 15:50:04 -04:00
continue # pragma: no cover
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
devrx , devtx = self . _old_net_stats_helper ( vm , dev )
2018-10-05 16:54:25 +02:00
rx + = devrx
tx + = devtx
return rx , tx
2018-10-09 16:27:25 -04:00
#######################
# disk stats handling #
#######################
def _old_disk_stats_helper ( self , vm , dev ) :
2018-10-10 10:34:52 -04:00
statslist = self . get_vm_statslist ( vm )
2018-10-09 16:27:25 -04:00
try :
io = vm . get_backend ( ) . blockStats ( dev )
if io :
rd = io [ 1 ]
wr = io [ 3 ]
return rd , wr
2020-09-05 15:50:04 -04:00
except libvirt . libvirtError as err : # pragma: no cover
2019-06-07 16:48:21 -04:00
if vm . conn . support . is_error_nosupport ( err ) :
2019-06-16 21:12:39 -04:00
log . debug ( " conn does not support blockStats " )
2018-10-09 16:27:25 -04:00
self . _disk_stats_supported = False
return 0 , 0
2019-06-16 21:12:39 -04:00
log . debug ( " Error in blockStats for ' %s ' dev ' %s ' : %s " ,
2018-10-09 16:27:25 -04:00
vm . get_name ( ) , dev , err )
if vm . is_active ( ) :
2019-06-16 21:12:39 -04:00
log . debug ( " Adding %s to skip list " , dev )
2018-10-10 10:34:52 -04:00
statslist . stats_disk_skip . append ( dev )
2018-10-09 16:27:25 -04:00
else :
2019-06-16 21:12:39 -04:00
log . debug ( " Aren ' t running, don ' t add to skiplist " )
2018-10-09 16:27:25 -04:00
2020-09-05 15:50:04 -04:00
return 0 , 0 # pragma: no cover
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
def _sample_disk_stats ( self , vm , allstats ) :
2018-10-05 16:54:25 +02:00
rd = 0
wr = 0
2018-10-10 10:34:52 -04:00
statslist = self . get_vm_statslist ( vm )
2018-10-09 16:27:25 -04:00
if ( not self . _disk_stats_supported or
2018-10-10 17:25:47 -04:00
not vm . is_active ( ) or
not self . config . get_stats_enable_disk_poll ( ) ) :
2018-10-10 10:34:52 -04:00
statslist . stats_disk_skip = [ ]
2018-10-05 16:54:25 +02:00
return rd , wr
2018-10-09 16:27:25 -04:00
if allstats :
for key in allstats . keys ( ) :
if re . match ( r " block.[0-9]+.rd.bytes " , key ) :
rd + = allstats [ key ]
if re . match ( r " block.[0-9]+.wr.bytes " , key ) :
wr + = allstats [ key ]
2018-10-05 16:54:25 +02:00
return rd , wr
2018-10-09 16:27:25 -04:00
# LXC has a special blockStats method
if vm . conn . is_lxc ( ) and self . _disk_stats_lxc_supported :
try :
io = vm . get_backend ( ) . blockStats ( ' ' )
if io :
rd = io [ 1 ]
wr = io [ 3 ]
return rd , wr
2020-09-05 15:50:04 -04:00
except libvirt . libvirtError as e : # pragma: no cover
2019-06-16 21:12:39 -04:00
log . debug ( " LXC style disk stats not supported: %s " , e )
2018-10-09 16:27:25 -04:00
self . _disk_stats_lxc_supported = False
for disk in vm . get_disk_devices_norefresh ( ) :
2018-10-05 16:54:25 +02:00
dev = disk . target
if not dev :
2020-09-05 15:50:04 -04:00
continue # pragma: no cover
2018-10-10 10:34:52 -04:00
if dev in statslist . stats_disk_skip :
2020-09-05 15:50:04 -04:00
continue # pragma: no cover
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
diskrd , diskwr = self . _old_disk_stats_helper ( vm , dev )
2018-10-05 16:54:25 +02:00
rd + = diskrd
wr + = diskwr
return rd , wr
2018-10-09 16:27:25 -04:00
#########################
# memory stats handling #
#########################
def _set_mem_stats_period ( self , vm ) :
2018-10-05 16:54:25 +02:00
# QEMU requires to explicitly enable memory stats polling per VM
# if we want fine grained memory stats
2019-06-07 16:06:52 -04:00
if not vm . conn . support . conn_mem_stats_period ( ) :
2018-10-05 16:54:25 +02:00
return
# Only works for virtio balloon
if not any ( [ b for b in vm . get_xmlobj ( ) . devices . memballoon if
b . model == " virtio " ] ) :
2020-09-05 15:50:04 -04:00
return # pragma: no cover
2018-10-05 16:54:25 +02:00
try :
secs = 5
vm . get_backend ( ) . setMemoryStatsPeriod ( secs ,
libvirt . VIR_DOMAIN_AFFECT_LIVE )
2020-09-05 15:50:04 -04:00
except Exception as e : # pragma: no cover
2019-06-16 21:12:39 -04:00
log . debug ( " Error setting memstats period: %s " , e )
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
def _old_mem_stats_helper ( self , vm ) :
totalmem = 1
curmem = 0
try :
stats = vm . get_backend ( ) . memoryStats ( )
totalmem = stats . get ( " actual " , 1 )
curmem = max ( 0 , totalmem - stats . get ( " unused " , totalmem ) )
2020-09-05 15:50:04 -04:00
except libvirt . libvirtError as err : # pragma: no cover
2019-06-07 16:48:21 -04:00
if vm . conn . support . is_error_nosupport ( err ) :
2019-06-16 21:12:39 -04:00
log . debug ( " conn does not support memoryStats " )
2018-10-09 16:27:25 -04:00
self . _mem_stats_supported = False
else :
2019-06-16 21:12:39 -04:00
log . debug ( " Error reading mem stats: %s " , err )
2018-10-05 16:54:25 +02:00
return totalmem , curmem
2018-10-09 16:27:25 -04:00
def _sample_mem_stats ( self , vm , allstats ) :
2018-10-10 10:34:52 -04:00
statslist = self . get_vm_statslist ( vm )
2018-10-09 16:27:25 -04:00
if ( not self . _mem_stats_supported or
2018-10-10 17:25:47 -04:00
not vm . is_active ( ) or
not self . config . get_stats_enable_memory_poll ( ) ) :
2018-10-10 10:34:52 -04:00
statslist . mem_stats_period_is_set = False
2018-10-05 16:54:25 +02:00
return 0 , 0
2018-10-10 10:34:52 -04:00
if statslist . mem_stats_period_is_set is False :
2018-10-05 16:54:25 +02:00
self . _set_mem_stats_period ( vm )
2018-10-10 10:34:52 -04:00
statslist . mem_stats_period_is_set = True
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
if allstats :
totalmem = allstats . get ( " balloon.current " , 1 )
curmem = max ( 0 ,
totalmem - allstats . get ( " balloon.unused " , totalmem ) )
else :
totalmem , curmem = self . _old_mem_stats_helper ( vm )
2018-10-05 16:54:25 +02:00
2018-10-10 10:34:52 -04:00
currMemPercent = ( curmem / float ( totalmem ) ) * 100
currMemPercent = max ( 0.0 , min ( currMemPercent , 100.0 ) )
2018-10-05 16:54:25 +02:00
2018-10-10 10:34:52 -04:00
return currMemPercent , curmem
2018-10-05 16:54:25 +02:00
2018-10-09 16:27:25 -04:00
2018-10-10 10:34:52 -04:00
####################
# alltats handling #
####################
2018-10-09 16:27:25 -04:00
2018-10-10 08:11:25 -04:00
def _get_all_stats ( self , conn ) :
2022-01-17 16:07:25 -05:00
# test conn supports allstats as of 2021, but for test coverage
# purposes lets still use the old stats code for the test driver
if not self . _all_stats_supported or conn . is_test ( ) :
2018-10-10 16:42:59 -04:00
return { }
2018-10-09 16:51:34 -04:00
2018-10-10 17:00:01 -04:00
statflags = 0
2018-10-10 17:25:47 -04:00
if self . config . get_stats_enable_cpu_poll ( ) :
2018-10-10 17:00:01 -04:00
statflags | = libvirt . VIR_DOMAIN_STATS_STATE
statflags | = libvirt . VIR_DOMAIN_STATS_CPU_TOTAL
statflags | = libvirt . VIR_DOMAIN_STATS_VCPU
2018-10-10 17:25:47 -04:00
if self . config . get_stats_enable_memory_poll ( ) :
2018-10-10 17:00:01 -04:00
statflags | = libvirt . VIR_DOMAIN_STATS_BALLOON
2018-10-10 17:25:47 -04:00
if self . config . get_stats_enable_disk_poll ( ) :
2018-10-10 17:00:01 -04:00
statflags | = libvirt . VIR_DOMAIN_STATS_BLOCK
2018-10-10 17:25:47 -04:00
if self . config . get_stats_enable_net_poll ( ) :
2018-10-10 17:00:01 -04:00
statflags | = libvirt . VIR_DOMAIN_STATS_INTERFACE
if statflags == 0 :
2020-09-05 15:50:04 -04:00
return { } # pragma: no cover
2018-10-10 17:00:01 -04:00
2018-10-10 16:42:59 -04:00
ret = { }
2018-10-05 16:54:25 +02:00
try :
2018-10-10 16:42:59 -04:00
timestamp = time . time ( )
2018-10-10 17:00:01 -04:00
rawallstats = conn . get_backend ( ) . getAllDomainStats ( statflags , 0 )
2018-10-10 16:42:59 -04:00
# Reformat the output to be a bit more friendly
for dom , domallstats in rawallstats :
domallstats [ " virt-manager.timestamp " ] = timestamp
ret [ dom . UUIDString ( ) ] = domallstats
2018-10-05 16:54:25 +02:00
except libvirt . libvirtError as err :
2019-06-07 16:48:21 -04:00
if conn . support . is_error_nosupport ( err ) :
2019-06-16 21:12:39 -04:00
log . debug ( " conn does not support getAllDomainStats() " )
2018-10-05 16:54:25 +02:00
self . _all_stats_supported = False
2020-09-05 15:50:04 -04:00
else : # pragma: no cover
2019-06-16 21:12:39 -04:00
log . debug ( " Error call getAllDomainStats(): %s " , err )
2018-10-10 16:42:59 -04:00
return ret
2018-10-05 16:54:25 +02:00
2018-10-10 10:34:52 -04:00
##############
# Public API #
##############
2018-10-10 08:11:25 -04:00
def refresh_vm_stats ( self , vm ) :
2018-10-10 16:42:59 -04:00
domallstats = self . _latest_all_stats . get ( vm . get_uuid ( ) , None )
2018-10-10 08:11:25 -04:00
2018-10-10 16:42:59 -04:00
( cpuTime , cpuTimeAbs , cpuHostPercent , cpuGuestPercent , timestamp ) = \
self . _sample_cpu_stats ( vm , domallstats )
2018-10-10 10:34:52 -04:00
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 = _VMStatsRecord (
timestamp , cpuTime , cpuTimeAbs ,
cpuHostPercent , cpuGuestPercent ,
curmem , currMemPercent ,
diskRdBytes , diskWrBytes ,
netRxBytes , netTxBytes )
self . get_vm_statslist ( vm ) . append_stats ( newstats )
2018-10-10 08:11:25 -04:00
def cache_all_stats ( self , conn ) :
self . _latest_all_stats = self . _get_all_stats ( conn )
2018-10-10 10:34:52 -04:00
def get_vm_statslist ( self , vm ) :
2020-09-01 12:35:26 -04:00
if vm . get_name ( ) not in self . _vm_stats :
self . _vm_stats [ vm . get_name ( ) ] = _VMStatsList ( )
return self . _vm_stats [ vm . get_name ( ) ]