From ed30ebf72b0b41bb45de1b1532738ed2f3a5930b Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Sun, 19 Oct 2014 16:05:16 +0200 Subject: [PATCH] Complete graph history feature #428 --- glances/core/glances_processes.py | 2 + glances/outputs/glances_curses.py | 8 +- glances/outputs/glances_history.py | 109 ++++++++++++------------ glances/plugins/glances_cpu.py | 6 +- glances/plugins/glances_diskio.py | 4 +- glances/plugins/glances_mem.py | 2 +- glances/plugins/glances_memswap.py | 2 +- glances/plugins/glances_network.py | 4 +- glances/plugins/glances_processcount.py | 3 + glances/plugins/glances_sensors.py | 23 +++-- 10 files changed, 87 insertions(+), 76 deletions(-) diff --git a/glances/core/glances_processes.py b/glances/core/glances_processes.py index 7eea8259..6b62563c 100644 --- a/glances/core/glances_processes.py +++ b/glances/core/glances_processes.py @@ -343,6 +343,8 @@ class GlancesProcesses(object): except KeyError: # Key did not exist, create it self.processcount[str(proc.status())] = 1 + except psutil.NoSuchProcess: + pass else: self.processcount['total'] += 1 # Update thread number (global statistics) diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 38b3fd0a..c9569e61 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -530,7 +530,8 @@ class GlancesCurses(object): if self.history_tag and self.args.enable_history: self.display_popup( _("Generate graphs history in %s\nPlease wait...") % self.glances_history.get_output_folder()) - self.glances_history.generate_graph(stats) + self.display_popup( + _("Generate graphs history in %s\nDone: %s graphs generated") % (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) @@ -580,13 +581,14 @@ class GlancesCurses(object): """ # Center the popup + sentence_list = message.split('\n') if size_x is None: - size_x = len(message) + 4 + size_x = len(max(sentence_list, key=len)) + 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 + size_y = len(sentence_list) + 4 screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] if size_x > screen_x or size_y > screen_y: diff --git a/glances/outputs/glances_history.py b/glances/outputs/glances_history.py index db0f7e74..436bf999 100644 --- a/glances/outputs/glances_history.py +++ b/glances/outputs/glances_history.py @@ -76,55 +76,75 @@ class GlancesHistory(object): else: return ret - def get_graph_ylegend(self, item): + def get_graph_legend(self, item): """ - Get the item's Y legend + Get the item's legend + """ + return item['name'] + + def get_graph_yunit(self, item, pre_label=''): + """ + Get the item's Y unit """ try: - ret = item['label_y'] + unit = " (%s)" % item['y_unit'] except KeyError: - return '' + unit = '' + if pre_label == '': + label = '' else: - return ' ' + ret + label = pre_label.split('_')[0] + return "%s%s" % (label, unit) def generate_graph(self, stats): """ Generate graphs from plugins history + Return the number of output files generated by the function """ if not self.graph_enabled(): - return False + return 0 + index_all = 0 for p in stats.getAllPlugins(): h = stats.get_plugin(p).get_stats_history() - # 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 + # Init graph + plt.clf() index_graph = 0 + handles = [] + labels = [] 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 + # Add the curves 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)) + # Labels + handles.append(plt.Rectangle((0, 0), 1, 1, fc=self.get_graph_color(i), ec=self.get_graph_color(i), linewidth=2)) + labels.append(self.get_graph_legend(i)) + # Legend + plt.ylabel(self.get_graph_yunit(i, pre_label='')) + # Curves 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) + if index_graph == 1: + # Title only on top of the first graph + plt.title(p.capitalize()) 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)) + # Add one curve per chart + 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 @@ -132,56 +152,35 @@ class GlancesHistory(object): 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.subplot( + len(stats_history_filtered), 1, index_item) + plt.ylabel(self.get_graph_yunit(i, pre_label=k)) plt.grid(True) plt.plot_date(h['date'], h[k], fmt='', drawstyle='default', linestyle='-', color=self.get_graph_color(i), xdate=True, ydate=False) + if index_item == 1: + # Title only on top of the first graph + plt.title(p.capitalize() + ' ' + i['name']) # Save the graph to output file + fig = plt.gcf() + fig.set_size_inches(20, 5 * index_item) plt.xlabel('Date') - plt.savefig(os.path.join(self.output_folder, 'glances_%s_%s.png' % (p, i['name'])), dpi=72) + plt.savefig( + os.path.join(self.output_folder, 'glances_%s_%s.png' % (p, i['name'])), dpi=72) + index_all += 1 if index_graph > 0: # Save the graph to output file + fig = plt.gcf() + fig.set_size_inches(20, 10) + plt.legend(handles, labels, loc=1, prop={'size': 9}) plt.xlabel('Date') - plt.savefig(os.path.join(self.output_folder, 'glances_%s.png' % (p)), dpi=72) + plt.savefig( + os.path.join(self.output_folder, 'glances_%s.png' % (p)), dpi=72) + index_all += 1 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 + return index_all diff --git a/glances/plugins/glances_cpu.py b/glances/plugins/glances_cpu.py index 9be4805e..dd135640 100644 --- a/glances/plugins/glances_cpu.py +++ b/glances/plugins/glances_cpu.py @@ -36,10 +36,10 @@ snmp_oid = {'default': {'user': '1.3.6.1.4.1.2021.11.9.0', # Define the history items list # - 'name' define the stat identifier # - 'color' define the graph color in #RGB format -# - 'label_y' define the Y label +# - 'y_unit' 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', 'label_y': '(%)'}, - {'name': 'system', 'color': '#FF0000', 'label_y': '(%)'}] +items_history_list = [{'name': 'user', 'color': '#00FF00', 'y_unit': '%'}, + {'name': 'system', 'color': '#FF0000', 'y_unit': '%'}] class Plugin(GlancesPlugin): diff --git a/glances/plugins/glances_diskio.py b/glances/plugins/glances_diskio.py index 92c33dbd..a86ceeda 100644 --- a/glances/plugins/glances_diskio.py +++ b/glances/plugins/glances_diskio.py @@ -28,8 +28,8 @@ 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)'}] +items_history_list = [{'name': 'read_bytes', 'color': '#00FF00', 'y_unit': 'B/s'}, + {'name': 'write_bytes', 'color': '#FF0000', 'y_unit': 'B/s'}] class Plugin(GlancesPlugin): diff --git a/glances/plugins/glances_mem.py b/glances/plugins/glances_mem.py index 6e6ed101..9217b242 100644 --- a/glances/plugins/glances_mem.py +++ b/glances/plugins/glances_mem.py @@ -48,7 +48,7 @@ snmp_oid = {'default': {'total': '1.3.6.1.4.1.2021.4.5.0', # 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'}] +items_history_list = [{'name': 'percent', 'color': '#00FF00', 'y_unit': '%'}] class Plugin(GlancesPlugin): diff --git a/glances/plugins/glances_memswap.py b/glances/plugins/glances_memswap.py index 1eaba46a..0596b531 100644 --- a/glances/plugins/glances_memswap.py +++ b/glances/plugins/glances_memswap.py @@ -36,7 +36,7 @@ snmp_oid = {'default': {'total': '1.3.6.1.4.1.2021.4.3.0', # 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'}] +items_history_list = [{'name': 'percent', 'color': '#00FF00', 'y_unit': '%'}] class Plugin(GlancesPlugin): diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index 07154465..2de42fc6 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -36,8 +36,8 @@ snmp_oid = {'default': {'interface_name': '1.3.6.1.2.1.2.2.1.2', # 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)'}] +items_history_list = [{'name': 'rx', 'color': '#00FF00', 'y_unit': 'bit/s'}, + {'name': 'tx', 'color': '#FF0000', 'y_unit': 'bit/s'}] class Plugin(GlancesPlugin): diff --git a/glances/plugins/glances_processcount.py b/glances/plugins/glances_processcount.py index b171258a..37d2eb68 100644 --- a/glances/plugins/glances_processcount.py +++ b/glances/plugins/glances_processcount.py @@ -23,6 +23,9 @@ from glances.core.glances_globals import glances_processes from glances.plugins.glances_plugin import GlancesPlugin +# Note: history items list is not compliant with process count +# if a filter is applyed, the graph will show the filtered processes count + class Plugin(GlancesPlugin): diff --git a/glances/plugins/glances_sensors.py b/glances/plugins/glances_sensors.py index 171b054d..f24b5184 100644 --- a/glances/plugins/glances_sensors.py +++ b/glances/plugins/glances_sensors.py @@ -27,7 +27,7 @@ except ImportError: pass # Import Glances lib -from glances.core.glances_globals import is_py3, logger +from glances.core.glances_globals import is_py3 from glances.plugins.glances_batpercent import Plugin as BatPercentPlugin from glances.plugins.glances_hddtemp import Plugin as HddTempPlugin from glances.plugins.glances_plugin import GlancesPlugin @@ -49,10 +49,12 @@ class Plugin(GlancesPlugin): # Init the sensor class self.glancesgrabsensors = GlancesGrabSensors() - # Instance for the HDDTemp Plugin in order to display the hard disks temperatures + # Instance for the HDDTemp Plugin in order to display the hard disks + # temperatures self.hddtemp_plugin = HddTempPlugin(args=args) - # Instance for the BatPercent in order to display the batteries capacities + # Instance for the BatPercent in order to display the batteries + # capacities self.batpercent_plugin = BatPercentPlugin(args=args) # We want to display the stat in the curse interface @@ -73,13 +75,13 @@ class Plugin(GlancesPlugin): if self.get_input() == 'local': # Update stats using the dedicated lib try: - self.stats = self.__set_type(self.glancesgrabsensors.get(), + self.stats = self.__set_type(self.glancesgrabsensors.get(), 'temperature_core') except: pass # Update HDDtemp stats try: - hddtemp = self.__set_type(self.hddtemp_plugin.update(), + hddtemp = self.__set_type(self.hddtemp_plugin.update(), 'temperature_hdd') except: pass @@ -88,7 +90,7 @@ class Plugin(GlancesPlugin): self.stats.extend(hddtemp) # Update batteries stats try: - batpercent = self.__set_type(self.batpercent_plugin.update(), + batpercent = self.__set_type(self.batpercent_plugin.update(), 'battery') except: pass @@ -97,7 +99,8 @@ class Plugin(GlancesPlugin): self.stats.extend(batpercent) elif self.get_input() == 'snmp': # Update stats using SNMP - # No standard: http://www.net-snmp.org/wiki/index.php/Net-SNMP_and_lm-sensors_on_Ubuntu_10.04 + # No standard: + # http://www.net-snmp.org/wiki/index.php/Net-SNMP_and_lm-sensors_on_Ubuntu_10.04 pass return self.stats @@ -147,11 +150,13 @@ class Plugin(GlancesPlugin): msg = '{0:>5}'.format(item['value']) if item['type'] == 'battery': try: - ret.append(self.curse_add_line(msg, self.get_alert(100 - item['value'], header=item['type']))) + ret.append(self.curse_add_line( + msg, self.get_alert(100 - item['value'], header=item['type']))) except TypeError: pass else: - ret.append(self.curse_add_line(msg, self.get_alert(item['value'], header=item['type']))) + ret.append( + self.curse_add_line(msg, self.get_alert(item['value'], header=item['type']))) return ret -- GitLab