From 38621f6c0fa6493037ed90d5ad1ec3513f9b3f5e Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 12 May 2020 18:36:27 +0200 Subject: [PATCH 1/8] First version of processus selector (no action for the moment) --- glances/outputs/glances_curses.py | 84 +++++++++++++++++++------- glances/plugins/glances_processlist.py | 25 +++++--- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index ff07a642..39ea3f62 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -50,10 +50,13 @@ class _GlancesCurses(object): """ _hotkeys = { + # 'ENTER' > Edit the process filter '0': {'switch': 'disable_irix'}, '1': {'switch': 'percpu'}, '2': {'switch': 'disable_left_sidebar'}, '3': {'switch': 'disable_quicklook'}, + # '4' > Enable or disable quicklook + # '5' > Enable or disable top menu '6': {'switch': 'meangpu'}, '/': {'switch': 'process_short_name'}, 'a': {'sort_key': 'auto'}, @@ -64,6 +67,9 @@ class _GlancesCurses(object): 'C': {'switch': 'disable_cloud'}, 'd': {'switch': 'disable_diskio'}, 'D': {'switch': 'disable_docker'}, + # 'e' > Enable/Disable process extended + # 'E' > Erase the process filter + # 'f' > Show/hide fs / folder stats 'F': {'switch': 'fs_free_space'}, 'g': {'switch': 'generate_graph'}, 'G': {'switch': 'disable_gpu'}, @@ -71,6 +77,7 @@ class _GlancesCurses(object): 'i': {'sort_key': 'io_counters'}, 'I': {'switch': 'disable_ip'}, 'k': {'switch': 'disable_connections'}, + # 'K' > Kill selected process 'l': {'switch': 'disable_alert'}, 'm': {'sort_key': 'memory_percent'}, 'M': {'switch': 'reset_minmax_tag'}, @@ -78,6 +85,7 @@ class _GlancesCurses(object): 'N': {'switch': 'disable_now'}, 'p': {'sort_key': 'name'}, 'P': {'switch': 'disable_ports'}, + # 'q' or ESCAPE > Quit 'Q': {'switch': 'enable_irq'}, 'r': {'switch': 'disable_smart'}, 'R': {'switch': 'disable_raid'}, @@ -87,7 +95,14 @@ class _GlancesCurses(object): 'T': {'switch': 'network_sum'}, 'u': {'sort_key': 'username'}, 'U': {'switch': 'network_cumul'}, + # 'w' > Delete finished warning logs 'W': {'switch': 'disable_wifi'}, + # 'x' > Delete finished warning and critical logs + # 'z' > Enable or disable processes + # "<" (left arrow) navigation through process sort + # ">" (right arrow) navigation through process sort + # 'UP' > Up in the server list + # 'DOWN' > Down in the server list } _sort_loop = ['cpu_percent', 'memory_percent', 'username', @@ -144,9 +159,15 @@ class _GlancesCurses(object): # Init edit filter tag self.edit_filter = False + # Init kill process tag + self.kill_process = False + # Init the process min/max reset self.args.reset_minmax_tag = False + # Init cursor + self.args.cursor_position = 0 + # Catch key pressed with non blocking mode self.term_window.keypad(1) self.term_window.nodelay(1) @@ -188,6 +209,8 @@ class _GlancesCurses(object): try: if hasattr(curses, 'start_color'): curses.start_color() + logger.debug( + 'Curses interface compatible with {} colors'.format(curses.COLORS)) if hasattr(curses, 'use_default_colors'): curses.use_default_colors() except Exception as e: @@ -226,35 +249,35 @@ class _GlancesCurses(object): curses.init_pair(8, curses.COLOR_BLUE, -1) # Colors text styles - if curses.COLOR_PAIRS > 8: - try: - curses.init_pair(9, curses.COLOR_MAGENTA, -1) - except Exception: - if self.is_theme('white'): - curses.init_pair(9, curses.COLOR_BLACK, -1) - else: - curses.init_pair(9, curses.COLOR_WHITE, -1) - try: - curses.init_pair(10, curses.COLOR_CYAN, -1) - except Exception: - if self.is_theme('white'): - curses.init_pair(10, curses.COLOR_BLACK, -1) - else: - curses.init_pair(10, curses.COLOR_WHITE, -1) - - self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD - self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD - self.filter_color = curses.color_pair(10) | A_BOLD - self.no_color = curses.color_pair(1) self.default_color = curses.color_pair(3) | A_BOLD - self.nice_color = curses.color_pair(9) - self.cpu_time_color = curses.color_pair(9) + self.nice_color = curses.color_pair(5) + self.cpu_time_color = curses.color_pair(5) self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD self.ifWARNING_color = curses.color_pair(5) | A_BOLD self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD self.default_color2 = curses.color_pair(7) self.ifCAREFUL_color2 = curses.color_pair(8) | A_BOLD + self.ifWARNING_color2 = curses.color_pair(5) | A_BOLD + self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD + self.filter_color = A_BOLD + self.selected_color = A_BOLD + + if curses.COLOR_PAIRS > 8: + colors_list = [curses.COLOR_MAGENTA, curses.COLOR_CYAN, curses.COLOR_YELLOW] + for i in range(0, 3): + try: + curses.init_pair(i + 9, colors_list[i], -1) + except Exception: + if self.is_theme('white'): + curses.init_pair(i + 9, curses.COLOR_BLACK, -1) + else: + curses.init_pair(i + 9, curses.COLOR_WHITE, -1) + self.nice_color = curses.color_pair(9) + self.cpu_time_color = curses.color_pair(9) + self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD + self.filter_color = curses.color_pair(10) | A_BOLD + self.selected_color = curses.color_pair(11) | A_BOLD else: # The screen is NOT compatible with a colored design @@ -271,6 +294,7 @@ class _GlancesCurses(object): self.ifWARNING_color2 = A_BOLD self.ifCRITICAL_color2 = curses.A_REVERSE self.filter_color = A_BOLD + self.selected_color = A_BOLD # Define the colors list (hash table) for stats self.colors_list = { @@ -293,7 +317,8 @@ class _GlancesCurses(object): 'CAREFUL_LOG': self.ifCAREFUL_color, 'WARNING_LOG': self.ifWARNING_color, 'CRITICAL_LOG': self.ifCRITICAL_color, - 'PASSWORD': curses.A_PROTECT + 'PASSWORD': curses.A_PROTECT, + 'SELECTED': self.selected_color } def set_cursor(self, value): @@ -341,12 +366,14 @@ class _GlancesCurses(object): # 'ENTER' > Edit the process filter self.edit_filter = not self.edit_filter elif self.pressedkey == ord('4'): + # '4' > Enable or disable quicklook self.args.full_quicklook = not self.args.full_quicklook if self.args.full_quicklook: self.enable_fullquicklook() else: self.disable_fullquicklook() elif self.pressedkey == ord('5'): + # '5' > Enable or disable top menu self.args.disable_top = not self.args.disable_top if self.args.disable_top: self.disable_top() @@ -366,6 +393,9 @@ class _GlancesCurses(object): # 'f' > Show/hide fs / folder stats self.args.disable_fs = not self.args.disable_fs self.args.disable_folders = not self.args.disable_folders + elif self.pressedkey == ord('K'): + # 'K' > Kill selected process (after confirmation) + self.kill_process = not self.kill_process elif self.pressedkey == ord('w'): # 'w' > Delete finished warning logs glances_events.clean() @@ -387,6 +417,14 @@ class _GlancesCurses(object): # ">" (right arrow) navigation through process sort next_sort = (self.loop_position() + 1) % len(self._sort_loop) glances_processes.set_sort_key(self._sort_loop[next_sort], False) + elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65: + # 'UP' > Up in the server list + if self.args.cursor_position > 0: + self.args.cursor_position -= 1 + elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66: + # 'DOWN' > Down in the server list + if self.args.cursor_position < glances_processes.max_processes - 2: + self.args.cursor_position += 1 # Return the key code return self.pressedkey diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 47029f40..10cb89de 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -76,6 +76,7 @@ class Plugin(GlancesPlugin): # Define the stat layout of the processes list columns layout_stat = { 'cpu': '{:<6.1f} ', + 'cpu_no_digit': '{:<6.0f} ', 'mem': '{:<5.1f} ', 'virt': '{:<5} ', 'res': '{:<5} ', @@ -172,19 +173,23 @@ class Plugin(GlancesPlugin): pass return 'DEFAULT' - def get_process_curses_data(self, p, first, args): + def get_process_curses_data(self, p, selected, args): """Get curses data to display for a process. - p is the process to display - - first is a tag=True if the process is the first on the list + - selected is a tag=True if the selected process """ ret = [self.curse_new_line()] + # Selected or not selected, that the question... + ret.append(self.curse_add_line('>' if selected else ' ', 'SELECTED')) # CPU if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '': + cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit'] if args.disable_irix and self.nb_log_core != 0: - msg = self.layout_stat['cpu'].format(p['cpu_percent'] / float(self.nb_log_core)) + msg = cpu_layout.format( + p['cpu_percent'] / float(self.nb_log_core)) else: - msg = self.layout_stat['cpu'].format(p['cpu_percent']) + msg = cpu_layout.format(p['cpu_percent']) alert = self.get_alert(p['cpu_percent'], highlight_zero=False, is_max=(p['cpu_percent'] == self.max_values['cpu_percent']), @@ -342,7 +347,7 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line('', splittable=True)) # Add extended stats but only for the top processes - if first and 'extended_stats' in p and args.enable_process_extended: + if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended: # Left padding xpad = ' ' * 13 # First line is CPU affinity @@ -429,11 +434,13 @@ class Plugin(GlancesPlugin): # Process list # Loop over processes (sorted by the sort key previously compute) - first = True + i = 0 for p in self.__sort_stats(process_sort_key): - ret.extend(self.get_process_curses_data(p, first, args)) - # End of extended stats - first = False + ret.extend(self.get_process_curses_data( + p, i == args.cursor_position, args)) + i += 1 + + # A filter is set Display the stats summaries if glances_processes.process_filter is not None: if args.reset_minmax_tag: args.reset_minmax_tag = not args.reset_minmax_tag From c57dca709dfe1a9f7f6d745b924b9bd118511040 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 17 May 2020 12:16:16 +0200 Subject: [PATCH 2/8] First version when click on K --- glances/outputs/glances_curses.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 39ea3f62..e914f03c 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -636,6 +636,22 @@ class _GlancesCurses(object): self.display_popup('Process filter only available in standalone mode') self.edit_filter = False + # Display kill process confirmation popup + # Only in standalone mode (cs_status is None) + if self.kill_process and cs_status is None: + selected_process_raw = stats.get_plugin('processlist').get_raw()[ + self.args.cursor_position] + confirm = self.display_popup( + 'Kill process: {} (pid: {})\n\n Please confirm [Y]es / [N]o'.format( + selected_process_raw['name'], + selected_process_raw['pid']), + is_input=True) + elif self.kill_process and cs_status is not None: + self.display_popup( + 'Kill process only available in standalone mode') + self.kill_process = False + + # Display graph generation popup if self.args.generate_graph: self.display_popup('Generate graph in {}'.format(self.args.export_graph_path)) From ea3dc43fd218721990dd2a0d11e09742652e63a7 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Thu, 21 May 2020 14:41:41 +0200 Subject: [PATCH 3/8] Missing temperature #1664 --- docs/aoa/sensors.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/aoa/sensors.rst b/docs/aoa/sensors.rst index 9aca0b5d..3ecdf685 100644 --- a/docs/aoa/sensors.rst +++ b/docs/aoa/sensors.rst @@ -18,3 +18,6 @@ There is no alert on this information. .. note:: This plugin is disabled by default in the configuration file. + To enable it just use the following option: + + # glances --enable-plugin sensors From 591ae02f8e6d63f8623b59d94a0a0a569ba0bf1a Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 13 Jun 2020 16:25:23 +0200 Subject: [PATCH 4/8] First working version of the kill feature --- glances/outputs/glances_curses.py | 85 +++++++++++++++++++++++-------- glances/processes.py | 8 +++ 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index e914f03c..7b0869c1 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -356,13 +356,7 @@ class _GlancesCurses(object): self._hotkeys[hotkey]['sort_key'] == 'auto') # Other actions... - if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'): - # 'ESC'|'q' > Quit - if return_to_browser: - logger.info("Stop Glances client and return to the browser") - else: - logger.info("Stop Glances (keypressed: {})".format(self.pressedkey)) - elif self.pressedkey == ord('\n'): + if self.pressedkey == ord('\n'): # 'ENTER' > Edit the process filter self.edit_filter = not self.edit_filter elif self.pressedkey == ord('4'): @@ -425,6 +419,14 @@ class _GlancesCurses(object): # 'DOWN' > Down in the server list if self.args.cursor_position < glances_processes.max_processes - 2: self.args.cursor_position += 1 + elif self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'): + # 'ESC'|'q' > Quit + if return_to_browser: + logger.info("Stop Glances client and return to the browser") + else: + logger.info( + "Stop Glances (keypressed: {})".format(self.pressedkey)) + # Return the key code return self.pressedkey @@ -629,7 +631,7 @@ class _GlancesCurses(object): '- cmdline:.*glances.*\n' + '- username:nicolargo\n' + '- username:^root ', - is_input=True, + popup_type='input', input_value=glances_processes.process_filter_input) glances_processes.process_filter = new_filter elif self.edit_filter and cs_status is not None: @@ -642,10 +644,20 @@ class _GlancesCurses(object): selected_process_raw = stats.get_plugin('processlist').get_raw()[ self.args.cursor_position] confirm = self.display_popup( - 'Kill process: {} (pid: {})\n\n Please confirm [Y]es / [N]o'.format( + 'Kill process: {} (pid: {}) ?\n\nConfirm ([y]es/[n]o): '.format( selected_process_raw['name'], selected_process_raw['pid']), - is_input=True) + popup_type='yesno') + if confirm.lower().startswith('y'): + try: + ret_kill = glances_processes.kill(selected_process_raw['pid']) + except Exception as e: + logger.error('Can not kill process {} ({})'.format( + selected_process_raw['name'], e)) + else: + logger.info('Kill signal has been sent to process {} (return code: {})'.format( + selected_process_raw['name'], ret_kill)) + elif self.kill_process and cs_status is not None: self.display_popup( 'Kill process only available in standalone mode') @@ -807,30 +819,37 @@ class _GlancesCurses(object): def display_popup(self, message, size_x=None, size_y=None, duration=3, - is_input=False, + popup_type='info', input_size=30, input_value=None): """ Display a centered popup. - If is_input is False: + popup_type='info' + Just an infotmation popup, no user interaction 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: + popup_type='input' 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 + + popup_type='yesno' + Display a centered popup with the given message + If size_x and size_y: set the popup size + else set it automatically + Return True (yes) or False (no) """ # Center the popup sentence_list = message.split('\n') if size_x is None: size_x = len(max(sentence_list, key=len)) + 4 # Add space for the input field - if is_input: + if popup_type == 'input': size_x += input_size if size_y is None: size_y = len(sentence_list) + 4 @@ -849,10 +868,15 @@ class _GlancesCurses(object): popup.border() # Add the message - for y, m in enumerate(message.split('\n')): + for y, m in enumerate(sentence_list): popup.addnstr(2 + y, 2, m, len(m)) - if is_input: + if popup_type == 'info': + # Display the popup + popup.refresh() + self.wait(duration * 1000) + return True + elif popup_type == 'input': # Create a subwindow for the text field subpop = popup.derwin(1, input_size, 2, 2 + len(m)) subpop.attron(self.colors_list['FILTER']) @@ -868,7 +892,7 @@ class _GlancesCurses(object): textbox = GlancesTextbox(subpop, insert_mode=False) textbox.edit() self.set_cursor(0) - self.term_window.keypad(0) + # self.term_window.keypad(0) if textbox.gather() != '': logger.debug( "User enters the following string: %s" % textbox.gather()) @@ -876,11 +900,23 @@ class _GlancesCurses(object): else: logger.debug("User centers an empty string") return None - else: + elif popup_type == 'yesno': + # # Create a subwindow for the text field + subpop = popup.derwin(1, 2, len(sentence_list) + 1, len(m) + 2) + subpop.attron(self.colors_list['FILTER']) + # Init the field with the current value + subpop.addnstr(0, 0, '', 0) # Display the popup popup.refresh() - self.wait(duration * 1000) - return True + subpop.refresh() + # Create the textbox inside the subwindows + self.set_cursor(2) + self.term_window.keypad(1) + textbox = GlancesTextboxYesNo(subpop, insert_mode=False) + textbox.edit() + self.set_cursor(0) + # self.term_window.keypad(0) + return textbox.gather() def display_plugin(self, plugin_stats, display_optional=True, @@ -1099,3 +1135,12 @@ class GlancesTextbox(Textbox, object): if ch == 127: # Back return 8 return super(GlancesTextbox, self).do_command(ch) + + +class GlancesTextboxYesNo(Textbox, object): + + def __init__(self, *args, **kwargs): + super(GlancesTextboxYesNo, self).__init__(*args, **kwargs) + + def do_command(self, ch): + return super(GlancesTextboxYesNo, self).do_command(ch) diff --git a/glances/processes.py b/glances/processes.py index f1291d59..6c05c39f 100644 --- a/glances/processes.py +++ b/glances/processes.py @@ -398,6 +398,14 @@ class GlancesProcesses(object): else: self.auto_sort = auto self._sort_key = key + + def kill(self, pid, timeout=3): + """Kill process with pid""" + assert pid != os.getpid(), "Glances can kill itself..." + p = psutil.Process(pid) + logger.debug('Send kill signal to process: {}'.format(p)) + p.kill() + return p.wait(timeout) def weighted(value): From f7abdea9d86024fc9a1b8652a2963667b6ecfb74 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 13 Jun 2020 16:54:38 +0200 Subject: [PATCH 5/8] Selected process is now undelined --- glances/outputs/glances_curses.py | 1 + glances/plugins/glances_processlist.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 7b0869c1..e9bcc871 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -307,6 +307,7 @@ class _GlancesCurses(object): 'FILTER': self.filter_color, 'TITLE': self.title_color, 'PROCESS': self.default_color2, + 'PROCESS_SELECTED': self.default_color2 | curses.A_UNDERLINE, 'STATUS': self.default_color2, 'NICE': self.nice_color, 'CPU_TIME': self.cpu_time_color, diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 10cb89de..d2ae387a 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -75,8 +75,8 @@ class Plugin(GlancesPlugin): # Define the stat layout of the processes list columns layout_stat = { - 'cpu': '{:<6.1f} ', - 'cpu_no_digit': '{:<6.0f} ', + 'cpu': '{:<6.1f}', + 'cpu_no_digit': '{:<6.0f}', 'mem': '{:<5.1f} ', 'virt': '{:<5} ', 'res': '{:<5} ', @@ -180,7 +180,9 @@ class Plugin(GlancesPlugin): - selected is a tag=True if the selected process """ ret = [self.curse_new_line()] - # Selected or not selected, that the question... + # When a process is selected: + # * display a special caracter at the beginning of the line + # * underline the command name ret.append(self.curse_add_line('>' if selected else ' ', 'SELECTED')) # CPU if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '': @@ -326,21 +328,24 @@ class Plugin(GlancesPlugin): else: cmdline = '?' try: + process_decoration = 'PROCESS_SELECTED' if selected else 'PROCESS' if cmdline: path, cmd, arguments = split_cmdline(cmdline) if os.path.isdir(path) and not args.process_short_name: msg = self.layout_stat['command'].format(path) + os.sep ret.append(self.curse_add_line(msg, splittable=True)) - ret.append(self.curse_add_line(cmd, decoration='PROCESS', splittable=True)) + ret.append(self.curse_add_line( + cmd, decoration=process_decoration, splittable=True)) else: msg = self.layout_stat['command'].format(cmd) - ret.append(self.curse_add_line(msg, decoration='PROCESS', splittable=True)) + ret.append(self.curse_add_line( + msg, decoration=process_decoration, splittable=True)) if arguments: msg = ' ' + self.layout_stat['command'].format(arguments) ret.append(self.curse_add_line(msg, splittable=True)) else: msg = self.layout_stat['name'].format(p['name']) - ret.append(self.curse_add_line(msg, splittable=True)) + ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True)) except (TypeError, UnicodeEncodeError) as e: # Avoid crach after running fine for several hours #1335 logger.debug("Can not decode command line '{}' ({})".format(cmdline, e)) From 16016598ad27e7ce70773188bfe3a2960298b4f6 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 20 Jul 2020 09:15:50 +0200 Subject: [PATCH 6/8] Update documentation for 'k' hotkey (kill process) --- conf/glances.conf | 1 + docs/aoa/ps.rst | 3 +++ docs/gw/rabbitmq.rst | 1 + docs/man/glances.1 | 11 ++++++++++- glances/outputs/glances_curses.py | 8 ++++---- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/conf/glances.conf b/conf/glances.conf index 7ed466ae..b41a5a1a 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -461,6 +461,7 @@ port=5672 user=guest password=guest queue=glances_queue +#protocol=amqps [mqtt] # Configuration for the --export mqtt option diff --git a/docs/aoa/ps.rst b/docs/aoa/ps.rst index 14906147..c9daacb2 100644 --- a/docs/aoa/ps.rst +++ b/docs/aoa/ps.rst @@ -128,6 +128,9 @@ The extended stats feature can be enabled using the ``--enable-process-extended`` option (command line) or the ``e`` key (curses interface). +In curses/standalone mode, you can select a process using ``UP`` and ``DOWN`` and press: +- ``k`` to kill the selected process + .. note:: Limit for CPU and MEM percent values can be overwritten in the configuration file under the ``[processlist]`` section. It is also diff --git a/docs/gw/rabbitmq.rst b/docs/gw/rabbitmq.rst index 173b9b3c..30ff7911 100644 --- a/docs/gw/rabbitmq.rst +++ b/docs/gw/rabbitmq.rst @@ -15,6 +15,7 @@ following: user=glances password=glances queue=glances_queue + #protocol=amqps and run Glances with: diff --git a/docs/man/glances.1 b/docs/man/glances.1 index cbc27a3a..abe6895c 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "GLANCES" "1" "May 12, 2020" "3.1.5_DEVELOP" "Glances" +.TH "GLANCES" "1" "Jul 20, 2020" "3.1.5_DEVELOP" "Glances" .SH NAME glances \- An eye on your system . @@ -395,6 +395,9 @@ Sort processes by I/O rate Show/hide IP module .TP .B \fBk\fP +Kill selected process (only in curses/standalone mode) +.TP +.B \fBK\fP Show/hide TCP connections .TP .B \fBl\fP @@ -479,6 +482,12 @@ Enable/disable mean GPU mode .TP .B \fB/\fP Switch between process command line or command name +.TP +.B \fBUP\fP +Up in the processes list +.TP +.B \fBDOWN\fP +Down in the processes list .UNINDENT .sp In the Glances client browser (accessible through the \fB\-\-browser\fP diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index e9bcc871..2a1afce3 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -76,8 +76,8 @@ class _GlancesCurses(object): 'h': {'switch': 'help_tag'}, 'i': {'sort_key': 'io_counters'}, 'I': {'switch': 'disable_ip'}, - 'k': {'switch': 'disable_connections'}, - # 'K' > Kill selected process + # 'k' > Kill selected process + 'K': {'switch': 'disable_connections'}, 'l': {'switch': 'disable_alert'}, 'm': {'sort_key': 'memory_percent'}, 'M': {'switch': 'reset_minmax_tag'}, @@ -388,8 +388,8 @@ class _GlancesCurses(object): # 'f' > Show/hide fs / folder stats self.args.disable_fs = not self.args.disable_fs self.args.disable_folders = not self.args.disable_folders - elif self.pressedkey == ord('K'): - # 'K' > Kill selected process (after confirmation) + elif self.pressedkey == ord('k'): + # 'k' > Kill selected process (after confirmation) self.kill_process = not self.kill_process elif self.pressedkey == ord('w'): # 'w' > Delete finished warning logs From 492db136d96cd8dfcc82da0f3b00077215ae1437 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 20 Jul 2020 09:55:03 +0200 Subject: [PATCH 7/8] Display 'k' kill process help message in the Curses UI --- glances/main.py | 7 +++++++ glances/plugins/glances_processlist.py | 10 ++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/glances/main.py b/glances/main.py index a869e3cb..3004aa66 100644 --- a/glances/main.py +++ b/glances/main.py @@ -419,6 +419,13 @@ Examples of use: disable(args, 'hddtemp') logger.debug("Sensors and HDDTemp are disabled") + # Let the plugins known the Glances mode + self.args.is_standalone = self.is_standalone() + self.args.is_client = self.is_client() + self.args.is_client_browser = self.is_client_browser() + self.args.is_server = self.is_server() + self.args.is_webserver = self.is_webserver() + return args def is_standalone(self): diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index d2ae387a..a4436972 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -70,7 +70,7 @@ class Plugin(GlancesPlugin): 'status': '{:>1} ', 'ior': '{:>4} ', 'iow': '{:<4} ', - 'command': '{}', + 'command': '{} {}', } # Define the stat layout of the processes list columns @@ -183,7 +183,8 @@ class Plugin(GlancesPlugin): # When a process is selected: # * display a special caracter at the beginning of the line # * underline the command name - ret.append(self.curse_add_line('>' if selected else ' ', 'SELECTED')) + if args.is_standalone: + ret.append(self.curse_add_line('>' if selected else ' ', 'SELECTED')) # CPU if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '': cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit'] @@ -328,7 +329,7 @@ class Plugin(GlancesPlugin): else: cmdline = '?' try: - process_decoration = 'PROCESS_SELECTED' if selected else 'PROCESS' + process_decoration = 'PROCESS_SELECTED' if (selected and args.is_standalone) else 'PROCESS' if cmdline: path, cmd, arguments = split_cmdline(cmdline) if os.path.isdir(path) and not args.process_short_name: @@ -490,7 +491,8 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True)) msg = self.layout_header['iow'].format('W/s') ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True)) - msg = self.layout_header['command'].format('Command') + msg = self.layout_header['command'].format('Command', + "('k' to kill)" if args.is_standalone else "") 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): From 5646379bd7e93c5d44c16de07ba67bedd6002adf Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 20 Jul 2020 10:19:50 +0200 Subject: [PATCH 8/8] Make the kill feature works while in filtered mode --- glances/outputs/glances_curses.py | 3 ++- glances/processes.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 2a1afce3..2f892894 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -418,7 +418,8 @@ class _GlancesCurses(object): self.args.cursor_position -= 1 elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66: # 'DOWN' > Down in the server list - if self.args.cursor_position < glances_processes.max_processes - 2: + # if self.args.cursor_position < glances_processes.max_processes - 2: + if self.args.cursor_position < glances_processes.processes_count: self.args.cursor_position += 1 elif self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'): # 'ESC'|'q' > Quit diff --git a/glances/processes.py b/glances/processes.py index 6c05c39f..b7dfe70d 100644 --- a/glances/processes.py +++ b/glances/processes.py @@ -176,6 +176,11 @@ class GlancesProcesses(object): else: return None + @property + def processes_count(self): + """Get the current number of processes showed in the UI.""" + return min(self._max_processes - 2, glances_processes.processcount['total'] - 1) + @property def max_processes(self): """Get the maximum number of processes showed in the UI."""