summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolargo <nicolas@nicolargo.com>2014-08-15 23:59:50 +0200
committerNicolargo <nicolas@nicolargo.com>2014-08-15 23:59:50 +0200
commit8302690995c23df7384b82de097e9781e5416325 (patch)
tree19d037ea3c42c0da7cfc5d875888ba7212ebb8db
parent37edbfc5a18f2bf424b1f34f2d158d03f18983c0 (diff)
Add the process filter feature
-rw-r--r--glances/core/glances_main.py4
-rw-r--r--glances/core/glances_processes.py50
-rw-r--r--glances/core/glances_standalone.py4
-rw-r--r--glances/outputs/glances_curses.py104
-rw-r--r--glances/plugins/glances_help.py6
-rw-r--r--glances/plugins/glances_processcount.py13
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 ")