diff options
author | Nicolargo <nicolas@nicolargo.com> | 2014-08-15 23:59:50 +0200 |
---|---|---|
committer | Nicolargo <nicolas@nicolargo.com> | 2014-08-15 23:59:50 +0200 |
commit | 8302690995c23df7384b82de097e9781e5416325 (patch) | |
tree | 19d037ea3c42c0da7cfc5d875888ba7212ebb8db | |
parent | 37edbfc5a18f2bf424b1f34f2d158d03f18983c0 (diff) |
Add the process filter feature
-rw-r--r-- | glances/core/glances_main.py | 4 | ||||
-rw-r--r-- | glances/core/glances_processes.py | 50 | ||||
-rw-r--r-- | glances/core/glances_standalone.py | 4 | ||||
-rw-r--r-- | glances/outputs/glances_curses.py | 104 | ||||
-rw-r--r-- | glances/plugins/glances_help.py | 6 | ||||
-rw-r--r-- | glances/plugins/glances_processcount.py | 13 |
6 files changed, 158 insertions, 23 deletions
diff --git a/glances/core/glances_main.py b/glances/core/glances_main.py index c7b9b90a..b7c58eb7 100644 --- a/glances/core/glances_main.py +++ b/glances/core/glances_main.py @@ -111,6 +111,8 @@ class GlancesMain(object): parser.add_argument('-w', '--webserver', action='store_true', default=False, dest='webserver', help=_('run Glances in web server mode')) # Display (Curses) options + parser.add_argument('-f', '--process-filter', default=None, type=str, + dest='process_filter', help=_('set the process filter patern (regular expression)')) parser.add_argument('-b', '--byte', action='store_true', default=False, dest='byte', help=_('display network rate in byte per second')) parser.add_argument('-1', '--percpu', action='store_true', default=False, @@ -141,7 +143,7 @@ class GlancesMain(object): # In web server mode, defaul refresh time: 5 sec if args.webserver: - args.time = 5 + args.time = 5 # Server or client login/password args.username = self.username diff --git a/glances/core/glances_processes.py b/glances/core/glances_processes.py index ef32e138..f41a2140 100644 --- a/glances/core/glances_processes.py +++ b/glances/core/glances_processes.py @@ -17,10 +17,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +# Import Glances lib from glances.core.glances_globals import is_linux, is_bsd, is_mac, is_windows, logger from glances.core.glances_timer import Timer, getTimeSinceLastUpdate +# Import Python lib import psutil +import re class GlancesProcesses(object): @@ -59,6 +62,13 @@ class GlancesProcesses(object): # None if no limit self.max_processes = None + # Process filter is a regular expression + self.process_filter = None + self.process_filter_re = None + + # !!! ONLY FOR TEST + # self.set_process_filter('.*python.*') + def enable(self): """Enable process stats.""" self.disable_tag = False @@ -86,6 +96,37 @@ class GlancesProcesses(object): """Get the maximum number of processes showed in the UI interfaces""" return self.max_processes + def set_process_filter(self, value): + """Set the process filter""" + logger.info(_("Set process filter to %s") % value) + self.process_filter = value + if value is not None: + try: + self.process_filter_re = re.compile(value) + logger.debug(_("Process filter regular expression compilation OK: %s") % self.get_process_filter()) + except: + logger.error(_("Can not compile process filter regular expression: %s") % value) + self.process_filter_re = None + else: + self.process_filter_re = None + return self.process_filter + + def get_process_filter(self): + """Get the process filter""" + return self.process_filter + + def get_process_filter_re(self): + """Get the process regular expression compiled""" + return self.process_filter_re + + def is_filtered(self, value): + """Return True if the value should be filtered""" + if self.get_process_filter() is None: + # No filter => Not filtered + return False + else: + return self.get_process_filter_re().match(value) is None + def __get_process_stats(self, proc, mandatory_stats=True, standard_stats=True, @@ -237,9 +278,12 @@ class GlancesProcesses(object): for proc in psutil.process_iter(): # If self.get_max_processes() is None: Only retreive mandatory stats # Else: retreive mandatoryadn standard stast - processdict[proc] = self.__get_process_stats(proc, - mandatory_stats=True, - standard_stats=self.get_max_processes() is None) + s = self.__get_process_stats(proc, + mandatory_stats=True, + standard_stats=self.get_max_processes() is None) + if self.is_filtered(s['name']): + continue + processdict[proc] = s # ignore the 'idle' process on Windows and *BSD # ignore the 'kernel_task' process on OS X # waiting for upstream patch from psutil diff --git a/glances/core/glances_standalone.py b/glances/core/glances_standalone.py index f74f120d..a6586f6b 100644 --- a/glances/core/glances_standalone.py +++ b/glances/core/glances_standalone.py @@ -51,6 +51,10 @@ class GlancesStandalone(object): logger.debug(_("Extended stats for top process is enabled (default behavor)")) glances_processes.enable_extended() + # Manage optionnal process filter + if args.process_filter is not None: + glances_processes.set_process_filter(args.process_filter) + # Initial system informations update self.stats.update() diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 5784cf29..bd5a2058 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -31,6 +31,7 @@ if not is_windows: try: import curses import curses.panel + from curses.textpad import Textbox, rectangle except ImportError: logger.critical('Curses module not found. Glances cannot start in standalone mode.') sys.exit(1) @@ -70,11 +71,7 @@ class GlancesCurses(object): curses.noecho() if hasattr(curses, 'cbreak'): curses.cbreak() - if hasattr(curses, 'curs_set'): - try: - curses.curs_set(0) - except Exception: - pass + self.set_cursor(0) # Init colors self.hascolors = False @@ -136,6 +133,7 @@ class GlancesCurses(object): 'BOLD': A_BOLD, 'SORT': A_BOLD, 'OK': self.default_color2, + 'FILTER': self.ifCAREFUL_color2, 'TITLE': self.title_color, 'PROCESS': self.default_color2, 'STATUS': self.default_color2, @@ -158,6 +156,9 @@ class GlancesCurses(object): # Init process sort method self.args.process_sorted_by = 'auto' + # Init edit filter tag + self.edit_filter = False + # Catch key pressed with non blocking mode self.term_window.keypad(1) self.term_window.nodelay(1) @@ -174,6 +175,18 @@ class GlancesCurses(object): args.enable_history = False logger.error('Stats history disabled because graph lib is not available') + def set_cursor(self, value): + """Configure the cursor + 0: invisible + 1: visible + 2: very visible + """ + if hasattr(curses, 'curs_set'): + try: + curses.curs_set(value) + except Exception: + pass + def __get_key(self, window): # Catch ESC key AND numlock key (issue #163) keycode = [0, 0] @@ -197,6 +210,9 @@ class GlancesCurses(object): self.end() logger.info("Stop Glances") sys.exit(0) + elif self.pressedkey == 10: + # 'ENTER' > Edit the process filter + self.edit_filter = not self.edit_filter elif self.pressedkey == ord('1'): # '1' > Switch between CPU and PerCPU information self.args.percpu = not self.args.percpu @@ -463,19 +479,41 @@ class GlancesCurses(object): self.history_tag = False self.reset_history_tag = False + # Display edit filter popup + if self.edit_filter: + new_filter = self.display_popup(_("Filter: "), + is_input=True, + input_value=glances_processes.get_process_filter()) + glances_processes.set_process_filter(new_filter) + self.edit_filter = False + return True - def display_popup(self, message, size_x=None, size_y=None, duration=3): + def display_popup(self, message, + size_x=None, size_y=None, + duration=3, + is_input=False, + input_size=20, + input_value=None): """ - Display a centered popup with the given message during duration seconds - If size_x and size_y: set the popup size - else set it automatically - Return True if the popup could be displayed + If is_input is False: + Display a centered popup with the given message during duration seconds + If size_x and size_y: set the popup size + else set it automatically + Return True if the popup could be displayed + If is_input is True: + Display a centered popup with the given message and a input field + If size_x and size_y: set the popup size + else set it automatically + Return the input string or None if the field is empty """ # Center the popup if size_x is None: size_x = len(message) + 4 + # Add space for the input field + if is_input: + size_x += input_size if size_y is None: size_y = message.count('\n') + 1 + 4 screen_x = self.screen.getmaxyx()[1] @@ -488,7 +526,7 @@ class GlancesCurses(object): # Create the popup popup = curses.newwin(size_y, size_x, pos_y, pos_x) - + # Fill the popup popup.border() @@ -498,11 +536,32 @@ class GlancesCurses(object): popup.addnstr(2 + y, 2, m, len(m)) y += 1 - # Display the popup - popup.refresh() - curses.napms(duration * 1000) - - return True + if is_input: + # Create a subwindow for the text field + subpop = popup.derwin(1, input_size, 2, 2 + len(m)) + subpop.attron(self.__colors_list['FILTER']) + # Init the field with the current value + if input_value is not None: + subpop.addnstr(0, 0, input_value, len(input_value)) + # Display the popup + popup.refresh() + subpop.refresh() + # Create the textbox inside the subwindows + self.set_cursor(2) + textbox = glances_textbox(subpop, insert_mode=False) + textbox.edit() + self.set_cursor(0) + if textbox.gather() != '': + logger.debug(_("User enters the following process filter patern: %s") % textbox.gather()) + return textbox.gather()[:-1] + else: + logger.debug(_("User clears the process filter patern")) + return None + else: + # Display the popup + popup.refresh() + curses.napms(duration * 1000) + return True def display_plugin(self, plugin_stats, display_optional=True, @@ -648,3 +707,16 @@ class GlancesCurses(object): return 0 else: return c + 1 + +class glances_textbox(Textbox): + """ + """ + def __init__(*args, **kwargs): + Textbox.__init__(*args, **kwargs) + + def do_command(self, ch): + if ch == 10: # Enter + return 0 + if ch == 127: # Enter + return 8 + return Textbox.do_command(self, ch)
\ No newline at end of file diff --git a/glances/plugins/glances_help.py b/glances/plugins/glances_help.py index 9ceff591..9ae48416 100644 --- a/glances/plugins/glances_help.py +++ b/glances/plugins/glances_help.py @@ -129,5 +129,11 @@ class Plugin(GlancesPlugin): msg = msg_col2.format("q", _("Quit (Esc and Ctrl-C also work)")) ret.append(self.curse_add_line(msg)) + ret.append(self.curse_new_line()) + ret.append(self.curse_new_line()) + msg = '{0}: {1}'.format("ENTER", _("Edit the process filter patern")) + ret.append(self.curse_add_line(msg)) + + # Return the message with decoration return ret diff --git a/glances/plugins/glances_processcount.py b/glances/plugins/glances_processcount.py index 95e507be..b171258a 100644 --- a/glances/plugins/glances_processcount.py +++ b/glances/plugins/glances_processcount.py @@ -69,9 +69,6 @@ class Plugin(GlancesPlugin): ret = [] # Only process if stats exist and display plugin enable... - # if self.stats == {} or args.disable_process: - # return ret - if args.disable_process: msg = _("PROCESSES DISABLED (press 'z' to display)") ret.append(self.curse_add_line(msg)) @@ -80,6 +77,16 @@ class Plugin(GlancesPlugin): if self.stats == {}: return ret + # Display the filter (if it exists) + if glances_processes.get_process_filter() is not None: + msg = _("Processes filter:") + ret.append(self.curse_add_line(msg, "TITLE")) + msg = _(" {0} ").format(glances_processes.get_process_filter()) + ret.append(self.curse_add_line(msg, "FILTER")) + msg = _("(press ENTER to edit)") + ret.append(self.curse_add_line(msg)) + ret.append(self.curse_new_line()) + # Build the string message # Header msg = _("TASKS ") |