Test Failed
Push — develop ( 66c9ff...e21229 )
by Nicolas
05:06
created

glances.plugins.glances_cpu.Plugin.__init__()   A

Complexity

Conditions 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 14
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
nop 3
dl 14
loc 14
rs 9.95
c 0
b 0
f 0
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
"""CPU plugin."""
21
22
from glances.timer import getTimeSinceLastUpdate
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
23
from glances.compat import iterkeys
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
24
from glances.cpu_percent import cpu_percent
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
25
from glances.globals import LINUX
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
26
from glances.plugins.glances_core import Plugin as CorePlugin
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
27
from glances.plugins.glances_plugin import GlancesPlugin
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
28
29
import psutil
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
introduced by
Unable to import 'psutil'
Loading history...
30
31
# SNMP OID
32
# percentage of user CPU time: .1.3.6.1.4.1.2021.11.9.0
33
# percentages of system CPU time: .1.3.6.1.4.1.2021.11.10.0
34
# percentages of idle CPU time: .1.3.6.1.4.1.2021.11.11.0
35
snmp_oid = {'default': {'user': '1.3.6.1.4.1.2021.11.9.0',
0 ignored issues
show
Coding Style Naming introduced by
The name snmp_oid does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
36
                        'system': '1.3.6.1.4.1.2021.11.10.0',
37
                        'idle': '1.3.6.1.4.1.2021.11.11.0'},
38
            'windows': {'percent': '1.3.6.1.2.1.25.3.3.1.2'},
39
            'esxi': {'percent': '1.3.6.1.2.1.25.3.3.1.2'},
40
            'netapp': {'system': '1.3.6.1.4.1.789.1.2.1.3.0',
41
                       'idle': '1.3.6.1.4.1.789.1.2.1.5.0',
42
                       'nb_log_core': '1.3.6.1.4.1.789.1.2.1.6.0'}}
43
44
# Define the history items list
45
# - 'name' define the stat identifier
46
# - 'y_unit' define the Y label
47
items_history_list = [{'name': 'user',
0 ignored issues
show
Coding Style Naming introduced by
The name items_history_list does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
48
                       'description': 'User CPU usage',
49
                       'y_unit': '%'},
50
                      {'name': 'system',
51
                       'description': 'System CPU usage',
52
                       'y_unit': '%'}]
53
54
55
class Plugin(GlancesPlugin):
56
    """Glances CPU plugin.
57
58
    'stats' is a dictionary that contains the system-wide CPU utilization as a
59
    percentage.
60
    """
61
62 View Code Duplication
    def __init__(self, args=None, config=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
63
        """Init the CPU plugin."""
64
        super(Plugin, self).__init__(args=args,
65
                                     config=config,
66
                                     items_history_list=items_history_list)
67
68
        # We want to display the stat in the curse interface
69
        self.display_curse = True
70
71
        # Call CorePlugin in order to display the core number
72
        try:
73
            self.nb_log_core = CorePlugin(args=self.args).update()["log"]
74
        except Exception:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
75
            self.nb_log_core = 1
76
77
    @GlancesPlugin._check_decorator
78
    @GlancesPlugin._log_result_decorator
79
    def update(self):
80
        """Update CPU stats using the input method."""
81
82
        # Grab stats into self.stats
83
        if self.input_method == 'local':
84
            stats = self.update_local()
85
        elif self.input_method == 'snmp':
86
            stats = self.update_snmp()
87
        else:
88
            stats = self.get_init_value()
89
90
        # Update the stats
91
        self.stats = stats
92
93
        return self.stats
94
95
    def update_local(self):
96
        """Update CPU stats using psutil."""
97
        # Grab CPU stats using psutil's cpu_percent and cpu_times_percent
98
        # Get all possible values for CPU stats: user, system, idle,
99
        # nice (UNIX), iowait (Linux), irq (Linux, FreeBSD), steal (Linux 2.6.11+)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
100
        # The following stats are returned by the API but not displayed in the UI:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
101
        # softirq (Linux), guest (Linux 2.6.24+), guest_nice (Linux 3.2.0+)
102
103
        # Init new stats
104
        stats = self.get_init_value()
105
106
        stats['total'] = cpu_percent.get()
107
        cpu_times_percent = psutil.cpu_times_percent(interval=0.0)
108
        for stat in ['user', 'system', 'idle', 'nice', 'iowait',
109
                     'irq', 'softirq', 'steal', 'guest', 'guest_nice']:
110
            if hasattr(cpu_times_percent, stat):
111
                stats[stat] = getattr(cpu_times_percent, stat)
112
113
        # Additional CPU stats (number of events not as a %; psutil>=4.1.0)
114
        # ctx_switches: number of context switches (voluntary + involuntary) per second
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (87/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
115
        # interrupts: number of interrupts per second
116
        # soft_interrupts: number of software interrupts per second. Always set to 0 on Windows and SunOS.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (106/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
117
        # syscalls: number of system calls since boot. Always set to 0 on Linux.
118
        cpu_stats = psutil.cpu_stats()
119
        # By storing time data we enable Rx/s and Tx/s calculations in the
120
        # XML/RPC API, which would otherwise be overly difficult work
121
        # for users of the API
122
        time_since_update = getTimeSinceLastUpdate('cpu')
123
124
        # Previous CPU stats are stored in the cpu_stats_old variable
125
        if not hasattr(self, 'cpu_stats_old'):
126
            # First call, we init the cpu_stats_old var
127
            self.cpu_stats_old = cpu_stats
0 ignored issues
show
Coding Style introduced by
The attribute cpu_stats_old was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
128
        else:
129
            for stat in cpu_stats._fields:
130
                if getattr(cpu_stats, stat) is not None:
131
                    stats[stat] = getattr(cpu_stats, stat) - getattr(self.cpu_stats_old, stat)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (94/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
132
133
            stats['time_since_update'] = time_since_update
134
135
            # Core number is needed to compute the CTX switch limit
136
            stats['cpucore'] = self.nb_log_core
137
138
            # Save stats to compute next step
139
            self.cpu_stats_old = cpu_stats
0 ignored issues
show
Coding Style introduced by
The attribute cpu_stats_old was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
140
141
        return stats
142
143
    def update_snmp(self):
144
        """Update CPU stats using SNMP."""
145
146
        # Init new stats
147
        stats = self.get_init_value()
148
149
        # Update stats using SNMP
150
        if self.short_system_name in ('windows', 'esxi'):
151
            # Windows or VMWare ESXi
152
            # You can find the CPU utilization of windows system by querying the oid
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (84/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
153
            # Give also the number of core (number of element in the table)
154
            try:
155
                cpu_stats = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name],
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (90/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
156
                                                bulk=True)
157
            except KeyError:
158
                self.reset()
159
160
            # Iter through CPU and compute the idle CPU stats
161
            stats['nb_log_core'] = 0
162
            stats['idle'] = 0
163
            for c in cpu_stats:
0 ignored issues
show
Coding Style Naming introduced by
The name c does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
164
                if c.startswith('percent'):
165
                    stats['idle'] += float(cpu_stats['percent.3'])
166
                    stats['nb_log_core'] += 1
167
            if stats['nb_log_core'] > 0:
168
                stats['idle'] = stats['idle'] / stats['nb_log_core']
0 ignored issues
show
introduced by
division w/o __future__ statement
Loading history...
169
            stats['idle'] = 100 - stats['idle']
170
            stats['total'] = 100 - stats['idle']
171
172
        else:
173
            # Default behavor
174
            try:
175
                stats = self.get_stats_snmp(
176
                    snmp_oid=snmp_oid[self.short_system_name])
177
            except KeyError:
178
                stats = self.get_stats_snmp(
179
                    snmp_oid=snmp_oid['default'])
180
181
            if stats['idle'] == '':
182
                self.reset()
183
                return self.stats
184
185
            # Convert SNMP stats to float
186
            for key in iterkeys(stats):
187
                stats[key] = float(stats[key])
188
            stats['total'] = 100 - stats['idle']
189
190
        return stats
191
192
    def update_views(self):
193
        """Update stats views."""
194
        # Call the father's method
195
        super(Plugin, self).update_views()
196
197
        # Add specifics informations
198
        # Alert and log
199
        for key in ['user', 'system', 'iowait']:
200
            if key in self.stats:
201
                self.views[key]['decoration'] = self.get_alert_log(self.stats[key], header=key)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (95/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
202
        # Alert only
203
        for key in ['steal', 'total']:
204
            if key in self.stats:
205
                self.views[key]['decoration'] = self.get_alert(self.stats[key], header=key)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (91/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
206
        # Alert only but depend on Core number
207
        for key in ['ctx_switches']:
208
            if key in self.stats:
209
                self.views[key]['decoration'] = self.get_alert(self.stats[key], maximum=100 * self.stats['cpucore'], header=key)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (128/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
210
        # Optional
211
        for key in ['nice', 'irq', 'iowait', 'steal', 'ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls']:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (115/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
212
            if key in self.stats:
213
                self.views[key]['optional'] = True
214
215
    def msg_curse(self, args=None, max_width=None):
216
        """Return the list to display in the UI."""
217
        # Init the return message
218
        ret = []
219
220
        # Only process if stats exist and plugin not disable
221
        if not self.stats or self.args.percpu or self.is_disable():
222
            return ret
223
224
        # Build the string message
225
        # If user stat is not here, display only idle / total CPU usage (for
226
        # exemple on Windows OS)
227
        idle_tag = 'user' not in self.stats
228
229
        # Header
230
        msg = '{}'.format('CPU')
231
        ret.append(self.curse_add_line(msg, "TITLE"))
232
        trend_user = self.get_trend('user')
233
        trend_system = self.get_trend('system')
234
        if trend_user is None or trend_user is None:
235
            trend_cpu = None
236
        else:
237
            trend_cpu = trend_user + trend_system
238
        msg = ' {:4}'.format(self.trend_msg(trend_cpu))
239
        ret.append(self.curse_add_line(msg))
240
        # Total CPU usage
241
        msg = '{:5.1f}%'.format(self.stats['total'])
242
        if idle_tag:
243
            ret.append(self.curse_add_line(
244
                msg, self.get_views(key='total', option='decoration')))
245
        else:
246
            ret.append(self.curse_add_line(msg))
247
        # Nice CPU
248
        if 'nice' in self.stats:
249
            msg = '  {:8}'.format('nice:')
250
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='nice', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
251
            msg = '{:5.1f}%'.format(self.stats['nice'])
252
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='nice', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
253
        # ctx_switches
254
        if 'ctx_switches' in self.stats:
255
            msg = '  {:8}'.format('ctx_sw:')
256
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='ctx_switches', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (112/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
257
            msg = '{:>5}'.format(self.auto_unit(int(self.stats['ctx_switches'] // self.stats['time_since_update']),
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (115/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
258
                                                min_symbol='K'))
259
            ret.append(self.curse_add_line(
260
                msg, self.get_views(key='ctx_switches', option='decoration'),
261
                optional=self.get_views(key='ctx_switches', option='optional')))
262
263
        # New line
264
        ret.append(self.curse_new_line())
265
        # User CPU
266 View Code Duplication
        if 'user' in self.stats:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
267
            msg = '{:8}'.format('user:')
268
            ret.append(self.curse_add_line(msg))
269
            msg = '{:5.1f}%'.format(self.stats['user'])
270
            ret.append(self.curse_add_line(
271
                msg, self.get_views(key='user', option='decoration')))
272
        elif 'idle' in self.stats:
273
            msg = '{:8}'.format('idle:')
274
            ret.append(self.curse_add_line(msg))
275
            msg = '{:5.1f}%'.format(self.stats['idle'])
276
            ret.append(self.curse_add_line(msg))
277
        # IRQ CPU
278
        if 'irq' in self.stats:
279
            msg = '  {:8}'.format('irq:')
280
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='irq', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
281
            msg = '{:5.1f}%'.format(self.stats['irq'])
282
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='irq', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
283
        # interrupts
284
        if 'interrupts' in self.stats:
285
            msg = '  {:8}'.format('inter:')
286
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='interrupts', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (110/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
287
            msg = '{:>5}'.format(int(self.stats['interrupts'] // self.stats['time_since_update']))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (98/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
288
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='interrupts', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (110/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
289
290
        # New line
291
        ret.append(self.curse_new_line())
292
        # System CPU
293 View Code Duplication
        if 'system' in self.stats and not idle_tag:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
294
            msg = '{:8}'.format('system:')
295
            ret.append(self.curse_add_line(msg))
296
            msg = '{:5.1f}%'.format(self.stats['system'])
297
            ret.append(self.curse_add_line(
298
                msg, self.get_views(key='system', option='decoration')))
299
        else:
300
            msg = '{:8}'.format('core:')
301
            ret.append(self.curse_add_line(msg))
302
            msg = '{:>6}'.format(self.stats['nb_log_core'])
303
            ret.append(self.curse_add_line(msg))
304
        # IOWait CPU
305
        if 'iowait' in self.stats:
306
            msg = '  {:8}'.format('iowait:')
307
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='iowait', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (106/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
308
            msg = '{:5.1f}%'.format(self.stats['iowait'])
309
            ret.append(self.curse_add_line(
310
                msg, self.get_views(key='iowait', option='decoration'),
311
                optional=self.get_views(key='iowait', option='optional')))
312
        # soft_interrupts
313
        if 'soft_interrupts' in self.stats:
314
            msg = '  {:8}'.format('sw_int:')
315
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='soft_interrupts', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (115/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
316
            msg = '{:>5}'.format(int(self.stats['soft_interrupts'] // self.stats['time_since_update']))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
317
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='soft_interrupts', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (115/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
318
319
        # New line
320
        ret.append(self.curse_new_line())
321
        # Idle CPU
322
        if 'idle' in self.stats and not idle_tag:
323
            msg = '{:8}'.format('idle:')
324
            ret.append(self.curse_add_line(msg))
325
            msg = '{:5.1f}%'.format(self.stats['idle'])
326
            ret.append(self.curse_add_line(msg))
327
        # Steal CPU usage
328
        if 'steal' in self.stats:
329
            msg = '  {:8}'.format('steal:')
330
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='steal', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (105/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
331
            msg = '{:5.1f}%'.format(self.stats['steal'])
332
            ret.append(self.curse_add_line(
333
                msg, self.get_views(key='steal', option='decoration'),
334
                optional=self.get_views(key='steal', option='optional')))
335
        # syscalls
336
        # syscalls: number of system calls since boot. Always set to 0 on Linux. (do not display)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (97/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
337
        if 'syscalls' in self.stats and not LINUX:
338
            msg = '  {:8}'.format('syscal:')
339
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='syscalls', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (108/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
340
            msg = '{:>5}'.format(int(self.stats['syscalls'] // self.stats['time_since_update']))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (96/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
341
            ret.append(self.curse_add_line(msg, optional=self.get_views(key='syscalls', option='optional')))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (108/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
342
343
        # Return the message with decoration
344
        return ret
345