integ/utilities/pci-irq-affinity-agent/pci_irq_affinity/pci_irq_affinity/guest.py

266 lines
8.0 KiB
Python

#
# Copyright (c) 2019 StarlingX.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# All Rights Reserved.
#
""" Encapsulate libvirt related interfaces"""
import libvirt
import os
import sys
import signal
from xml.dom import minidom
from xml.etree import ElementTree
from log import LOG
debug = 0
# libvirt timeout parameters
LIBVIRT_TIMEOUT_SEC = 5.0
total_cpus = 0
def range_to_list(csv_range=None):
"""Convert a string of comma separate ranges into an expanded list of integers.
E.g., '1-3,8-9,15' is converted to [1,2,3,8,9,15]
"""
if not csv_range:
return []
xranges = [(lambda L: range(L[0], L[-1] + 1))(map(int, r.split('-')))
for r in csv_range.split(',')]
return [y for x in xranges for y in x]
def _translate_virDomainState(state):
"""Return human readable virtual domain state string."""
states = {}
states[0] = 'NOSTATE'
states[1] = 'Running'
states[2] = 'Blocked'
states[3] = 'Paused'
states[4] = 'Shutdown'
states[5] = 'Shutoff'
states[6] = 'Crashed'
states[7] = 'pmSuspended'
states[8] = 'Last'
return states[state]
def _mask_to_cpulist(mask=0):
"""Create cpulist from mask, list in socket-core-thread enumerated order.
:param extended: extended info
:param mask: cpuset mask
:returns cpulist: list of cpus in socket-core-thread enumerated order
"""
cpulist = []
if mask is None or mask <= 0:
return cpulist
# Assume max number of cpus for now...
max_cpus = 1024
for cpu in range(max_cpus):
if ((1 << cpu) & mask):
cpulist.append(cpu)
return cpulist
class suppress_stdout_stderr(object):
"""A context manager for doing a "deep suppression" of stdout and stderr in Python
i.e. will suppress all print, even if the print originates in a compiled C/Fortran
sub-function.
This will not suppress raised exceptions, since exceptions are printed
to stderr just before a script exits, and after the context manager has
exited (at least, I think that is why it lets exceptions through).
"""
def __init__(self):
# Open a pair of null files
self.null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)]
# Save the actual stdout (1) and stderr (2) file descriptors.
self.save_fds = (os.dup(1), os.dup(2))
def __enter__(self):
# Assign the null pointers to stdout and stderr.
os.dup2(self.null_fds[0], 1)
os.dup2(self.null_fds[1], 2)
def __exit__(self, *_):
# Re-assign the real stdout/stderr back to (1) and (2)
os.dup2(self.save_fds[0], 1)
os.dup2(self.save_fds[1], 2)
# Close the null files
os.close(self.null_fds[0])
os.close(self.null_fds[1])
class TimeoutError(Exception):
pass
def timeout_handler(signum, frame):
raise TimeoutError('timeout')
def connect_to_libvirt():
"""Connect to local libvirt."""
duri = "qemu:///system"
try:
signal.signal(signal.SIGALRM, timeout_handler)
signal.setitimer(signal.ITIMER_REAL, LIBVIRT_TIMEOUT_SEC)
with suppress_stdout_stderr():
conn = libvirt.openReadOnly(duri)
signal.alarm(0)
except TimeoutError:
conn = None
raise
except Exception as e:
conn = None
raise
finally:
signal.alarm(0)
return conn
def get_host_cpu_topology():
"""Enumerate logical cpu topology using socket_id, core_id, thread_id.
This generates the following dictionary:
topology[socket_id][core_id][thread_id] = cpu_id
"""
global total_cpus
# Connect to local libvirt hypervisor
conn = connect_to_libvirt()
# Get host capabilities
caps_str = conn.getCapabilities()
doc = ElementTree.fromstring(caps_str)
caps = minidom.parseString(caps_str)
caps_host = caps.getElementsByTagName('host')[0]
caps_cells = caps_host.getElementsByTagName('cells')[0]
total_cpus = caps_cells.getElementsByTagName('cpu').length
Thread_cnt = {}
topology = {}
cells = doc.findall('./host/topology/cells/cell')
for cell in cells:
for cpu in cell.findall('./cpus/cpu'):
# obtain core_id, cpu_id, and socket_id; ignore 'siblings' since
# that can be inferred by enumeration of thread_id.
core_id = int(cpu.get('core_id'))
cpu_id = int(cpu.get('id'))
socket_id = int(cpu.get('socket_id'))
# thread_id's are enumerated assuming cpu_id is already sorted
if socket_id not in Thread_cnt:
Thread_cnt[socket_id] = {}
if core_id not in Thread_cnt[socket_id]:
Thread_cnt[socket_id][core_id] = 0
else:
Thread_cnt[socket_id][core_id] += 1
thread_id = Thread_cnt[socket_id][core_id]
# save topology[socket_id][core_id][thread_id]
if socket_id not in topology:
topology[socket_id] = {}
if core_id not in topology[socket_id]:
topology[socket_id][core_id] = {}
topology[socket_id][core_id][thread_id] = cpu_id
conn.close()
return topology
def get_guest_domain_info(dom):
"""Obtain cpulist of pcpus in the order of vcpus.
This applies to either pinned or floating vcpus, Note that the cpuinfo
pcpu value can be stale if we scale down cpus since it reports cpu-last-run.
For this reason use cpumap = d_vcpus[1][vcpu], instead of cpuinfo
(i.e., vcpu, state, cpuTime, pcpu = d_vcpus[0][vcpu]).
"""
uuid = dom.UUIDString()
d_state, d_maxMem_KiB, d_memory_KiB, \
d_nrVirtCpu, d_cpuTime = dom.info()
try:
with suppress_stdout_stderr():
d_vcpus = dom.vcpus()
except Exception as e:
d_vcpus = tuple([d_nrVirtCpu * [],
d_nrVirtCpu * [tuple(total_cpus * [False])]])
cpulist_p = []
cpulist_d = {}
cpuset_total = 0
up_total = 0
for vcpu in range(d_nrVirtCpu):
cpuset_b = d_vcpus[1][vcpu]
cpuset = 0
for cpu, up in enumerate(cpuset_b):
if up:
cpulist_d[vcpu] = cpu
aff = 1 << cpu
cpuset |= aff
up_total += 1
cpuset_total |= cpuset
cpulist_f = _mask_to_cpulist(mask=cpuset_total)
for key in sorted(cpulist_d.keys()):
cpulist_p.append(cpulist_d[key])
# Determine if floating or pinned, display appropriate cpulist
if up_total > d_nrVirtCpu:
d_cpulist = cpulist_f
cpu_pinned = False
else:
d_cpulist = cpulist_p
cpu_pinned = True
# Determine list of numa nodes (the hard way)
dom_xml = ElementTree.fromstring(dom.XMLDesc(0))
nodeset = set([])
for elem in dom_xml.findall('./numatune/memnode'):
nodes = range_to_list(elem.get('nodeset'))
nodeset.update(nodes)
d_nodelist = list(sorted(nodeset))
# Get pci info.
pci_addrs = set()
for interface in dom_xml.findall('./devices/interface'):
if interface.find('driver').get('name').startswith('vfio'):
addr_tag = interface.find('source/address')
if addr_tag.get('type') == 'pci':
pci_addr = "%04x:%02x:%02x.%01x" % (
addr_tag.get('domain'),
addr_tag.get('bus'),
addr_tag.get('slot'),
addr_tag.get('function'))
pci_addrs.update([pci_addr])
# Update dictionary with per-domain information
domain = {
'uuid': uuid,
'state': _translate_virDomainState(d_state),
'IsCpuPinned': cpu_pinned,
'nr_vcpus': d_nrVirtCpu,
'nodelist': d_nodelist,
'cpulist': d_cpulist,
'cpu_pinning': cpulist_d,
'pci_addrs': pci_addrs
}
return domain
def get_guest_domain_by_uuid(conn, uuid):
try:
dom = conn.lookupByUUIDString(uuid)
except Exception as e:
LOG.warning("Failed to get domain for uuid=%s! error=%s" % (uuid, e))
return None
domain = get_guest_domain_info(dom)
return domain