glances_wifi.py 7.1 KB
Newer Older
N
nicolargo 已提交
1 2 3 4
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
A
Alessio Sergi 已提交
5
# Copyright (C) 2017 Nicolargo <nicolas@nicolargo.com>
N
nicolargo 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
#
# 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/>.

"""Wifi plugin."""

import operator

from glances.logger import logger
from glances.plugins.glances_plugin import GlancesPlugin

import psutil
# Use the Wifi Python lib (https://pypi.python.org/pypi/wifi)
# Linux-only
try:
    from wifi.scan import Cell
    from wifi.exceptions import InterfaceError
A
Alessio Sergi 已提交
33
except ImportError:
N
nicolargo 已提交
34 35 36 37 38 39 40 41
    logger.debug("Wifi library not found. Glances cannot grab Wifi info.")
    wifi_tag = False
else:
    wifi_tag = True


class Plugin(GlancesPlugin):
    """Glances Wifi plugin.
42

N
nicolargo 已提交
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    Get stats of the current Wifi hotspots.
    """

    def __init__(self, args=None):
        """Init the plugin."""
        super(Plugin, self).__init__(args=args)

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

        # Init the stats
        self.reset()

    def get_key(self):
        """Return the key of the list.

        :returns: string -- SSID is the dict key
        """
        return 'ssid'

    def reset(self):
        """Reset/init the stats to an empty list.

        :returns: None
        """
        self.stats = []

    @GlancesPlugin._check_decorator
    @GlancesPlugin._log_result_decorator
    def update(self):
        """Update Wifi stats using the input method.

        Stats is a list of dict (one dict per hotspot)

        :returns: list -- Stats is a list of dict (hotspot)
        """
        # Reset stats
        self.reset()

        # Exist if we can not grab the stats
N
nicolargo 已提交
83
        if not wifi_tag:
N
nicolargo 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
            return self.stats

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

            # Grab network interface stat using the PsUtil net_io_counter method
            try:
                netiocounters = psutil.net_io_counters(pernic=True)
            except UnicodeDecodeError:
                return self.stats

            for net in netiocounters:
                # Do not take hidden interface into account
                if self.is_hide(net):
                    continue

                # Grab the stats using the Wifi Python lib
                try:
                    wifi_cells = Cell.all(net)
                except InterfaceError:
                    # Not a Wifi interface
                    pass
N
Nicolargo 已提交
106 107 108 109
                except Exception as e:
                    # Other error
                    logger.debug("WIFI plugin: Can not grab cellule stats ({})".format(e))
                    pass
N
nicolargo 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
                else:
                    for wifi_cell in wifi_cells:
                        hotspot = {
                            'key': self.get_key(),
                            'ssid': wifi_cell.ssid,
                            'signal': wifi_cell.signal,
                            'quality': wifi_cell.quality,
                            'encrypted': wifi_cell.encrypted,
                            'encryption_type': wifi_cell.encryption_type if wifi_cell.encrypted else None
                        }
                        # Add the hotspot to the list
                        self.stats.append(hotspot)

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

            # Not implemented yet
            pass

        return self.stats

131 132
    def get_alert(self, value):
        """Overwrite the default get_alert method.
133

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
        Alert is on signal quality where lower is better...

        :returns: string -- Signal alert
        """
        ret = 'OK'
        try:
            if value <= self.get_limit('critical', stat_name=self.plugin_name):
                ret = 'CRITICAL'
            elif value <= self.get_limit('warning', stat_name=self.plugin_name):
                ret = 'WARNING'
            elif value <= self.get_limit('careful', stat_name=self.plugin_name):
                ret = 'CAREFUL'
        except KeyError:
            ret = 'DEFAULT'

        return ret

N
nicolargo 已提交
151 152 153 154 155 156
    def update_views(self):
        """Update stats views."""
        # Call the father's method
        super(Plugin, self).update_views()

        # Add specifics informations
157 158 159 160
        # Alert on signal thresholds
        for i in self.stats:
            self.views[i[self.get_key()]]['signal']['decoration'] = self.get_alert(i['signal'])
            self.views[i[self.get_key()]]['quality']['decoration'] = self.views[i[self.get_key()]]['signal']['decoration']
N
nicolargo 已提交
161 162 163 164 165 166 167 168 169 170 171 172

    def msg_curse(self, args=None, max_width=None):
        """Return the dict to display in the curse interface."""
        # Init the return message
        ret = []

        # Only process if stats exist and display plugin enable...
        if not self.stats or args.disable_wifi or not wifi_tag:
            return ret

        # Max size for the interface name
        if max_width is not None and max_width >= 23:
N
nicolargo 已提交
173 174
            # Interface size name = max_width - space for encyption + quality
            ifname_max_width = max_width - 5
N
nicolargo 已提交
175
        else:
N
nicolargo 已提交
176
            ifname_max_width = 16
N
nicolargo 已提交
177 178 179 180 181

        # Build the string message
        # Header
        msg = '{:{width}}'.format('WIFI', width=ifname_max_width)
        ret.append(self.curse_add_line(msg, "TITLE"))
182
        msg = '{:>7}'.format('dBm')
N
nicolargo 已提交
183 184 185 186
        ret.append(self.curse_add_line(msg))

        # Hotspot list (sorted by name)
        for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):
187 188 189
            # Do not display hotspot with no name (/ssid)...
            # of ssid None... See issue #1151
            if i['ssid'] == '' or i['ssid'] is None:
N
nicolargo 已提交
190
                continue
191
            ret.append(self.curse_new_line())
N
nicolargo 已提交
192 193
            # New hotspot
            hotspotname = i['ssid']
N
nicolargo 已提交
194 195
            # Add the encryption type (if it is available)
            if i['encrypted']:
196
                hotspotname += ' {}'.format(i['encryption_type'])
N
nicolargo 已提交
197
            # Cut hotspotname if it is too long
N
nicolargo 已提交
198 199
            if len(hotspotname) > ifname_max_width:
                hotspotname = '_' + hotspotname[-ifname_max_width + 1:]
N
nicolargo 已提交
200
            # Add the new hotspot to the message
N
nicolargo 已提交
201 202
            msg = '{:{width}}'.format(hotspotname, width=ifname_max_width)
            ret.append(self.curse_add_line(msg))
203 204 205 206 207
            msg = '{:>7}'.format(i['signal'], width=ifname_max_width)
            ret.append(self.curse_add_line(msg,
                                           self.get_views(item=i[self.get_key()],
                                                          key='signal',
                                                          option='decoration')))
N
nicolargo 已提交
208 209

        return ret