diff --git a/gns3server/handlers/api/compute/server_handler.py b/gns3server/handlers/api/compute/server_handler.py index 82e69a73..4f043f95 100644 --- a/gns3server/handlers/api/compute/server_handler.py +++ b/gns3server/handlers/api/compute/server_handler.py @@ -21,8 +21,11 @@ import platform from gns3server.web.route import Route from gns3server.config import Config from gns3server.schemas.version import VERSION_SCHEMA +from gns3server.schemas.server_statistics import SERVER_STATISTICS_SCHEMA from gns3server.compute.port_manager import PortManager from gns3server.version import __version__ +from aiohttp.web import HTTPConflict +from psutil._common import bytes2human class ServerHandler: @@ -37,6 +40,42 @@ class ServerHandler: local_server = config.get_section_config("Server").getboolean("local", False) response.json({"version": __version__, "local": local_server}) + @Route.get( + r"/statistics", + description="Retrieve server statistics", + output=SERVER_STATISTICS_SCHEMA, + status_codes={ + 200: "Statistics information returned", + 409: "Conflict" + }) + def statistics(request, response): + + try: + memory_total = psutil.virtual_memory().total + memory_free = psutil.virtual_memory().available + memory_used = memory_total - memory_free # actual memory usage in a cross platform fashion + swap_total = psutil.swap_memory().total + swap_free = psutil.swap_memory().free + swap_used = psutil.swap_memory().used + cpu_percent = int(psutil.cpu_percent()) + load_average_percent = [int(x / psutil.cpu_count() * 100) for x in psutil.getloadavg()] + memory_percent = int(psutil.virtual_memory().percent) + swap_percent = int(psutil.swap_memory().percent) + disk_usage_percent = int(psutil.disk_usage('/').percent) + except psutil.Error as e: + raise HTTPConflict(text="Psutil error detected: {}".format(e)) + response.json({"memory_total": memory_total, + "memory_free": memory_free, + "memory_used": memory_used, + "swap_total": swap_total, + "swap_free": swap_free, + "swap_used": swap_used, + "cpu_usage_percent": cpu_percent, + "memory_usage_percent": memory_percent, + "swap_usage_percent": swap_percent, + "disk_usage_percent": disk_usage_percent, + "load_average_percent": load_average_percent}) + @Route.get( r"/debug", description="Return debug information about the compute", diff --git a/gns3server/handlers/api/controller/server_handler.py b/gns3server/handlers/api/controller/server_handler.py index ae1160c4..a033ac88 100644 --- a/gns3server/handlers/api/controller/server_handler.py +++ b/gns3server/handlers/api/controller/server_handler.py @@ -130,6 +130,24 @@ class ServerHandler: response.json(iou_license) response.set_status(201) + @Route.get( + r"/statistics", + description="Retrieve server statistics", + status_codes={ + 200: "Statistics information returned", + 409: "Conflict" + }) + async def statistics(request, response): + + compute_statistics = [] + for compute in list(Controller.instance().computes.values()): + try: + r = await compute.get("/statistics") + compute_statistics.append({"compute_id": compute.id, "compute_name": compute.name, "statistics": r.json}) + except HTTPConflict as e: + log.error("Could not retrieve statistics on compute {}: {}".format(compute.name, e.text)) + response.json(compute_statistics) + @Route.post( r"/debug", description="Dump debug information to disk (debug directory in config directory). Work only for local server", diff --git a/gns3server/schemas/server_statistics.py b/gns3server/schemas/server_statistics.py new file mode 100644 index 00000000..048799b3 --- /dev/null +++ b/gns3server/schemas/server_statistics.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +SERVER_STATISTICS_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": ["memory_total", + "memory_free", + "memory_used", + "swap_total", + "swap_free", + "swap_used", + "cpu_usage_percent", + "memory_usage_percent", + "swap_usage_percent", + "disk_usage_percent", + "load_average_percent"], + "additionalProperties": False, + "properties": { + "memory_total": { + "description": "Total physical memory (exclusive swap) in bytes", + "type": "integer", + }, + "memory_free": { + "description": "Free memory in bytes", + "type": "integer", + }, + "memory_used": { + "description": "Memory used in bytes", + "type": "integer", + }, + "swap_total": { + "description": "Total swap memory in bytes", + "type": "integer", + }, + "swap_free": { + "description": "Free swap memory in bytes", + "type": "integer", + }, + "swap_used": { + "description": "Swap memory used in bytes", + "type": "integer", + }, + "cpu_usage_percent": { + "description": "CPU usage in percent", + "type": "integer", + }, + "memory_usage_percent": { + "description": "Memory usage in percent", + "type": "integer", + }, + "swap_usage_percent": { + "description": "Swap usage in percent", + "type": "integer", + }, + "disk_usage_percent": { + "description": "Disk usage in percent", + "type": "integer", + }, + "load_average_percent": { + "description": "Average system load over the last 1, 5 and 15 minutes", + "type": "array", + "items": [{"type": "integer"}], + "minItems": 3, + "maxItems": 3 + }, + } +} diff --git a/tests/handlers/api/compute/test_server.py b/tests/handlers/api/compute/test_server.py index e39c2913..c811d907 100644 --- a/tests/handlers/api/compute/test_server.py +++ b/tests/handlers/api/compute/test_server.py @@ -37,3 +37,8 @@ def test_version_output(http_compute): def test_debug_output(http_compute): response = http_compute.get('/debug') assert response.status == 200 + + +def test_statistics_output(http_compute): + response = http_compute.get('/statistics') + assert response.status == 200 diff --git a/tests/handlers/api/controller/test_server.py b/tests/handlers/api/controller/test_server.py index 32c56725..80453f0e 100644 --- a/tests/handlers/api/controller/test_server.py +++ b/tests/handlers/api/controller/test_server.py @@ -70,3 +70,8 @@ def test_debug_non_local(http_controller, config, tmpdir): config.set("Server", "local", False) response = http_controller.post('/debug') assert response.status == 403 + + +def test_statistics_output(http_controller): + response = http_controller.get('/statistics') + assert response.status == 200