From 41342c813308a95c8cc4904f7948206133b4d670 Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Thu, 16 Oct 2014 22:34:44 +0200 Subject: [PATCH] Add Network, FS, diskio to the graph generation feature --- glances/core/glances_main.py | 2 +- glances/outputs/glances_curses.py | 144 +++++++++++++++++------------ glances/outputs/glances_history.py | 140 ++++++++++++++++++++++------ glances/plugins/glances_cpu.py | 8 +- glances/plugins/glances_diskio.py | 11 ++- glances/plugins/glances_fs.py | 22 +++-- glances/plugins/glances_memswap.py | 45 ++++++--- glances/plugins/glances_network.py | 17 +++- glances/plugins/glances_plugin.py | 72 +++++++++------ 9 files changed, 312 insertions(+), 149 deletions(-) diff --git a/glances/core/glances_main.py b/glances/core/glances_main.py index 21c633a5..e4ccff93 100644 --- a/glances/core/glances_main.py +++ b/glances/core/glances_main.py @@ -197,7 +197,7 @@ class GlancesMain(object): if not os.access(args.path_history, os.W_OK): logger.critical(_("History output path (%s) do not exist or is not writable") % args.path_history) sys.exit(2) - logger.info(_("History output path is %s") % args.path_history) + logger.debug(_("History output path is set to %s") % args.path_history) return args diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 76b6d3f7..38b3fd0a 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -23,7 +23,7 @@ import sys # Import Glances lib -from glances.core.glances_globals import glances_logs, glances_processes, is_linux, is_bsd, is_mac, is_windows, logger +from glances.core.glances_globals import glances_logs, glances_processes, is_mac, is_windows, logger from glances.core.glances_timer import Timer # Import curses lib for "normal" operating system and consolelog for Windows @@ -31,9 +31,10 @@ if not is_windows: try: import curses import curses.panel - from curses.textpad import Textbox, rectangle + from curses.textpad import Textbox except ImportError: - logger.critical('Curses module not found. Glances cannot start in standalone mode.') + logger.critical( + 'Curses module not found. Glances cannot start in standalone mode.') sys.exit(1) else: from glances.outputs.glances_colorconsole import WCurseLight @@ -184,15 +185,17 @@ class GlancesCurses(object): self.reset_history_tag = False self.history_tag = False if args.enable_history: - logger.info('Stats history enabled with output path %s' % args.path_history) + logger.info('Stats history enabled with output path %s' % + args.path_history) from glances.outputs.glances_history import GlancesHistory self.glances_history = GlancesHistory(args.path_history) if not self.glances_history.graph_enabled(): args.enable_history = False - logger.error('Stats history disabled because MatPlotLib is not installed') + logger.error( + 'Stats history disabled because MatPlotLib is not installed') def set_cursor(self, value): - """Configure the cursor + """Configure the cursor 0: invisible 1: visible 2: very visible @@ -234,7 +237,7 @@ class GlancesCurses(object): self.args.percpu = not self.args.percpu elif self.pressedkey == ord('2'): # '2' > Enable/disable left sidebar - self.args.disable_left_sidebar = not self.args.disable_left_sidebar + self.args.disable_left_sidebar = not self.args.disable_left_sidebar elif self.pressedkey == ord('/'): # '/' > Switch between short/long name for processes self.args.process_short_name = not self.args.process_short_name @@ -289,7 +292,7 @@ class GlancesCurses(object): glances_processes.setmanualsortkey(self.args.process_sorted_by) elif self.pressedkey == ord('r'): # 'r' > Reset history - self.reset_history_tag = not self.reset_history_tag + self.reset_history_tag = not self.reset_history_tag elif self.pressedkey == ord('s'): # 's' > Show/hide sensors stats (Linux-only) self.args.disable_sensors = not self.args.disable_sensors @@ -318,7 +321,7 @@ class GlancesCurses(object): return self.pressedkey def end(self): - """Shutdown the curses window.""" + """Shutdown the curses window.""" if hasattr(curses, 'echo'): curses.echo() if hasattr(curses, 'nocbreak'): @@ -329,7 +332,7 @@ class GlancesCurses(object): except Exception: pass curses.endwin() - + def init_line_column(self): """Init the line and column position for the curses inteface""" self.line = 0 @@ -386,10 +389,10 @@ class GlancesCurses(object): # Update the stats messages ########################### - # Update the client server status self.args.cs_status = cs_status - stats_system = stats.get_plugin('system').get_stats_display(args=self.args) + stats_system = stats.get_plugin( + 'system').get_stats_display(args=self.args) stats_uptime = stats.get_plugin('uptime').get_stats_display() if self.args.percpu: stats_percpu = stats.get_plugin('percpu').get_stats_display() @@ -398,27 +401,37 @@ class GlancesCurses(object): stats_load = stats.get_plugin('load').get_stats_display() stats_mem = stats.get_plugin('mem').get_stats_display() stats_memswap = stats.get_plugin('memswap').get_stats_display() - stats_network = stats.get_plugin('network').get_stats_display(args=self.args, max_width=plugin_max_width) - stats_diskio = stats.get_plugin('diskio').get_stats_display(args=self.args) - stats_fs = stats.get_plugin('fs').get_stats_display(args=self.args, max_width=plugin_max_width) - stats_sensors = stats.get_plugin('sensors').get_stats_display(args=self.args) + stats_network = stats.get_plugin('network').get_stats_display( + args=self.args, max_width=plugin_max_width) + stats_diskio = stats.get_plugin( + 'diskio').get_stats_display(args=self.args) + stats_fs = stats.get_plugin('fs').get_stats_display( + args=self.args, max_width=plugin_max_width) + stats_sensors = stats.get_plugin( + 'sensors').get_stats_display(args=self.args) stats_now = stats.get_plugin('now').get_stats_display() - stats_processcount = stats.get_plugin('processcount').get_stats_display(args=self.args) - stats_monitor = stats.get_plugin('monitor').get_stats_display(args=self.args) - stats_alert = stats.get_plugin('alert').get_stats_display(args=self.args) + stats_processcount = stats.get_plugin( + 'processcount').get_stats_display(args=self.args) + stats_monitor = stats.get_plugin( + 'monitor').get_stats_display(args=self.args) + stats_alert = stats.get_plugin( + 'alert').get_stats_display(args=self.args) # Adapt number of processes to the available space - max_processes_displayed = screen_y - 11 - self.get_stats_display_height(stats_alert) + max_processes_displayed = screen_y - 11 - \ + self.get_stats_display_height(stats_alert) if not self.args.disable_process_extended: max_processes_displayed -= 4 if max_processes_displayed < 0: - max_processes_displayed = 0 + max_processes_displayed = 0 if glances_processes.get_max_processes() is None or \ glances_processes.get_max_processes() != max_processes_displayed: - logger.debug(_("Set number of displayed processes to %s") % max_processes_displayed) + logger.debug( + _("Set number of displayed processes to %s") % max_processes_displayed) glances_processes.set_max_processes(max_processes_displayed) - stats_processlist = stats.get_plugin('processlist').get_stats_display(args=self.args) + stats_processlist = stats.get_plugin( + 'processlist').get_stats_display(args=self.args) # Display the stats on the curses interface ########################################### @@ -426,13 +439,15 @@ class GlancesCurses(object): # Help screen (on top of the other stats) if self.args.help_tag: # Display the stats... - self.display_plugin(stats.get_plugin('help').get_stats_display(args=self.args)) + self.display_plugin( + stats.get_plugin('help').get_stats_display(args=self.args)) # ... and exit return False # Display first line (system+uptime) self.new_line() - l = self.get_stats_display_width(stats_system) + self.get_stats_display_width(stats_uptime) + self.space_between_column + l = self.get_stats_display_width( + stats_system) + self.get_stats_display_width(stats_uptime) + self.space_between_column self.display_plugin(stats_system, display_optional=(screen_x >= l)) self.new_column() self.display_plugin(stats_uptime) @@ -440,14 +455,16 @@ class GlancesCurses(object): # Display second line (CPU|PERCPU+LOAD+MEM+SWAP+) # CPU|PERCPU self.init_column() - self.new_line() + self.new_line() if self.args.percpu: l = self.get_stats_display_width(stats_percpu) else: l = self.get_stats_display_width(stats_cpu) - l += self.get_stats_display_width(stats_load) + self.get_stats_display_width(stats_mem) + self.get_stats_display_width(stats_memswap) + l += self.get_stats_display_width(stats_load) + self.get_stats_display_width( + stats_mem) + self.get_stats_display_width(stats_memswap) # Space between column - space_number = int(stats_load['msgdict'] != []) + int(stats_mem['msgdict'] != []) + int(stats_memswap['msgdict'] != []) + space_number = int(stats_load['msgdict'] != [ + ]) + int(stats_mem['msgdict'] != []) + int(stats_memswap['msgdict'] != []) if space_number == 0: space_number = 1 if screen_x > (space_number * self.space_between_column + l): @@ -460,7 +477,8 @@ class GlancesCurses(object): self.new_column() self.display_plugin(stats_load) self.new_column() - self.display_plugin(stats_mem, display_optional=(screen_x >= (space_number * self.space_between_column + l))) + self.display_plugin(stats_mem, display_optional=( + screen_x >= (space_number * self.space_between_column + l))) self.new_column() self.display_plugin(stats_memswap) # Space between column @@ -471,9 +489,9 @@ class GlancesCurses(object): # Display left sidebar (NETWORK+DISKIO+FS+SENSORS+Current time) self.init_column() - if (not (self.args.disable_network and self.args.disable_diskio \ - and self.args.disable_fs and self.args.disable_sensors)) \ - and not self.args.disable_left_sidebar: + if (not (self.args.disable_network and self.args.disable_diskio + and self.args.disable_fs and self.args.disable_sensors)) \ + and not self.args.disable_left_sidebar: self.new_line() self.display_plugin(stats_network) self.new_line() @@ -490,18 +508,19 @@ class GlancesCurses(object): # Restore line position self.next_line = self.saved_line - # Display right sidebar (PROCESS_COUNT+MONITORED+PROCESS_LIST+ALERT) + # Display right sidebar + # (PROCESS_COUNT+MONITORED+PROCESS_LIST+ALERT) self.new_column() self.new_line() self.display_plugin(stats_processcount) - if glances_processes.get_process_filter() == None and cs_status == 'None': + if glances_processes.get_process_filter() is None and cs_status == 'None': # Do not display stats monitor list if a filter exist self.new_line() self.display_plugin(stats_monitor) self.new_line() self.display_plugin(stats_processlist, display_optional=(screen_x > 102), - display_additional=(is_mac == False), + display_additional=(not is_mac), max_y=(screen_y - self.get_stats_display_height(stats_alert) - 2)) self.new_line() self.display_plugin(stats_alert) @@ -509,36 +528,40 @@ class GlancesCurses(object): # History option # Generate history graph if self.history_tag and self.args.enable_history: - self.display_popup(_("Graphs history generated in %s") % self.glances_history.get_output_folder()) + self.display_popup( + _("Generate graphs history in %s\nPlease wait...") % self.glances_history.get_output_folder()) self.glances_history.generate_graph(stats) elif self.reset_history_tag and self.args.enable_history: self.display_popup(_("Reset history")) - self.glances_history.reset(stats) + self.glances_history.reset(stats) elif (self.history_tag or self.reset_history_tag) and not self.args.enable_history: try: self.glances_history.graph_enabled() except: - self.display_popup(_("History disabled\nEnable it using --enable-history")) + self.display_popup( + _("History disabled\nEnable it using --enable-history")) else: - self.display_popup(_("History disabled\nPlease install MatPlotLib")) + self.display_popup( + _("History disabled\nPlease install MatPlotLib")) self.history_tag = False self.reset_history_tag = False # Display edit filter popup # Only in standalone mode (cs_status == 'None') if self.edit_filter and cs_status == 'None': - new_filter = self.display_popup(_("Process filter pattern: "), + new_filter = self.display_popup(_("Process filter pattern: "), is_input=True, input_value=glances_processes.get_process_filter()) glances_processes.set_process_filter(new_filter) elif self.edit_filter and cs_status != 'None': - self.display_popup(_("Process filter only available in standalone mode")) + self.display_popup( + _("Process filter only available in standalone mode")) self.edit_filter = False return True - def display_popup(self, message, - size_x=None, size_y=None, + def display_popup(self, message, + size_x=None, size_y=None, duration=3, is_input=False, input_size=30, @@ -553,7 +576,7 @@ class GlancesCurses(object): 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 + Return the input string or None if the field is empty """ # Center the popup @@ -568,13 +591,13 @@ class GlancesCurses(object): screen_y = self.screen.getmaxyx()[0] if size_x > screen_x or size_y > screen_y: # No size to display the popup => abord - return False + return False pos_x = int((screen_x - size_x) / 2) pos_y = int((screen_y - size_y) / 2) # Create the popup popup = curses.newwin(size_y, size_x, pos_y, pos_x) - + # Fill the popup popup.border() @@ -600,7 +623,8 @@ class GlancesCurses(object): textbox.edit() self.set_cursor(0) if textbox.gather() != '': - logger.debug(_("User enters the following process filter patern: %s") % 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")) @@ -611,9 +635,9 @@ class GlancesCurses(object): curses.napms(duration * 1000) return True - def display_plugin(self, plugin_stats, + def display_plugin(self, plugin_stats, display_optional=True, - display_additional=True, + display_additional=True, max_y=65535): """Display the plugin_stats on the screen. @@ -643,7 +667,7 @@ class GlancesCurses(object): display_y = screen_y - self.get_stats_display_height(plugin_stats) else: display_y = self.line - + # Display x = display_x y = display_y @@ -673,8 +697,9 @@ class GlancesCurses(object): try: self.term_window.addnstr(y, x, m['msg'], - screen_x - x, # Do not disply outside the screen - self.__colors_list[m['decoration']]) + # Do not disply outside the screen + screen_x - x, + self.__colors_list[m['decoration']]) except: pass else: @@ -721,7 +746,7 @@ class GlancesCurses(object): # Getkey if self.__catch_key() > -1: # Redraw display - self.flush(stats, cs_status=cs_status) + self.flush(stats, cs_status=cs_status) # Wait 100ms... curses.napms(100) @@ -734,11 +759,11 @@ class GlancesCurses(object): if without_option: # Size without options c = len(max(''.join([(i['msg'] if not i['optional'] else "") - for i in curse_msg['msgdict']]).split('\n'), key=len)) + for i in curse_msg['msgdict']]).split('\n'), key=len)) else: # Size with all options c = len(max(''.join([i['msg'] - for i in curse_msg['msgdict']]).split('\n'), key=len)) + for i in curse_msg['msgdict']]).split('\n'), key=len)) except: return 0 else: @@ -758,14 +783,15 @@ class GlancesCurses(object): if not is_windows: class glances_textbox(Textbox): + """ """ def __init__(*args, **kwargs): Textbox.__init__(*args, **kwargs) - + def do_command(self, ch): - if ch == 10: # Enter + if ch == 10: # Enter return 0 - if ch == 127: # Enter + if ch == 127: # Enter return 8 - return Textbox.do_command(self, ch) \ No newline at end of file + return Textbox.do_command(self, ch) diff --git a/glances/outputs/glances_history.py b/glances/outputs/glances_history.py index afc916d6..db0f7e74 100644 --- a/glances/outputs/glances_history.py +++ b/glances/outputs/glances_history.py @@ -29,10 +29,10 @@ from glances.core.glances_globals import logger try: from matplotlib import __version__ as matplotlib_version import matplotlib.pyplot as plt - import matplotlib.dates as dates except: matplotlib_check = False - logger.warning('Can not load Matplotlib library. Please install it using "pip install matplotlib"') + logger.warning( + 'Can not load Matplotlib library. Please install it using "pip install matplotlib"') else: matplotlib_check = True logger.info('Load Matplotlib version %s' % matplotlib_version) @@ -65,6 +65,28 @@ class GlancesHistory(object): stats.get_plugin(p).reset_stats_history() return True + def get_graph_color(self, item): + """ + Get the item's color + """ + try: + ret = item['color'] + except KeyError: + return '#FFFFFF' + else: + return ret + + def get_graph_ylegend(self, item): + """ + Get the item's Y legend + """ + try: + ret = item['label_y'] + except KeyError: + return '' + else: + return ' ' + ret + def generate_graph(self, stats): """ Generate graphs from plugins history @@ -74,32 +96,92 @@ class GlancesHistory(object): for p in stats.getAllPlugins(): h = stats.get_plugin(p).get_stats_history() - if h is not None: - # Build the graph - # fig = plt.figure(dpi=72) - ax = plt.subplot(1, 1, 1) - - # Label - plt.title("%s stats" % p) - - handles = [] - for i in stats.get_plugin(p).get_items_history_list(): - handles.append(plt.Rectangle((0, 0), 1, 1, fc=i['color'], ec=i['color'], linewidth=1)) - labels = [i['name'] for i in stats.get_plugin(p).get_items_history_list()] - plt.legend(handles, labels, loc=1, prop={'size': 9}) - formatter = dates.DateFormatter('%H:%M:%S') - ax.xaxis.set_major_formatter(formatter) - # ax.set_ylabel('%') - - # Draw the stats - for i in stats.get_plugin(p).get_items_history_list(): - ax.plot_date(h['date'], h[i['name']], - i['color'], - label='%s' % i['name'], - xdate=True, ydate=False) - - # Save and display - plt.savefig(os.path.join(self.output_folder, 'glances_%s.png' % p), dpi=72) - # plt.show() + # Init graph + plt.clf() + fig = plt.gcf() + fig.set_size_inches(20, 10) + # Data + if h is None: + # History (h) not available for plugin (p) + continue + index_graph = 0 + for i in stats.get_plugin(p).get_items_history_list(): + if i['name'] in h.keys(): + # The key exist + # Add the curve in the current chart + logger.debug("Generate graph: %s %s" % (p, i['name'])) + index_graph += 1 + plt.title(p.capitalize()) + plt.subplot(len(stats.get_plugin(p).get_items_history_list()), 1, index_graph) + plt.ylabel(i['name'] + self.get_graph_ylegend(i)) + plt.grid(True) + plt.plot_date(h['date'], h[i['name']], + fmt='', drawstyle='default', linestyle='-', + color=self.get_graph_color(i), + xdate=True, ydate=False) + else: + # The key did not exist + # Find if anothers key ends with the key + # Ex: key='tx' => 'ethernet_tx' + stats_history_filtered = sorted([key for key in h.keys() if key.endswith(i['name'])]) + logger.debug("Generate graphs: %s %s" % (p, stats_history_filtered)) + if len(stats_history_filtered) > 0: + # Create 'n' graph + # Each graph iter through the stats + plt.clf() + index_item = 0 + for k in stats_history_filtered: + index_item += 1 + plt.title(p.capitalize()) + plt.subplot(len(stats_history_filtered), 1, index_item) + plt.ylabel(k + self.get_graph_ylegend(i)) + plt.grid(True) + plt.plot_date(h['date'], h[k], + fmt='', drawstyle='default', linestyle='-', + color=self.get_graph_color(i), + xdate=True, ydate=False) + # Save the graph to output file + plt.xlabel('Date') + plt.savefig(os.path.join(self.output_folder, 'glances_%s_%s.png' % (p, i['name'])), dpi=72) + + if index_graph > 0: + # Save the graph to output file + plt.xlabel('Date') + plt.savefig(os.path.join(self.output_folder, 'glances_%s.png' % (p)), dpi=72) + + plt.close() + + return True + +def generate_graph_OLD(self, stats): + """ + Generate graphs from plugins history + """ + if not self.graph_enabled(): + return False + + for p in stats.getAllPlugins(): + h = stats.get_plugin(p).get_stats_history() + # Generate graph (init) + plt.clf() + # Title and axis + plt.title(p.capitalize()) + plt.grid(True) + if h is not None: + # History (h) available for plugin (p) + index = 1 + for k, v in h.iteritems(): + if k != 'date': + plt.subplot(len(h), 1, index) + index += 1 + plt.xlabel('Date') + plt.ylabel(self.get_graph_ylegend(stats, p, k)) + # Data + plt.plot_date(h['date'], h[k], + fmt='', drawstyle='default', linestyle='-', + color=self.get_graph_color(stats, p, k), + xdate=True, ydate=False) + # Save the graph to output file + plt.savefig(os.path.join(self.output_folder, 'glances_%s_%s.png' % (p, k)), dpi=72) return True diff --git a/glances/plugins/glances_cpu.py b/glances/plugins/glances_cpu.py index db4a7a43..9be4805e 100644 --- a/glances/plugins/glances_cpu.py +++ b/glances/plugins/glances_cpu.py @@ -34,10 +34,12 @@ snmp_oid = {'default': {'user': '1.3.6.1.4.1.2021.11.9.0', 'esxi': {'percent': '1.3.6.1.2.1.25.3.3.1.2'}} # Define the history items list -# 'color' define the graph color in #RGB format +# - 'name' define the stat identifier +# - 'color' define the graph color in #RGB format +# - 'label_y' define the Y label # All items in this list will be historised if the --enable-history tag is set -items_history_list = [{'name': 'user', 'color': '#00FF00'}, - {'name': 'system', 'color': '#FF0000'}] +items_history_list = [{'name': 'user', 'color': '#00FF00', 'label_y': '(%)'}, + {'name': 'system', 'color': '#FF0000', 'label_y': '(%)'}] class Plugin(GlancesPlugin): diff --git a/glances/plugins/glances_diskio.py b/glances/plugins/glances_diskio.py index 5997570f..92c33dbd 100644 --- a/glances/plugins/glances_diskio.py +++ b/glances/plugins/glances_diskio.py @@ -25,6 +25,12 @@ from glances.plugins.glances_plugin import GlancesPlugin import psutil +# Define the history items list +# All items in this list will be historised if the --enable-history tag is set +# 'color' define the graph color in #RGB format +items_history_list = [{'name': 'read_bytes', 'color': '#00FF00', 'label_y': '(B/s)'}, + {'name': 'write_bytes', 'color': '#FF0000', 'label_y': '(B/s)'}] + class Plugin(GlancesPlugin): @@ -35,7 +41,7 @@ class Plugin(GlancesPlugin): def __init__(self, args=None): """Init the plugin.""" - GlancesPlugin.__init__(self, args=args) + GlancesPlugin.__init__(self, args=args, items_history_list=items_history_list) # We want to display the stat in the curse interface self.display_curse = True @@ -104,6 +110,9 @@ class Plugin(GlancesPlugin): # No standard way for the moment... pass + # Update the history list + self.update_stats_history('disk_name') + return self.stats def msg_curse(self, args=None): diff --git a/glances/plugins/glances_fs.py b/glances/plugins/glances_fs.py index 3a12e089..a2f74433 100644 --- a/glances/plugins/glances_fs.py +++ b/glances/plugins/glances_fs.py @@ -19,11 +19,7 @@ """File system plugin.""" -# System libs -import base64 - # Glances libs -from glances.core.glances_globals import version, logger from glances.plugins.glances_plugin import GlancesPlugin # PSutil lib for local grab @@ -54,6 +50,11 @@ snmp_oid = {'default': {'mnt_point': '1.3.6.1.4.1.2021.9.1.2', 'used': '1.3.6.1.2.1.25.2.3.1.6'}} snmp_oid['esxi'] = snmp_oid['windows'] +# Define the history items list +# All items in this list will be historised if the --enable-history tag is set +# 'color' define the graph color in #RGB format +items_history_list = [{'name': 'percent', 'color': '#00FF00'}] + class Plugin(GlancesPlugin): @@ -64,7 +65,7 @@ class Plugin(GlancesPlugin): def __init__(self, args=None): """Init the plugin.""" - GlancesPlugin.__init__(self, args=args) + GlancesPlugin.__init__(self, args=args, items_history_list=items_history_list) # We want to display the stat in the curse interface self.display_curse = True @@ -114,12 +115,12 @@ class Plugin(GlancesPlugin): elif self.get_input() == 'snmp': # Update stats using SNMP - # SNMP bulk command to get all file system in one shot + # SNMP bulk command to get all file system in one shot try: - fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], + fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], bulk=True) except KeyError: - fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid['default'], + fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True) # Loop over fs @@ -135,7 +136,7 @@ class Plugin(GlancesPlugin): fs_current['size'] = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit']) fs_current['used'] = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit']) fs_current['percent'] = float(fs_current['used'] * 100 / fs_current['size']) - self.stats.append(fs_current) + self.stats.append(fs_current) else: # Default behavor for fs in fs_stat: @@ -147,6 +148,9 @@ class Plugin(GlancesPlugin): fs_current['percent'] = float(fs_stat[fs]['percent']) self.stats.append(fs_current) + # Update the history list + self.update_stats_history('mnt_point') + return self.stats def msg_curse(self, args=None, max_width=None): diff --git a/glances/plugins/glances_memswap.py b/glances/plugins/glances_memswap.py index 180b3344..1eaba46a 100644 --- a/glances/plugins/glances_memswap.py +++ b/glances/plugins/glances_memswap.py @@ -31,7 +31,12 @@ snmp_oid = {'default': {'total': '1.3.6.1.4.1.2021.4.3.0', 'windows': {'mnt_point': '1.3.6.1.2.1.25.2.3.1.3', 'alloc_unit': '1.3.6.1.2.1.25.2.3.1.4', 'size': '1.3.6.1.2.1.25.2.3.1.5', - 'used': '1.3.6.1.2.1.25.2.3.1.6'}} + 'used': '1.3.6.1.2.1.25.2.3.1.6'}} + +# Define the history items list +# All items in this list will be historised if the --enable-history tag is set +# 'color' define the graph color in #RGB format +items_history_list = [{'name': 'percent', 'color': '#00FF00'}] class Plugin(GlancesPlugin): @@ -43,7 +48,8 @@ class Plugin(GlancesPlugin): def __init__(self, args=None): """Init the plugin.""" - GlancesPlugin.__init__(self, args=args) + GlancesPlugin.__init__( + self, args=args, items_history_list=items_history_list) # We want to display the stat in the curse interface self.display_curse = True @@ -71,7 +77,8 @@ class Plugin(GlancesPlugin): # free: free swap memory in bytes # percent: the percentage usage # sin: the number of bytes the system has swapped in from disk (cumulative) - # sout: the number of bytes the system has swapped out from disk (cumulative) + # sout: the number of bytes the system has swapped out from disk + # (cumulative) for swap in ['total', 'used', 'free', 'percent', 'sin', 'sout']: if hasattr(sm_stats, swap): @@ -81,21 +88,26 @@ class Plugin(GlancesPlugin): if self.get_short_system_name() == 'windows': # Mem stats for Windows OS are stored in the FS table try: - fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], + fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], bulk=True) except KeyError: self.reset() else: - for fs in fs_stat: - # The virtual memory concept is used by the operating system to extend (virtually) the physical - # memory and thus to run more programs by swapping unused memory zone (page) to a disk file. + for fs in fs_stat: + # The virtual memory concept is used by the operating system to extend (virtually) the physical + # memory and thus to run more programs by swapping + # unused memory zone (page) to a disk file. if fs == 'Virtual Memory': - self.stats['total'] = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit']) - self.stats['used'] = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit']) - self.stats['percent'] = float(self.stats['used'] * 100 / self.stats['total']) - self.stats['free'] = self.stats['total'] - self.stats['used'] + self.stats['total'] = int( + fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit']) + self.stats['used'] = int( + fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit']) + self.stats['percent'] = float( + self.stats['used'] * 100 / self.stats['total']) + self.stats['free'] = self.stats[ + 'total'] - self.stats['used'] break - else: + else: self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default']) if self.stats['total'] == '': @@ -109,8 +121,13 @@ class Plugin(GlancesPlugin): # used=total-free self.stats['used'] = self.stats['total'] - self.stats['free'] - # percent: the percentage usage calculated as (total - available) / total * 100. - self.stats['percent'] = float((self.stats['total'] - self.stats['free']) / self.stats['total'] * 100) + # percent: the percentage usage calculated as (total - + # available) / total * 100. + self.stats['percent'] = float( + (self.stats['total'] - self.stats['free']) / self.stats['total'] * 100) + + # Update the history list + self.update_stats_history() return self.stats diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index 32d52fc4..07154465 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -33,6 +33,12 @@ snmp_oid = {'default': {'interface_name': '1.3.6.1.2.1.2.2.1.2', 'cumulative_rx': '1.3.6.1.2.1.2.2.1.10', 'cumulative_tx': '1.3.6.1.2.1.2.2.1.16'}} +# Define the history items list +# All items in this list will be historised if the --enable-history tag is set +# 'color' define the graph color in #RGB format +items_history_list = [{'name': 'rx', 'color': '#00FF00', 'label_y': '(bit/s)'}, + {'name': 'tx', 'color': '#FF0000', 'label_y': '(bit/s)'}] + class Plugin(GlancesPlugin): @@ -43,7 +49,7 @@ class Plugin(GlancesPlugin): def __init__(self, args=None): """Init the plugin.""" - GlancesPlugin.__init__(self, args=args) + GlancesPlugin.__init__(self, args=args, items_history_list=items_history_list) # We want to display the stat in the curse interface self.display_curse = True @@ -90,7 +96,7 @@ class Plugin(GlancesPlugin): for net in network_new: try: # Try necessary to manage dynamic network interface - netstat = {} + netstat = {} netstat['interface_name'] = net netstat['time_since_update'] = time_since_update netstat['cumulative_rx'] = network_new[net].bytes_recv @@ -115,10 +121,10 @@ class Plugin(GlancesPlugin): # SNMP bulk command to get all network interface in one shot try: - netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], + netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()], bulk=True) except KeyError: - netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid['default'], + netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True) # Previous network interface stats are stored in the network_old variable @@ -166,6 +172,9 @@ class Plugin(GlancesPlugin): # Save stats to compute next bitrate self.network_old = network_new + # Update the history list + self.update_stats_history('interface_name') + return self.stats def msg_curse(self, args=None, max_width=None): diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index 29071ed3..b0ad3b1d 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -70,39 +70,48 @@ class GlancesPlugin(object): """Return the human-readable stats.""" return str(self.stats) + def add_item_history(self, key, value): + """Add an new item (key, value) to the current history""" + try: + self.stats_history[key].append(value) + except KeyError: + self.stats_history[key] = [value] + def init_stats_history(self): """Init the stats history (dict of list)""" ret = None if self.args is not None and self.args.enable_history and self.get_items_history_list() is not None: iList = [i['name'] for i in self.get_items_history_list()] - logger.debug(_("Stats history activated for plugin %s (items: %s)") % (self.plugin_name, iList)) + logger.debug(_("Stats history activated for plugin %s (items: %s)") % ( + self.plugin_name, iList)) ret = {} - # First column for the date - ret['date'] = [] - for i in self.get_items_history_list(): - # One column per item - ret[i['name']] = [] return ret def reset_stats_history(self): """Reset the stats history (dict of list)""" if self.args is not None and self.args.enable_history and self.get_items_history_list() is not None: iList = [i['name'] for i in self.get_items_history_list()] - logger.debug(_("Reset history for plugin %s (items: %s)") % (self.plugin_name, iList)) + logger.debug( + _("Reset history for plugin %s (items: %s)") % (self.plugin_name, iList)) self.stats_history = {} - # First column for the date - self.stats_history['date'] = [] - for i in self.get_items_history_list(): - # One column per item - self.stats_history[i['name']] = [] return self.stats_history - def update_stats_history(self): + def update_stats_history(self, item_name=''): """Update stats history""" - if self.args is not None and self.args.enable_history and self.get_items_history_list() is not None: - self.stats_history['date'].append(datetime.now()) + if self.stats != [] and self.args is not None and self.args.enable_history and self.get_items_history_list() is not None: + self.add_item_history('date', datetime.now()) for i in self.get_items_history_list(): - self.stats_history[i['name']].append(self.stats[i['name']]) + if type(self.stats) is list: + # Stats is a list of data + # Iter throught it (for exemple, iter throught network + # interface) + for l in self.stats: + self.add_item_history( + l[item_name] + '_' + i['name'], l[i['name']]) + else: + # Stats is not a list + # Add the item to the history directly + self.add_item_history(i['name'], self.stats[i['name']]) return self.stats_history def get_stats_history(self): @@ -160,10 +169,11 @@ class GlancesPlugin(object): if len(snmp_oid) == 1: # Bulk command for only one OID - # Note: key is the item indexed but the OID result + # Note: key is the item indexed but the OID result for item in snmpresult: if item.keys()[0].startswith(snmp_oid.values()[0]): - ret[snmp_oid.keys()[0] + item.keys()[0].split(snmp_oid.values()[0])[1]] = item.values()[0] + ret[snmp_oid.keys()[0] + item.keys() + [0].split(snmp_oid.values()[0])[1]] = item.values()[0] else: # Build the internal dict with the SNMP result # Note: key is the first item in the snmp_oid @@ -203,19 +213,20 @@ class GlancesPlugin(object): """ Return the stats object for a specific item (in JSON format) Stats should be a list of dict (processlist, network...) - """ + """ if type(self.stats) is not list: if type(self.stats) is dict: try: - return json.dumps({ item: self.stats[item] }) + return json.dumps({item: self.stats[item]}) except KeyError as e: logger.error(_("Can not get item %s (%s)") % (item, e)) else: return None else: try: - # Source: http://stackoverflow.com/questions/4573875/python-get-index-of-dictionary-item-in-list - return json.dumps({ item: map(itemgetter(item), self.stats) }) + # Source: + # http://stackoverflow.com/questions/4573875/python-get-index-of-dictionary-item-in-list + return json.dumps({item: map(itemgetter(item), self.stats)}) except (KeyError, ValueError) as e: logger.error(_("Can not get item %s (%s)") % (item, e)) return None @@ -231,9 +242,10 @@ class GlancesPlugin(object): if value.isdigit(): value = int(value) try: - return json.dumps({ value: [i for i in self.stats if i[item] == value] }) + return json.dumps({value: [i for i in self.stats if i[item] == value]}) except (KeyError, ValueError) as e: - logger.error(_("Can not get item(%s)=value(%s) (%s)") % (item, value,e)) + logger.error( + _("Can not get item(%s)=value(%s) (%s)") % (item, value, e)) return None def load_limits(self, config): @@ -243,9 +255,11 @@ class GlancesPlugin(object): for s, v in config.items(self.plugin_name): # Read limits try: - self.limits[self.plugin_name + '_' + s] = config.get_option(self.plugin_name, s) + self.limits[ + self.plugin_name + '_' + s] = config.get_option(self.plugin_name, s) except ValueError: - self.limits[self.plugin_name + '_' + s] = config.get_raw_option(self.plugin_name, s).split(",") + self.limits[ + self.plugin_name + '_' + s] = config.get_raw_option(self.plugin_name, s).split(",") def set_limits(self, input_limits): """Set the limits to input_limits.""" @@ -389,8 +403,8 @@ class GlancesPlugin(object): return ret - def curse_add_line(self, msg, decoration="DEFAULT", - optional=False, additional=False, + def curse_add_line(self, msg, decoration="DEFAULT", + optional=False, additional=False, splittable=False): """Return a dict with @@ -424,7 +438,7 @@ class GlancesPlugin(object): def set_align(self, align='left'): """Set the Curse align""" - if align in ('left', 'right', 'bottom'): + if align in ('left', 'right', 'bottom'): self.align = align else: self.align = 'left' -- GitLab