glances_network.py 16.2 KB
Newer Older
A
Alessio Sergi 已提交
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
A
Alessio Sergi 已提交
4
#
N
nicolargo 已提交
5
# Copyright (C) 2019 Nicolargo <nicolas@nicolargo.com>
A
Alessio Sergi 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18
#
# 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 <http://www.gnu.org/licenses/>.
A
PEP 257  
Alessio Sergi 已提交
19 20

"""Network plugin."""
N
nicolargo 已提交
21
from __future__ import unicode_literals
A
Alessio Sergi 已提交
22

N
Nicolargo 已提交
23 24
import base64

25
from glances.timer import getTimeSinceLastUpdate
A
Alessio Sergi 已提交
26
from glances.plugins.glances_plugin import GlancesPlugin
N
nicolargo 已提交
27
from glances.compat import n
28
from glances.logger import logger
A
Alessio Sergi 已提交
29

A
flake8  
Alessio Sergi 已提交
30 31
import psutil

N
nicolargo 已提交
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
# {'interface_name': 'mpqemubr0-dummy',
# 'alias': None,
# 'time_since_update': 2.081636428833008,
# 'cumulative_rx': 0,
# 'rx': 0, 'cumulative_tx': 0, 'tx': 0, 'cumulative_cx': 0, 'cx': 0,
# 'is_up': False,
# 'speed': 0,
# 'key': 'interface_name'}
# Fields description
fields_description = {
    'interface_name': {'description': 'Interface name.',
                       'unit': 'string'},
    'alias': {'description': 'Interface alias name (optional).',
              'unit': 'string'},
    'rx': {'description': 'The received/input rate (in bit per second).',
           'unit': 'bps'},
    'tx': {'description': 'The sent/output rate (in bit per second).',
           'unit': 'bps'},
    'cumulative_rx': {'description': 'The number of bytes received through the interface (cumulative).',
                      'unit': 'bytes'},
    'cumulative_tx': {'description': 'The number of bytes sent through the interface (cumulative).',
                      'unit': 'bytes'},
    'speed': {'description': 'Maximum interface speed (in bit per second). Can return 0 on some operating-system.',
              'unit': 'bps'},
    'is_up': {'description': 'Is the interface up ?',
              'unit': 'bool'},
    'time_since_update': {'description': 'Number of seconds since last update.',
                          'unit': 'seconds'},
}

62
# SNMP OID
63
# http://www.net-snmp.org/docs/mibs/interfaces.html
64
# Dict key = interface_name
N
Nicolargo 已提交
65 66 67
snmp_oid = {'default': {'interface_name': '1.3.6.1.2.1.2.2.1.2',
                        'cumulative_rx': '1.3.6.1.2.1.2.2.1.10',
                        'cumulative_tx': '1.3.6.1.2.1.2.2.1.16'}}
68

69
# Define the history items list
70 71 72 73 74 75
items_history_list = [{'name': 'rx',
                       'description': 'Download rate per second',
                       'y_unit': 'bit/s'},
                      {'name': 'tx',
                       'description': 'Upload rate per second',
                       'y_unit': 'bit/s'}]
76

A
Alessio Sergi 已提交
77 78

class Plugin(GlancesPlugin):
A
PEP 257  
Alessio Sergi 已提交
79
    """Glances network plugin.
A
Alessio Sergi 已提交
80 81 82 83

    stats is a list
    """

84
    def __init__(self, args=None, config=None):
A
PEP 257  
Alessio Sergi 已提交
85
        """Init the plugin."""
86
        super(Plugin, self).__init__(args=args,
87
                                     config=config,
88
                                     items_history_list=items_history_list,
N
nicolargo 已提交
89
                                     fields_description=fields_description,
90
                                     stats_init_value=[])
A
Alessio Sergi 已提交
91 92 93

        # We want to display the stat in the curse interface
        self.display_curse = True
94

95
        # Hide stats if it has never been != 0
96 97 98 99 100
        if config is not None:
            self.hide_zero = config.get_bool_value(
                self.plugin_name, 'hide_zero', default=False)
        else:
            self.hide_zero = False
101
        self.hide_zero_fields = ['rx', 'tx']
A
Alessio Sergi 已提交
102

103 104 105 106
        # Force a first update because we need two update to have the first stat
        self.update()
        self.refresh_timer.set(0)

107
    def get_key(self):
A
PEP 257  
Alessio Sergi 已提交
108
        """Return the key of the list."""
109 110
        return 'interface_name'

111
    # @GlancesPlugin._check_decorator
112
    @GlancesPlugin._log_result_decorator
113
    def update(self):
A
PEP 257  
Alessio Sergi 已提交
114 115
        """Update network stats using the input method.

N
Nicolas Hennion 已提交
116
        Stats is a list of dict (one dict per interface)
A
Alessio Sergi 已提交
117
        """
118 119
        # Init new stats
        stats = self.get_init_value()
A
Alessio Sergi 已提交
120

121
        if self.input_method == 'local':
122 123
            # Update stats using the standard system lib

A
Alessio Sergi 已提交
124
            # Grab network interface stat using the psutil net_io_counter method
A
Alessio Sergi 已提交
125
            try:
126
                netiocounters = psutil.net_io_counters(pernic=True)
127 128
            except UnicodeDecodeError as e:
                logger.debug('Can not get network interface counters ({})'.format(e))
129
                return self.stats
A
Alessio Sergi 已提交
130

131 132
            # Grab interface's status (issue #765)
            # Grab interface's speed (issue #718)
133 134 135
            netstatus = {}
            try:
                netstatus = psutil.net_if_stats()
136
            except OSError as e:
A
Alessio Sergi 已提交
137
                # see psutil #797/glances #1106
138
                logger.debug('Can not get network interface status ({})'.format(e))
139

140
            # Previous network interface stats are stored in the network_old variable
141
            if not hasattr(self, 'network_old'):
142 143 144 145 146
                # First call, we init the network_old var
                try:
                    self.network_old = netiocounters
                except (IOError, UnboundLocalError):
                    pass
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
                return self.stats

            # By storing time data we enable Rx/s and Tx/s calculations in the
            # XML/RPC API, which would otherwise be overly difficult work
            # for users of the API
            time_since_update = getTimeSinceLastUpdate('net')

            # Loop over interfaces
            network_new = netiocounters
            for net in network_new:
                # Do not take hidden interface into account
                # or KeyError: 'eth0' when interface is not connected #1348
                if self.is_hide(net) or net not in netstatus:
                    continue
                try:
                    cumulative_rx = network_new[net].bytes_recv
                    cumulative_tx = network_new[net].bytes_sent
                    cumulative_cx = cumulative_rx + cumulative_tx
                    rx = cumulative_rx - self.network_old[net].bytes_recv
                    tx = cumulative_tx - self.network_old[net].bytes_sent
                    cx = rx + tx
N
nicolargo 已提交
168
                    netstat = {'interface_name': n(net),
169
                               'alias': self.has_alias(n(net)),
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
                               'time_since_update': time_since_update,
                               'cumulative_rx': cumulative_rx,
                               'rx': rx,
                               'cumulative_tx': cumulative_tx,
                               'tx': tx,
                               'cumulative_cx': cumulative_cx,
                               'cx': cx,
                               # Interface status
                               'is_up': netstatus[net].isup,
                               # Interface speed in Mbps, convert it to bps
                               # Can be always 0 on some OSes
                               'speed': netstatus[net].speed * 1048576,
                               # Set the key for the dict
                               'key': self.get_key()
                               }
                except KeyError:
                    continue
                else:
                    # Append the interface stats to the list
                    stats.append(netstat)

            # Save stats to compute next bitrate
            self.network_old = network_new
N
Nicolargo 已提交
193

194
        elif self.input_method == 'snmp':
195
            # Update stats using SNMP
196

197
            # SNMP bulk command to get all network interface in one shot
N
Nicolargo 已提交
198
            try:
199
                netiocounters = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name],
N
Nicolargo 已提交
200 201
                                                    bulk=True)
            except KeyError:
202
                netiocounters = self.get_stats_snmp(snmp_oid=snmp_oid['default'],
N
Nicolargo 已提交
203
                                                    bulk=True)
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

            # Previous network interface stats are stored in the network_old variable
            if not hasattr(self, 'network_old'):
                # First call, we init the network_old var
                try:
                    self.network_old = netiocounters
                except (IOError, UnboundLocalError):
                    pass
            else:
                # See description in the 'local' block
                time_since_update = getTimeSinceLastUpdate('net')

                # Loop over interfaces
                network_new = netiocounters

                for net in network_new:
220 221 222 223
                    # Do not take hidden interface into account
                    if self.is_hide(net):
                        continue

224
                    try:
N
Nicolargo 已提交
225 226
                        # Windows: a tips is needed to convert HEX to TXT
                        # 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
227
                        if self.short_system_name == 'windows':
N
Nicolargo 已提交
228
                            try:
229
                                interface_name = str(base64.b16decode(net[2:-2].upper()))
N
Nicolargo 已提交
230
                            except TypeError:
231
                                interface_name = net
N
Nicolargo 已提交
232
                        else:
233
                            interface_name = net
234

235 236 237 238 239 240 241 242
                        cumulative_rx = float(network_new[net]['cumulative_rx'])
                        cumulative_tx = float(network_new[net]['cumulative_tx'])
                        cumulative_cx = cumulative_rx + cumulative_tx
                        rx = cumulative_rx - float(self.network_old[net]['cumulative_rx'])
                        tx = cumulative_tx - float(self.network_old[net]['cumulative_tx'])
                        cx = rx + tx
                        netstat = {
                            'interface_name': interface_name,
243
                            'alias': self.has_alias(interface_name),
244 245 246 247 248 249 250
                            'time_since_update': time_since_update,
                            'cumulative_rx': cumulative_rx,
                            'rx': rx,
                            'cumulative_tx': cumulative_tx,
                            'tx': tx,
                            'cumulative_cx': cumulative_cx,
                            'cx': cx}
251 252 253
                    except KeyError:
                        continue
                    else:
254
                        netstat['key'] = self.get_key()
255
                        stats.append(netstat)
A
Alessio Sergi 已提交
256

257 258
                # Save stats to compute next bitrate
                self.network_old = network_new
259

260 261 262
        # Update the stats
        self.stats = stats

N
Nicolas Hennion 已提交
263 264
        return self.stats

265
    def update_views(self):
A
PEP 257  
Alessio Sergi 已提交
266
        """Update stats views."""
267
        # Call the father's method
A
Alessio Sergi 已提交
268
        super(Plugin, self).update_views()
269

270 271 272
        # Check if the stats should be hidden
        self.update_views_hidden()

273 274
        # Add specifics informations
        # Alert
275
        for i in self.get_raw():
276
            ifrealname = i['interface_name'].split(':')[0]
N
nicolargo 已提交
277
            # Convert rate in bps (to be able to compare to interface speed)
278 279
            bps_rx = int(i['rx'] // i['time_since_update'] * 8)
            bps_tx = int(i['tx'] // i['time_since_update'] * 8)
N
nicolargo 已提交
280

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
            # Decorate the bitrate with the configuration file thresolds
            alert_rx = self.get_alert(bps_rx, header=ifrealname + '_rx')
            alert_tx = self.get_alert(bps_tx, header=ifrealname + '_tx')
            # If nothing is define in the configuration file...
            # ... then use the interface speed (not available on all systems)
            if alert_rx == 'DEFAULT' and 'speed' in i and i['speed'] != 0:
                alert_rx = self.get_alert(current=bps_rx,
                                          maximum=i['speed'],
                                          header='rx')
            if alert_tx == 'DEFAULT' and 'speed' in i and i['speed'] != 0:
                alert_tx = self.get_alert(current=bps_tx,
                                          maximum=i['speed'],
                                          header='tx')
            # then decorates
            self.views[i[self.get_key()]]['rx']['decoration'] = alert_rx
            self.views[i[self.get_key()]]['tx']['decoration'] = alert_tx
297

N
Nicolargo 已提交
298
    def msg_curse(self, args=None, max_width=None):
A
PEP 257  
Alessio Sergi 已提交
299
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
300 301 302
        # Init the return message
        ret = []

303
        # Only process if stats exist and display plugin enable...
N
nicolargo 已提交
304
        if not self.stats or self.is_disable():
305 306
            return ret

N
Nicolargo 已提交
307
        # Max size for the interface name
308
        name_max_width = max_width - 12
N
Nicolargo 已提交
309

A
Alessio Sergi 已提交
310
        # Header
311
        msg = '{:{width}}'.format('NETWORK', width=name_max_width)
A
Alessio Sergi 已提交
312
        ret.append(self.curse_add_line(msg, "TITLE"))
313
        if args.network_cumul:
N
Nicolas Hennion 已提交
314
            # Cumulative stats
315
            if args.network_sum:
316
                # Sum stats
317
                msg = '{:>14}'.format('Rx+Tx')
318 319 320
                ret.append(self.curse_add_line(msg))
            else:
                # Rx/Tx stats
321
                msg = '{:>7}'.format('Rx')
322
                ret.append(self.curse_add_line(msg))
323
                msg = '{:>7}'.format('Tx')
324
                ret.append(self.curse_add_line(msg))
N
Nicolas Hennion 已提交
325 326
        else:
            # Bitrate stats
327
            if args.network_sum:
328
                # Sum stats
329
                msg = '{:>14}'.format('Rx+Tx/s')
330 331
                ret.append(self.curse_add_line(msg))
            else:
332
                msg = '{:>7}'.format('Rx/s')
333
                ret.append(self.curse_add_line(msg))
334
                msg = '{:>7}'.format('Tx/s')
A
Alessio Sergi 已提交
335
                ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
336
        # Interface list (sorted by name)
337
        for i in self.sorted_stats():
338 339 340
            # Do not display interface in down state (issue #765)
            if ('is_up' in i) and (i['is_up'] is False):
                continue
341 342
            # Hide stats if never be different from 0 (issue #1787)
            if all([self.get_views(item=i[self.get_key()], key=f, option='hidden') for f in self.hide_zero_fields]):
N
nicolargo 已提交
343
                continue
A
Alessio Sergi 已提交
344
            # Format stats
345
            # Is there an alias for the interface name ?
346 347 348 349
            if i['alias'] is None:
                ifname = i['interface_name'].split(':')[0]
            else:
                ifname = i['alias']
350
            if len(ifname) > name_max_width:
351
                # Cut interface name if it is too long
352
                ifname = '_' + ifname[-name_max_width + 1:]
353

354
            if args.byte:
355
                # Bytes per second (for dummy)
356 357
                to_bit = 1
                unit = ''
A
Alessio Sergi 已提交
358
            else:
A
Alessio Sergi 已提交
359
                # Bits per second (for real network administrator | Default)
360 361 362 363 364 365 366 367 368 369 370 371 372 373
                to_bit = 8
                unit = 'b'

            if args.network_cumul:
                rx = self.auto_unit(int(i['cumulative_rx'] * to_bit)) + unit
                tx = self.auto_unit(int(i['cumulative_tx'] * to_bit)) + unit
                sx = self.auto_unit(int(i['cumulative_rx'] * to_bit) +
                                    int(i['cumulative_tx'] * to_bit)) + unit
            else:
                rx = self.auto_unit(int(i['rx'] // i['time_since_update'] * to_bit)) + unit
                tx = self.auto_unit(int(i['tx'] // i['time_since_update'] * to_bit)) + unit
                sx = self.auto_unit(int(i['rx'] // i['time_since_update'] * to_bit) +
                                    int(i['tx'] // i['time_since_update'] * to_bit)) + unit

A
Alessio Sergi 已提交
374 375
            # New line
            ret.append(self.curse_new_line())
376
            msg = '{:{width}}'.format(ifname, width=name_max_width)
A
Alessio Sergi 已提交
377
            ret.append(self.curse_add_line(msg))
378
            if args.network_sum:
379
                msg = '{:>14}'.format(sx)
380 381
                ret.append(self.curse_add_line(msg))
            else:
382
                msg = '{:>7}'.format(rx)
A
Alessio Sergi 已提交
383
                ret.append(self.curse_add_line(
384
                    msg, self.get_views(item=i[self.get_key()], key='rx', option='decoration')))
385
                msg = '{:>7}'.format(tx)
A
Alessio Sergi 已提交
386
                ret.append(self.curse_add_line(
387
                    msg, self.get_views(item=i[self.get_key()], key='tx', option='decoration')))
A
Alessio Sergi 已提交
388 389

        return ret