Skip to content

Commit 4301eb1

Browse files
committed
Show CPU/memory usage in Qube manager again
fixes: QubesOS/qubes-issues#7817
1 parent 8dd162e commit 4301eb1

File tree

3 files changed

+94
-4
lines changed

3 files changed

+94
-4
lines changed

qubesmanager/qube_manager.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,18 @@ def __init__(self, vm):
244244
self.state = {'power': "", 'outdated': ""}
245245
self.updateable = getattr(vm, 'updateable', False)
246246
self.update(True)
247+
self.CPU_usage = None
248+
self.RAM_usage = None
249+
250+
def update_resource_usage(self, RAM_usage, CPU_usage):
251+
self.RAM_usage = RAM_usage
252+
self.CPU_usage = CPU_usage
247253

248254
def update_power_state(self):
249255
try:
250256
self.state['power'] = self.vm.get_power_state()
257+
if self.state["power"] in ["Halted", "Paused", "Suspended"]:
258+
self.update_resource_usage(None, None)
251259
if self.state['power'] == "Halted" and \
252260
self.vm.klass != "AdminVM" and \
253261
manager_utils.get_feature(
@@ -259,6 +267,7 @@ def update_power_state(self):
259267
except exc.QubesDaemonAccessError:
260268
self.state['power'] = ""
261269

270+
262271
self.state['outdated'] = ""
263272
try:
264273
if manager_utils.is_running(self.vm, False):
@@ -422,6 +431,8 @@ def __init__(self, qubes_cache):
422431
"Label",
423432
"Name",
424433
"State",
434+
"CPU",
435+
"MEM",
425436
"Template",
426437
"NetVM",
427438
"Disk Usage",
@@ -453,6 +464,12 @@ def data(self, index, role):
453464
col_name = self.columns_indices[col]
454465
vm = self.qubes_cache.get_vm(row)
455466

467+
if role == Qt.ItemDataRole.SizeHintRole:
468+
if col_name in ["CPU", "MEM"]:
469+
return QSize(100, 22)
470+
if role == Qt.ItemDataRole.TextAlignmentRole:
471+
if col_name in ["CPU", "MEM", "Disk Usage"]:
472+
return Qt.AlignmentFlag.AlignCenter
456473
if role == Qt.ItemDataRole.DisplayRole:
457474
if col_name == "Name":
458475
return vm.name
@@ -464,6 +481,14 @@ def data(self, index, role):
464481
return vm.template
465482
if col_name == "NetVM":
466483
return vm.netvm
484+
if col_name == "CPU":
485+
if not vm.CPU_usage:
486+
return "-"
487+
return vm.CPU_usage + " %"
488+
if col_name == "MEM":
489+
if not vm.RAM_usage:
490+
return "-"
491+
return str(int(int(vm.RAM_usage) / 1024)) + " MiB"
467492
if col_name == "Disk Usage":
468493
return vm.disk
469494
if col_name == "Internal":
@@ -508,6 +533,13 @@ def data(self, index, role):
508533
if role == Qt.ItemDataRole.UserRole + 1:
509534
if vm.klass == 'AdminVM':
510535
return ""
536+
# Consider allowing dom0 to be sorted for CPU & MEM usage
537+
if col_name == "CPU":
538+
if vm.CPU_usage:
539+
return int(vm.CPU_usage)
540+
if col_name == "MEM":
541+
if vm.RAM_usage:
542+
return int(vm.RAM_usage)
511543
if col_name == "Label":
512544
vmtype, vmcolor = vm.icon.split("-", 1)
513545
try:
@@ -746,7 +778,14 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
746778
# suppress saving settings while initializing widgets
747779
settings_loaded = False
748780

749-
def __init__(self, qt_app, qubes_app, dispatcher, _parent=None):
781+
def __init__(
782+
self,
783+
qt_app,
784+
qubes_app,
785+
dispatcher,
786+
stats_dispatcher=None,
787+
_parent=None
788+
):
750789
# pylint: disable=too-many-statements
751790
super().__init__()
752791
self.setupUi(self)
@@ -772,6 +811,7 @@ def __init__(self, qt_app, qubes_app, dispatcher, _parent=None):
772811

773812
self.frame_width = 0
774813
self.frame_height = 0
814+
self.foreground = True
775815

776816
self.init_template_menu()
777817
self.init_network_menu()
@@ -908,6 +948,9 @@ def __init__(self, qt_app, qubes_app, dispatcher, _parent=None):
908948
dispatcher.add_handler('domain-feature-delete:skip-update',
909949
self.on_domain_updates_available)
910950

951+
if stats_dispatcher:
952+
stats_dispatcher.add_handler("vm-stats", self.on_vm_stats)
953+
911954
self.installEventFilter(self)
912955

913956
# It needs to store threads until they finish
@@ -927,8 +970,10 @@ def eventFilter(self, _object, event):
927970
if event.type() == QEvent.Type.WindowActivate:
928971
self.update_running_size()
929972
self.size_on_disk_timer.setInterval(1000 * 60)
973+
self.foreground = True
930974
elif event.type() == QEvent.Type.WindowDeactivate:
931975
self.size_on_disk_timer.setInterval(1000 * 60 * 5)
976+
self.foreground = False
932977
return False
933978

934979
def scroll_to_top(self):
@@ -1191,6 +1236,16 @@ def update_running_size(self, *_args):
11911236
self.qubes_cache.get_vm(qid=vm.qid).update(
11921237
update_size_on_disk=True, event='disk_size')
11931238

1239+
def on_vm_stats(self, vm, _event, **kwargs):
1240+
if not self.foreground:
1241+
return
1242+
domain = self.qubes_app.domains[vm]
1243+
self.qubes_cache.get_vm(qid=domain.qid).update_resource_usage(
1244+
kwargs["memory_kb"],
1245+
kwargs["cpu_usage"],
1246+
)
1247+
self.proxy.invalidate()
1248+
11941249
def on_domain_added(self, _submitter, _event, vm, **_kwargs):
11951250
try:
11961251
domain = self.qubes_app.domains[vm]

qubesmanager/tests/test_qube_manager.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import time
2424
from datetime import datetime
2525

26-
from PyQt6.QtCore import Qt, QSettings, QItemSelectionModel
26+
from PyQt6.QtCore import Qt, QSettings, QItemSelectionModel, QEvent
2727
from PyQt6.QtGui import QPixmap, QIcon
2828
from PyQt6.QtWidgets import QMessageBox
2929

@@ -168,7 +168,12 @@ def test_000_window_loads(qapp, test_qubes_app):
168168

169169
def test_001_model_correctness(qapp, test_qubes_app):
170170
dispatcher = MockDispatcher(test_qubes_app)
171-
qm = qube_manager.VmManagerWindow(qapp, test_qubes_app, dispatcher)
171+
qm = qube_manager.VmManagerWindow(
172+
qapp,
173+
test_qubes_app,
174+
dispatcher,
175+
stats_dispatcher=dispatcher,
176+
)
172177

173178
model = qm.qubes_model
174179

@@ -177,6 +182,13 @@ def test_001_model_correctness(qapp, test_qubes_app):
177182
# number of domains
178183
assert model.rowCount(None) == len(domains)
179184

185+
# emulating CPU/MEM usage update for dom0
186+
qm.on_vm_stats('dom0', _event=None, memory_kb="67108864", cpu_usage="100")
187+
# emulating skipping of CPU/MEM usage update for personal
188+
qm.eventFilter(_object=None, event=QEvent(QEvent.Type.WindowDeactivate))
189+
qm.on_vm_stats('personal', _event=None, memory_kb="524288", cpu_usage="1")
190+
qm.eventFilter(_object=None, event=QEvent(QEvent.Type.WindowActivate))
191+
180192
# domain data
181193
for row in range(model.rowCount(None)):
182194
# name
@@ -222,6 +234,24 @@ def test_001_model_correctness(qapp, test_qubes_app):
222234
if getattr(vm_object, 'internal', False):
223235
assert internal_data == "Yes"
224236

237+
# cpu usage
238+
cpu_index = model.index(row, model.columns_indices.index(
239+
"CPU"))
240+
cpu_data = model.data(cpu_index, Qt.ItemDataRole.DisplayRole)
241+
if vm_object.klass == "AdminVM":
242+
assert str(cpu_data) == "100 %"
243+
else:
244+
assert str(cpu_data) == "-"
245+
246+
# mem usage
247+
mem_index = model.index(row, model.columns_indices.index(
248+
"MEM"))
249+
mem_data = model.data(mem_index, Qt.ItemDataRole.DisplayRole)
250+
if vm_object.klass == "AdminVM":
251+
assert str(mem_data) == "65536 MiB"
252+
else:
253+
assert str(mem_data) == "-"
254+
225255
# disk usage
226256
du_index = model.index(row, model.columns_indices.index(
227257
"Disk Usage"))

qubesmanager/utils.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,10 +562,14 @@ def run_asynchronous(window_class):
562562
loop = qasync.QEventLoop(qt_app)
563563
asyncio.set_event_loop(loop)
564564

565+
stats_dispatcher = events.EventsDispatcher(
566+
qubes_app,
567+
api_method = "admin.vm.Stats"
568+
)
565569
async def setup():
566570
dispatcher = events.EventsDispatcher(qubes_app)
567571

568-
window = window_class(qt_app, qubes_app, dispatcher)
572+
window = window_class(qt_app, qubes_app, dispatcher, stats_dispatcher)
569573

570574
if hasattr(window, "setup_application"):
571575
window.setup_application()
@@ -575,6 +579,7 @@ async def setup():
575579
await dispatcher.listen_for_events()
576580

577581
try:
582+
loop.create_task(stats_dispatcher.listen_for_events())
578583
loop.run_until_complete(asyncio.ensure_future(setup()))
579584
except asyncio.CancelledError:
580585
pass

0 commit comments

Comments
 (0)