From 0dbd4c58d4e4531027064804a0a1ce7196fb22b0 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 14 Jun 2025 10:47:00 +0200 Subject: [PATCH] Implement a basic memory cache with TTL for API call (set to ~1 second) #3202 --- glances/globals.py | 20 +++++++++++-- glances/outputs/glances_restful_api.py | 22 ++++++++++---- glances/outputs/static/css/style.scss | 9 ++++++ .../static/js/components/plugin-system.vue | 2 +- glances/outputs/static/package-lock.json | 12 ++++---- glances/outputs/static/public/browser.js | Bin 517592 -> 517666 bytes glances/outputs/static/public/glances.js | Bin 622485 -> 622603 bytes tests/test_restful.py | 28 +++++++++++++++--- 8 files changed, 73 insertions(+), 20 deletions(-) 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 22617eafc4cd0f0a9477e8095840a52c02001739..42eaab6e74628883cfa4d01decb7851be676154e 100644 GIT binary patch delta 72 zcmca{T7JFEiAX(*@`QROHy;EH~O+j!r1@3SyY)a3&3L2wLDn(+L=9A QfS47C*|sx#uwR`E0NVo?lmGw# delta 43 wcmZ2FEiAX(nKBEeKX7M}Y`6Dd0b*7lX4`J>!TxwI0CheO?*IS* diff --git a/glances/outputs/static/public/glances.js b/glances/outputs/static/public/glances.js index ad9b1cbdeca5ff08a9ca0578606205fe6db95577..db7bf7988b09935013440aa5680a2e8738e1c587 100644 GIT binary patch delta 130 zcmbRGU$whIt)Ydng{g&k3(GBcw&Kd-lGNPkjlL|BF!n!h7FDLq09t}FSFPsvp6|FFE2H@BsFFF TMl(*y>2kiD%G(`$IYW&A83ZZA delta 80 zcmeBfP@DQ+wV{Qvg{g&k3(GBcrp$ur58PQK+wDDAfS47C*?^cGh&i^~dvGQ&PY-C~ f5Sw1_%*np}kT>TFMn=!+6MQ+