WebUI is OK, had to manage the error when Glances stop, see Github issue #2252 for detail

This commit is contained in:
nicolargo 2024-07-24 11:32:57 +02:00
parent 9d76bfeb3e
commit 5ed876423f
8 changed files with 228 additions and 66 deletions

View File

@ -144,6 +144,11 @@ body {
#processlist-plugin .table-cell {
padding: 0px 5px 0px 5px;
white-space: nowrap;
#vms-plugin .table-cell {
padding: 0px 10px 0px 10px;
white-space: nowrap;
}
}
#containers-plugin .table-cell {
padding: 0px 10px 0px 10px;

View File

@ -79,6 +79,10 @@
</div>
</div>
<div class="col-sm-18">
<glances-plugin-vms
v-if="!args.disable_vms"
:data="data"
></glances-plugin-vms>
<glances-plugin-containers
v-if="!args.disable_containers"
:data="data"
@ -126,6 +130,7 @@ import GlancesPluginSmart from './components/plugin-smart.vue';
import GlancesPluginSensors from './components/plugin-sensors.vue';
import GlancesPluginSystem from './components/plugin-system.vue';
import GlancesPluginUptime from './components/plugin-uptime.vue';
import GlancesPluginVms from './components/plugin-vms.vue';
import GlancesPluginWifi from './components/plugin-wifi.vue';
import uiconfig from './uiconfig.json';
@ -159,6 +164,7 @@ export default {
GlancesPluginSmart,
GlancesPluginSystem,
GlancesPluginUptime,
GlancesPluginVms,
GlancesPluginWifi
},
data() {
@ -374,6 +380,11 @@ export default {
this.store.args.disable_ports = !this.store.args.disable_ports;
});
// V => Enable/disable VMs stats
hotkeys('shift+V', () => {
this.store.args.disable_vms = !this.store.args.disable_vms;
});
// 'W' > Enable/Disable Wifi plugin
hotkeys('shift+W', () => {
this.store.args.disable_wifi = !this.store.args.disable_wifi;

View File

@ -60,6 +60,9 @@
<div class="divTableCell">
{{ help.sort_io_rate }}
</div>
<div class="divTableCell">
{{ help.show_hide_vms }}
</div>
<div class="divTableCell">
{{ help.show_hide_containers }}
</div>

View File

@ -0,0 +1,158 @@
<template>
<section id="vms-plugin" class="plugin" v-if="vms.length">
<span class="title">VMs</span>
{{ vms.length }} sorted by {{ sorter.getColumnLabel(sorter.column) }}
<div class="table">
<div class="table-row">
<div class="table-cell text-left" v-show="showEngine">Engine</div>
<div
class="table-cell text-left"
:class="['sortable', sorter.column === 'name' && 'sort']"
@click="args.sort_processes_key = 'name'"
>
Name
</div>
<div class="table-cell">Status</div>
<div class="table-cell">Core</div>
<div
class="table-cell"
:class="['sortable', sorter.column === 'memory_usage' && 'sort']"
@click="args.sort_processes_key = 'memory_usage'"
>
MEM
</div>
<div class="table-cell text-left">/MAX</div>
<div
class="table-cell"
:class="['sortable', sorter.column === 'load_1min' && 'sort']"
@click="args.sort_processes_key = 'load_1min'"
>
LOAD 1/5/15min
</div>
<div class="table-cell text-right">Release</div>
</div>
<div
class="table-row"
v-for="(vm, vmId) in vms"
:key="vmId"
>
<div class="table-cell text-left" v-show="showEngine">{{ vm.engine }}</div>
<div class="table-cell text-left">{{ vm.name }}</div>
<div class="table-cell" :class="vm.status == 'stopped' ? 'careful' : 'ok'">
{{ vm.status }}
</div>
<div class="table-cell">
{{ $filters.number(vm.cpu_count, 1) }}
</div>
<div class="table-cell">
{{ $filters.bytes(vm.memory_usage) }}
</div>
<div class="table-cell text-left">
/{{ $filters.bytes(vm.memory_total) }}
</div>
<div class="table-cell">
{{ $filters.number(vm.load_1min) }}/{{ $filters.number(vm.load_5min) }}/{{ $filters.number(vm.load_15min) }}
</div>
<div class="table-cell text-right">
{{ vm.release }}
</div>
</div>
</div>
</section>
</template>
<script>
import { orderBy } from 'lodash';
import { store } from '../store.js';
export default {
props: {
data: {
type: Object
}
},
data() {
return {
store,
sorter: undefined
};
},
computed: {
args() {
return this.store.args || {};
},
sortProcessesKey() {
return this.args.sort_processes_key;
},
stats() {
return this.data.stats['vms'];
},
views() {
return this.data.views['vms'];
},
vms() {
const { sorter } = this;
const vms = (this.stats || []).map(
(vmData) => {
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 : '?',
'release': vmData.release,
'image': vmData.image,
'engine': vmData.engine,
'engine_version': vmData.engine_version,
};
}
);
return orderBy(
vms,
[sorter.column].reduce((retval, col) => {
if (col === 'memory_usage') {
col = ['memory_usage'];
}
return retval.concat(col);
}, []),
[sorter.isReverseColumn(sorter.column) ? 'desc' : 'asc']
);
},
showEngine() {
return this.views.show_engine_name;
},
},
watch: {
sortProcessesKey: {
immediate: true,
handler(sortProcessesKey) {
const sortable = ['load_1min', 'memory_usage', 'name'];
function isReverseColumn(column) {
return !['name'].includes(column);
}
function getColumnLabel(value) {
const labels = {
load_1min: 'load',
memory_usage: 'memory consumption',
name: 'VM name',
None: 'None'
};
return labels[value] || value;
}
if (!sortProcessesKey || sortable.includes(sortProcessesKey)) {
this.sorter = {
column: this.args.sort_processes_key || 'load_1min',
auto: !this.args.sort_processes_key,
isReverseColumn,
getColumnLabel
};
}
}
}
}
};
</script>

File diff suppressed because one or more lines are too long

View File

@ -227,7 +227,7 @@ class PluginModel(GlancesPluginModel):
for engine, watcher in iteritems(self.watchers):
version, containers = watcher.update(all_tag=self._all_tag())
for container in containers:
container["engine"] = 'docker'
container["engine"] = engine
stats.extend(containers)
# Sort and update the stats
@ -307,6 +307,9 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_add_line(msg))
msg = f' sorted by {sort_for_human[self.sort_key]}'
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_new_line())
# Header
ret.append(self.curse_new_line())
@ -430,25 +433,29 @@ class PluginModel(GlancesPluginModel):
@staticmethod
def container_alert(status: str) -> str:
"""Analyse the container status."""
"""Analyse the container status.
One of created, restarting, running, removing, paused, exited, or dead
"""
if status == 'running':
return 'OK'
if status == 'exited':
return 'WARNING'
if status == 'dead':
return 'CRITICAL'
return 'CAREFUL'
return 'ERROR'
if status in ['created', 'restarting', 'exited']:
return 'WARNING'
return 'INFO'
def sort_docker_stats(stats: List[Dict[str, Any]]) -> Tuple[str, List[Dict[str, Any]]]:
# Sort Docker stats using the same function than processes
sort_by = glances_processes.sort_key
sort_by_secondary = 'memory_usage'
if sort_by == 'memory_percent':
# Make VM sort related to process sort
if glances_processes.sort_key == 'memory_percent':
sort_by = 'memory_usage'
sort_by_secondary = 'cpu_percent'
elif sort_by in ['username', 'io_counters', 'cpu_times']:
elif glances_processes.sort_key == 'name':
sort_by = 'name'
sort_by_secondary = 'cpu_percent'
else:
sort_by = 'cpu_percent'
sort_by_secondary = 'memory_usage'
# Sort docker stats
sort_stats_processes(

View File

@ -61,6 +61,12 @@ fields_description = {
'ipv4': {
'description': 'Vm IP v4 address',
},
'engine': {
'description': 'VM engine name (only Mutlipass is currently supported)',
},
'engine_version': {
'description': 'VM engine version',
},
}
# Define the items history list (list of items to add to history)
@ -73,7 +79,8 @@ export_exclude_list = []
sort_for_human = {
'cpu_count': 'CPU count',
'memory_usage': 'memory consumption',
'name': 'vm name',
'load_1min': 'load',
'name': 'VM name',
None: 'None',
}
@ -155,9 +162,9 @@ class PluginModel(GlancesPluginModel):
stats = []
for engine, watcher in iteritems(self.watchers):
version, vms = watcher.update(all_tag=self._all_tag())
# print(engine, version, vms)
for vm in vms:
vm["engine"] = 'vm'
vm["engine"] = engine
vm["engine_version"] = version
stats.extend(vms)
# Sort and update the stats
@ -173,36 +180,6 @@ class PluginModel(GlancesPluginModel):
if not self.stats:
return False
# Add specifics information
# Alert
# TODO
# for i in self.stats:
# # Init the views for the current vm (key = vm name)
# self.views[i[self.get_key()]] = {'cpu': {}, 'mem': {}}
# # CPU alert
# if 'cpu' in i and 'total' in i['cpu']:
# # Looking for specific CPU vm threshold in the conf file
# alert = self.get_alert(i['cpu']['total'], header=i['name'] + '_cpu', action_key=i['name'])
# if alert == 'DEFAULT':
# # Not found ? Get back to default CPU threshold value
# alert = self.get_alert(i['cpu']['total'], header='cpu')
# self.views[i[self.get_key()]]['cpu']['decoration'] = alert
# # MEM alert
# if 'memory' in i and 'usage' in i['memory']:
# # Looking for specific MEM vm threshold in the conf file
# alert = self.get_alert(
# self.memory_usage_no_cache(i['memory']),
# maximum=i['memory']['limit'],
# header=i['name'] + '_mem',
# action_key=i['name'],
# )
# if alert == 'DEFAULT':
# # Not found ? Get back to default MEM threshold value
# alert = self.get_alert(
# self.memory_usage_no_cache(i['memory']), maximum=i['memory']['limit'], header='mem'
# )
# self.views[i[self.get_key()]]['mem']['decoration'] = alert
# Display Engine ?
show_engine_name = False
if len({ct["engine"] for ct in self.stats}) > 1:
@ -228,6 +205,9 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_add_line(msg))
msg = f' sorted by {sort_for_human[self.sort_key]}'
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_new_line())
# Header
ret.append(self.curse_new_line())
@ -239,13 +219,13 @@ class PluginModel(GlancesPluginModel):
)
if self.views['show_engine_name']:
msg = ' {:{width}}'.format('Engine', width=6)
msg = ' {:{width}}'.format('Engine', width=8)
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'))
msg = '{:>10}'.format('Status')
ret.append(self.curse_add_line(msg))
msg = '{:>6}'.format('CPU')
msg = '{:>6}'.format('Core')
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'cpu_count' else 'DEFAULT'))
msg = '{:>7}'.format('MEM')
ret.append(self.curse_add_line(msg, 'SORT' if self.sort_key == 'memory_usage' else 'DEFAULT'))
@ -260,7 +240,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=6)))
ret.append(self.curse_add_line(' {:{width}}'.format(vm["engine"], width=8)))
# Name
ret.append(self.curse_add_line(' {:{width}}'.format(vm['name'][:name_max_width], width=name_max_width)))
# Status
@ -269,7 +249,7 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_add_line(msg, status))
# CPU (count)
try:
msg = '{:>6.1f}'.format(vm['cpu_count'])
msg = '{:>6}'.format(vm['cpu_count'])
except (KeyError, TypeError):
msg = '{:>6}'.format('-')
ret.append(self.curse_add_line(msg, self.get_views(item=vm['name'], key='cpu_count', option='decoration')))
@ -309,23 +289,20 @@ class PluginModel(GlancesPluginModel):
if status == 'running':
return 'OK'
if status in ['starting', 'restarting', 'delayed shutdown']:
return 'INFO'
if status in ['stopped', 'deleted', 'suspending', 'suspended']:
return 'CRITICAL'
return 'CAREFUL'
return 'WARNING'
return 'INFO'
def sort_vm_stats(stats: List[Dict[str, Any]]) -> Tuple[str, List[Dict[str, Any]]]:
# Sort Vm stats using the same function than processes
sort_by = glances_processes.sort_key
if sort_by == 'cpu_percent':
sort_by = 'cpu_count'
sort_by_secondary = 'memory_usage'
elif sort_by == 'memory_percent':
# Make VM sort related to process sort
if glances_processes.sort_key == 'memory_percent':
sort_by = 'memory_usage'
sort_by_secondary = 'cpu_count'
elif sort_by in ['username', 'io_counters', 'cpu_times']:
sort_by = 'cpu_count'
sort_by_secondary = 'load_1min'
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 vm stats

View File

@ -41,7 +41,8 @@ class VmExtension:
# "multipass": "1.13.1",
# "multipassd": "1.13.1"
# }
return orjson.loads(secure_popen(f'{MULTIPASS_PATH} {MULTIPASS_VERSION_OPTIONS}'))
ret = orjson.loads(secure_popen(f'{MULTIPASS_PATH} {MULTIPASS_VERSION_OPTIONS}'))
return ret.get('multipass', None)
def update_info(self):
# > multipass info --format json