Test Failed
Push — master ( aea994...69b639 )
by Nicolas
03:44 queued 10s
created

glances.config.Config.sections_set_default()   D

Complexity

Conditions 13

Size

Total Lines 81
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 56
nop 1
dl 0
loc 81
rs 4.2
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.config.Config.sections_set_default() 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
# 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
"""Manage the configuration file."""
21
22
import os
23
import sys
24
import multiprocessing
25
from io import open
26
import re
27
28
from glances.compat import ConfigParser, NoOptionError, NoSectionError, system_exec
29
from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS
30
from glances.logger import logger
31
32
33
def user_config_dir():
34
    r"""Return the per-user config dir (full path).
35
36
    - Linux, *BSD, SunOS: ~/.config/glances
37
    - macOS: ~/Library/Application Support/glances
38
    - Windows: %APPDATA%\glances
39
    """
40
    if WINDOWS:
41
        path = os.environ.get('APPDATA')
42
    elif MACOS:
43
        path = os.path.expanduser('~/Library/Application Support')
44
    else:
45
        path = os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config')
46
    if path is None:
47
        path = ''
48
    else:
49
        path = os.path.join(path, 'glances')
50
51
    return path
52
53
54
def user_cache_dir():
55
    r"""Return the per-user cache dir (full path).
56
57
    - Linux, *BSD, SunOS: ~/.cache/glances
58
    - macOS: ~/Library/Caches/glances
59
    - Windows: {%LOCALAPPDATA%,%APPDATA%}\glances\cache
60
    """
61
    if WINDOWS:
62
        path = os.path.join(os.environ.get('LOCALAPPDATA') or os.environ.get('APPDATA'),
63
                            'glances', 'cache')
64
    elif MACOS:
65
        path = os.path.expanduser('~/Library/Caches/glances')
66
    else:
67
        path = os.path.join(os.environ.get('XDG_CACHE_HOME') or os.path.expanduser('~/.cache'),
68
                            'glances')
69
70
    return path
71
72
73
def system_config_dir():
74
    r"""Return the system-wide config dir (full path).
75
76
    - Linux, SunOS: /etc/glances
77
    - *BSD, macOS: /usr/local/etc/glances
78
    - Windows: %APPDATA%\glances
79
    """
80
    if LINUX or SUNOS:
81
        path = '/etc'
82
    elif BSD or MACOS:
83
        path = '/usr/local/etc'
84
    else:
85
        path = os.environ.get('APPDATA')
86
    if path is None:
87
        path = ''
88
    else:
89
        path = os.path.join(path, 'glances')
90
91
    return path
92
93
94
class Config(object):
95
96
    """This class is used to access/read config file, if it exists.
97
98
    :param config_dir: the path to search for config file
99
    :type config_dir: str or None
100
    """
101
102
    def __init__(self, config_dir=None):
103
        self.config_dir = config_dir
104
        self.config_filename = 'glances.conf'
105
        self._loaded_config_file = None
106
107
        # Re patern for optimize research of `foo`
108
        self.re_pattern = re.compile(r'(\`.+?\`)')
109
110
        try:
111
            self.parser = ConfigParser(interpolation=None)
112
        except TypeError:
113
            self.parser = ConfigParser()
114
115
        self.read()
116
117
    def config_file_paths(self):
118
        r"""Get a list of config file paths.
119
120
        The list is built taking into account of the OS, priority and location.
121
122
        * custom path: /path/to/glances
123
        * Linux, SunOS: ~/.config/glances, /etc/glances
124
        * *BSD: ~/.config/glances, /usr/local/etc/glances
125
        * macOS: ~/Library/Application Support/glances, /usr/local/etc/glances
126
        * Windows: %APPDATA%\glances
127
128
        The config file will be searched in the following order of priority:
129
            * /path/to/file (via -C flag)
130
            * user's home directory (per-user settings)
131
            * system-wide directory (system-wide settings)
132
        """
133
        paths = []
134
135
        if self.config_dir:
136
            paths.append(self.config_dir)
137
138
        paths.append(os.path.join(user_config_dir(), self.config_filename))
139
        paths.append(os.path.join(system_config_dir(), self.config_filename))
140
141
        return paths
142
143
    def read(self):
144
        """Read the config file, if it exists. Using defaults otherwise."""
145
        for config_file in self.config_file_paths():
146
            logger.info('Search glances.conf file in {}'.format(config_file))
147
            if os.path.exists(config_file):
148
                try:
149
                    with open(config_file, encoding='utf-8') as f:
150
                        self.parser.read_file(f)
151
                        self.parser.read(f)
152
                    logger.info("Read configuration file '{}'".format(config_file))
153
                except UnicodeDecodeError as err:
154
                    logger.error("Can not read configuration file '{}': {}".format(config_file, err))
155
                    sys.exit(1)
156
                # Save the loaded configuration file path (issue #374)
157
                self._loaded_config_file = config_file
158
                break
159
160
        # Set the default values for section not configured
161
        self.sections_set_default()
162
163
    def sections_set_default(self):
164
        # Globals
165
        if not self.parser.has_section('global'):
166
            self.parser.add_section('global')
167
        self.set_default('global', 'strftime_format', '')
168
169
        # check_update
170
        if not self.parser.has_section('global'):
171
            self.parser.add_section('global')
172
        self.set_default('global', 'check_update', 'false')
173
174
        # Quicklook
175
        if not self.parser.has_section('quicklook'):
176
            self.parser.add_section('quicklook')
177
        self.set_default_cwc('quicklook', 'cpu')
178
        self.set_default_cwc('quicklook', 'mem')
179
        self.set_default_cwc('quicklook', 'swap')
180
181
        # CPU
182
        if not self.parser.has_section('cpu'):
183
            self.parser.add_section('cpu')
184
        self.set_default_cwc('cpu', 'user')
185
        self.set_default_cwc('cpu', 'system')
186
        self.set_default_cwc('cpu', 'steal')
187
        # By default I/O wait should be lower than 1/number of CPU cores
188
        iowait_bottleneck = (1.0 / multiprocessing.cpu_count()) * 100.0
189
        self.set_default_cwc('cpu', 'iowait',
190
                             [str(iowait_bottleneck - (iowait_bottleneck * 0.20)),
191
                              str(iowait_bottleneck - (iowait_bottleneck * 0.10)),
192
                              str(iowait_bottleneck)])
193
        # Context switches bottleneck identification #1212
194
        ctx_switches_bottleneck = (500000 * 0.10) * multiprocessing.cpu_count()
195
        self.set_default_cwc('cpu', 'ctx_switches',
196
                             [str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.20)),
197
                              str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.10)),
198
                              str(ctx_switches_bottleneck)])
199
200
        # Per-CPU
201
        if not self.parser.has_section('percpu'):
202
            self.parser.add_section('percpu')
203
        self.set_default_cwc('percpu', 'user')
204
        self.set_default_cwc('percpu', 'system')
205
206
        # Load
207
        if not self.parser.has_section('load'):
208
            self.parser.add_section('load')
209
        self.set_default_cwc('load', cwc=['0.7', '1.0', '5.0'])
210
211
        # Mem
212
        if not self.parser.has_section('mem'):
213
            self.parser.add_section('mem')
214
        self.set_default_cwc('mem')
215
216
        # Swap
217
        if not self.parser.has_section('memswap'):
218
            self.parser.add_section('memswap')
219
        self.set_default_cwc('memswap')
220
221
        # NETWORK
222
        if not self.parser.has_section('network'):
223
            self.parser.add_section('network')
224
        self.set_default_cwc('network', 'rx')
225
        self.set_default_cwc('network', 'tx')
226
227
        # FS
228
        if not self.parser.has_section('fs'):
229
            self.parser.add_section('fs')
230
        self.set_default_cwc('fs')
231
232
        # Sensors
233
        if not self.parser.has_section('sensors'):
234
            self.parser.add_section('sensors')
235
        self.set_default_cwc('sensors', 'temperature_core', cwc=['60', '70', '80'])
236
        self.set_default_cwc('sensors', 'temperature_hdd', cwc=['45', '52', '60'])
237
        self.set_default_cwc('sensors', 'battery', cwc=['80', '90', '95'])
238
239
        # Process list
240
        if not self.parser.has_section('processlist'):
241
            self.parser.add_section('processlist')
242
        self.set_default_cwc('processlist', 'cpu')
243
        self.set_default_cwc('processlist', 'mem')
244
245
    @property
246
    def loaded_config_file(self):
247
        """Return the loaded configuration file."""
248
        return self._loaded_config_file
249
250
    def as_dict(self):
251
        """Return the configuration as a dict"""
252
        dictionary = {}
253
        for section in self.parser.sections():
254
            dictionary[section] = {}
255
            for option in self.parser.options(section):
256
                dictionary[section][option] = self.parser.get(section, option)
257
        return dictionary
258
259
    def sections(self):
260
        """Return a list of all sections."""
261
        return self.parser.sections()
262
263
    def items(self, section):
264
        """Return the items list of a section."""
265
        return self.parser.items(section)
266
267
    def has_section(self, section):
268
        """Return info about the existence of a section."""
269
        return self.parser.has_section(section)
270
271
    def set_default_cwc(self, section,
272
                        option_header=None,
273
                        cwc=['50', '70', '90']):
274
        """Set default values for careful, warning and critical."""
275
        if option_header is None:
276
            header = ''
277
        else:
278
            header = option_header + '_'
279
        self.set_default(section, header + 'careful', cwc[0])
280
        self.set_default(section, header + 'warning', cwc[1])
281
        self.set_default(section, header + 'critical', cwc[2])
282
283
    def set_default(self, section, option,
284
                    default):
285
        """If the option did not exist, create a default value."""
286
        if not self.parser.has_option(section, option):
287
            self.parser.set(section, option, default)
288
289
    def get_value(self, section, option,
290
                  default=None):
291
        """Get the value of an option, if it exists.
292
293
        If it did not exist, then return the default value.
294
295
        It allows user to define dynamic configuration key (see issue#1204)
296
        Dynamic value should starts and end with the ` char
297
        Example: prefix=`hostname`
298
        """
299
        ret = default
300
        try:
301
            ret = self.parser.get(section, option)
302
        except (NoOptionError, NoSectionError):
303
            pass
304
305
        # Search a substring `foo` and replace it by the result of its exec
306
        if ret is not None:
307
            try:
308
                match = self.re_pattern.findall(ret)
309
                for m in match:
310
                    ret = ret.replace(m, system_exec(m[1:-1]))
311
            except TypeError:
312
                pass
313
        return ret
314
315
    def get_int_value(self, section, option, default=0):
316
        """Get the int value of an option, if it exists."""
317
        try:
318
            return self.parser.getint(section, option)
319
        except (NoOptionError, NoSectionError):
320
            return int(default)
321
322
    def get_float_value(self, section, option, default=0.0):
323
        """Get the float value of an option, if it exists."""
324
        try:
325
            return self.parser.getfloat(section, option)
326
        except (NoOptionError, NoSectionError):
327
            return float(default)
328
329
    def get_bool_value(self, section, option, default=True):
330
        """Get the bool value of an option, if it exists."""
331
        try:
332
            return self.parser.getboolean(section, option)
333
        except (NoOptionError, NoSectionError):
334
            return bool(default)
335