Test Failed
Push — develop ( 6b0de5...610ee0 )
by Nicolas
02:11
created

glances.plugins.wifi   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 97
dl 0
loc 214
rs 10
c 0
b 0
f 0
wmc 27

7 Methods

Rating   Name   Duplication   Size   Complexity  
A PluginModel.get_key() 0 6 1
A PluginModel.get_alert() 0 20 5
B PluginModel.update() 0 49 7
A PluginModel.update_views() 0 9 2
A PluginModel.exit() 0 6 2
C PluginModel.msg_curse() 0 48 9
A PluginModel.__init__() 0 9 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# SPDX-FileCopyrightText: 2023 Nicolas Hennion <[email protected]>
6
#
7
# SPDX-License-Identifier: LGPL-3.0-only
8
#
9
10
"""Wifi plugin.
11
12
Stats are retreived from the /proc/net/wireless file (Linux only):
13
14
# cat /proc/net/wireless
15
Inter-| sta-|   Quality        |   Discarded packets               | Missed | WE
16
 face | tus | link level noise |  nwid  crypt   frag  retry   misc | beacon | 22
17
wlp2s0: 0000   51.  -59.  -256        0      0      0      0   5881        0
18
"""
19
20
import operator
21
22
from glances.globals import nativestr, file_exists
23
from glances.plugins.plugin.model import GlancesPluginModel
24
from glances.logger import logger
25
26
# Backup solution is to use the /proc/net/wireless file
27
# but it only give signal information about the current hotspot
28
WIRELESS_FILE = '/proc/net/wireless'
29
wireless_file_exists = file_exists(WIRELESS_FILE)
30
31
if not wireless_file_exists:
32
    logger.debug("Wifi plugin is disabled (no %s file found)" % (WIRELESS_FILE))
33
34
# Fields description
35
# description: human readable description
36
# short_name: shortname to use un UI
37
# unit: unit type
38
# rate: if True then compute and add *_gauge and *_rate_per_is fields
39
# min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)...
40
fields_description = {
41
    'ssid': {
42
        'description': 'Wi-Fi network name.'
43
    },
44
    'quality_link': {
45
        'description': 'Signal quality level.',
46
        'unit': 'dBm',
47
    },
48
    'quality_level': {
49
        'description': 'Signal strong level.',
50
        'unit': 'dBm',
51
    },
52
}
53
54
55
class PluginModel(GlancesPluginModel):
56
    """Glances Wifi plugin.
57
58
    Get stats of the current Wifi hotspots.
59
    """
60
61
    def __init__(self, args=None, config=None):
62
        """Init the plugin."""
63
        super(PluginModel, self).__init__(args=args, config=config, stats_init_value=[])
64
65
        # We want to display the stat in the curse interface
66
        self.display_curse = True
67
68
        # Global Thread running all the scans
69
        self._thread = None
70
71
    def exit(self):
72
        """Overwrite the exit method to close threads."""
73
        if self._thread is not None:
74
            self._thread.stop()
75
        # Call the father class
76
        super(PluginModel, self).exit()
77
78
    def get_key(self):
79
        """Return the key of the list.
80
81
        :returns: string -- SSID is the dict key
82
        """
83
        return 'ssid'
84
85
    @GlancesPluginModel._check_decorator
86
    @GlancesPluginModel._log_result_decorator
87
    def update(self):
88
        """Update Wifi stats using the input method.
89
90
        Stats is a list of dict (one dict per hotspot)
91
92
        :returns: list -- Stats is a list of dict (hotspot)
93
        """
94
        # Init new stats
95
        stats = self.get_init_value()
96
97
        # Exist if we can not grab the stats
98
        if not wireless_file_exists:
99
            return stats
100
101
        if self.input_method == 'local' and wireless_file_exists:
102
            # As a backup solution, use the /proc/net/wireless file
103
            with open(WIRELESS_FILE, 'r') as f:
104
                # The first two lines are header
105
                f.readline()
106
                f.readline()
107
                # Others lines are Wifi stats
108
                wifi_stats = f.readline()
109
                while wifi_stats != '':
110
                    # Extract the stats
111
                    wifi_stats = wifi_stats.split()
112
                    # Add the Wifi link to the list
113
                    stats.append(
114
                        {
115
                            'key': self.get_key(),
116
                            'ssid': wifi_stats[0][:-1],
117
                            'quality_link': float(wifi_stats[2]),
118
                            'quality_level': float(wifi_stats[3]),
119
                        }
120
                    )
121
                    # Next line
122
                    wifi_stats = f.readline()
123
124
        elif self.input_method == 'snmp':
125
            # Update stats using SNMP
126
127
            # Not implemented yet
128
            pass
129
130
        # Update the stats
131
        self.stats = stats
132
133
        return self.stats
134
135
    def get_alert(self, value):
136
        """Overwrite the default get_alert method.
137
138
        Alert is on signal quality where lower is better...
139
140
        :returns: string -- Signal alert
141
        """
142
        ret = 'OK'
143
        try:
144
            if value <= self.get_limit('critical', stat_name=self.plugin_name):
145
                ret = 'CRITICAL'
146
            elif value <= self.get_limit('warning', stat_name=self.plugin_name):
147
                ret = 'WARNING'
148
            elif value <= self.get_limit('careful', stat_name=self.plugin_name):
149
                ret = 'CAREFUL'
150
        except (TypeError, KeyError):
151
            # Catch TypeError for issue1373
152
            ret = 'DEFAULT'
153
154
        return ret
155
156
    def update_views(self):
157
        """Update stats views."""
158
        # Call the father's method
159
        super(PluginModel, self).update_views()
160
161
        # Add specifics information
162
        # Alert on quality_level thresholds
163
        for i in self.stats:
164
            self.views[i[self.get_key()]]['quality_level']['decoration'] = self.get_alert(i['quality_level'])
165
166
    def msg_curse(self, args=None, max_width=None):
167
        """Return the dict to display in the curse interface."""
168
        # Init the return message
169
        ret = []
170
171
        # Only process if stats exist and display plugin enable...
172
        if not self.stats or not wireless_file_exists or self.is_disabled():
173
            return ret
174
175
        # Max size for the interface name
176
        if max_width:
177
            if_name_max_width = max_width - 5
178
        else:
179
            # No max_width defined, return an emptu curse message
180
            logger.debug("No max_width defined for the {} plugin, it will not be displayed.".format(self.plugin_name))
181
            return ret
182
183
        # Build the string message
184
        # Header
185
        msg = '{:{width}}'.format('WIFI', width=if_name_max_width)
186
        ret.append(self.curse_add_line(msg, "TITLE"))
187
        msg = '{:>7}'.format('dBm')
188
        ret.append(self.curse_add_line(msg))
189
190
        # Hotspot list (sorted by name)
191
        for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):
192
            # Do not display hotspot with no name (/ssid)...
193
            # of ssid/signal None... See issue #1151 and #issue1973
194
            if i['ssid'] == '' or i['ssid'] is None or i['quality_level'] is None:
195
                continue
196
            ret.append(self.curse_new_line())
197
            # New hotspot
198
            hotspot_name = i['ssid']
199
            msg = '{:{width}}'.format(
200
                nativestr(hotspot_name),
201
                width=if_name_max_width
202
            )
203
            ret.append(self.curse_add_line(msg))
204
            msg = '{:>7}'.format(
205
                i['quality_level'],
206
            )
207
            ret.append(
208
                self.curse_add_line(msg, self.get_views(item=i[self.get_key()],
209
                                                        key='quality_level',
210
                                                        option='decoration'))
211
            )
212
213
        return ret
214