Checks whether an import is not accompanied by ``from __future__ import absolute_import`` (default behaviour in Python 3)
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
Loading history...
|
|||
23 | import sys |
||
0 ignored issues
–
show
|
|||
24 | import multiprocessing |
||
0 ignored issues
–
show
|
|||
25 | from io import open |
||
0 ignored issues
–
show
|
|||
26 | import re |
||
0 ignored issues
–
show
|
|||
27 | |||
28 | from glances.compat import ConfigParser, NoOptionError, system_exec |
||
0 ignored issues
–
show
|
|||
29 | from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS |
||
0 ignored issues
–
show
|
|||
30 | from glances.logger import logger |
||
0 ignored issues
–
show
|
|||
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, |
||
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 |