Completed
Push — master ( 040528...aea994 )
by Nicolas
03:23
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
26
import re
27
28
from glances.compat import ConfigParser, NoOptionError, NoSectionError, system_exec
0 ignored issues
show
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...
import missing from __future__ import absolute_import
Loading history...
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
        # Globals
161
        if not self.parser.has_section('global'):
162
            self.parser.add_section('global')
163
        self.set_default('global', 'strftime_format', '')
164
165
        # check_update
166
        if not self.parser.has_section('global'):
167
            self.parser.add_section('global')
168
        self.set_default('global', 'check_update', 'false')
169
170
        # Quicklook
171
        if not self.parser.has_section('quicklook'):
172
            self.parser.add_section('quicklook')
173
        self.set_default_cwc('quicklook', 'cpu')
174
        self.set_default_cwc('quicklook', 'mem')
175
        self.set_default_cwc('quicklook', 'swap')
176
177
        # CPU
178
        if not self.parser.has_section('cpu'):
179
            self.parser.add_section('cpu')
180
        self.set_default_cwc('cpu', 'user')
181
        self.set_default_cwc('cpu', 'system')
182
        self.set_default_cwc('cpu', 'steal')
183
        # By default I/O wait should be lower than 1/number of CPU cores
184
        iowait_bottleneck = (1.0 / multiprocessing.cpu_count()) * 100.0
185
        self.set_default_cwc('cpu', 'iowait',
186
                             [str(iowait_bottleneck - (iowait_bottleneck * 0.20)),
187
                              str(iowait_bottleneck - (iowait_bottleneck * 0.10)),
188
                              str(iowait_bottleneck)])
189
        # Context switches bottleneck identification #1212
190
        ctx_switches_bottleneck = (500000 * 0.10) * multiprocessing.cpu_count()
191
        self.set_default_cwc('cpu', 'ctx_switches',
192
                             [str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.20)),
193
                              str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.10)),
194
                              str(ctx_switches_bottleneck)])
195
196
        # Per-CPU
197
        if not self.parser.has_section('percpu'):
198
            self.parser.add_section('percpu')
199
        self.set_default_cwc('percpu', 'user')
200
        self.set_default_cwc('percpu', 'system')
201
202
        # Load
203
        if not self.parser.has_section('load'):
204
            self.parser.add_section('load')
205
        self.set_default_cwc('load', cwc=['0.7', '1.0', '5.0'])
206
207
        # Mem
208
        if not self.parser.has_section('mem'):
209
            self.parser.add_section('mem')
210
        self.set_default_cwc('mem')
211
212
        # Swap
213
        if not self.parser.has_section('memswap'):
214
            self.parser.add_section('memswap')
215
        self.set_default_cwc('memswap')
216
217
        # NETWORK
218
        if not self.parser.has_section('network'):
219
            self.parser.add_section('network')
220
        self.set_default_cwc('network', 'rx')
221
        self.set_default_cwc('network', 'tx')
222
223
        # FS
224
        if not self.parser.has_section('fs'):
225
            self.parser.add_section('fs')
226
        self.set_default_cwc('fs')
227
228
        # Sensors
229
        if not self.parser.has_section('sensors'):
230
            self.parser.add_section('sensors')
231
        self.set_default_cwc('sensors', 'temperature_core', cwc=['60', '70', '80'])
232
        self.set_default_cwc('sensors', 'temperature_hdd', cwc=['45', '52', '60'])
233
        self.set_default_cwc('sensors', 'battery', cwc=['80', '90', '95'])
234
235
        # Process list
236
        if not self.parser.has_section('processlist'):
237
            self.parser.add_section('processlist')
238
        self.set_default_cwc('processlist', 'cpu')
239
        self.set_default_cwc('processlist', 'mem')
240
241
    @property
242
    def loaded_config_file(self):
243
        """Return the loaded configuration file."""
244
        return self._loaded_config_file
245
246
    def as_dict(self):
247
        """Return the configuration as a dict"""
248
        dictionary = {}
249
        for section in self.parser.sections():
250
            dictionary[section] = {}
251
            for option in self.parser.options(section):
252
                dictionary[section][option] = self.parser.get(section, option)
253
        return dictionary
254
255
    def sections(self):
256
        """Return a list of all sections."""
257
        return self.parser.sections()
258
259
    def items(self, section):
260
        """Return the items list of a section."""
261
        return self.parser.items(section)
262
263
    def has_section(self, section):
264
        """Return info about the existence of a section."""
265
        return self.parser.has_section(section)
266
267
    def set_default_cwc(self, section,
268
                        option_header=None,
269
                        cwc=['50', '70', '90']):
270
        """Set default values for careful, warning and critical."""
271
        if option_header is None:
272
            header = ''
273
        else:
274
            header = option_header + '_'
275
        self.set_default(section, header + 'careful', cwc[0])
276
        self.set_default(section, header + 'warning', cwc[1])
277
        self.set_default(section, header + 'critical', cwc[2])
278
279
    def set_default(self, section, option,
280
                    default):
281
        """If the option did not exist, create a default value."""
282
        if not self.parser.has_option(section, option):
283
            self.parser.set(section, option, default)
284
285
    def get_value(self, section, option,
286
                  default=None):
287
        """Get the value of an option, if it exists.
288
289
        If it did not exist, then return the default value.
290
291
        It allows user to define dynamic configuration key (see issue#1204)
292
        Dynamic vlaue should starts and end with the ` char
293
        Example: prefix=`hostname`
294
        """
295
        ret = default
296
        try:
297
            ret = self.parser.get(section, option)
298
        except (NoOptionError, NoSectionError):
299
            pass
300
301
        # Search a substring `foo` and replace it by the result of its exec
302
        if ret is not None:
303
            try:
304
                match = self.re_pattern.findall(ret)
305
                for m in match:
306
                    ret = ret.replace(m, system_exec(m[1:-1]))
307
            except TypeError:
308
                pass
309
        return ret
310
311
    def get_int_value(self, section, option, default=0):
312
        """Get the int value of an option, if it exists."""
313
        try:
314
            return self.parser.getint(section, option)
315
        except (NoOptionError, NoSectionError):
316
            return int(default)
317
318
    def get_float_value(self, section, option, default=0.0):
319
        """Get the float value of an option, if it exists."""
320
        try:
321
            return self.parser.getfloat(section, option)
322
        except (NoOptionError, NoSectionError):
323
            return float(default)
324
325
    def get_bool_value(self, section, option, default=True):
326
        """Get the bool value of an option, if it exists."""
327
        try:
328
            return self.parser.getboolean(section, option)
329
        except (NoOptionError, NoSectionError):
330
            return bool(default)
331