Merge branch 'issue1029' into develop

This commit is contained in:
nicolargo 2017-02-12 18:50:33 +01:00
commit dbb59bca02
6 changed files with 194 additions and 4 deletions

1
NEWS
View File

@ -9,6 +9,7 @@ Enhancements and new features:
* Use new sensors-related APIs of Psutil 5.1.0 (issue #1018) * Use new sensors-related APIs of Psutil 5.1.0 (issue #1018)
=> Remove Py3Sensors and Batinfo dependencies => Remove Py3Sensors and Batinfo dependencies
* Add a "Cloud" plugin to grab stats inside the AWS EC2 API (issue #1029)
Bugs corrected: Bugs corrected:

View File

@ -11,6 +11,9 @@ Additionally, on GNU/Linux, it also shows the kernel version.
In client mode, the server connection status is also displayed. In client mode, the server connection status is also displayed.
If you are hosted on an AWS EC2 instance, some additional information
can be displayed (AMI-ID, region).
**Connected**: **Connected**:
.. image:: ../_static/connected.png .. image:: ../_static/connected.png

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "GLANCES" "1" "Feb 11, 2017" "2.8.3_DEVELOP" "Glances" .TH "GLANCES" "1" "Feb 12, 2017" "2.8.3_DEVELOP" "Glances"
.SH NAME .SH NAME
glances \- An eye on your system glances \- An eye on your system
. .

View File

@ -103,6 +103,8 @@ Start the client browser (browser mode):\n\
dest='disable_alert', help='disable alert module') dest='disable_alert', help='disable alert module')
parser.add_argument('--disable-amps', action='store_true', default=False, parser.add_argument('--disable-amps', action='store_true', default=False,
dest='disable_amps', help='disable applications monitoring process (AMP) module') dest='disable_amps', help='disable applications monitoring process (AMP) module')
parser.add_argument('--disable-cloud', action='store_true', default=False,
dest='disable_cloud', help='disable Cloud module')
parser.add_argument('--disable-cpu', action='store_true', default=False, parser.add_argument('--disable-cpu', action='store_true', default=False,
dest='disable_cpu', help='disable CPU module') dest='disable_cpu', help='disable CPU module')
parser.add_argument('--disable-diskio', action='store_true', default=False, parser.add_argument('--disable-diskio', action='store_true', default=False,

View File

@ -54,11 +54,12 @@ class _GlancesCurses(object):
'3': {'switch': 'disable_quicklook'}, '3': {'switch': 'disable_quicklook'},
'6': {'switch': 'meangpu'}, '6': {'switch': 'meangpu'},
'/': {'switch': 'process_short_name'}, '/': {'switch': 'process_short_name'},
'd': {'switch': 'disable_diskio'},
'A': {'switch': 'disable_amps'}, 'A': {'switch': 'disable_amps'},
'b': {'switch': 'byte'}, 'b': {'switch': 'byte'},
'B': {'switch': 'diskio_iops'}, 'B': {'switch': 'diskio_iops'},
'C': {'switch': 'disable_cloud'},
'D': {'switch': 'disable_docker'}, 'D': {'switch': 'disable_docker'},
'd': {'switch': 'disable_diskio'},
'F': {'switch': 'fs_free_space'}, 'F': {'switch': 'fs_free_space'},
'G': {'switch': 'disable_gpu'}, 'G': {'switch': 'disable_gpu'},
'h': {'switch': 'help_tag'}, 'h': {'switch': 'help_tag'},
@ -545,6 +546,7 @@ class _GlancesCurses(object):
# ===================================== # =====================================
# Display first line (system+ip+uptime) # Display first line (system+ip+uptime)
# Optionnaly: Cloud on second line
# ===================================== # =====================================
self.__display_firstline(__stat_display) self.__display_firstline(__stat_display)
@ -628,7 +630,11 @@ class _GlancesCurses(object):
# Space between column # Space between column
self.space_between_column = 3 self.space_between_column = 3
self.new_column() self.new_column()
self.display_plugin(stat_display["uptime"]) self.display_plugin(stat_display["uptime"],
add_space=self.get_stats_display_width(stat_display["cloud"]) == 0)
self.init_column()
self.new_line()
self.display_plugin(stat_display["cloud"])
def __display_secondline(self, stat_display, stats): def __display_secondline(self, stat_display, stats):
"""Display the second line in the Curses interface. """Display the second line in the Curses interface.
@ -829,7 +835,8 @@ class _GlancesCurses(object):
def display_plugin(self, plugin_stats, def display_plugin(self, plugin_stats,
display_optional=True, display_optional=True,
display_additional=True, display_additional=True,
max_y=65535): max_y=65535,
add_space=True):
"""Display the plugin_stats on the screen. """Display the plugin_stats on the screen.
If display_optional=True display the optional stats If display_optional=True display the optional stats
@ -913,6 +920,10 @@ class _GlancesCurses(object):
self.next_column, x_max + self.space_between_column) self.next_column, x_max + self.space_between_column)
self.next_line = max(self.next_line, y + self.space_between_line) self.next_line = max(self.next_line, y + self.space_between_line)
if not add_space and self.next_line > 0:
# Do not have empty line after
self.next_line -= 1
def erase(self): def erase(self):
"""Erase the content of the screen.""" """Erase the content of the screen."""
self.term_window.erase() self.term_window.erase()

View File

@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2017 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/>.
"""Cloud plugin.
Supported Cloud API:
- AWS EC2 (class ThreadAwsEc2Grabber, see bellow)
"""
try:
import requests
except ImportError:
cloud_tag = False
else:
cloud_tag = True
import threading
from glances.compat import iteritems
from glances.plugins.glances_plugin import GlancesPlugin
from glances.logger import logger
class Plugin(GlancesPlugin):
"""Glances' cloud plugin.
The goal of this plugin is to retreive additional information
concerning the datacenter where the host is connected.
See https://github.com/nicolargo/glances/issues/1029
stats is a dict
"""
def __init__(self, args=None):
"""Init the plugin."""
super(Plugin, self).__init__(args=args)
# We want to display the stat in the curse interface
self.display_curse = True
# Init the stats
self.reset()
# Init thread to grab AWS EC2 stats asynchroniously
self.aws_ec2 = ThreadAwsEc2Grabber()
# Run the thread
self.aws_ec2. start()
def reset(self):
"""Reset/init the stats."""
self.stats = {}
def exit(self):
"""Overwrite the exit method to close threads"""
self.aws_ec2.stop()
# Call the father class
super(Plugin, self).exit()
@GlancesPlugin._check_decorator
@GlancesPlugin._log_result_decorator
def update(self):
"""Update the cloud stats.
Return the stats (dict)
"""
# Reset stats
self.reset()
# Requests lib is needed to get stats from the Cloud API
if not cloud_tag:
return self.stats
# Update the stats
if self.input_method == 'local':
self.stats = self.aws_ec2.stats
return self.stats
def msg_curse(self, args=None):
"""Return the string to display in the curse interface."""
# Init the return message
ret = []
if not self.stats \
or self.stats == {} \
or self.is_disable():
return ret
# Generate the output
if 'ami-id' in self.stats and 'region' in self.stats:
msg = 'AWS EC2'
ret.append(self.curse_add_line(msg, "TITLE"))
msg = ' instance {} ({})'.format(self.stats['ami-id'],
self.stats['region'])
ret.append(self.curse_add_line(msg))
# Return the message with decoration
logger.info(ret)
return ret
class ThreadAwsEc2Grabber(threading.Thread):
"""
Specific thread to grab AWS EC2 stats.
stats is a dict
"""
# AWS EC2
# http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
AWS_EC2_API_URL = 'http://169.254.169.254/latest/meta-data'
AWS_EC2_API_METADATA = {'ami-id': 'ami-id',
'region': 'placement/availability-zone'}
def __init__(self):
"""Init the class"""
logger.debug("cloud plugin - Create thread for AWS EC2")
super(ThreadAwsEc2Grabber, self).__init__()
# Event needed to stop properly the thread
self._stopper = threading.Event()
# The class return the stats as a dict
self._stats = {}
def run(self):
"""Function called to grab stats.
Infinite loop, should be stopped by calling the stop() method"""
for k, v in iteritems(self.AWS_EC2_API_METADATA):
r_url = '{}/{}'.format(self.AWS_EC2_API_URL, v)
try:
r = requests.get(r_url)
except Exception as e:
logger.debug('Can not connect to the AWS EC2 API {}'.format(r_url, e))
else:
self._stats[k] = r
@property
def stats(self):
"""Stats getter"""
return self._stats
@stats.setter
def stats(self, value):
"""Stats setter"""
self._stats = value
def stop(self, timeout=None):
"""Stop the thread"""
logger.debug("cloud plugin - Close thread for AWS EC2")
self._stopper.set()
def stopped(self):
"""Return True is the thread is stopped"""
return self._stopper.isSet()