Merge branch 'issue1531' into develop

This commit is contained in:
nicolargo 2025-04-21 10:40:03 +02:00
commit 2cbf7a7fba
18 changed files with 1173 additions and 699 deletions

View File

@ -505,7 +505,8 @@ port_default_gateway=True
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', 'Starting' or 'Restarting'
# 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

View File

@ -41,6 +41,7 @@ Legend:
hddtemp
ps
containers
vms
amps
events
actions

36
docs/aoa/vms.rst Normal file
View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -352,7 +352,7 @@ The following commands (key pressed) are supported while in Glances:
Show/hide RAID plugin
``s``
Show/hide sensors stats
Show/hide sensors plugin
``S``
Enable/disable spark lines
@ -369,6 +369,9 @@ The following commands (key pressed) are supported while in Glances:
``U``
View cumulative network I/O
``V``
Show/hide VMS plugin
``w``
Delete finished warning log messages

View File

@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "GLANCES" "1" "Apr 11, 2025" "4.3.2_dev01" "Glances"
.TH "GLANCES" "1" "Apr 21, 2025" "4.3.2_dev01" "Glances"
.SH NAME
glances \- An eye on your system
.SH SYNOPSIS
@ -458,7 +458,7 @@ Reset history
Show/hide RAID plugin
.TP
.B \fBs\fP
Show/hide sensors stats
Show/hide sensors plugin
.TP
.B \fBS\fP
Enable/disable spark lines
@ -475,6 +475,9 @@ Sort processes by USER
.B \fBU\fP
View cumulative network I/O
.TP
.B \fBV\fP
Show/hide VMS plugin
.TP
.B \fBw\fP
Delete finished warning log messages
.TP

View File

@ -308,13 +308,13 @@ body {
* > td:nth-child(3) {
width: 6em;
}
* > td:nth-child(5) {
* > td:nth-child(6) {
text-align: right;
}
* > td:nth-child(7) {
* > td:nth-child(8) {
width: 10em;
}
* > td:nth-child(6), td:nth-child(7), td:nth-child(8) {
* > td:nth-child(7), td:nth-child(8), td:nth-child(9) {
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -6,21 +6,22 @@
<thead>
<tr>
<td v-show="showEngine">Engine</td>
<td
:class="['sortable', sorter.column === 'name' && 'sort']"
<td :class="['sortable', sorter.column === 'name' && 'sort']"
@click="args.sort_processes_key = 'name'">
Name
</td>
<td>Status</td>
<td>Core</td>
<td
:class="['sortable', sorter.column === 'memory_usage' && 'sort']"
<td :class="['sortable', sorter.column === 'cpu_time' && 'sort']"
@click="args.sort_processes_key = 'cpu_time'">
CPU%
</td>
<td :class="['sortable', sorter.column === 'memory_usage' && 'sort']"
@click="args.sort_processes_key = 'memory_usage'">
MEM
</td>
<td>/ MAX</td>
<td
:class="['sortable', sorter.column === 'load_1min' && 'sort']"
<td :class="['sortable', sorter.column === 'load_1min' && 'sort']"
@click="args.sort_processes_key = 'load_1min'">
LOAD 1/5/15min
</td>
@ -37,6 +38,9 @@
<td>
{{ $filters.number(vm.cpu_count, 1) }}
</td>
<td>
{{ $filters.number(vm.cpu_time, 1) }}
</td>
<td>
{{ $filters.bytes(vm.memory_usage) }}
</td>
@ -92,13 +96,15 @@ export default {
return {
'id': vmData.id,
'name': vmData.name,
'status': vmData.status != undefined ? vmData.status : '?',
'cpu_count': vmData.cpu_count != undefined ? vmData.cpu_count : '?',
'memory_usage': vmData.memory_usage != undefined ? vmData.memory_usage : '?',
'memory_total': vmData.memory_total != undefined ? vmData.memory_total : '?',
'load_1min': vmData.load_1min != undefined ? vmData.load_1min : '?',
'load_5min': vmData.load_5min != undefined ? vmData.load_5min : '?',
'load_15min': vmData.load_15min != undefined ? vmData.load_15min : '?',
'status': vmData.status != undefined ? vmData.status : '-',
'cpu_count': vmData.cpu_count != undefined ? vmData.cpu_count : '-',
'cpu_time': vmData.cpu_time != undefined ? vmData.cpu_time : '-',
'cpu_time_rate_per_sec': vmData.cpu_time_rate_per_sec != undefined ? vmData.cpu_time_rate_per_sec : '?',
'memory_usage': vmData.memory_usage != undefined ? vmData.memory_usage : '-',
'memory_total': vmData.memory_total != undefined ? vmData.memory_total : '-',
'load_1min': vmData.load_1min != undefined ? vmData.load_1min : '-',
'load_5min': vmData.load_5min != undefined ? vmData.load_5min : '-',
'load_15min': vmData.load_15min != undefined ? vmData.load_15min : '-',
'release': vmData.release,
'image': vmData.image,
'engine': vmData.engine,
@ -125,13 +131,14 @@ export default {
sortProcessesKey: {
immediate: true,
handler(sortProcessesKey) {
const sortable = ['load_1min', 'memory_usage', 'name'];
const sortable = ['load_1min', 'cpu_time', 'memory_usage', 'name'];
function isReverseColumn(column) {
return !['name'].includes(column);
}
function getColumnLabel(value) {
const labels = {
load_1min: 'load',
cpu_time: 'CPU time',
memory_usage: 'memory consumption',
name: 'VM name',
None: 'None'
@ -140,7 +147,7 @@ export default {
}
if (!sortProcessesKey || sortable.includes(sortProcessesKey)) {
this.sorter = {
column: this.args.sort_processes_key || 'load_1min',
column: this.args.sort_processes_key || 'cpu_time',
auto: !this.args.sort_processes_key,
isReverseColumn,
getColumnLabel

View File

@ -104,6 +104,11 @@ export function nl2br(input) {
}
export function number(value, options) {
// If value is undefined or not a number then return '-'
if ((typeof value === 'undefined') || isNaN(value)) {
return '-';
}
// Else
return new Intl.NumberFormat(
"en-US",
typeof options === 'number' ? { maximumFractionDigits: options } : options

View File

@ -9,7 +9,7 @@
"favico.js": "^0.3.10",
"hotkeys-js": "^3.13.9",
"lodash": "^4.17.21",
"sanitize-html": "^2.15.0",
"sanitize-html": "^2.16.0",
"vue": "^3.5.13"
},
"devDependencies": {
@ -17,7 +17,7 @@
"copy-webpack-plugin": "^13.0.0",
"css-loader": "^7.1.2",
"del": "^8.0.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-vue": "^10.0.0",
"globals": "^16.0.0",
@ -27,10 +27,10 @@
"sass": "^1.86.3",
"sass-loader": "^16.0.5",
"style-loader": "^4.0.0",
"typescript-eslint": "^8.29.1",
"typescript-eslint": "^8.30.1",
"url-loader": "^4.1.1",
"vue-loader": "^17.4.2",
"webpack": "^5.99.5",
"webpack": "^5.99.6",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.1"
}
@ -92,9 +92,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
"integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz",
"integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -159,9 +159,9 @@
}
},
"node_modules/@eslint/core": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@ -209,9 +209,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.24.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz",
"integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==",
"version": "9.25.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.0.tgz",
"integrity": "sha512-iWhsUS8Wgxz9AXNfvfOPFSW4VfMXdVhp1hjkZVhXCrpgh/aLcc45rX6MPu+tIVUWDw0HfNwth7O28M1xDxNf9w==",
"dev": true,
"license": "MIT",
"engines": {
@ -242,19 +242,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@ -1050,17 +1037,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz",
"integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz",
"integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.29.1",
"@typescript-eslint/type-utils": "8.29.1",
"@typescript-eslint/utils": "8.29.1",
"@typescript-eslint/visitor-keys": "8.29.1",
"@typescript-eslint/scope-manager": "8.30.1",
"@typescript-eslint/type-utils": "8.30.1",
"@typescript-eslint/utils": "8.30.1",
"@typescript-eslint/visitor-keys": "8.30.1",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@ -1080,16 +1067,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz",
"integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz",
"integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.29.1",
"@typescript-eslint/types": "8.29.1",
"@typescript-eslint/typescript-estree": "8.29.1",
"@typescript-eslint/visitor-keys": "8.29.1",
"@typescript-eslint/scope-manager": "8.30.1",
"@typescript-eslint/types": "8.30.1",
"@typescript-eslint/typescript-estree": "8.30.1",
"@typescript-eslint/visitor-keys": "8.30.1",
"debug": "^4.3.4"
},
"engines": {
@ -1105,14 +1092,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz",
"integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz",
"integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.29.1",
"@typescript-eslint/visitor-keys": "8.29.1"
"@typescript-eslint/types": "8.30.1",
"@typescript-eslint/visitor-keys": "8.30.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1123,14 +1110,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz",
"integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz",
"integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.29.1",
"@typescript-eslint/utils": "8.29.1",
"@typescript-eslint/typescript-estree": "8.30.1",
"@typescript-eslint/utils": "8.30.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.0.1"
},
@ -1147,9 +1134,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz",
"integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz",
"integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==",
"dev": true,
"license": "MIT",
"engines": {
@ -1161,14 +1148,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz",
"integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz",
"integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.29.1",
"@typescript-eslint/visitor-keys": "8.29.1",
"@typescript-eslint/types": "8.30.1",
"@typescript-eslint/visitor-keys": "8.30.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@ -1214,16 +1201,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz",
"integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz",
"integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.29.1",
"@typescript-eslint/types": "8.29.1",
"@typescript-eslint/typescript-estree": "8.29.1"
"@typescript-eslint/scope-manager": "8.30.1",
"@typescript-eslint/types": "8.30.1",
"@typescript-eslint/typescript-estree": "8.30.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1238,13 +1225,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz",
"integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz",
"integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.29.1",
"@typescript-eslint/types": "8.30.1",
"eslint-visitor-keys": "^4.2.0"
},
"engines": {
@ -2032,9 +2019,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001713",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz",
"integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==",
"version": "1.0.30001715",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
"integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
"dev": true,
"funding": [
{
@ -2688,9 +2675,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.136",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz",
"integrity": "sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==",
"version": "1.5.139",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.139.tgz",
"integrity": "sha512-GGnRYOTdN5LYpwbIr0rwP/ZHOQSvAF6TG0LSzp28uCBb9JiXHJGmaaKw29qjNJc5bGnnp6kXJqRnGMQoELwi5w==",
"dev": true,
"license": "ISC"
},
@ -2837,20 +2824,20 @@
}
},
"node_modules/eslint": {
"version": "9.24.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
"version": "9.25.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.0.tgz",
"integrity": "sha512-MsBdObhM4cEwkzCiraDv7A6txFXEqtNXOb877TsSp2FCkBNl8JfVQrmiuDqC1IkejT6JLPzYBXx/xAiYhyzgGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.0",
"@eslint/config-helpers": "^0.2.0",
"@eslint/core": "^0.12.0",
"@eslint/config-helpers": "^0.2.1",
"@eslint/core": "^0.13.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.24.0",
"@eslint/plugin-kit": "^0.2.7",
"@eslint/js": "9.25.0",
"@eslint/plugin-kit": "^0.2.8",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@ -4768,9 +4755,9 @@
}
},
"node_modules/open": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz",
"integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/open/-/open-10.1.1.tgz",
"integrity": "sha512-zy1wx4+P3PfhXSEPJNtZmJXfhkkIaxU1VauWIrDZw1O7uJRDRJtKr9n3Ic4NgbA16KyOxOXO2ng9gYwCdXuSXA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -5604,9 +5591,9 @@
"license": "MIT"
},
"node_modules/sanitize-html": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.15.0.tgz",
"integrity": "sha512-wIjst57vJGpLyBP8ioUbg6ThwJie5SuSIjHxJg53v5Fg+kUK+AXlb7bK3RNXpp315MvwM+0OBGCV6h5pPHsVhA==",
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.16.0.tgz",
"integrity": "sha512-0s4caLuHHaZFVxFTG74oW91+j6vW7gKbGD6CD2+miP73CE6z6YtOBN0ArtLd2UGyi4IC7K47v3ENUbQX4jV3Mg==",
"license": "MIT",
"dependencies": {
"deepmerge": "^4.2.2",
@ -6404,13 +6391,13 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
"integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.3",
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
@ -6421,9 +6408,9 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@ -6551,15 +6538,15 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz",
"integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==",
"version": "8.30.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.30.1.tgz",
"integrity": "sha512-D7lC0kcehVH7Mb26MRQi64LMyRJsj3dToJxM1+JVTl53DQSV5/7oUGWQLcKl1C1KnoVHxMMU2FNQMffr7F3Row==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.29.1",
"@typescript-eslint/parser": "8.29.1",
"@typescript-eslint/utils": "8.29.1"
"@typescript-eslint/eslint-plugin": "8.30.1",
"@typescript-eslint/parser": "8.30.1",
"@typescript-eslint/utils": "8.30.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -6830,9 +6817,9 @@
}
},
"node_modules/webpack": {
"version": "5.99.5",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz",
"integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==",
"version": "5.99.6",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.6.tgz",
"integrity": "sha512-TJOLrJ6oeccsGWPl7ujCYuc0pIq2cNsuD6GZDma8i5o5Npvcco/z+NKvZSFsP0/x6SShVb0+X2JK/JHUjKY9dQ==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@ -5,7 +5,7 @@
"favico.js": "^0.3.10",
"hotkeys-js": "^3.13.9",
"lodash": "^4.17.21",
"sanitize-html": "^2.15.0",
"sanitize-html": "^2.16.0",
"vue": "^3.5.13"
},
"devDependencies": {
@ -13,7 +13,7 @@
"copy-webpack-plugin": "^13.0.0",
"css-loader": "^7.1.2",
"del": "^8.0.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-vue": "^10.0.0",
"globals": "^16.0.0",
@ -23,10 +23,10 @@
"sass": "^1.86.3",
"sass-loader": "^16.0.5",
"style-loader": "^4.0.0",
"typescript-eslint": "^8.29.1",
"typescript-eslint": "^8.30.1",
"url-loader": "^4.1.1",
"vue-loader": "^17.4.2",
"webpack": "^5.99.5",
"webpack": "^5.99.6",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.1"
},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -531,8 +531,10 @@ class GlancesPluginModel:
else:
item_views = self.views[item]
if key is None or key not in item_views:
if key is None:
return item_views
if key not in item_views:
return 'DEFAULT'
if option is None:
return item_views[key]
if option in item_views[key]:

View File

@ -15,7 +15,8 @@ from glances.globals import iteritems
from glances.logger import logger
from glances.plugins.plugin.model import GlancesPluginModel
from glances.plugins.vms.engines import VmsExtension
from glances.plugins.vms.engines.multipass import VmExtension
from glances.plugins.vms.engines.multipass import VmExtension as MultipassVmExtension
from glances.plugins.vms.engines.virsh import VmExtension as VirshVmExtension
from glances.processes import glances_processes
from glances.processes import sort_stats as sort_stats_processes
@ -41,6 +42,11 @@ fields_description = {
'cpu_count': {
'description': 'Vm CPU count',
},
'cpu_time': {
'description': 'Vm CPU time',
'rate': True,
'unit': 'percent',
},
'memory_usage': {
'description': 'Vm memory usage',
'unit': 'byte',
@ -62,7 +68,7 @@ fields_description = {
'description': 'Vm IP v4 address',
},
'engine': {
'description': 'VM engine name (only Mutlipass is currently supported)',
'description': 'VM engine name',
},
'engine_version': {
'description': 'VM engine version',
@ -78,6 +84,7 @@ export_exclude_list = []
# Sort dictionary for human
sort_for_human = {
'cpu_count': 'CPU count',
'cpu_time': 'CPU time',
'memory_usage': 'memory consumption',
'load_1min': 'load',
'name': 'VM name',
@ -88,7 +95,7 @@ sort_for_human = {
class PluginModel(GlancesPluginModel):
"""Glances Vm plugin.
stats is a dict: {'version': {...}, 'vms': [{}, {}]}
stats is a dict: {'version': '', 'vms': [{}, {}]}
"""
def __init__(self, args=None, config=None):
@ -109,7 +116,10 @@ class PluginModel(GlancesPluginModel):
self.watchers: dict[str, VmsExtension] = {}
# Init the Multipass API
self.watchers['multipass'] = VmExtension()
self.watchers['multipass'] = MultipassVmExtension()
# Init the Virsh API
self.watchers['virsh'] = VirshVmExtension()
# Sort key
self.sort_key = None
@ -158,6 +168,15 @@ class PluginModel(GlancesPluginModel):
return self.get_init_value()
# Update stats
stats = self.update_local()
# Sort and update the stats
self.sort_key, self.stats = sort_vm_stats(stats)
return self.stats
@GlancesPluginModel._manage_rate
def update_local(self):
"""Update stats localy"""
stats = []
for engine, watcher in iteritems(self.watchers):
version, vms = watcher.update(all_tag=self._all_tag())
@ -165,11 +184,7 @@ class PluginModel(GlancesPluginModel):
vm["engine"] = engine
vm["engine_version"] = version
stats.extend(vms)
# Sort and update the stats
# TODO: test
self.sort_key, self.stats = sort_vm_stats(stats)
return self.stats
return stats
def update_views(self) -> bool:
"""Update stats views."""
@ -207,11 +222,12 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_add_line(msg))
if not self.views['show_engine_name']:
msg = f' (served by {self.stats[0].get("engine", "")})'
ret.append(self.curse_add_line(msg))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
# Header
ret.append(self.curse_new_line())
# Get the maximum VMs name
# Get the maximum VMs name length
# Max size is configurable. See feature request #1723.
name_max_width = min(
self.config.get_int_value('vms', 'max_name_size', default=20) if self.config is not None else 20,
@ -219,7 +235,9 @@ class PluginModel(GlancesPluginModel):
)
if self.views['show_engine_name']:
msg = ' {:{width}}'.format('Engine', width=8)
# Get the maximum engine length
engine_max_width = max(len(max(self.stats, key=lambda x: len(x['engine']))['engine']), 8)
msg = ' {:{width}}'.format('Engine', width=engine_max_width)
ret.append(self.curse_add_line(msg))
msg = ' {:{width}}'.format('Name', width=name_max_width)
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'name' else 'DEFAULT'))
@ -227,6 +245,8 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_add_line(msg))
msg = '{:>6}'.format('Core')
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'cpu_count' else 'DEFAULT'))
msg = '{:>6}'.format('CPU%')
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'cpu_time' else 'DEFAULT'))
msg = '{:>7}'.format('MEM')
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'memory_usage' else 'DEFAULT'))
msg = '/{:<7}'.format('MAX')
@ -240,7 +260,7 @@ class PluginModel(GlancesPluginModel):
for vm in self.stats:
ret.append(self.curse_new_line())
if self.views['show_engine_name']:
ret.append(self.curse_add_line(' {:{width}}'.format(vm["engine"], width=8)))
ret.append(self.curse_add_line(' {:{width}}'.format(vm["engine"], width=engine_max_width)))
# Name
ret.append(self.curse_add_line(' {:{width}}'.format(vm['name'][:name_max_width], width=name_max_width)))
# Status
@ -253,6 +273,16 @@ class PluginModel(GlancesPluginModel):
except (KeyError, TypeError):
msg = '{:>6}'.format('-')
ret.append(self.curse_add_line(msg, self.get_views(item=vm['name'], key='cpu_count', option='decoration')))
# CPU (time)
try:
msg = '{:>6}'.format(vm['cpu_time_rate_per_sec'])
except (KeyError, TypeError):
msg = '{:>6}'.format('-')
ret.append(
self.curse_add_line(
msg, self.get_views(item=vm['name'], key='cpu_time_rate_per_sec', option='decoration')
)
)
# MEM
try:
msg = '{:>7}'.format(self.auto_unit(vm['memory_usage']))
@ -297,13 +327,13 @@ def sort_vm_stats(stats: list[dict[str, Any]]) -> tuple[str, list[dict[str, Any]
# Make VM sort related to process sort
if glances_processes.sort_key == 'memory_percent':
sort_by = 'memory_usage'
sort_by_secondary = 'load_1min'
sort_by_secondary = 'cpu_time'
elif glances_processes.sort_key == 'name':
sort_by = 'name'
sort_by_secondary = 'load_1min'
else:
sort_by = 'load_1min'
sort_by_secondary = 'memory_usage'
sort_by = 'cpu_time'
sort_by_secondary = 'load_1min'
# Sort vm stats
sort_stats_processes(

View File

@ -10,9 +10,11 @@
import json
import os
from functools import cache
from typing import Any
from glances.globals import json_loads, nativestr
from glances.plugins.vms.engines import VmsExtension
from glances.secure import secure_popen
# Check if multipass binary exist
@ -20,10 +22,10 @@ from glances.secure import secure_popen
MULTIPASS_PATH = '/snap/bin/multipass'
MULTIPASS_VERSION_OPTIONS = 'version --format json'
MULTIPASS_INFO_OPTIONS = 'info --format json'
import_multipass_error_tag = not os.path.exists(MULTIPASS_PATH)
import_multipass_error_tag = not os.path.exists(MULTIPASS_PATH) and os.access(MULTIPASS_PATH, os.X_OK)
class VmExtension:
class VmExtension(VmsExtension):
"""Glances' Vms Plugin's Vm Extension unit"""
CONTAINER_ACTIVE_STATUS = ['running']
@ -31,6 +33,7 @@ class VmExtension:
def __init__(self):
self.ext_name = "Multipass (Vm)"
@cache
def update_version(self):
# > multipass version --format json
# {
@ -41,46 +44,11 @@ class VmExtension:
try:
ret = json_loads(ret_cmd)
except json.JSONDecodeError:
return {}
return ''
else:
return ret.get('multipass', None)
def update_info(self):
# > multipass info --format json
# {
# "errors": [
# ],
# "info": {
# "adapted-budgerigar": {
# "cpu_count": "1",
# "disks": {
# "sda1": {
# "total": "5116440064",
# "used": "2287162880"
# }
# },
# "image_hash": "182dc760bfca26c45fb4e4668049ecd4d0ecdd6171b3bae81d0135e8f1e9d93e",
# "image_release": "24.04 LTS",
# "ipv4": [
# "10.160.166.174"
# ],
# "load": [
# 0,
# 0.03,
# 0
# ],
# "memory": {
# "total": 1002500096,
# "used": 432058368
# },
# "mounts": {
# },
# "release": "Ubuntu 24.04 LTS",
# "snapshot_count": "0",
# "state": "Running"
# }
# }
# }
ret_cmd = secure_popen(f'{MULTIPASS_PATH} {MULTIPASS_INFO_OPTIONS}')
try:
ret = json_loads(ret_cmd)
@ -89,11 +57,11 @@ class VmExtension:
else:
return ret.get('info', {})
def update(self, all_tag) -> tuple[dict, list[dict]]:
def update(self, all_tag) -> tuple[str, list[dict]]:
"""Update Vm stats using the input method."""
# Can not run multipass on this system then...
if import_multipass_error_tag:
return {}, []
return '', []
# Get the stats from the system
version_stats = self.update_version()
@ -124,6 +92,7 @@ class VmExtension:
'status': vm_stats.get('state').lower() if vm_stats.get('state') else None,
'release': vm_stats.get('release') if vm_stats.get('release') else vm_stats.get('image_release'),
'cpu_count': int(vm_stats.get('cpu_count', 1)) if vm_stats.get('cpu_count', 1) else None,
'cpu_time': None, # Not available through the multipass CLI
'memory_usage': vm_stats.get('memory').get('used') if vm_stats.get('memory') else None,
'memory_total': vm_stats.get('memory').get('total') if vm_stats.get('memory') else None,
'load_1min': vm_stats.get('load')[0] if vm_stats.get('load') else None,

View File

@ -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
}

View File

@ -15,17 +15,16 @@ from time import time
last_update_times = {}
def getTimeSinceLastUpdate(IOType):
def getTimeSinceLastUpdate(key):
"""Return the elapsed time since last update."""
global last_update_times
# assert(IOType in ['net', 'disk', 'process_disk'])
current_time = time()
last_time = last_update_times.get(IOType)
last_time = last_update_times.get(key)
if not last_time:
time_since_update = 1
else:
time_since_update = current_time - last_time
last_update_times[IOType] = current_time
last_update_times[key] = current_time
return time_since_update