Thread mode ('j' hotkey) is not taken into accound in the WebUI #3019

This commit is contained in:
nicolargo 2024-11-17 15:31:10 +01:00
parent fa57d5a5ab
commit a7d5b1e869
12 changed files with 617 additions and 345 deletions

View File

@ -123,6 +123,7 @@ Columns display
The non-swapped physical memory a process is
using (what's currently in the physical memory).
``PID`` Process ID (column is replaced by NPROCS in accumulated mode)
``NPROCS`` Number of process + childs (only in accumulated mode)
``USER`` User ID
``THR`` Threads number of the process
``TIME+`` Cumulative CPU time used by the process

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,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" "Nov 16, 2024" "4.3.0_dev03" "Glances"
.TH "GLANCES" "1" "Nov 17, 2024" "4.3.0_dev04" "Glances"
.SH NAME
glances \- An eye on your system
.SH SYNOPSIS

View File

@ -616,8 +616,8 @@ Examples of use:
args.network_sum = False
args.network_cumul = False
# Processlist id updated in processcount
if getattr(args, 'enable_processlist', False):
# Processlist is updated in processcount
if getattr(args, 'enable_processlist', False) or getattr(args, 'enable_programlist', False):
enable(args, 'processcount')
# Set a default export_process_filter (with all process) when using the stdout mode

View File

@ -124,8 +124,8 @@ class _GlancesCurses:
_left_sidebar_min_width = 23
_left_sidebar_max_width = 34
# Define right sidebar
_right_sidebar = ['vms', 'containers', 'processcount', 'amps', 'processlist', 'alert']
# Define right sidebar in a method because it depends of self.args.programs
# See def _right_sidebar method
def __init__(self, config=None, args=None):
# Init
@ -204,6 +204,16 @@ class _GlancesCurses:
# Set the left sidebar list
self._left_sidebar = config.get_list_value('outputs', 'left_menu', default=self._left_sidebar)
def _right_sidebar(self):
return [
'vms',
'containers',
'processcount',
'amps',
'programlist' if self.args.programs else 'processlist',
'alert',
]
def _init_history(self):
"""Init the history option."""
@ -790,7 +800,7 @@ class _GlancesCurses:
# Display right sidebar
self.new_column()
for p in self._right_sidebar:
for p in self._right_sidebar():
if (hasattr(self.args, 'enable_' + p) or hasattr(self.args, 'disable_' + p)) and p in stat_display:
self.new_line()
if p == 'processlist':

View File

@ -252,10 +252,9 @@ export default {
});
// j => Accumulate processes by program
// Disable for the moment, see https://github.com/nicolargo/glances/issues/3019
// hotkeys('j', () => {
// this.store.args.programs = !this.store.args.programs;
// });
hotkeys('j', () => {
this.store.args.programs = !this.store.args.programs;
});
// k => Show/hide connections stats
hotkeys('k', () => {

View File

@ -1,6 +1,7 @@
<template>
<section class="plugin" id="processlist">
<table class="table table-sm table-borderless table-striped table-hover">
<section class="plugin" id="processlist" v-if="!args.programs">
<!-- Display processes -->
<table class=" table table-sm table-borderless table-striped table-hover">
<thead>
<tr>
<td scope="col" :class="['sortable', sorter.column === 'cpu_percent' && 'sort']"
@ -40,12 +41,12 @@
<td scope="row" v-show="!getDisableStats().includes('nice')">NI</td>
<td scope="row" class="table-cell widtd-60" v-show="!getDisableStats().includes('status')">S
</td>
<td scope="row" v-show="ioReadWritePresent && !getDisableStats().includes('io_counters')"
<td scope="row" v-show="ioReadWritePresentProcesses && !getDisableStats().includes('io_counters')"
class="hidden-xs hidden-sm" :class="['sortable', sorter.column === 'io_counters' && 'sort']"
@click="$emit('update:sorter', 'io_counters')">
IOR/s
</td>
<td scope="row" v-show="ioReadWritePresent && !getDisableStats().includes('io_counters')"
<td scope="row" v-show="ioReadWritePresentProcesses && !getDisableStats().includes('io_counters')"
class="text-start hidden-xs hidden-sm"
:class="['sortable', sorter.column === 'io_counters' && 'sort']"
@click="$emit('update:sorter', 'io_counters')">
@ -102,11 +103,133 @@
{{ process.status }}
</td>
<td scope="row" class="hidden-xs hidden-sm"
v-show="ioReadWritePresent && !getDisableStats().includes('io_counters')">
v-show="ioReadWritePresentProcesses && !getDisableStats().includes('io_counters')">
{{ $filters.bytes(process.io_read) }}
</td>
<td scope="row" class="hidden-xs hidden-sm"
v-show="ioReadWritePresent && !getDisableStats().includes('io_counters')">
v-show="ioReadWritePresentProcesses && !getDisableStats().includes('io_counters')">
{{ $filters.bytes(process.io_write) }}
</td>
<td scope="row" class="text-truncate"
v-show="args.process_short_name && !getDisableStats().includes('cmdline')">
{{ process.name }}
</td>
<td scope="row" v-show="!args.process_short_name && !getDisableStats().includes('cmdline')">
{{ process.cmdline }}
</td>
</tr>
</tbody>
</table>
</section>
<section class="plugin" id="processlist" v-if="args.programs">
<!-- Display programs -->
<table class=" table table-sm table-borderless table-striped table-hover">
<thead>
<tr>
<td scope="col" :class="['sortable', sorter.column === 'cpu_percent' && 'sort']"
@click="$emit('update:sorter', 'cpu_percent')"
v-show="!getDisableStats().includes('cpu_percent')">
CPU%
</td>
<td scope="col" :class="['sortable', sorter.column === 'memory_percent' && 'sort']"
@click="$emit('update:sorter', 'memory_percent')"
v-show="!getDisableStats().includes('memory_percent')">
MEM%
</td>
<td scope="col" class="hidden-xs hidden-sm" v-show="!getDisableStats().includes('memory_info')">
VIRT
</td>
<td scope="col" class="hidden-xs hidden-sm" v-show="!getDisableStats().includes('memory_info')">
RES
</td>
<td scope="col" v-show="!getDisableStats().includes('nprocs')">
NPROCS
</td>
<td scope="row" :class="['sortable', sorter.column === 'username' && 'sort']"
@click="$emit('update:sorter', 'username')" v-show="!getDisableStats().includes('username')">
USER
</td>
<td scope="row" class="hidden-xs hidden-sm"
:class="['sortable', sorter.column === 'timemillis' && 'sort']"
@click="$emit('update:sorter', 'timemillis')" v-show="!getDisableStats().includes('cpu_times')">
TIME+
</td>
<td scope="row" class="hidden-xs hidden-sm"
:class="['sortable', sorter.column === 'num_threads' && 'sort']"
@click="$emit('update:sorter', 'num_threads')"
v-show="!getDisableStats().includes('num_threads')">
THR
</td>
<td scope="row" v-show="!getDisableStats().includes('nice')">NI</td>
<td scope="row" class="table-cell widtd-60" v-show="!getDisableStats().includes('status')">S
</td>
<td scope="row" v-show="ioReadWritePresentPrograms && !getDisableStats().includes('io_counters')"
class="hidden-xs hidden-sm" :class="['sortable', sorter.column === 'io_counters' && 'sort']"
@click="$emit('update:sorter', 'io_counters')">
IOR/s
</td>
<td scope="row" v-show="ioReadWritePresentPrograms && !getDisableStats().includes('io_counters')"
class="text-start hidden-xs hidden-sm"
:class="['sortable', sorter.column === 'io_counters' && 'sort']"
@click="$emit('update:sorter', 'io_counters')">
IOW/s
</td>
<td scope="row" :class="['sortable', sorter.column === 'name' && 'sort']"
@click="$emit('update:sorter', 'name')" v-show="!getDisableStats().includes('cmdline')">
Command
</td>
</tr>
</thead>
<tbody>
<tr v-for="(process, processId) in programs" :key="processId">
<td scope="row" :class="getCpuPercentAlert(process)"
v-show="!getDisableStats().includes('cpu_percent')">
{{ process.cpu_percent == -1 ? '?' : $filters.number(process.cpu_percent, 1) }}
</td>
<td scope="row" :class="getMemoryPercentAlert(process)"
v-show="!getDisableStats().includes('memory_percent')">
{{ process.memory_percent == -1 ? '?' : $filters.number(process.memory_percent, 1) }}
</td>
<td scope="row" v-show="!getDisableStats().includes('memory_info')">
{{ $filters.bytes(process.memvirt) }}
</td>
<td scope="row" v-show="!getDisableStats().includes('memory_info')">
{{ $filters.bytes(process.memres) }}
</td>
<td scope="row" v-show="!getDisableStats().includes('nprocs')">
{{ process.nprocs }}
</td>
<td scope="row" v-show="!getDisableStats().includes('username')">
{{ process.username }}
</td>
<td scope="row" class="hidden-xs hidden-sm" v-if="process.timeplus != '?'"
v-show="!getDisableStats().includes('cpu_times')">
<span v-show="process.timeplus.hours > 0" class="highlight">{{ process.timeplus.hours }}h</span>
{{ $filters.leftPad(process.timeplus.minutes, 2, '0') }}:{{
$filters.leftPad(process.timeplus.seconds,
2, '0') }}
<span v-show="process.timeplus.hours <= 0">.{{ $filters.leftPad(process.timeplus.milliseconds,
2, '0')
}}</span>
</td>
<td scope="row" class="hidden-xs hidden-sm" v-if="process.timeplus == '?'"
v-show="!getDisableStats().includes('cpu_times')">?</td>
<td scope="row" class="hidden-xs hidden-sm" v-show="!getDisableStats().includes('num_threads')">
{{ process.num_threads == -1 ? '?' : process.num_threads }}
</td>
<td scope="row" :class="{ nice: process.isNice }" v-show="!getDisableStats().includes('nice')">
{{ $filters.exclamation(process.nice) }}
</td>
<td scope="row" :class="{ status: process.status == 'R' }"
v-show="!getDisableStats().includes('status')">
{{ process.status }}
</td>
<td scope="row" class="hidden-xs hidden-sm"
v-show="ioReadWritePresentPrograms && !getDisableStats().includes('io_counters')">
{{ $filters.bytes(process.io_read) }}
</td>
<td scope="row" class="hidden-xs hidden-sm"
v-show="ioReadWritePresentPrograms && !getDisableStats().includes('io_counters')">
{{ $filters.bytes(process.io_write) }}
</td>
<td scope="row" class="text-truncate"
@ -149,13 +272,13 @@ export default {
config() {
return this.store.config || {};
},
stats() {
stats_processlist() {
return this.data.stats['processlist'];
},
processes() {
const { sorter } = this;
const isWindows = this.data.stats['isWindows'];
const processes = (this.stats || []).map((process) => {
const processes = (this.stats_processlist || []).map((process) => {
process.memvirt = '?';
process.memres = '?';
if (process.memory_info) {
@ -224,8 +347,86 @@ export default {
[sorter.isReverseColumn(sorter.column) ? 'desc' : 'asc']
).slice(0, this.limit);
},
ioReadWritePresent() {
return (this.stats || []).some(({ io_counters }) => io_counters);
ioReadWritePresentProcesses() {
return (this.stats_processlist || []).some(({ io_counters }) => io_counters);
},
stats_programlist() {
return this.data.stats['programlist'];
},
programs() {
const { sorter } = this;
const isWindows = this.data.stats['isWindows'];
const programs = (this.stats_programlist || []).map((process) => {
process.memvirt = '?';
process.memres = '?';
if (process.memory_info) {
process.memvirt = process.memory_info.vms;
process.memres = process.memory_info.rss;
}
if (isWindows && process.username !== null) {
process.username = last(process.username.split('\\'));
}
process.timeplus = '?';
process.timemillis = '?';
if (process.cpu_times) {
process.timeplus = timedelta(process.cpu_times);
process.timemillis = timemillis(process.cpu_times);
}
if (process.num_threads === null) {
process.num_threads = -1;
}
if (process.cpu_percent === null) {
process.cpu_percent = -1;
}
if (process.memory_percent === null) {
process.memory_percent = -1;
}
process.io_read = null;
process.io_write = null;
if (process.io_counters) {
process.io_read =
(process.io_counters[0] - process.io_counters[2]) /
process.time_since_update;
process.io_write =
(process.io_counters[1] - process.io_counters[3]) /
process.time_since_update;
}
process.isNice =
process.nice !== undefined &&
((isWindows && process.nice != 32) || (!isWindows && process.nice != 0));
if (Array.isArray(process.cmdline)) {
process.cmdline = process.cmdline.join(' ').replace(/\n/g, ' ');
}
if (process.cmdline === null || process.cmdline.length === 0) {
process.cmdline = process.name;
}
return process;
});
return orderBy(
programs,
[sorter.column].reduce((retval, col) => {
if (col === 'io_counters') {
col = ['io_read', 'io_write']
}
return retval.concat(col);
}, []),
[sorter.isReverseColumn(sorter.column) ? 'desc' : 'asc']
).slice(0, this.limit);
},
ioReadWritePresentPrograms() {
return (this.stats_programlist || []).some(({ io_counters }) => io_counters);
},
limit() {
return this.config.outputs !== undefined

File diff suppressed because one or more lines are too long

View File

@ -81,7 +81,7 @@ class PluginModel(GlancesPluginModel):
# Update the stats
if self.input_method == 'local':
# Update stats using the standard system lib
# Here, update is call for processcount AND processlist
# Here, update is call for processcount, processlist and programlist
glances_processes.update()
# For the ProcessCount, only return the processes count

View File

@ -230,7 +230,10 @@ class PluginModel(GlancesPluginModel):
"""Update processes stats using the input method."""
# Update the stats
if self.input_method == 'local':
stats = self.update_local()
# Update stats using the standard system lib
# Note: Update is done in the processcount plugin
# Just return the result
stats = glances_processes.get_list()
else:
stats = self.get_init_value()
@ -243,17 +246,6 @@ class PluginModel(GlancesPluginModel):
return self.stats
def update_local(self):
# Update stats using the standard system lib
# Note: Update is done in the processcount plugin
# Just return the result
if self.args.programs:
stats = glances_processes.get_list(as_programs=True)
else:
stats = glances_processes.get_list()
return stats
def get_export(self):
"""Return the processes list to export.
Not all the processeses are exported.
@ -345,15 +337,8 @@ class PluginModel(GlancesPluginModel):
def _get_process_curses_pid(self, p, selected, args):
"""Return process PID curses"""
if not self.args.programs:
# Display processes, so the PID should be displayed
msg = self.layout_stat['pid'].format(p['pid'], width=self.__max_pid_size())
else:
# Display programs, so the PID should not be displayed
# Instead displays the number of children
msg = self.layout_stat['pid'].format(
len(p['childrens']) if 'childrens' in p else '_', width=self.__max_pid_size()
)
# Display processes, so the PID should be displayed
msg = self.layout_stat['pid'].format(p['pid'], width=self._max_pid_size())
return self.curse_add_line(msg)
def _get_process_curses_username(self, p, selected, args):
@ -538,19 +523,19 @@ class PluginModel(GlancesPluginModel):
# Compute the sort key
process_sort_key = glances_processes.sort_key
processes_list_sorted = self.__sort_stats(process_sort_key)
processes_list_sorted = self._sort_stats(process_sort_key)
# Display extended stats for selected process
#############################################
if self.is_selected_process(args):
self.__msg_curse_extended_process(ret, glances_processes.extended_process)
self._msg_curse_extended_process(ret, glances_processes.extended_process)
# Display others processes list
###############################
# Header
self.__msg_curse_header(ret, process_sort_key, args)
self._msg_curse_header(ret, process_sort_key, args)
# Process list
# Loop over processes (sorted by the sort key previously compute)
@ -563,15 +548,15 @@ class PluginModel(GlancesPluginModel):
if glances_processes.process_filter is not None:
if args.reset_minmax_tag:
args.reset_minmax_tag = not args.reset_minmax_tag
self.__mmm_reset()
self.__msg_curse_sum(ret, args=args)
self.__msg_curse_sum(ret, mmm='min', args=args)
self.__msg_curse_sum(ret, mmm='max', args=args)
self._mmm_reset()
self._msg_curse_sum(ret, args=args)
self._msg_curse_sum(ret, mmm='min', args=args)
self._msg_curse_sum(ret, mmm='max', args=args)
# Return the message with decoration
return ret
def __msg_curse_extended_process(self, ret, p):
def _msg_curse_extended_process(self, ret, p):
"""Get extended curses data for the selected process (see issue #2225)
The result depends of the process type (process or thread).
@ -598,18 +583,7 @@ class PluginModel(GlancesPluginModel):
'cpu_max': 7.0,
'cpu_mean': 3.2}
"""
if self.args.programs:
self.__msg_curse_extended_process_program(ret, p)
else:
self.__msg_curse_extended_process_thread(ret, p)
def __msg_curse_extended_process_program(self, ret, p):
# Title
msg = "Pinned program {} ('e' to unpin)".format(p['name'])
ret.append(self.curse_add_line(msg, "TITLE"))
ret.append(self.curse_new_line())
ret.append(self.curse_new_line())
self._msg_curse_extended_process_thread(ret, p)
def add_title_line(self, ret, prog):
ret.append(self.curse_add_line("Pinned thread ", "TITLE"))
@ -713,7 +687,7 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_add_line(' {} '.format(stat_prefix.replace('num_', ''))))
return ret
def __msg_curse_extended_process_thread(self, ret, prog):
def _msg_curse_extended_process_thread(self, ret, prog):
# `append_newlines` has dummy arguments for piping thru `functools.reduce`
def append_newlines(ret, prog):
(ret.append(self.curse_new_line()),)
@ -733,7 +707,7 @@ class PluginModel(GlancesPluginModel):
functools.reduce(lambda ret, step: step(ret, prog), steps, ret)
def __msg_curse_header(self, ret, process_sort_key, args=None):
def _msg_curse_header(self, ret, process_sort_key, args=None):
"""Build the header and add it to the ret dict."""
sort_style = 'SORT'
@ -757,10 +731,7 @@ class PluginModel(GlancesPluginModel):
msg = self.layout_header['res'].format('RES')
ret.append(self.curse_add_line(msg, optional=True))
if 'pid' in display_stats:
if not self.args.programs:
msg = self.layout_header['pid'].format('PID', width=self.__max_pid_size())
else:
msg = self.layout_header['pid'].format('NPROCS', width=self.__max_pid_size())
msg = self.layout_header['pid'].format('PID', width=self._max_pid_size())
ret.append(self.curse_add_line(msg))
if 'username' in display_stats:
msg = self.layout_header['user'].format('USER')
@ -793,17 +764,14 @@ class PluginModel(GlancesPluginModel):
)
)
if args.is_standalone and not args.disable_cursor:
if self.args.programs:
shortkey = "('k' to kill)"
else:
shortkey = "('e' to pin | 'k' to kill)"
shortkey = "('e' to pin | 'k' to kill)"
else:
shortkey = ""
if 'cmdline' in display_stats:
msg = self.layout_header['command'].format("Programs" if self.args.programs else "Command", shortkey)
msg = self.layout_header['command'].format("Command", shortkey)
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
def _msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
"""
Build the sum message (only when filter is on) and add it to the ret dict.
@ -818,11 +786,11 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_new_line())
# CPU percent sum
msg = ' '
msg += self.layout_stat['cpu'].format(self.__sum_stats('cpu_percent', mmm=mmm))
ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm)))
msg += self.layout_stat['cpu'].format(self._sum_stats('cpu_percent', mmm=mmm))
ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm)))
# MEM percent sum
msg = self.layout_stat['mem'].format(self.__sum_stats('memory_percent', mmm=mmm))
ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm)))
msg = self.layout_stat['mem'].format(self._sum_stats('memory_percent', mmm=mmm))
ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm)))
# VIRT and RES memory sum
if (
'memory_info' in self.stats[0]
@ -831,21 +799,21 @@ class PluginModel(GlancesPluginModel):
):
# VMS
msg = self.layout_stat['virt'].format(
self.auto_unit(self.__sum_stats('memory_info', sub_key='vms', mmm=mmm), low_precision=False)
self.auto_unit(self._sum_stats('memory_info', sub_key='vms', mmm=mmm), low_precision=False)
)
ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm), optional=True))
ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True))
# RSS
msg = self.layout_stat['res'].format(
self.auto_unit(self.__sum_stats('memory_info', sub_key='rss', mmm=mmm), low_precision=False)
self.auto_unit(self._sum_stats('memory_info', sub_key='rss', mmm=mmm), low_precision=False)
)
ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm), optional=True))
ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True))
else:
msg = self.layout_header['virt'].format('')
ret.append(self.curse_add_line(msg))
msg = self.layout_header['res'].format('')
ret.append(self.curse_add_line(msg))
# PID
msg = self.layout_header['pid'].format('', width=self.__max_pid_size())
msg = self.layout_header['pid'].format('', width=self._max_pid_size())
ret.append(self.curse_add_line(msg))
# USER
msg = self.layout_header['user'].format('')
@ -866,24 +834,24 @@ class PluginModel(GlancesPluginModel):
if 'io_counters' in self.stats[0] and mmm is None:
# IO read
io_rs = int(
(self.__sum_stats('io_counters', 0) - self.__sum_stats('io_counters', sub_key=2, mmm=mmm))
(self._sum_stats('io_counters', 0) - self._sum_stats('io_counters', sub_key=2, mmm=mmm))
/ self.stats[0]['time_since_update']
)
if io_rs == 0:
msg = self.layout_stat['ior'].format('0')
else:
msg = self.layout_stat['ior'].format(self.auto_unit(io_rs, low_precision=True))
ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm), optional=True, additional=True))
ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True, additional=True))
# IO write
io_ws = int(
(self.__sum_stats('io_counters', 1) - self.__sum_stats('io_counters', sub_key=3, mmm=mmm))
(self._sum_stats('io_counters', 1) - self._sum_stats('io_counters', sub_key=3, mmm=mmm))
/ self.stats[0]['time_since_update']
)
if io_ws == 0:
msg = self.layout_stat['iow'].format('0')
else:
msg = self.layout_stat['iow'].format(self.auto_unit(io_ws, low_precision=True))
ret.append(self.curse_add_line(msg, decoration=self.__mmm_deco(mmm), optional=True, additional=True))
ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True, additional=True))
else:
msg = self.layout_header['ior'].format('')
ret.append(self.curse_add_line(msg, optional=True, additional=True))
@ -898,18 +866,18 @@ class PluginModel(GlancesPluginModel):
msg = '(\'M\' to reset)'
ret.append(self.curse_add_line(msg, optional=True))
def __mmm_deco(self, mmm):
def _mmm_deco(self, mmm):
"""Return the decoration string for the current mmm status."""
if mmm is not None:
return 'DEFAULT'
return 'FILTER'
def __mmm_reset(self):
def _mmm_reset(self):
"""Reset the MMM stats."""
self.mmm_min = {}
self.mmm_max = {}
def __sum_stats(self, key, sub_key=None, mmm=None):
def _sum_stats(self, key, sub_key=None, mmm=None):
"""Return the sum of the stats value for the given key.
:param sub_key: If sub_key is set, get the p[key][sub_key]
@ -930,7 +898,7 @@ class PluginModel(GlancesPluginModel):
ret += p[key][sub_key]
# Manage Min/Max/Mean
mmm_key = self.__mmm_key(key, sub_key)
mmm_key = self._mmm_key(key, sub_key)
if mmm == 'min':
try:
if self.mmm_min[mmm_key] > ret:
@ -954,17 +922,17 @@ class PluginModel(GlancesPluginModel):
return ret
def __mmm_key(self, key, sub_key):
def _mmm_key(self, key, sub_key):
ret = key
if sub_key is not None:
ret += str(sub_key)
return ret
def __sort_stats(self, sorted_by=None):
def _sort_stats(self, sorted_by=None):
"""Return the stats (dict) sorted by (sorted_by)."""
return sort_stats(self.stats, sorted_by, reverse=glances_processes.sort_reverse)
def __max_pid_size(self):
def _max_pid_size(self):
"""Return the maximum PID size in number of char."""
if self.pid_max is not None:
return len(str(self.pid_max))

View File

@ -54,6 +54,11 @@ def update_program_dict(program, p):
program['status'] = p['status'] if p['status'] == program['status'] else '_'
def compute_nprocs(p):
p['nprocs'] = len(p['childrens'])
return p
def processes_to_programs(processes):
"""Convert a list of processes to a list of programs."""
# Start to build a dict of programs (key is program name)
@ -66,4 +71,4 @@ def processes_to_programs(processes):
update_program_dict(programs_dict[p[key]], p)
# Convert the dict to a list of programs
return [programs_dict[p] for p in programs_dict]
return [compute_nprocs(p) for p in programs_dict.values()]

View File

@ -23,7 +23,6 @@ from glances.globals import LINUX, WINDOWS, pretty_date, string_value_to_float,
from glances.main import GlancesMain
from glances.outputs.glances_bars import Bar
from glances.plugins.plugin.model import GlancesPluginModel
from glances.programs import processes_to_programs
from glances.stats import GlancesStats
from glances.thresholds import (
GlancesThresholdCareful,
@ -470,12 +469,14 @@ class TestGlances(unittest.TestCase):
print(f'INFO: SMART stats: {stats_grab}')
def test_017_programs(self):
"""Check Programs function (it's not a plugin)."""
"""Check Programs plugin."""
# stats_to_check = [ ]
print('INFO: [TEST_017] Check PROGRAM stats')
stats_grab = processes_to_programs(stats.get_plugin('processlist').get_raw())
self.assertIsInstance(stats_grab, list, msg='Programs stats list is not a list')
self.assertIsInstance(stats_grab[0], dict, msg='First item should be a dict')
print('INFO: [TEST_022] Check PROGRAMS stats')
stats_grab = stats.get_plugin('programlist').get_raw()
self.assertTrue(isinstance(stats_grab, list), msg='Programs stats is not a list')
if stats_grab:
self.assertTrue(isinstance(stats_grab[0], dict), msg='Programs stats is not a list of dict')
self.assertTrue('nprocs' in stats_grab[0], msg='No nprocs')
def test_018_string_value_to_float(self):
"""Check string_value_to_float function"""