diff --git a/glances/globals.py b/glances/globals.py index 4d596977..f3b09326 100644 --- a/glances/globals.py +++ b/glances/globals.py @@ -476,18 +476,32 @@ def folder_size(path, errno=0): return ret_size, ret_err -def weak_lru_cache(maxsize=128, typed=False): +def _get_ttl_hash(ttl): + """A simple (dummy) function to return a hash based on the current second. + TODO: Implement a real TTL mechanism. + """ + if ttl is None: + return 0 + now = datetime.now() + return now.second + + +def weak_lru_cache(maxsize=1, typed=False, ttl=None): """LRU Cache decorator that keeps a weak reference to self + + Warning: When used in a class, the class should implement __eq__(self, other) and __hash__(self) methods + Source: https://stackoverflow.com/a/55990799""" def wrapper(func): @functools.lru_cache(maxsize, typed) - def _func(_self, *args, **kwargs): + def _func(_self, *args, ttl_hash=None, **kwargs): + del ttl_hash # Unused parameter, but kept for compatibility return func(_self(), *args, **kwargs) @functools.wraps(func) def inner(self, *args, **kwargs): - return _func(weakref.ref(self), *args, **kwargs) + return _func(weakref.ref(self), *args, ttl_hash=_get_ttl_hash(ttl), **kwargs) return inner diff --git a/glances/outputs/glances_restful_api.py b/glances/outputs/glances_restful_api.py index aba6cd82..73594c76 100644 --- a/glances/outputs/glances_restful_api.py +++ b/glances/outputs/glances_restful_api.py @@ -18,7 +18,7 @@ from urllib.parse import urljoin from glances import __apiversion__, __version__ from glances.events_list import glances_events -from glances.globals import json_dumps +from glances.globals import json_dumps, weak_lru_cache from glances.logger import logger from glances.password import GlancesPassword from glances.processes import glances_processes @@ -141,6 +141,12 @@ class GlancesRestfulApi: self.TEMPLATE_PATH = os.path.join(webui_root_path, 'static/templates') self._templates = Jinja2Templates(directory=self.TEMPLATE_PATH) + # FastAPI Enable GZIP compression + # https://fastapi.tiangolo.com/advanced/middleware/ + # Should be done before other middlewares to avoid + # LocalProtocolError("Too much data for declared Content-Length") + self._app.add_middleware(GZipMiddleware, minimum_size=1000) + # FastAPI Enable CORS # https://fastapi.tiangolo.com/tutorial/cors/ self._app.add_middleware( @@ -152,10 +158,6 @@ class GlancesRestfulApi: allow_headers=config.get_list_value('outputs', 'cors_headers', default=["*"]), ) - # FastAPI Enable GZIP compression - # https://fastapi.tiangolo.com/advanced/middleware/ - self._app.add_middleware(GZipMiddleware, minimum_size=1000) - # FastAPI Define routes self._app.include_router(self._router()) @@ -196,7 +198,7 @@ class GlancesRestfulApi: def authentication(self, creds: Annotated[HTTPBasicCredentials, Depends(security)]): """Check if a username/password combination is valid.""" if creds.username == self.args.username: - # check_password and get_hash are (lru) cached to optimize the requests + # check_password if self._password.check_password(self.args.password, self._password.get_hash(creds.password)): return creds.username @@ -453,6 +455,7 @@ class GlancesRestfulApi: return GlancesJSONResponse(self.servers_list.get_servers_list() if self.servers_list else []) + @weak_lru_cache(maxsize=1, ttl=1) def _api_all(self): """Glances API RESTful implementation. @@ -480,6 +483,7 @@ class GlancesRestfulApi: return GlancesJSONResponse(statval) + @weak_lru_cache(maxsize=1, ttl=1) def _api_all_limits(self): """Glances API RESTful implementation. @@ -496,6 +500,7 @@ class GlancesRestfulApi: return GlancesJSONResponse(limits) + @weak_lru_cache(maxsize=1, ttl=1) def _api_all_views(self): """Glances API RESTful implementation. @@ -512,6 +517,7 @@ class GlancesRestfulApi: return GlancesJSONResponse(limits) + @weak_lru_cache(maxsize=1, ttl=1) def _api(self, plugin: str): """Glances API RESTful implementation. @@ -541,6 +547,7 @@ class GlancesRestfulApi: status.HTTP_400_BAD_REQUEST, f"Unknown plugin {plugin} (available plugins: {self.plugins_list})" ) + @weak_lru_cache(maxsize=1, ttl=1) def _api_top(self, plugin: str, nb: int = 0): """Glances API RESTful implementation. @@ -569,6 +576,7 @@ class GlancesRestfulApi: return GlancesJSONResponse(statval) + @weak_lru_cache(maxsize=1, ttl=1) def _api_history(self, plugin: str, nb: int = 0): """Glances API RESTful implementation. @@ -591,6 +599,7 @@ class GlancesRestfulApi: return statval + @weak_lru_cache(maxsize=1, ttl=1) def _api_limits(self, plugin: str): """Glances API RESTful implementation. @@ -609,6 +618,7 @@ class GlancesRestfulApi: return GlancesJSONResponse(ret) + @weak_lru_cache(maxsize=1, ttl=1) def _api_views(self, plugin: str): """Glances API RESTful implementation. diff --git a/glances/outputs/static/css/style.scss b/glances/outputs/static/css/style.scss index bd68bb20..c59e0465 100644 --- a/glances/outputs/static/css/style.scss +++ b/glances/outputs/static/css/style.scss @@ -197,6 +197,15 @@ body { width: 8em; } +#system { + span { + padding-left: 10px; + } + span:nth-child(1) { + padding-left: 0px; + } +} + #ip { span { padding-left: 10px; diff --git a/glances/outputs/static/js/components/plugin-system.vue b/glances/outputs/static/js/components/plugin-system.vue index 5450bb49..d03e8650 100644 --- a/glances/outputs/static/js/components/plugin-system.vue +++ b/glances/outputs/static/js/components/plugin-system.vue @@ -2,7 +2,7 @@
Disconnected from {{ hostname }} - {{ humanReadableName }} + {{ humanReadableName }}
diff --git a/glances/outputs/static/package-lock.json b/glances/outputs/static/package-lock.json index 1454d8d9..a61ae176 100644 --- a/glances/outputs/static/package-lock.json +++ b/glances/outputs/static/package-lock.json @@ -1176,9 +1176,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1878,9 +1878,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/glances/outputs/static/public/browser.js b/glances/outputs/static/public/browser.js index 22617eaf..42eaab6e 100644 Binary files a/glances/outputs/static/public/browser.js and b/glances/outputs/static/public/browser.js differ diff --git a/glances/outputs/static/public/glances.js b/glances/outputs/static/public/glances.js index ad9b1cbd..db7bf798 100644 Binary files a/glances/outputs/static/public/glances.js and b/glances/outputs/static/public/glances.js differ diff --git a/tests/test_restful.py b/tests/test_restful.py index ebf919f9..609096d3 100755 --- a/tests/test_restful.py +++ b/tests/test_restful.py @@ -22,6 +22,7 @@ import requests from glances import __version__ from glances.globals import text_type from glances.outputs.glances_restful_api import GlancesRestfulApi +from glances.timer import Counter SERVER_PORT = 61234 API_VERSION = GlancesRestfulApi.API_VERSION @@ -71,10 +72,29 @@ class TestGlances(unittest.TestCase): method = "all" print('INFO: [TEST_001] Get all stats') print(f"HTTP RESTful request: {URL}/{method}") - req = self.http_get(f"{URL}/{method}") - - self.assertTrue(req.ok) - self.assertTrue(req.json(), dict) + # First call is not cached + counter_first_call = Counter() + first_req = self.http_get(f"{URL}/{method}") + self.assertTrue(first_req.ok) + self.assertTrue(first_req.json(), dict) + counter_first_call_result = counter_first_call.get() + # Second call (if it is in the same second) is cached + counter_second_call = Counter() + second_req = self.http_get(f"{URL}/{method}") + self.assertTrue(second_req.ok) + self.assertTrue(second_req.json(), dict) + counter_second_call_result = counter_second_call.get() + # Check if result of first call is equal to second call + self.assertEqual(first_req.json(), second_req.json(), "The result of the first and second call should be equal") + # Check cache result + print( + f"First API call took {counter_first_call_result:.2f} seconds" + f" and second API call (cached) took {counter_second_call_result:.2f} seconds" + ) + self.assertTrue( + counter_second_call_result < counter_first_call_result, + "The second call should be cached (faster than the first one)", + ) def test_002_pluginslist(self): """Plugins list."""