glances.config.Config.read()   F
last analyzed

Complexity

Conditions 15

Size

Total Lines 87
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 63
nop 1
dl 0
loc 87
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.config.Config.read() 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
0 ignored issues
show
introduced by Nicolas Hennion
import missing from __future__ import absolute_import
Loading history...
23
import sys
0 ignored issues
show
introduced by Alessio Sergi
import missing from __future__ import absolute_import
Loading history...
24
import multiprocessing
0 ignored issues
show
introduced by Nicolas Hennion
import missing from __future__ import absolute_import
Loading history...
25
from io import open
0 ignored issues
show
Bug Best Practice introduced by Alessio Sergi
This seems to re-define the built-in open.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
introduced by Alessio Sergi
import missing from __future__ import absolute_import
Loading history...
26
import re
0 ignored issues
show
introduced by Nicolas Hennion
import missing from __future__ import absolute_import
Loading history...
27
28
from glances.compat import ConfigParser, NoOptionError, system_exec
0 ignored issues
show
introduced by Nicolas Hennion
import missing from __future__ import absolute_import
Loading history...
29
from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS
0 ignored issues
show
introduced by Alessio Sergi
import missing from __future__ import absolute_import
Loading history...
30
from glances.logger import logger
0 ignored issues
show
introduced by Alessio Sergi
import missing from __future__ import absolute_import
Loading history...
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')
0 ignored issues
show
Coding Style introduced by Alessio Sergi
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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'),
0 ignored issues
show
Coding Style introduced by Alessio Sergi
This line is too long as per the coding-style (88/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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'),
0 ignored issues
show
Coding Style introduced by Alessio Sergi
This line is too long as per the coding-style (95/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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('(\`.+?\`)')
0 ignored issues
show
Bug introduced by Nicolas Hennion
A suspicious escape sequence \` was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
109
110
        self.parser = ConfigParser()
111
        self.read()
112
113
    def config_file_paths(self):
114
        r"""Get a list of config file paths.
115
116
        The list is built taking into account of the OS, priority and location.
117
118
        * custom path: /path/to/glances
119
        * Linux, SunOS: ~/.config/glances, /etc/glances
120
        * *BSD: ~/.config/glances, /usr/local/etc/glances
121
        * macOS: ~/Library/Application Support/glances, /usr/local/etc/glances
122
        * Windows: %APPDATA%\glances
123
124
        The config file will be searched in the following order of priority:
125
            * /path/to/file (via -C flag)
126
            * user's home directory (per-user settings)
127
            * system-wide directory (system-wide settings)
128
        """
129
        paths = []
130
131
        if self.config_dir:
132
            paths.append(self.config_dir)
133
134
        paths.append(os.path.join(user_config_dir(), self.config_filename))
135
        paths.append(os.path.join(system_config_dir(), self.config_filename))
136
137
        return paths
138
139
    def read(self):
140
        """Read the config file, if it exists. Using defaults otherwise."""
141
        for config_file in self.config_file_paths():
142
            logger.info('Search glances.conf file in {}'.format(config_file))
0 ignored issues
show
introduced by Nicolas Hennion
Use formatting in logging functions and pass the parameters as arguments
Loading history...
143
            if os.path.exists(config_file):
144
                try:
145
                    with open(config_file, encoding='utf-8') as f:
0 ignored issues
show
Coding Style Naming introduced by Alessio Sergi
The name f does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
146
                        self.parser.read_file(f)
147
                        self.parser.read(f)
148
                    logger.info("Read configuration file '{}'".format(config_file))
0 ignored issues
show
Coding Style introduced by Alessio Sergi
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by Alessio Sergi
Use formatting in logging functions and pass the parameters as arguments
Loading history...
149
                except UnicodeDecodeError as err:
150
                    logger.error("Can not read configuration file '{}': {}".format(config_file, err))
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
This line is too long as per the coding-style (101/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by Nicolas Hennion
Use formatting in logging functions and pass the parameters as arguments
Loading history...
151
                    sys.exit(1)
152
                # Save the loaded configuration file path (issue #374)
153
                self._loaded_config_file = config_file
154
                break
155
156
        # Quicklook
157
        if not self.parser.has_section('quicklook'):
158
            self.parser.add_section('quicklook')
159
        self.set_default_cwc('quicklook', 'cpu')
160
        self.set_default_cwc('quicklook', 'mem')
161
        self.set_default_cwc('quicklook', 'swap')
162
163
        # CPU
164
        if not self.parser.has_section('cpu'):
165
            self.parser.add_section('cpu')
166
        self.set_default_cwc('cpu', 'user')
167
        self.set_default_cwc('cpu', 'system')
168
        self.set_default_cwc('cpu', 'steal')
169
        # By default I/O wait should be lower than 1/number of CPU cores
170
        iowait_bottleneck = (1.0 / multiprocessing.cpu_count()) * 100.0
171
        self.set_default_cwc('cpu', 'iowait',
172
                             [str(iowait_bottleneck - (iowait_bottleneck * 0.20)),
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
173
                              str(iowait_bottleneck - (iowait_bottleneck * 0.10)),
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
174
                              str(iowait_bottleneck)])
175
        # Context switches bottleneck identification #1212
176
        ctx_switches_bottleneck = (500000 * 0.10) * multiprocessing.cpu_count()
177
        self.set_default_cwc('cpu', 'ctx_switches',
178
                             [str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.20)),
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
This line is too long as per the coding-style (94/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
179
                              str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.10)),
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
This line is too long as per the coding-style (94/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
180
                              str(ctx_switches_bottleneck)])
181
182
        # Per-CPU
183
        if not self.parser.has_section('percpu'):
184
            self.parser.add_section('percpu')
185
        self.set_default_cwc('percpu', 'user')
186
        self.set_default_cwc('percpu', 'system')
187
188
        # Load
189
        if not self.parser.has_section('load'):
190
            self.parser.add_section('load')
191
        self.set_default_cwc('load', cwc=['0.7', '1.0', '5.0'])
192
193
        # Mem
194
        if not self.parser.has_section('mem'):
195
            self.parser.add_section('mem')
196
        self.set_default_cwc('mem')
197
198
        # Swap
199
        if not self.parser.has_section('memswap'):
200
            self.parser.add_section('memswap')
201
        self.set_default_cwc('memswap')
202
203
        # NETWORK
204
        if not self.parser.has_section('network'):
205
            self.parser.add_section('network')
206
        self.set_default_cwc('network', 'rx')
207
        self.set_default_cwc('network', 'tx')
208
209
        # FS
210
        if not self.parser.has_section('fs'):
211
            self.parser.add_section('fs')
212
        self.set_default_cwc('fs')
213
214
        # Sensors
215
        if not self.parser.has_section('sensors'):
216
            self.parser.add_section('sensors')
217
        self.set_default_cwc('sensors', 'temperature_core', cwc=['60', '70', '80'])
0 ignored issues
show
Coding Style introduced by Alessio Sergi
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
218
        self.set_default_cwc('sensors', 'temperature_hdd', cwc=['45', '52', '60'])
0 ignored issues
show
Coding Style introduced by Alessio Sergi
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
219
        self.set_default_cwc('sensors', 'battery', cwc=['80', '90', '95'])
220
221
        # Process list
222
        if not self.parser.has_section('processlist'):
223
            self.parser.add_section('processlist')
224
        self.set_default_cwc('processlist', 'cpu')
225
        self.set_default_cwc('processlist', 'mem')
226
227
    @property
228
    def loaded_config_file(self):
229
        """Return the loaded configuration file."""
230
        return self._loaded_config_file
231
232
    def as_dict(self):
233
        """Return the configuration as a dict"""
234
        dictionary = {}
235
        for section in self.parser.sections():
236
            dictionary[section] = {}
237
            for option in self.parser.options(section):
238
                dictionary[section][option] = self.parser.get(section, option)
239
        return dictionary
240
241
    def sections(self):
242
        """Return a list of all sections."""
243
        return self.parser.sections()
244
245
    def items(self, section):
246
        """Return the items list of a section."""
247
        return self.parser.items(section)
248
249
    def has_section(self, section):
250
        """Return info about the existence of a section."""
251
        return self.parser.has_section(section)
252
253
    def set_default_cwc(self, section,
0 ignored issues
show
Bug Best Practice introduced by Nicolas Hennion
The default value [] might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
254
                        option_header=None,
255
                        cwc=['50', '70', '90']):
256
        """Set default values for careful, warning and critical."""
257
        if option_header is None:
258
            header = ''
259
        else:
260
            header = option_header + '_'
261
        self.set_default(section, header + 'careful', cwc[0])
262
        self.set_default(section, header + 'warning', cwc[1])
263
        self.set_default(section, header + 'critical', cwc[2])
264
265
    def set_default(self, section, option,
266
                    default):
267
        """If the option did not exist, create a default value."""
268
        if not self.parser.has_option(section, option):
269
            self.parser.set(section, option, default)
270
271
    def get_value(self, section, option,
272
                  default=None):
273
        """Get the value of an option, if it exists.
274
275
        If it did not exist, then return the default value.
276
277
        It allows user to define dynamic configuration key (see issue#1204)
278
        Dynamic vlaue should starts and end with the ` char
279
        Example: prefix=`hostname`
280
        """
281
        ret = default
282
        try:
283
            ret = self.parser.get(section, option)
284
        except NoOptionError:
285
            pass
286
287
        # Search a substring `foo` and replace it by the result of its exec
288
        if ret is not None:
289
            try:
290
                match = self.re_pattern.findall(ret)
291
                for m in match:
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name m does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
292
                    ret = ret.replace(m, system_exec(m[1:-1]))
293
            except TypeError:
294
                pass
295
        return ret
296
297
    def get_int_value(self, section, option, default=0):
298
        """Get the int value of an option, if it exists."""
299
        try:
300
            return self.parser.getint(section, option)
301
        except NoOptionError:
302
            return int(default)
303
304
    def get_float_value(self, section, option, default=0.0):
305
        """Get the float value of an option, if it exists."""
306
        try:
307
            return self.parser.getfloat(section, option)
308
        except NoOptionError:
309
            return float(default)
310
311
    def get_bool_value(self, section, option, default=True):
312
        """Get the bool value of an option, if it exists."""
313
        try:
314
            return self.parser.getboolean(section, option)
315
        except NoOptionError:
316
            return bool(default)
317