From 4ab100b716aea75058f60514a41bc1c7f9210d4b Mon Sep 17 00:00:00 2001 From: Bernhard Ehlers Date: Sun, 19 Apr 2020 20:06:47 +0200 Subject: [PATCH 1/2] Revert "Implement a minimum interval between psutil calls. Fixes #2262" This reverts commit 3a09bd43dc1514f285f6fd69317756ccd4068611. --- .../handlers/api/compute/project_handler.py | 8 ++- gns3server/notification_queue.py | 17 ++++-- gns3server/utils/ping_stats.py | 58 ------------------- 3 files changed, 19 insertions(+), 64 deletions(-) delete mode 100644 gns3server/utils/ping_stats.py diff --git a/gns3server/handlers/api/compute/project_handler.py b/gns3server/handlers/api/compute/project_handler.py index ddca533c..fed82210 100644 --- a/gns3server/handlers/api/compute/project_handler.py +++ b/gns3server/handlers/api/compute/project_handler.py @@ -19,12 +19,12 @@ import aiohttp import asyncio import json import os +import psutil import tempfile from gns3server.web.route import Route from gns3server.compute.project_manager import ProjectManager from gns3server.compute import MODULES -from gns3server.utils.ping_stats import PingStats from gns3server.schemas.project import ( PROJECT_OBJECT_SCHEMA, @@ -206,7 +206,11 @@ class ProjectHandler: :returns: hash """ - return {"action": "ping", "event": PingStats.get()} + stats = {} + # Non blocking call in order to get cpu usage. First call will return 0 + stats["cpu_usage_percent"] = psutil.cpu_percent(interval=None) + stats["memory_usage_percent"] = psutil.virtual_memory().percent + return {"action": "ping", "event": stats} @Route.get( r"/projects/{project_id}/files", diff --git a/gns3server/notification_queue.py b/gns3server/notification_queue.py index 6a022eec..a3eab916 100644 --- a/gns3server/notification_queue.py +++ b/gns3server/notification_queue.py @@ -17,8 +17,7 @@ import asyncio import json - -from gns3server.utils.ping_stats import PingStats +import psutil class NotificationQueue(asyncio.Queue): @@ -38,14 +37,24 @@ class NotificationQueue(asyncio.Queue): # At first get we return a ping so the client immediately receives data if self._first: self._first = False - return ("ping", PingStats.get(), {}) + return ("ping", self._getPing(), {}) try: (action, msg, kwargs) = await asyncio.wait_for(super().get(), timeout) except asyncio.TimeoutError: - return ("ping", PingStats.get(), {}) + return ("ping", self._getPing(), {}) return (action, msg, kwargs) + def _getPing(self): + """ + Return the content of the ping notification + """ + msg = {} + # Non blocking call in order to get cpu usage. First call will return 0 + msg["cpu_usage_percent"] = psutil.cpu_percent(interval=None) + msg["memory_usage_percent"] = psutil.virtual_memory().percent + return msg + async def get_json(self, timeout): """ Get a message as a JSON diff --git a/gns3server/utils/ping_stats.py b/gns3server/utils/ping_stats.py deleted file mode 100644 index 88734793..00000000 --- a/gns3server/utils/ping_stats.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2018 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 . - -import psutil -import time - - -class PingStats: - """ - Ping messages are regularly sent to the client to keep the connection open. - We send with it some information about server load. - """ - - _last_measurement = 0.0 # time of last measurement - _last_cpu_percent = 0.0 # last cpu_percent - _last_mem_percent = 0.0 # last virtual_memory().percent - - @classmethod - def get(cls): - """ - Get ping statistics - - :returns: hash - """ - stats = {} - cur_time = time.time() - # minimum interval for getting CPU and memory statistics - if cur_time < cls._last_measurement or \ - cur_time > cls._last_measurement + 1.9: - cls._last_measurement = cur_time - # Non blocking call to get cpu usage. First call will return 0 - try: - cls._last_cpu_percent = psutil.cpu_percent(interval=None) - cls._last_mem_percent = psutil.virtual_memory().percent - except RuntimeError: - # ignore the following error: - # RuntimeError: host_statistics(HOST_CPU_LOAD_INFO) syscall failed: (ipc/send) invalid reply port - pass - except PermissionError: - # [Errno 13] Permission denied: '/proc/stat' - pass - stats["cpu_usage_percent"] = cls._last_cpu_percent - stats["memory_usage_percent"] = cls._last_mem_percent - return stats From 10bb2592484f529cf5a9d926d0546c9bae625aea Mon Sep 17 00:00:00 2001 From: Bernhard Ehlers Date: Sun, 19 Apr 2020 20:42:46 +0200 Subject: [PATCH 2/2] Implement a minimum interval between cpu_percent() calls. Fixes #1738 --- .../handlers/api/compute/project_handler.py | 3 +- .../handlers/api/compute/server_handler.py | 3 +- gns3server/notification_queue.py | 4 +- gns3server/utils/cpu_percent.py | 48 +++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 gns3server/utils/cpu_percent.py diff --git a/gns3server/handlers/api/compute/project_handler.py b/gns3server/handlers/api/compute/project_handler.py index fed82210..d8eb0e11 100644 --- a/gns3server/handlers/api/compute/project_handler.py +++ b/gns3server/handlers/api/compute/project_handler.py @@ -25,6 +25,7 @@ import tempfile from gns3server.web.route import Route from gns3server.compute.project_manager import ProjectManager from gns3server.compute import MODULES +from gns3server.utils.cpu_percent import CpuPercent from gns3server.schemas.project import ( PROJECT_OBJECT_SCHEMA, @@ -208,7 +209,7 @@ class ProjectHandler: """ stats = {} # Non blocking call in order to get cpu usage. First call will return 0 - stats["cpu_usage_percent"] = psutil.cpu_percent(interval=None) + stats["cpu_usage_percent"] = CpuPercent.get(interval=None) stats["memory_usage_percent"] = psutil.virtual_memory().percent return {"action": "ping", "event": stats} diff --git a/gns3server/handlers/api/compute/server_handler.py b/gns3server/handlers/api/compute/server_handler.py index 4f043f95..c158a55d 100644 --- a/gns3server/handlers/api/compute/server_handler.py +++ b/gns3server/handlers/api/compute/server_handler.py @@ -23,6 +23,7 @@ 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.utils.cpu_percent import CpuPercent from gns3server.version import __version__ from aiohttp.web import HTTPConflict from psutil._common import bytes2human @@ -57,7 +58,7 @@ class ServerHandler: swap_total = psutil.swap_memory().total swap_free = psutil.swap_memory().free swap_used = psutil.swap_memory().used - cpu_percent = int(psutil.cpu_percent()) + cpu_percent = int(CpuPercent.get()) 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) diff --git a/gns3server/notification_queue.py b/gns3server/notification_queue.py index a3eab916..d81a2a2b 100644 --- a/gns3server/notification_queue.py +++ b/gns3server/notification_queue.py @@ -19,6 +19,8 @@ import asyncio import json import psutil +from gns3server.utils.cpu_percent import CpuPercent + class NotificationQueue(asyncio.Queue): """ @@ -51,7 +53,7 @@ class NotificationQueue(asyncio.Queue): """ msg = {} # Non blocking call in order to get cpu usage. First call will return 0 - msg["cpu_usage_percent"] = psutil.cpu_percent(interval=None) + msg["cpu_usage_percent"] = CpuPercent.get(interval=None) msg["memory_usage_percent"] = psutil.virtual_memory().percent return msg diff --git a/gns3server/utils/cpu_percent.py b/gns3server/utils/cpu_percent.py new file mode 100644 index 00000000..441b7efb --- /dev/null +++ b/gns3server/utils/cpu_percent.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2020 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 . + +import psutil +import time + + +class CpuPercent: + """ + Ensures a minumum interval between two cpu_percent() calls + """ + + _last_measurement = None # time of last measurement + _last_cpu_percent = 0.0 # last cpu_percent + + @classmethod + def get(cls, interval=None): + """ + Get CPU utilization as a percentage + + :returns: float + """ + + if interval: + cls._last_cpu_percent = psutil.cpu_percent(interval=interval) + cls._last_measurement = time.monotonic() + else: + cur_time = time.monotonic() + if cls._last_measurement is None or \ + (cur_time - cls._last_measurement) >= 1.9: + cls._last_cpu_percent = psutil.cpu_percent(interval=None) + cls._last_measurement = cur_time + + return cls._last_cpu_percent