mirror of
https://github.com/nicolargo/glances.git
synced 2026-03-14 20:07:23 -04:00
Merge branch 'develop' of https://github.com/nicolargo/glances into develop
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
##############################################################################
|
||||
|
||||
[global]
|
||||
# Does Glances should check if a newer version is available on Pypi ?
|
||||
# Does Glances should check if a newer version is available on PyPI ?
|
||||
check_update=true
|
||||
# History size (maximum number of values)
|
||||
# Default is 28800: 1 day with 1 point every 3 seconds (default refresh time)
|
||||
@@ -48,12 +48,12 @@ steal_warning=70
|
||||
steal_critical=90
|
||||
#steal_log=True
|
||||
# I/O wait percentage should be lower than 1/# (of CPU cores)
|
||||
# Let commented for default config (1/#-20% / 1/#-10% / 1/#)
|
||||
# Leave commented to just use the default config (1/#-20% / 1/#-10% / 1/#)
|
||||
#iowait_careful=30
|
||||
#iowait_warning=40
|
||||
#iowait_critical=50
|
||||
# Context switch limit (core / second)
|
||||
# Let commented for default config (critical is 56000/# (of CPU core))
|
||||
# Leave commented to just use the default config (critical is 56000/# (of CPU core))
|
||||
#ctx_switches_careful=10000
|
||||
#ctx_switches_warning=12000
|
||||
#ctx_switches_critical=14000
|
||||
@@ -211,7 +211,7 @@ mem_critical=90
|
||||
# Ports scanner plugin configuration
|
||||
# Interval in second between two scans
|
||||
refresh=30
|
||||
# Set the default timeout (in second) for a scan (can be overwrite in the scan list)
|
||||
# Set the default timeout (in second) for a scan (can be overwritten in the scan list)
|
||||
timeout=3
|
||||
# If port_default_gateway is True, add the default gateway on top of the scan list
|
||||
port_default_gateway=True
|
||||
@@ -365,7 +365,7 @@ prefix=G
|
||||
# * regex: Regular expression to filter the process(es)
|
||||
# * refresh: The AMP is executed every refresh seconds
|
||||
# * one_line: (optional) Force (if true) the AMP to be displayed in one line
|
||||
* * command: (optional) command to execute when the process is detected (thk to the regex)
|
||||
# * command: (optional) command to execute when the process is detected (thk to the regex)
|
||||
# * countmin: (optional) minimal number of processes
|
||||
# A warning will be displayed if number of process < count
|
||||
# * countmax: (optional) maximum number of processes
|
||||
|
||||
@@ -27,7 +27,7 @@ from glances.timer import Timer
|
||||
try:
|
||||
import pystache
|
||||
except ImportError:
|
||||
logger.warning("PyStache lib not installed (action script with mustache will not work)")
|
||||
logger.debug("Pystache library not found (action scripts won't work)")
|
||||
pystache_tag = False
|
||||
else:
|
||||
pystache_tag = True
|
||||
|
||||
@@ -133,5 +133,5 @@ class GlancesAttribute(object):
|
||||
def history_mean(self, nb=5):
|
||||
"""Return the mean on the <nb> values in the history.
|
||||
"""
|
||||
d, v = zip(*self._history)
|
||||
_, v = zip(*self._history)
|
||||
return sum(v[-nb:]) / float(v[-1] - v[-nb])
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"""Cassandra/Scylla interface class."""
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from numbers import Number
|
||||
|
||||
from glances.compat import NoOptionError, NoSectionError
|
||||
@@ -29,7 +30,6 @@ from glances.exports.glances_export import GlancesExport
|
||||
from cassandra.cluster import Cluster
|
||||
from cassandra.util import uuid_from_time
|
||||
from cassandra import InvalidRequest
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Export(GlancesExport):
|
||||
@@ -118,7 +118,7 @@ class Export(GlancesExport):
|
||||
# Table
|
||||
try:
|
||||
session.execute("CREATE TABLE %s (plugin text, time timeuuid, stat map<text,float>, PRIMARY KEY (plugin, time)) WITH CLUSTERING ORDER BY (time DESC)" % self.table)
|
||||
except:
|
||||
except Exception:
|
||||
logger.debug("Cassandra table %s already exist" % self.table)
|
||||
|
||||
return cluster, session
|
||||
|
||||
@@ -93,7 +93,7 @@ class FolderList(object):
|
||||
for i in ['careful', 'warning', 'critical']:
|
||||
try:
|
||||
value[i] = self.config.get_value(section, key + i)
|
||||
except:
|
||||
except Exception:
|
||||
value[i] = None
|
||||
logger.debug("No {} threshold for folder {}".format(i, value["path"]))
|
||||
|
||||
|
||||
@@ -19,11 +19,14 @@
|
||||
|
||||
"""Custom logger class."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import json
|
||||
from logging.config import dictConfig
|
||||
|
||||
import logging
|
||||
import logging.config
|
||||
|
||||
LOG_FILENAME = os.path.join(tempfile.gettempdir(), 'glances.log')
|
||||
|
||||
# Define the logging configuration
|
||||
LOGGING_CFG = {
|
||||
@@ -48,8 +51,7 @@ LOGGING_CFG = {
|
||||
"file": {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"formatter": "standard",
|
||||
'filename': os.path.join(tempfile.gettempdir(), 'glances.log')
|
||||
"formatter": "standard"
|
||||
},
|
||||
"console": {
|
||||
"level": "CRITICAL",
|
||||
@@ -88,14 +90,13 @@ LOGGING_CFG = {
|
||||
|
||||
def tempfile_name():
|
||||
"""Return the tempfile name (full path)."""
|
||||
ret = os.path.join(tempfile.gettempdir(), 'glances.log')
|
||||
if os.access(ret, os.F_OK) and not os.access(ret, os.W_OK):
|
||||
print("WARNING: Couldn't write to log file {} (Permission denied)".format(ret))
|
||||
ret = tempfile.mkstemp(prefix='glances', suffix='.log', text=True)
|
||||
print("Create a new log file: {}".format(ret[1]))
|
||||
if os.access(LOG_FILENAME, os.F_OK) and not os.access(LOG_FILENAME, os.W_OK):
|
||||
print("WARNING: Couldn't write to '{}' (Permission denied)".format(LOG_FILENAME))
|
||||
ret = tempfile.mkstemp(prefix='glances-', suffix='.log', text=True)
|
||||
print("Create log file at '{}'".format(ret[1]))
|
||||
return ret[1]
|
||||
|
||||
return ret
|
||||
return LOG_FILENAME
|
||||
|
||||
|
||||
def glances_logger(env_key='LOG_CFG'):
|
||||
@@ -111,7 +112,7 @@ def glances_logger(env_key='LOG_CFG'):
|
||||
# Overwrite the default logger file
|
||||
LOGGING_CFG['handlers']['file']['filename'] = tempfile_name()
|
||||
|
||||
# By default, use the LOGGING_CFG lgger configuration
|
||||
# By default, use the LOGGING_CFG logger configuration
|
||||
config = LOGGING_CFG
|
||||
|
||||
# Check if a specific configuration is available
|
||||
@@ -122,8 +123,9 @@ def glances_logger(env_key='LOG_CFG'):
|
||||
config = json.load(f)
|
||||
|
||||
# Load the configuration
|
||||
dictConfig(config)
|
||||
logging.config.dictConfig(config)
|
||||
|
||||
return _logger
|
||||
|
||||
|
||||
logger = glances_logger()
|
||||
|
||||
@@ -27,7 +27,6 @@ from io import open
|
||||
import webbrowser
|
||||
|
||||
from glances.timer import Timer
|
||||
from glances.globals import WINDOWS
|
||||
from glances.logger import logger
|
||||
|
||||
try:
|
||||
@@ -134,9 +133,7 @@ class GlancesBottle(object):
|
||||
# Try to open the Glances Web UI in the default Web browser if:
|
||||
# 1) --open-web-browser option is used
|
||||
# 2) Glances standalone mode is running on Windows OS
|
||||
webbrowser.open(bindurl,
|
||||
new=2,
|
||||
autoraise=1)
|
||||
webbrowser.open(bindurl, new=2, autoraise=1)
|
||||
self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug)
|
||||
|
||||
def end(self):
|
||||
|
||||
@@ -36,8 +36,7 @@ if not WINDOWS:
|
||||
import curses.panel
|
||||
from curses.textpad import Textbox
|
||||
except ImportError:
|
||||
logger.critical(
|
||||
"Curses module not found. Glances cannot start in standalone mode.")
|
||||
logger.critical("Curses module not found. Glances cannot start in standalone mode.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
from glances.outputs.glances_colorconsole import WCurseLight
|
||||
@@ -87,8 +86,7 @@ class _GlancesCurses(object):
|
||||
'm': {'auto_sort': False, 'sort_key': 'memory_percent'},
|
||||
'p': {'auto_sort': False, 'sort_key': 'name'},
|
||||
't': {'auto_sort': False, 'sort_key': 'cpu_times'},
|
||||
'u': {'auto_sort': False, 'sort_key': 'username'},
|
||||
'c': {'auto_sort': False, 'sort_key': 'cpu_percent'}
|
||||
'u': {'auto_sort': False, 'sort_key': 'username'}
|
||||
}
|
||||
|
||||
def __init__(self, config=None, args=None):
|
||||
@@ -141,7 +139,7 @@ class _GlancesCurses(object):
|
||||
self._init_history()
|
||||
|
||||
def load_config(self, config):
|
||||
'''Load the outputs section of the configuration file'''
|
||||
"""Load the outputs section of the configuration file."""
|
||||
# Load the theme
|
||||
if config is not None and config.has_section('outputs'):
|
||||
logger.debug('Read the outputs section in the configuration file')
|
||||
@@ -149,11 +147,11 @@ class _GlancesCurses(object):
|
||||
logger.debug('Theme for the curse interface: {}'.format(self.theme['name']))
|
||||
|
||||
def is_theme(self, name):
|
||||
'''Return True if the theme *name* should be used'''
|
||||
"""Return True if the theme *name* should be used."""
|
||||
return getattr(self.args, 'theme_' + name) or self.theme['name'] == name
|
||||
|
||||
def _init_history(self):
|
||||
'''Init the history option'''
|
||||
"""Init the history option."""
|
||||
|
||||
self.reset_history_tag = False
|
||||
self.graph_tag = False
|
||||
@@ -167,7 +165,7 @@ class _GlancesCurses(object):
|
||||
logger.error('Export graphs disabled')
|
||||
|
||||
def _init_cursor(self):
|
||||
'''Init cursors'''
|
||||
"""Init cursors."""
|
||||
|
||||
if hasattr(curses, 'noecho'):
|
||||
curses.noecho()
|
||||
@@ -176,7 +174,7 @@ class _GlancesCurses(object):
|
||||
self.set_cursor(0)
|
||||
|
||||
def _init_colors(self):
|
||||
'''Init the Curses color layout'''
|
||||
"""Init the Curses color layout."""
|
||||
|
||||
# Set curses options
|
||||
if hasattr(curses, 'start_color'):
|
||||
@@ -776,12 +774,11 @@ class _GlancesCurses(object):
|
||||
# Only in standalone mode (cs_status is None)
|
||||
if self.edit_filter and cs_status is None:
|
||||
new_filter = self.display_popup(
|
||||
'Process filter pattern: \n' +
|
||||
'\n' +
|
||||
'Process filter pattern: \n\n' +
|
||||
'Examples:\n' +
|
||||
'- python\n' +
|
||||
'- .*python.*\n' +
|
||||
'- \/usr\/lib.*' +
|
||||
'- \/usr\/lib.*\n' +
|
||||
'- name:.*nautilus.*\n' +
|
||||
'- cmdline:.*glances.*\n' +
|
||||
'- username:nicolargo\n' +
|
||||
@@ -789,7 +786,7 @@ class _GlancesCurses(object):
|
||||
is_input=True,
|
||||
input_value=glances_processes.process_filter_input)
|
||||
glances_processes.process_filter = new_filter
|
||||
elif self.edit_filter and cs_status != 'None':
|
||||
elif self.edit_filter and cs_status is not None:
|
||||
self.display_popup('Process filter only available in standalone mode')
|
||||
self.edit_filter = False
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class GlancesPasswordList(GlancesPassword):
|
||||
|
||||
def load(self, config):
|
||||
"""Load the password from the configuration file."""
|
||||
password_dict = []
|
||||
password_dict = {}
|
||||
|
||||
if config is None:
|
||||
logger.warning("No configuration file available. Cannot load password list.")
|
||||
@@ -70,7 +70,6 @@ class GlancesPasswordList(GlancesPassword):
|
||||
return self._password_dict['default']
|
||||
except (KeyError, TypeError):
|
||||
return None
|
||||
return None
|
||||
|
||||
def set_password(self, host, password):
|
||||
"""Set a password for a specific host."""
|
||||
|
||||
@@ -64,8 +64,7 @@ class Plugin(GlancesPlugin):
|
||||
'timer': v.time_until_refresh(),
|
||||
'count': v.count(),
|
||||
'countmin': v.count_min(),
|
||||
'countmax': v.count_max(),
|
||||
})
|
||||
'countmax': v.count_max()})
|
||||
else:
|
||||
# Not available in SNMP mode
|
||||
pass
|
||||
|
||||
@@ -158,7 +158,7 @@ class Plugin(GlancesPlugin):
|
||||
# First time, try to connect to the server
|
||||
try:
|
||||
self.docker_client = self.connect()
|
||||
except:
|
||||
except Exception:
|
||||
docker_tag = False
|
||||
else:
|
||||
if self.docker_client is None:
|
||||
|
||||
@@ -1,239 +1,239 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of Glances.
|
||||
#
|
||||
# Copyright (C) 2016 Kirby Banman <kirby.banman@gmail.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/>.
|
||||
|
||||
"""GPU plugin (limited to NVIDIA chipsets)"""
|
||||
|
||||
from glances.logger import logger
|
||||
from glances.plugins.glances_plugin import GlancesPlugin
|
||||
|
||||
try:
|
||||
import pynvml
|
||||
except ImportError:
|
||||
logger.debug("Could not import pynvml. NVIDIA stats will not be collected.")
|
||||
gpu_nvidia_tag = False
|
||||
else:
|
||||
gpu_nvidia_tag = True
|
||||
|
||||
|
||||
class Plugin(GlancesPlugin):
|
||||
|
||||
"""Glances GPU plugin (limited to NVIDIA chipsets).
|
||||
|
||||
stats is a list of dictionaries with one entry per GPU
|
||||
"""
|
||||
|
||||
def __init__(self, args=None):
|
||||
"""Init the plugin"""
|
||||
super(Plugin, self).__init__(args=args)
|
||||
|
||||
# Init the NVidia API
|
||||
self.init_nvidia()
|
||||
|
||||
# We want to display the stat in the curse interface
|
||||
self.display_curse = True
|
||||
|
||||
# Init the stats
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""Reset/init the stats."""
|
||||
self.stats = []
|
||||
|
||||
def init_nvidia(self):
|
||||
"""Init the NVIDIA API"""
|
||||
if not gpu_nvidia_tag:
|
||||
self.nvml_ready = False
|
||||
|
||||
try:
|
||||
pynvml.nvmlInit()
|
||||
self.device_handles = self.get_device_handles()
|
||||
self.nvml_ready = True
|
||||
except Exception:
|
||||
logger.debug("pynvml could not be initialized.")
|
||||
self.nvml_ready = False
|
||||
|
||||
return self.nvml_ready
|
||||
|
||||
def get_key(self):
|
||||
"""Return the key of the list."""
|
||||
return 'gpu_id'
|
||||
|
||||
@GlancesPlugin._check_decorator
|
||||
@GlancesPlugin._log_result_decorator
|
||||
def update(self):
|
||||
"""Update the GPU stats"""
|
||||
|
||||
self.reset()
|
||||
|
||||
# !!! JUST FOR TEST
|
||||
# self.stats = [{"key": "gpu_id", "mem": None, "proc": 60, "gpu_id": 0, "name": "GeForce GTX 560 Ti"}]
|
||||
# self.stats = [{"key": "gpu_id", "mem": 30, "proc": 60, "gpu_id": 0, "name": "GeForce GTX 560 Ti"},
|
||||
# {"key": "gpu_id", "mem": 70, "proc": 80, "gpu_id": 1, "name": "GeForce GTX 560 Ti"}]
|
||||
# !!! TO BE REMOVED
|
||||
|
||||
if not self.nvml_ready:
|
||||
return self.stats
|
||||
|
||||
if self.input_method == 'local':
|
||||
self.stats = self.get_device_stats()
|
||||
elif self.input_method == 'snmp':
|
||||
# not available
|
||||
pass
|
||||
|
||||
return self.stats
|
||||
|
||||
def update_views(self):
|
||||
"""Update stats views."""
|
||||
# Call the father's method
|
||||
super(Plugin, self).update_views()
|
||||
|
||||
# Add specifics informations
|
||||
# Alert
|
||||
for i in self.stats:
|
||||
# Init the views for the current GPU
|
||||
self.views[i[self.get_key()]] = {'proc': {}, 'mem': {}}
|
||||
# Processor alert
|
||||
if 'proc' in i:
|
||||
alert = self.get_alert(i['proc'], header='proc')
|
||||
self.views[i[self.get_key()]]['proc']['decoration'] = alert
|
||||
# Memory alert
|
||||
if 'mem' in i:
|
||||
alert = self.get_alert(i['mem'], header='mem')
|
||||
self.views[i[self.get_key()]]['mem']['decoration'] = alert
|
||||
|
||||
return True
|
||||
|
||||
def msg_curse(self, args=None):
|
||||
"""Return the dict to display in the curse interface."""
|
||||
# Init the return message
|
||||
ret = []
|
||||
|
||||
# Only process if stats exist, not empty (issue #871) and plugin not disabled
|
||||
if not self.stats or (self.stats == []) or self.is_disable():
|
||||
return ret
|
||||
|
||||
# Build the string message
|
||||
if len(self.stats) == 1:
|
||||
# Mono GPU
|
||||
gpu_stats = self.stats[0]
|
||||
# Header
|
||||
header = '{} {}'.format('GPU', gpu_stats['name'])
|
||||
msg = header[:16]
|
||||
ret.append(self.curse_add_line(msg, "TITLE"))
|
||||
# New line
|
||||
ret.append(self.curse_new_line())
|
||||
# GPU CPU
|
||||
msg = '{:8}'.format('proc:')
|
||||
ret.append(self.curse_add_line(msg))
|
||||
msg = '{:>7d}%'.format(int(gpu_stats['proc']))
|
||||
ret.append(self.curse_add_line(
|
||||
msg, self.get_views(item=gpu_stats[self.get_key()],
|
||||
key='proc',
|
||||
option='decoration')))
|
||||
# New line
|
||||
ret.append(self.curse_new_line())
|
||||
# GPU MEM
|
||||
msg = '{:8}'.format('mem:')
|
||||
ret.append(self.curse_add_line(msg))
|
||||
if gpu_stats['mem'] is None:
|
||||
msg = '{:>8}'.format('N/A')
|
||||
else:
|
||||
msg = '{:>7d}%'.format(int(gpu_stats['mem']))
|
||||
ret.append(self.curse_add_line(
|
||||
msg, self.get_views(item=gpu_stats[self.get_key()],
|
||||
key='mem',
|
||||
option='decoration')))
|
||||
else:
|
||||
# Multi GPU
|
||||
# Header
|
||||
header = '{} {}'.format(len(self.stats), 'GPUs')
|
||||
msg = header[:16]
|
||||
ret.append(self.curse_add_line(msg, "TITLE"))
|
||||
for gpu_stats in self.stats:
|
||||
# New line
|
||||
ret.append(self.curse_new_line())
|
||||
# GPU ID + PROC + MEM
|
||||
msg = '{}: {:>3}% mem: {:>3}%'.format(gpu_stats['gpu_id'],
|
||||
gpu_stats['proc'],
|
||||
gpu_stats['mem'],)
|
||||
ret.append(self.curse_add_line(msg))
|
||||
|
||||
return ret
|
||||
|
||||
def get_device_handles(self):
|
||||
"""
|
||||
Returns a list of NVML device handles, one per device. Can throw NVMLError.
|
||||
"""
|
||||
return [pynvml.nvmlDeviceGetHandleByIndex(i) for i in range(0, pynvml.nvmlDeviceGetCount())]
|
||||
|
||||
def get_device_stats(self):
|
||||
"""Get GPU stats"""
|
||||
stats = []
|
||||
|
||||
for index, device_handle in enumerate(self.device_handles):
|
||||
device_stats = {}
|
||||
# Dictionnary key is the GPU_ID
|
||||
device_stats['key'] = self.get_key()
|
||||
# GPU id (for multiple GPU, start at 0)
|
||||
device_stats['gpu_id'] = index
|
||||
# GPU name
|
||||
device_stats['name'] = self.get_device_name(device_handle)
|
||||
# Memory consumption in % (not available on all GPU)
|
||||
device_stats['mem'] = self.get_mem(device_handle)
|
||||
# Processor consumption in %
|
||||
device_stats['proc'] = self.get_proc(device_handle)
|
||||
stats.append(device_stats)
|
||||
|
||||
return stats
|
||||
|
||||
def get_device_name(self, device_handle):
|
||||
"""Get GPU device name"""
|
||||
try:
|
||||
return pynvml.nvmlDeviceGetName(device_handle)
|
||||
except pynvml.NVMlError:
|
||||
return "NVIDIA GPU"
|
||||
|
||||
def get_mem(self, device_handle):
|
||||
"""Get GPU device memory consumption in percent"""
|
||||
try:
|
||||
return pynvml.nvmlDeviceGetUtilizationRates(device_handle).memory
|
||||
except pynvml.NVMLError:
|
||||
try:
|
||||
memory_info = pynvml.nvmlDeviceGetMemoryInfo(device_handle)
|
||||
return memory_info.used * 100 / memory_info.total
|
||||
except pynvml.NVMLError:
|
||||
return None
|
||||
|
||||
def get_proc(self, device_handle):
|
||||
"""Get GPU device CPU consumption in percent"""
|
||||
try:
|
||||
return pynvml.nvmlDeviceGetUtilizationRates(device_handle).gpu
|
||||
except pynvml.NVMLError:
|
||||
return None
|
||||
|
||||
def exit(self):
|
||||
"""Overwrite the exit method to close the GPU API"""
|
||||
if self.nvml_ready:
|
||||
try:
|
||||
pynvml.nvmlShutdown()
|
||||
except Exception as e:
|
||||
logger.debug("pynvml failed to shutdown correctly ({})".format(e))
|
||||
|
||||
# Call the father exit method
|
||||
super(Plugin, self).exit()
|
||||
#
|
||||
# This file is part of Glances.
|
||||
#
|
||||
# Copyright (C) 2016 Kirby Banman <kirby.banman@gmail.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/>.
|
||||
|
||||
"""GPU plugin (limited to NVIDIA chipsets)"""
|
||||
|
||||
from glances.logger import logger
|
||||
from glances.plugins.glances_plugin import GlancesPlugin
|
||||
|
||||
try:
|
||||
import pynvml
|
||||
except ImportError:
|
||||
logger.debug("Could not import pynvml. NVIDIA stats will not be collected.")
|
||||
gpu_nvidia_tag = False
|
||||
else:
|
||||
gpu_nvidia_tag = True
|
||||
|
||||
|
||||
class Plugin(GlancesPlugin):
|
||||
|
||||
"""Glances GPU plugin (limited to NVIDIA chipsets).
|
||||
|
||||
stats is a list of dictionaries with one entry per GPU
|
||||
"""
|
||||
|
||||
def __init__(self, args=None):
|
||||
"""Init the plugin"""
|
||||
super(Plugin, self).__init__(args=args)
|
||||
|
||||
# Init the NVidia API
|
||||
self.init_nvidia()
|
||||
|
||||
# We want to display the stat in the curse interface
|
||||
self.display_curse = True
|
||||
|
||||
# Init the stats
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""Reset/init the stats."""
|
||||
self.stats = []
|
||||
|
||||
def init_nvidia(self):
|
||||
"""Init the NVIDIA API"""
|
||||
if not gpu_nvidia_tag:
|
||||
self.nvml_ready = False
|
||||
|
||||
try:
|
||||
pynvml.nvmlInit()
|
||||
self.device_handles = self.get_device_handles()
|
||||
self.nvml_ready = True
|
||||
except Exception:
|
||||
logger.debug("pynvml could not be initialized.")
|
||||
self.nvml_ready = False
|
||||
|
||||
return self.nvml_ready
|
||||
|
||||
def get_key(self):
|
||||
"""Return the key of the list."""
|
||||
return 'gpu_id'
|
||||
|
||||
@GlancesPlugin._check_decorator
|
||||
@GlancesPlugin._log_result_decorator
|
||||
def update(self):
|
||||
"""Update the GPU stats"""
|
||||
|
||||
self.reset()
|
||||
|
||||
# !!! JUST FOR TEST
|
||||
# self.stats = [{"key": "gpu_id", "mem": None, "proc": 60, "gpu_id": 0, "name": "GeForce GTX 560 Ti"}]
|
||||
# self.stats = [{"key": "gpu_id", "mem": 30, "proc": 60, "gpu_id": 0, "name": "GeForce GTX 560 Ti"},
|
||||
# {"key": "gpu_id", "mem": 70, "proc": 80, "gpu_id": 1, "name": "GeForce GTX 560 Ti"}]
|
||||
# !!! TO BE REMOVED
|
||||
|
||||
if not self.nvml_ready:
|
||||
return self.stats
|
||||
|
||||
if self.input_method == 'local':
|
||||
self.stats = self.get_device_stats()
|
||||
elif self.input_method == 'snmp':
|
||||
# not available
|
||||
pass
|
||||
|
||||
return self.stats
|
||||
|
||||
def update_views(self):
|
||||
"""Update stats views."""
|
||||
# Call the father's method
|
||||
super(Plugin, self).update_views()
|
||||
|
||||
# Add specifics informations
|
||||
# Alert
|
||||
for i in self.stats:
|
||||
# Init the views for the current GPU
|
||||
self.views[i[self.get_key()]] = {'proc': {}, 'mem': {}}
|
||||
# Processor alert
|
||||
if 'proc' in i:
|
||||
alert = self.get_alert(i['proc'], header='proc')
|
||||
self.views[i[self.get_key()]]['proc']['decoration'] = alert
|
||||
# Memory alert
|
||||
if 'mem' in i:
|
||||
alert = self.get_alert(i['mem'], header='mem')
|
||||
self.views[i[self.get_key()]]['mem']['decoration'] = alert
|
||||
|
||||
return True
|
||||
|
||||
def msg_curse(self, args=None):
|
||||
"""Return the dict to display in the curse interface."""
|
||||
# Init the return message
|
||||
ret = []
|
||||
|
||||
# Only process if stats exist, not empty (issue #871) and plugin not disabled
|
||||
if not self.stats or (self.stats == []) or self.is_disable():
|
||||
return ret
|
||||
|
||||
# Build the string message
|
||||
if len(self.stats) == 1:
|
||||
# Mono GPU
|
||||
gpu_stats = self.stats[0]
|
||||
# Header
|
||||
header = '{} {}'.format('GPU', gpu_stats['name'])
|
||||
msg = header[:16]
|
||||
ret.append(self.curse_add_line(msg, "TITLE"))
|
||||
# New line
|
||||
ret.append(self.curse_new_line())
|
||||
# GPU CPU
|
||||
msg = '{:8}'.format('proc:')
|
||||
ret.append(self.curse_add_line(msg))
|
||||
if gpu_stats['proc'] is None:
|
||||
msg = '{:>8}'.format('N/A')
|
||||
else:
|
||||
msg = '{:>7d}%'.format(int(gpu_stats['proc']))
|
||||
ret.append(self.curse_add_line(
|
||||
msg, self.get_views(item=gpu_stats[self.get_key()],
|
||||
key='proc',
|
||||
option='decoration')))
|
||||
# New line
|
||||
ret.append(self.curse_new_line())
|
||||
# GPU MEM
|
||||
msg = '{:8}'.format('mem:')
|
||||
ret.append(self.curse_add_line(msg))
|
||||
if gpu_stats['mem'] is None:
|
||||
msg = '{:>8}'.format('N/A')
|
||||
else:
|
||||
msg = '{:>7d}%'.format(int(gpu_stats['mem']))
|
||||
ret.append(self.curse_add_line(
|
||||
msg, self.get_views(item=gpu_stats[self.get_key()],
|
||||
key='mem',
|
||||
option='decoration')))
|
||||
else:
|
||||
# Multi GPU
|
||||
# Header
|
||||
header = '{} {}'.format(len(self.stats), 'GPUs')
|
||||
msg = header[:16]
|
||||
ret.append(self.curse_add_line(msg, "TITLE"))
|
||||
for gpu_stats in self.stats:
|
||||
# New line
|
||||
ret.append(self.curse_new_line())
|
||||
# GPU ID + PROC + MEM
|
||||
msg = '{}: {:>3}% mem: {:>3}%'.format(gpu_stats['gpu_id'],
|
||||
gpu_stats['proc'],
|
||||
gpu_stats['mem'],)
|
||||
ret.append(self.curse_add_line(msg))
|
||||
|
||||
return ret
|
||||
|
||||
def get_device_handles(self):
|
||||
"""
|
||||
Returns a list of NVML device handles, one per device. Can throw NVMLError.
|
||||
"""
|
||||
return [pynvml.nvmlDeviceGetHandleByIndex(i) for i in range(pynvml.nvmlDeviceGetCount())]
|
||||
|
||||
def get_device_stats(self):
|
||||
"""Get GPU stats"""
|
||||
stats = []
|
||||
|
||||
for index, device_handle in enumerate(self.device_handles):
|
||||
device_stats = {}
|
||||
# Dictionnary key is the GPU_ID
|
||||
device_stats['key'] = self.get_key()
|
||||
# GPU id (for multiple GPU, start at 0)
|
||||
device_stats['gpu_id'] = index
|
||||
# GPU name
|
||||
device_stats['name'] = self.get_device_name(device_handle)
|
||||
# Memory consumption in % (not available on all GPU)
|
||||
device_stats['mem'] = self.get_mem(device_handle)
|
||||
# Processor consumption in %
|
||||
device_stats['proc'] = self.get_proc(device_handle)
|
||||
stats.append(device_stats)
|
||||
|
||||
return stats
|
||||
|
||||
def get_device_name(self, device_handle):
|
||||
"""Get GPU device name"""
|
||||
try:
|
||||
return pynvml.nvmlDeviceGetName(device_handle)
|
||||
except pynvml.NVMlError:
|
||||
return "NVIDIA GPU"
|
||||
|
||||
def get_mem(self, device_handle):
|
||||
"""Get GPU device memory consumption in percent"""
|
||||
try:
|
||||
memory_info = pynvml.nvmlDeviceGetMemoryInfo(device_handle)
|
||||
return memory_info.used * 100 / memory_info.total
|
||||
except pynvml.NVMLError:
|
||||
return None
|
||||
|
||||
def get_proc(self, device_handle):
|
||||
"""Get GPU device CPU consumption in percent"""
|
||||
try:
|
||||
return pynvml.nvmlDeviceGetUtilizationRates(device_handle).gpu
|
||||
except pynvml.NVMLError:
|
||||
return None
|
||||
|
||||
def exit(self):
|
||||
"""Overwrite the exit method to close the GPU API"""
|
||||
if self.nvml_ready:
|
||||
try:
|
||||
pynvml.nvmlShutdown()
|
||||
except Exception as e:
|
||||
logger.debug("pynvml failed to shutdown correctly ({})".format(e))
|
||||
|
||||
# Call the father exit method
|
||||
super(Plugin, self).exit()
|
||||
|
||||
@@ -173,9 +173,9 @@ class PublicIpAddress(object):
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
t = Timer(self.timeout)
|
||||
timer = Timer(self.timeout)
|
||||
ip = None
|
||||
while not t.finished() and ip is None:
|
||||
while not timer.finished() and ip is None:
|
||||
if q.qsize() > 0:
|
||||
ip = q.get()
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
import os
|
||||
import operator
|
||||
|
||||
from glances.logger import logger
|
||||
from glances.globals import LINUX
|
||||
from glances.timer import getTimeSinceLastUpdate
|
||||
from glances.plugins.glances_plugin import GlancesPlugin
|
||||
|
||||
@@ -98,7 +98,7 @@ class Plugin(GlancesPlugin):
|
||||
netstatus = {}
|
||||
try:
|
||||
netstatus = psutil.net_if_stats()
|
||||
except:
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Previous network interface stats are stored in the network_old variable
|
||||
|
||||
@@ -251,7 +251,7 @@ class ThreadScanner(threading.Thread):
|
||||
try:
|
||||
ret = _socket.connect_ex((ip, int(port['port'])))
|
||||
except Exception as e:
|
||||
logger.debug("0}: Error while scanning port {} ({})".format(self.plugin_name, port, e))
|
||||
logger.debug("{}: Error while scanning port {} ({})".format(self.plugin_name, port, e))
|
||||
else:
|
||||
if ret == 0:
|
||||
port['status'] = counter.get()
|
||||
|
||||
@@ -28,7 +28,7 @@ import psutil
|
||||
cpuinfo_tag = False
|
||||
try:
|
||||
from cpuinfo import cpuinfo
|
||||
except:
|
||||
except ImportError:
|
||||
# Correct issue #754
|
||||
# Waiting for a correction on the upstream Cpuinfo lib
|
||||
pass
|
||||
|
||||
@@ -182,7 +182,7 @@ class Plugin(GlancesPlugin):
|
||||
|
||||
for i in self.stats:
|
||||
# Do not display anything if no battery are detected
|
||||
if (i['type'] == 'battery' and i['value'] == []):
|
||||
if i['type'] == 'battery' and i['value'] == []:
|
||||
continue
|
||||
# New line
|
||||
ret.append(self.curse_new_line())
|
||||
|
||||
@@ -19,11 +19,9 @@
|
||||
|
||||
"""Wifi plugin."""
|
||||
|
||||
import base64
|
||||
import operator
|
||||
|
||||
from glances.logger import logger
|
||||
from glances.timer import getTimeSinceLastUpdate
|
||||
from glances.plugins.glances_plugin import GlancesPlugin
|
||||
|
||||
import psutil
|
||||
@@ -32,7 +30,7 @@ import psutil
|
||||
try:
|
||||
from wifi.scan import Cell
|
||||
from wifi.exceptions import InterfaceError
|
||||
except ImportError as e:
|
||||
except ImportError:
|
||||
logger.debug("Wifi library not found. Glances cannot grab Wifi info.")
|
||||
wifi_tag = False
|
||||
else:
|
||||
@@ -190,7 +188,7 @@ class Plugin(GlancesPlugin):
|
||||
hotspotname = i['ssid']
|
||||
# Add the encryption type (if it is available)
|
||||
if i['encrypted']:
|
||||
hotspotname = hotspotname + ' {}'.format(i['encryption_type'])
|
||||
hotspotname += ' {}'.format(i['encryption_type'])
|
||||
# Cut hotspotname if it is too long
|
||||
if len(hotspotname) > ifname_max_width:
|
||||
hotspotname = '_' + hotspotname[-ifname_max_width + 1:]
|
||||
|
||||
@@ -475,9 +475,9 @@ class GlancesProcesses(object):
|
||||
# ignore the 'kernel_task' process on OS X
|
||||
# waiting for upstream patch from psutil
|
||||
if (s is None or
|
||||
BSD and s['name'] == 'idle' or
|
||||
WINDOWS and s['name'] == 'System Idle Process' or
|
||||
OSX and s['name'] == 'kernel_task'):
|
||||
BSD and s['name'] == 'idle' or
|
||||
WINDOWS and s['name'] == 'System Idle Process' or
|
||||
OSX and s['name'] == 'kernel_task'):
|
||||
continue
|
||||
# Continue to the next process if it has to be filtered
|
||||
if self._filter.is_filtered(s):
|
||||
|
||||
40
setup.py
40
setup.py
@@ -1,25 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import glob
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from io import open
|
||||
|
||||
from setuptools import setup, Command
|
||||
|
||||
|
||||
if sys.version_info < (2, 7) or (3, 0) <= sys.version_info < (3, 3):
|
||||
print('Glances requires at least Python 2.7 or 3.3 to run.')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Global functions
|
||||
##################
|
||||
|
||||
def get_version():
|
||||
"""Get version inside the __init__.py file"""
|
||||
init_file = open("glances/__init__.py").read()
|
||||
reg_version = r"^__version__ = ['\"]([^'\"]*)['\"]"
|
||||
find_version = re.search(reg_version, init_file, re.M)
|
||||
if find_version:
|
||||
return find_version.group(1)
|
||||
else:
|
||||
print("Can not retreive Glances version in the glances/__init__.py file.")
|
||||
sys.exit(1)
|
||||
with open(os.path.join('glances', '__init__.py'), encoding='utf-8') as f:
|
||||
version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", f.read(), re.M).group(1)
|
||||
|
||||
if not version:
|
||||
raise RuntimeError('Cannot find Glances version information.')
|
||||
|
||||
with open('README.rst', encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
|
||||
def get_data_files():
|
||||
@@ -56,23 +61,14 @@ class tests(Command):
|
||||
raise SystemExit(ret)
|
||||
raise SystemExit(0)
|
||||
|
||||
# Global vars
|
||||
#############
|
||||
|
||||
glances_version = get_version()
|
||||
|
||||
if sys.version_info < (2, 7) or (3, 0) <= sys.version_info < (3, 3):
|
||||
print('Glances {} require at least Python 2.7 or 3.3 to run.'.format(glances_version))
|
||||
print('Please install Glances 2.6.2 on your system.')
|
||||
sys.exit(1)
|
||||
|
||||
# Setup !
|
||||
|
||||
setup(
|
||||
name='Glances',
|
||||
version=glances_version,
|
||||
version=version,
|
||||
description="A cross-platform curses-based monitoring tool",
|
||||
long_description=open('README.rst').read(),
|
||||
long_description=long_description,
|
||||
author='Nicolas Hennion',
|
||||
author_email='nicolas@nicolargo.com',
|
||||
url='https://github.com/nicolargo/glances',
|
||||
|
||||
Reference in New Issue
Block a user