Completed
Push — master ( e61e99...a0b053 )
by Nicolas
01:23
created

Config   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 191
rs 9
wmc 35

13 Methods

Rating   Name   Duplication   Size   Complexity  
A sections() 0 3 1
A loaded_config_file() 0 4 1
A __init__() 0 7 1
A set_default() 0 4 2
A items() 0 3 1
A get_int_value() 0 6 2
B config_file_paths() 0 25 2
A has_section() 0 3 1
A set_default_cwc() 0 11 2
A get_float_value() 0 6 2
A get_value() 0 6 2
A as_dict() 0 8 3
F read() 0 85 15
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2017 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
27
from glances.compat import ConfigParser, NoOptionError
28
from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS
29
from glances.logger import logger
30
31
32
def user_config_dir():
33
    r"""Return the per-user config dir (full path).
34
35
    - Linux, *BSD, SunOS: ~/.config/glances
36
    - macOS: ~/Library/Application Support/glances
37
    - Windows: %APPDATA%\glances
38
    """
39
    if WINDOWS:
40
        path = os.environ.get('APPDATA')
41
    elif MACOS:
42
        path = os.path.expanduser('~/Library/Application Support')
43
    else:
44
        path = os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config')
45
    path = os.path.join(path, 'glances')
46
47
    return path
48
49
50
def user_cache_dir():
51
    r"""Return the per-user cache dir (full path).
52
53
    - Linux, *BSD, SunOS: ~/.cache/glances
54
    - macOS: ~/Library/Caches/glances
55
    - Windows: %LOCALAPPDATA%\glances\cache
56
    """
57
    if WINDOWS:
58
        path = os.path.join(os.environ.get('LOCALAPPDATA'), 'glances', 'cache')
59
    elif MACOS:
60
        path = os.path.expanduser('~/Library/Caches/glances')
61
    else:
62
        path = os.path.join(os.environ.get('XDG_CACHE_HOME') or os.path.expanduser('~/.cache'),
63
                            'glances')
64
65
    return path
66
67
68
def system_config_dir():
69
    r"""Return the system-wide config dir (full path).
70
71
    - Linux, SunOS: /etc/glances
72
    - *BSD, macOS: /usr/local/etc/glances
73
    - Windows: %APPDATA%\glances
74
    """
75
    if LINUX or SUNOS:
76
        path = '/etc'
77
    elif BSD or MACOS:
78
        path = '/usr/local/etc'
79
    else:
80
        path = os.environ.get('APPDATA')
81
    path = os.path.join(path, 'glances')
82
83
    return path
84
85
86
class Config(object):
87
88
    """This class is used to access/read config file, if it exists.
89
90
    :param config_dir: the path to search for config file
91
    :type config_dir: str or None
92
    """
93
94
    def __init__(self, config_dir=None):
95
        self.config_dir = config_dir
96
        self.config_filename = 'glances.conf'
97
        self._loaded_config_file = None
98
99
        self.parser = ConfigParser()
100
        self.read()
101
102
    def config_file_paths(self):
103
        r"""Get a list of config file paths.
104
105
        The list is built taking into account of the OS, priority and location.
106
107
        * custom path: /path/to/glances
108
        * Linux, SunOS: ~/.config/glances, /etc/glances
109
        * *BSD: ~/.config/glances, /usr/local/etc/glances
110
        * macOS: ~/Library/Application Support/glances, /usr/local/etc/glances
111
        * Windows: %APPDATA%\glances
112
113
        The config file will be searched in the following order of priority:
114
            * /path/to/file (via -C flag)
115
            * user's home directory (per-user settings)
116
            * system-wide directory (system-wide settings)
117
        """
118
        paths = []
119
120
        if self.config_dir:
121
            paths.append(self.config_dir)
122
123
        paths.append(os.path.join(user_config_dir(), self.config_filename))
124
        paths.append(os.path.join(system_config_dir(), self.config_filename))
125
126
        return paths
127
128
    def read(self):
129
        """Read the config file, if it exists. Using defaults otherwise."""
130
        for config_file in self.config_file_paths():
131
            if os.path.exists(config_file):
132
                try:
133
                    with open(config_file, encoding='utf-8') as f:
134
                        self.parser.read_file(f)
135
                        self.parser.read(f)
136
                    logger.info("Read configuration file '{}'".format(config_file))
137
                except UnicodeDecodeError as err:
138
                    logger.error("Cannot decode configuration file '{}': {}".format(config_file, err))
139
                    sys.exit(1)
140
                # Save the loaded configuration file path (issue #374)
141
                self._loaded_config_file = config_file
142
                break
143
144
        # Quicklook
145
        if not self.parser.has_section('quicklook'):
146
            self.parser.add_section('quicklook')
147
        self.set_default_cwc('quicklook', 'cpu')
148
        self.set_default_cwc('quicklook', 'mem')
149
        self.set_default_cwc('quicklook', 'swap')
150
151
        # CPU
152
        if not self.parser.has_section('cpu'):
153
            self.parser.add_section('cpu')
154
        self.set_default_cwc('cpu', 'user')
155
        self.set_default_cwc('cpu', 'system')
156
        self.set_default_cwc('cpu', 'steal')
157
        # By default I/O wait should be lower than 1/number of CPU cores
158
        iowait_bottleneck = (1.0 / multiprocessing.cpu_count()) * 100.0
159
        self.set_default_cwc('cpu', 'iowait',
160
                             [str(iowait_bottleneck - (iowait_bottleneck * 0.20)),
161
                              str(iowait_bottleneck - (iowait_bottleneck * 0.10)),
162
                              str(iowait_bottleneck)])
163
        ctx_switches_bottleneck = 56000 / multiprocessing.cpu_count()
164
        self.set_default_cwc('cpu', 'ctx_switches',
165
                             [str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.20)),
166
                              str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.10)),
167
                              str(ctx_switches_bottleneck)])
168
169
        # Per-CPU
170
        if not self.parser.has_section('percpu'):
171
            self.parser.add_section('percpu')
172
        self.set_default_cwc('percpu', 'user')
173
        self.set_default_cwc('percpu', 'system')
174
175
        # Load
176
        if not self.parser.has_section('load'):
177
            self.parser.add_section('load')
178
        self.set_default_cwc('load', cwc=['0.7', '1.0', '5.0'])
179
180
        # Mem
181
        if not self.parser.has_section('mem'):
182
            self.parser.add_section('mem')
183
        self.set_default_cwc('mem')
184
185
        # Swap
186
        if not self.parser.has_section('memswap'):
187
            self.parser.add_section('memswap')
188
        self.set_default_cwc('memswap')
189
190
        # NETWORK
191
        if not self.parser.has_section('network'):
192
            self.parser.add_section('network')
193
        self.set_default_cwc('network', 'rx')
194
        self.set_default_cwc('network', 'tx')
195
196
        # FS
197
        if not self.parser.has_section('fs'):
198
            self.parser.add_section('fs')
199
        self.set_default_cwc('fs')
200
201
        # Sensors
202
        if not self.parser.has_section('sensors'):
203
            self.parser.add_section('sensors')
204
        self.set_default_cwc('sensors', 'temperature_core', cwc=['60', '70', '80'])
205
        self.set_default_cwc('sensors', 'temperature_hdd', cwc=['45', '52', '60'])
206
        self.set_default_cwc('sensors', 'battery', cwc=['80', '90', '95'])
207
208
        # Process list
209
        if not self.parser.has_section('processlist'):
210
            self.parser.add_section('processlist')
211
        self.set_default_cwc('processlist', 'cpu')
212
        self.set_default_cwc('processlist', 'mem')
213
214
    @property
215
    def loaded_config_file(self):
216
        """Return the loaded configuration file."""
217
        return self._loaded_config_file
218
219
    def as_dict(self):
220
        """Return the configuration as a dict"""
221
        dictionary = {}
222
        for section in self.parser.sections():
223
            dictionary[section] = {}
224
            for option in self.parser.options(section):
225
                dictionary[section][option] = self.parser.get(section, option)
226
        return dictionary
227
228
    def sections(self):
229
        """Return a list of all sections."""
230
        return self.parser.sections()
231
232
    def items(self, section):
233
        """Return the items list of a section."""
234
        return self.parser.items(section)
235
236
    def has_section(self, section):
237
        """Return info about the existence of a section."""
238
        return self.parser.has_section(section)
239
240
    def set_default_cwc(self, section,
241
                        option_header=None,
242
                        cwc=['50', '70', '90']):
243
        """Set default values for careful, warning and critical."""
244
        if option_header is None:
245
            header = ''
246
        else:
247
            header = option_header + '_'
248
        self.set_default(section, header + 'careful', cwc[0])
249
        self.set_default(section, header + 'warning', cwc[1])
250
        self.set_default(section, header + 'critical', cwc[2])
251
252
    def set_default(self, section, option, default):
253
        """If the option did not exist, create a default value."""
254
        if not self.parser.has_option(section, option):
255
            self.parser.set(section, option, default)
256
257
    def get_value(self, section, option, default=None):
258
        """Get the value of an option, if it exists."""
259
        try:
260
            return self.parser.get(section, option)
261
        except NoOptionError:
262
            return default
263
264
    def get_int_value(self, section, option, default=0):
265
        """Get the int value of an option, if it exists."""
266
        try:
267
            return self.parser.getint(section, option)
268
        except NoOptionError:
269
            return int(default)
270
271
    def get_float_value(self, section, option, default=0.0):
272
        """Get the float value of an option, if it exists."""
273
        try:
274
            return self.parser.getfloat(section, option)
275
        except NoOptionError:
276
            return float(default)
277