mirror of https://github.com/nicolargo/glances.git
Add stats about running VMS (qemu/libvirt/kvm support through virsh) #1531
This commit is contained in:
parent
f9250c9985
commit
0f10ffc245
|
|
@ -0,0 +1,36 @@
|
|||
.. _vms:
|
||||
|
||||
VMs
|
||||
===
|
||||
|
||||
Glances ``vms`` plugin is designed to display stats about VMs ran on the host.
|
||||
|
||||
It's actually support two engines: `Multipass` and `Virsh`.
|
||||
|
||||
No Python dependency is needed but Multipass and Virsh binary should be available:
|
||||
- multipass should be executable from /snap/bin/multipass
|
||||
- virsh should be executable from /usr/bin/virsh
|
||||
|
||||
Note: CPU information is not availble for Multipass VM. Load is not available for Virsh VM.
|
||||
|
||||
Configuration file options:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[vms]
|
||||
disable=True
|
||||
# Define the maximum VMs size name (default is 20 chars)
|
||||
max_name_size=20
|
||||
# By default, Glances only display running VMs with states:
|
||||
# 'Running', 'Paused', 'Starting' or 'Restarting'
|
||||
# Set the following key to True to display all VMs regarding their states
|
||||
all=False
|
||||
|
||||
You can use all the variables ({{foo}}) available in the containers plugin.
|
||||
|
||||
Filtering (for hide or show) is based on regular expression. Please be sure that your regular
|
||||
expression works as expected. You can use an online tool like `regex101`_ in
|
||||
order to test your regular expression.
|
||||
|
||||
.. _Multipass: https://canonical.com/multipass
|
||||
.. _Virsh: https://www.libvirt.org/manpages/virsh.html
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
#
|
||||
# This file is part of Glances.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2025 Nicolas Hennion <nicolas@nicolargo.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
#
|
||||
|
||||
"""Virsh (QEMU/KVM) Extension unit for Glances' Vms plugin."""
|
||||
|
||||
import os
|
||||
import re
|
||||
from functools import cache
|
||||
from typing import Any
|
||||
|
||||
from glances.globals import nativestr
|
||||
from glances.plugins.vms.engines import VmsExtension
|
||||
from glances.secure import secure_popen
|
||||
|
||||
# Check if virsh binary exist
|
||||
# TODO: make this path configurable from the Glances configuration file
|
||||
VIRSH_PATH = '/usr/bin/virsh'
|
||||
VIRSH_VERSION_OPTIONS = 'version'
|
||||
VIRSH_INFO_OPTIONS = 'list --all'
|
||||
VIRSH_DOMAIN_STATS_OPTIONS = 'domstats'
|
||||
VIRSH_DOMAIN_TITLE_OPTIONS = 'desc --title'
|
||||
import_virsh_error_tag = not os.path.exists(VIRSH_PATH) and os.access(VIRSH_PATH, os.X_OK)
|
||||
|
||||
|
||||
class VmExtension(VmsExtension):
|
||||
"""Glances' Virsh Plugin's Vm Extension unit"""
|
||||
|
||||
CONTAINER_ACTIVE_STATUS = ['running']
|
||||
|
||||
def __init__(self):
|
||||
self.ext_name = "Virsh (Vm)"
|
||||
|
||||
@cache
|
||||
def update_version(self):
|
||||
# > virsh version
|
||||
# Compiled against library: libvirt 10.0.0
|
||||
# Using library: libvirt 10.0.0
|
||||
# Using API: QEMU 10.0.0
|
||||
# Running hypervisor: QEMU 8.2.2
|
||||
ret_cmd = secure_popen(f'{VIRSH_PATH} {VIRSH_VERSION_OPTIONS}')
|
||||
try:
|
||||
ret = ret_cmd.splitlines()[3].split(':')[1].lstrip()
|
||||
except IndexError:
|
||||
return ''
|
||||
return ret
|
||||
|
||||
def update_domains(self):
|
||||
# ❯ virsh list --all
|
||||
# Id Name State
|
||||
# ----------------------------------
|
||||
# 4 win11 paused
|
||||
# - Kali_Linux_2024 shut off
|
||||
ret_cmd = secure_popen(f'{VIRSH_PATH} {VIRSH_INFO_OPTIONS}')
|
||||
|
||||
try:
|
||||
# Ignore first two lines (header) and the last one (empty)
|
||||
lines = ret_cmd.splitlines()[2:-1]
|
||||
except IndexError:
|
||||
return {}
|
||||
|
||||
ret = {}
|
||||
for line in lines:
|
||||
domain = re.split(r'\s{2,}', line)
|
||||
ret[domain[1]] = {
|
||||
'id': domain[0],
|
||||
'state': domain[2],
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
def update_stats(self, domain):
|
||||
# ❯ virsh domstats win11
|
||||
# Domain: 'win11'
|
||||
# state.state=1
|
||||
# state.reason=1
|
||||
# cpu.time=1702145606000
|
||||
# cpu.user=1329954143000
|
||||
# cpu.system=372191462000
|
||||
# cpu.cache.monitor.count=0
|
||||
# cpu.haltpoll.success.time=60243506159
|
||||
# cpu.haltpoll.fail.time=34398762096
|
||||
# balloon.current=8388608
|
||||
# balloon.maximum=8388608
|
||||
# balloon.last-update=0
|
||||
# balloon.rss=8439116
|
||||
# vcpu.current=4
|
||||
# vcpu.maximum=4
|
||||
# vcpu.0.state=1
|
||||
# vcpu.0.time=955260000000
|
||||
# vcpu.0.wait=0
|
||||
# vcpu.0.delay=1305744538
|
||||
# vcpu.0.halt_wakeup.sum=701415
|
||||
# vcpu.0.halt_successful_poll.sum=457799
|
||||
# vcpu.0.pf_mmio_spte_created.sum=38376
|
||||
# vcpu.0.pf_emulate.sum=38873
|
||||
# vcpu.0.fpu_reload.sum=8252258
|
||||
# vcpu.0.insn_emulation.sum=6544119
|
||||
# vcpu.0.signal_exits.sum=144
|
||||
# vcpu.0.invlpg.sum=0
|
||||
# vcpu.0.request_irq_exits.sum=0
|
||||
# vcpu.0.preemption_reported.sum=25322
|
||||
# vcpu.0.l1d_flush.sum=0
|
||||
# vcpu.0.guest_mode.cur=no
|
||||
# vcpu.0.halt_poll_fail_ns.sum=8369146975
|
||||
# vcpu.0.pf_taken.sum=11455007
|
||||
# vcpu.0.notify_window_exits.sum=0
|
||||
# vcpu.0.directed_yield_successful.sum=0
|
||||
# vcpu.0.host_state_reload.sum=8982225
|
||||
# vcpu.0.nested_run.sum=0
|
||||
# vcpu.0.nmi_injections.sum=1
|
||||
# vcpu.0.pf_spurious.sum=7
|
||||
# vcpu.0.halt_exits.sum=1173865
|
||||
# vcpu.0.exits.sum=28094008
|
||||
# vcpu.0.mmio_exits.sum=6536664
|
||||
# vcpu.0.pf_fixed.sum=11410566
|
||||
# vcpu.0.insn_emulation_fail.sum=0
|
||||
# vcpu.0.io_exits.sum=1723037
|
||||
# vcpu.0.halt_attempted_poll.sum=610576
|
||||
# vcpu.0.req_event.sum=3245967
|
||||
# vcpu.0.irq_exits.sum=1265655
|
||||
# vcpu.0.blocking.cur=yes
|
||||
# vcpu.0.irq_injections.sum=594
|
||||
# vcpu.0.preemption_other.sum=11312
|
||||
# vcpu.0.pf_fast.sum=5377009
|
||||
# vcpu.0.hypercalls.sum=4799
|
||||
# vcpu.0.nmi_window_exits.sum=0
|
||||
# vcpu.0.directed_yield_attempted.sum=0
|
||||
# vcpu.0.tlb_flush.sum=286197
|
||||
# vcpu.0.halt_wait_ns.sum=872563463627
|
||||
# vcpu.0.irq_window_exits.sum=0
|
||||
# vcpu.0.pf_guest.sum=0
|
||||
# vcpu.0.halt_poll_success_ns.sum=13527499051
|
||||
# vcpu.0.halt_poll_invalid.sum=0
|
||||
# vcpu.1...
|
||||
# net.count=1
|
||||
# net.0.name=vnet3
|
||||
# net.0.rx.bytes=418454563
|
||||
# net.0.rx.pkts=291468
|
||||
# net.0.rx.errs=0
|
||||
# net.0.rx.drop=7
|
||||
# net.0.tx.bytes=3884562
|
||||
# net.0.tx.pkts=35405
|
||||
# net.0.tx.errs=0
|
||||
# net.0.tx.drop=0
|
||||
# block.count=2
|
||||
# block.0.name=sda
|
||||
# block.0.path=/var/lib/libvirt/images/win11.qcow2
|
||||
# block.0.backingIndex=2
|
||||
# block.0.rd.reqs=246802
|
||||
# block.0.rd.bytes=18509028864
|
||||
# block.0.rd.times=83676206416
|
||||
# block.0.wr.reqs=471370
|
||||
# block.0.wr.bytes=28260229120
|
||||
# block.0.wr.times=158585666809
|
||||
# block.0.fl.reqs=46269
|
||||
# block.0.fl.times=181262854634
|
||||
# block.0.allocation=137460187136
|
||||
# block.0.capacity=137438953472
|
||||
# block.0.physical=13457760256
|
||||
# block.1...
|
||||
# dirtyrate.calc_status=0
|
||||
# dirtyrate.calc_start_time=0
|
||||
# dirtyrate.calc_period=0
|
||||
# dirtyrate.calc_mode=page-sampling
|
||||
# vm.remote_tlb_flush.sum=436456
|
||||
# vm.nx_lpage_splits.cur=0
|
||||
# vm.pages_1g.cur=0
|
||||
# vm.pages_2m.cur=859
|
||||
# vm.pages_4k.cur=1196897
|
||||
# vm.max_mmu_page_hash_collisions.max=0
|
||||
# vm.mmu_pde_zapped.sum=0
|
||||
# vm.max_mmu_rmap_size.max=0
|
||||
# vm.mmu_cache_miss.sum=0
|
||||
# vm.mmu_recycled.sum=0
|
||||
# vm.mmu_unsync.cur=0
|
||||
# vm.mmu_shadow_zapped.sum=0
|
||||
# vm.mmu_flooded.sum=0
|
||||
# vm.mmu_pte_write.sum=0
|
||||
# vm.remote_tlb_flush_requests.sum=609455
|
||||
ret_cmd = secure_popen(f'{VIRSH_PATH} {VIRSH_DOMAIN_STATS_OPTIONS} {domain}')
|
||||
|
||||
try:
|
||||
# Ignore first line (domain name already know) and last line (empty)
|
||||
lines = ret_cmd.splitlines()[1:-1]
|
||||
except IndexError:
|
||||
return {}
|
||||
|
||||
ret = {}
|
||||
for line in lines:
|
||||
k, v = re.split(r'\s*=\s*', line.lstrip())
|
||||
ret[k] = v
|
||||
|
||||
return ret
|
||||
|
||||
@cache
|
||||
def update_title(self, domain):
|
||||
# ❯ virsh desc --title Kali_Linux_2024
|
||||
# Kali Linux 2024
|
||||
ret_cmd = secure_popen(f'{VIRSH_PATH} {VIRSH_DOMAIN_TITLE_OPTIONS} {domain}')
|
||||
|
||||
return ret_cmd.rstrip()
|
||||
|
||||
def update(self, all_tag) -> tuple[dict, list[dict]]:
|
||||
"""Update Vm stats using the input method."""
|
||||
# Can not run virsh on this system then...
|
||||
if import_virsh_error_tag:
|
||||
return '', []
|
||||
|
||||
# Update VM stats
|
||||
version_stats = self.update_version()
|
||||
domains_stats = self.update_domains()
|
||||
returned_stats = []
|
||||
for k, v in domains_stats.items():
|
||||
# Only display when VM in on 'running' and 'paused' states
|
||||
# Why paused ? Because a paused VM consume memory
|
||||
if all_tag or self._want_display(v, 'state', ['running', 'paused']):
|
||||
returned_stats.append(self.generate_stats(k, v))
|
||||
|
||||
return version_stats, returned_stats
|
||||
|
||||
@property
|
||||
def key(self) -> str:
|
||||
"""Return the key of the list."""
|
||||
return 'name'
|
||||
|
||||
def _want_display(self, domain_stats, key, values):
|
||||
return domain_stats.get(key).lower() in [v.lower() for v in values]
|
||||
|
||||
def generate_stats(self, domain_name, domain_stats) -> dict[str, Any]:
|
||||
# Update stats and title for the domain
|
||||
vm_stats = self.update_stats(domain_name)
|
||||
vm_title = self.update_title(domain_name)
|
||||
|
||||
return {
|
||||
'key': self.key,
|
||||
'name': nativestr(domain_name),
|
||||
'id': domain_stats.get('id'),
|
||||
'status': domain_stats.get('state').lower() if domain_stats.get('state') else None,
|
||||
'release': nativestr(vm_title),
|
||||
'cpu_count': int(vm_stats.get('vcpu.current', 1)) if vm_stats.get('vcpu.current', 1) else None,
|
||||
'cpu_time': int(vm_stats.get('cpu.time', 0)) / 1000000000 * 100,
|
||||
'memory_usage': int(vm_stats.get('balloon.rss')) * 1024 if vm_stats.get('balloon.rss') else None,
|
||||
'memory_total': int(vm_stats.get('balloon.maximum')) * 1024 if vm_stats.get('balloon.maximum') else None,
|
||||
'load_1min': None,
|
||||
'load_5min': None,
|
||||
'load_15min': None,
|
||||
'ipv4': None,
|
||||
# TODO: disk
|
||||
}
|
||||
Loading…
Reference in New Issue