diff --git a/NEWS b/NEWS index f3e93d6f..99587432 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ Enhancements and new features: * Add Application Monitoring Process plugin (issue #780) * Improve IP plugin to display public IP address (issue #646) * CPU additionnal stats monitoring: Context switch, Interrupts... (issue #810) + * Filter processes by others stats (username) (issue #748) * [Folders] Differentiate permission issue and non-existence of a directory (issue #828) * [Web UI] Add cpu name in quicklook plugin (issue #825) * Allow theme to be set in configuration file (issue #862) diff --git a/docs/_static/processlist-filter.png b/docs/_static/processlist-filter.png index a804bf0f..b8d3e312 100644 Binary files a/docs/_static/processlist-filter.png and b/docs/_static/processlist-filter.png differ diff --git a/docs/aoa/ps.rst b/docs/aoa/ps.rst index a85a7d96..3d8b5c4c 100644 --- a/docs/aoa/ps.rst +++ b/docs/aoa/ps.rst @@ -79,6 +79,18 @@ Columns display pressing on the ``'/'`` key ========================= ============================================== +Process filtering +----------------- + +It's possible to filter the processes list using the ``ENTER`` key. + +Filter syntax is the following (examples): + +- python > Filter processes name or command line starting with *python* (regexp) +- .*python.* > Filter processes name or command line containing *python* (regexp) +- username:nicolargo > Processes of nicolargo user (key:regexp) +- cmdline:\/usr\/bin.* > Processes starting by */usr/bin* + Extended info ------------- diff --git a/glances/filter.py b/glances/filter.py index d73379f9..74732303 100644 --- a/glances/filter.py +++ b/glances/filter.py @@ -30,49 +30,116 @@ class GlancesFilter(object): >>> f.filter = '.*python.*' >>> f.filter '.*python.*' + >>> f.key + None + >>> f.filter = 'user:nicolargo' + >>> f.filter + 'nicolargo' + >>> f.key + 'user' + >>> f.filter = 'username:.*nico.*' + >>> f.filter + '.*nico.*' + >>> f.key + 'username' """ def __init__(self): # Filter entered by the user (string) + self._filter_input = None + # Filter to apply self._filter = None # Filter regular expression self._filter_re = None + # Dict key where the filter should be applied + # Default is None: search on command line and process name + self._filter_key = None + + @property + def filter_input(self): + """Return the filter given by the user (as a sting)""" + return self._filter_input @property def filter(self): - """Return the filter (as a sting)""" + """Return the current filter to be applied""" return self._filter @filter.setter def filter(self, value): - """Set the filter (as a sting) and compute the regular expression""" - self._filter = value - self._filter_re = None + """Set the filter (as a sting) and compute the regular expression + A filter could be one of the following: + - python > Process name of cmd start with python + - .*python.* > Process name of cmd contain python + - username:nicolargo > Process of nicolargo user + """ + self._filter_input = value + if value is None: + self._filter = None + self._filter_key = None + else: + new_filter = value.split(':') + if len(new_filter) == 1: + self._filter = new_filter[0] + self._filter_key = None + else: + self._filter = new_filter[1] + self._filter_key = new_filter[0] + self._filter_re = None if self.filter is not None: - logger.info("Set filter to {0}".format(self.filter)) + logger.info("Set filter to {0} on key {1}".format(self.filter, self.filter_key)) # Compute the regular expression try: self._filter_re = re.compile(self.filter) logger.debug("Filter regex compilation OK: {0}".format(self.filter)) except Exception as e: logger.error("Cannot compile filter regex: {0} ({1})".format(self.filter, e)) + self._filter = None + self._filter_re = None + self._filter_key = None @property def filter_re(self): """Return the filter regular expression""" return self._filter_re - def is_filtered(self, process, key='cmdline'): + @property + def filter_key(self): + """key where the filter should be applied""" + return self._filter_key + + def is_filtered(self, process): """Return True if the process item match the current filter The proces item is a dict. - The filter will be applyed on the process[key] (command line by default) """ if self.filter is None: # No filter => Not filtered return False + + if self.filter_key is None: + # Apply filter on command line and process name + return self._is_process_filtered(process, key='cmdline') and self._is_process_filtered(process, key='name') + else: + # Apply filter on + return self._is_process_filtered(process) + + def _is_process_filtered(self, process, key=None): + """Return True if the process[key] should be filtered according to the current filter""" + if key is None: + key = self.filter_key try: - return self._filter_re.match(' '.join(process[key])) is None + # If the item process[key] is a list, convert it to a string + # in order to match it with the current regular expression + if isinstance(process[key], list): + value = ' '.join(process[key]) + else: + value = process[key] + except KeyError: + # If the key did not exist + return False + try: + return self._filter_re.match(value) is None except AttributeError: # Filter processes crashs with a bad regular expression pattern (issue #665) return False diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 0e3ac81a..aef95d07 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -781,8 +781,18 @@ class _GlancesCurses(object): # Only in standalone mode (cs_status is None) if self.edit_filter and cs_status is None: new_filter = self.display_popup( - 'Process filter pattern: ', is_input=True, - input_value=glances_processes.process_filter) + 'Process filter pattern: \n' + + '\n' + + 'Examples:\n' + + '- python\n' + + '- .*python.*\n' + + '- \/usr\/lib.*' + + '- name:.*nautilus.*\n' + + '- cmdline:.*glances.*\n' + + '- username:nicolargo\n' + + '- username:^root ', + is_input=True, + input_value=glances_processes.process_filter_input) glances_processes.process_filter = new_filter elif self.edit_filter and cs_status != 'None': self.display_popup('Process filter only available in standalone mode') diff --git a/glances/plugins/glances_processcount.py b/glances/plugins/glances_processcount.py index 05724199..10ce0386 100644 --- a/glances/plugins/glances_processcount.py +++ b/glances/plugins/glances_processcount.py @@ -84,6 +84,8 @@ class Plugin(GlancesPlugin): msg = 'Processes filter:' ret.append(self.curse_add_line(msg, "TITLE")) msg = ' {0} '.format(glances_processes.process_filter) + if glances_processes.process_filter_key is not None: + msg += 'on column {0} '.format(glances_processes.process_filter_key) ret.append(self.curse_add_line(msg, "FILTER")) msg = '(\'ENTER\' to edit, \'E\' to reset)' ret.append(self.curse_add_line(msg)) diff --git a/glances/processes.py b/glances/processes.py index 7cf32f83..500449a0 100644 --- a/glances/processes.py +++ b/glances/processes.py @@ -116,9 +116,14 @@ class GlancesProcesses(object): """Set the maximum number of processes showed in the UI.""" self._max_processes = value + @property + def process_filter_input(self): + """Get the process filter (given by the user).""" + return self._filter.filter_input + @property def process_filter(self): - """Get the process filter.""" + """Get the process filter (current apply filter).""" return self._filter.filter @process_filter.setter @@ -126,6 +131,11 @@ class GlancesProcesses(object): """Set the process filter.""" self._filter.filter = value + @property + def process_filter_key(self): + """Get the process filter key.""" + return self._filter.filter_key + @property def process_filter_re(self): """Get the process regular expression compiled.""" @@ -398,7 +408,7 @@ class GlancesProcesses(object): OSX and s['name'] == 'kernel_task'): continue # Continue to the next process if it has to be filtered - if s is None or (self._filter.is_filtered(s, 'cmdline') and self._filter.is_filtered(s, 'name')): + if s is None or self._filter.is_filtered(s): excluded_processes.add(proc) continue