Test Failed
Push — master ( 7cfc0c...4c3161 )
by Nicolas
04:02
created

glances.plugins.vms.engines.virsh   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 87
dl 0
loc 253
rs 10
c 0
b 0
f 0
wmc 22

9 Methods

Rating   Name   Duplication   Size   Complexity  
A VmExtension.update_title() 0 7 1
A VmExtension._want_display() 0 2 1
A VmExtension.update_version() 0 13 2
A VmExtension.generate_stats() 0 19 5
A VmExtension.update_stats() 0 123 3
A VmExtension.key() 0 4 1
A VmExtension.update() 0 17 5
A VmExtension.__init__() 0 2 1
A VmExtension.update_domains() 0 23 3
1
#
2
# This file is part of Glances.
3
#
4
# SPDX-FileCopyrightText: 2025 Nicolas Hennion <[email protected]>
5
#
6
# SPDX-License-Identifier: LGPL-3.0-only
7
#
8
9
"""Virsh (QEMU/KVM) Extension unit for Glances' Vms plugin."""
10
11
import os
12
import re
13
from functools import cache
14
from typing import Any
15
16
from glances.globals import nativestr
17
from glances.plugins.vms.engines import VmsExtension
18
from glances.secure import secure_popen
19
20
# Check if virsh binary exist
21
# TODO: make this path configurable from the Glances configuration file
22
VIRSH_PATH = '/usr/bin/virsh'
23
VIRSH_VERSION_OPTIONS = 'version'
24
VIRSH_INFO_OPTIONS = 'list --all'
25
VIRSH_DOMAIN_STATS_OPTIONS = 'domstats'
26
VIRSH_DOMAIN_TITLE_OPTIONS = 'desc --title'
27
import_virsh_error_tag = not os.path.exists(VIRSH_PATH) or not os.access(VIRSH_PATH, os.X_OK)
28
29
30
class VmExtension(VmsExtension):
31
    """Glances' Virsh Plugin's Vm Extension unit"""
32
33
    CONTAINER_ACTIVE_STATUS = ['running']
34
35
    def __init__(self):
36
        self.ext_name = "Virsh (Vm)"
37
38
    @cache
39
    def update_version(self):
40
        # > virsh version
41
        # Compiled against library: libvirt 10.0.0
42
        # Using library: libvirt 10.0.0
43
        # Using API: QEMU 10.0.0
44
        # Running hypervisor: QEMU 8.2.2
45
        ret_cmd = secure_popen(f'{VIRSH_PATH} {VIRSH_VERSION_OPTIONS}')
46
        try:
47
            ret = ret_cmd.splitlines()[3].split(':')[1].lstrip()
48
        except IndexError:
49
            return ''
50
        return ret
51
52
    def update_domains(self):
53
        # ❯ virsh list --all
54
        #  Id   Name              State
55
        # ----------------------------------
56
        #  4    win11             paused
57
        #  -    Kali_Linux_2024   shut off
58
        ret_cmd = secure_popen(f'{VIRSH_PATH} {VIRSH_INFO_OPTIONS}')
59
60
        try:
61
            # Ignore first two lines (header) and the last one (empty)
62
            lines = ret_cmd.splitlines()[2:-1]
63
        except IndexError:
64
            return {}
65
66
        ret = {}
67
        for line in lines:
68
            domain = re.split(r'\s{2,}', line)
69
            ret[domain[1]] = {
70
                'id': domain[0],
71
                'state': domain[2],
72
            }
73
74
        return ret
75
76
    def update_stats(self, domain):
77
        # ❯ virsh domstats win11
78
        # Domain: 'win11'
79
        #   state.state=1
80
        #   state.reason=1
81
        #   cpu.time=1702145606000
82
        #   cpu.user=1329954143000
83
        #   cpu.system=372191462000
84
        #   cpu.cache.monitor.count=0
85
        #   cpu.haltpoll.success.time=60243506159
86
        #   cpu.haltpoll.fail.time=34398762096
87
        #   balloon.current=8388608
88
        #   balloon.maximum=8388608
89
        #   balloon.last-update=0
90
        #   balloon.rss=8439116
91
        #   vcpu.current=4
92
        #   vcpu.maximum=4
93
        #   vcpu.0.state=1
94
        #   vcpu.0.time=955260000000
95
        #   vcpu.0.wait=0
96
        #   vcpu.0.delay=1305744538
97
        #   vcpu.0.halt_wakeup.sum=701415
98
        #   vcpu.0.halt_successful_poll.sum=457799
99
        #   vcpu.0.pf_mmio_spte_created.sum=38376
100
        #   vcpu.0.pf_emulate.sum=38873
101
        #   vcpu.0.fpu_reload.sum=8252258
102
        #   vcpu.0.insn_emulation.sum=6544119
103
        #   vcpu.0.signal_exits.sum=144
104
        #   vcpu.0.invlpg.sum=0
105
        #   vcpu.0.request_irq_exits.sum=0
106
        #   vcpu.0.preemption_reported.sum=25322
107
        #   vcpu.0.l1d_flush.sum=0
108
        #   vcpu.0.guest_mode.cur=no
109
        #   vcpu.0.halt_poll_fail_ns.sum=8369146975
110
        #   vcpu.0.pf_taken.sum=11455007
111
        #   vcpu.0.notify_window_exits.sum=0
112
        #   vcpu.0.directed_yield_successful.sum=0
113
        #   vcpu.0.host_state_reload.sum=8982225
114
        #   vcpu.0.nested_run.sum=0
115
        #   vcpu.0.nmi_injections.sum=1
116
        #   vcpu.0.pf_spurious.sum=7
117
        #   vcpu.0.halt_exits.sum=1173865
118
        #   vcpu.0.exits.sum=28094008
119
        #   vcpu.0.mmio_exits.sum=6536664
120
        #   vcpu.0.pf_fixed.sum=11410566
121
        #   vcpu.0.insn_emulation_fail.sum=0
122
        #   vcpu.0.io_exits.sum=1723037
123
        #   vcpu.0.halt_attempted_poll.sum=610576
124
        #   vcpu.0.req_event.sum=3245967
125
        #   vcpu.0.irq_exits.sum=1265655
126
        #   vcpu.0.blocking.cur=yes
127
        #   vcpu.0.irq_injections.sum=594
128
        #   vcpu.0.preemption_other.sum=11312
129
        #   vcpu.0.pf_fast.sum=5377009
130
        #   vcpu.0.hypercalls.sum=4799
131
        #   vcpu.0.nmi_window_exits.sum=0
132
        #   vcpu.0.directed_yield_attempted.sum=0
133
        #   vcpu.0.tlb_flush.sum=286197
134
        #   vcpu.0.halt_wait_ns.sum=872563463627
135
        #   vcpu.0.irq_window_exits.sum=0
136
        #   vcpu.0.pf_guest.sum=0
137
        #   vcpu.0.halt_poll_success_ns.sum=13527499051
138
        #   vcpu.0.halt_poll_invalid.sum=0
139
        #   vcpu.1...
140
        #   net.count=1
141
        #   net.0.name=vnet3
142
        #   net.0.rx.bytes=418454563
143
        #   net.0.rx.pkts=291468
144
        #   net.0.rx.errs=0
145
        #   net.0.rx.drop=7
146
        #   net.0.tx.bytes=3884562
147
        #   net.0.tx.pkts=35405
148
        #   net.0.tx.errs=0
149
        #   net.0.tx.drop=0
150
        #   block.count=2
151
        #   block.0.name=sda
152
        #   block.0.path=/var/lib/libvirt/images/win11.qcow2
153
        #   block.0.backingIndex=2
154
        #   block.0.rd.reqs=246802
155
        #   block.0.rd.bytes=18509028864
156
        #   block.0.rd.times=83676206416
157
        #   block.0.wr.reqs=471370
158
        #   block.0.wr.bytes=28260229120
159
        #   block.0.wr.times=158585666809
160
        #   block.0.fl.reqs=46269
161
        #   block.0.fl.times=181262854634
162
        #   block.0.allocation=137460187136
163
        #   block.0.capacity=137438953472
164
        #   block.0.physical=13457760256
165
        #   block.1...
166
        #   dirtyrate.calc_status=0
167
        #   dirtyrate.calc_start_time=0
168
        #   dirtyrate.calc_period=0
169
        #   dirtyrate.calc_mode=page-sampling
170
        #   vm.remote_tlb_flush.sum=436456
171
        #   vm.nx_lpage_splits.cur=0
172
        #   vm.pages_1g.cur=0
173
        #   vm.pages_2m.cur=859
174
        #   vm.pages_4k.cur=1196897
175
        #   vm.max_mmu_page_hash_collisions.max=0
176
        #   vm.mmu_pde_zapped.sum=0
177
        #   vm.max_mmu_rmap_size.max=0
178
        #   vm.mmu_cache_miss.sum=0
179
        #   vm.mmu_recycled.sum=0
180
        #   vm.mmu_unsync.cur=0
181
        #   vm.mmu_shadow_zapped.sum=0
182
        #   vm.mmu_flooded.sum=0
183
        #   vm.mmu_pte_write.sum=0
184
        #   vm.remote_tlb_flush_requests.sum=609455
185
        ret_cmd = secure_popen(f'{VIRSH_PATH} {VIRSH_DOMAIN_STATS_OPTIONS} {domain}')
186
187
        try:
188
            # Ignore first line (domain name already know) and last line (empty)
189
            lines = ret_cmd.splitlines()[1:-1]
190
        except IndexError:
191
            return {}
192
193
        ret = {}
194
        for line in lines:
195
            k, v = re.split(r'\s*=\s*', line.lstrip())
196
            ret[k] = v
197
198
        return ret
199
200
    @cache
201
    def update_title(self, domain):
202
        # ❯ virsh desc --title Kali_Linux_2024
203
        # Kali Linux 2024
204
        ret_cmd = secure_popen(f'{VIRSH_PATH} {VIRSH_DOMAIN_TITLE_OPTIONS} {domain}')
205
206
        return ret_cmd.rstrip()
207
208
    def update(self, all_tag) -> tuple[dict, list[dict]]:
209
        """Update Vm stats using the input method."""
210
        # Can not run virsh on this system then...
211
        if import_virsh_error_tag:
212
            return '', []
213
214
        # Update VM stats
215
        version_stats = self.update_version()
216
        domains_stats = self.update_domains()
217
        returned_stats = []
218
        for k, v in domains_stats.items():
219
            # Only display when VM in on 'running' and 'paused' states
220
            # Why paused ? Because a paused VM consume memory
221
            if all_tag or self._want_display(v, 'state', ['running', 'paused']):
222
                returned_stats.append(self.generate_stats(k, v))
223
224
        return version_stats, returned_stats
225
226
    @property
227
    def key(self) -> str:
228
        """Return the key of the list."""
229
        return 'name'
230
231
    def _want_display(self, domain_stats, key, values):
232
        return domain_stats.get(key).lower() in [v.lower() for v in values]
233
234
    def generate_stats(self, domain_name, domain_stats) -> dict[str, Any]:
235
        # Update stats and title for the domain
236
        vm_stats = self.update_stats(domain_name)
237
        vm_title = self.update_title(domain_name)
238
239
        return {
240
            'key': self.key,
241
            'name': nativestr(domain_name),
242
            'id': domain_stats.get('id'),
243
            'status': domain_stats.get('state').lower() if domain_stats.get('state') else None,
244
            'release': nativestr(vm_title),
245
            'cpu_count': int(vm_stats.get('vcpu.current', 1)) if vm_stats.get('vcpu.current', 1) else None,
246
            'cpu_time': int(vm_stats.get('cpu.time', 0)) / 1000000000 * 100,
247
            'memory_usage': int(vm_stats.get('balloon.rss')) * 1024 if vm_stats.get('balloon.rss') else None,
248
            'memory_total': int(vm_stats.get('balloon.maximum')) * 1024 if vm_stats.get('balloon.maximum') else None,
249
            'load_1min': None,
250
            'load_5min': None,
251
            'load_15min': None,
252
            'ipv4': None,
253
            # TODO: disk
254
        }
255