From 15d252a40b90f791d8fb46b2147518502d9d7623 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 22 Dec 2025 10:58:09 +0100 Subject: [PATCH 01/13] Some imporovement regarding Dockerfile - #3359 --- docker-files/alpine.Dockerfile | 27 ++++++++++++++++----------- docker-files/ubuntu.Dockerfile | 25 ++++++++++++++++++------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/docker-files/alpine.Dockerfile b/docker-files/alpine.Dockerfile index cedf5e32..2088e0e8 100644 --- a/docker-files/alpine.Dockerfile +++ b/docker-files/alpine.Dockerfile @@ -9,7 +9,7 @@ # WARNING: the Alpine image version and Python version should be set. # Alpine 3.18 tag is a link to the latest 3.18.x version. # Be aware that if you change the Alpine version, you may have to change the Python version. -ARG IMAGE_VERSION=3.22 +ARG IMAGE_VERSION=3.23 ARG PYTHON_VERSION=3.12 ############################################################################## @@ -61,9 +61,6 @@ RUN apk add --no-cache \ RUN python${PYTHON_VERSION} -m venv venv-build RUN /venv-build/bin/python${PYTHON_VERSION} -m pip install --upgrade pip -RUN python${PYTHON_VERSION} -m venv venv-build -RUN /venv-build/bin/python${PYTHON_VERSION} -m pip install --upgrade pip - RUN python${PYTHON_VERSION} -m venv --without-pip venv COPY pyproject.toml docker-requirements.txt all-requirements.txt ./ @@ -105,17 +102,18 @@ COPY docker-bin.sh /usr/local/bin/glances RUN chmod a+x /usr/local/bin/glances ENV PATH="/venv/bin:$PATH" -# Copy binary and update PATH -COPY docker-bin.sh /usr/local/bin/glances -RUN chmod a+x /usr/local/bin/glances -ENV PATH="/venv/bin:$PATH" - # EXPOSE PORT (XMLRPC / WebUI) EXPOSE 61209 61208 +# Add glances user +# RUN addgroup -g 1000 glances && \ +# adduser -D -u 1000 -G glances glances && \ +# chown -R glances:glances /app + # Define default command. WORKDIR /app -CMD ["/bin/sh", "-c", "/venv/bin/python3 -m glances ${GLANCES_OPT}"] +ENV PYTHON_VERSION=${PYTHON_VERSION} +CMD ["/bin/sh", "-c", "/venv/bin/python${PYTHON_VERSION} -m glances ${GLANCES_OPT}"] ################################################################################ # RELEASE: minimal @@ -123,6 +121,8 @@ FROM release AS minimal COPY --from=buildminimal /venv /venv +# USER glances + ################################################################################ # RELEASE: full FROM release AS full @@ -131,6 +131,8 @@ RUN apk add --no-cache libzmq COPY --from=buildfull /venv /venv +# USER glances + ################################################################################ # RELEASE: dev - to be compatible with CI FROM full AS dev @@ -140,5 +142,8 @@ FROM full AS dev COPY ./docker-files/docker-logger.json /app ENV LOG_CFG=/app/docker-logger.json +# USER glances + WORKDIR /app -CMD ["/bin/sh", "-c", "/venv/bin/python3 -m glances ${GLANCES_OPT}"] +ENV PYTHON_VERSION=${PYTHON_VERSION} +CMD ["/bin/sh", "-c", "/venv/bin/python${PYTHON_VERSION} -m glances ${GLANCES_OPT}"] diff --git a/docker-files/ubuntu.Dockerfile b/docker-files/ubuntu.Dockerfile index 2ae96056..f208f1f1 100644 --- a/docker-files/ubuntu.Dockerfile +++ b/docker-files/ubuntu.Dockerfile @@ -89,17 +89,21 @@ COPY docker-bin.sh /usr/local/bin/glances RUN chmod a+x /usr/local/bin/glances ENV PATH="/venv/bin:$PATH" -# Copy binary and update PATH -COPY docker-bin.sh /usr/local/bin/glances -RUN chmod a+x /usr/local/bin/glances -ENV PATH="/venv/bin:$PATH" - # EXPOSE PORT (XMLRPC / WebUI) EXPOSE 61209 61208 +# Add glances user +# NOTE: If used, the Glances Docker plugin do not work... +# UID and GUID 1000 are already configured for the ubuntu user +# Create anew one with UID and GUID 1001 +# RUN groupadd -g 1001 glances && \ +# useradd -u 1001 -g glances glances && \ +# chown -R glances:glances /app + # Define default command. WORKDIR /app -CMD ["/bin/sh", "-c", "/venv/bin/python3 -m glances ${GLANCES_OPT}"] +ENV PYTHON_VERSION=${PYTHON_VERSION} +CMD ["/bin/sh", "-c", "/venv/bin/python${PYTHON_VERSION} -m glances ${GLANCES_OPT}"] ################################################################################ # RELEASE: minimal @@ -108,6 +112,8 @@ ARG PYTHON_VERSION COPY --from=buildMinimal /venv /venv +# USER glances + ################################################################################ # RELEASE: full FROM release AS full @@ -120,6 +126,8 @@ RUN apt-get update \ COPY --from=buildfull /venv /venv +# USER glances + ################################################################################ # RELEASE: dev - to be compatible with CI FROM full AS dev @@ -130,5 +138,8 @@ ARG PYTHON_VERSION COPY ./docker-files/docker-logger.json /app ENV LOG_CFG=/app/docker-logger.json +# USER glances + WORKDIR /app -CMD ["/bin/sh", "-c", "/venv/bin/python3 -m glances ${GLANCES_OPT}"] +ENV PYTHON_VERSION=${PYTHON_VERSION} +CMD ["/bin/sh", "-c", "/venv/bin/python${PYTHON_VERSION} -m glances ${GLANCES_OPT}"] From f3b3b42e2971ddd99dcca1b765dadd977ebc2cae Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 23 Dec 2025 14:14:58 +0100 Subject: [PATCH 02/13] Switch Docker Alpine based images to hardened --- docker-files/alpine.Dockerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docker-files/alpine.Dockerfile b/docker-files/alpine.Dockerfile index 2088e0e8..0b1e53db 100644 --- a/docker-files/alpine.Dockerfile +++ b/docker-files/alpine.Dockerfile @@ -9,12 +9,16 @@ # WARNING: the Alpine image version and Python version should be set. # Alpine 3.18 tag is a link to the latest 3.18.x version. # Be aware that if you change the Alpine version, you may have to change the Python version. -ARG IMAGE_VERSION=3.23 +ARG IMAGE_VERSION=3.22 ARG PYTHON_VERSION=3.12 ############################################################################## # Base layer to be used for building dependencies and the release images -FROM alpine:${IMAGE_VERSION} AS base +# Base layer uses the Alpine Hardened image +# https://hub.docker.com/orgs/nicolargo/hardened-images/catalog/dhi/alpine-base/guides +# Please note that the -dev tag should be used to install via apk +############################################################################## +FROM dhi.io/alpine-base:${IMAGE_VERSION}-dev AS base # Upgrade the system RUN apk update \ From 1353b224452f29575245c3065e5ec1ca0379f5ad Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 23 Dec 2025 14:33:55 +0100 Subject: [PATCH 03/13] Revert "Switch Docker Alpine based images to hardened" This reverts commit f3b3b42e2971ddd99dcca1b765dadd977ebc2cae. --- docker-files/alpine.Dockerfile | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docker-files/alpine.Dockerfile b/docker-files/alpine.Dockerfile index 0b1e53db..2088e0e8 100644 --- a/docker-files/alpine.Dockerfile +++ b/docker-files/alpine.Dockerfile @@ -9,16 +9,12 @@ # WARNING: the Alpine image version and Python version should be set. # Alpine 3.18 tag is a link to the latest 3.18.x version. # Be aware that if you change the Alpine version, you may have to change the Python version. -ARG IMAGE_VERSION=3.22 +ARG IMAGE_VERSION=3.23 ARG PYTHON_VERSION=3.12 ############################################################################## # Base layer to be used for building dependencies and the release images -# Base layer uses the Alpine Hardened image -# https://hub.docker.com/orgs/nicolargo/hardened-images/catalog/dhi/alpine-base/guides -# Please note that the -dev tag should be used to install via apk -############################################################################## -FROM dhi.io/alpine-base:${IMAGE_VERSION}-dev AS base +FROM alpine:${IMAGE_VERSION} AS base # Upgrade the system RUN apk update \ From b9a89006a2ec03a45565b7cf7077b1621b7cd190 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 26 Dec 2025 18:22:20 +0100 Subject: [PATCH 04/13] Sort by time do not work anymore #3387 --- glances/plugins/processlist/__init__.py | 2 ++ glances/processes.py | 18 +++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/glances/plugins/processlist/__init__.py b/glances/plugins/processlist/__init__.py index f64c3962..800778f0 100644 --- a/glances/plugins/processlist/__init__.py +++ b/glances/plugins/processlist/__init__.py @@ -966,3 +966,5 @@ class ProcesslistPlugin(GlancesPluginModel): # By default return 5 (corresponding to 99999 PID number) return 5 + +# End of file diff --git a/glances/processes.py b/glances/processes.py index 3b05a6ff..a1b33003 100644 --- a/glances/processes.py +++ b/glances/processes.py @@ -478,10 +478,7 @@ class GlancesProcesses: # Only get the info key # PsUtil 6+ no longer check PID reused #2755 so use is_running in the loop # Note: not sure it is realy needed but CPU consumption look the same with or without it - processlist = [p.info for p in processlist if p.is_running()] - - # Sort the processes list by the current sort_key - return sort_stats(processlist, sorted_by=self.sort_key, reverse=True) + return [p.info for p in processlist if p.is_running()] def get_sorted_attrs(self): defaults = ['cpu_percent', 'cpu_times', 'memory_percent', 'name', 'status', 'num_threads'] @@ -599,6 +596,8 @@ class GlancesProcesses: # Remove attributes set by the user in the config file (see #1524) sorted_attrs = [i for i in sorted_attrs if i not in self.disable_stats] + + # Buid and sort the process list processlist = self.build_process_list(sorted_attrs) # Update the processcount @@ -757,6 +756,7 @@ def _sort_io_counters(process, sorted_by='io_counters', sorted_by_secondary='mem :return: Sum of io_r + io_w """ + logger.info(f'*** Sort by cpu_times called {type(process[sorted_by])} {process[sorted_by]}') return process[sorted_by][0] - process[sorted_by][2] + process[sorted_by][1] - process[sorted_by][3] @@ -768,7 +768,7 @@ def _sort_cpu_times(process, sorted_by='cpu_times', sorted_by_secondary='memory_ see (https://github.com/giampaolo/psutil/issues/1339) The following implementation takes user and system time into account """ - return process[sorted_by][0] + process[sorted_by][1] + return process[sorted_by]['user'] + process[sorted_by]['system'] def _sort_lambda(sorted_by='cpu_percent', sorted_by_secondary='memory_percent'): @@ -793,18 +793,22 @@ def sort_stats(stats, sorted_by='cpu_percent', sorted_by_secondary='memory_perce # Specific sort try: stats = sorted(stats, key=sort_lambda, reverse=reverse) - except Exception: + except Exception as e: # If an error is detected, fallback to cpu_percent + logger.debug(f'Error while sorting by {sorted_by}, fallback to cpu_percent ({e})') stats = sorted(stats, key=sort_by_these_keys('cpu_percent', sorted_by_secondary), reverse=reverse) else: # Standard sort try: stats = sorted(stats, key=sort_by_these_keys(sorted_by, sorted_by_secondary), reverse=reverse) - except (KeyError, TypeError): + except (KeyError, TypeError) as e: # Fallback to name + logger.debug(f'Error while sorting by {sorted_by}, fallback to name ({e})') stats.sort(key=lambda process: process['name'] if process['name'] is not None else '~', reverse=False) return stats glances_processes = GlancesProcesses() + +# End of file processes.py From 588632115617693029c4521381573000c2a0d0b9 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 26 Dec 2025 18:27:13 +0100 Subject: [PATCH 05/13] Lint code --- glances/plugins/processlist/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glances/plugins/processlist/__init__.py b/glances/plugins/processlist/__init__.py index 800778f0..7c6bed8f 100644 --- a/glances/plugins/processlist/__init__.py +++ b/glances/plugins/processlist/__init__.py @@ -482,7 +482,7 @@ class ProcesslistPlugin(GlancesPluginModel): ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True)) if arguments: msg = ' ' if args.cursor_process_name_position == 0 else unicode_message('THREE_DOTS') - msg += self.layout_stat['command'].format(arguments[args.cursor_process_name_position :]) + msg += self.layout_stat['command'].format(arguments[args.cursor_process_name_position:]) ret.append(self.curse_add_line(msg, splittable=True)) else: msg = self.layout_stat['name'].format(bare_process_name) From 5d9593b3935f7681dc2314c8c00ea57c8fb5804c Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 26 Dec 2025 18:47:01 +0100 Subject: [PATCH 06/13] Lint the code --- .pre-commit-config.yaml | 15 ++++++--------- Makefile | 2 +- docker-compose/glances.conf | 0 glances/exports/glances_mqtt/__init__.py | 0 glances/plugins/processlist/__init__.py | 3 ++- tests/test_export_influxdb_v3.sh | 2 +- tests/test_export_prometheus.sh | 2 +- tests/test_export_timescaledb.sh | 2 +- 8 files changed, 12 insertions(+), 14 deletions(-) mode change 100755 => 100644 docker-compose/glances.conf mode change 100755 => 100644 glances/exports/glances_mqtt/__init__.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7be0c2e9..260d0204 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,10 +11,7 @@ repos: - id: ruff-check name: "馃悕 python 路 Linter with Ruff" types_or: [ python, pyi ] - args: [ --fix, --exit-non-zero-on-fix ] - - id: ruff-format - name: "馃悕 python 路 Format with Ruff" - types_or: [ python, pyi ] + args: [ --fix, --exit-non-zero-on-fix, --config, './pyproject.toml' ] # - repo: https://github.com/RobertCraigie/pyright-python # rev: v1.1.391 @@ -48,11 +45,11 @@ repos: name: "馃悮 shell 路 Check shell script code style" entry: bashate --error . --ignore=E006 - - repo: https://github.com/mrtazz/checkmake.git - rev: 0.2.2 - hooks: - - id: checkmake - name: "馃惍 Makefile 路 Lint Makefile" + # - repo: https://github.com/mrtazz/checkmake.git + # rev: 0.2.2 + # hooks: + # - id: checkmake + # name: "馃惍 Makefile 路 Lint Makefile" - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 diff --git a/Makefile b/Makefile index a0c7b8c1..f97cbc4d 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ UV_RUN := .venv-uv/bin/uv # if the command is only `make`, the default tasks will be the printing of the help. .DEFAULT_GOAL := help -.PHONY: help test docs docs-server venv requirements profiling docker all clean +.PHONY: help test docs docs-server venv requirements profiling docker all clean all test help: ## List all make commands available @grep -E '^[\.a-zA-Z_%-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ diff --git a/docker-compose/glances.conf b/docker-compose/glances.conf old mode 100755 new mode 100644 diff --git a/glances/exports/glances_mqtt/__init__.py b/glances/exports/glances_mqtt/__init__.py old mode 100755 new mode 100644 diff --git a/glances/plugins/processlist/__init__.py b/glances/plugins/processlist/__init__.py index 7c6bed8f..5b4bde66 100644 --- a/glances/plugins/processlist/__init__.py +++ b/glances/plugins/processlist/__init__.py @@ -482,7 +482,7 @@ class ProcesslistPlugin(GlancesPluginModel): ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True)) if arguments: msg = ' ' if args.cursor_process_name_position == 0 else unicode_message('THREE_DOTS') - msg += self.layout_stat['command'].format(arguments[args.cursor_process_name_position:]) + msg += self.layout_stat['command'].format(arguments[args.cursor_process_name_position :]) ret.append(self.curse_add_line(msg, splittable=True)) else: msg = self.layout_stat['name'].format(bare_process_name) @@ -967,4 +967,5 @@ class ProcesslistPlugin(GlancesPluginModel): # By default return 5 (corresponding to 99999 PID number) return 5 + # End of file diff --git a/tests/test_export_influxdb_v3.sh b/tests/test_export_influxdb_v3.sh index 0a104a2e..f8d5dad8 100755 --- a/tests/test_export_influxdb_v3.sh +++ b/tests/test_export_influxdb_v3.sh @@ -72,4 +72,4 @@ docker rm influxdb-v3-for-glances # Remove the temporary configuration file rm -f /tmp/glances.conf -echo "Script completed successfully!" \ No newline at end of file +echo "Script completed successfully!" diff --git a/tests/test_export_prometheus.sh b/tests/test_export_prometheus.sh index d5447ab6..5206f625 100755 --- a/tests/test_export_prometheus.sh +++ b/tests/test_export_prometheus.sh @@ -20,7 +20,7 @@ curl http://localhost:9091/metrics # Kill the glances process if it's still running if ps -p $GLANCES_PID > /dev/null; then - kill $GLANCES_PID + kill $GLANCES_PID fi echo "Script completed successfully!" \ No newline at end of file diff --git a/tests/test_export_timescaledb.sh b/tests/test_export_timescaledb.sh index cfae9a7e..4865cf60 100755 --- a/tests/test_export_timescaledb.sh +++ b/tests/test_export_timescaledb.sh @@ -40,4 +40,4 @@ docker exec timescaledb-for-glances psql -d "postgres://postgres:password@localh echo "Stopping and removing TimescaleDB container..." # docker stop timescaledb-for-glances && docker rm timescaledb-for-glances -echo "Script completed successfully!" \ No newline at end of file +echo "Script completed successfully!" From ba932e72bdbb447c5eceef9c6e66c20aaf547acc Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 30 Dec 2025 10:40:36 +0100 Subject: [PATCH 07/13] Read only container (with docker-compose) #3382 --- README.rst | 77 ++++++++++--------- docker-compose/Dockerfile | 3 - .../docker-compose-with-traefik.yml | 40 ---------- docker-compose/docker-compose.yml | 43 ++++++++--- 4 files changed, 71 insertions(+), 92 deletions(-) delete mode 100644 docker-compose/Dockerfile delete mode 100644 docker-compose/docker-compose-with-traefik.yml diff --git a/README.rst b/README.rst index bf4b243a..2adf580c 100644 --- a/README.rst +++ b/README.rst @@ -255,14 +255,14 @@ PyPI: Pip, the standard way Glances is on ``PyPI``. By using PyPI, you will be using the latest stable version. -To install Glances, simply use the ``pip`` command line. - -Warning: on modern Linux operating systems, you may have an externally-managed-environment -error message when you try to use ``pip``. In this case, go to the the PipX section below. +To install Glances, simply use the ``pip`` command line in an virtual environment. .. code-block:: console - pip install --user glances + cd ~ + python3 -m venv ~/.venv + source ~/.venv/bin/activate + pip install glances *Note*: Python headers are required to install `psutil`_, a Glances dependency. For example, on Debian/Ubuntu **the simplest** is @@ -271,17 +271,18 @@ the *python-dev* package and gcc (*python-devel* on Fedora/CentOS/RHEL). For Windows, just install psutil from the binary installation file. By default, Glances is installed **without** the Web interface dependencies. + To install it, use the following command: .. code-block:: console - pip install --user 'glances[web]' + pip install 'glances[web]' For a full installation (with all features, see features list bellow): .. code-block:: console - pip install --user 'glances[all]' + pip install 'glances[all]' Features list: @@ -306,21 +307,18 @@ To upgrade Glances to the latest version: .. code-block:: console - pip install --user --upgrade glances - -The current develop branch is published to the test.pypi.org package index. -If you want to test the develop version (could be instable), enter: - -.. code-block:: console - - pip install --user -i https://test.pypi.org/simple/ Glances + pip install --upgrade glances PyPI: PipX, the alternative way ------------------------------- -Install PipX on your system (apt install pipx on Ubuntu). +Install PipX on your system. For example on Ubuntu/Debian: -Install Glances (with all features): +.. code-block:: console + + sudo apt install pipx + +Then install Glances (with all features): .. code-block:: console @@ -328,14 +326,11 @@ Install Glances (with all features): The glances script will be installed in the ~/.local/bin folder. -Brew: The missing package manager ---------------------------------- - -For Linux and Mac OS, it is also possible to install Glances with `Brew`_: +To upgrade Glances to the latest version: .. code-block:: console - brew install glances + pipx upgrade glances Docker: the cloudy way ---------------------- @@ -343,12 +338,6 @@ Docker: the cloudy way Glances Docker images are available. You can use it to monitor your server and all your containers ! -Get the Glances container: - -.. code-block:: console - - docker pull nicolargo/glances:latest-full - The following tags are available: - *latest-full* for a full Alpine Glances image (latest release) with all dependencies @@ -393,13 +382,32 @@ Run the container in *Web server mode*: For a full list of options, see the Glances `Docker`_ documentation page. +It is also possible to use a simple Docker compose file (see in ./docker-compose/docker-compose.yml): + +.. code-block:: console + + cd ./docker-compose + docker-compose up + +It will start a Glances server with WebUI. + +Brew: The missing package manager +--------------------------------- + +For Linux and Mac OS, it is also possible to install Glances with `Brew`_: + +.. code-block:: console + + brew install glances + GNU/Linux package ----------------- `Glances` is available on many Linux distributions, so you should be -able to install it using your favorite package manager. Be aware that -when you use this method the operating system `package`_ for `Glances` -may not be the latest version and only basics plugins are enabled. +able to install it using your favorite package manager. Nevetheless, +i do not recommend it. Be aware that when you use this method the operating +system `package`_ for `Glances`may not be the latest version and only basics +plugins are enabled. Note: The Debian package (and all other Debian-based distributions) do not include anymore the JS statics files used by the Web interface @@ -418,7 +426,6 @@ Check for Python version: # python --version - Install the Glances package: .. code-block:: console @@ -464,11 +471,7 @@ Windows ------- Install `Python`_ for Windows (Python 3.4+ ship with pip) and -then run the following command: - -.. code-block:: console - - $ pip install glances +follow the Glances Pip install procedure. Android ------- diff --git a/docker-compose/Dockerfile b/docker-compose/Dockerfile deleted file mode 100644 index 51cbff4f..00000000 --- a/docker-compose/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM glances:local-alpine-minimal as glancesminimal -COPY glances.conf /glances/conf/glances.conf -CMD python -m glances -C /glances/conf/glances.conf $GLANCES_OPT diff --git a/docker-compose/docker-compose-with-traefik.yml b/docker-compose/docker-compose-with-traefik.yml deleted file mode 100644 index 06caf682..00000000 --- a/docker-compose/docker-compose-with-traefik.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: "3.9" -services: - reverse-proxy: - image: traefik - command: --api --docker - ports: - - "80:80" - - "8080:8080" - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - whoami: - image: emilevauge/whoami - labels: - - "traefik.frontend.rule=Host:whoami.docker.localhost" - - monitoring: - image: nicolargo/glances:dev - restart: unless-stopped - pid: host - privileged: true - network_mode: "host" - volumes: - - "/var/run/docker.sock:/var/run/docker.sock:ro" - - "/run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro" - - "./glances.conf:/glances/conf/glances.conf" - environment: - - TZ=${TZ} - - "GLANCES_OPT=-C /glances/conf/glances.conf -w" - # Uncomment for GPU compatibility (Nvidia) inside the container - # deploy: - # resources: - # reservations: - # devices: - # - driver: nvidia - # count: 1 - # capabilities: [gpu] - labels: - - "traefik.port=61208" - - "traefik.frontend.rule=Host:glances.docker.localhost" diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index b0e32e47..55fa9c06 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,23 +1,41 @@ -version: '3.9' services: glances: - build: - context: ./ - dockerfile: Dockerfile + # See all images tags here: https://hub.docker.com/r/nicolargo/glances/tags + image: nicolargo/glances:latest-full restart: always + pid: "host" - privileged: true network_mode: "host" + + read_only: true + privileged: false + # Uncomment next line for SATA or NVME smartctl monitoring + # cap_add: + # Uncomment next line for SATA smartctl monitoring + # - SYS_RAWIO + # Uncomment next line for NVME smartctl monitoring + # - SYS_ADMIN + # devices: + # - "/dev/nvme0" + volumes: + - "/:/rootfs:ro" - "/var/run/docker.sock:/var/run/docker.sock:ro" - "/run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro" - "./glances.conf:/glances/conf/glances.conf" -# # Uncomment for proper distro information in upper panel. + # Uncomment for proper distro information in upper panel. # # Works only for distros that do have this file (most of distros do). # - "/etc/os-release:/etc/os-release:ro" + + tmpfs: + - /tmp + environment: - - TZ=${TZ} - - GLANCES_OPT=-C /glances/conf/glances.conf -w + # Please set to your local timezone (or use local ${TZ} environment variable if set on your host) + - TZ=Europe/Paris + - GLANCES_OPT=-C /glances/conf/glances.conf -w --enable-plugin smart + - PYTHONPYCACHEPREFIX=/tmp/py_caches + # # Uncomment for GPU compatibility (Nvidia) inside the container # deploy: # resources: @@ -26,10 +44,11 @@ services: # - driver: nvidia # count: 1 # capabilities: [gpu] -# # Uncomment to protect Glances WebUI by a login/password (add --password to GLANCES_OPT) -# secrets: -# - source: glances_password -# target: /root/.config/glances/.pwd + + # Uncomment to protect Glances WebUI by a login/password (add --password to GLANCES_OPT) + # secrets: + # - source: glances_password + # target: /root/.config/glances/.pwd # secrets: # glances_password: From b7c6cce37312cfddaff00399d10d52f523f6cbcf Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 30 Dec 2025 19:04:07 +0100 Subject: [PATCH 08/13] First version ok. Log message should be removed. Code should be tested. --- README.rst | 3 +- conf/glances.conf | 8 + docs/gw/index.rst | 1 + docs/gw/nats.rst | 68 +++++++ glances/exports/glances_nats/__init__.py | 221 +++++++++++++++++++++++ pyproject.toml | 1 + tests-data/issues/issue3341-NATS/pub.py | 17 ++ tests-data/issues/issue3341-NATS/sub.py | 27 +++ tests/test_export_nats.sh | 34 ++++ tests/test_export_timescaledb.sh | 2 +- 10 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 docs/gw/nats.rst create mode 100644 glances/exports/glances_nats/__init__.py create mode 100644 tests-data/issues/issue3341-NATS/pub.py create mode 100644 tests-data/issues/issue3341-NATS/sub.py create mode 100755 tests/test_export_nats.sh diff --git a/README.rst b/README.rst index 2adf580c..a7d780f0 100644 --- a/README.rst +++ b/README.rst @@ -242,7 +242,7 @@ Glances can export stats to: - files: ``CSV`` and ``JSON`` - databases: ``InfluxDB``, ``ElasticSearch``, ``PostgreSQL/TimeScale``, ``Cassandra``, ``CouchDB``, ``OpenTSDB``, ``Prometheus``, ``StatsD``, ``Riemann`` and ``Graphite`` -- brokers: ``RabbitMQ/ActiveMQ``, ``ZeroMQ`` and ``Kafka`` +- brokers: ``RabbitMQ/ActiveMQ``, ``NATS``, ``ZeroMQ`` and ``Kafka`` - others: ``RESTful`` endpoint Installation 馃殌 @@ -574,6 +574,7 @@ Extra dependencies: - ``influxdb`` (for the InfluxDB version 1 export module) - ``influxdb-client`` (for the InfluxDB version 2 export module) - ``kafka-python`` (for the Kafka export module) +- ``nats-py`` (for the NATS export module) - ``netifaces2`` (for the IP plugin) - ``nvidia-ml-py`` (for the GPU plugin) - ``pycouchdb`` (for the CouchDB export module) diff --git a/conf/glances.conf b/conf/glances.conf index 6a0fb795..fba18a73 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -890,6 +890,14 @@ password=password # Most of the time, you should not overwrite this value #hostname=mycomputer +[nats] +# Configuration for the --export nats option +# https://nats.io/ +# Host is a separated list of NATS nodes +host=nats://localhost:4222 +# Prefix for the subjects (default is 'glances') +prefix=glances + ############################################################################## # AMPS # * enable: Enable (true) or disable (false) the AMP diff --git a/docs/gw/index.rst b/docs/gw/index.rst index db2dc976..c4c8947c 100644 --- a/docs/gw/index.rst +++ b/docs/gw/index.rst @@ -36,6 +36,7 @@ This section describes the available exporters and how to configure them: kafka mqtt mongodb + nats opentsdb prometheus rabbitmq diff --git a/docs/gw/nats.rst b/docs/gw/nats.rst new file mode 100644 index 00000000..9843c09b --- /dev/null +++ b/docs/gw/nats.rst @@ -0,0 +1,68 @@ +.. _nats: + +NATS +==== + +NATS is a message broker. + +You can export statistics to a ``NATS`` server. + +The connection should be defined in the Glances configuration file as +following: + +.. code-block:: ini + + [nats] + host=nats://localhost:4222 + prefix=glances + +and run Glances with: + +.. code-block:: console + + $ glances --export nats + +Data model +----------- + +Glances stats are published as JSON messagesto the following subjects: + + . + +Example: + + CPU stats are published to glances.cpu + +So a simple Python client will subscribe to this subject with: + + + import asyncio + + import nats + + + async def main(): + nc = nats.NATS() + + await nc.connect(servers=["nats://localhost:4222"]) + + future = asyncio.Future() + + async def cb(msg): + nonlocal future + future.set_result(msg) + + await nc.subscribe("glances.cpu", cb=cb) + + # Wait for message to come in + print("Waiting (max 30 seconds) for a message on 'glances' subject...") + msg = await asyncio.wait_for(future, 30) + print(msg.subject, msg.data) + + if __name__ == '__main__': + asyncio.run(main()) + +To subscribe to all Glannces stats use wildcard: + + await nc.subscribe("glances.*", cb=cb) + diff --git a/glances/exports/glances_nats/__init__.py b/glances/exports/glances_nats/__init__.py new file mode 100644 index 00000000..3cdec291 --- /dev/null +++ b/glances/exports/glances_nats/__init__.py @@ -0,0 +1,221 @@ +"""NATS interface class.""" +import asyncio +import threading +import time + +from nats.aio.client import Client as NATS +from nats.errors import ConnectionClosedError +from nats.errors import TimeoutError as NatsTimeoutError + +from glances.exports.export import GlancesExport +from glances.globals import json_dumps +from glances.logger import logger + + +class Export(GlancesExport): + """This class manages the NATS export module.""" + + def __init__(self, config=None, args=None): + """Init the NATS export IF.""" + super().__init__(config=config, args=args) + + # Load the NATS configuration file + self.export_enable = self.load_conf( + 'nats', + mandatories=['host'], + options=['prefix'], + ) + if not self.export_enable: + exit('Missing NATS config') + + self.prefix = self.prefix or 'glances' + self.hosts = self.host + + logger.info(f"Initializing NATS export with prefix '{self.prefix}' to {self.hosts}") + + # Create a persistent event loop in a background thread + self.loop = None + self.client = None + self._connected = False + self._shutdown = False + self._loop_ready = threading.Event() + self._loop_exception = None + self._publish_count = 0 + + self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True) + self._loop_thread.start() + + # Wait for the loop to be ready + if not self._loop_ready.wait(timeout=10): + exit("NATS event loop failed to start within timeout") + + if self._loop_exception: + exit(f"NATS event loop creation failed: {self._loop_exception}") + + if self.loop is None: + exit("NATS event loop is None after initialization") + + # Initial connection attempt + future = asyncio.run_coroutine_threadsafe(self._connect(), self.loop) + try: + future.result(timeout=10) + logger.info("NATS export initialized successfully") + except Exception as e: + logger.warning(f"Initial NATS connection failed: {e}. Will retry in background.") + + def _run_event_loop(self): + """Run event loop in background thread.""" + try: + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self._loop_ready.set() + logger.info("NATS event loop started") + self.loop.run_forever() + except Exception as e: + self._loop_exception = e + self._loop_ready.set() + logger.error(f"Event loop thread error: {e}") + finally: + # Clean up any pending tasks + pending = asyncio.all_tasks(self.loop) + for task in pending: + task.cancel() + if pending: + self.loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + self.loop.close() + logger.info("NATS event loop stopped") + + async def _connect(self): + """Connect to NATS with error handling.""" + try: + if self.client: + try: + await self.client.close() + except Exception as e: + logger.debug(f"Error closing existing NATS client: {e}") + + self.client = NATS() + + logger.info(f"Connecting to NATS servers: {self.hosts}") + + # Configure with reconnection callbacks + await self.client.connect( + servers=[s.strip() for s in self.hosts.split(',')], + reconnect_time_wait=2, + max_reconnect_attempts=60, + error_cb=self._error_callback, + disconnected_cb=self._disconnected_callback, + reconnected_cb=self._reconnected_callback, + ) + + self._connected = True + logger.info(f"Successfully connected to NATS: {self.hosts}") + except Exception as e: + self._connected = False + logger.error(f"NATS connection error: {e}") + raise + + async def _error_callback(self, e): + """Called when NATS client encounters an error.""" + logger.error(f"NATS error callback: {e}") + + async def _disconnected_callback(self): + """Called when disconnected from NATS.""" + self._connected = False + logger.warning("NATS disconnected callback") + + async def _reconnected_callback(self): + """Called when reconnected to NATS.""" + self._connected = True + logger.info("NATS reconnected callback") + + def exit(self): + """Close the NATS connection.""" + super().exit() + self._shutdown = True + logger.info("NATS export shutting down") + + if self.loop and self.client: + future = asyncio.run_coroutine_threadsafe(self._disconnect(), self.loop) + try: + future.result(timeout=5) + except Exception as e: + logger.error(f"Error disconnecting from NATS: {e}") + + if self.loop: + self.loop.call_soon_threadsafe(self.loop.stop) + time.sleep(0.5) + + logger.info(f"NATS export shutdown complete. Total messages published: {self._publish_count}") + + async def _disconnect(self): + """Disconnect from NATS.""" + try: + if self.client and self._connected: + await self.client.drain() + await self.client.close() + self._connected = False + logger.info("NATS disconnected cleanly") + except Exception as e: + logger.error(f"Error in disconnect: {e}") + + def export(self, name, columns, points): + """Write the points in NATS.""" + logger.info(f"Export called: name={name}, columns={columns}, connected={self._connected}") + + if self._shutdown: + logger.warning("Export called during shutdown, skipping") + return + + if not self.loop or not self.loop.is_running(): + logger.error("NATS event loop is not running") + return + + if not self._connected: + logger.warning("NATS not connected, skipping export") + return + + subject_name = f"{self.prefix}.{name}" + subject_data = dict(zip(columns, points)) + + logger.info(f"Publishing to subject: {subject_name}") + + # Submit the publish operation to the background event loop + try: + future = asyncio.run_coroutine_threadsafe( + self._publish(subject_name, json_dumps(subject_data)), + self.loop + ) + # Don't block forever - use a short timeout + future.result(timeout=1) + self._publish_count += 1 + logger.info(f"Successfully published message #{self._publish_count} to {subject_name}") + except asyncio.TimeoutError: + logger.warning(f"NATS publish timeout for {subject_name}") + except Exception as e: + logger.error(f"NATS publish error for {subject_name}: {e}", exc_info=True) + + async def _publish(self, subject, data): + """Publish data to NATS.""" + try: + logger.info(f"_publish called: subject={subject}, data_length={len(data)}") + + if not self._connected: + raise ConnectionClosedError("Not connected to NATS") + + logger.info(f"Calling client.publish for {subject}") + await self.client.publish(subject, data) + + logger.info(f"Calling client.flush for {subject}") + await asyncio.wait_for(self.client.flush(), timeout=2.0) + + logger.info(f"Successfully published and flushed to '{subject}'") + except (ConnectionClosedError, NatsTimeoutError) as e: + self._connected = False + logger.error(f"NATS publish failed: {e}") + raise + except Exception as e: + logger.error(f"Unexpected error in _publish: {e}", exc_info=True) + raise + +# End of glances/exports/glances_nats/__init__.py diff --git a/pyproject.toml b/pyproject.toml index b0bcdbb3..5e702e7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ export = [ "influxdb>=1.0.0", "influxdb3-python", "kafka-python", + "nats-py", "paho-mqtt", "pika", "potsdb", diff --git a/tests-data/issues/issue3341-NATS/pub.py b/tests-data/issues/issue3341-NATS/pub.py new file mode 100644 index 00000000..0123b08c --- /dev/null +++ b/tests-data/issues/issue3341-NATS/pub.py @@ -0,0 +1,17 @@ +import asyncio + +import nats + + +async def main(): + nc = nats.NATS() + + await nc.connect(servers=["nats://localhost:4222"]) + + await nc.publish("glances.test", b'A test') + await nc.flush() + +if __name__ == '__main__': + asyncio.run(main()) + +# To run this test script, make sure you have a NATS server running locally. diff --git a/tests-data/issues/issue3341-NATS/sub.py b/tests-data/issues/issue3341-NATS/sub.py new file mode 100644 index 00000000..39d84101 --- /dev/null +++ b/tests-data/issues/issue3341-NATS/sub.py @@ -0,0 +1,27 @@ +import asyncio + +import nats + + +async def main(): + nc = nats.NATS() + + await nc.connect(servers=["nats://localhost:4222"]) + + future = asyncio.Future() + + async def cb(msg): + nonlocal future + future.set_result(msg) + + await nc.subscribe("glances.*", cb=cb) + + # Wait for message to come in + print("Waiting (max 30s) for a message on 'glances' subject...") + msg = await asyncio.wait_for(future, 30) + print(msg.subject, msg.data) + +if __name__ == '__main__': + asyncio.run(main()) + +# To run this test script, make sure you have a NATS server running locally. diff --git a/tests/test_export_nats.sh b/tests/test_export_nats.sh new file mode 100755 index 00000000..32497682 --- /dev/null +++ b/tests/test_export_nats.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Pre-requisites: +# - docker +# - jq + +# Exit on error +set -e + +echo "Stop previous nats container..." +docker stop nats-for-glances || true +docker rm nats-for-glances || true + +echo "Starting nats container..." +docker run -d \ + --name nats-for-glances \ + -p 4222:4222 \ + -p 8222:8222 \ + -p 6222:6222 \ + nats:latest + +# Wait for InfluxDB to be ready (5 seconds) +echo "Waiting for nats to start (~ 5 seconds)..." +sleep 5 + +# Run glances with export to nats, stopping after 10 writes +# This will run synchronously now since we're using --stop-after +echo "Glances to export system stats to nats (duration: ~ 20 seconds)" +.venv/bin/python -m glances --config ./conf/glances.conf --export nats --stop-after 10 --quiet + +# Stop and remove the nats container +echo "Stopping and removing nats container..." +docker stop nats-for-glances && docker rm nats-for-glances + +echo "Script completed successfully!" diff --git a/tests/test_export_timescaledb.sh b/tests/test_export_timescaledb.sh index 4865cf60..6f080070 100755 --- a/tests/test_export_timescaledb.sh +++ b/tests/test_export_timescaledb.sh @@ -38,6 +38,6 @@ docker exec timescaledb-for-glances psql -d "postgres://postgres:password@localh # Stop and remove the TimescaleDB container echo "Stopping and removing TimescaleDB container..." -# docker stop timescaledb-for-glances && docker rm timescaledb-for-glances +docker stop timescaledb-for-glances && docker rm timescaledb-for-glances echo "Script completed successfully!" From 12083f33fc365b73362941d60c8ea8376c6cb56d Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 2 Jan 2026 09:42:03 +0100 Subject: [PATCH 09/13] Remove debug message --- glances/exports/glances_nats/__init__.py | 56 ++++++++++-------------- tests-data/issues/issue3341-NATS/sub.py | 18 +++++--- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/glances/exports/glances_nats/__init__.py b/glances/exports/glances_nats/__init__.py index 3cdec291..47d44338 100644 --- a/glances/exports/glances_nats/__init__.py +++ b/glances/exports/glances_nats/__init__.py @@ -1,4 +1,13 @@ +# +# This file is part of Glances. +# +# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# +# SPDX-License-Identifier: LGPL-3.0-only +# + """NATS interface class.""" + import asyncio import threading import time @@ -29,10 +38,9 @@ class Export(GlancesExport): exit('Missing NATS config') self.prefix = self.prefix or 'glances' + # Host is a comma-separated list of NATS servers self.hosts = self.host - logger.info(f"Initializing NATS export with prefix '{self.prefix}' to {self.hosts}") - # Create a persistent event loop in a background thread self.loop = None self.client = None @@ -61,7 +69,7 @@ class Export(GlancesExport): future.result(timeout=10) logger.info("NATS export initialized successfully") except Exception as e: - logger.warning(f"Initial NATS connection failed: {e}. Will retry in background.") + logger.warning(f"NATS Initial connection failed: {e}. Will retry in background.") def _run_event_loop(self): """Run event loop in background thread.""" @@ -69,21 +77,18 @@ class Export(GlancesExport): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self._loop_ready.set() - logger.info("NATS event loop started") self.loop.run_forever() except Exception as e: self._loop_exception = e self._loop_ready.set() - logger.error(f"Event loop thread error: {e}") + logger.error(f"NATS Export Event loop thread error: {e}") finally: - # Clean up any pending tasks pending = asyncio.all_tasks(self.loop) for task in pending: task.cancel() if pending: self.loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) self.loop.close() - logger.info("NATS event loop stopped") async def _connect(self): """Connect to NATS with error handling.""" @@ -92,11 +97,11 @@ class Export(GlancesExport): try: await self.client.close() except Exception as e: - logger.debug(f"Error closing existing NATS client: {e}") + logger.debug(f"NATS Error closing existing client: {e}") self.client = NATS() - logger.info(f"Connecting to NATS servers: {self.hosts}") + logger.debug(f"NATS Connecting to servers: {self.hosts}") # Configure with reconnection callbacks await self.client.connect( @@ -109,7 +114,7 @@ class Export(GlancesExport): ) self._connected = True - logger.info(f"Successfully connected to NATS: {self.hosts}") + logger.debug(f"NATS Successfully connected to servers: {self.hosts}") except Exception as e: self._connected = False logger.error(f"NATS connection error: {e}") @@ -122,12 +127,12 @@ class Export(GlancesExport): async def _disconnected_callback(self): """Called when disconnected from NATS.""" self._connected = False - logger.warning("NATS disconnected callback") + logger.debug("NATS disconnected callback") async def _reconnected_callback(self): """Called when reconnected to NATS.""" self._connected = True - logger.info("NATS reconnected callback") + logger.debug("NATS reconnected callback") def exit(self): """Close the NATS connection.""" @@ -140,13 +145,13 @@ class Export(GlancesExport): try: future.result(timeout=5) except Exception as e: - logger.error(f"Error disconnecting from NATS: {e}") + logger.error(f"NATS Error disconnecting from server: {e}") if self.loop: self.loop.call_soon_threadsafe(self.loop.stop) time.sleep(0.5) - logger.info(f"NATS export shutdown complete. Total messages published: {self._publish_count}") + logger.debug(f"NATS export shutdown complete. Total messages published: {self._publish_count}") async def _disconnect(self): """Disconnect from NATS.""" @@ -155,16 +160,14 @@ class Export(GlancesExport): await self.client.drain() await self.client.close() self._connected = False - logger.info("NATS disconnected cleanly") + logger.debug("NATS disconnected cleanly") except Exception as e: - logger.error(f"Error in disconnect: {e}") + logger.error(f"NATS Error in disconnect: {e}") def export(self, name, columns, points): """Write the points in NATS.""" - logger.info(f"Export called: name={name}, columns={columns}, connected={self._connected}") - if self._shutdown: - logger.warning("Export called during shutdown, skipping") + logger.debug("NATS Export called during shutdown, skipping") return if not self.loop or not self.loop.is_running(): @@ -178,8 +181,6 @@ class Export(GlancesExport): subject_name = f"{self.prefix}.{name}" subject_data = dict(zip(columns, points)) - logger.info(f"Publishing to subject: {subject_name}") - # Submit the publish operation to the background event loop try: future = asyncio.run_coroutine_threadsafe( @@ -189,7 +190,6 @@ class Export(GlancesExport): # Don't block forever - use a short timeout future.result(timeout=1) self._publish_count += 1 - logger.info(f"Successfully published message #{self._publish_count} to {subject_name}") except asyncio.TimeoutError: logger.warning(f"NATS publish timeout for {subject_name}") except Exception as e: @@ -198,24 +198,16 @@ class Export(GlancesExport): async def _publish(self, subject, data): """Publish data to NATS.""" try: - logger.info(f"_publish called: subject={subject}, data_length={len(data)}") - if not self._connected: - raise ConnectionClosedError("Not connected to NATS") - - logger.info(f"Calling client.publish for {subject}") + raise ConnectionClosedError("NATS Not connected to server") await self.client.publish(subject, data) - - logger.info(f"Calling client.flush for {subject}") await asyncio.wait_for(self.client.flush(), timeout=2.0) - - logger.info(f"Successfully published and flushed to '{subject}'") except (ConnectionClosedError, NatsTimeoutError) as e: self._connected = False logger.error(f"NATS publish failed: {e}") raise except Exception as e: - logger.error(f"Unexpected error in _publish: {e}", exc_info=True) + logger.error(f"NATS Unexpected error in _publish: {e}", exc_info=True) raise # End of glances/exports/glances_nats/__init__.py diff --git a/tests-data/issues/issue3341-NATS/sub.py b/tests-data/issues/issue3341-NATS/sub.py index 39d84101..07cf5b62 100644 --- a/tests-data/issues/issue3341-NATS/sub.py +++ b/tests-data/issues/issue3341-NATS/sub.py @@ -4,6 +4,9 @@ import nats async def main(): + duration = 30 + subject = "glances.*" + nc = nats.NATS() await nc.connect(servers=["nats://localhost:4222"]) @@ -11,15 +14,16 @@ async def main(): future = asyncio.Future() async def cb(msg): - nonlocal future - future.set_result(msg) + subject = msg.subject + reply = msg.reply + data = msg.data.decode() + print(f"Received a message on '{subject} {reply}': {data}") - await nc.subscribe("glances.*", cb=cb) + print(f"Receiving message from {subject} during {duration} seconds...") + await nc.subscribe(subject, cb=cb) + await asyncio.wait_for(future, duration) - # Wait for message to come in - print("Waiting (max 30s) for a message on 'glances' subject...") - msg = await asyncio.wait_for(future, 30) - print(msg.subject, msg.data) + await nc.close() if __name__ == '__main__': asyncio.run(main()) From 6cedd16753f60cae36ba3685954c7fa3e2cf0dad Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 2 Jan 2026 17:02:49 +0100 Subject: [PATCH 10/13] Add test for NATS in the Makefile --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f97cbc4d..90264121 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,10 @@ test-export-influxdb-v3: ## Run interface tests with InfluxDB version 3 (Core) test-export-timescaledb: ## Run interface tests with TimescaleDB /bin/bash ./tests/test_export_timescaledb.sh -test-exports: test-export-csv test-export-json test-export-influxdb-v1 test-export-influxdb-v3 test-export-timescaledb ## Tests all exports +test-export-nats: ## Run interface tests with NATS + /bin/bash ./tests/test_export_nats.sh + +test-exports: test-export-csv test-export-json test-export-influxdb-v1 test-export-influxdb-v3 test-export-timescaledb test-export-nats ## Tests all exports # =================================================================== # Linters, profilers and cyber security From 4bd36a34a1e6c7f11d3d57ee2db7ff9d68ea2786 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 2 Jan 2026 17:28:12 +0100 Subject: [PATCH 11/13] Refactor NATS export to use async base class Replace custom event loop threading implementation with GlancesExportAsyncio base class. This simplifies the code by delegating async lifecycle management to the parent class and using standardized _async_init(), _async_exit(), and _async_export() methods instead of manual thread and event loop handling. --- glances/exports/export.py | 2 +- glances/exports/export_asyncio.py | 168 +++++++++++++++++++++++ glances/exports/glances_nats/__init__.py | 134 ++++-------------- 3 files changed, 196 insertions(+), 108 deletions(-) create mode 100644 glances/exports/export_asyncio.py diff --git a/glances/exports/export.py b/glances/exports/export.py index bbd37306..c0cf7422 100644 --- a/glances/exports/export.py +++ b/glances/exports/export.py @@ -1,7 +1,7 @@ # # This file is part of Glances. # -# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# SPDX-FileCopyrightText: 2026 Nicolas Hennion # # SPDX-License-Identifier: LGPL-3.0-only # diff --git a/glances/exports/export_asyncio.py b/glances/exports/export_asyncio.py new file mode 100644 index 00000000..71ec6a98 --- /dev/null +++ b/glances/exports/export_asyncio.py @@ -0,0 +1,168 @@ +# +# This file is part of Glances. +# +# SPDX-FileCopyrightText: 2026 Nicolas Hennion +# +# SPDX-License-Identifier: LGPL-3.0-only +# + +""" +I am your son... +...abstract class for AsyncIO-based Glances exports. +""" + +import asyncio +import threading +import time +from abc import abstractmethod + +from glances.exports.export import GlancesExport +from glances.logger import logger + + +class GlancesExportAsyncio(GlancesExport): + """Abstract class for AsyncIO-based export modules. + + This class manages a persistent event loop in a background thread, + allowing child classes to use AsyncIO operations for exporting data. + + Child classes must implement: + - async _async_init(): AsyncIO initialization (e.g., connection setup) + - async _async_exit(): AsyncIO cleanup (e.g., disconnection) + - async _async_export(name, columns, points): AsyncIO export operation + """ + + def __init__(self, config=None, args=None): + """Init the AsyncIO export interface.""" + super().__init__(config=config, args=args) + + # AsyncIO event loop management + self.loop = None + self._loop_ready = threading.Event() + self._loop_exception = None + self._shutdown = False + + # Start the background event loop thread + self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True) + self._loop_thread.start() + + # Wait for the loop to be ready + if not self._loop_ready.wait(timeout=10): + raise RuntimeError("AsyncIO event loop failed to start within timeout") + + if self._loop_exception: + raise RuntimeError(f"AsyncIO event loop creation failed: {self._loop_exception}") + + if self.loop is None: + raise RuntimeError("AsyncIO event loop is None after initialization") + + # Call child class AsyncIO initialization + future = asyncio.run_coroutine_threadsafe(self._async_init(), self.loop) + try: + future.result(timeout=10) + logger.debug(f"{self.export_name} AsyncIO export initialized successfully") + except Exception as e: + logger.warning(f"{self.export_name} AsyncIO initialization failed: {e}. Will retry in background.") + + def _run_event_loop(self): + """Run event loop in background thread.""" + try: + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self._loop_ready.set() + self.loop.run_forever() + except Exception as e: + self._loop_exception = e + self._loop_ready.set() + logger.error(f"{self.export_name} AsyncIO event loop thread error: {e}") + finally: + # Clean up pending tasks + pending = asyncio.all_tasks(self.loop) + for task in pending: + task.cancel() + if pending: + self.loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + self.loop.close() + + @abstractmethod + async def _async_init(self): + """AsyncIO initialization method. + + Child classes should implement this method to perform AsyncIO-based + initialization such as connecting to servers, setting up clients, etc. + + This method is called once during __init__ after the event loop is ready. + """ + pass + + @abstractmethod + async def _async_exit(self): + """AsyncIO cleanup method. + + Child classes should implement this method to perform AsyncIO-based + cleanup such as disconnecting from servers, closing clients, etc. + + This method is called during exit() before stopping the event loop. + """ + pass + + @abstractmethod + async def _async_export(self, name, columns, points): + """AsyncIO export method. + + Child classes must implement this method to perform the actual + export operation using AsyncIO. + + :param name: plugin name + :param columns: list of column names + :param points: list of values corresponding to columns + """ + pass + + def exit(self): + """Close the AsyncIO export module.""" + super().exit() + self._shutdown = True + logger.info(f"{self.export_name} AsyncIO export shutting down") + + # Call child class cleanup + if self.loop: + future = asyncio.run_coroutine_threadsafe(self._async_exit(), self.loop) + try: + future.result(timeout=5) + except Exception as e: + logger.error(f"{self.export_name} Error in AsyncIO cleanup: {e}") + + # Stop the event loop + if self.loop: + self.loop.call_soon_threadsafe(self.loop.stop) + time.sleep(0.5) + + logger.debug(f"{self.export_name} AsyncIO export shutdown complete") + + def export(self, name, columns, points): + """Export data using AsyncIO. + + This method bridges the synchronous export() interface with + the AsyncIO _async_export() implementation. + """ + if self._shutdown: + logger.debug(f"{self.export_name} Export called during shutdown, skipping") + return + + if not self.loop or not self.loop.is_running(): + logger.error(f"{self.export_name} AsyncIO event loop is not running") + return + + # Submit the export operation to the background event loop + try: + future = asyncio.run_coroutine_threadsafe( + self._async_export(name, columns, points), + self.loop + ) + # Don't block forever - use a short timeout + future.result(timeout=1) + except asyncio.TimeoutError: + logger.warning(f"{self.export_name} AsyncIO export timeout for {name}") + except Exception as e: + logger.error(f"{self.export_name} AsyncIO export error for {name}: {e}", exc_info=True) diff --git a/glances/exports/glances_nats/__init__.py b/glances/exports/glances_nats/__init__.py index 47d44338..97941cfd 100644 --- a/glances/exports/glances_nats/__init__.py +++ b/glances/exports/glances_nats/__init__.py @@ -1,96 +1,57 @@ # # This file is part of Glances. # -# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# SPDX-FileCopyrightText: 2026 Nicolas Hennion # # SPDX-License-Identifier: LGPL-3.0-only # """NATS interface class.""" -import asyncio -import threading -import time - from nats.aio.client import Client as NATS from nats.errors import ConnectionClosedError from nats.errors import TimeoutError as NatsTimeoutError -from glances.exports.export import GlancesExport +from glances.exports.export_asyncio import GlancesExportAsyncio from glances.globals import json_dumps from glances.logger import logger -class Export(GlancesExport): +class Export(GlancesExportAsyncio): """This class manages the NATS export module.""" def __init__(self, config=None, args=None): """Init the NATS export IF.""" - super().__init__(config=config, args=args) + # Load the NATS configuration file before calling super().__init__ + # because super().__init__ will call _async_init() which needs config + self.config = config + self.args = args + self.export_name = self.__class__.__module__ - # Load the NATS configuration file - self.export_enable = self.load_conf( + export_enable = self.load_conf( 'nats', mandatories=['host'], options=['prefix'], ) - if not self.export_enable: + if not export_enable: exit('Missing NATS config') self.prefix = self.prefix or 'glances' # Host is a comma-separated list of NATS servers self.hosts = self.host - # Create a persistent event loop in a background thread - self.loop = None + # NATS-specific attributes self.client = None self._connected = False - self._shutdown = False - self._loop_ready = threading.Event() - self._loop_exception = None self._publish_count = 0 - self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True) - self._loop_thread.start() + # Call parent __init__ which will start event loop and call _async_init() + super().__init__(config=config, args=args) - # Wait for the loop to be ready - if not self._loop_ready.wait(timeout=10): - exit("NATS event loop failed to start within timeout") + # Restore export_enable after super().__init__() resets it to False + self.export_enable = export_enable - if self._loop_exception: - exit(f"NATS event loop creation failed: {self._loop_exception}") - - if self.loop is None: - exit("NATS event loop is None after initialization") - - # Initial connection attempt - future = asyncio.run_coroutine_threadsafe(self._connect(), self.loop) - try: - future.result(timeout=10) - logger.info("NATS export initialized successfully") - except Exception as e: - logger.warning(f"NATS Initial connection failed: {e}. Will retry in background.") - - def _run_event_loop(self): - """Run event loop in background thread.""" - try: - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - self._loop_ready.set() - self.loop.run_forever() - except Exception as e: - self._loop_exception = e - self._loop_ready.set() - logger.error(f"NATS Export Event loop thread error: {e}") - finally: - pending = asyncio.all_tasks(self.loop) - for task in pending: - task.cancel() - if pending: - self.loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) - self.loop.close() - - async def _connect(self): + async def _async_init(self): """Connect to NATS with error handling.""" try: if self.client: @@ -114,7 +75,7 @@ class Export(GlancesExport): ) self._connected = True - logger.debug(f"NATS Successfully connected to servers: {self.hosts}") + logger.info(f"NATS Successfully connected to servers: {self.hosts}") except Exception as e: self._connected = False logger.error(f"NATS connection error: {e}") @@ -134,46 +95,19 @@ class Export(GlancesExport): self._connected = True logger.debug("NATS reconnected callback") - def exit(self): - """Close the NATS connection.""" - super().exit() - self._shutdown = True - logger.info("NATS export shutting down") - - if self.loop and self.client: - future = asyncio.run_coroutine_threadsafe(self._disconnect(), self.loop) - try: - future.result(timeout=5) - except Exception as e: - logger.error(f"NATS Error disconnecting from server: {e}") - - if self.loop: - self.loop.call_soon_threadsafe(self.loop.stop) - time.sleep(0.5) - - logger.debug(f"NATS export shutdown complete. Total messages published: {self._publish_count}") - - async def _disconnect(self): + async def _async_exit(self): """Disconnect from NATS.""" try: if self.client and self._connected: await self.client.drain() await self.client.close() self._connected = False - logger.debug("NATS disconnected cleanly") + logger.debug(f"NATS disconnected cleanly. Total messages published: {self._publish_count}") except Exception as e: logger.error(f"NATS Error in disconnect: {e}") - def export(self, name, columns, points): - """Write the points in NATS.""" - if self._shutdown: - logger.debug("NATS Export called during shutdown, skipping") - return - - if not self.loop or not self.loop.is_running(): - logger.error("NATS event loop is not running") - return - + async def _async_export(self, name, columns, points): + """Write the points to NATS using AsyncIO.""" if not self._connected: logger.warning("NATS not connected, skipping export") return @@ -181,33 +115,19 @@ class Export(GlancesExport): subject_name = f"{self.prefix}.{name}" subject_data = dict(zip(columns, points)) - # Submit the publish operation to the background event loop - try: - future = asyncio.run_coroutine_threadsafe( - self._publish(subject_name, json_dumps(subject_data)), - self.loop - ) - # Don't block forever - use a short timeout - future.result(timeout=1) - self._publish_count += 1 - except asyncio.TimeoutError: - logger.warning(f"NATS publish timeout for {subject_name}") - except Exception as e: - logger.error(f"NATS publish error for {subject_name}: {e}", exc_info=True) - - async def _publish(self, subject, data): - """Publish data to NATS.""" + # Publish data to NATS try: if not self._connected: raise ConnectionClosedError("NATS Not connected to server") - await self.client.publish(subject, data) - await asyncio.wait_for(self.client.flush(), timeout=2.0) + await self.client.publish(subject_name, json_dumps(subject_data)) + await self.client.flush(timeout=2.0) + self._publish_count += 1 except (ConnectionClosedError, NatsTimeoutError) as e: self._connected = False - logger.error(f"NATS publish failed: {e}") + logger.error(f"NATS publish failed for {subject_name}: {e}") raise except Exception as e: - logger.error(f"NATS Unexpected error in _publish: {e}", exc_info=True) + logger.error(f"NATS Unexpected error publishing {subject_name}: {e}", exc_info=True) raise # End of glances/exports/glances_nats/__init__.py From 91279b9144f36390dbf0ba681fd07eefc9db77be Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 2 Jan 2026 18:07:28 +0100 Subject: [PATCH 12/13] Lint and format the code --- glances/exports/export_asyncio.py | 5 +---- glances/exports/glances_nats/__init__.py | 1 + tests-data/issues/issue3341-NATS/pub.py | 1 + tests-data/issues/issue3341-NATS/sub.py | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/glances/exports/export_asyncio.py b/glances/exports/export_asyncio.py index 71ec6a98..6a37f295 100644 --- a/glances/exports/export_asyncio.py +++ b/glances/exports/export_asyncio.py @@ -156,10 +156,7 @@ class GlancesExportAsyncio(GlancesExport): # Submit the export operation to the background event loop try: - future = asyncio.run_coroutine_threadsafe( - self._async_export(name, columns, points), - self.loop - ) + future = asyncio.run_coroutine_threadsafe(self._async_export(name, columns, points), self.loop) # Don't block forever - use a short timeout future.result(timeout=1) except asyncio.TimeoutError: diff --git a/glances/exports/glances_nats/__init__.py b/glances/exports/glances_nats/__init__.py index 97941cfd..b9f17529 100644 --- a/glances/exports/glances_nats/__init__.py +++ b/glances/exports/glances_nats/__init__.py @@ -130,4 +130,5 @@ class Export(GlancesExportAsyncio): logger.error(f"NATS Unexpected error publishing {subject_name}: {e}", exc_info=True) raise + # End of glances/exports/glances_nats/__init__.py diff --git a/tests-data/issues/issue3341-NATS/pub.py b/tests-data/issues/issue3341-NATS/pub.py index 0123b08c..674c4e96 100644 --- a/tests-data/issues/issue3341-NATS/pub.py +++ b/tests-data/issues/issue3341-NATS/pub.py @@ -11,6 +11,7 @@ async def main(): await nc.publish("glances.test", b'A test') await nc.flush() + if __name__ == '__main__': asyncio.run(main()) diff --git a/tests-data/issues/issue3341-NATS/sub.py b/tests-data/issues/issue3341-NATS/sub.py index 07cf5b62..a42fb205 100644 --- a/tests-data/issues/issue3341-NATS/sub.py +++ b/tests-data/issues/issue3341-NATS/sub.py @@ -25,6 +25,7 @@ async def main(): await nc.close() + if __name__ == '__main__': asyncio.run(main()) From 6e844f3c8b34c4a5f859c6694c462bbf97129403 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 2 Jan 2026 18:10:00 +0100 Subject: [PATCH 13/13] Update doc --- all-requirements.txt | 54 +- dev-requirements.txt | 227 ++---- docker-requirements.txt | 10 +- docs/api/openapi.json | 924 +---------------------- docs/api/python.rst | 394 +++++----- docs/api/restful.rst | 673 ++++++----------- docs/man/glances.1 | 4 +- glances/outputs/static/package-lock.json | 7 +- requirements.txt | 2 +- 9 files changed, 531 insertions(+), 1764 deletions(-) diff --git a/all-requirements.txt b/all-requirements.txt index 1490938c..443e69f2 100644 --- a/all-requirements.txt +++ b/all-requirements.txt @@ -40,23 +40,17 @@ cryptography==46.0.3 # via pysnmpcrypto defusedxml==0.7.1 # via glances -dnspython==2.7.0 ; python_full_version < '3.10' - # via pymongo -dnspython==2.8.0 ; python_full_version >= '3.10' +dnspython==2.8.0 # via pymongo docker==7.1.0 # via glances -elastic-transport==9.1.0 ; python_full_version < '3.10' +elastic-transport==9.2.1 # via elasticsearch -elastic-transport==9.2.0 ; python_full_version >= '3.10' - # via elasticsearch -elasticsearch==9.1.2 ; python_full_version < '3.10' - # via glances -elasticsearch==9.2.0 ; python_full_version >= '3.10' +elasticsearch==9.2.1 # via glances exceptiongroup==1.2.2 ; python_full_version < '3.11' # via anyio -fastapi==0.125.0 +fastapi==0.128.0 # via glances geomet==1.1.0 # via cassandra-driver @@ -68,9 +62,7 @@ humanfriendly==10.0 # via pysmart ibm-cloud-sdk-core==3.24.2 # via ibmcloudant -ibmcloudant==0.11.0 ; python_full_version < '3.10' - # via glances -ibmcloudant==0.11.2 ; python_full_version >= '3.10' +ibmcloudant==0.11.2 # via glances idna==3.11 # via @@ -78,9 +70,7 @@ idna==3.11 # requests ifaddr==0.2.0 # via zeroconf -importlib-metadata==7.1.0 ; python_full_version < '3.10' - # via pygal -importlib-metadata==8.7.0 ; python_full_version >= '3.10' +importlib-metadata==8.7.1 # via pygal influxdb==5.3.2 # via glances @@ -98,6 +88,8 @@ markupsafe==3.0.3 # via jinja2 msgpack==1.1.2 # via influxdb +nats-py==2.12.0 + # via glances netifaces2==0.0.22 # via glances nvidia-ml-py==13.590.44 @@ -118,23 +110,15 @@ potsdb==1.0.3 # via glances prometheus-client==0.23.1 # via glances -protobuf==4.25.8 ; python_full_version < '3.10' +protobuf==6.33.2 # via bernhard -protobuf==6.33.2 ; python_full_version >= '3.10' - # via bernhard -psutil==7.1.3 +psutil==7.2.1 # via glances -psycopg==3.2.13 ; python_full_version < '3.10' +psycopg==3.3.2 # via glances -psycopg==3.3.2 ; python_full_version >= '3.10' - # via glances -psycopg-binary==3.2.13 ; python_full_version < '3.10' and implementation_name != 'pypy' +psycopg-binary==3.3.2 ; implementation_name != 'pypy' # via psycopg -psycopg-binary==3.3.2 ; python_full_version >= '3.10' and implementation_name != 'pypy' - # via psycopg -pyarrow==21.0.0 ; python_full_version < '3.10' - # via influxdb3-python -pyarrow==22.0.0 ; python_full_version >= '3.10' +pyarrow==22.0.0 # via influxdb3-python pyasn1==0.6.1 # via pysnmp-lextudio @@ -203,21 +187,17 @@ six==1.17.0 # glances # influxdb # python-dateutil -sniffio==1.3.1 ; python_full_version >= '3.10' +sniffio==1.3.1 # via # elastic-transport # elasticsearch sparklines==0.7.0 # via glances -starlette==0.49.3 ; python_full_version < '3.10' - # via fastapi -starlette==0.50.0 ; python_full_version >= '3.10' +starlette==0.50.0 # via fastapi statsd==4.0.1 # via glances -termcolor==3.1.0 ; python_full_version < '3.10' - # via sparklines -termcolor==3.2.0 ; python_full_version >= '3.10' +termcolor==3.3.0 # via sparklines tomli==2.0.2 ; python_full_version < '3.11' # via podman @@ -247,7 +227,7 @@ urllib3==2.6.2 # influxdb3-python # podman # requests -uvicorn==0.38.0 +uvicorn==0.40.0 # via glances wifi==0.3.8 # via glances diff --git a/dev-requirements.txt b/dev-requirements.txt index bb88d3e3..2ed09c62 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,12 +1,10 @@ # This file was autogenerated by uv via the following command: # uv export --no-hashes --only-dev --output-file dev-requirements.txt -alabaster==0.7.16 ; python_full_version < '3.10' - # via sphinx -alabaster==1.0.0 ; python_full_version >= '3.10' +alabaster==1.0.0 # via sphinx annotated-types==0.7.0 # via pydantic -anyio==4.12.0 ; python_full_version >= '3.10' +anyio==4.12.0 # via # httpx # mcp @@ -23,17 +21,13 @@ attrs==25.4.0 # trio babel==2.17.0 # via sphinx -binaryornot==0.4.4 ; python_full_version < '3.10' - # via reuse boltons==21.0.0 # via # face # glom # semgrep boolean-py==5.0 - # via - # license-expression - # reuse + # via license-expression bracex==2.6 # via wcmatch certifi==2025.11.12 @@ -42,16 +36,12 @@ certifi==2025.11.12 # httpx # requests # selenium -cffi==2.0.0 ; (python_full_version >= '3.10' and implementation_name == 'pypy' and platform_python_implementation != 'PyPy') or (python_full_version >= '3.10' and os_name != 'nt' and platform_python_implementation != 'PyPy') or (implementation_name != 'pypy' and os_name == 'nt') +cffi==2.0.0 ; (implementation_name != 'pypy' and os_name == 'nt') or platform_python_implementation != 'PyPy' # via # cryptography # trio -cfgv==3.4.0 ; python_full_version < '3.10' +cfgv==3.5.0 # via pre-commit -cfgv==3.5.0 ; python_full_version >= '3.10' - # via pre-commit -chardet==5.2.0 ; python_full_version < '3.10' - # via binaryornot charset-normalizer==3.4.4 # via # python-debian @@ -72,22 +62,14 @@ colorama==0.4.6 # pytest # semgrep # sphinx -contourpy==1.3.0 ; python_full_version < '3.10' - # via matplotlib -contourpy==1.3.2 ; python_full_version == '3.10.*' +contourpy==1.3.2 ; python_full_version < '3.11' # via matplotlib contourpy==1.3.3 ; python_full_version >= '3.11' # via matplotlib -cryptography==46.0.3 ; python_full_version >= '3.10' +cryptography==46.0.3 # via pyjwt cycler==0.12.1 # via matplotlib -defusedxml==0.7.1 ; python_full_version < '3.10' - # via semgrep -deprecated==1.3.1 ; python_full_version < '3.10' - # via - # opentelemetry-api - # opentelemetry-exporter-otlp-proto-http distlib==0.4.0 # via virtualenv docutils==0.21.2 @@ -104,13 +86,9 @@ exceptiongroup==1.2.2 # trio-websocket face==24.0.0 # via glom -filelock==3.19.1 ; python_full_version < '3.10' +filelock==3.20.2 # via virtualenv -filelock==3.20.1 ; python_full_version >= '3.10' - # via virtualenv -fonttools==4.60.2 ; python_full_version < '3.10' - # via matplotlib -fonttools==4.61.1 ; python_full_version >= '3.10' +fonttools==4.61.1 # via matplotlib glom==22.1.0 # via semgrep @@ -122,11 +100,11 @@ h11==0.16.0 # httpcore # uvicorn # wsproto -httpcore==1.0.9 ; python_full_version >= '3.10' +httpcore==1.0.9 # via httpx -httpx==0.28.1 ; python_full_version >= '3.10' +httpx==0.28.1 # via mcp -httpx-sse==0.4.3 ; python_full_version >= '3.10' +httpx-sse==0.4.3 # via mcp identify==2.6.15 # via pre-commit @@ -138,17 +116,9 @@ idna==3.11 # trio imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 ; python_full_version < '3.10' - # via - # opentelemetry-api - # sphinx -importlib-metadata==8.7.0 ; python_full_version >= '3.10' +importlib-metadata==8.7.1 # via opentelemetry-api -importlib-resources==6.5.2 ; python_full_version < '3.10' - # via matplotlib -iniconfig==2.1.0 ; python_full_version < '3.10' - # via pytest -iniconfig==2.3.0 ; python_full_version >= '3.10' +iniconfig==2.3.0 # via pytest jinja2==3.1.6 # via @@ -160,42 +130,33 @@ jsonschema==4.25.1 # semgrep jsonschema-specifications==2025.9.1 # via jsonschema -kiwisolver==1.4.7 ; python_full_version < '3.10' - # via matplotlib -kiwisolver==1.4.9 ; python_full_version >= '3.10' +kiwisolver==1.4.9 # via matplotlib license-expression==30.4.4 # via reuse -markdown-it-py==3.0.0 ; python_full_version < '3.10' - # via rich -markdown-it-py==4.0.0 ; python_full_version >= '3.10' +markdown-it-py==4.0.0 # via rich markupsafe==3.0.3 # via jinja2 -matplotlib==3.9.4 ; python_full_version < '3.10' -matplotlib==3.10.8 ; python_full_version >= '3.10' -mcp==1.23.3 ; python_full_version >= '3.10' +matplotlib==3.10.8 +mcp==1.23.3 # via semgrep mdurl==0.1.2 # via markdown-it-py memory-profiler==0.61.0 -nodeenv==1.9.1 +nodeenv==1.10.0 # via # pre-commit # pyright -numpy==2.0.2 ; python_full_version < '3.10' +numpy==2.2.6 ; python_full_version < '3.11' # via # contourpy # matplotlib -numpy==2.2.6 ; python_full_version == '3.10.*' +numpy==2.4.0 ; python_full_version >= '3.11' # via # contourpy # matplotlib -numpy==2.3.5 ; python_full_version >= '3.11' - # via - # contourpy - # matplotlib -opentelemetry-api==1.25.0 ; python_full_version < '3.10' +opentelemetry-api==1.37.0 # via # opentelemetry-exporter-otlp-proto-http # opentelemetry-instrumentation @@ -203,58 +164,28 @@ opentelemetry-api==1.25.0 ; python_full_version < '3.10' # opentelemetry-sdk # opentelemetry-semantic-conventions # semgrep -opentelemetry-api==1.37.0 ; python_full_version >= '3.10' - # via - # opentelemetry-exporter-otlp-proto-http - # opentelemetry-instrumentation - # opentelemetry-instrumentation-requests - # opentelemetry-sdk - # opentelemetry-semantic-conventions - # semgrep -opentelemetry-exporter-otlp-proto-common==1.25.0 ; python_full_version < '3.10' +opentelemetry-exporter-otlp-proto-common==1.37.0 # via opentelemetry-exporter-otlp-proto-http -opentelemetry-exporter-otlp-proto-common==1.37.0 ; python_full_version >= '3.10' - # via opentelemetry-exporter-otlp-proto-http -opentelemetry-exporter-otlp-proto-http==1.25.0 ; python_full_version < '3.10' +opentelemetry-exporter-otlp-proto-http==1.37.0 # via semgrep -opentelemetry-exporter-otlp-proto-http==1.37.0 ; python_full_version >= '3.10' - # via semgrep -opentelemetry-instrumentation==0.46b0 ; python_full_version < '3.10' +opentelemetry-instrumentation==0.58b0 # via opentelemetry-instrumentation-requests -opentelemetry-instrumentation==0.58b0 ; python_full_version >= '3.10' - # via opentelemetry-instrumentation-requests -opentelemetry-instrumentation-requests==0.46b0 ; python_full_version < '3.10' +opentelemetry-instrumentation-requests==0.58b0 # via semgrep -opentelemetry-instrumentation-requests==0.58b0 ; python_full_version >= '3.10' - # via semgrep -opentelemetry-proto==1.25.0 ; python_full_version < '3.10' +opentelemetry-proto==1.37.0 # via # opentelemetry-exporter-otlp-proto-common # opentelemetry-exporter-otlp-proto-http -opentelemetry-proto==1.37.0 ; python_full_version >= '3.10' - # via - # opentelemetry-exporter-otlp-proto-common - # opentelemetry-exporter-otlp-proto-http -opentelemetry-sdk==1.25.0 ; python_full_version < '3.10' +opentelemetry-sdk==1.37.0 # via # opentelemetry-exporter-otlp-proto-http # semgrep -opentelemetry-sdk==1.37.0 ; python_full_version >= '3.10' - # via - # opentelemetry-exporter-otlp-proto-http - # semgrep -opentelemetry-semantic-conventions==0.46b0 ; python_full_version < '3.10' - # via - # opentelemetry-instrumentation-requests - # opentelemetry-sdk -opentelemetry-semantic-conventions==0.58b0 ; python_full_version >= '3.10' +opentelemetry-semantic-conventions==0.58b0 # via # opentelemetry-instrumentation # opentelemetry-instrumentation-requests # opentelemetry-sdk -opentelemetry-util-http==0.46b0 ; python_full_version < '3.10' - # via opentelemetry-instrumentation-requests -opentelemetry-util-http==0.58b0 ; python_full_version >= '3.10' +opentelemetry-util-http==0.58b0 # via opentelemetry-instrumentation-requests outcome==1.3.0.post0 # via @@ -271,30 +202,21 @@ packaging==25.0 # webdriver-manager peewee==3.18.3 # via semgrep -pillow==11.3.0 ; python_full_version < '3.10' +pillow==12.1.0 # via matplotlib -pillow==12.0.0 ; python_full_version >= '3.10' - # via matplotlib -platformdirs==4.4.0 ; python_full_version < '3.10' - # via virtualenv -platformdirs==4.5.1 ; python_full_version >= '3.10' +platformdirs==4.5.1 # via virtualenv pluggy==1.6.0 # via pytest -pre-commit==4.3.0 ; python_full_version < '3.10' -pre-commit==4.5.1 ; python_full_version >= '3.10' +pre-commit==4.5.1 +protobuf==6.33.2 # via # googleapis-common-protos # opentelemetry-proto -protobuf==6.33.2 ; python_full_version >= '3.10' -protobuf==4.25.8 ; python_full_version < '3.10' - # via - # googleapis-common-protos - # opentelemetry-proto -psutil==7.1.3 +psutil==7.2.1 # via memory-profiler py-spy==0.4.1 -pycparser==2.23 ; (python_full_version >= '3.10' and implementation_name != 'PyPy' and os_name != 'nt' and platform_python_implementation != 'PyPy') or (python_full_version >= '3.10' and implementation_name == 'pypy' and os_name == 'nt' and platform_python_implementation != 'PyPy') or (implementation_name != 'PyPy' and implementation_name != 'pypy' and os_name == 'nt') +pycparser==2.23 ; (implementation_name != 'PyPy' and implementation_name != 'pypy' and os_name == 'nt') or (implementation_name != 'PyPy' and platform_python_implementation != 'PyPy') # via cffi pydantic==2.12.5 # via @@ -303,7 +225,7 @@ pydantic==2.12.5 # rstcheck-core pydantic-core==2.41.5 # via pydantic -pydantic-settings==2.12.0 ; python_full_version >= '3.10' +pydantic-settings==2.12.0 # via mcp pygments==2.19.2 # via @@ -311,15 +233,14 @@ pygments==2.19.2 # rich # sphinx pyinstrument==5.1.1 -pyjwt==2.10.1 ; python_full_version >= '3.10' +pyjwt==2.10.1 # via mcp -pyparsing==3.2.5 +pyparsing==3.3.1 # via matplotlib pyright==1.1.407 pysocks==1.7.1 # via urllib3 -pytest==8.4.2 ; python_full_version < '3.10' -pytest==9.0.2 ; python_full_version >= '3.10' +pytest==9.0.2 python-dateutil==2.9.0.post0 # via matplotlib python-debian==1.0.1 @@ -328,21 +249,17 @@ python-dotenv==1.2.1 # via # pydantic-settings # webdriver-manager -python-magic==0.4.27 ; python_full_version >= '3.10' +python-magic==0.4.27 # via reuse -python-multipart==0.0.21 ; python_full_version >= '3.10' +python-multipart==0.0.21 # via mcp -pywin32==311 ; python_full_version >= '3.10' and sys_platform == 'win32' +pywin32==311 ; sys_platform == 'win32' # via # mcp # semgrep pyyaml==6.0.3 # via pre-commit -referencing==0.36.2 ; python_full_version < '3.10' - # via - # jsonschema - # jsonschema-specifications -referencing==0.37.0 ; python_full_version >= '3.10' +referencing==0.37.0 # via # jsonschema # jsonschema-specifications @@ -353,8 +270,7 @@ requests==2.32.5 # sphinx # webdriver-manager requirements-parser==0.13.0 -reuse==5.1.1 ; python_full_version < '3.10' -reuse==6.2.0 ; python_full_version >= '3.10' +reuse==6.2.0 rich==13.5.3 # via # semgrep @@ -363,30 +279,21 @@ roman-numerals==4.1.0 ; python_full_version >= '3.11' # via roman-numerals-py roman-numerals-py==4.1.0 ; python_full_version >= '3.11' # via sphinx -rpds-py==0.27.1 ; python_full_version < '3.10' - # via - # jsonschema - # referencing -rpds-py==0.30.0 ; python_full_version >= '3.10' +rpds-py==0.30.0 # via # jsonschema # referencing rstcheck==6.2.5 rstcheck-core==1.2.2 # via rstcheck -ruamel-yaml==0.18.16 +ruamel-yaml==0.19.1 + # via semgrep +ruamel-yaml-clib==0.2.14 # via semgrep -ruamel-yaml-clib==0.2.14 ; python_full_version >= '3.10' or platform_python_implementation == 'CPython' - # via - # ruamel-yaml - # semgrep ruff==0.14.10 -selenium==4.36.0 ; python_full_version < '3.10' -selenium==4.39.0 ; python_full_version >= '3.10' -semgrep==1.136.0 ; python_full_version < '3.10' -semgrep==1.146.0 ; python_full_version >= '3.10' +selenium==4.39.0 +semgrep==1.146.0 setuptools==80.9.0 - # via opentelemetry-instrumentation shellingham==1.5.4 # via typer six==1.17.0 @@ -397,11 +304,7 @@ snowballstemmer==3.0.1 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==7.4.7 ; python_full_version < '3.10' - # via - # sphinx-rtd-theme - # sphinxcontrib-jquery -sphinx==8.1.3 ; python_full_version == '3.10.*' +sphinx==8.1.3 ; python_full_version < '3.11' # via # sphinx-rtd-theme # sphinxcontrib-jquery @@ -424,9 +327,9 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sse-starlette==3.0.4 ; python_full_version >= '3.10' +sse-starlette==3.1.2 # via mcp -starlette==0.50.0 ; python_full_version >= '3.10' +starlette==0.50.0 # via # mcp # sse-starlette @@ -437,17 +340,13 @@ tomli==2.0.2 # sphinx tomlkit==0.13.3 # via reuse -trio==0.31.0 ; python_full_version < '3.10' - # via - # selenium - # trio-websocket -trio==0.32.0 ; python_full_version >= '3.10' +trio==0.32.0 # via # selenium # trio-websocket trio-websocket==0.12.2 # via selenium -typer==0.20.1 +typer==0.21.0 # via rstcheck typing-extensions==4.15.0 # via @@ -479,7 +378,7 @@ urllib3==2.6.2 # requests # selenium # semgrep -uvicorn==0.38.0 ; python_full_version >= '3.10' and sys_platform != 'emscripten' +uvicorn==0.40.0 ; sys_platform != 'emscripten' # via mcp virtualenv==20.35.4 # via pre-commit @@ -489,14 +388,8 @@ webdriver-manager==4.0.2 websocket-client==1.9.0 # via selenium wrapt==1.17.3 - # via - # deprecated - # opentelemetry-instrumentation -wsproto==1.2.0 ; python_full_version < '3.10' - # via trio-websocket -wsproto==1.3.2 ; python_full_version >= '3.10' + # via opentelemetry-instrumentation +wsproto==1.3.2 # via trio-websocket zipp==3.23.0 - # via - # importlib-metadata - # importlib-resources + # via importlib-metadata diff --git a/docker-requirements.txt b/docker-requirements.txt index b719a96f..ab2fb801 100644 --- a/docker-requirements.txt +++ b/docker-requirements.txt @@ -20,7 +20,7 @@ docker==7.1.0 # via glances exceptiongroup==1.2.2 ; python_full_version < '3.11' # via anyio -fastapi==0.125.0 +fastapi==0.128.0 # via glances h11==0.16.0 # via uvicorn @@ -36,7 +36,7 @@ packaging==25.0 # via glances podman==5.6.0 # via glances -psutil==7.1.3 +psutil==7.2.1 # via glances pydantic==2.12.5 # via fastapi @@ -57,9 +57,7 @@ six==1.17.0 # via # glances # python-dateutil -starlette==0.49.3 ; python_full_version < '3.10' - # via fastapi -starlette==0.50.0 ; python_full_version >= '3.10' +starlette==0.50.0 # via fastapi tomli==2.0.2 ; python_full_version < '3.11' # via podman @@ -79,7 +77,7 @@ urllib3==2.6.2 # docker # podman # requests -uvicorn==0.38.0 +uvicorn==0.40.0 # via glances windows-curses==2.4.1 ; sys_platform == 'win32' # via glances diff --git a/docs/api/openapi.json b/docs/api/openapi.json index 7d252361..ac06893f 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -1,923 +1 @@ -{ - "openapi": "3.0.2", - "info": { "title": "FastAPI", "version": "0.1.0" }, - "paths": { - "/api/4/status": { - "get": { - "summary": " Api Status", - "description": "Glances API RESTful implementation.\n\nReturn a 200 status code.\nThis entry point should be used to check the API health.\n\nSee related issue: Web server health check endpoint #1988", - "operationId": "_api_status_api_4_status_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - }, - "head": { - "summary": " Api Status", - "description": "Glances API RESTful implementation.\n\nReturn a 200 status code.\nThis entry point should be used to check the API health.\n\nSee related issue: Web server health check endpoint #1988", - "operationId": "_api_status_api_4_status_head", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/events/clear/warning": { - "post": { - "summary": " Events Clear Warning", - "description": "Glances API RESTful implementation.\n\nReturn a 200 status code.\n\nIt's a post message to clean warning events", - "operationId": "_events_clear_warning_api_4_events_clear_warning_post", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/events/clear/all": { - "post": { - "summary": " Events Clear All", - "description": "Glances API RESTful implementation.\n\nReturn a 200 status code.\n\nIt's a post message to clean all events", - "operationId": "_events_clear_all_api_4_events_clear_all_post", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/processes/extended/disable": { - "post": { - "summary": " Api Disable Extended Processes", - "description": "Glances API RESTful implementation.\n\nDisable extended process stats\nHTTP/200 if OK\nHTTP/400 if PID is not found\nHTTP/404 if others error", - "operationId": "_api_disable_extended_processes_api_4_processes_extended_disable_post", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/processes/extended/{pid}": { - "post": { - "summary": " Api Set Extended Processes", - "description": "Glances API RESTful implementation.\n\nSet the extended process stats for the given PID\nHTTP/200 if OK\nHTTP/400 if PID is not found\nHTTP/404 if others error", - "operationId": "_api_set_extended_processes_api_4_processes_extended__pid__post", - "parameters": [ - { - "name": "pid", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Pid" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/config": { - "get": { - "summary": " Api Config", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances configuration file\nHTTP/200 if OK\nHTTP/404 if others error", - "operationId": "_api_config_api_4_config_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/config/{section}": { - "get": { - "summary": " Api Config Section", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances configuration section\nHTTP/200 if OK\nHTTP/400 if item is not found\nHTTP/404 if others error", - "operationId": "_api_config_section_api_4_config__section__get", - "parameters": [ - { - "name": "section", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Section" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/config/{section}/{item}": { - "get": { - "summary": " Api Config Section Item", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances configuration section/item\nHTTP/200 if OK\nHTTP/400 if item is not found\nHTTP/404 if others error", - "operationId": "_api_config_section_item_api_4_config__section___item__get", - "parameters": [ - { - "name": "section", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Section" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/args": { - "get": { - "summary": " Api Args", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances command line arguments\nHTTP/200 if OK\nHTTP/404 if others error", - "operationId": "_api_args_api_4_args_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/args/{item}": { - "get": { - "summary": " Api Args Item", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances command line arguments item\nHTTP/200 if OK\nHTTP/400 if item is not found\nHTTP/404 if others error", - "operationId": "_api_args_item_api_4_args__item__get", - "parameters": [ - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/help": { - "get": { - "summary": " Api Help", - "description": "Glances API RESTful implementation.\n\nReturn the help data or 404 error.", - "operationId": "_api_help_api_4_help_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/all": { - "get": { - "summary": " Api All", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of all the plugins\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_all_api_4_all_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/all/limits": { - "get": { - "summary": " Api All Limits", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of all the plugins limits\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_all_limits_api_4_all_limits_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/all/views": { - "get": { - "summary": " Api All Views", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of all the plugins views\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_all_views_api_4_all_views_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/pluginslist": { - "get": { - "summary": " Api Plugins", - "description": "Glances API RESTFul implementation.\n\n@api {get} /api/%s/pluginslist Get plugins list\n@apiVersion 2.0\n@apiName pluginslist\n@apiGroup plugin\n\n@apiSuccess {String[]} Plugins list.\n\n@apiSuccessExample Success-Response:\n HTTP/1.1 200 OK\n [\n \"load\",\n \"help\",\n \"ip\",\n \"memswap\",\n \"processlist\",\n ...\n ]\n\n @apiError Cannot get plugin list.\n\n @apiErrorExample Error-Response:\n HTTP/1.1 404 Not Found", - "operationId": "_api_plugins_api_4_pluginslist_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/serverslist": { - "get": { - "summary": " Api Servers List", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the servers list (for browser mode)\nHTTP/200 if OK", - "operationId": "_api_servers_list_api_4_serverslist_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/processes/extended": { - "get": { - "summary": " Api Get Extended Processes", - "description": "Glances API RESTful implementation.\n\nGet the extended process stats (if set before)\nHTTP/200 if OK\nHTTP/400 if PID is not found\nHTTP/404 if others error", - "operationId": "_api_get_extended_processes_api_4_processes_extended_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - } - }, - "/api/4/processes/{pid}": { - "get": { - "summary": " Api Get Processes", - "description": "Glances API RESTful implementation.\n\nGet the process stats for the given PID\nHTTP/200 if OK\nHTTP/400 if PID is not found\nHTTP/404 if others error", - "operationId": "_api_get_processes_api_4_processes__pid__get", - "parameters": [ - { - "name": "pid", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Pid" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}": { - "get": { - "summary": " Api", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of a given plugin\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_api_4__plugin__get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/history": { - "get": { - "summary": " Api History", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of a given plugin history\nLimit to the last nb items (all if nb=0)\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_history_api_4__plugin__history_get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "nb", - "in": "query", - "required": false, - "schema": { "type": "integer", "default": 0, "title": "Nb" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/history/{nb}": { - "get": { - "summary": " Api History", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of a given plugin history\nLimit to the last nb items (all if nb=0)\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_history_api_4__plugin__history__nb__get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "nb", - "in": "path", - "required": true, - "schema": { "type": "integer", "title": "Nb" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/top/{nb}": { - "get": { - "summary": " Api Top", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of a given plugin limited to the top nb items.\nIt is used to reduce the payload of the HTTP response (example: processlist).\n\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_top_api_4__plugin__top__nb__get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "nb", - "in": "path", - "required": true, - "schema": { "type": "integer", "title": "Nb" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/limits": { - "get": { - "summary": " Api Limits", - "description": "Glances API RESTful implementation.\n\nReturn the JSON limits of a given plugin\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_limits_api_4__plugin__limits_get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/views": { - "get": { - "summary": " Api Views", - "description": "Glances API RESTful implementation.\n\nReturn the JSON views of a given plugin\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_views_api_4__plugin__views_get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}": { - "get": { - "summary": " Api Item", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/item\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_item_api_4__plugin___item__get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}/views": { - "get": { - "summary": " Api Item Views", - "description": "Glances API RESTful implementation.\n\nReturn the JSON view representation of the couple plugin/item\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_item_views_api_4__plugin___item__views_get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}/history": { - "get": { - "summary": " Api Item History", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/history of item\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_item_history_api_4__plugin___item__history_get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - }, - { - "name": "nb", - "in": "query", - "required": false, - "schema": { "type": "integer", "default": 0, "title": "Nb" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}/history/{nb}": { - "get": { - "summary": " Api Item History", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/history of item\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_item_history_api_4__plugin___item__history__nb__get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - }, - { - "name": "nb", - "in": "path", - "required": true, - "schema": { "type": "integer", "title": "Nb" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}/description": { - "get": { - "summary": " Api Item Description", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/item description\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_item_description_api_4__plugin___item__description_get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}/unit": { - "get": { - "summary": " Api Item Unit", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/item unit\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_item_unit_api_4__plugin___item__unit_get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}/value/{value}": { - "get": { - "summary": " Api Value", - "description": "Glances API RESTful implementation.\n\nReturn the process stats (dict) for the given item=value\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_value_api_4__plugin___item__value__value__get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - }, - { - "name": "value", - "in": "path", - "required": true, - "schema": { - "anyOf": [ - { "type": "string" }, - { "type": "integer" }, - { "type": "number" } - ], - "title": "Value" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}/{key}": { - "get": { - "summary": " Api Key", - "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of plugin/item/key\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_key_api_4__plugin___item___key__get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - }, - { - "name": "key", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Key" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/4/{plugin}/{item}/{key}/views": { - "get": { - "summary": " Api Key Views", - "description": "Glances API RESTful implementation.\n\nReturn the JSON view representation of plugin/item/key\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", - "operationId": "_api_key_views_api_4__plugin___item___key__views_get", - "parameters": [ - { - "name": "plugin", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Plugin" } - }, - { - "name": "item", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Item" } - }, - { - "name": "key", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Key" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/": { - "get": { - "summary": " Index", - "description": "Return main index.html (/) file.\n\nParameters are available through the request object.\nExample: http://localhost:61208/?refresh=5\n\nNote: This function is only called the first time the page is loaded.", - "operationId": "_index__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "text/html": { "schema": { "type": "string" } } } - } - } - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { "$ref": "#/components/schemas/ValidationError" }, - "type": "array", - "title": "Detail" - } - }, - "type": "object", - "title": "HTTPValidationError" - }, - "ValidationError": { - "properties": { - "loc": { - "items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] }, - "type": "array", - "title": "Location" - }, - "msg": { "type": "string", "title": "Message" }, - "type": { "type": "string", "title": "Error Type" } - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError" - } - } - } -} +{"openapi": "3.0.2", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": {"/api/4/status": {"get": {"summary": " Api Status", "description": "Glances API RESTful implementation.\n\nReturn a 200 status code.\nThis entry point should be used to check the API health.\n\nSee related issue: Web server health check endpoint #1988", "operationId": "_api_status_api_4_status_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}, "head": {"summary": " Api Status", "description": "Glances API RESTful implementation.\n\nReturn a 200 status code.\nThis entry point should be used to check the API health.\n\nSee related issue: Web server health check endpoint #1988", "operationId": "_api_status_api_4_status_head", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/events/clear/warning": {"post": {"summary": " Events Clear Warning", "description": "Glances API RESTful implementation.\n\nReturn a 200 status code.\n\nIt's a post message to clean warning events", "operationId": "_events_clear_warning_api_4_events_clear_warning_post", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/events/clear/all": {"post": {"summary": " Events Clear All", "description": "Glances API RESTful implementation.\n\nReturn a 200 status code.\n\nIt's a post message to clean all events", "operationId": "_events_clear_all_api_4_events_clear_all_post", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/processes/extended/disable": {"post": {"summary": " Api Disable Extended Processes", "description": "Glances API RESTful implementation.\n\nDisable extended process stats\nHTTP/200 if OK\nHTTP/400 if PID is not found\nHTTP/404 if others error", "operationId": "_api_disable_extended_processes_api_4_processes_extended_disable_post", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/processes/extended/{pid}": {"post": {"summary": " Api Set Extended Processes", "description": "Glances API RESTful implementation.\n\nSet the extended process stats for the given PID\nHTTP/200 if OK\nHTTP/400 if PID is not found\nHTTP/404 if others error", "operationId": "_api_set_extended_processes_api_4_processes_extended__pid__post", "parameters": [{"name": "pid", "in": "path", "required": true, "schema": {"type": "string", "title": "Pid"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/config": {"get": {"summary": " Api Config", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances configuration file\nHTTP/200 if OK\nHTTP/404 if others error", "operationId": "_api_config_api_4_config_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/config/{section}": {"get": {"summary": " Api Config Section", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances configuration section\nHTTP/200 if OK\nHTTP/400 if item is not found\nHTTP/404 if others error", "operationId": "_api_config_section_api_4_config__section__get", "parameters": [{"name": "section", "in": "path", "required": true, "schema": {"type": "string", "title": "Section"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/config/{section}/{item}": {"get": {"summary": " Api Config Section Item", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances configuration section/item\nHTTP/200 if OK\nHTTP/400 if item is not found\nHTTP/404 if others error", "operationId": "_api_config_section_item_api_4_config__section___item__get", "parameters": [{"name": "section", "in": "path", "required": true, "schema": {"type": "string", "title": "Section"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/args": {"get": {"summary": " Api Args", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances command line arguments\nHTTP/200 if OK\nHTTP/404 if others error", "operationId": "_api_args_api_4_args_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/args/{item}": {"get": {"summary": " Api Args Item", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the Glances command line arguments item\nHTTP/200 if OK\nHTTP/400 if item is not found\nHTTP/404 if others error", "operationId": "_api_args_item_api_4_args__item__get", "parameters": [{"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/help": {"get": {"summary": " Api Help", "description": "Glances API RESTful implementation.\n\nReturn the help data or 404 error.", "operationId": "_api_help_api_4_help_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/all": {"get": {"summary": " Api All", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of all the plugins\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_all_api_4_all_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/all/limits": {"get": {"summary": " Api All Limits", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of all the plugins limits\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_all_limits_api_4_all_limits_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/all/views": {"get": {"summary": " Api All Views", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of all the plugins views\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_all_views_api_4_all_views_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/pluginslist": {"get": {"summary": " Api Plugins", "description": "Glances API RESTFul implementation.\n\n@api {get} /api/%s/pluginslist Get plugins list\n@apiVersion 2.0\n@apiName pluginslist\n@apiGroup plugin\n\n@apiSuccess {String[]} Plugins list.\n\n@apiSuccessExample Success-Response:\n HTTP/1.1 200 OK\n [\n \"load\",\n \"help\",\n \"ip\",\n \"memswap\",\n \"processlist\",\n ...\n ]\n\n @apiError Cannot get plugin list.\n\n @apiErrorExample Error-Response:\n HTTP/1.1 404 Not Found", "operationId": "_api_plugins_api_4_pluginslist_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/serverslist": {"get": {"summary": " Api Servers List", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the servers list (for browser mode)\nHTTP/200 if OK", "operationId": "_api_servers_list_api_4_serverslist_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/processes/extended": {"get": {"summary": " Api Get Extended Processes", "description": "Glances API RESTful implementation.\n\nGet the extended process stats (if set before)\nHTTP/200 if OK\nHTTP/400 if PID is not found\nHTTP/404 if others error", "operationId": "_api_get_extended_processes_api_4_processes_extended_get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/api/4/processes/{pid}": {"get": {"summary": " Api Get Processes", "description": "Glances API RESTful implementation.\n\nGet the process stats for the given PID\nHTTP/200 if OK\nHTTP/400 if PID is not found\nHTTP/404 if others error", "operationId": "_api_get_processes_api_4_processes__pid__get", "parameters": [{"name": "pid", "in": "path", "required": true, "schema": {"type": "string", "title": "Pid"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}": {"get": {"summary": " Api", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of a given plugin\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_api_4__plugin__get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/history": {"get": {"summary": " Api History", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of a given plugin history\nLimit to the last nb items (all if nb=0)\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_history_api_4__plugin__history_get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "nb", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "title": "Nb"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/history/{nb}": {"get": {"summary": " Api History", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of a given plugin history\nLimit to the last nb items (all if nb=0)\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_history_api_4__plugin__history__nb__get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "nb", "in": "path", "required": true, "schema": {"type": "integer", "title": "Nb"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/top/{nb}": {"get": {"summary": " Api Top", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of a given plugin limited to the top nb items.\nIt is used to reduce the payload of the HTTP response (example: processlist).\n\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_top_api_4__plugin__top__nb__get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "nb", "in": "path", "required": true, "schema": {"type": "integer", "title": "Nb"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/limits": {"get": {"summary": " Api Limits", "description": "Glances API RESTful implementation.\n\nReturn the JSON limits of a given plugin\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_limits_api_4__plugin__limits_get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/views": {"get": {"summary": " Api Views", "description": "Glances API RESTful implementation.\n\nReturn the JSON views of a given plugin\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_views_api_4__plugin__views_get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}": {"get": {"summary": " Api Item", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/item\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_item_api_4__plugin___item__get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}/views": {"get": {"summary": " Api Item Views", "description": "Glances API RESTful implementation.\n\nReturn the JSON view representation of the couple plugin/item\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_item_views_api_4__plugin___item__views_get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}/history": {"get": {"summary": " Api Item History", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/history of item\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_item_history_api_4__plugin___item__history_get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}, {"name": "nb", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "title": "Nb"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}/history/{nb}": {"get": {"summary": " Api Item History", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/history of item\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_item_history_api_4__plugin___item__history__nb__get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}, {"name": "nb", "in": "path", "required": true, "schema": {"type": "integer", "title": "Nb"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}/description": {"get": {"summary": " Api Item Description", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/item description\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_item_description_api_4__plugin___item__description_get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}/unit": {"get": {"summary": " Api Item Unit", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of the couple plugin/item unit\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_item_unit_api_4__plugin___item__unit_get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}/value/{value}": {"get": {"summary": " Api Value", "description": "Glances API RESTful implementation.\n\nReturn the process stats (dict) for the given item=value\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_value_api_4__plugin___item__value__value__get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}, {"name": "value", "in": "path", "required": true, "schema": {"anyOf": [{"type": "string"}, {"type": "integer"}, {"type": "number"}], "title": "Value"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}/{key}": {"get": {"summary": " Api Key", "description": "Glances API RESTful implementation.\n\nReturn the JSON representation of plugin/item/key\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_key_api_4__plugin___item___key__get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}, {"name": "key", "in": "path", "required": true, "schema": {"type": "string", "title": "Key"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/api/4/{plugin}/{item}/{key}/views": {"get": {"summary": " Api Key Views", "description": "Glances API RESTful implementation.\n\nReturn the JSON view representation of plugin/item/key\nHTTP/200 if OK\nHTTP/400 if plugin is not found\nHTTP/404 if others error", "operationId": "_api_key_views_api_4__plugin___item___key__views_get", "parameters": [{"name": "plugin", "in": "path", "required": true, "schema": {"type": "string", "title": "Plugin"}}, {"name": "item", "in": "path", "required": true, "schema": {"type": "string", "title": "Item"}}, {"name": "key", "in": "path", "required": true, "schema": {"type": "string", "title": "Key"}}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/": {"get": {"summary": " Index", "description": "Return main index.html (/) file.\n\nParameters are available through the request object.\nExample: http://localhost:61208/?refresh=5\n\nNote: This function is only called the first time the page is loaded.", "operationId": "_index__get", "responses": {"200": {"description": "Successful Response", "content": {"text/html": {"schema": {"type": "string"}}}}}}}}, "components": {"schemas": {"HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}}} \ No newline at end of file diff --git a/docs/api/python.rst b/docs/api/python.rst index 398e5bb9..90ba6a1d 100644 --- a/docs/api/python.rst +++ b/docs/api/python.rst @@ -22,25 +22,25 @@ use the following code: >>> gl = api.GlancesAPI() >>> gl.cpu {'cpucore': 16, - 'ctx_switches': 9270037, + 'ctx_switches': 392968698, 'guest': 0.0, - 'idle': 92.9, - 'interrupts': 6399251, - 'iowait': 0.2, + 'idle': 93.4, + 'interrupts': 347279201, + 'iowait': 0.5, 'irq': 0.0, 'nice': 0.0, - 'soft_interrupts': 2465459, + 'soft_interrupts': 146726416, 'steal': 0.0, 'syscalls': 0, - 'system': 4.5, - 'total': 6.6, - 'user': 2.3} + 'system': 4.4, + 'total': 5.9, + 'user': 1.7} >>> gl.cpu.get("total") - 6.6 + 5.9 >>> gl.mem.get("used") - 9278827984 + 12044965912 >>> gl.auto_unit(gl.mem.get("used")) - 8.64G + 11.2G If the stats return a list of items (like network interfaces or processes), you can access them by their name: @@ -51,19 +51,19 @@ access them by their name: ['wlp0s20f3'] >>> gl.network["wlp0s20f3"] {'alias': None, - 'bytes_all': 2046, - 'bytes_all_gauge': 1072158728, - 'bytes_all_rate_per_sec': 11750.0, - 'bytes_recv': 1424, - 'bytes_recv_gauge': 1055691424, - 'bytes_recv_rate_per_sec': 8178.0, - 'bytes_sent': 622, - 'bytes_sent_gauge': 16467304, - 'bytes_sent_rate_per_sec': 3572.0, + 'bytes_all': 1149, + 'bytes_all_gauge': 14756012143, + 'bytes_all_rate_per_sec': 7603.0, + 'bytes_recv': 608, + 'bytes_recv_gauge': 13741392863, + 'bytes_recv_rate_per_sec': 4023.0, + 'bytes_sent': 541, + 'bytes_sent_gauge': 1014619280, + 'bytes_sent_rate_per_sec': 3580.0, 'interface_name': 'wlp0s20f3', 'key': 'interface_name', 'speed': 0, - 'time_since_update': 0.1741182804107666} + 'time_since_update': 0.15111541748046875} Init Glances Python API ----------------------- @@ -95,7 +95,32 @@ Alert stats: >>> type(gl.alert) >>> gl.alert - [] + [{'avg': 96.85105977159478, + 'begin': 1767373692, + 'count': 2, + 'desc': '', + 'end': -1, + 'global_msg': 'High swap (paging) usage', + 'max': 96.85105977159478, + 'min': 96.85105977159478, + 'sort': 'memory_percent', + 'state': 'CRITICAL', + 'sum': 193.70211954318955, + 'top': ['code', 'firefox', 'code'], + 'type': 'MEMSWAP'}, + {'avg': 73.31673281888682, + 'begin': 1767373692, + 'count': 2, + 'desc': '', + 'end': -1, + 'global_msg': 'High swap (paging) usage', + 'max': 73.34283346687624, + 'min': 73.2906321708974, + 'sort': 'memory_percent', + 'state': 'WARNING', + 'sum': 146.63346563777364, + 'top': [], + 'type': 'MEM'}] Alert fields description: @@ -131,12 +156,12 @@ Ports stats: >>> gl.ports [{'description': 'DefaultGateway', - 'host': '192.168.1.1', + 'host': '192.168.0.254', 'indice': 'port_0', 'port': 0, 'refresh': 30, 'rtt_warning': None, - 'status': 0.005353, + 'status': 0.002841, 'timeout': 3}] Ports fields description: @@ -177,14 +202,14 @@ Diskio stats: >>> gl.diskio.get("nvme0n1") {'disk_name': 'nvme0n1', 'key': 'disk_name', - 'read_bytes': 6307091968, - 'read_count': 212136, + 'read_bytes': 26400656896, + 'read_count': 995162, 'read_latency': 0, - 'read_time': 25497, - 'write_bytes': 5472228352, - 'write_count': 237246, + 'read_time': 249396, + 'write_bytes': 39424275456, + 'write_count': 2633670, 'write_latency': 0, - 'write_time': 197091} + 'write_time': 2045785} Diskio fields description: @@ -269,11 +294,11 @@ Processcount stats: >>> type(gl.processcount) >>> gl.processcount - {'pid_max': 0, 'running': 1, 'sleeping': 406, 'thread': 1964, 'total': 559} + {'pid_max': 0, 'running': 1, 'sleeping': 444, 'thread': 2451, 'total': 593} >>> gl.processcount.keys() ['total', 'running', 'sleeping', 'thread', 'pid_max'] >>> gl.processcount.get("total") - 559 + 593 Processcount fields description: @@ -346,7 +371,7 @@ Percpu stats: 'dpc': None, 'guest': 0.0, 'guest_nice': 0.0, - 'idle': 28.0, + 'idle': 27.0, 'interrupt': None, 'iowait': 0.0, 'irq': 0.0, @@ -354,8 +379,8 @@ Percpu stats: 'nice': 0.0, 'softirq': 0.0, 'steal': 0.0, - 'system': 4.0, - 'total': 72.0, + 'system': 8.0, + 'total': 73.0, 'user': 0.0} Percpu fields description: @@ -404,10 +429,10 @@ System stats: >>> gl.system {'hostname': 'nicolargo-xps15', - 'hr_name': 'Ubuntu 24.04 64bit / Linux 6.14.0-35-generic', + 'hr_name': 'Ubuntu 24.04 64bit / Linux 6.14.0-37-generic', 'linux_distro': 'Ubuntu 24.04', 'os_name': 'Linux', - 'os_version': '6.14.0-35-generic', + 'os_version': '6.14.0-37-generic', 'platform': '64bit'} >>> gl.system.keys() ['os_name', 'hostname', 'platform', 'os_version', 'linux_distro', 'hr_name'] @@ -446,18 +471,18 @@ Network stats: >>> gl.network.get("wlp0s20f3") {'alias': None, 'bytes_all': 0, - 'bytes_all_gauge': 1072158728, + 'bytes_all_gauge': 14756012143, 'bytes_all_rate_per_sec': 0.0, 'bytes_recv': 0, - 'bytes_recv_gauge': 1055691424, + 'bytes_recv_gauge': 13741392863, 'bytes_recv_rate_per_sec': 0.0, 'bytes_sent': 0, - 'bytes_sent_gauge': 16467304, + 'bytes_sent_gauge': 1014619280, 'bytes_sent_rate_per_sec': 0.0, 'interface_name': 'wlp0s20f3', 'key': 'interface_name', 'speed': 0, - 'time_since_update': 0.0031774044036865234} + 'time_since_update': 0.0019180774688720703} Network fields description: @@ -498,23 +523,23 @@ Cpu stats: >>> gl.cpu {'cpucore': 16, - 'ctx_switches': 9270037, + 'ctx_switches': 392968698, 'guest': 0.0, - 'idle': 92.9, - 'interrupts': 6399251, - 'iowait': 0.2, + 'idle': 93.4, + 'interrupts': 347279201, + 'iowait': 0.5, 'irq': 0.0, 'nice': 0.0, - 'soft_interrupts': 2465459, + 'soft_interrupts': 146726416, 'steal': 0.0, 'syscalls': 0, - 'system': 4.5, - 'total': 6.6, - 'user': 2.3} + 'system': 4.4, + 'total': 5.9, + 'user': 1.7} >>> gl.cpu.keys() ['total', 'user', 'nice', 'system', 'idle', 'iowait', 'irq', 'steal', 'guest', 'ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls', 'cpucore'] >>> gl.cpu.get("total") - 6.6 + 5.9 Cpu fields description: @@ -586,7 +611,7 @@ Amps stats: 'refresh': 3.0, 'regex': True, 'result': None, - 'timer': 0.1661975383758545} + 'timer': 0.18659353256225586} Amps fields description: @@ -617,38 +642,33 @@ Processlist stats: >>> gl.processlist Return a dict of dict with key= >>> gl.processlist.keys() - [30532, 129, 7722, 6446, 8756, 8852, 6423, 8840, 9543, 7548, 10383, 5247, 10239, 6971, 7838, 7562, 7550, 10672, 7715, 8639, 6711, 10143, 29150, 8081, 8853, 9153, 8794, 3018, 6207, 9171, 9629, 3613, 9374, 8333, 6244, 6219, 8711, 30172, 28756, 29409, 5453, 29262, 5991, 5363, 9409, 9408, 7371, 9406, 9407, 9402, 5677, 8641, 8642, 3031, 2669, 29063, 5812, 6722, 5815, 30529, 6689, 5605, 6684, 8156, 6124, 5724, 5575, 5375, 5826, 5878, 6763, 3025, 6242, 5409, 5412, 5355, 4271, 3609, 5386, 5437, 2776, 5394, 5189, 749, 4946, 2700, 5087, 2641, 1, 2704, 2703, 5627, 5803, 3087, 4924, 4943, 4948, 3495, 2509, 2649, 5388, 5381, 3621, 5429, 3004, 5418, 2858, 5479, 6050, 3596, 2797, 4904, 4950, 5435, 2974, 3685, 5634, 3496, 5783, 2688, 5432, 5462, 808, 5389, 5678, 2684, 2510, 2864, 2690, 2645, 5175, 2508, 5009, 5661, 5574, 5738, 5336, 5230, 2636, 5653, 5584, 5643, 5186, 5383, 5157, 8342, 2685, 4961, 5427, 5690, 2655, 5013, 5073, 2635, 5428, 5367, 8469, 4944, 2746, 9198, 9154, 5161, 2710, 5243, 2634, 2507, 4931, 2640, 3778, 6250, 8656, 6540, 30525, 2979, 2981, 3626, 3634, 3234, 3650, 5020, 3633, 3629, 3501, 30528, 2980, 2769, 3016, 3017, 3235, 3500, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 89, 90, 91, 92, 93, 95, 96, 97, 98, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 157, 164, 165, 166, 168, 169, 171, 176, 186, 188, 203, 212, 216, 218, 264, 270, 271, 272, 273, 274, 275, 276, 277, 278, 352, 355, 357, 358, 359, 360, 361, 362, 363, 364, 367, 369, 370, 447, 448, 450, 611, 616, 617, 618, 624, 651, 652, 653, 654, 657, 658, 659, 660, 661, 662, 683, 684, 738, 780, 781, 817, 1000, 1001, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1255, 1303, 1366, 1369, 1370, 1371, 1387, 1388, 1389, 1390, 1439, 1440, 1443, 1444, 1525, 1529, 1896, 1897, 1898, 1899, 1900, 1901, 1902, 1903, 1904, 1905, 1906, 1907, 1908, 1909, 1938, 1939, 1941, 1942, 1943, 1944, 1945, 1946, 1947, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, 2044, 2045, 2050, 2051, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 2061, 2063, 2064, 2066, 2068, 2071, 2072, 2075, 2322, 2323, 2326, 3558, 3559, 3560, 3561, 3562, 3660, 3706, 3707, 3708, 3709, 3710, 3711, 3712, 3713, 3949, 3956, 4080, 4725, 4859, 4877, 7686, 13970, 14398, 14514, 15908, 16227, 16410, 16721, 28674, 28676, 28726, 29096, 30133] - >>> gl.processlist.get("30532") - {'cmdline': ['/home/nicolargo/dev/glances/.venv/bin/python3', - '-m', - 'glances', - '-C', - 'conf/glances.conf', - '--api-doc'], - 'cpu_percent': 75.1, - 'cpu_times': {'children_system': 0.03, - 'children_user': 0.01, + [1, 2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 48, 49, 50, 51, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 89, 90, 91, 92, 93, 95, 96, 97, 98, 99, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 121, 122, 123, 124, 125, 126, 127, 128, 133, 135, 136, 137, 138, 139, 140, 142, 143, 144, 145, 146, 147, 148, 150, 154, 156, 157, 158, 166, 179, 188, 189, 218, 219, 239, 240, 258, 267, 268, 269, 270, 271, 273, 279, 280, 364, 367, 369, 370, 371, 372, 373, 450, 452, 613, 618, 619, 620, 627, 659, 660, 726, 757, 758, 787, 795, 970, 971, 986, 1037, 1040, 1042, 1043, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1056, 1058, 1059, 1064, 1065, 1218, 1219, 1223, 1275, 1277, 1278, 1279, 1320, 1327, 1534, 1537, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2023, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2078, 2079, 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099, 2100, 2101, 2102, 2103, 2104, 2105, 2117, 2122, 2123, 2124, 2125, 2126, 2127, 2128, 2129, 2130, 2133, 2134, 2135, 2136, 2137, 2138, 2139, 2140, 2141, 2142, 2143, 2144, 2145, 2149, 2603, 2604, 2605, 2606, 2613, 2615, 2744, 2745, 2746, 2750, 2751, 2755, 2762, 2774, 2781, 2788, 2791, 2796, 2799, 2802, 2813, 2820, 2823, 2887, 2904, 2905, 2916, 3031, 3038, 3081, 3181, 3182, 3186, 3191, 3192, 3240, 3382, 3383, 3670, 3671, 3695, 3698, 3807, 3817, 3818, 3822, 3827, 3828, 3846, 3853, 3874, 3901, 3902, 3903, 3904, 3905, 3906, 3907, 3908, 3982, 4194, 4596, 5146, 5166, 5172, 5179, 5189, 5190, 5193, 5195, 5197, 5207, 5263, 5267, 5274, 5318, 5333, 5421, 5425, 5441, 5453, 5457, 5492, 5505, 5509, 5593, 5612, 5620, 5621, 5634, 5657, 5658, 5661, 5664, 5665, 5667, 5669, 5672, 5675, 5678, 5680, 5685, 5687, 5693, 5695, 5700, 5708, 5726, 5734, 5829, 5832, 5833, 5844, 5861, 5871, 5914, 5921, 5931, 5941, 5954, 5957, 6017, 6063, 6087, 6093, 6094, 6123, 6154, 6206, 6244, 6475, 6505, 6552, 6559, 6562, 7227, 8412, 8526, 9631, 9649, 9661, 9731, 9734, 11010, 12121, 12123, 12124, 12138, 12192, 12239, 12278, 12321, 12335, 12336, 12417, 12686, 12726, 12735, 12929, 12931, 12932, 12933, 12934, 13048, 13115, 13536, 13665, 36430, 40117, 53755, 53764, 62488, 62637, 72064, 72065, 72120, 72139, 102070, 340569, 358046, 388396, 413445, 444052, 444061, 471623, 471714, 471892, 471899, 471919, 471929, 471954, 472557, 472566, 472570, 472785, 472854, 472861, 473314, 481055, 498113, 513901, 514243, 514681, 515455, 518033, 520070, 524431, 524432, 524447, 524457, 524551, 530020, 531982, 531983, 532003, 532019, 532126, 532187, 532625, 542169, 542928, 545701, 546233, 546254, 547205, 547220, 547714, 547732, 548091, 548102, 548622, 550414, 555282, 557645, 557646, 557648, 561234, 564559, 567573, 572276, 572387, 572901, 574078, 581119, 581221, 581222, 582989, 583097, 583966, 585509, 585867, 586640, 586660, 586676, 586686, 588116, 588144, 588161, 588690, 589569, 589794, 591548, 591550, 591551, 591712, 591853, 592272, 593020, 593126, 594465, 595041, 595152, 596658, 596663, 596890, 597033, 598027, 598303, 598538, 598539, 598540, 598541, 598598, 598756, 599183, 599214, 599225, 600160, 600310, 601882, 601885, 601886, 601889] + >>> gl.processlist.get("1") + {'cmdline': ['/sbin/init', 'splash'], + 'cpu_percent': 0.0, + 'cpu_times': {'children_system': 287.89, + 'children_user': 5146.36, 'iowait': 0.0, - 'system': 0.4, - 'user': 0.4}, - 'gids': {'effective': 1000, 'real': 1000, 'saved': 1000}, - 'io_counters': [20688896, 86016, 8990720, 86016, 1], + 'system': 7.96, + 'user': 11.06}, + 'gids': {'effective': 0, 'real': 0, 'saved': 0}, + 'io_counters': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'key': 'pid', - 'memory_info': {'data': 100184064, + 'memory_info': {'data': 7282688, 'dirty': 0, 'lib': 0, - 'rss': 81117184, - 'shared': 18477056, - 'text': 31211520, - 'vms': 415424512}, - 'memory_percent': 0.49392852502078194, - 'name': 'python3', + 'rss': 14622720, + 'shared': 8175616, + 'text': 45056, + 'vms': 26632192}, + 'memory_percent': 0.0890389998301525, + 'name': 'systemd', 'nice': 0, - 'num_threads': 3, - 'pid': 30532, - 'status': 'R', - 'time_since_update': 0.45221495628356934, - 'username': 'nicolargo'} + 'num_threads': 1, + 'pid': 1, + 'status': 'S', + 'time_since_update': 0.39455604553222656, + 'username': 'root'} Processlist fields description: @@ -732,13 +752,13 @@ Load stats: >>> gl.load {'cpucore': 16, - 'min1': 1.77197265625, - 'min15': 0.69287109375, - 'min5': 1.29052734375} + 'min1': 0.52685546875, + 'min15': 0.517578125, + 'min5': 0.5439453125} >>> gl.load.keys() ['min1', 'min5', 'min15', 'cpucore'] >>> gl.load.get("min1") - 1.77197265625 + 0.52685546875 Load fields description: @@ -770,14 +790,14 @@ Sensors stats: >>> gl.sensors Return a dict of dict with key=