You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

309 lines
11 KiB

# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2018 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Manage the configuration file."""
import os
import sys
import multiprocessing
from io import open
import re
from glances.compat import ConfigParser, NoOptionError, system_exec
from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS
from glances.logger import logger
def user_config_dir():
r"""Return the per-user config dir (full path).
- Linux, *BSD, SunOS: ~/.config/glances
- macOS: ~/Library/Application Support/glances
- Windows: %APPDATA%\glances
"""
if WINDOWS:
path = os.environ.get('APPDATA')
elif MACOS:
path = os.path.expanduser('~/Library/Application Support')
else:
path = os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config')
if path is None:
path = ''
else:
path = os.path.join(path, 'glances')
return path
def user_cache_dir():
r"""Return the per-user cache dir (full path).
- Linux, *BSD, SunOS: ~/.cache/glances
- macOS: ~/Library/Caches/glances
- Windows: {%LOCALAPPDATA%,%APPDATA%}\glances\cache
"""
if WINDOWS:
path = os.path.join(os.environ.get('LOCALAPPDATA') or os.environ.get('APPDATA'),
'glances', 'cache')
elif MACOS:
path = os.path.expanduser('~/Library/Caches/glances')
else:
path = os.path.join(os.environ.get('XDG_CACHE_HOME') or os.path.expanduser('~/.cache'),
'glances')
return path
def system_config_dir():
r"""Return the system-wide config dir (full path).
- Linux, SunOS: /etc/glances
- *BSD, macOS: /usr/local/etc/glances
- Windows: %APPDATA%\glances
"""
if LINUX or SUNOS:
path = '/etc'
elif BSD or MACOS:
path = '/usr/local/etc'
else:
path = os.environ.get('APPDATA')
if path is None:
path = ''
else:
path = os.path.join(path, 'glances')
return path
class Config(object):
"""This class is used to access/read config file, if it exists.
:param config_dir: the path to search for config file
:type config_dir: str or None
"""
def __init__(self, config_dir=None):
self.config_dir = config_dir
self.config_filename = 'glances.conf'
self._loaded_config_file = None
# Re patern for optimize research of `foo`
self.re_pattern = re.compile('(\`.+?\`)')
self.parser = ConfigParser()
self.read()
def config_file_paths(self):
r"""Get a list of config file paths.
The list is built taking into account of the OS, priority and location.
* custom path: /path/to/glances
* Linux, SunOS: ~/.config/glances, /etc/glances
* *BSD: ~/.config/glances, /usr/local/etc/glances
* macOS: ~/Library/Application Support/glances, /usr/local/etc/glances
* Windows: %APPDATA%\glances
The config file will be searched in the following order of priority:
* /path/to/file (via -C flag)
* user's home directory (per-user settings)
* system-wide directory (system-wide settings)
"""
paths = []
if self.config_dir:
paths.append(self.config_dir)
paths.append(os.path.join(user_config_dir(), self.config_filename))
paths.append(os.path.join(system_config_dir(), self.config_filename))
return paths
def read(self):
"""Read the config file, if it exists. Using defaults otherwise."""
for config_file in self.config_file_paths():
logger.info('Search glances.conf file in {}'.format(config_file))
if os.path.exists(config_file):
try:
with open(config_file, encoding='utf-8') as f:
self.parser.read_file(f)
self.parser.read(f)
logger.info("Read configuration file '{}'".format(config_file))
except UnicodeDecodeError as err:
logger.error("Can not read configuration file '{}': {}".format(config_file, err))
sys.exit(1)
# Save the loaded configuration file path (issue #374)
self._loaded_config_file = config_file
break
# Quicklook
if not self.parser.has_section('quicklook'):
self.parser.add_section('quicklook')
self.set_default_cwc('quicklook', 'cpu')
self.set_default_cwc('quicklook', 'mem')
self.set_default_cwc('quicklook', 'swap')
# CPU
if not self.parser.has_section('cpu'):
self.parser.add_section('cpu')
self.set_default_cwc('cpu', 'user')
self.set_default_cwc('cpu', 'system')
self.set_default_cwc('cpu', 'steal')
# By default I/O wait should be lower than 1/number of CPU cores
iowait_bottleneck = (1.0 / multiprocessing.cpu_count()) * 100.0
self.set_default_cwc('cpu', 'iowait',
[str(iowait_bottleneck - (iowait_bottleneck * 0.20)),
str(iowait_bottleneck - (iowait_bottleneck * 0.10)),
str(iowait_bottleneck)])
# Context switches bottleneck identification #1212
ctx_switches_bottleneck = (500000 * 0.10) * multiprocessing.cpu_count()
self.set_default_cwc('cpu', 'ctx_switches',
[str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.20)),
str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.10)),
str(ctx_switches_bottleneck)])
# Per-CPU
if not self.parser.has_section('percpu'):
self.parser.add_section('percpu')
self.set_default_cwc('percpu', 'user')
self.set_default_cwc('percpu', 'system')
# Load
if not self.parser.has_section('load'):
self.parser.add_section('load')
self.set_default_cwc('load', cwc=['0.7', '1.0', '5.0'])
# Mem
if not self.parser.has_section('mem'):
self.parser.add_section('mem')
self.set_default_cwc('mem')
# Swap
if not self.parser.has_section('memswap'):
self.parser.add_section('memswap')
self.set_default_cwc('memswap')
# NETWORK
if not self.parser.has_section('network'):
self.parser.add_section('network')
self.set_default_cwc('network', 'rx')
self.set_default_cwc('network', 'tx')
# FS
if not self.parser.has_section('fs'):
self.parser.add_section('fs')
self.set_default_cwc('fs')
# Sensors
if not self.parser.has_section('sensors'):
self.parser.add_section('sensors')
self.set_default_cwc('sensors', 'temperature_core', cwc=['60', '70', '80'])
self.set_default_cwc('sensors', 'temperature_hdd', cwc=['45', '52', '60'])
self.set_default_cwc('sensors', 'battery', cwc=['80', '90', '95'])
# Process list
if not self.parser.has_section('processlist'):
self.parser.add_section('processlist')
self.set_default_cwc('processlist', 'cpu')
self.set_default_cwc('processlist', 'mem')
@property
def loaded_config_file(self):
"""Return the loaded configuration file."""
return self._loaded_config_file
def as_dict(self):
"""Return the configuration as a dict"""
dictionary = {}
for section in self.parser.sections():
dictionary[section] = {}
for option in self.parser.options(section):
dictionary[section][option] = self.parser.get(section, option)
return dictionary
def sections(self):
"""Return a list of all sections."""
return self.parser.sections()
def items(self, section):
"""Return the items list of a section."""
return self.parser.items(section)
def has_section(self, section):
"""Return info about the existence of a section."""
return self.parser.has_section(section)
def set_default_cwc(self, section,
option_header=None,
cwc=['50', '70', '90']):
"""Set default values for careful, warning and critical."""
if option_header is None:
header = ''
else:
header = option_header + '_'
self.set_default(section, header + 'careful', cwc[0])
self.set_default(section, header + 'warning', cwc[1])
self.set_default(section, header + 'critical', cwc[2])
def set_default(self, section, option,
default):
"""If the option did not exist, create a default value."""
if not self.parser.has_option(section, option):
self.parser.set(section, option, default)
def get_value(self, section, option,
default=None):
"""Get the value of an option, if it exists.
If it did not exist, then return de default value.
It allows user to define dynamic configuration key (see issue#1204)
Dynamic vlaue should starts and end with the ` char
Example: prefix=`hostname`
"""
ret = default
try:
ret = self.parser.get(section, option)
except NoOptionError:
pass
# Search a substring `foo` and replace it by the result of its exec
if ret is not None:
try:
match = self.re_pattern.findall(ret)
for m in match:
ret = ret.replace(m, system_exec(m[1:-1]))
except TypeError:
pass
return ret
def get_int_value(self, section, option, default=0):
"""Get the int value of an option, if it exists."""
try:
return self.parser.getint(section, option)
except NoOptionError:
return int(default)
def get_float_value(self, section, option, default=0.0):
"""Get the float value of an option, if it exists."""
try:
return self.parser.getfloat(section, option)
except NoOptionError:
return float(default)