Test Failed
Push — develop ( abf64f...1d1151 )
by Nicolas
02:59
created

glances/config.py (2 issues)

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
0 ignored issues
show
Bug Best Practice introduced by
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...
26
import re
27
28
from glances.compat import ConfigParser, NoOptionError, 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('(\`.+?\`)')
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))
143
            if os.path.exists(config_file):
144
                try:
145
                    with open(config_file, encoding='utf-8') as f:
146
                        self.parser.read_file(f)
147
                        self.parser.read(f)
148
                    logger.info("Read configuration file '{}'".format(config_file))
149
                except UnicodeDecodeError as err:
150
                    logger.error("Can not read configuration file '{}': {}".format(config_file, err))
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)),
173
                              str(iowait_bottleneck - (iowait_bottleneck * 0.10)),
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)),
179
                              str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.10)),
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'])
218
        self.set_default_cwc('sensors', 'temperature_hdd', cwc=['45', '52', '60'])
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
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:
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