Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Bugs corrected:

Enhancements:

* Add a new /all/msgpack endpoint in the API #3593
* Add meter for CPU and MEM of GPU in the Quicklook plugin #1711
* Add cpu limit to docker, podman and lxd containers #3557
* GPU Monitoring (ARM / RaspberryPi) #1048
Expand Down
2 changes: 1 addition & 1 deletion docs/api/openapi.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions docs/api/restful.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1945,6 +1945,11 @@ Get all Glances stats::
# curl http://localhost:61208/api/4/all
Return a very big dictionary with all stats

Get all Glances stats encoded with msgpack (binary, ~25% smaller than JSON)::

# curl http://localhost:61208/api/4/all/msgpack
Return the same dictionary as /all, serialized with msgpack

Note: Update is done automatically every time /all or /<plugin> is called.

GET stats of a specific process
Expand Down
51 changes: 50 additions & 1 deletion glances/outputs/glances_restful_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@
except ImportError:
MCP_AVAILABLE = False
GlancesMcpServer = None
# msgspec import with fallback (optional, used by the /all/msgpack endpoint)
try:
import msgspec

MSGSPEC_AVAILABLE = True
except ImportError:
MSGSPEC_AVAILABLE = False
msgspec = None

from glances.plugins.plugin.dag import get_plugin_dependencies
from glances.processes import glances_processes
Expand All @@ -51,7 +59,7 @@
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.responses import HTMLResponse, JSONResponse, Response
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
Expand Down Expand Up @@ -179,6 +187,21 @@ def render(self, content: Any) -> bytes:
return json_dumps(content)


class GlancesMsgpackResponse(Response):
"""
Glances msgpack response, a compact binary alternative to GlancesJSONResponse.

Stats are serialized to the MessagePack format using msgspec. The decoded
payload is identical to the JSON one (datetime objects become ISO 8601
strings), just more compact on the wire.
"""

media_type = "application/msgpack"

def render(self, content: Any) -> bytes:
return msgspec.msgpack.encode(content)


class GlancesUvicornServer(uvicorn.Server):
def install_signal_handlers(self):
pass
Expand Down Expand Up @@ -545,6 +568,7 @@ def _router(self) -> APIRouter:
f'{base_path}/args/{{item}}': self._api_args_item,
f'{base_path}/help': self._api_help,
f'{base_path}/all': self._api_all,
f'{base_path}/all/msgpack': self._api_all_msgpack,
f'{base_path}/all/limits': self._api_all_limits,
f'{base_path}/all/views': self._api_all_views,
f'{base_path}/pluginslist': self._api_plugins,
Expand Down Expand Up @@ -952,6 +976,31 @@ def _api_all(self):

return GlancesJSONResponse(statval)

def _api_all_msgpack(self):
"""Glances API RESTful implementation.

Return a msgpack (binary) representation of all the plugins.
This is a compact alternative to the (potentially huge) /all JSON endpoint.
HTTP/200 if OK
HTTP/404 if stats cannot be retrieved
HTTP/501 if the msgspec library is not installed
"""
if not MSGSPEC_AVAILABLE:
raise HTTPException(
status.HTTP_501_NOT_IMPLEMENTED,
"The msgspec library is not installed (pip install msgspec)",
)

# Update the stat
self.__update_stats()

try:
statval = self.stats.getAllAsDict()
except Exception as e:
raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get stats ({str(e)})")

return GlancesMsgpackResponse(statval)

def _api_all_limits(self):
"""Glances API RESTful implementation.

Expand Down
5 changes: 5 additions & 0 deletions glances/outputs/glances_stdout_api_restful_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,11 @@ def print_all():
print(f' # curl {API_URL}/all')
print(' Return a very big dictionary with all stats')
print('')
print('Get all Glances stats encoded with msgpack (binary, ~25% smaller than JSON)::')
print('')
print(f' # curl {API_URL}/all/msgpack')
print(' Return the same dictionary as /all, serialized with msgpack')
print('')
print('Note: Update is done automatically every time /all or /<plugin> is called.')
print('')

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ snmp = ["pysnmp-lextudio<6.2.0"]
sparklines = ["sparklines"]
web = [
"fastapi>=0.82.0",
"msgspec",
"python-jose[cryptography]>=3.3.0",
"requests",
"uvicorn",
Expand Down
20 changes: 20 additions & 0 deletions tests/test_restful.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,26 @@ def test_017_item_key(self):
self.assertIsInstance(req.json(), dict)
self.assertIsInstance(req.json()[item], int)

def test_018_all_msgpack(self):
"""All stats encoded with msgpack (binary, compact alternative to /all)."""
import json

import msgspec

method = "all/msgpack"
print('INFO: [TEST_018] Get all stats (msgpack encoding)')
print(f"HTTP RESTful request: {URL}/{method}")
req = self.http_get(f"{URL}/{method}")

self.assertTrue(req.ok)
self.assertEqual(req.headers['Content-Type'], 'application/msgpack')
# The body is valid msgpack and decodes to a dict of plugins
stats = msgspec.msgpack.decode(req.content)
self.assertIsInstance(stats, dict)
self.assertIn('cpu', stats)
# The msgpack payload must be more compact than the equivalent JSON
self.assertLess(len(req.content), len(json.dumps(stats).encode()))

def test_050_start_cors_server(self):
"""Start a second Web server with a wildcard+trusted multi-origin allowlist.

Expand Down