Docker plugin first version

This commit is contained in:
Nicolargo 2015-01-04 20:59:04 +01:00 committed by Alessio Sergi
parent f2e7c9e880
commit 71f14b8cb6
9 changed files with 228 additions and 3 deletions

3
NEWS
View File

@ -10,7 +10,8 @@ Version 2.3
* Add Statsd export module (--export-statsd) (issue #465)
* Refactor export module (CSV export option is now --export-csv). It is now possible to export stats from the Glances client mode (issue #463)
* The Web inteface is now based on BootStarp / RWD grid (issue #417, #366 and #461) Thanks to Nicolas Hart @nclsHart
* Add the RAID plugins (issue #447)
* Add the RAID plugin (issue #447)
* Add the Docker plugin (issue #440)
Version 2.2.1
=============

View File

@ -44,6 +44,7 @@ Optional dependencies:
- ``influxdb`` (for the InfluxDB export module)
- ``statsd`` (for the StatsD export module)
- ``pystache`` (for the action script feature)
- ``docker-py`` (for the Docker monitoring support) [Linux-only]
Installation
============

View File

@ -671,6 +671,13 @@ Each alert message displays the following information:
4. {min,avg,max} values or number of running processes for monitored
processes list alerts
Docker
------
If you use Docker, Glances can help you to monitor your container. Glances uses the Docker API through the Docker-Py library.
.. image:: images/docker.png
Actions
-------

BIN
docs/images/docker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -77,6 +77,8 @@ class GlancesMain(object):
dest='disable_sensors', help=_('disable sensors module'))
parser.add_argument('--disable-raid', action='store_true', default=False,
dest='disable_raid', help=_('disable RAID module'))
parser.add_argument('--disable-docker', action='store_true', default=False,
dest='disable_docker', help=_('disable Docker module'))
parser.add_argument('--disable-left-sidebar', action='store_true', default=False,
dest='disable_left_sidebar', help=_('disable network, disk io, FS and sensors modules'))
parser.add_argument('--disable-process', action='store_true', default=False,

View File

@ -266,6 +266,9 @@ class _GlancesCurses(object):
elif self.pressedkey == ord('d'):
# 'd' > Show/hide disk I/O stats
self.args.disable_diskio = not self.args.disable_diskio
elif self.pressedkey == ord('D'):
# 'D' > Show/hide Docker stats
self.args.disable_docker = not self.args.disable_docker
elif self.pressedkey == ord('e'):
# 'e' > Enable/Disable extended stats for top process
self.args.enable_process_extended = not self.args.enable_process_extended
@ -432,6 +435,8 @@ class _GlancesCurses(object):
stats_sensors = stats.get_plugin(
'sensors').get_stats_display(args=self.args)
stats_now = stats.get_plugin('now').get_stats_display()
stats_docker = stats.get_plugin('docker').get_stats_display(
args=self.args)
stats_processcount = stats.get_plugin(
'processcount').get_stats_display(args=self.args)
stats_monitor = stats.get_plugin(
@ -441,7 +446,8 @@ class _GlancesCurses(object):
# Adapt number of processes to the available space
max_processes_displayed = screen_y - 11 - \
self.get_stats_display_height(stats_alert)
self.get_stats_display_height(stats_alert) - \
self.get_stats_display_height(stats_docker)
if self.args.enable_process_extended and not self.args.process_tree:
max_processes_displayed -= 4
if max_processes_displayed < 0:
@ -534,9 +540,11 @@ class _GlancesCurses(object):
self.next_line = self.saved_line
# Display right sidebar
# (PROCESS_COUNT+MONITORED+PROCESS_LIST+ALERT)
# ((DOCKER)+PROCESS_COUNT+(MONITORED)+PROCESS_LIST+ALERT)
self.new_column()
self.new_line()
self.display_plugin(stats_docker)
self.new_line()
self.display_plugin(stats_processcount)
if glances_processes.get_process_filter() is None and cs_status == 'None':
# Do not display stats monitor list if a filter exist

View File

@ -56,6 +56,7 @@ class Plugin(GlancesPlugin):
"""Reset/init the stats."""
self.stats = []
@GlancesPlugin._log_result_decorator
def update(self):
"""Update battery capacity stats using the input method."""
# Reset stats

View File

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2015 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/>.
"""Docker plugin."""
# Import Glances libs
from glances.core.glances_logging import logger
from glances.plugins.glances_plugin import GlancesPlugin
# Docker-py library (optional and Linux-only)
# https://github.com/docker/docker-py
try:
import docker
import requests
except ImportError as e:
logger.debug("Docker library not found (%s). Glances cannot grab Docker info." % e)
docker_tag = False
else:
docker_tag = True
class Plugin(GlancesPlugin):
"""Glances' Docker plugin.
stats is a list
"""
def __init__(self, args=None):
"""Init the plugin."""
GlancesPlugin.__init__(self, args=args)
# The plgin can be disable using: args.disable_docker
self.args = args
# We want to display the stat in the curse interface
self.display_curse = True
# Init the Docker API
self.docker_client = self.connect()
if self.docker_client is None:
global docker_tag
docker_tag = False
def connect(self, version=None):
"""Connect to the Docker server"""
# Init connection to the Docker API
if version is None:
ret = docker.Client(base_url='unix://var/run/docker.sock')
else:
ret = docker.Client(base_url='unix://var/run/docker.sock',
version=version)
try:
ret.version()
except requests.exceptions.ConnectionError as e:
# Connexion error (Docker not detected)
# Let this message in debug mode
logger.debug("Can't connect to the Docker server (%s)" % e)
ret = None
except docker.errors.APIError as e:
if version is None:
# API error (Version mismatch ?)
logger.debug("Docker API error (%s)" % e)
# Try the connection with the server version
import re
version = re.search('server\:\ (.*)\)\"\)', str(e))
if version:
logger.debug("Try connection with Docker API version %s" % version.group(1))
ret = self.connect(version=version.group(1))
else:
# API error
logger.error("Docker API error (%s)" % e)
ret = None
except Exception as e:
# Others exceptions...
# Connexion error (Docker not detected)
logger.error("Can't connect to the Docker server (%s)" % e)
ret = None
return ret
def reset(self):
"""Reset/init the stats."""
self.stats = {}
@GlancesPlugin._log_result_decorator
def update(self):
"""Update Docker stats using the input method.
"""
# Reset stats
self.reset()
# The Docker-py lib is mandatory
if not docker_tag or self.args.disable_docker:
return self.stats
if self.get_input() == 'local':
# Update stats
# Exemple: {
# "KernelVersion": "3.16.4-tinycore64",
# "Arch": "amd64",
# "ApiVersion": "1.15",
# "Version": "1.3.0",
# "GitCommit": "c78088f",
# "Os": "linux",
# "GoVersion": "go1.3.3"
# }
self.stats['version'] = self.docker_client.version()
# Example: [{u'Status': u'Up 36 seconds',
# u'Created': 1420378904,
# u'Image': u'nginx:1',
# u'Ports': [{u'Type': u'tcp', u'PrivatePort': 443},
# {u'IP': u'0.0.0.0', u'Type': u'tcp', u'PublicPort': 8080, u'PrivatePort': 80}],
# u'Command': u"nginx -g 'daemon off;'",
# u'Names': [u'/webstack_nginx_1'],
# u'Id': u'b0da859e84eb4019cf1d965b15e9323006e510352c402d2f442ea632d61faaa5'}]
self.stats['containers'] = self.docker_client.containers()
elif self.get_input() == 'snmp':
# Update stats using SNMP
# Not available
pass
return self.stats
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 (and non null) and display plugin enable...
if self.stats == {} or args.disable_docker or len(self.stats['containers']) == 0:
return ret
# Build the string message
# Title
msg = '{0}'.format(_("CONTAINERS"))
ret.append(self.curse_add_line(msg, "TITLE"))
msg = ' {0}'.format(len(self.stats['containers']))
ret.append(self.curse_add_line(msg))
msg = ' ({0} {1})'.format(_("served by Docker"),
self.stats['version']["Version"])
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
# Header
ret.append(self.curse_new_line())
msg = '{0:>14}'.format(_("Id"))
ret.append(self.curse_add_line(msg))
msg = ' {0:20}'.format(_("Name"))
ret.append(self.curse_add_line(msg))
msg = '{0:>26}'.format(_("Status"))
ret.append(self.curse_add_line(msg))
msg = ' {0:8}'.format(_("Command"))
ret.append(self.curse_add_line(msg))
# Data
for container in self.stats['containers']:
ret.append(self.curse_new_line())
# Id
msg = '{0:>14}'.format(container['Id'][0:12])
ret.append(self.curse_add_line(msg))
# Name
name = container['Names'][0]
if len(name) > 20:
name = '_' + name[:-19]
else:
name[0:20]
msg = ' {0:20}'.format(name)
ret.append(self.curse_add_line(msg))
# Status
status = self.container_alert(container['Status'])
msg = container['Status'].replace("minute", "min")
msg = '{0:>26}'.format(msg[0:25])
ret.append(self.curse_add_line(msg, status))
# Command
msg = ' {0}'.format(container['Command'])
ret.append(self.curse_add_line(msg))
return ret
def container_alert(self, status):
"""Analyse the container status"""
if "Paused" in status:
return 'CAREFUL'
else:
return 'OK'

View File

@ -139,6 +139,10 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_new_line())
msg = msg_col.format("/", _("Enable/disable short processes name"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("D", _("Enable/disable Docker stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
ret.append(self.curse_new_line())