glances.plugins.glances_network.Plugin.msg_curse()   F
last analyzed

Complexity

Conditions 15

Size

Total Lines 98
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 61
nop 3
dl 0
loc 98
rs 2.9998
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like glances.plugins.glances_network.Plugin.msg_curse() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# SPDX-FileCopyrightText: 2022 Nicolas Hennion <[email protected]>
6
#
7
# SPDX-License-Identifier: LGPL-3.0-only
8
#
9
10
"""Network plugin."""
11
from __future__ import unicode_literals
12
13
import base64
14
15
from glances.timer import getTimeSinceLastUpdate
16
from glances.plugins.glances_plugin import GlancesPlugin
17
from glances.compat import n
18
from glances.logger import logger
19
20
import psutil
21
22
# {'interface_name': 'mpqemubr0-dummy',
23
# 'alias': None,
24
# 'time_since_update': 2.081636428833008,
25
# 'cumulative_rx': 0,
26
# 'rx': 0, 'cumulative_tx': 0, 'tx': 0, 'cumulative_cx': 0, 'cx': 0,
27
# 'is_up': False,
28
# 'speed': 0,
29
# 'key': 'interface_name'}
30
# Fields description
31
fields_description = {
32
    'interface_name': {'description': 'Interface name.', 'unit': 'string'},
33
    'alias': {'description': 'Interface alias name (optional).', 'unit': 'string'},
34
    'rx': {'description': 'The received/input rate (in bit per second).', 'unit': 'bps'},
35
    'tx': {'description': 'The sent/output rate (in bit per second).', 'unit': 'bps'},
36
    'cx': {'description': 'The cumulative received+sent rate (in bit per second).', 'unit': 'bps'},
37
    'cumulative_rx': {
38
        'description': 'The number of bytes received through the interface (cumulative).',
39
        'unit': 'bytes',
40
    },
41
    'cumulative_tx': {'description': 'The number of bytes sent through the interface (cumulative).', 'unit': 'bytes'},
42
    'cumulative_cx': {
43
        'description': 'The cumulative number of bytes reveived and sent through the interface (cumulative).',
44
        'unit': 'bytes',
45
    },
46
    'speed': {
47
        'description': 'Maximum interface speed (in bit per second). Can return 0 on some operating-system.',
48
        'unit': 'bps',
49
    },
50
    'is_up': {'description': 'Is the interface up ?', 'unit': 'bool'},
51
    'time_since_update': {'description': 'Number of seconds since last update.', 'unit': 'seconds'},
52
}
53
54
# SNMP OID
55
# http://www.net-snmp.org/docs/mibs/interfaces.html
56
# Dict key = interface_name
57
snmp_oid = {
58
    'default': {
59
        'interface_name': '1.3.6.1.2.1.2.2.1.2',
60
        'cumulative_rx': '1.3.6.1.2.1.2.2.1.10',
61
        'cumulative_tx': '1.3.6.1.2.1.2.2.1.16',
62
    }
63
}
64
65
# Define the history items list
66
items_history_list = [
67
    {'name': 'rx', 'description': 'Download rate per second', 'y_unit': 'bit/s'},
68
    {'name': 'tx', 'description': 'Upload rate per second', 'y_unit': 'bit/s'},
69
]
70
71
72
class Plugin(GlancesPlugin):
73
    """Glances network plugin.
74
75
    stats is a list
76
    """
77
78 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...
79
        """Init the plugin."""
80
        super(Plugin, self).__init__(
81
            args=args,
82
            config=config,
83
            items_history_list=items_history_list,
84
            fields_description=fields_description,
85
            stats_init_value=[],
86
        )
87
88
        # We want to display the stat in the curse interface
89
        self.display_curse = True
90
91
        # Hide stats if it has never been != 0
92
        if config is not None:
93
            self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False)
94
        else:
95
            self.hide_zero = False
96
        self.hide_zero_fields = ['rx', 'tx']
97
98
        # Force a first update because we need two update to have the first stat
99
        self.update()
100
        self.refresh_timer.set(0)
101
102
    def get_key(self):
103
        """Return the key of the list."""
104
        return 'interface_name'
105
106
    # @GlancesPlugin._check_decorator
107
    @GlancesPlugin._log_result_decorator
108
    def update(self):
109
        """Update network stats using the input method.
110
111
        :return: list of stats dict (one dict per interface)
112
        """
113
        # Init new stats
114
        stats = self.get_init_value()
115
116
        if self.input_method == 'local':
117
            # Update stats using the standard system lib
118
119
            # Grab network interface stat using the psutil net_io_counter method
120
            try:
121
                net_io_counters = psutil.net_io_counters(pernic=True)
122
            except UnicodeDecodeError as e:
123
                logger.debug('Can not get network interface counters ({})'.format(e))
124
                return self.stats
125
126
            # Grab interface's status (issue #765)
127
            # Grab interface's speed (issue #718)
128
            net_status = {}
129
            try:
130
                net_status = psutil.net_if_stats()
131
            except OSError as e:
132
                # see psutil #797/glances #1106
133
                logger.debug('Can not get network interface status ({})'.format(e))
134
135
            # Previous network interface stats are stored in the network_old variable
136
            if not hasattr(self, 'network_old'):
137
                # First call, we init the network_old var
138
                try:
139
                    self.network_old = net_io_counters
140
                except (IOError, UnboundLocalError):
141
                    pass
142
                return self.stats
143
144
            # By storing time data we enable Rx/s and Tx/s calculations in the
145
            # XML/RPC API, which would otherwise be overly difficult work
146
            # for users of the API
147
            time_since_update = getTimeSinceLastUpdate('net')
148
149
            # Loop over interfaces
150
            network_new = net_io_counters
151
            for net in network_new:
152
                # Do not take hidden interface into account
153
                # or KeyError: 'eth0' when interface is not connected #1348
154
                if not self.is_display(net) or net not in net_status:
155
                    continue
156
                try:
157
                    cumulative_rx = network_new[net].bytes_recv
158
                    cumulative_tx = network_new[net].bytes_sent
159
                    cumulative_cx = cumulative_rx + cumulative_tx
160
                    rx = cumulative_rx - self.network_old[net].bytes_recv
161
                    tx = cumulative_tx - self.network_old[net].bytes_sent
162
                    cx = rx + tx
163
                    netstat = {
164
                        'interface_name': n(net),
165
                        'alias': self.has_alias(n(net)),
166
                        'time_since_update': time_since_update,
167
                        'cumulative_rx': cumulative_rx,
168
                        'rx': rx,
169
                        'cumulative_tx': cumulative_tx,
170
                        'tx': tx,
171
                        'cumulative_cx': cumulative_cx,
172
                        'cx': cx,
173
                        # Interface status
174
                        'is_up': net_status[net].isup,
175
                        # Interface speed in Mbps, convert it to bps
176
                        # Can be always 0 on some OSes
177
                        'speed': net_status[net].speed * 1048576,
178
                        # Set the key for the dict
179
                        'key': self.get_key(),
180
                    }
181
                except KeyError:
182
                    continue
183
                else:
184
                    # Append the interface stats to the list
185
                    stats.append(netstat)
186
187
            # Save stats to compute next bitrate
188
            self.network_old = network_new
189
190
        elif self.input_method == 'snmp':
191
            # Update stats using SNMP
192
193
            # SNMP bulk command to get all network interface in one shot
194
            try:
195
                net_io_counters = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True)
196
            except KeyError:
197
                net_io_counters = self.get_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True)
198
199
            # Previous network interface stats are stored in the network_old variable
200
            if not hasattr(self, 'network_old'):
201
                # First call, we init the network_old var
202
                try:
203
                    self.network_old = net_io_counters
204
                except (IOError, UnboundLocalError):
205
                    pass
206
            else:
207
                # See description in the 'local' block
208
                time_since_update = getTimeSinceLastUpdate('net')
209
210
                # Loop over interfaces
211
                network_new = net_io_counters
212
213
                for net in network_new:
214
                    # Do not take hidden interface into account
215
                    if not self.is_display(net):
216
                        continue
217
218
                    try:
219
                        # Windows: a tips is needed to convert HEX to TXT
220
                        # 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
221
                        if self.short_system_name == 'windows':
222
                            try:
223
                                interface_name = str(base64.b16decode(net[2:-2].upper()))
224
                            except TypeError:
225
                                interface_name = net
226
                        else:
227
                            interface_name = net
228
229
                        cumulative_rx = float(network_new[net]['cumulative_rx'])
230
                        cumulative_tx = float(network_new[net]['cumulative_tx'])
231
                        cumulative_cx = cumulative_rx + cumulative_tx
232
                        rx = cumulative_rx - float(self.network_old[net]['cumulative_rx'])
233
                        tx = cumulative_tx - float(self.network_old[net]['cumulative_tx'])
234
                        cx = rx + tx
235
                        netstat = {
236
                            'interface_name': interface_name,
237
                            'alias': self.has_alias(interface_name),
238
                            'time_since_update': time_since_update,
239
                            'cumulative_rx': cumulative_rx,
240
                            'rx': rx,
241
                            'cumulative_tx': cumulative_tx,
242
                            'tx': tx,
243
                            'cumulative_cx': cumulative_cx,
244
                            'cx': cx,
245
                        }
246
                    except KeyError:
247
                        continue
248
                    else:
249
                        netstat['key'] = self.get_key()
250
                        stats.append(netstat)
251
252
                # Save stats to compute next bitrate
253
                self.network_old = network_new
254
255
        # Update the stats
256
        self.stats = stats
257
258
        return self.stats
259
260
    def update_views(self):
261
        """Update stats views."""
262
        # Call the father's method
263
        super(Plugin, self).update_views()
264
265
        # Check if the stats should be hidden
266
        self.update_views_hidden()
267
268
        # Add specifics information
269
        # Alert
270
        for i in self.get_raw():
271
            if i['time_since_update'] == 0:
272
                # Skip alert if no timespan to measure
273
                continue
274
275
            if_real_name = i['interface_name'].split(':')[0]
276
            # Convert rate in bps (to be able to compare to interface speed)
277
            bps_rx = int(i['rx'] // i['time_since_update'] * 8)
278
            bps_tx = int(i['tx'] // i['time_since_update'] * 8)
279
280
            # Decorate the bitrate with the configuration file thresholds
281
            alert_rx = self.get_alert(bps_rx, header=if_real_name + '_rx')
282
            alert_tx = self.get_alert(bps_tx, header=if_real_name + '_tx')
283
            # If nothing is define in the configuration file...
284
            # ... then use the interface speed (not available on all systems)
285
            if alert_rx == 'DEFAULT' and 'speed' in i and i['speed'] != 0:
286
                alert_rx = self.get_alert(current=bps_rx, maximum=i['speed'], header='rx')
287
            if alert_tx == 'DEFAULT' and 'speed' in i and i['speed'] != 0:
288
                alert_tx = self.get_alert(current=bps_tx, maximum=i['speed'], header='tx')
289
            # then decorates
290
            self.views[i[self.get_key()]]['rx']['decoration'] = alert_rx
291
            self.views[i[self.get_key()]]['tx']['decoration'] = alert_tx
292
293
    def msg_curse(self, args=None, max_width=None):
294
        """Return the dict to display in the curse interface."""
295
        # Init the return message
296
        ret = []
297
298
        # Only process if stats exist and display plugin enable...
299
        if not self.stats or self.is_disabled():
300
            return ret
301
302
        # Max size for the interface name
303
        name_max_width = max_width - 12
304
305
        # Header
306
        msg = '{:{width}}'.format('NETWORK', width=name_max_width)
307
        ret.append(self.curse_add_line(msg, "TITLE"))
308
        if args.network_cumul:
309
            # Cumulative stats
310
            if args.network_sum:
311
                # Sum stats
312
                msg = '{:>14}'.format('Rx+Tx')
313
                ret.append(self.curse_add_line(msg))
314
            else:
315
                # Rx/Tx stats
316
                msg = '{:>7}'.format('Rx')
317
                ret.append(self.curse_add_line(msg))
318
                msg = '{:>7}'.format('Tx')
319
                ret.append(self.curse_add_line(msg))
320
        else:
321
            # Bitrate stats
322
            if args.network_sum:
323
                # Sum stats
324
                msg = '{:>14}'.format('Rx+Tx/s')
325
                ret.append(self.curse_add_line(msg))
326
            else:
327
                msg = '{:>7}'.format('Rx/s')
328
                ret.append(self.curse_add_line(msg))
329
                msg = '{:>7}'.format('Tx/s')
330
                ret.append(self.curse_add_line(msg))
331
        # Interface list (sorted by name)
332
        for i in self.sorted_stats():
333
            # Do not display interface in down state (issue #765)
334
            if ('is_up' in i) and (i['is_up'] is False):
335
                continue
336
            # Hide stats if never be different from 0 (issue #1787)
337
            if all([self.get_views(item=i[self.get_key()], key=f, option='hidden') for f in self.hide_zero_fields]):
338
                continue
339
            # Format stats
340
            # Is there an alias for the interface name ?
341
            if i['alias'] is None:
342
                if_name = i['interface_name'].split(':')[0]
343
            else:
344
                if_name = i['alias']
345
            if len(if_name) > name_max_width:
346
                # Cut interface name if it is too long
347
                if_name = '_' + if_name[-name_max_width + 1 :]
348
349
            if args.byte:
350
                # Bytes per second (for dummy)
351
                to_bit = 1
352
                unit = ''
353
            else:
354
                # Bits per second (for real network administrator | Default)
355
                to_bit = 8
356
                unit = 'b'
357
358
            if args.network_cumul:
359
                rx = self.auto_unit(int(i['cumulative_rx'] * to_bit)) + unit
360
                tx = self.auto_unit(int(i['cumulative_tx'] * to_bit)) + unit
361
                sx = self.auto_unit(int(i['cumulative_rx'] * to_bit) + int(i['cumulative_tx'] * to_bit)) + unit
362
            else:
363
                rx = self.auto_unit(int(i['rx'] // i['time_since_update'] * to_bit)) + unit
364
                tx = self.auto_unit(int(i['tx'] // i['time_since_update'] * to_bit)) + unit
365
                sx = (
366
                    self.auto_unit(
367
                        int(i['rx'] // i['time_since_update'] * to_bit)
368
                        + int(i['tx'] // i['time_since_update'] * to_bit)
369
                    )
370
                    + unit
371
                )
372
373
            # New line
374
            ret.append(self.curse_new_line())
375
            msg = '{:{width}}'.format(if_name, width=name_max_width)
376
            ret.append(self.curse_add_line(msg))
377
            if args.network_sum:
378
                msg = '{:>14}'.format(sx)
379
                ret.append(self.curse_add_line(msg))
380
            else:
381
                msg = '{:>7}'.format(rx)
382
                ret.append(
383
                    self.curse_add_line(msg, self.get_views(item=i[self.get_key()], key='rx', option='decoration'))
384
                )
385
                msg = '{:>7}'.format(tx)
386
                ret.append(
387
                    self.curse_add_line(msg, self.get_views(item=i[self.get_key()], key='tx', option='decoration'))
388
                )
389
390
        return ret
391