Merge branch 'feature/issue365' into develop

pull/404/head
Nicolargo 2014-07-07 21:49:58 +02:00
commit 1a23f36d0d
4 changed files with 204 additions and 10 deletions

View File

@ -592,13 +592,15 @@ CSV files have two lines per stats:
- Stats description
- Stats (comma separated)
API Documentation
=================
APIs Documentations
===================
Glances uses a `XML-RPC server`_ and can be used by another client software.
Glances includes a `XML-RPC server`_ and a `RESTFULL-JSON`_ API which and can be used by another client software.
API documentation is available at
https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to.
APIs documentations are available at:
- XML-RPC: https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to
- RESTFULL-JSON: https://github.com/nicolargo/glances/wiki/The-Glances-RESTFULL-JSON-API
Support
=======
@ -614,3 +616,4 @@ Feel free to contribute!
.. _psutil: https://code.google.com/p/psutil/
.. _XML-RPC server: http://docs.python.org/2/library/simplexmlrpcserver.html
.. _RESTFULL-JSON: http://jsonapi.org/

View File

@ -127,9 +127,13 @@ class GlancesStats(object):
self.__update__(input_stats)
def getAll(self):
"""Return all the stats."""
"""Return all the stats (list)"""
return [self._plugins[p].get_raw() for p in self._plugins]
def getAllAsDict(self):
"""Return all the stats (dict)"""
return {p: self._plugins[p].get_raw() for p in self._plugins}
def get_plugin_list(self):
"""Return the plugin list."""
self._plugins
@ -163,9 +167,13 @@ class GlancesStatsServer(GlancesStats):
self.all_stats[p] = self._plugins[p].get_raw()
def getAll(self):
"""Return the stats as a dict."""
"""Return the stats as a list"""
return self.all_stats
def getAllAsDict(self):
"""Return the stats as a dict"""
return {p: self.all_stats[p] for p in self._plugins}
def getAllPlugins(self):
"""Return the plugins list."""
return [p for p in self._plugins]

View File

@ -27,11 +27,12 @@ from glances.core.glances_globals import logger
# Import mandatory Bottle lib
try:
from bottle import Bottle, template, static_file, TEMPLATE_PATH
from bottle import Bottle, template, static_file, TEMPLATE_PATH, abort, response
except ImportError:
logger.critical('Bottle module not found. Glances cannot start in web server mode.')
print(_("Install it using pip: # pip install bottle"))
sys.exit(2)
import json
class GlancesBottle(object):
@ -82,12 +83,23 @@ class GlancesBottle(object):
self._app.route('/<refresh_time:int>', method=["GET", "POST"], callback=self._index)
self._app.route('/<filename:re:.*\.css>', method="GET", callback=self._css)
self._app.route('/<filename:re:.*\.js>', method="GET", callback=self._js)
# REST API
self._app.route('/api/2/pluginslist', method="GET", callback=self._api_plugins)
self._app.route('/api/2/all', method="GET", callback=self._api_all)
self._app.route('/api/2/:plugin', method="GET", callback=self._api)
self._app.route('/api/2/:plugin/limits', method="GET", callback=self._api_limits)
self._app.route('/api/2/:plugin/:item', method="GET", callback=self._api_item)
self._app.route('/api/2/:plugin/:item/:value', method="GET", callback=self._api_value)
def start(self, stats):
"""Start the bottle."""
# Init stats
self.stats = stats
# Init plugin list
self.plugins_list = self.stats.getAllPlugins()
# Bind the Bottle TCP address/port
bindmsg = _("Glances web server started on http://{}:{}/").format(self.args.bind_address, self.args.port)
logger.info(bindmsg)
print(bindmsg)
@ -99,6 +111,7 @@ class GlancesBottle(object):
def _index(self, refresh_time=None):
"""Bottle callback for index.html (/) file."""
response.content_type = 'text/html'
# Manage parameter
if refresh_time is None:
refresh_time = self.args.time
@ -111,14 +124,146 @@ class GlancesBottle(object):
def _css(self, filename):
"""Bottle callback for *.css files."""
response.content_type = 'text/html'
# Return the static file
return static_file(filename, root=os.path.join(self.STATIC_PATH, 'css'))
def _js(self, filename):
"""Bottle callback for *.js files."""
response.content_type = 'text/html'
# Return the static file
return static_file(filename, root=os.path.join(self.STATIC_PATH, 'js'))
def _api_plugins(self):
"""
Glances API RESTFul implementation
Return the plugin list
or 404 error
"""
response.content_type = 'application/json'
# Update the stat
self.stats.update()
try:
plist = json.dumps(self.plugins_list)
except Exception as e:
abort(404, "Can not get plugin list (%s)" % str(e))
return plist
def _api_all(self):
"""
Glances API RESTFul implementation
Return the JSON representation of all the plugins
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
# Update the stat
self.stats.update()
try:
# Get the JSON value of the stat ID
statval = json.dumps(self.stats.getAllAsDict())
except Exception as e:
abort(404, "Can not get stats (%s)" % str(e))
return statval
def _api(self, plugin):
"""
Glances API RESTFul implementation
Return the JSON representation of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.stats.update()
try:
# Get the JSON value of the stat ID
statval = self.stats.get_plugin(plugin).get_stats()
except Exception as e:
abort(404, "Can not get plugin %s (%s)" % (plugin, str(e)))
return statval
def _api_limits(self, plugin):
"""
Glances API RESTFul implementation
Return the JSON limits of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
# self.stats.update()
try:
# Get the JSON value of the stat ID
limits = self.stats.get_plugin(plugin).get_limits()
except Exception as e:
abort(404, "Can not get limits for plugin %s (%s)" % (plugin, str(e)))
return limits
def _api_item(self, plugin, item):
"""
Glances API RESTFul implementation
Return the JSON represenation of the couple plugin/item
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.stats.update()
plist = self.stats.get_plugin(plugin).get_stats_item(item)
if plist is None:
abort(404, "Can not get item %s in plugin %s" % (item, plugin))
else:
return plist
def _api_value(self, plugin, item, value):
"""
Glances API RESTFul implementation
Return the process stats (dict) for the given item=value
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.stats.update()
pdict = self.stats.get_plugin(plugin).get_stats_value(item, value)
if pdict is None:
abort(404, "Can not get item(%s)=value(%s) in plugin %s" % (item, value, plugin))
else:
return pdict
def display(self, stats, refresh_time=None):
"""Display stats on the web page.

View File

@ -25,9 +25,10 @@ I am your father...
# Import system libs
import json
from operator import itemgetter
# Import Glances lib
from glances.core.glances_globals import glances_logs
from glances.core.glances_globals import glances_logs, logger
class GlancesPlugin(object):
@ -146,9 +147,46 @@ class GlancesPlugin(object):
return self.stats
def get_stats(self):
"""Return the stats object in JSON format for the XML-RPC API."""
"""Return the stats object in JSON format"""
return json.dumps(self.stats)
def get_stats_item(self, item):
"""
Return the stats object for a specific item (in JSON format)
Stats should be a list of dict (processlist, network...)
"""
if type(self.stats) is not list:
if type(self.stats) is dict:
try:
return json.dumps({ item: self.stats[item] })
except KeyError as e:
logger.error(_("Can not get item %s (%s)") % (item, e))
else:
return None
else:
try:
# Source: http://stackoverflow.com/questions/4573875/python-get-index-of-dictionary-item-in-list
return json.dumps({ item: map(itemgetter(item), self.stats) })
except (KeyError, ValueError) as e:
logger.error(_("Can not get item %s (%s)") % (item, e))
return None
def get_stats_value(self, item, value):
"""
Return the stats object for a specific item=value (in JSON format)
Stats should be a list of dict (processlist, network...)
"""
if type(self.stats) is not list:
return None
else:
if value.isdigit():
value = int(value)
try:
return json.dumps({ value: [i for i in self.stats if i[item] == value] })
except (KeyError, ValueError) as e:
logger.error(_("Can not get item(%s)=value(%s) (%s)") % (item, value,e))
return None
def load_limits(self, config):
"""Load the limits from the configuration file."""
if (hasattr(config, 'has_section') and