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

glances/plugins/glances_network.py (34 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
"""Network plugin."""
21
from __future__ import unicode_literals
22
23
import base64
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
24
import operator
25
26
from glances.timer import getTimeSinceLastUpdate
27
from glances.plugins.glances_plugin import GlancesPlugin
28
from glances.compat import n, u, b, nativestr
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
Unused b imported from glances.compat
Loading history...
Unused u imported from glances.compat
Loading history...
Unused nativestr imported from glances.compat
Loading history...
29
30
import psutil
31
32
# SNMP OID
33
# http://www.net-snmp.org/docs/mibs/interfaces.html
34
# Dict key = interface_name
35
snmp_oid = {'default': {'interface_name': '1.3.6.1.2.1.2.2.1.2',
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
                        'cumulative_rx': '1.3.6.1.2.1.2.2.1.10',
37
                        'cumulative_tx': '1.3.6.1.2.1.2.2.1.16'}}
38
39
# Define the history items list
40
items_history_list = [{'name': 'rx',
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...
41
                       'description': 'Download rate per second',
42
                       'y_unit': 'bit/s'},
43
                      {'name': 'tx',
44
                       'description': 'Upload rate per second',
45
                       'y_unit': 'bit/s'}]
46
47
48
class Plugin(GlancesPlugin):
49
    """Glances network plugin.
50
51
    stats is a list
52
    """
53
54
    def __init__(self, args=None, config=None):
55
        """Init the plugin."""
56
        super(Plugin, self).__init__(args=args,
57
                                     config=config,
58
                                     items_history_list=items_history_list,
59
                                     stats_init_value=[])
60
61
        # We want to display the stat in the curse interface
62
        self.display_curse = True
63
64
    def get_key(self):
65
        """Return the key of the list."""
66
        return 'interface_name'
67
68
    @GlancesPlugin._check_decorator
69
    @GlancesPlugin._log_result_decorator
70
    def update(self):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (16/15).
Loading history...
71
        """Update network stats using the input method.
72
73
        Stats is a list of dict (one dict per interface)
74
        """
75
        # Init new stats
76
        stats = self.get_init_value()
77
78
        if self.input_method == 'local':
79
            # Update stats using the standard system lib
80
81
            # Grab network interface stat using the psutil net_io_counter method
82
            try:
83
                netiocounters = psutil.net_io_counters(pernic=True)
84
            except UnicodeDecodeError as e:
0 ignored issues
show
Coding Style Naming introduced by
The name e 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...
85
                logger.debug('Can not get network interface counters ({})'.format(e))
0 ignored issues
show
This line is too long as per the coding-style (85/80).

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

Loading history...
Comprehensibility Best Practice introduced by
Undefined variable 'logger'
Loading history...
Comprehensibility Best Practice introduced by
The variable logger does not seem to be defined.
Loading history...
86
                return self.stats
87
88
            # Grab interface's status (issue #765)
89
            # Grab interface's speed (issue #718)
90
            netstatus = {}
91
            try:
92
                netstatus = psutil.net_if_stats()
93
            except OSError as e:
0 ignored issues
show
Coding Style Naming introduced by
The name e 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...
94
                # see psutil #797/glances #1106
95
                logger.debug('Can not get network interface status ({})'.format(e))
0 ignored issues
show
This line is too long as per the coding-style (83/80).

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

Loading history...
Comprehensibility Best Practice introduced by
Undefined variable 'logger'
Loading history...
96
97
            # Previous network interface stats are stored in the network_old variable
0 ignored issues
show
This line is too long as per the coding-style (85/80).

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

Loading history...
98
            if not hasattr(self, 'network_old'):
99
                # First call, we init the network_old var
100
                try:
101
                    self.network_old = netiocounters
0 ignored issues
show
The attribute network_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...
102
                except (IOError, UnboundLocalError):
103
                    pass
104
                return self.stats
105
106
            # By storing time data we enable Rx/s and Tx/s calculations in the
107
            # XML/RPC API, which would otherwise be overly difficult work
108
            # for users of the API
109
            time_since_update = getTimeSinceLastUpdate('net')
110
111
            # Loop over interfaces
112
            network_new = netiocounters
113
            for net in network_new:
114
                # Do not take hidden interface into account
115
                # or KeyError: 'eth0' when interface is not connected #1348
116
                if self.is_hide(net) or net not in netstatus:
117
                    continue
118
                try:
119
                    cumulative_rx = network_new[net].bytes_recv
120
                    cumulative_tx = network_new[net].bytes_sent
121
                    cumulative_cx = cumulative_rx + cumulative_tx
122
                    rx = cumulative_rx - self.network_old[net].bytes_recv
123
                    tx = cumulative_tx - self.network_old[net].bytes_sent
124
                    cx = rx + tx
125
                    netstat = {'interface_name': n(net),
126
                               'time_since_update': time_since_update,
127
                               'cumulative_rx': cumulative_rx,
128
                               'rx': rx,
129
                               'cumulative_tx': cumulative_tx,
130
                               'tx': tx,
131
                               'cumulative_cx': cumulative_cx,
132
                               'cx': cx,
133
                               # Interface status
134
                               'is_up': netstatus[net].isup,
135
                               # Interface speed in Mbps, convert it to bps
136
                               # Can be always 0 on some OSes
137
                               'speed': netstatus[net].speed * 1048576,
138
                               # Set the key for the dict
139
                               'key': self.get_key()
140
                               }
0 ignored issues
show
Wrong continued indentation (remove 1 space).
Loading history...
141
                except KeyError:
142
                    continue
143
                else:
144
                    # Append the interface stats to the list
145
                    stats.append(netstat)
146
147
            # Save stats to compute next bitrate
148
            self.network_old = network_new
0 ignored issues
show
The attribute network_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...
149
150
        elif self.input_method == 'snmp':
151
            # Update stats using SNMP
152
153
            # SNMP bulk command to get all network interface in one shot
154
            try:
155
                netiocounters = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name],
156
                                                    bulk=True)
157
            except KeyError:
158
                netiocounters = self.get_stats_snmp(snmp_oid=snmp_oid['default'],
159
                                                    bulk=True)
160
161
            # Previous network interface stats are stored in the network_old variable
0 ignored issues
show
This line is too long as per the coding-style (85/80).

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

Loading history...
162
            if not hasattr(self, 'network_old'):
163
                # First call, we init the network_old var
164
                try:
165
                    self.network_old = netiocounters
0 ignored issues
show
The attribute network_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...
166
                except (IOError, UnboundLocalError):
167
                    pass
168
            else:
169
                # See description in the 'local' block
170
                time_since_update = getTimeSinceLastUpdate('net')
171
172
                # Loop over interfaces
173
                network_new = netiocounters
174
175
                for net in network_new:
176
                    # Do not take hidden interface into account
177
                    if self.is_hide(net):
178
                        continue
179
180
                    try:
181
                        # Windows: a tips is needed to convert HEX to TXT
182
                        # http://blogs.technet.com/b/networking/archive/2009/12/18/how-to-query-the-list-of-network-interfaces-using-snmp-via-the-ifdescr-counter.aspx
183
                        if self.short_system_name == 'windows':
184
                            try:
185
                                interface_name = str(base64.b16decode(net[2:-2].upper()))
186
                            except TypeError:
187
                                interface_name = net
188
                        else:
189
                            interface_name = net
190
191
                        cumulative_rx = float(network_new[net]['cumulative_rx'])
192
                        cumulative_tx = float(network_new[net]['cumulative_tx'])
193
                        cumulative_cx = cumulative_rx + cumulative_tx
194
                        rx = cumulative_rx - float(self.network_old[net]['cumulative_rx'])
195
                        tx = cumulative_tx - float(self.network_old[net]['cumulative_tx'])
196
                        cx = rx + tx
197
                        netstat = {
198
                            'interface_name': interface_name,
199
                            'time_since_update': time_since_update,
200
                            'cumulative_rx': cumulative_rx,
201
                            'rx': rx,
202
                            'cumulative_tx': cumulative_tx,
203
                            'tx': tx,
204
                            'cumulative_cx': cumulative_cx,
205
                            'cx': cx}
206
                    except KeyError:
207
                        continue
208
                    else:
209
                        netstat['key'] = self.get_key()
210
                        stats.append(netstat)
211
212
                # Save stats to compute next bitrate
213
                self.network_old = network_new
0 ignored issues
show
The attribute network_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...
214
215
        # Update the stats
216
        self.stats = stats
217
218
        return self.stats
219
220
    def update_views(self):
221
        """Update stats views."""
222
        # Call the father's method
223
        super(Plugin, self).update_views()
224
225
        # Add specifics informations
226
        # Alert
227
        for i in self.stats:
228
            ifrealname = i['interface_name'].split(':')[0]
229
            # Convert rate in bps ( to be able to compare to interface speed)
230
            bps_rx = int(i['rx'] // i['time_since_update'] * 8)
231
            bps_tx = int(i['tx'] // i['time_since_update'] * 8)
232
            # Decorate the bitrate with the configuration file thresolds
233
            alert_rx = self.get_alert(bps_rx, header=ifrealname + '_rx')
234
            alert_tx = self.get_alert(bps_tx, header=ifrealname + '_tx')
235
            # If nothing is define in the configuration file...
236
            # ... then use the interface speed (not available on all systems)
237
            if alert_rx == 'DEFAULT' and 'speed' in i and i['speed'] != 0:
238
                alert_rx = self.get_alert(current=bps_rx,
239
                                          maximum=i['speed'],
240
                                          header='rx')
241
            if alert_tx == 'DEFAULT' and 'speed' in i and i['speed'] != 0:
242
                alert_tx = self.get_alert(current=bps_tx,
243
                                          maximum=i['speed'],
244
                                          header='tx')
245
            # then decorates
246
            self.views[i[self.get_key()]]['rx']['decoration'] = alert_rx
247
            self.views[i[self.get_key()]]['tx']['decoration'] = alert_tx
248
249
    def msg_curse(self, args=None, max_width=None):
250
        """Return the dict to display in the curse interface."""
251
        # Init the return message
252
        ret = []
253
254
        # Only process if stats exist and display plugin enable...
255
        if not self.stats or self.is_disable():
256
            return ret
257
258
        # Max size for the interface name
259
        name_max_width = max_width - 12
260
261
        # Header
262
        msg = '{:{width}}'.format('NETWORK', width=name_max_width)
263
        ret.append(self.curse_add_line(msg, "TITLE"))
264
        if args.network_cumul:
265
            # Cumulative stats
266
            if args.network_sum:
267
                # Sum stats
268
                msg = '{:>14}'.format('Rx+Tx')
269
                ret.append(self.curse_add_line(msg))
270
            else:
271
                # Rx/Tx stats
272
                msg = '{:>7}'.format('Rx')
273
                ret.append(self.curse_add_line(msg))
274
                msg = '{:>7}'.format('Tx')
275
                ret.append(self.curse_add_line(msg))
276
        else:
277
            # Bitrate stats
278
            if args.network_sum:
279
                # Sum stats
280
                msg = '{:>14}'.format('Rx+Tx/s')
281
                ret.append(self.curse_add_line(msg))
282
            else:
283
                msg = '{:>7}'.format('Rx/s')
284
                ret.append(self.curse_add_line(msg))
285
                msg = '{:>7}'.format('Tx/s')
286
                ret.append(self.curse_add_line(msg))
287
        # Interface list (sorted by name)
288
        for i in self.sorted_stats():
289
            # Do not display interface in down state (issue #765)
290
            if ('is_up' in i) and (i['is_up'] is False):
291
                continue
292
            # Format stats
293
            # Is there an alias for the interface name ?
294
            ifrealname = i['interface_name'].split(':')[0]
295
            ifname = self.has_alias(i['interface_name'])
296
            if ifname is None:
297
                ifname = ifrealname
298
            if len(ifname) > name_max_width:
299
                # Cut interface name if it is too long
300
                ifname = '_' + ifname[-name_max_width + 1:]
301
302
            if args.byte:
303
                # Bytes per second (for dummy)
304
                to_bit = 1
305
                unit = ''
306
            else:
307
                # Bits per second (for real network administrator | Default)
308
                to_bit = 8
309
                unit = 'b'
310
311
            if args.network_cumul:
312
                rx = self.auto_unit(int(i['cumulative_rx'] * to_bit)) + unit
0 ignored issues
show
Coding Style Naming introduced by
The name rx 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...
313
                tx = self.auto_unit(int(i['cumulative_tx'] * to_bit)) + unit
0 ignored issues
show
Coding Style Naming introduced by
The name tx 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...
314
                sx = self.auto_unit(int(i['cumulative_rx'] * to_bit) +
0 ignored issues
show
Coding Style Naming introduced by
The name sx 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...
315
                                    int(i['cumulative_tx'] * to_bit)) + unit
316
            else:
317
                rx = self.auto_unit(int(i['rx'] // i['time_since_update'] * to_bit)) + unit
0 ignored issues
show
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...
Coding Style Naming introduced by
The name rx 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...
318
                tx = self.auto_unit(int(i['tx'] // i['time_since_update'] * to_bit)) + unit
0 ignored issues
show
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...
Coding Style Naming introduced by
The name tx 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...
319
                sx = self.auto_unit(int(i['rx'] // i['time_since_update'] * to_bit) +
0 ignored issues
show
This line is too long as per the coding-style (85/80).

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

Loading history...
Coding Style Naming introduced by
The name sx 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...
320
                                    int(i['tx'] // i['time_since_update'] * to_bit)) + unit
0 ignored issues
show
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...
321
322
            # New line
323
            ret.append(self.curse_new_line())
324
            msg = '{:{width}}'.format(ifname, width=name_max_width)
325
            ret.append(self.curse_add_line(msg))
326
            if args.network_sum:
327
                msg = '{:>14}'.format(sx)
328
                ret.append(self.curse_add_line(msg))
329
            else:
330
                msg = '{:>7}'.format(rx)
331
                ret.append(self.curse_add_line(
332
                    msg, self.get_views(item=i[self.get_key()], key='rx', option='decoration')))
0 ignored issues
show
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...
333
                msg = '{:>7}'.format(tx)
334
                ret.append(self.curse_add_line(
335
                    msg, self.get_views(item=i[self.get_key()], key='tx', option='decoration')))
0 ignored issues
show
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...
336
337
        return ret
338