diff --git a/docs/_static/logs.png b/docs/_static/events.png similarity index 100% rename from docs/_static/logs.png rename to docs/_static/events.png diff --git a/docs/aoa/logs.rst b/docs/aoa/events.rst similarity index 64% rename from docs/aoa/logs.rst rename to docs/aoa/events.rst index c9365915..88aa88fe 100644 --- a/docs/aoa/logs.rst +++ b/docs/aoa/events.rst @@ -1,17 +1,16 @@ -.. _logs: +.. _events: -Logs -==== +events +====== -.. image:: ../_static/logs.png +.. image:: ../_static/events.png -A log messages list is displayed in the bottom of the screen if and only -if: +Events list is displayed in the bottom of the screen if and only if: - at least one ``WARNING`` or ``CRITICAL`` alert was occurred - space is available in the bottom of the console/terminal -Each alert message displays the following information: +Each event message displays the following information: 1. start datetime 2. duration if alert is terminated or `ongoing` if the alert is still in diff --git a/docs/aoa/index.rst b/docs/aoa/index.rst index 082c1c88..18fb5ba1 100644 --- a/docs/aoa/index.rst +++ b/docs/aoa/index.rst @@ -37,6 +37,6 @@ Legend: ps monitor amps - logs + events docker actions diff --git a/glances/events.py b/glances/events.py new file mode 100644 index 00000000..4b14143e --- /dev/null +++ b/glances/events.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2018 Nicolargo +# +# Glances is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Glances is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +"""Manage Glances events (previously Glances logs in Glances <3.1).""" + +import time +from datetime import datetime + +from glances.compat import range +from glances.processes import glances_processes, sort_stats + + +class GlancesEvents(object): + + """This class manages events inside the Glances software. + + Events is a list of event (stored in the self.events_list var) + event_state = "OK|CAREFUL|WARNING|CRITICAL" + event_type = "CPU*|LOAD|MEM|MON" + event_value = value + + Item (or event) is defined by: + ["begin", + "end", + "WARNING|CRITICAL", + "CPU|LOAD|MEM", + MAX, AVG, MIN, SUM, COUNT, + [top3 process list], + "Processes description", + "top sort key"] + """ + + def __init__(self): + """Init the events class.""" + # Maximum size of the events list + self.events_max = 10 + + # Init the logs list + self.events_list = [] + + def get(self): + """Return the raw events list.""" + return self.events_list + + def len(self): + """Return the number of events in the logs list.""" + return self.events_list.__len__() + + def __event_exist(self, event_type): + """Return the event position, if it exists. + + An event exist if: + * end is < 0 + * event_type is matching + Return -1 if the item is not found. + """ + for i in range(self.len()): + if self.events_list[i][1] < 0 and self.events_list[i][3] == event_type: + return i + return -1 + + def get_process_sort_key(self, event_type): + """Return the process sort key""" + # Process sort depending on alert type + if event_type.startswith("MEM"): + # Sort TOP process by memory_percent + ret = 'memory_percent' + elif event_type.startswith("CPU_IOWAIT"): + # Sort TOP process by io_counters (only for Linux OS) + ret = 'io_counters' + else: + # Default sort is... + ret = 'cpu_percent' + return ret + + def set_process_sort(self, event_type): + """Define the process auto sort key from the alert type.""" + glances_processes.auto_sort = True + glances_processes.sort_key = self.get_process_sort_key(event_type) + + def reset_process_sort(self): + """Reset the process auto sort key.""" + # Default sort is... + glances_processes.auto_sort = True + glances_processes.sort_key = 'cpu_percent' + + def add(self, event_state, event_type, event_value, + proc_list=None, proc_desc="", peak_time=6): + """Add a new item to the logs list. + + If 'event' is a 'new one', add it at the beginning of the list. + If 'event' is not a 'new one', update the list . + If event < peak_time then the alert is not set. + """ + proc_list = proc_list or glances_processes.getlist() + + # Add or update the log + event_index = self.__event_exist(event_type) + if event_index < 0: + # Event did not exist, add it + self._create_event(event_state, event_type, event_value, + proc_list, proc_desc, peak_time) + else: + # Event exist, update it + self._update_event(event_index, event_state, event_type, event_value, + proc_list, proc_desc, peak_time) + + return self.len() + + def _create_event(self, event_state, event_type, event_value, + proc_list, proc_desc, peak_time): + """Add a new item in the log list. + + Item is added only if the criticity (event_state) is WARNING or CRITICAL. + """ + if event_state == "WARNING" or event_state == "CRITICAL": + # Define the automatic process sort key + self.set_process_sort(event_type) + + # Create the new log item + # Time is stored in Epoch format + # Epoch -> DMYHMS = datetime.fromtimestamp(epoch) + item = [ + time.mktime(datetime.now().timetuple()), # START DATE + -1, # END DATE + event_state, # STATE: WARNING|CRITICAL + event_type, # TYPE: CPU, LOAD, MEM... + event_value, # MAX + event_value, # AVG + event_value, # MIN + event_value, # SUM + 1, # COUNT + [], # TOP 3 PROCESS LIST + proc_desc, # MONITORED PROCESSES DESC + glances_processes.sort_key] # TOP PROCESS SORTKEY + + # Add the item to the list + self.events_list.insert(0, item) + + # Limit the list to 'events_max' items + if self.len() > self.events_max: + self.events_list.pop() + + return True + else: + return False + + def _update_event(self, event_index, event_state, event_type, event_value, + proc_list, proc_desc, peak_time): + """Update an event in the list""" + if event_state == "OK" or event_state == "CAREFUL": + # Reset the automatic process sort key + self.reset_process_sort() + + # Set the end of the events + endtime = time.mktime(datetime.now().timetuple()) + if endtime - self.events_list[event_index][0] > peak_time: + # If event is > peak_time seconds + self.events_list[event_index][1] = endtime + else: + # If event <= peak_time seconds, ignore + self.events_list.remove(self.events_list[event_index]) + else: + # Update the item + + # State + if event_state == "CRITICAL": + self.events_list[event_index][2] = event_state + + # Value + # if event_value > self.events_list[event_index][4]: + # # MAX + # self.events_list[event_index][4] = event_value + # elif event_value < self.events_list[event_index][6]: + # # MIN + # self.events_list[event_index][6] = event_value + # Min value + self.events_list[event_index][6] = min(self.events_list[event_index][6], + event_value) + # Max value + self.events_list[event_index][4] = max(self.events_list[event_index][4], + event_value) + # Average value + self.events_list[event_index][7] += event_value + self.events_list[event_index][8] += 1 + self.events_list[event_index][5] = (self.events_list[event_index][7] / + self.events_list[event_index][8]) + + # TOP PROCESS LIST (only for CRITICAL ALERT) + if event_state == "CRITICAL": + # Sort the current process list to retreive the TOP 3 processes + self.events_list[event_index][9] = sort_stats(proc_list, glances_processes.sort_key)[0:3] + self.events_list[event_index][11] = glances_processes.sort_key + + # MONITORED PROCESSES DESC + self.events_list[event_index][10] = proc_desc + + return True + + def clean(self, critical=False): + """Clean the logs list by deleting finished items. + + By default, only delete WARNING message. + If critical = True, also delete CRITICAL message. + """ + # Create a new clean list + clean_events_list = [] + while self.len() > 0: + item = self.events_list.pop() + if item[1] < 0 or (not critical and item[2].startswith("CRITICAL")): + clean_events_list.insert(0, item) + # The list is now the clean one + self.events_list = clean_events_list + return self.len() + + +glances_events = GlancesEvents() diff --git a/glances/logs.py b/glances/logs.py deleted file mode 100644 index 1389ffae..00000000 --- a/glances/logs.py +++ /dev/null @@ -1,222 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Glances. -# -# Copyright (C) 2018 Nicolargo -# -# Glances is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Glances is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -"""Manage logs.""" - -import time -from datetime import datetime - -from glances.compat import range -from glances.processes import glances_processes, sort_stats - - -class GlancesLogs(object): - - """This class manages logs inside the Glances software. - - Logs is a list of list (stored in the self.logs_list var) - item_state = "OK|CAREFUL|WARNING|CRITICAL" - item_type = "CPU*|LOAD|MEM|MON" - item_value = value - - Item is defined by: - ["begin", - "end", - "WARNING|CRITICAL", - "CPU|LOAD|MEM", - MAX, AVG, MIN, SUM, COUNT, - [top3 process list], - "Processes description", - "top sort key"] - """ - - def __init__(self): - """Init the logs class.""" - # Maximum size of the logs list - self.logs_max = 10 - - # Init the logs list - self.logs_list = [] - - def get(self): - """Return the raw logs list.""" - return self.logs_list - - def len(self): - """Return the number of item in the logs list.""" - return self.logs_list.__len__() - - def __itemexist__(self, item_type): - """Return the item position, if it exists. - - An item exist in the list if: - * end is < 0 - * item_type is matching - Return -1 if the item is not found. - """ - for i in range(self.len()): - if self.logs_list[i][1] < 0 and self.logs_list[i][3] == item_type: - return i - return -1 - - def get_process_sort_key(self, item_type): - """Return the process sort key""" - # Process sort depending on alert type - if item_type.startswith("MEM"): - # Sort TOP process by memory_percent - ret = 'memory_percent' - elif item_type.startswith("CPU_IOWAIT"): - # Sort TOP process by io_counters (only for Linux OS) - ret = 'io_counters' - else: - # Default sort is... - ret = 'cpu_percent' - return ret - - def set_process_sort(self, item_type): - """Define the process auto sort key from the alert type.""" - glances_processes.auto_sort = True - glances_processes.sort_key = self.get_process_sort_key(item_type) - - def reset_process_sort(self): - """Reset the process auto sort key.""" - # Default sort is... - glances_processes.auto_sort = True - glances_processes.sort_key = 'cpu_percent' - - def add(self, item_state, item_type, item_value, - proc_list=None, proc_desc="", peak_time=6): - """Add a new item to the logs list. - - If 'item' is a 'new one', add the new item at the beginning of - the logs list. - If 'item' is not a 'new one', update the existing item. - If event < peak_time the the alert is not setoff. - """ - proc_list = proc_list or glances_processes.getlist() - - # Add or update the log - item_index = self.__itemexist__(item_type) - if item_index < 0: - # Item did not exist, add if WARNING or CRITICAL - self._create_item(item_state, item_type, item_value, - proc_list, proc_desc, peak_time) - else: - # Item exist, update - self._update_item(item_index, item_state, item_type, item_value, - proc_list, proc_desc, peak_time) - - return self.len() - - def _create_item(self, item_state, item_type, item_value, - proc_list, proc_desc, peak_time): - """Create a new item in the log list""" - if item_state == "WARNING" or item_state == "CRITICAL": - # Define the automatic process sort key - self.set_process_sort(item_type) - - # Create the new log item - # Time is stored in Epoch format - # Epoch -> DMYHMS = datetime.fromtimestamp(epoch) - item = [ - time.mktime(datetime.now().timetuple()), # START DATE - -1, # END DATE - item_state, # STATE: WARNING|CRITICAL - item_type, # TYPE: CPU, LOAD, MEM... - item_value, # MAX - item_value, # AVG - item_value, # MIN - item_value, # SUM - 1, # COUNT - [], # TOP 3 PROCESS LIST - proc_desc, # MONITORED PROCESSES DESC - glances_processes.sort_key] # TOP PROCESS SORTKEY - - # Add the item to the list - self.logs_list.insert(0, item) - if self.len() > self.logs_max: - self.logs_list.pop() - - return True - else: - return False - - def _update_item(self, item_index, item_state, item_type, item_value, - proc_list, proc_desc, peak_time): - """Update a item in the log list""" - if item_state == "OK" or item_state == "CAREFUL": - # Reset the automatic process sort key - self.reset_process_sort() - - endtime = time.mktime(datetime.now().timetuple()) - if endtime - self.logs_list[item_index][0] > peak_time: - # If event is > peak_time seconds - self.logs_list[item_index][1] = endtime - else: - # If event <= peak_time seconds, ignore - self.logs_list.remove(self.logs_list[item_index]) - else: - # Update the item - - # State - if item_state == "CRITICAL": - self.logs_list[item_index][2] = item_state - - # Value - if item_value > self.logs_list[item_index][4]: - # MAX - self.logs_list[item_index][4] = item_value - elif item_value < self.logs_list[item_index][6]: - # MIN - self.logs_list[item_index][6] = item_value - # AVG (compute average value) - self.logs_list[item_index][7] += item_value - self.logs_list[item_index][8] += 1 - self.logs_list[item_index][5] = (self.logs_list[item_index][7] / - self.logs_list[item_index][8]) - - # TOP PROCESS LIST (only for CRITICAL ALERT) - if item_state == "CRITICAL": - # Sort the current process list to retreive the TOP 3 processes - self.logs_list[item_index][9] = sort_stats(proc_list, glances_processes.sort_key)[0:3] - self.logs_list[item_index][11] = glances_processes.sort_key - - # MONITORED PROCESSES DESC - self.logs_list[item_index][10] = proc_desc - - return True - - def clean(self, critical=False): - """Clean the logs list by deleting finished items. - - By default, only delete WARNING message. - If critical = True, also delete CRITICAL message. - """ - # Create a new clean list - clean_logs_list = [] - while self.len() > 0: - item = self.logs_list.pop() - if item[1] < 0 or (not critical and item[2].startswith("CRITICAL")): - clean_logs_list.insert(0, item) - # The list is now the clean one - self.logs_list = clean_logs_list - return self.len() - - -glances_logs = GlancesLogs() diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 59c309e8..261a5853 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -25,7 +25,7 @@ import sys from glances.compat import u, itervalues from glances.globals import MACOS, WINDOWS from glances.logger import logger -from glances.logs import glances_logs +from glances.events import glances_events from glances.processes import glances_processes from glances.timer import Timer @@ -375,10 +375,10 @@ class _GlancesCurses(object): self.args.disable_folders = not self.args.disable_folders elif self.pressedkey == ord('w'): # 'w' > Delete finished warning logs - glances_logs.clean() + glances_events.clean() elif self.pressedkey == ord('x'): # 'x' > Delete finished warning and critical logs - glances_logs.clean(critical=True) + glances_events.clean(critical=True) elif self.pressedkey == ord('z'): # 'z' > Enable or disable processes self.args.disable_process = not self.args.disable_process diff --git a/glances/plugins/glances_alert.py b/glances/plugins/glances_alert.py index c46ecffe..c3f8e9ca 100644 --- a/glances/plugins/glances_alert.py +++ b/glances/plugins/glances_alert.py @@ -21,7 +21,7 @@ from datetime import datetime -from glances.logs import glances_logs +from glances.events import glances_events from glances.thresholds import glances_thresholds # from glances.logger import logger from glances.plugins.glances_plugin import GlancesPlugin @@ -96,8 +96,8 @@ class Plugin(GlancesPlugin): def update(self): """Nothing to do here. Just return the global glances_log.""" - # Set the stats to the glances_logs - self.stats = glances_logs.get() + # Set the stats to the glances_events + self.stats = glances_events.get() # Define the global message thanks to the current thresholds # and the decision tree # !!! Call directly in the msg_curse function @@ -119,7 +119,7 @@ class Plugin(GlancesPlugin): # Header # msg = 'Warning or critical alerts' # ret.append(self.curse_add_line(msg, "TITLE")) - # logs_len = glances_logs.len() + # logs_len = glances_events.len() # if logs_len > 1: # msg = ' (last {} entries)'.format(logs_len) # else: diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index c9e315e5..22669c0c 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -32,7 +32,7 @@ from glances.compat import iterkeys, itervalues, listkeys, map, mean from glances.actions import GlancesActions from glances.history import GlancesHistory from glances.logger import logger -from glances.logs import glances_logs +from glances.events import glances_events from glances.thresholds import glances_thresholds @@ -600,7 +600,7 @@ class GlancesPlugin(object): # So stats will be highlited with a specific color log_str = "_LOG" # Add the log to the list - glances_logs.add(ret, stat_name.upper(), value) + glances_events.add(ret, stat_name.upper(), value) # Manage threshold self.manage_threshold(stat_name, ret)