Test Failed
Push — develop ( d7cf39...faa4bd )
by Nicolas
04:34 queued 10s
created

glances/plugins/glances_alert.py (2 issues)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2019 Nicolargo <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""Alert plugin."""
21
22
from datetime import datetime
23
24
from glances.logger import logger
25
from glances.events import glances_events
26
from glances.thresholds import glances_thresholds
27
# from glances.logger import logger
28
from glances.plugins.glances_plugin import GlancesPlugin
29
30
# Static decision tree for the global alert message
31
# - msg: Message to be displayed (result of the decision tree)
32
# - threasholds: a list of stats to take into account
33
# - thresholds_min: minimal value of the threasholds sum
34
# -                 0: OK
35
# -                 1: CAREFUL
36
# -                 2: WARNING
37
# -                 3: CRITICAL
38
tree = [{'msg': 'No warning or critical alert detected',
39
         'thresholds': [],
40
         'thresholds_min': 0},
41
        {'msg': 'High CPU user mode',
42
         'thresholds': ['cpu_user'],
43
         'thresholds_min': 2},
44
        {'msg': 'High CPU kernel usage',
45
         'thresholds': ['cpu_system'],
46
         'thresholds_min': 2},
47
        {'msg': 'High CPU I/O waiting',
48
         'thresholds': ['cpu_iowait'],
49
         'thresholds_min': 2},
50
        {'msg': 'Large CPU stolen time. System running the hypervisor is too busy.',
51
         'thresholds': ['cpu_steal'],
52
         'thresholds_min': 2},
53
        {'msg': 'High CPU niced value',
54
         'thresholds': ['cpu_niced'],
55
         'thresholds_min': 2},
56
        {'msg': 'System overloaded in the last 5 minutes',
57
         'thresholds': ['load'],
58
         'thresholds_min': 2},
59
        {'msg': 'High swap (paging) usage',
60
         'thresholds': ['memswap'],
61
         'thresholds_min': 2},
62
        {'msg': 'High memory consumption',
63
         'thresholds': ['mem'],
64
         'thresholds_min': 2},
65
        ]
66
67
68
def global_message():
69
    """Parse the decision tree and return the message.
70
71
    Note: message corresponding to the current threasholds values
72
    """
73
    # Compute the weight for each item in the tree
74
    current_thresholds = glances_thresholds.get()
75
    for i in tree:
76
        i['weight'] = sum([current_thresholds[t].value() for t in i['thresholds'] if t in current_thresholds])
77
    themax = max(tree, key=lambda d: d['weight'])
78
    if themax['weight'] >= themax['thresholds_min']:
0 ignored issues
show
Unnecessary "else" after "return"
Loading history...
79
        # Check if the weight is > to the minimal threashold value
80
        return themax['msg']
81
    else:
82
        return tree[0]['msg']
83
84
85
class Plugin(GlancesPlugin):
86
    """Glances alert plugin.
87
88
    Only for display.
89
    """
90
91
    def __init__(self, args=None, config=None):
92
        """Init the plugin."""
93
        super(Plugin, self).__init__(args=args,
94
                                     config=config,
95
                                     stats_init_value=[])
96
97
        # We want to display the stat in the curse interface
98
        self.display_curse = True
99
100
        # Set the message position
101
        self.align = 'bottom'
102
103
    def update(self):
104
        """Nothing to do here. Just return the global glances_log."""
105
        # Set the stats to the glances_events
106
        self.stats = glances_events.get()
107
        # Define the global message thanks to the current thresholds
108
        # and the decision tree
109
        # !!! Call directly in the msg_curse function
110
        # global_message()
111
112
    def msg_curse(self, args=None, max_width=None):
113
        """Return the dict to display in the curse interface."""
114
        # Init the return message
115
        ret = []
116
117
        # Only process if display plugin enable...
118
        if not self.stats or self.is_disable():
119
            return ret
120
121
        # Build the string message
122
        # Header
123
        ret.append(self.curse_add_line(global_message(), "TITLE"))
124
        # Loop over alerts
125
        for alert in self.stats:
126
            # New line
127
            ret.append(self.curse_new_line())
128
            # Start
129
            msg = str(datetime.fromtimestamp(alert[0]))
130
            ret.append(self.curse_add_line(msg))
131
            # Duration
132
            if alert[1] > 0:
133
                # If finished display duration
134
                msg = ' ({})'.format(datetime.fromtimestamp(alert[1]) -
135
                                     datetime.fromtimestamp(alert[0]))
136
            else:
137
                msg = ' (ongoing)'
138
            ret.append(self.curse_add_line(msg))
139
            ret.append(self.curse_add_line(" - "))
140
            # Infos
141
            if alert[1] > 0:
142
                # If finished do not display status
143
                msg = '{} on {}'.format(alert[2], alert[3])
144
                ret.append(self.curse_add_line(msg))
145
            else:
146
                msg = str(alert[3])
147
                ret.append(self.curse_add_line(msg, decoration=alert[2]))
148
            # Min / Mean / Max
149
            if self.approx_equal(alert[6], alert[4], tolerance=0.1):
150
                msg = ' ({:.1f})'.format(alert[5])
151
            else:
152
                msg = ' (Min:{:.1f} Mean:{:.1f} Max:{:.1f})'.format(
153
                    alert[6], alert[5], alert[4])
154
            ret.append(self.curse_add_line(msg))
155
            # Top processes
156
            top_process = ', '.join([p['name'] for p in alert[9]])
157
            if top_process != '':
158
                msg = ': {}'.format(top_process)
159
                ret.append(self.curse_add_line(msg))
160
161
        return ret
162
163
    def approx_equal(self, a, b, tolerance=0.0):
164
        """Compare a with b using the tolerance (if numerical)."""
165
        if str(int(a)).isdigit() and str(int(b)).isdigit():
0 ignored issues
show
Unnecessary "else" after "return"
Loading history...
166
            return abs(a - b) <= max(abs(a), abs(b)) * tolerance
167
        else:
168
            return a == b
169