Add a focus option for the processlist plugin #3293

This commit is contained in:
nicolargo 2025-11-11 10:40:06 +01:00
parent df4b312bec
commit ab68fcbe28
16 changed files with 737 additions and 600 deletions

View File

@ -487,6 +487,8 @@ status_critical=Z,D
# Define the list of processes to export using:
# a comma-separated list of Glances filter
#export=.*firefox.*,pid:1234
# Define a list of process to focus on (comma-separated list of Glances filter)
#focus=.*firefox.*,.*python.*
[ports]
disable=False

View File

@ -166,7 +166,7 @@ Process filtering
It's possible to filter the processes list using the ``ENTER`` key.
Filter syntax is the following (examples):
Glances filter syntax is the following (examples):
- ``python``: Filter processes name or command line starting with
*python* (regexp)
@ -175,6 +175,25 @@ Filter syntax is the following (examples):
- ``username:nicolargo``: Processes of nicolargo user (key:regexp)
- ``cmdline:\/usr\/bin.*``: Processes starting by */usr/bin*
Process focus
-------------
It's also possible to select a processes list to focus on.
A list of Glances filters (see upper) can be define from the command line:
.. code-block:: bash
glances --process-focus .*python.*,.*firefox.*
or the glances.conf file:
.. code-block:: ini
[processlist]
focus=.*python.*,.*firefox.*
Extended info
-------------

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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" "Nov 10, 2025" "4.4.2_dev1" "Glances"
.TH "GLANCES" "1" "Nov 11, 2025" "4.4.2_dev1" "Glances"
.SH NAME
glances \- An eye on your system
.SH SYNOPSIS

View File

@ -152,6 +152,10 @@ Examples of use:
if not self.args.process_filter and not self.is_standalone():
logger.debug("Process filter is only available in standalone mode")
# Focus filter is only available in standalone mode
if not self.args.process_focus and not self.is_standalone():
logger.debug("Process focus is only available in standalone mode")
# Cursor option is only available in standalone mode
if not self.args.disable_cursor and not self.is_standalone():
logger.debug("Cursor is only available in standalone mode")
@ -496,6 +500,14 @@ Examples of use:
dest='process_filter',
help='set the process filter pattern (regular expression)',
)
# Process will focus on some process (comma-separated list of Glances filter)
parser.add_argument(
'--process-focus',
default=None,
type=str,
dest='process_focus',
help='set a process list to focus on (comma-separated list of Glances filter)',
)
parser.add_argument(
'--process-short-name',
action='store_true',

View File

@ -208,6 +208,16 @@ class GlancesRestfulApi:
status.HTTP_401_UNAUTHORIZED, "Incorrect username or password", {"WWW-Authenticate": "Basic"}
)
def _logo(self):
return rf"""
_____ _
/ ____| |
| | __| | __ _ _ __ ___ ___ ___
| | |_ | |/ _` | '_ \ / __/ _ \/ __|
| |__| | | (_| | | | | (_| __/\__
\_____|_|\__,_|_| |_|\___\___||___/ {__version__}
"""
def _router(self) -> APIRouter:
"""Define a custom router for Glances path."""
base_path = f'/api/{self.API_VERSION}'
@ -267,6 +277,9 @@ class GlancesRestfulApi:
for path, endpoint in route_mapping.items():
router.add_api_route(path, endpoint)
# Logo
print(self._logo())
# Browser WEBUI
if hasattr(self.args, 'browser') and self.args.browser:
# Template for the root browser.html file

View File

@ -169,10 +169,10 @@ body {
}
.button {
color: #99CCFF; /* Bleu clair high-tech */
background: rgba(0, 0, 0, 0.4); /* Fond légèrement transparent */
border: 1px solid #99CCFF; /* Bordure discrète */
padding: 5px 10px;
color: #99CCFF;
background: rgba(0, 0, 0, 0.4);
border: 1px solid #99CCFF;
padding: 1px 5px;
border-radius: 5px;
letter-spacing: 1px;
cursor: pointer;
@ -182,14 +182,14 @@ body {
}
.button:hover {
background: rgba(153, 204, 255, 0.15); /* Légère coloration au survol */
background: rgba(183, 214, 255, 0.30);
border-color: #B0D0FF;
color: #B0D0FF;
}
.button:active {
transform: scale(0.95); /* Légère réduction pour effet de pression */
box-shadow: 0 0 8px rgba(153, 204, 255, 0.5); /* Flash léger */
transform: scale(0.95);
box-shadow: 0 0 8px rgba(153, 204, 255, 0.5);
}
.frequency {
@ -413,6 +413,8 @@ body {
#processlist {
overflow-y: auto;
height: 600px;
margin-top: 1em;
.table {
margin-bottom: 1em;
}

View File

@ -19,10 +19,10 @@
<div v-if="!args.disable_ip" class="d-none d-lg-block"><glances-plugin-ip
:data="data"></glances-plugin-ip>
</div>
<div v-if="!args.disable_now" class="d-none d-xl-block"><glances-plugin-now
:data="data"></glances-plugin-now></div>
<div v-if="!args.disable_uptime" class="d-none d-md-block"><glances-plugin-uptime
:data="data"></glances-plugin-uptime></div>
<div v-if="!args.disable_now" class="d-none d-xl-block"><glances-plugin-now
:data="data"></glances-plugin-now></div>
</div>
</div>
<div class="d-flex d-none d-sm-block">

View File

@ -101,7 +101,9 @@ export default {
return this.store.config || {};
},
available_args() {
return this.config.mem.available || false;
return this.config !== undefined && this.config.mem !== undefined && this.config.available !== undefined
? this.config.mem.available || false
: false
},
stats() {
return this.data.stats['mem'];

View File

@ -11,7 +11,7 @@
<div>
<span>CPU Min/Max/Mean: </span>
<span class="careful">{{ $filters.number(extended_stats.cpu_min, 1)
}}% / {{
}}% / {{
$filters.number(extended_stats.cpu_max, 1) }}% / {{ $filters.number(extended_stats.cpu_mean, 1)
}}%</span>
<span>Affinity: </span>
@ -29,7 +29,8 @@
</span>
</div>
</div>
<div class="table-responsive d-lg-none">
<div v-show="is_focus">Focus on following processes: {{ focus.join(', ') }}</div>
<div class="table-responsive d-lg-none" id="processlist-table">
<table class="table table-sm table-borderless table-striped table-hover">
<thead>
<tr>
@ -520,6 +521,16 @@ export default {
return this.config.outputs !== undefined
? this.config.outputs.max_processes_display
: undefined;
},
focus() {
return this.args !== undefined && this.args.process_focus !== undefined && this.args.process_focus !== null
? this.args.process_focus.split(',')
: this.config.processlist !== undefined && this.config.processlist.focus !== undefined && this.config.processlist.focus !== null
? this.config.processlist.focus.split(',')
: [];
},
is_focus() {
return this.focus.length > 0;
}
},
methods: {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -215,6 +215,11 @@ class ProcesslistPlugin(GlancesPluginModel):
glances_processes.export_process_filter = config.as_dict()['processlist']['export']
if args.export:
logger.info("Export process filter is set to: {}".format(config.as_dict()['processlist']['export']))
if 'focus' in config.as_dict()['processlist']:
glances_processes.process_focus = config.as_dict()['processlist']['focus']
logger.info(
"Focus process filter (in glances.conf) is set to: {}".format(config.as_dict()['processlist']['focus'])
)
if 'disable_stats' in config.as_dict()['processlist']:
logger.info(
'Followings processes stats wil not be displayed: {}'.format(
@ -727,6 +732,11 @@ class ProcesslistPlugin(GlancesPluginModel):
"""Build the header and add it to the ret dict."""
sort_style = 'SORT'
if glances_processes.process_focus and glances_processes.process_focus != []:
msg = 'Focus on following processes: ' + ', '.join([i.filter for i in glances_processes.process_focus])
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
display_stats = [i for i in self.enable_stats if i not in glances_processes.disable_stats]
if 'cpu_percent' in display_stats:

View File

@ -137,6 +137,10 @@ class ProgramlistPlugin(ProcesslistPlugin):
"""Init the plugin."""
super().__init__(args=args, config=config)
def load(self, args, config):
"""Load already done in processlist"""
pass
def get_key(self):
"""Return the key of the list."""
return 'name'

View File

@ -75,6 +75,9 @@ class GlancesProcesses:
# Cache is a dict with key=pid and value = dict of cached value
self.processlist_cache = {}
# List of processes to focus on
self._filter_focus = GlancesFilterList()
# List of processes stats to export
# Only process matching one of the filter will be exported
self._filter_export = GlancesFilterList()
@ -138,6 +141,10 @@ class GlancesProcesses:
"""Set args."""
self.args = args
if self.args.process_focus is not None:
logger.info(f"Focus process filter (--process-focus option) is set to: {self.args.process_focus}")
self.process_focus = self.args.process_focus
def reset_internal_cache(self):
"""Reset the internal cache."""
self.cache_timer = Timer(0)
@ -267,7 +274,21 @@ class GlancesProcesses:
"""Get the process regular expression compiled."""
return self._filter.filter_re
# Process focus filter
# List of Glances filter
@property
def process_focus(self):
"""Get the focus process filter."""
return self._filter_focus.filter
@process_focus.setter
def process_focus(self, value):
"""Set the focus process filter list."""
self._filter_focus.filter = value
# Export filter
# List of Glances filter
@property
def export_process_filter(self):
@ -642,6 +663,9 @@ class GlancesProcesses:
def update_list(self, processlist):
"""Return the process list after filtering and transformation (namedtuple to dict)."""
if self._filter_focus.filter is not None and self._filter_focus.filter != []:
ret = list(filter(lambda p: self._filter_focus.is_filtered(p), processlist))
return list_of_namedtuple_to_list_of_dict(ret)
if self._filter.filter is None:
return list_of_namedtuple_to_list_of_dict(processlist)
ret = list(filter(lambda p: self._filter.is_filtered(p), processlist))