Add stats about running VMS (qemu/libvirt/kvm support through virsh) #1531

This commit is contained in:
nicolargo 2025-04-21 10:39:20 +02:00
parent 50c440f739
commit fb81a2a5f7
16 changed files with 883 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

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

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