mirror of
https://github.com/nicolargo/glances.git
synced 2024-11-24 13:23:12 +03:00
Merge branch 'issue1446' into develop
This commit is contained in:
commit
0c0adb35cf
1
NEWS
1
NEWS
@ -7,6 +7,7 @@ Version 3.1.1
|
||||
|
||||
Enhancements and new features:
|
||||
|
||||
* Please add some sparklines! #1446
|
||||
* Add authprovider for cassandra export (thanks to @EmilienMottet) #1395
|
||||
* Curses's browser server list sorting added (thanks to @limfreee) #1396
|
||||
* ElasticSearch: add date to index, unbreak object push (thanks to @genevera) # 1438
|
||||
|
BIN
docs/_static/sparkline.png
vendored
Normal file
BIN
docs/_static/sparkline.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
@ -12,6 +12,13 @@ If the per CPU mode is on (by clicking the ``1`` key):
|
||||
|
||||
.. image:: ../_static/quicklook-percpu.png
|
||||
|
||||
In the Curses/terminal interface, it is also possible to switch from bar to
|
||||
sparkline using 'S' hot key or --sparkline command line option. Please be
|
||||
aware that sparklines use the Glances history and will not be available
|
||||
if the history is disabled from the command line.
|
||||
|
||||
.. image:: ../_static/sparkline.png
|
||||
|
||||
.. note::
|
||||
Limit values can be overwritten in the configuration file under
|
||||
the ``[quicklook]`` section.
|
||||
|
@ -303,6 +303,11 @@ display FS free space instead of used
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-sparkline
|
||||
display sparlines instead of bar in the curses interface
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-theme\-white
|
||||
optimize display colors for white background
|
||||
.UNINDENT
|
||||
@ -422,6 +427,9 @@ Show/hide RAID plugin
|
||||
.B \fBs\fP
|
||||
Show/hide sensors stats
|
||||
.TP
|
||||
.B \fBS\fP
|
||||
Switch from bars to sparklines
|
||||
.TP
|
||||
.B \fBt\fP
|
||||
Sort process by CPU times (TIME+)
|
||||
.TP
|
||||
|
@ -240,6 +240,8 @@ Examples of use:
|
||||
dest='fahrenheit', help='display temperature in Fahrenheit (default is Celsius)')
|
||||
parser.add_argument('--fs-free-space', action='store_true', default=False,
|
||||
dest='fs_free_space', help='display FS free space instead of used')
|
||||
parser.add_argument('--sparkline', action='store_true', default=False,
|
||||
dest='sparkline', help='display sparklines instead of bar in the curses interface')
|
||||
parser.add_argument('--theme-white', action='store_true', default=False,
|
||||
dest='theme_white', help='optimize display colors for white background')
|
||||
# Globals options
|
||||
|
@ -62,10 +62,6 @@ class Bar(object):
|
||||
if self.__with_text:
|
||||
return self.__size - 6
|
||||
|
||||
# @size.setter
|
||||
# def size(self, value):
|
||||
# self.__size = value
|
||||
|
||||
@property
|
||||
def percent(self):
|
||||
return self.__percent
|
||||
|
@ -73,6 +73,7 @@ class _GlancesCurses(object):
|
||||
'Q': {'switch': 'enable_irq'},
|
||||
'R': {'switch': 'disable_raid'},
|
||||
's': {'switch': 'disable_sensors'},
|
||||
'S': {'switch': 'sparkline'},
|
||||
'T': {'switch': 'network_sum'},
|
||||
'U': {'switch': 'network_cumul'},
|
||||
'W': {'switch': 'disable_wifi'},
|
||||
@ -1001,18 +1002,15 @@ class _GlancesCurses(object):
|
||||
curses.napms(100)
|
||||
|
||||
def get_stats_display_width(self, curse_msg, without_option=False):
|
||||
"""Return the width of the formatted curses message.
|
||||
|
||||
The height is defined by the maximum line.
|
||||
"""
|
||||
"""Return the width of the formatted curses message."""
|
||||
try:
|
||||
if without_option:
|
||||
# Size without options
|
||||
c = len(max(''.join([(re.sub(r'[^\x00-\x7F]+', ' ', i['msg']) if not i['optional'] else "")
|
||||
c = len(max(''.join([(i['msg'].decode('utf-8').encode('ascii', 'replace') if not i['optional'] else "")
|
||||
for i in curse_msg['msgdict']]).split('\n'), key=len))
|
||||
else:
|
||||
# Size with all options
|
||||
c = len(max(''.join([re.sub(r'[^\x00-\x7F]+', ' ', i['msg'])
|
||||
c = len(max(''.join([i['msg'].decode('utf-8').encode('ascii', 'replace')
|
||||
for i in curse_msg['msgdict']]).split('\n'), key=len))
|
||||
except Exception:
|
||||
return 0
|
||||
|
91
glances/outputs/glances_sparklines.py
Normal file
91
glances/outputs/glances_sparklines.py
Normal file
@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of Glances.
|
||||
#
|
||||
# Copyright (C) 2019 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 sparklines for Glances output."""
|
||||
|
||||
from __future__ import division
|
||||
from math import modf
|
||||
from glances.logger import logger
|
||||
|
||||
sparklines_module = True
|
||||
try:
|
||||
from sparklines import sparklines
|
||||
except ImportError as e:
|
||||
logger.debug("Sparklines module not found ({})".format(e))
|
||||
sparklines_module = False
|
||||
|
||||
try:
|
||||
'\xe2\x96\x81'.decode('utf-8')
|
||||
except ImportError as e:
|
||||
logger.debug("UTF-8 for sparklines module not available".format(e))
|
||||
sparklines_module = False
|
||||
|
||||
|
||||
class Sparkline(object):
|
||||
|
||||
r"""Manage sparklines (see https://pypi.org/project/sparklines/)."""
|
||||
|
||||
def __init__(self, size, pre_char='[', post_char=']', empty_char=' ', with_text=True):
|
||||
# If the sparklines python module available ?
|
||||
self.__available = sparklines_module
|
||||
# Sparkline size
|
||||
self.__size = size
|
||||
# Sparkline current percents list
|
||||
self.__percent = []
|
||||
# Char used for the decoration
|
||||
self.__pre_char = pre_char
|
||||
self.__post_char = post_char
|
||||
self.__empty_char = empty_char
|
||||
self.__with_text = with_text
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return self.__available
|
||||
|
||||
@property
|
||||
def size(self, with_decoration=False):
|
||||
# Return the sparkine size, with or without decoration
|
||||
if with_decoration:
|
||||
return self.__size
|
||||
if self.__with_text:
|
||||
return self.__size - 6
|
||||
|
||||
@property
|
||||
def percents(self):
|
||||
return self.__percent
|
||||
|
||||
@percents.setter
|
||||
def percents(self, value):
|
||||
self.__percent = value
|
||||
|
||||
@property
|
||||
def pre_char(self):
|
||||
return self.__pre_char
|
||||
|
||||
@property
|
||||
def post_char(self):
|
||||
return self.__post_char
|
||||
|
||||
def get(self):
|
||||
"""Return the sparkline."""
|
||||
return sparklines(self.percents)[0].encode('utf8')
|
||||
|
||||
def __str__(self):
|
||||
"""Return the sparkline."""
|
||||
return self.get()
|
@ -149,19 +149,19 @@ class GlancesPlugin(object):
|
||||
except UnicodeDecodeError:
|
||||
return json.dumps(d, ensure_ascii=False)
|
||||
|
||||
def _history_enable(self):
|
||||
def history_enable(self):
|
||||
return self.args is not None and not self.args.disable_history and self.get_items_history_list() is not None
|
||||
|
||||
def init_stats_history(self):
|
||||
"""Init the stats history (dict of GlancesAttribute)."""
|
||||
if self._history_enable():
|
||||
if self.history_enable():
|
||||
init_list = [a['name'] for a in self.get_items_history_list()]
|
||||
logger.debug("Stats history activated for plugin {} (items: {})".format(self.plugin_name, init_list))
|
||||
return GlancesHistory()
|
||||
|
||||
def reset_stats_history(self):
|
||||
"""Reset the stats history (dict of GlancesAttribute)."""
|
||||
if self._history_enable():
|
||||
if self.history_enable():
|
||||
reset_list = [a['name'] for a in self.get_items_history_list()]
|
||||
logger.debug("Reset history for plugin {} (items: {})".format(self.plugin_name, reset_list))
|
||||
self.stats_history.reset()
|
||||
@ -174,7 +174,7 @@ class GlancesPlugin(object):
|
||||
else:
|
||||
item_name = self.get_key()
|
||||
# Build the history
|
||||
if self.get_export() and self._history_enable():
|
||||
if self.get_export() and self.history_enable():
|
||||
for i in self.get_items_history_list():
|
||||
if isinstance(self.get_export(), list):
|
||||
# Stats is a list of data
|
||||
|
@ -19,9 +19,11 @@
|
||||
|
||||
"""Quicklook plugin."""
|
||||
|
||||
from glances.compat import nativestr
|
||||
from glances.cpu_percent import cpu_percent
|
||||
from glances.logger import logger
|
||||
from glances.outputs.glances_bars import Bar
|
||||
from glances.outputs.glances_sparklines import Sparkline
|
||||
from glances.plugins.glances_plugin import GlancesPlugin
|
||||
|
||||
import psutil
|
||||
@ -36,6 +38,22 @@ else:
|
||||
cpuinfo_tag = True
|
||||
|
||||
|
||||
# Define the history items list
|
||||
# All items in this list will be historised if the --enable-history tag is set
|
||||
items_history_list = [{'name': 'cpu',
|
||||
'description': 'CPU percent usage',
|
||||
'y_unit': '%'},
|
||||
{'name': 'percpu',
|
||||
'description': 'PERCPU percent usage',
|
||||
'y_unit': '%'},
|
||||
{'name': 'mem',
|
||||
'description': 'MEM percent usage',
|
||||
'y_unit': '%'},
|
||||
{'name': 'swap',
|
||||
'description': 'SWAP percent usage',
|
||||
'y_unit': '%'}]
|
||||
|
||||
|
||||
class Plugin(GlancesPlugin):
|
||||
"""Glances quicklook plugin.
|
||||
|
||||
@ -44,8 +62,8 @@ class Plugin(GlancesPlugin):
|
||||
|
||||
def __init__(self, args=None):
|
||||
"""Init the quicklook plugin."""
|
||||
super(Plugin, self).__init__(args=args)
|
||||
|
||||
super(Plugin, self).__init__(args=args,
|
||||
items_history_list=items_history_list)
|
||||
# We want to display the stat in the curse interface
|
||||
self.display_curse = True
|
||||
|
||||
@ -105,8 +123,14 @@ class Plugin(GlancesPlugin):
|
||||
if not self.stats or self.is_disable():
|
||||
return ret
|
||||
|
||||
# Define the bar
|
||||
bar = Bar(max_width)
|
||||
# Define the data: Bar (default behavor) or Sparkline
|
||||
sparkline_tag = False
|
||||
if self.args.sparkline and self.history_enable():
|
||||
data = Sparkline(max_width)
|
||||
sparkline_tag = data.available
|
||||
if not sparkline_tag:
|
||||
# Fallback to bar if Sparkline module is not installed
|
||||
data = Bar(max_width)
|
||||
|
||||
# Build the string message
|
||||
if 'cpu_name' in self.stats and 'cpu_hz_current' in self.stats and 'cpu_hz' in self.stats:
|
||||
@ -119,18 +143,34 @@ class Plugin(GlancesPlugin):
|
||||
ret.append(self.curse_new_line())
|
||||
for key in ['cpu', 'mem', 'swap']:
|
||||
if key == 'cpu' and args.percpu:
|
||||
for cpu in self.stats['percpu']:
|
||||
bar.percent = cpu['total']
|
||||
if sparkline_tag:
|
||||
raw_cpu = self.get_raw_history(item='percpu', nb=data.size)
|
||||
for cpu_index, cpu in enumerate(self.stats['percpu']):
|
||||
if sparkline_tag:
|
||||
# Sparkline display an history
|
||||
data.percents = [i[1][cpu_index]['total'] for i in raw_cpu]
|
||||
# A simple padding in order to align metrics to the right
|
||||
data.percents += [None] * (data.size - len(data.percents))
|
||||
else:
|
||||
# Bar only the last value
|
||||
data.percent = cpu['total']
|
||||
if cpu[cpu['key']] < 10:
|
||||
msg = '{:3}{} '.format(key.upper(), cpu['cpu_number'])
|
||||
else:
|
||||
msg = '{:4} '.format(cpu['cpu_number'])
|
||||
ret.extend(self._msg_create_line(msg, bar, key))
|
||||
ret.extend(self._msg_create_line(msg, data, key))
|
||||
ret.append(self.curse_new_line())
|
||||
else:
|
||||
bar.percent = self.stats[key]
|
||||
if sparkline_tag:
|
||||
# Sparkline display an history
|
||||
data.percents = [i[1] for i in self.get_raw_history(item=key, nb=data.size)]
|
||||
# A simple padding in order to align metrics to the right
|
||||
data.percents += [None] * (data.size - len(data.percents))
|
||||
else:
|
||||
# Bar only the last value
|
||||
data.percent = self.stats[key]
|
||||
msg = '{:4} '.format(key.upper())
|
||||
ret.extend(self._msg_create_line(msg, bar, key))
|
||||
ret.extend(self._msg_create_line(msg, data, key))
|
||||
ret.append(self.curse_new_line())
|
||||
|
||||
# Remove the last new line
|
||||
@ -139,14 +179,14 @@ class Plugin(GlancesPlugin):
|
||||
# Return the message with decoration
|
||||
return ret
|
||||
|
||||
def _msg_create_line(self, msg, bar, key):
|
||||
def _msg_create_line(self, msg, data, key):
|
||||
"""Create a new line to the Quickview."""
|
||||
ret = []
|
||||
|
||||
ret.append(self.curse_add_line(msg))
|
||||
ret.append(self.curse_add_line(bar.pre_char, decoration='BOLD'))
|
||||
ret.append(self.curse_add_line(str(bar), self.get_views(key=key, option='decoration')))
|
||||
ret.append(self.curse_add_line(bar.post_char, decoration='BOLD'))
|
||||
ret.append(self.curse_add_line(data.pre_char, decoration='BOLD'))
|
||||
ret.append(self.curse_add_line(str(data), self.get_views(key=key, option='decoration')))
|
||||
ret.append(self.curse_add_line(data.post_char, decoration='BOLD'))
|
||||
ret.append(self.curse_add_line(' '))
|
||||
|
||||
return ret
|
||||
|
Loading…
Reference in New Issue
Block a user