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 disable=True
# Define the maximum VMs size name (default is 20 chars) # Define the maximum VMs size name (default is 20 chars)
max_name_size=20 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 # Set the following key to True to display all VMs regarding their states
all=False all=False

View File

@ -41,6 +41,7 @@ Legend:
hddtemp hddtemp
ps ps
containers containers
vms
amps amps
events events
actions 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 Show/hide RAID plugin
``s`` ``s``
Show/hide sensors stats Show/hide sensors plugin
``S`` ``S``
Enable/disable spark lines Enable/disable spark lines
@ -369,6 +369,9 @@ The following commands (key pressed) are supported while in Glances:
``U`` ``U``
View cumulative network I/O View cumulative network I/O
``V``
Show/hide VMS plugin
``w`` ``w``
Delete finished warning log messages 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]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .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 .SH NAME
glances \- An eye on your system glances \- An eye on your system
.SH SYNOPSIS .SH SYNOPSIS
@ -458,7 +458,7 @@ Reset history
Show/hide RAID plugin Show/hide RAID plugin
.TP .TP
.B \fBs\fP .B \fBs\fP
Show/hide sensors stats Show/hide sensors plugin
.TP .TP
.B \fBS\fP .B \fBS\fP
Enable/disable spark lines Enable/disable spark lines
@ -475,6 +475,9 @@ Sort processes by USER
.B \fBU\fP .B \fBU\fP
View cumulative network I/O View cumulative network I/O
.TP .TP
.B \fBV\fP
Show/hide VMS plugin
.TP
.B \fBw\fP .B \fBw\fP
Delete finished warning log messages Delete finished warning log messages
.TP .TP

View File

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

View File

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

View File

@ -104,6 +104,11 @@ export function nl2br(input) {
} }
export function number(value, options) { 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( return new Intl.NumberFormat(
"en-US", "en-US",
typeof options === 'number' ? { maximumFractionDigits: options } : options typeof options === 'number' ? { maximumFractionDigits: options } : options

View File

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

View File

@ -5,7 +5,7 @@
"favico.js": "^0.3.10", "favico.js": "^0.3.10",
"hotkeys-js": "^3.13.9", "hotkeys-js": "^3.13.9",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"sanitize-html": "^2.15.0", "sanitize-html": "^2.16.0",
"vue": "^3.5.13" "vue": "^3.5.13"
}, },
"devDependencies": { "devDependencies": {
@ -13,7 +13,7 @@
"copy-webpack-plugin": "^13.0.0", "copy-webpack-plugin": "^13.0.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"del": "^8.0.0", "del": "^8.0.0",
"eslint": "^9.24.0", "eslint": "^9.25.0",
"eslint-config-prettier": "^10.1.2", "eslint-config-prettier": "^10.1.2",
"eslint-plugin-vue": "^10.0.0", "eslint-plugin-vue": "^10.0.0",
"globals": "^16.0.0", "globals": "^16.0.0",
@ -23,10 +23,10 @@
"sass": "^1.86.3", "sass": "^1.86.3",
"sass-loader": "^16.0.5", "sass-loader": "^16.0.5",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"typescript-eslint": "^8.29.1", "typescript-eslint": "^8.30.1",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-loader": "^17.4.2", "vue-loader": "^17.4.2",
"webpack": "^5.99.5", "webpack": "^5.99.6",
"webpack-cli": "^6.0.1", "webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.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: else:
item_views = self.views[item] item_views = self.views[item]
if key is None or key not in item_views: if key is None:
return item_views return item_views
if key not in item_views:
return 'DEFAULT'
if option is None: if option is None:
return item_views[key] return item_views[key]
if option in 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.logger import logger
from glances.plugins.plugin.model import GlancesPluginModel from glances.plugins.plugin.model import GlancesPluginModel
from glances.plugins.vms.engines import VmsExtension 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 glances_processes
from glances.processes import sort_stats as sort_stats_processes from glances.processes import sort_stats as sort_stats_processes
@ -41,6 +42,11 @@ fields_description = {
'cpu_count': { 'cpu_count': {
'description': 'Vm CPU count', 'description': 'Vm CPU count',
}, },
'cpu_time': {
'description': 'Vm CPU time',
'rate': True,
'unit': 'percent',
},
'memory_usage': { 'memory_usage': {
'description': 'Vm memory usage', 'description': 'Vm memory usage',
'unit': 'byte', 'unit': 'byte',
@ -62,7 +68,7 @@ fields_description = {
'description': 'Vm IP v4 address', 'description': 'Vm IP v4 address',
}, },
'engine': { 'engine': {
'description': 'VM engine name (only Mutlipass is currently supported)', 'description': 'VM engine name',
}, },
'engine_version': { 'engine_version': {
'description': 'VM engine version', 'description': 'VM engine version',
@ -78,6 +84,7 @@ export_exclude_list = []
# Sort dictionary for human # Sort dictionary for human
sort_for_human = { sort_for_human = {
'cpu_count': 'CPU count', 'cpu_count': 'CPU count',
'cpu_time': 'CPU time',
'memory_usage': 'memory consumption', 'memory_usage': 'memory consumption',
'load_1min': 'load', 'load_1min': 'load',
'name': 'VM name', 'name': 'VM name',
@ -88,7 +95,7 @@ sort_for_human = {
class PluginModel(GlancesPluginModel): class PluginModel(GlancesPluginModel):
"""Glances Vm plugin. """Glances Vm plugin.
stats is a dict: {'version': {...}, 'vms': [{}, {}]} stats is a dict: {'version': '', 'vms': [{}, {}]}
""" """
def __init__(self, args=None, config=None): def __init__(self, args=None, config=None):
@ -109,7 +116,10 @@ class PluginModel(GlancesPluginModel):
self.watchers: dict[str, VmsExtension] = {} self.watchers: dict[str, VmsExtension] = {}
# Init the Multipass API # Init the Multipass API
self.watchers['multipass'] = VmExtension() self.watchers['multipass'] = MultipassVmExtension()
# Init the Virsh API
self.watchers['virsh'] = VirshVmExtension()
# Sort key # Sort key
self.sort_key = None self.sort_key = None
@ -158,6 +168,15 @@ class PluginModel(GlancesPluginModel):
return self.get_init_value() return self.get_init_value()
# Update stats # 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 = [] stats = []
for engine, watcher in iteritems(self.watchers): for engine, watcher in iteritems(self.watchers):
version, vms = watcher.update(all_tag=self._all_tag()) version, vms = watcher.update(all_tag=self._all_tag())
@ -165,11 +184,7 @@ class PluginModel(GlancesPluginModel):
vm["engine"] = engine vm["engine"] = engine
vm["engine_version"] = version vm["engine_version"] = version
stats.extend(vms) stats.extend(vms)
return stats
# Sort and update the stats
# TODO: test
self.sort_key, self.stats = sort_vm_stats(stats)
return self.stats
def update_views(self) -> bool: def update_views(self) -> bool:
"""Update stats views.""" """Update stats views."""
@ -207,11 +222,12 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_add_line(msg)) ret.append(self.curse_add_line(msg))
if not self.views['show_engine_name']: if not self.views['show_engine_name']:
msg = f' (served by {self.stats[0].get("engine", "")})' 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()) ret.append(self.curse_new_line())
# Header # Header
ret.append(self.curse_new_line()) 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. # Max size is configurable. See feature request #1723.
name_max_width = min( name_max_width = min(
self.config.get_int_value('vms', 'max_name_size', default=20) if self.config is not None else 20, 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']: 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)) ret.append(self.curse_add_line(msg))
msg = ' {:{width}}'.format('Name', width=name_max_width) msg = ' {:{width}}'.format('Name', width=name_max_width)
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'name' else 'DEFAULT')) 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)) ret.append(self.curse_add_line(msg))
msg = '{:>6}'.format('Core') msg = '{:>6}'.format('Core')
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'cpu_count' else 'DEFAULT')) 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') msg = '{:>7}'.format('MEM')
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'memory_usage' else 'DEFAULT')) ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'memory_usage' else 'DEFAULT'))
msg = '/{:<7}'.format('MAX') msg = '/{:<7}'.format('MAX')
@ -240,7 +260,7 @@ class PluginModel(GlancesPluginModel):
for vm in self.stats: for vm in self.stats:
ret.append(self.curse_new_line()) ret.append(self.curse_new_line())
if self.views['show_engine_name']: 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 # Name
ret.append(self.curse_add_line(' {:{width}}'.format(vm['name'][:name_max_width], width=name_max_width))) ret.append(self.curse_add_line(' {:{width}}'.format(vm['name'][:name_max_width], width=name_max_width)))
# Status # Status
@ -253,6 +273,16 @@ class PluginModel(GlancesPluginModel):
except (KeyError, TypeError): except (KeyError, TypeError):
msg = '{:>6}'.format('-') msg = '{:>6}'.format('-')
ret.append(self.curse_add_line(msg, self.get_views(item=vm['name'], key='cpu_count', option='decoration'))) 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 # MEM
try: try:
msg = '{:>7}'.format(self.auto_unit(vm['memory_usage'])) 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 # Make VM sort related to process sort
if glances_processes.sort_key == 'memory_percent': if glances_processes.sort_key == 'memory_percent':
sort_by = 'memory_usage' sort_by = 'memory_usage'
sort_by_secondary = 'load_1min' sort_by_secondary = 'cpu_time'
elif glances_processes.sort_key == 'name': elif glances_processes.sort_key == 'name':
sort_by = 'name' sort_by = 'name'
sort_by_secondary = 'load_1min' sort_by_secondary = 'load_1min'
else: else:
sort_by = 'load_1min' sort_by = 'cpu_time'
sort_by_secondary = 'memory_usage' sort_by_secondary = 'load_1min'
# Sort vm stats # Sort vm stats
sort_stats_processes( sort_stats_processes(

View File

@ -10,9 +10,11 @@
import json import json
import os import os
from functools import cache
from typing import Any from typing import Any
from glances.globals import json_loads, nativestr from glances.globals import json_loads, nativestr
from glances.plugins.vms.engines import VmsExtension
from glances.secure import secure_popen from glances.secure import secure_popen
# Check if multipass binary exist # Check if multipass binary exist
@ -20,10 +22,10 @@ from glances.secure import secure_popen
MULTIPASS_PATH = '/snap/bin/multipass' MULTIPASS_PATH = '/snap/bin/multipass'
MULTIPASS_VERSION_OPTIONS = 'version --format json' MULTIPASS_VERSION_OPTIONS = 'version --format json'
MULTIPASS_INFO_OPTIONS = 'info --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""" """Glances' Vms Plugin's Vm Extension unit"""
CONTAINER_ACTIVE_STATUS = ['running'] CONTAINER_ACTIVE_STATUS = ['running']
@ -31,6 +33,7 @@ class VmExtension:
def __init__(self): def __init__(self):
self.ext_name = "Multipass (Vm)" self.ext_name = "Multipass (Vm)"
@cache
def update_version(self): def update_version(self):
# > multipass version --format json # > multipass version --format json
# { # {
@ -41,46 +44,11 @@ class VmExtension:
try: try:
ret = json_loads(ret_cmd) ret = json_loads(ret_cmd)
except json.JSONDecodeError: except json.JSONDecodeError:
return {} return ''
else: else:
return ret.get('multipass', None) return ret.get('multipass', None)
def update_info(self): 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}') ret_cmd = secure_popen(f'{MULTIPASS_PATH} {MULTIPASS_INFO_OPTIONS}')
try: try:
ret = json_loads(ret_cmd) ret = json_loads(ret_cmd)
@ -89,11 +57,11 @@ class VmExtension:
else: else:
return ret.get('info', {}) 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.""" """Update Vm stats using the input method."""
# Can not run multipass on this system then... # Can not run multipass on this system then...
if import_multipass_error_tag: if import_multipass_error_tag:
return {}, [] return '', []
# Get the stats from the system # Get the stats from the system
version_stats = self.update_version() version_stats = self.update_version()
@ -124,6 +92,7 @@ class VmExtension:
'status': vm_stats.get('state').lower() if vm_stats.get('state') else None, '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'), '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_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_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, '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, '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 = {} last_update_times = {}
def getTimeSinceLastUpdate(IOType): def getTimeSinceLastUpdate(key):
"""Return the elapsed time since last update.""" """Return the elapsed time since last update."""
global last_update_times global last_update_times
# assert(IOType in ['net', 'disk', 'process_disk'])
current_time = time() current_time = time()
last_time = last_update_times.get(IOType) last_time = last_update_times.get(key)
if not last_time: if not last_time:
time_since_update = 1 time_since_update = 1
else: else:
time_since_update = current_time - last_time time_since_update = current_time - last_time
last_update_times[IOType] = current_time last_update_times[key] = current_time
return time_since_update return time_since_update