diff --git a/.coveragerc b/.coveragerc index 9b1c27c6aa..52ed847ad3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,7 +4,6 @@ include = *glances* omit = - setup.py glances/outputs/* glances/exports/* glances/compat.py diff --git a/.dockerignore b/.dockerignore index 45e2da8a3a..f1bb98a92f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,6 +15,7 @@ # Include Config file !/docker-compose/glances.conf +!/docker-files/docker-logger.json # Include Binary file !/docker-bin.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 024a37bc25..9b5192d051 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,165 +1,81 @@ -# This pipeline aims at building Glances for the following targets: -# - Pypi -# - Docker Hub +# This pipeline aims at building Glances Pypi packages name: build -env: - DEFAULT_DOCKER_IMAGE: nicolargo/glances - NODE_ENV: ${{ (contains('refs/heads/master', github.ref) || startsWith(github.ref, 'refs/tags/v')) && 'prod' || 'dev' }} - PUSH_BRANCH: ${{ 'refs/heads/develop' == github.ref || 'refs/heads/master' == github.ref || startsWith(github.ref, 'refs/tags/v') }} - # Alpine image platform: https://hub.docker.com/_/alpine - # linux/arm/v6,linux/arm/v7 do not work (timeout during the build) - DOCKER_PLATFORMS: linux/amd64,linux/arm64/v8 - # Ubuntu image platforms list: https://hub.docker.com/_/ubuntu - # linux/arm/v7 do not work (Cargo/Rust not available) - DOCKER_PLATFORMS_UBUNTU: linux/amd64,linux/arm64/v8 - on: workflow_call: - secrets: - TEST_PYPI_API_TOKEN: - description: 'Test PyPI API token' - required: true - PYPI_API_TOKEN: - description: 'PyPI API token' - required: true - DOCKER_USERNAME: - description: 'Docker Hub username' - required: true - DOCKER_TOKEN: - description: 'Docker Hub token' - required: true jobs: - pypi: + build: + name: Build distribution 📦 + if: github.event_name == 'push' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - name: Upgrade pip - run: >- - python -m - pip install - --upgrade - pip - - - name: Install build tools + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install pypa/build run: >- - python -m + python3 -m pip install build --user - - name: Build a binary wheel and a source tarball - run: >- - python -m - build - --sdist - --wheel - --outdir dist/ - - - name: Publish distribution package to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.TEST_PYPI_API_TOKEN }} - repository-url: https://test.pypi.org/legacy/ - skip-existing: true - - - name: Publish distribution package to PyPI - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@release/v1 + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + name: python-package-distributions + path: dist/ - create_Docker_builds: - runs-on: ubuntu-latest - # Make sure we release the python package first. So we are sure to get the latest. + pypi: + name: Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags') needs: - - pypi - outputs: - tags: ${{ steps.config.outputs.tags }} - steps: - - name: Determine image tags - id: config - shell: bash - run: | - TAG_ARRAY='[' - - if [[ $GITHUB_REF == refs/tags/* ]]; then - VERSION=${GITHUB_REF#refs/tags/v} - TAG_ARRAY="$TAG_ARRAY { \"target\": \"minimal\", \"tag\": \"${VERSION}\" }," - TAG_ARRAY="$TAG_ARRAY { \"target\": \"full\", \"tag\": \"${VERSION}-full\" }," - - elif [[ $GITHUB_REF == refs/heads/develop ]]; then - TAG_ARRAY="$TAG_ARRAY { \"target\": \"dev\", \"tag\": \"dev\" }," - - else - TAG_ARRAY="$TAG_ARRAY { \"target\": \"minimal\", \"tag\": \"latest\" }," - TAG_ARRAY="$TAG_ARRAY { \"target\": \"full\", \"tag\": \"latest-full\" }," - fi - - TAG_ARRAY="${TAG_ARRAY::-1} ]" - - echo "Tags to build: $TAG_ARRAY" - echo "tags=$TAG_ARRAY" >> $GITHUB_OUTPUT - - build_Docker_image: + - build runs-on: ubuntu-latest - needs: - - create_Docker_builds - strategy: - fail-fast: false - matrix: - os: ['alpine', 'ubuntu'] - tag: ${{ fromJson(needs.create_Docker_builds.outputs.tags) }} + environment: + name: pypi + url: https://pypi.org/p/glances + permissions: + attestations: write + id-token: write steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Retrieve Repository Docker metadata - id: docker_meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.DEFAULT_DOCKER_IMAGE }} - labels: | - org.opencontainers.image.url=https://nicolargo.github.io/glances/ - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Download all the dists + uses: actions/download-artifact@v4 with: - platforms: all - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 with: - version: latest + skip-existing: true + attestations: false - - name: Login to DockerHub - uses: docker/login-action@v3 - if: ${{ env.PUSH_BRANCH == 'true' }} + pypi_test: + name: Publish Python 🐍 distribution 📦 to TestPyPI + if: github.ref == 'refs/heads/develop' + needs: + - build + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://pypi.org/p/glances + permissions: + attestations: write + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_TOKEN }} - - - name: Build and push image - uses: docker/build-push-action@v5 + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 with: - push: ${{ env.PUSH_BRANCH == 'true' }} - tags: "${{ env.DEFAULT_DOCKER_IMAGE }}:${{ matrix.os != 'alpine' && format('{0}-', matrix.os) || '' }}${{ matrix.tag.tag }}" - build-args: | - CHANGING_ARG=${{ github.sha }} - context: . - file: "docker-files/${{ matrix.os }}.Dockerfile" - platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }} - target: ${{ matrix.tag.target }} - labels: ${{ steps.docker_meta.outputs.labels }} - # GHA default behaviour overwrites last build cache. Causes alpine and ubuntu cache to overwrite each other. - # Use `scope` with the os name to prevent that - cache-from: 'type=gha,scope=${{ matrix.os }}' - cache-to: 'type=gha,mode=max,scope=${{ matrix.os }}' + repository-url: https://test.pypi.org/legacy/ + skip-existing: true + attestations: false diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml new file mode 100644 index 0000000000..d807ad38f3 --- /dev/null +++ b/.github/workflows/build_docker.yml @@ -0,0 +1,109 @@ +# This pipeline aims at building Glances Docker images + +name: build_docker + +env: + DEFAULT_DOCKER_IMAGE: nicolargo/glances + PUSH_BRANCH: ${{ 'refs/heads/develop' == github.ref || 'refs/heads/master' == github.ref || startsWith(github.ref, 'refs/tags/v') }} + # Alpine image platform: https://hub.docker.com/_/alpine + # linux/arm/v6,linux/arm/v7 do not work (timeout during the build) + DOCKER_PLATFORMS: linux/amd64,linux/arm64/v8 + # Ubuntu image platforms list: https://hub.docker.com/_/ubuntu + # linux/arm/v7 do not work (Cargo/Rust not available) + DOCKER_PLATFORMS_UBUNTU: linux/amd64,linux/arm64/v8 + +on: + workflow_call: + secrets: + DOCKER_USERNAME: + description: 'Docker Hub username' + required: true + DOCKER_TOKEN: + description: 'Docker Hub token' + required: true + +jobs: + + create_docker_images_list: + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.config.outputs.tags }} + steps: + - name: Determine image tags + id: config + shell: bash + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/v} + TAG_ARRAY="[{ \"target\": \"minimal\", \"tag\": \"${VERSION}\" }," + TAG_ARRAY="$TAG_ARRAY { \"target\": \"full\", \"tag\": \"${VERSION}-full\" }]" + elif [[ $GITHUB_REF == refs/heads/develop ]]; then + TAG_ARRAY="[{ \"target\": \"dev\", \"tag\": \"dev\" }]" + elif [[ $GITHUB_REF == refs/heads/master ]]; then + TAG_ARRAY="[{ \"target\": \"minimal\", \"tag\": \"latest\" }," + TAG_ARRAY="$TAG_ARRAY { \"target\": \"full\", \"tag\": \"latest-full\" }]" + elif [[ $GITHUB_REF == refs/heads/main ]]; then + TAG_ARRAY="[{ \"target\": \"minimal\", \"tag\": \"latest\" }," + TAG_ARRAY="$TAG_ARRAY { \"target\": \"full\", \"tag\": \"latest-full\" }]" + else + TAG_ARRAY="[]" + fi + + echo "Tags to build: $TAG_ARRAY" + echo "tags=$TAG_ARRAY" >> $GITHUB_OUTPUT + + build_docker_images: + runs-on: ubuntu-latest + needs: + - create_docker_images_list + strategy: + fail-fast: false + matrix: + os: ['alpine', 'ubuntu'] + tag: ${{ fromJson(needs.create_docker_images_list.outputs.tags) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Retrieve Repository Docker metadata + id: docker_meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.DEFAULT_DOCKER_IMAGE }} + labels: | + org.opencontainers.image.url=https://nicolargo.github.io/glances/ + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + with: + version: latest + + - name: Login to DockerHub + uses: docker/login-action@v3 + if: ${{ env.PUSH_BRANCH == 'true' }} + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v5 + with: + push: ${{ env.PUSH_BRANCH == 'true' }} + tags: "${{ env.DEFAULT_DOCKER_IMAGE }}:${{ matrix.os != 'alpine' && format('{0}-', matrix.os) || '' }}${{ matrix.tag.tag }}" + build-args: | + CHANGING_ARG=${{ github.sha }} + context: . + file: "docker-files/${{ matrix.os }}.Dockerfile" + platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }} + target: ${{ matrix.tag.target }} + labels: ${{ steps.docker_meta.outputs.labels }} + # GHA default behaviour overwrites last build cache. Causes alpine and ubuntu cache to overwrite each other. + # Use `scope` with the os name to prevent that + cache-from: 'type=gha,scope=${{ matrix.os }}' + cache-to: 'type=gha,mode=max,scope=${{ matrix.os }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21a93ee518..fe0f662474 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,20 +14,14 @@ jobs: test: uses: ./.github/workflows/test.yml needs: [quality] - webui: - if: github.event_name != 'pull_request' && !contains(github.ref, 'refs/tags/') - uses: ./.github/workflows/webui.yml - needs: [quality, test] - cyber: - if: github.event_name != 'pull_request' - uses: ./.github/workflows/cyber.yml - needs: [quality, test] build: if: github.event_name != 'pull_request' uses: ./.github/workflows/build.yml + needs: [quality, test] + build_docker: + if: github.event_name != 'pull_request' + uses: ./.github/workflows/build_docker.yml secrets: - TEST_PYPI_API_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }} - PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} needs: [quality, test] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5465ba2f38..8e3427b443 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,21 +23,21 @@ jobs: with: args: 'check' - - name: Static type check - run: | - echo "Skipping static type check for the moment, too much error..."; - # pip install pyright - # pyright glances + # - name: Static type check + # run: | + # echo "Skipping static type check for the moment, too much error..."; + # # pip install pyright + # # pyright glances test-linux: needs: source-code-checks # https://github.com/actions/runner-images?tab=readme-ov-file#available-images - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: @@ -51,46 +51,46 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f dev-requirements.txt ]; then python -m pip install -r dev-requirements.txt; fi + if [ -f requirements.txt ]; then python -m pip install -r requirements.txt; fi - name: Unitary tests run: | - python ./unittest-core.py + python -m pytest ./tests/test_core.py # Error appear with h11, not related to Glances # Should be tested if correction is done # Installed c:\hostedtoolcache\windows\python\3.9.13\x64\lib\site-packages\exceptiongroup-1.2.1-py3.9.egg # error: h11 0.14.0 is installed but h11<0.13,>=0.11 is required by {'httpcore'} # Error: Process completed with exit code 1. - # test-windows: - - # # https://github.com/actions/runner-images?tab=readme-ov-file#available-images - # runs-on: windows-2022 - # strategy: - # matrix: - # # Python version "3.12" introduce this issue: - # # https://github.com/nicolargo/glances/actions/runs/6439648370/job/17487567454 - # python-version: ["3.8", "3.9", "3.10", "3.11"] - # steps: + test-windows: + + needs: source-code-checks + # https://github.com/actions/runner-images?tab=readme-ov-file#available-images + runs-on: windows-2022 + strategy: + matrix: + # Windows-curses not available for Python 3.13 for the moment + python-version: ["3.9", "3.10", "3.11", "3.12"] + steps: - # - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - # - name: Set up Python ${{ matrix.python-version }} - # uses: actions/setup-python@v5 - # with: - # python-version: ${{ matrix.python-version }} - # cache: 'pip' + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' - # - name: Install dependencies - # run: | - # python -m pip install --upgrade pip - # if (Test-Path -PathType Leaf "requirements.txt") { python -m pip install -r requirements.txt } - # python setup.py install + - name: Install dependencies + run: | + if (Test-Path -PathType Leaf "dev-requirements.txt") { python -m pip install -r dev-requirements.txt } + if (Test-Path -PathType Leaf "requirements.txt") { python -m pip install -r requirements.txt } + pip install . - # - name: Unitary tests - # run: | - # python ./unittest-core.py + - name: Unitary tests + run: | + python -m pytest ./tests/test_core.py test-macos: @@ -100,7 +100,7 @@ jobs: strategy: matrix: # Only test the latest stable version - python-version: ["3.12"] + python-version: ["3.13"] steps: @@ -114,12 +114,12 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f dev-requirements.txt ]; then python -m pip install -r dev-requirements.txt; fi + if [ -f requirements.txt ]; then python -m pip install -r requirements.txt; fi - name: Unitary tests run: | - python ./unittest-core.py + python -m pytest ./tests/test_core.py # Error when trying to implement #2749 # pkg: No packages available to install matching 'py-pip' have been found in the repositories @@ -136,8 +136,10 @@ jobs: # with: # usesh: true # prepare: | - # pkg install -y python3 py-pip + # pkg install -y python3 # run: | # set -e -x + # python3 -m pip install pytest + # python3 -m pip install --user -r dev-requirements.txt # python3 -m pip install --user -r requirements.txt - # python ./unittest-core.py + # python3 -m pytest ./tests/test_core.py diff --git a/.gitignore b/.gitignore index aa0be710a2..a5a48d85c1 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ bower_components/ # Virtual env /venv*/ +# Test +.coverage diff --git a/Makefile b/Makefile index 1ed64e3565..df8ca307e4 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,19 @@ PORT ?= 8008 venv_full:= venv/bin -venv_dev := venv-dev/bin venv_min := venv-min/bin CONF := conf/glances.conf PIP := $(venv_full)/pip PYTHON := $(venv_full)/python +PYTEST := $(venv_full)/python -m pytest LASTTAG = $(shell git describe --tags --abbrev=0) -VENV_TYPES := full min dev +VENV_TYPES := full min VENV_PYTHON := $(VENV_TYPES:%=venv-%-python) VENV_UPG := $(VENV_TYPES:%=venv-%-upgrade) VENV_DEPS := $(VENV_TYPES:%=venv-%) VENV_INST_UPG := $(VENV_DEPS) $(VENV_UPG) -IMAGES_TYPES := full minimal dev +IMAGES_TYPES := full minimal DISTROS := alpine ubuntu alpine_images := $(IMAGES_TYPES:%=docker-alpine-%) ubuntu_images := $(IMAGES_TYPES:%=docker-ubuntu-%) @@ -30,7 +30,7 @@ DOCKER_OPTS := --rm -e TZ="${TZ}" -e GLANCES_OPT="" --pid host --network h # 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 venv-min venv-dev +.PHONY: help test docs docs-server venv venv-min help: ## List all make commands available @grep -E '^[\.a-zA-Z_%-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ @@ -53,7 +53,7 @@ endef $(foreach TYPE,$(VENV_TYPES),$(eval $(DEFINE_VARS_FOR_TYPE))) $(VENV_PYTHON): venv-%-python: - virtualenv -p /usr/bin/python3 $(if $(filter full,$*),venv,venv-$*) + virtualenv -p python3 $(if $(filter full,$*),venv,venv-$*) $(VENV_INST_UPG): venv-%: $(if $(UPGRADE),$(VIRTUAL_ENV)/pip install --upgrade pip,) @@ -66,65 +66,69 @@ venv-upgrade: $(VENV_UPG) ## Upgrade all Python 3 dependencies # For full installation (with optional dependencies) -venv-full venv-full-upgrade: REQS = requirements.txt optional-requirements.txt +venv-full venv-full-upgrade: REQS = requirements.txt optional-requirements.txt dev-requirements.txt doc-requirements.txt venv-full-python: ## Install Python 3 venv venv-full: venv-python ## Install Python 3 run-time venv-full-upgrade: ## Upgrade Python 3 run-time dependencies +venv-full: PRE_COMMIT = 1 # For minimal installation (without optional dependencies) -venv-min venv-min-upgrade: REQS = requirements.txt +venv-min venv-min-upgrade: REQS = requirements.txt dev-requirements.txt doc-requirements.txt venv-min-python: ## Install Python 3 venv minimal venv-min: venv-min-python ## Install Python 3 minimal run-time dependencies venv-min-upgrade: ## Upgrade Python 3 minimal run-time dependencies -# For development - -venv-dev venv-dev-upgrade: REQS = dev-requirements.txt doc-requirements.txt -venv-dev: PRE_COMMIT = 1 - -venv-dev-python: ## Install Python 3 venv -venv-dev: venv-python ## Install Python 3 dev dependencies -venv-dev-upgrade: ## Upgrade Python 3 dev dependencies - # =================================================================== # Tests # =================================================================== -$(UNIT_TESTS): test-%: unittest-%.py - $(PYTHON) $< +test: ## Run All unit tests + $(PYTEST) + +test-core: ## Run Core unit tests + $(PYTEST) tests/test_core.py + +test-memoryleak: ## Run Memory-leak unit tests + $(PYTEST) tests/test_memoryleak.py -test-core: ## Run core unit tests -test-restful: ## Run Restful unit tests -test-xmlrpc: ## Run XMLRPC unit tests +test-perf: ## Run Perf unit tests + $(PYTEST) tests/test_perf.py -test: $(UNIT_TESTS) ## Run unit tests +test-restful: ## Run Restful API unit tests + $(PYTEST) tests/test_restful.py -test-with-upgrade: venv-upgrade venv-dev-upgrade test ## Upgrade deps and run unit tests +test-webui: ## Run WebUI unit tests + $(PYTEST) tests/test_webui.py + +test-xmlrpc: ## Run XMLRPC API unit tests + $(PYTEST) tests/test_xmlrpc.py + +test-with-upgrade: venv-upgrade test ## Upgrade deps and run unit tests test-min: ## Run core unit tests in minimal environment - $(venv_min)/python unittest-core.py + $(venv_min)/python -m pytest tests/test_core.py test-min-with-upgrade: venv-min-upgrade ## Upgrade deps and run unit tests in minimal environment - $(venv_min)/python unittest-core.py + $(venv_min)/python -m pytest tests/test_core.py # =================================================================== # Linters, profilers and cyber security # =================================================================== format: ## Format the code - $(venv_dev)/python -m ruff format . + $(venv_full)/python -m ruff format . lint: ## Lint the code. - $(venv_dev)/python -m ruff check . --fix + $(venv_full)/python -m ruff check . --fix codespell: ## Run codespell to fix common misspellings in text files - $(venv_dev)/codespell -S .git,./docs/_build,./Glances.egg-info,./venv*,./glances/outputs,*.svg -L hart,bu,te,statics -w + $(venv_full)/codespell -S .git,./docs/_build,./Glances.egg-info,./venv*,./glances/outputs,*.svg -L hart,bu,te,statics -w semgrep: ## Run semgrep to find bugs and enforce code standards - $(venv_dev)/semgrep scan --config=auto + $(venv_full)/semgrep scan --config=auto profiling-%: SLEEP = 3 profiling-%: TIMES = 30 @@ -138,18 +142,18 @@ endef profiling-gprof: CPROF = glances.cprof profiling-gprof: ## Callgraph profiling (need "apt install graphviz") $(DISPLAY-BANNER) - $(PYTHON) -m cProfile -o $(CPROF) run.py --stop-after $(TIMES) - $(venv_dev)/gprof2dot -f pstats $(CPROF) | dot -Tsvg -o $(OUT_DIR)/glances-cgraph.svg + $(PYTHON) -m cProfile -o $(CPROF) run-venv.py -C $(CONF) --stop-after $(TIMES) + $(venv_full)/gprof2dot -f pstats $(CPROF) | dot -Tsvg -o $(OUT_DIR)/glances-cgraph.svg rm -f $(CPROF) profiling-pyinstrument: ## PyInstrument profiling $(DISPLAY-BANNER) $(PIP) install pyinstrument - $(PYTHON) -m pyinstrument -r html -o $(OUT_DIR)/glances-pyinstrument.html -m glances --stop-after $(TIMES) + $(PYTHON) -m pyinstrument -r html -o $(OUT_DIR)/glances-pyinstrument.html -m glances -C $(CONF) --stop-after $(TIMES) -profiling-pyspy: ## Flame profiling (currently not compatible with Python 3.12) +profiling-pyspy: ## Flame profiling $(DISPLAY-BANNER) - $(venv_dev)/py-spy record -o $(OUT_DIR)/glances-flame.svg -d 60 -s -- $(PYTHON) run.py --stop-after $(TIMES) + $(venv_full)/py-spy record -o $(OUT_DIR)/glances-flame.svg -d 60 -s -- $(PYTHON) run-venv.py -C $(CONF) --stop-after $(TIMES) profiling: profiling-gprof profiling-pyinstrument profiling-pyspy ## Profiling of the Glances software @@ -162,16 +166,17 @@ memory-leak: ## Profile memory leaks memory-profiling: TIMES = 2400 memory-profiling: PROFILE = mprofile_*.dat +memory-profiling: OUT_DIR = docs/_static memory-profiling: ## Profile memory usage @echo "It's a very long test (~4 hours)..." rm -f $(PROFILE) @echo "1/2 - Start memory profiling with the history option enable" - $(venv_dev)/mprof run -T 1 -C run.py -C $(CONF) --stop-after $(TIMES) --quiet - $(venv_dev)/mprof plot --output $(OUT_DIR)/glances-memory-profiling-with-history.png + $(venv_full)/mprof run -T 1 -C run-venv.py -C $(CONF) --stop-after $(TIMES) --quiet + $(venv_full)/mprof plot --output $(OUT_DIR)/glances-memory-profiling-with-history.png rm -f $(PROFILE) @echo "2/2 - Start memory profiling with the history option disable" - $(venv_dev)/mprof run -T 1 -C run.py -C $(CONF) --disable-history --stop-after $(TIMES) --quiet - $(venv_dev)/mprof plot --output $(OUT_DIR)/glances-memory-profiling-without-history.png + $(venv_full)/mprof run -T 1 -C run-venv.py -C $(CONF) --disable-history --stop-after $(TIMES) --quiet + $(venv_full)/mprof plot --output $(OUT_DIR)/glances-memory-profiling-without-history.png rm -f $(PROFILE) # Trivy installation: https://aquasecurity.github.io/trivy/latest/getting-started/installation/ @@ -206,6 +211,7 @@ install: ## Open a Web Browser to the installation procedure webui webui%: DIR = glances/outputs/static/ webui: ## Build the Web UI + $(PYTHON) -c 'import json; from glances.outputs.glances_curses import _GlancesCurses; print(json.dumps({ "leftMenu": [p for p in _GlancesCurses._left_sidebar if p != "now"]}, indent=4))' > ./glances/outputs/static/js/uiconfig.json cd $(DIR) && npm ci && npm run build webui-audit: ## Audit the Web UI @@ -218,7 +224,7 @@ webui-audit-fix: ## Fix audit the Web UI # Packaging # =================================================================== -flatpak: venv-dev-upgrade ## Generate FlatPack JSON file +flatpak: venv-upgrade ## Generate FlatPack JSON file git clone https://github.com/flatpak/flatpak-builder-tools.git $(PYTHON) ./flatpak-builder-tools/pip/flatpak-pip-generator glances rm -rf ./flatpak-builder-tools @@ -279,6 +285,9 @@ run-min-debug: ## Start minimal Glances in debug console mode (also called stand run-min-local-conf: ## Start minimal Glances in console mode with the system conf file $(venv_min)/python -m glances +run-like-htop: ## Start Glances with the same features than Htop + $(venv_min)/python -m glances --disable-plugin network,ports,wifi,connections,diskio,fs,irq,folders,raid,smart,sensors,vms,containers,ip,amps --disable-left-sidebar + $(DOCKER_RUNTIMES): run-docker-%: $(DOCKER_RUN) $(DOCKER_OPTS) $(DOCKER_SOCKS) -it glances:local-$* @@ -310,6 +319,9 @@ run-client: ## Start Glances in client mode (RPC) run-browser: ## Start Glances in browser mode (RPC) $(PYTHON) -m glances -C $(CONF) --browser +run-web-browser: ## Start Web Central Browser + $(PYTHON) -m glances -C $(CONF) -w --browser + run-issue: ## Start Glances in issue mode $(PYTHON) -m glances -C $(CONF) --issue diff --git a/NEWS.rst b/NEWS.rst index 1ce3bd9a20..3ae445ccd3 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,14 +1,251 @@ ============================================================================== Glances ChangeLog -============================================================================== +============================================================================ + +============= +Version 4.3.1 +============= + +Enhancements: + +* [WebUI] Top processes extended stats and processes filter in Web server mode #410 +* I'd like a feature to make the forground color for colored background white #3119 +* -disable-bg in ~/.config/glances.conf #3113 +* Entry point in the API to get extended process stats #3095 +* Replace netifaces by netifaces-plus dependencies #3053 +* Replace docker by containers in glances-grafana-flux.json #3118 + +Bug corrected: + +* default_config_dir: Fix config path to include glances/ directory #3106 +* Cannot set warning/critical temperature for a specific sensor needs test #3102 +* Try to reduce latency between stat's update and view - #3086 +* Error on Cloud plugin initialisation make TUI crash #3085 + +Continious integration: + +* Add Selenium to test WebUI #3044 + +Thanks to all contributors and bug reporters ! + +Special thanks to: +- Alexander Kuznetsov +- Jonathan Chemla +- mizulike + +=============== +Version 4.3.0.8 +=============== + +Bug corrected: + +* IP plugin broken with Netifaces2 #3076 +* WebUI if is notresponsive on mobile #3059 (second run) + +=============== +Version 4.3.0.7 +=============== + +Bug corrected: + +* WebUI if is notresponsive on mobile #3059 + +=============== +Version 4.3.0.6 +=============== + +Bug corrected: + +* Browser mode do not working with the sensors plugin #3069 +* netifaces is deprecated, use netifaces-plus or netifaces2 #3055 + +Continuous integration and documentation: + +* Update alpine Docker tag to v3.21 #3061 + +=============== +Version 4.3.0.5 +=============== + +Bug corrected: + +* WebUI errors in 4.3.0.4 on iPad Air (and Browser with low resolution) #3057 + +=============== +Version 4.3.0.4 +=============== + +Continuous integration and documentation: + +* Pin Python version in Ubuntu image to 3.12 + +=============== +Version 4.3.0.3 +=============== + +Continuous integration and documentation: + +* Pin Alpine image to 3.20 (3.21 is not compliant with Netifaces) Related to #3053 + +=============== +Version 4.3.0.2 +=============== + +Enhancements: + +* Revert "Replace netifaces by netifaces-plus" #3053 because it break build on Alpine Image + +=============== +Version 4.3.0.1 +=============== + +Enhancements: + +* Replace netifaces by netifaces-plus #3053 + +Bug corrected: + +* CONTAINERS section missing in 4.3.0 WebUI #3052 + +=============== +Version 4.3.0 +=============== + +Enhancements: + +* Web Based Glances Central Browser #1121 +* Ability to specify hide or show for smart plugin #2996 +* Thread mode ('j' hotkey) is not taken into accound in the WebUI #3019 +* [WEBUI] Clear old alert messages in the WebUI #3042 +* Raise an (Alert) Event for a group of sensors #3049 +* Allow processlist columns to be selected in config file #1524 +* Allow containers columns to be selected in config file #2722 +* [WebUI] Unecessary space between Processcount and processlist #3032 +* Add comparable NVML_LIB check for Windows #3000 +* Change the default path for graph export to /tmp/glances +* Improve CCS of WebUI #3024 + +Bug corrected: + +* Thresholds not displayed in the WebUI for the DiskIO plugin #1498 +* FS module alias configuration do not taken into account everytime #3010 +* Unexpected behaviour while running glances in docker with --export influxdb2 #2904 +* Correct issue when key name contains space - Related to #2983 +* Issue with ports plugin (for URL request) #3008 +* Network problem when no bitrate available #3014 +* SyntaxError: f-string: unmatched '[' in server list (on the DEVELOP branch only) #3018 +* Uptime for Docker containers not working #3021 +* WebUI doesn't display valid time for process list #2902 +* Bug In the Web-UI, Timestamps for 'Warning or critical alerts' are showing incorrect month #3023 +* Correct display issue on Containers plugin in WebUI #3028 + +Continuous integration and documentation: + +* Bumped minimal Python version to 3.9 #3005 +* Make the glances/outputs/static/js/uiconfig.json generated automaticaly from the make webui task +* Update unit-test for Glances Central Browser +* Add unit-test for new entry point in the API (plugin/item/key) +* Add a target to start Glances with Htop features +* Try new build and publish to Pypi CI actions + +Thanks to all contributors and bug reporters ! + +Special thanks to: + +* Ariel Otilibili for code quality improvements #2801 + +=============== +Version 4.2.1 +=============== + +Enhancements: + +* [WEBUI] Came back to default Black Theme / Reduce font size #2993 +* Improve hide_zero option #2958 + +Bug corrected: + +* Possible memory leak #2976 +* Docker/Podman shoud not flood log file with ERROR if containers list can not be retreived #2994 +* Using "-w" option gives error: NameError: name 'Any' is not defined #2992 +* Non blocking error message when Glances starts from a container (alpine-dev image) #2991 + +Continuous integration and documentation: + +* Migrate from setup.py to pyproject.yml #2956 +* Make pyproject.toml's version dynamic #2990 + +Thanks to all contributors and bug reporters ! + +Special thanks to: + +* @branchvincent for pyproject migration =============== Version 4.2.0 =============== -Under development, see roadmap here: https://github.com/nicolargo/glances/milestone/73 +Enhancements: + +* [WEBUI] Migration to bootstrap 5 #2914 +* New Ubuntu Multipass VM orchestartor plugin #2252 +* Show only active Disk I/O (and network interface) #2929 +* Make the central client UI configurable (example: GPU status) #1289 +* Please make py-orjson optional: it pulls in dependency on Rust #2930 +* Use defusedxml lib #2979 +* Do not display Unknown information in the cloud plugin #2485 +* Filter Docker containers - #2962 +* Add retain to availability topic in MQTT plugin #2974 +* Make fields labelled in Green easier to see #2882 + +Bug corrected: + +* In TUI, when processes are filtered, column are not aligned #2980 +* Can't kill process. Standalone, Ubuntu 24.04 #2942 +* Internal Server Error #2943 +* Timezone for warning/errors is incorrect #2901 +* Error while initializing the containers plugin ('type' object is not subscriptable) #2922 +* url_prefix do not work in Glances < 4.2.0 - Correct issue with mount #2912 +* Raid plugin breaks with inactive raid0 arrays #2908 +* Crash when terminal is resized #2872 +* Check if server name is not null in the Glances browser - Related to #2861 +* Only display VMs with a running status (in the Vms plugin) + +Continuous integration and documentation: + +* Incomplete pipx install to allow webui + containers #2955 +* Stick FastAPI version to 0.82.0 or higher (latest is better) - Related to #2926 +* api/4/vms returns a dict, thus breaking make test-restful #2918 +* Migration to Alpine 3.20 and Python 3.12 for Alpine Docker + +Improve code quality (thanks to Ariel Otilibili !): + +* Merge pull request #2959 from ariel-anieli/plugins-port-alerts +* Merge pull request #2957 from ariel-anieli/plugin-port-msg +* Merge pull request #2954 from ariel-anieli/makefile +* Merge pull request #2941 from ariel-anieli/refactor-alert +* Merge pull request #2950 from ariel-anieli/revert-commit-01823df9 +* Merge pull request #2932 from ariel-anieli/refactorize-display-plugin +* Merge pull request #2924 from ariel-anieli/makefile +* Merge pull request #2919 from ariel-anieli/refactor-plugin-model-msg-curse +* Merge pull request #2917 from ariel-anieli/makefile +* Merge pull request #2915 from ariel-anieli/refactor-process-thread +* Merge pull request #2913 from ariel-anieli/makefile +* Merge pull request #2910 from ariel-anieli/makefile +* Merge pull request #2900 from ariel-anieli/issue-2801-catch-key +* Merge pull request #2907 from ariel-anieli/refactorize-makefile +* Merge pull request #2891 from ariel-anieli/issue-2801-plugin-msg-curse +* Merge pull request #2884 from ariel-anieli/issue-2801-plugin-update + +Thanks to all contributors and bug reporters ! -Contributors are welcome ! +Special thanks to: + +* Ariel Otilibili, he has made an incredible work to improve Glances code quality ! +* RazCrimson, thanks for all your contributions ! +* Bharath Vignesh J K +* Neveda +* ey-jo =============== Version 4.1.2 @@ -16,7 +253,7 @@ Version 4.1.2 Bug corrected: -* AttributeError: 'CpuPercent' object has no attribute 'cpu_percent' #2859 +* AttributeError: 'CpuPercent' object has no attribute 'cpu_percent' #2859 =============== Version 4.1.1 @@ -24,7 +261,7 @@ Version 4.1.1 Bug corrected: -* Sensors data is not exported using InfluxDB2 exporter #2856 +* Sensors data is not exported using InfluxDB2 exporter #2856 =============== Version 4.1.0 diff --git a/README.rst b/README.rst index 7fbc43196f..6f66f78ad6 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Glances - An eye on your system =============================== | |pypi| |test| |contributors| |quality| -| |starts| |docker| |pypistat| +| |starts| |docker| |pypistat| |ossrank| | |sponsors| |twitter| .. |pypi| image:: https://img.shields.io/pypi/v/glances.svg @@ -21,6 +21,10 @@ Glances - An eye on your system :target: https://pepy.tech/project/glances :alt: Pypi downloads +.. |ossrank| image:: https://shields.io/endpoint?url=https://ossrank.com/shield/3689 + :target: https://ossrank.com/p/3689 + :alt: OSSRank + .. |test| image:: https://github.com/nicolargo/glances/actions/workflows/ci.yml/badge.svg?branch=develop :target: https://github.com/nicolargo/glances/actions :alt: Linux tests (GitHub Actions) @@ -82,17 +86,21 @@ Any and all contributions are greatly appreciated. Requirements ============ -- ``python>=3.8`` (use Glances 3.4.x for lower Python version) -- ``psutil`` (better with latest version) -- ``defusedxml`` (in order to monkey patch xmlrpc) -- ``packaging`` (for the version comparison) -- ``orjson`` (an optimized alternative to the standard json module) +Glances is developed in Python. A minimal Python version 3.9 or higher +should be installed on your system. *Note for Python 2 users* -Glances version 4 or higher do not support Python 2 (and Python 3 < 3.8). +Glances version 4 or higher do not support Python 2 (and Python 3 < 3.9). Please uses Glances version 3.4.x if you need Python 2 support. +Dependencies: + +- ``psutil`` (better with latest version) +- ``defusedxml`` (in order to monkey patch xmlrpc) +- ``packaging`` (for the version comparison) +- ``windows-curses`` (Windows Curses implementation) [Windows-only] + Optional dependencies: - ``batinfo`` (for battery monitoring) @@ -108,7 +116,7 @@ Optional dependencies: - ``influxdb-client`` (for the InfluxDB version 2 export module) - ``jinja2`` (for templating, used under the hood by FastAPI) - ``kafka-python`` (for the Kafka export module) -- ``netifaces`` (for the IP plugin) +- ``netifaces2`` (for the IP plugin) - ``nvidia-ml-py`` (for the GPU plugin) - ``pycouchdb`` (for the CouchDB export module) - ``pika`` (for the RabbitMQ/ActiveMQ export module) @@ -153,19 +161,38 @@ dependency. For example, on Debian/Ubuntu **the simplest** is 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. +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]' -For a full installation (with all features): +For a full installation (with all features, see features list bellow): .. code-block:: console pip install --user 'glances[all]' +Features list: + +- all: install dependencies for all features +- action: install dependencies for action feature +- browser: install dependencies for Glances centram browser +- cloud: install dependencies for cloud plugin +- containers: install dependencies for container plugin +- export: install dependencies for all exports modules +- gpu: install dependencies for GPU plugin +- graph: install dependencies for graph export +- ip: install dependencies for IP public option +- raid: install dependencies for RAID plugin +- sensors: install dependencies for sensors plugin +- smart: install dependencies for smart plugin +- snmp: install dependencies for SNMP +- sparklines: install dependencies for sparklines option +- web: install dependencies for Webserver (WebUI) and Web API +- wifi: install dependencies for Wifi plugin + To upgrade Glances to the latest version: .. code-block:: console @@ -195,7 +222,7 @@ The glances script will be installed in the ~/.local/bin folder. Docker: the cloudy way ---------------------- -Glances Docker images are availables. You can use it to monitor your +Glances Docker images are available. You can use it to monitor your server and all your containers ! Get the Glances container: @@ -204,7 +231,7 @@ Get the Glances container: docker pull nicolargo/glances:latest-full -The following tags are availables: +The following tags are available: - *latest-full* for a full Alpine Glances image (latest release) with all dependencies - *latest* for a basic Alpine Glances (latest release) version with minimal dependencies (FastAPI and Docker) @@ -341,9 +368,7 @@ To install Glances from source: .. code-block:: console - $ wget https://github.com/nicolargo/glances/archive/vX.Y.tar.gz -O - | tar xz - $ cd glances-* - # python setup.py install + $ pip install https://github.com/nicolargo/glances/archive/vX.Y.tar.gz *Note*: Python headers are required to install psutil. diff --git a/conf/glances-grafana-flux.json b/conf/glances-grafana-flux.json index aee7ca47dd..aff1017907 100644 --- a/conf/glances-grafana-flux.json +++ b/conf/glances-grafana-flux.json @@ -2217,10 +2217,10 @@ } ], "hide": false, - "measurement": "docker", + "measurement": "containers", "orderByTime": "ASC", "policy": "default", - "query": "from(bucket: \"glances\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"docker\" and\n r._field == \"memory_usage\" and\n r.name == \"${container}\" and\n r.hostname == \"${host}\"\n )\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"MEM\")\n \n ", + "query": "from(bucket: \"glances\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"containers\" and\n r._field == \"memory_usage\" and\n r.name == \"${container}\" and\n r.hostname == \"${host}\"\n )\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"MEM\")\n \n ", "rawQuery": false, "refId": "B", "resultFormat": "time_series", @@ -2269,10 +2269,10 @@ } ], "hide": false, - "measurement": "docker", + "measurement": "containers", "orderByTime": "ASC", "policy": "default", - "query": "from(bucket: \"glances\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"docker\" and\n r._field == \"cpu_percent\" and\n r.name == \"${container}\" and\n r.hostname == \"${host}\"\n )\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"CPU%\")\n \n ", + "query": "from(bucket: \"glances\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"containers\" and\n r._field == \"cpu_percent\" and\n r.name == \"${container}\" and\n r.hostname == \"${host}\"\n )\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"CPU%\")\n \n ", "rawQuery": false, "refId": "A", "resultFormat": "time_series", @@ -2450,4 +2450,4 @@ "title": "Glances For FLUX", "uid": "ESYAe0tnk", "version": 21 -} \ No newline at end of file +} diff --git a/conf/glances.conf b/conf/glances.conf index 597d4220da..e1f06d6b96 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -13,7 +13,7 @@ check_update=true # Default is 1200 values (~1h with the default refresh rate) history_size=1200 # Set the way Glances should display the date (default is %Y-%m-%d %H:%M:%S %Z) -# strftime_format=%Y-%m-%d %H:%M:%S %Z +#strftime_format=%Y-%m-%d %H:%M:%S %Z # Define external directory for loading additional plugins # The layout follows the glances standard for plugin definitions #plugin_dir=/home/user/dev/plugins @@ -30,9 +30,15 @@ history_size=1200 # Set the the Curses and WebUI interface left menu plugin list (comma-separated) #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now # Limit the number of processes to display (in the WebUI) -max_processes_display=25 -# Options for WebUI -#------------------ +#max_processes_display=25 +# +# Specifics options for TUI +#-------------------------- +# Disable background color +#disable_bg=True +# +# Specifics options for WebUI +#---------------------------- # Set URL prefix for the WebUI and the API # Example: url_prefix=/glances/ => http://localhost/glances/ # Note: The final / is mandatory @@ -222,6 +228,9 @@ hide_no_up=True hide_no_ip=True # Set hide_zero to True to automatically hide interface with no traffic hide_zero=False +# Set hide_threshold_bytes to an integer value to automatically hide +# interface with traffic less or equal than this value +#hide_threshold_bytes=0 # It is possible to overwrite the bitrate thresholds per interface # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate #wlan0_rx_careful=4000000 @@ -284,10 +293,22 @@ disable=False hide=loop.*,/dev/loop.* # Set hide_zero to True to automatically hide disk with no read/write hide_zero=False +# Set hide_threshold_bytes to an integer value to automatically hide +# interface with traffic less or equal than this value +#hide_threshold_bytes=0 # Define the list of disks to be show (comma-separated) #show=sda.* # Alias for sda1 and sdb1 #alias=sda1:SystemDisk,sdb1:DataDisk +# Set thresholds (in bytes per second) for a given disk name (rx = read / tx = write) +#dm-0_rx_careful=4000000000 +#dm-0_rx_warning=5000000000 +#dm-0_rx_critical=6000000000 +#dm-0_rx_log=True +#dm-0_tx_careful=700000000 +#dm-0_tx_warning=900000000 +#dm-0_tx_critical=1000000000 +#dm-0_tx_log=True [fs] disable=False @@ -305,7 +326,7 @@ critical=90 # Allow additional file system types (comma-separated FS type) #allow=shm # Alias for root file system -#alias=/:Root +#alias=/:Root,/zsfpool:ZSF [irq] # Documentation: https://glances.readthedocs.io/en/latest/aoa/irq.html @@ -348,6 +369,10 @@ disable=True # Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html # This plugin is disabled by default disable=True +# Define the list of sensors to hide (comma-separated regexp) +#hide=.*Hide_this_driver.* +# Define the list of sensors to show (comma-separated regexp) +#show=.*Drive_Temperature.* [hddtemp] disable=False @@ -366,19 +391,26 @@ hide=unknown.* # Show only the following sensors (comma separated list of regexp) #show=CPU.* # Sensors core thresholds (in Celsius...) -# Default values are grabbed from the system +# By default values are grabbed from the system +# Overwrite thresholds for a specific sensor +#temperature_core_Ambient_careful=45 +#temperature_core_Ambient_warning=65 +#temperature_core_Ambient_critical=80 +#temperature_core_Ambient_log=False +# Overwrite thresholds for a specific type of sensor #temperature_core_careful=45 #temperature_core_warning=65 #temperature_core_critical=80 # Temperatures threshold in °C for hddtemp # Default values if not defined: 45/52/60 -temperature_hdd_careful=45 -temperature_hdd_warning=52 -temperature_hdd_critical=60 +#temperature_hdd_careful=45 +#temperature_hdd_warning=52 +#temperature_hdd_critical=60 # Battery threshold in % -battery_careful=80 -battery_warning=90 -battery_critical=95 +# Default values if not defined: 70/80/90 +#battery_careful=70 +#battery_warning=80 +#battery_critical=90 # Fan speed threshold in RPM #fan_speed_careful=100 # Sensors alias @@ -395,6 +427,10 @@ disable=False # Should be one of the following: # cpu_percent, memory_percent, io_counters, name, cpu_times, username #sort_key=memory_percent +# List of stats to disable (not grabed and not display) +# Stats that can be disabled: cpu_percent,memory_info,memory_percent,username,cpu_times,num_threads,nice,status,io_counters,cmdline +# Stats that can not be disable: pid,name +#disable_stats=cpu_percent,memory_info,memory_percent,username,cpu_times,num_threads,nice,status,io_counters,cmdline # Define CPU/MEM (per process) thresholds in % # Default values if not defined: 50/70/90 cpu_careful=50 @@ -466,7 +502,7 @@ port_default_gateway=True #web_4_description=Intranet [vms] -disable=False +disable=True # Define the maximum VMs size name (default is 20 chars) max_name_size=20 # By default, Glances only display running VMs with states: 'Running', 'Starting' or 'Restarting' @@ -483,8 +519,11 @@ disable=False ; hide=telegraf # Define the maximum docker size name (default is 20 chars) max_name_size=20 -; cpu_careful=50 +# List of stats to disable (not display) +# Following stats can be disabled: name,status,uptime,cpu,mem,diskio,networkio,command +; disable_stats=diskio,networkio # Thresholds for CPU and MEM (in %) +; cpu_careful=50 ; cpu_warning=70 ; cpu_critical=90 ; mem_careful=20 @@ -518,20 +557,30 @@ disable=False ;min_interval=6 ############################################################################## -# Client/server +# Browser mode - Static servers definition ############################################################################## [serverlist] +# Define columns (comma separated list of ::()) to grab/display +# Default is: system:hr_name,load:min5,cpu:total,mem:percent +# You can also add stats with key, like sensors:value:Ambient (key is case sensitive) +#columns=system:hr_name,load:min5,cpu:total,mem:percent,memswap:percent,sensors:value:Ambient,sensors:value:Composite # Define the static servers list +# _protocol can be: rpc (default if not defined) or rest +# List is limited to 256 servers max (1 to 256) #server_1_name=localhost -#server_1_alias=My local PC -#server_1_port=61209 +#server_1_alias=Local WebUI +#server_1_port=61266 +#server_1_protocol=rest #server_2_name=localhost -#server_2_port=61235 +#server_2_alias=My local PC +#server_2_port=61209 +#server_2_protocol=rpc #server_3_name=192.168.0.17 #server_3_alias=Another PC on my network #server_3_port=61209 -#server_4_name=pasbon +#server_1_protocol=rpc +#server_4_name=notagooddefinition #server_4_port=61237 [passwords] @@ -554,7 +603,7 @@ disable=False # Configuration for the --export graph option # Set the path where the graph (.svg files) will be created # Can be overwrite by the --graph-path command line option -path=/tmp +path=/tmp/glances # It is possible to generate the graphs automatically by setting the # generate_every to a non zero value corresponding to the seconds between # two generation. Set it to 0 to disable graph auto generation. diff --git a/dev-requirements.txt b/dev-requirements.txt index b0ade5836f..88ed67536b 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,5 @@ codespell +coverage fonttools>=4.43.0 # not directly required, pinned by Snyk to avoid a vulnerability gprof2dot matplotlib @@ -8,7 +9,10 @@ pillow>=10.0.1 # not directly required, pinned by Snyk to avoid a vulnerability pre-commit py-spy pyright +pytest requirements-parser ruff -semgrep +selenium +semgrep; platform_system == 'Linux' setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability +webdriver-manager diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index f446dad6ad..f71346c651 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -14,12 +14,20 @@ services: - "./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] + - 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] +# # 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: +# file: ./secrets/glances_password diff --git a/docker-compose/glances.conf b/docker-compose/glances.conf index ff80851d0f..7b41f525a8 100755 --- a/docker-compose/glances.conf +++ b/docker-compose/glances.conf @@ -13,7 +13,7 @@ check_update=False # Default is 1200 values (~1h with the default refresh rate) history_size=1200 # Set the way Glances should display the date (default is %Y-%m-%d %H:%M:%S %Z) -#strftime_format=%Y-%m-%d %H:%M:%S %Z +# strftime_format=%Y-%m-%d %H:%M:%S %Z # Define external directory for loading additional plugins # The layout follows the glances standard for plugin definitions #plugin_dir=/home/user/dev/plugins @@ -31,8 +31,14 @@ history_size=1200 #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now # Limit the number of processes to display (in the WebUI) max_processes_display=25 -# Options for WebUI -#------------------ +# +# Specifics options for TUI +#-------------------------- +# Disable background color +#disable_bg=True +# +# Specifics options for WebUI +#---------------------------- # Set URL prefix for the WebUI and the API # Example: url_prefix=/glances/ => http://localhost/glances/ # Note: The final / is mandatory @@ -222,6 +228,9 @@ hide_no_up=True hide_no_ip=True # Set hide_zero to True to automatically hide interface with no traffic hide_zero=False +# Set hide_threshold_bytes to an integer value to automatically hide +# interface with traffic less or equal than this value +#hide_threshold_bytes=0 # It is possible to overwrite the bitrate thresholds per interface # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate #wlan0_rx_careful=4000000 @@ -284,10 +293,22 @@ disable=False hide=loop.*,/dev/loop.* # Set hide_zero to True to automatically hide disk with no read/write hide_zero=False +# Set hide_threshold_bytes to an integer value to automatically hide +# interface with traffic less or equal than this value +#hide_threshold_bytes=0 # Define the list of disks to be show (comma-separated) #show=sda.* # Alias for sda1 and sdb1 #alias=sda1:SystemDisk,sdb1:DataDisk +# Set thresholds (in bytes per second) for a given disk name (rx = read / tx = write) +#dm-0_rx_careful=4000000000 +#dm-0_rx_warning=5000000000 +#dm-0_rx_critical=6000000000 +#dm-0_rx_log=True +#dm-0_tx_careful=700000000 +#dm-0_tx_warning=900000000 +#dm-0_tx_critical=1000000000 +#dm-0_tx_log=True [fs] disable=False @@ -305,7 +326,7 @@ critical=90 # Allow additional file system types (comma-separated FS type) #allow=shm # Alias for root file system -#alias=/:Root +#alias=/:Root,/zsfpool:ZSF [irq] # Documentation: https://glances.readthedocs.io/en/latest/aoa/irq.html @@ -348,6 +369,10 @@ disable=True # Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html # This plugin is disabled by default disable=True +# Define the list of sensors to hide (comma-separated regexp) +#hide=.*Hide_this_driver.* +# Define the list of sensors to show (comma-separated regexp) +#show=.*Drive_Temperature.* [hddtemp] disable=False @@ -366,19 +391,26 @@ hide=unknown.* # Show only the following sensors (comma separated list of regexp) #show=CPU.* # Sensors core thresholds (in Celsius...) -# Default values are grabbed from the system +# By default values are grabbed from the system +# Overwrite thresholds for a specific sensor +#temperature_core_Ambient_careful=45 +#temperature_core_Ambient_warning=65 +#temperature_core_Ambient_critical=80 +#temperature_core_Ambient_log=False +# Overwrite thresholds for a specific type of sensor #temperature_core_careful=45 #temperature_core_warning=65 #temperature_core_critical=80 # Temperatures threshold in °C for hddtemp # Default values if not defined: 45/52/60 -temperature_hdd_careful=45 -temperature_hdd_warning=52 -temperature_hdd_critical=60 +#temperature_hdd_careful=45 +#temperature_hdd_warning=52 +#temperature_hdd_critical=60 # Battery threshold in % -battery_careful=80 -battery_warning=90 -battery_critical=95 +# Default values if not defined: 70/80/90 +#battery_careful=70 +#battery_warning=80 +#battery_critical=90 # Fan speed threshold in RPM #fan_speed_careful=100 # Sensors alias @@ -395,6 +427,10 @@ disable=False # Should be one of the following: # cpu_percent, memory_percent, io_counters, name, cpu_times, username #sort_key=memory_percent +# List of stats to disable (not grabed and not display) +# Stats that can be disabled: cpu_percent,memory_info,memory_percent,username,cpu_times,num_threads,nice,status,io_counters,cmdline +# Stats that can not be disable: pid,name +#disable_stats=cpu_percent,memory_info,memory_percent,username,cpu_times,num_threads,nice,status,io_counters,cmdline # Define CPU/MEM (per process) thresholds in % # Default values if not defined: 50/70/90 cpu_careful=50 @@ -469,8 +505,8 @@ port_default_gateway=False disable=True # Define the maximum VMs size name (default is 20 chars) max_name_size=20 -# By default, Glances only display running VMs -# Set the following key to True to display all VMs +# By default, Glances only display running VMs with states: 'Running', 'Starting' or 'Restarting' +# Set the following key to True to display all VMs regarding their states all=False [containers] @@ -483,8 +519,11 @@ disable=False ; hide=telegraf # Define the maximum docker size name (default is 20 chars) max_name_size=20 -; cpu_careful=50 +# List of stats to disable (not display) +# Following stats can be disabled: name,status,uptime,cpu,mem,diskio,networkio,command +; disable_stats=diskio,networkio # Thresholds for CPU and MEM (in %) +; cpu_careful=50 ; cpu_warning=70 ; cpu_critical=90 ; mem_careful=20 @@ -518,20 +557,30 @@ disable=False ;min_interval=6 ############################################################################## -# Client/server +# Browser mode - Static servers definition ############################################################################## [serverlist] +# Define columns (comma separated list of ::()) to grab/display +# Default is: system:hr_name,load:min5,cpu:total,mem:percent +# You can also add stats with key, like sensors:value:Ambient (key is case sensitive) +#columns=system:hr_name,load:min5,cpu:total,mem:percent,memswap:percent,sensors:value:Ambient,sensors:value:Composite # Define the static servers list +# _protocol can be: rpc (default if not defined) or rest +# List is limited to 256 servers max (1 to 256) #server_1_name=localhost -#server_1_alias=My local PC -#server_1_port=61209 +#server_1_alias=Local WebUI +#server_1_port=61266 +#server_1_protocol=rest #server_2_name=localhost -#server_2_port=61235 +#server_2_alias=My local PC +#server_2_port=61209 +#server_2_protocol=rpc #server_3_name=192.168.0.17 #server_3_alias=Another PC on my network #server_3_port=61209 -#server_4_name=pasbon +#server_1_protocol=rpc +#server_4_name=notagooddefinition #server_4_port=61237 [passwords] @@ -554,7 +603,7 @@ disable=False # Configuration for the --export graph option # Set the path where the graph (.svg files) will be created # Can be overwrite by the --graph-path command line option -path=/tmp +path=/tmp/glances # It is possible to generate the graphs automatically by setting the # generate_every to a non zero value corresponding to the seconds between # two generation. Set it to 0 to disable graph auto generation. diff --git a/docker-files/alpine.Dockerfile b/docker-files/alpine.Dockerfile index 2b2bfe66f5..2c1413ca39 100644 --- a/docker-files/alpine.Dockerfile +++ b/docker-files/alpine.Dockerfile @@ -9,8 +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.20 +ARG IMAGE_VERSION=3.21 ARG PYTHON_VERSION=3.12 ############################################################################## @@ -56,7 +55,8 @@ RUN apk add --no-cache \ pkgconfig \ libffi-dev \ openssl-dev \ - cmake # Issue: https://github.com/nicolargo/glances/issues/2735 + cmake + # for cmake: Issue: https://github.com/nicolargo/glances/issues/2735 RUN python${PYTHON_VERSION} -m venv venv-build RUN /venv-build/bin/python${PYTHON_VERSION} -m pip install --upgrade pip @@ -138,6 +138,10 @@ COPY --from=buildFull /venv /venv # RELEASE: dev - to be compatible with CI FROM full as dev -# Forward access and error logs to Docker's log collector -RUN ln -sf /dev/stdout /tmp/glances-root.log \ - && ln -sf /dev/stderr /var/log/error.log \ No newline at end of file +# Add the specific logger configuration file for Docker dev +# All logs will be forwarded to stdout +COPY ./docker-files/docker-logger.json /app +ENV LOG_CFG=/app/docker-logger.json + +WORKDIR /app +CMD /venv/bin/python3 -m glances $GLANCES_OPT \ No newline at end of file diff --git a/docker-files/docker-logger.json b/docker-files/docker-logger.json new file mode 100644 index 0000000000..d3cccedaed --- /dev/null +++ b/docker-files/docker-logger.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "disable_existing_loggers": "False", + "root": {"level": "INFO", "handlers": ["console"]}, + "formatters": { + "standard": {"format": "%(asctime)s -- %(levelname)s -- %(message)s"}, + "short": {"format": "%(levelname)s -- %(message)s"}, + "long": {"format": "%(asctime)s -- %(levelname)s -- %(message)s (%(funcName)s in %(filename)s)"}, + "free": {"format": "%(message)s"} + }, + "handlers": { + "console": {"class": "logging.StreamHandler", "formatter": "standard"} + }, + "loggers": { + "debug": {"handlers": ["console"], "level": "DEBUG"}, + "verbose": {"handlers": ["console"], "level": "INFO"}, + "standard": {"handlers": ["console"], "level": "INFO"}, + "requests": {"handlers": ["console"], "level": "ERROR"}, + "elasticsearch": {"handlers": ["console"], "level": "ERROR"}, + "elasticsearch.trace": {"handlers": ["console"], "level": "ERROR"} + } +} \ No newline at end of file diff --git a/docker-files/ubuntu.Dockerfile b/docker-files/ubuntu.Dockerfile index 3a5ccc4c63..93320faa81 100644 --- a/docker-files/ubuntu.Dockerfile +++ b/docker-files/ubuntu.Dockerfile @@ -128,6 +128,10 @@ COPY --from=buildFull /venv /venv FROM full as dev ARG PYTHON_VERSION -# Forward access and error logs to Docker's log collector -RUN ln -sf /dev/stdout /tmp/glances-root.log \ - && ln -sf /dev/stderr /var/log/error.log +# Add the specific logger configuration file for Docker dev +# All logs will be forwarded to stdout +COPY ./docker-files/docker-logger.json /app +ENV LOG_CFG=/app/docker-logger.json + +WORKDIR /app +CMD /venv/bin/python3 -m glances $GLANCES_OPT \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index 637331bd23..75bae57f3a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = ../venv-dev/bin/sphinx-build +SPHINXBUILD = ../venv/bin/sphinx-build PAPER = BUILDDIR = _build diff --git a/docs/_static/glances-cgraph.svg b/docs/_static/glances-cgraph.svg index 2dbf95b0e1..d1735d9db4 100644 --- a/docs/_static/glances-cgraph.svg +++ b/docs/_static/glances-cgraph.svg @@ -4,1452 +4,1384 @@ - + %3 - - + + -13 - - -~:0:<method 'acquire' of '_thread.lock' objects> -7.90% -(0.11%) -6768× +26 + + +model:1121:wrapper +10.40% +(0.01%) +684× - + -42 - - -__init__:1460:process_iter -7.14% -(0.03%) -6971× +2393 + + +glances_curses:275:__catch_key +88.72% +(0.01%) +538× - + -13->42 - - -0.15% -362× +26->2393 + + +2.13% +14× - + + +3464 + + +glances_curses:244:get_key +88.71% +(0.00%) +538× + + + + + +2393->3464 + + +0.76% + + + -15 - - -threading:973:start -3.09% -(0.01%) -907× +30 + + +subprocess:2062:_communicate +5.34% +(0.00%) +34× - + -989 - - -standalone:138:__serve_once -62.18% -(0.00%) -31× +169 + + +selectors:402:select +5.33% +(0.00%) +68× - + -15->989 - - -43.99% - +30->169 + + +5.33% +68× - - -366 - - -stats:268:update -33.63% -(0.01%) -31× + + +1727 + + +~:0:<method 'poll' of 'select.poll' objects> +5.33% +(5.53%) +98× - - -989->366 - - -0.61% - + + +169->1727 + + +5.33% +68× - + -16 - - -_pslinux:1711:wrapper -3.96% -(0.19%) -124176× +31 + + +client:1082:_send_output +4.56% +(0.00%) +34× - + -18 - - -glances_curses:361:__catch_key -100.00% -(0.01%) -562× +136 + + +~:0:<method 'acquire' of '_thread.lock' objects> +3.04% +(0.00%) +575× - + -2002 - - -glances_curses:356:get_key -99.99% -(0.01%) -562× +138 + + +<frozen importlib:1304:_find_and_load_unlocked +0.54% +(0.00%) +656× - - -18->2002 - - -0.35% - - - - -463 - - -client:345:_stream_helper -51.19% -(0.00%) -129× - - - - - -2002->463 - - -0.79% - - - - -3587 - - -~:0:<method 'getch' of '_curses.window' objects> -47.10% -(78.05%) -561× + + +410 + + +<frozen importlib:911:_load_unlocked +0.54% +(0.00%) +628× - - -2002->3587 - - -0.35% - + + +138->410 + + +0.54% +40× - - -19 - - -model:1165:wrapper -6.36% -(0.01%) -684× + + +2071 + + +<frozen importlib:989:exec_module +0.54% +(0.00%) +579× - - -19->15 - - -0.13% -44× + + +410->2071 + + +0.54% +40× - + -21 - - -connectionpool:598:urlopen -3.23% -(0.00%) -125× +143 + + +threading:1056:_bootstrap_inner +6.86% +(0.00%) +40× - + -22 - - -__init__:518:msg_curse -0.84% -(0.02%) -30× +167 + + +__init__:2281:sensors_temperatures +2.56% +(0.00%) +10× - + -331 - - -__init__:423:get_process_curses_data -0.86% -(0.27%) -13042× +174 + + +_pslinux:1376:sensors_temperatures +2.56% +(0.00%) +10× - - -22->331 - - -0.19% -3016× - - - -22->463 - - -0.77% - + + +167->174 + + +0.21% + - - -23 - - -json_stream:10:stream_as_text -50.47% -(0.00%) -65× + + +1834 + + +threading:1115:join +3.05% +(0.00%) +36× - + + +167->1834 + + +0.35% + + + -24 - - -api:62:get -2.62% -(0.00%) - +1623 + + +_common:849:bcat +2.34% +(0.02%) +25517× - - -25 - - -model:1183:wrapper -6.39% -(0.02%) -650× + + +174->1623 + + +0.20% +166× + + + +780 + + +_common:832:cat +2.42% +(0.04%) +26337× - - -25->42 - - -0.47% -165× + + +1623->780 + + +0.28% +2424× - - -78 - - -threading:1115:join -6.08% -(0.00%) -900× + + +175 + + +_pslinux:1494:sensors_fans +0.99% +(0.00%) +10× - - -25->78 - - -0.35% -26× + + +210 + + +__init__:471:get_process_curses_data +2.38% +(0.56%) +30024× + - + + -31 - - -_pslinux:2362:gids -0.92% -(0.03%) -6956× +218 + + +battery:106:update +0.72% +(0.00%) +10× - + -34 - - -__init__:322:__init__ -1.16% -(0.01%) -6962× +235 + + +thread:53:run +2.36% +(0.00%) +36× - + -36 - - -threading:637:wait -2.85% -(0.02%) -906× +236 + + +multipass:92:update +5.38% +(0.00%) +17× - - -36->42 - - -0.14% -62× - - + -44 - - -glances_curses:1166:update -34.06% -(0.01%) -31× +359 + + +multipass:48:update_info +3.06% +(0.00%) +17× - - -36->44 - - -1.69% - + + +236->359 + + +3.06% +17× - + -105 - - -~:0:<method 'read' of '_io.BufferedReader' objects> -1.99% -(2.96%) -26973× +361 + + +multipass:34:update_version +2.32% +(0.00%) +17× - - -36->105 - - -0.19% - - - - -44->18 - - -0.35% - + + +236->361 + + +2.32% +17× - - -2402 - - -glances_curses:1152:flush -0.97% -(0.02%) -30× + + +668 + + +secure:17:secure_popen +5.38% +(0.00%) +34× - + -44->2402 - - -0.20% - +359->668 + + +3.06% +17× + + + +361->668 + + +2.32% +17× - + -41 - - -threading:323:wait -2.45% -(0.12%) -906× +238 + + +__init__:141:__get_sensor_data +2.36% +(0.00%) +36× - - -41->18 - - -6.07% - - - + -64 - - -_common:838:bcat -3.50% -(0.04%) -20110× - - - - - -41->64 - - -0.17% -20× - - - -77 - - -_common:821:cat -2.04% -(0.07%) -21504× +239 + + +__init__:352:__fetch_psutil +2.60% +(0.00%) +20× - - -64->77 - - -0.23% -181× + + +239->167 + + +0.21% + - + -43 - - -__init__:644:name -0.82% -(0.02%) -6955× +263 + + +threading:1016:_bootstrap +8.16% +(0.00%) +40× - - -2020 - - -glances_curses:616:display -0.95% -(0.00%) -30× + + +270 + + +threading:999:run +2.36% +(0.00%) +36× - - -2402->2020 - - -0.19% - - - + -46 - - -_pslinux:1766:_parse_stat_file -0.86% -(0.08%) -13917× +286 + + +__init__:152:update +5.38% +(0.00%) +17× - + + +286->236 + + +5.38% +17× + + -47 - - -_pslinux:1847:cmdline -0.64% -(0.03%) -4180× +287 + + +__init__:507:msg_curse +2.44% +(0.02%) +60× - + + +287->210 + + +2.38% +30024× + + -49 - - -client:463:read -50.06% -(0.00%) -131× +288 + + +__init__:167:update +2.36% +(0.00%) + - - -49->18 - - -0.70% - - - + -51 - - -response:790:_fp_read -50.06% -(0.00%) -130× +303 + + +subprocess:1165:communicate +5.36% +(0.00%) +37× - + + +303->30 + + +5.34% +34× + + -52 - - -response:899:read -50.21% -(0.00%) -129× +321 + + +<frozen importlib:1349:_find_and_load +0.55% +(0.00%) +659× - - -52->18 - - -0.70% - + + +321->138 + + +0.54% +39× - + -56 - - -glances_batpercent:61:update -1.17% -(0.00%) - +349 + + +sessions:673:send +4.66% +(0.00%) +34× - - -57 - - -sessions:593:get -0.79% -(0.00%) -122× + + +655 + + +secure:33:__secure_popen +5.38% +(0.00%) +34× - - -58 - - -stats:262:__update_plugin -7.15% -(0.01%) -868× + + +668->655 + + +5.38% +34× + + + +1243 + + +<frozen importlib:480:_call_with_frames_removed +0.53% +(0.00%) +1377× - - -58->42 - - -0.16% -418× + + +2071->1243 + + +0.53% +40× - + -59 - - -client:537:_read_next_chunk_size -37.88% -(0.02%) -66× +433 + + +__init__:1491:process_iter +1.73% +(0.02%) +9545× - - -59->18 - - -1.11% -13× - - + -60 - - -client:1320:endheaders -2.73% -(0.00%) -126× +440 + + +stats:256:__update_plugin +11.07% +(0.01%) +899× - + -63 - - -_common:787:open_binary -1.56% -(0.08%) -41863× +511 + + +sessions:500:request +4.71% +(0.00%) +34× - - -77->105 - - -0.22% -85× - - - -69 - - -_pslinux:1943:create_time -1.07% -(0.02%) -6962× + + +561 + + +glances_batpercent:98:update +0.67% +(0.00%) + - - -72 - - -__init__:746:create_time -1.10% -(0.01%) -6962× + + +564 + + +model:1139:wrapper +10.46% +(0.02%) +449× - - -73 - - -socket:693:readinto -3.13% -(0.04%) -186× - + + +564->286 + + +5.38% +17× + + +564->288 + + +2.36% + - - -74 - - -__init__:600:is_running -0.77% -(0.02%) -6516× + + +618 + + +connection:27:create_connection +4.56% +(0.00%) + - - -74->42 - - -0.13% -409× + + +655->303 + + +5.34% +34× - - -75 - - -_common:487:wrapper -2.88% -(0.11%) -71302× + + +662 + + +thread:69:_worker +2.36% +(0.00%) +36× - + -82 +1795 - -~:0:<built-in method _io.open> -1.02% -(0.55%) -47975× + +~:0:<method 'read' of '_io.BufferedReader' objects> +2.21% +(2.46%) +35321× - + + +780->1795 + + +0.31% +2334× + + -86 - - -images:317:get -0.66% -(0.00%) -60× +794 + + +__init__:143:main +96.39% +(0.00%) + - + -88 - - -client:591:_read_chunked -50.05% -(0.01%) -129× +2109 + + +__init__:77:start +96.37% +(0.00%) + - + -88->989 - - -10.03% -26× - - +794->2109 + + +96.37% + + + + +2109->321 + + +0.16% + + + -89 - - -client:1082:_send_output -2.72% -(0.01%) -126× +801 + + +connectionpool:592:urlopen +4.64% +(0.00%) +34× - + -92 - - -response:847:_raw_read -50.07% -(0.01%) -130× - - - - - -93 - - -connectionpool:380:_make_request -3.20% -(0.00%) -125× +817 + + +client:1320:endheaders +4.56% +(0.00%) +34× - + -97 - - -client:567:_get_chunk_left -41.95% -(0.00%) -130× +1276 + + +~:0:<built-in method builtins.exec> +96.48% +(0.03%) +826× - + + +1243->1276 + + +0.53% +40× + + -98 - - -__init__:717:cmdline -1.01% -(0.00%) -4180× +1653 + + +run-venv:1:<module> +96.48% +(0.00%) + - + + +1276->1653 + + +96.48% + + + + +1653->794 + + +96.39% + + + -106 +1287 - -__init__:325:_init -1.14% -(0.04%) -6962× + +__init__:545:as_dict +1.65% +(0.14%) +9531× - + -118 - - -~:0:<method 'recv_into' of '_socket.socket' objects> -3.12% -(4.87%) -185× +2290 + + +_common:498:wrapper +0.82% +(0.08%) +98106× - + + +1287->2290 + + +0.28% +19055× + + -122 - - -~:0:<method 'readline' of '_io.BufferedReader' objects> -3.20% -(0.06%) -8584× +1301 + + +stats:95:_load_plugin +0.62% +(0.00%) +34× - + -123 - - -~:0:<method 'read' of '_io.TextIOWrapper' objects> -0.64% -(0.41%) -5634× +1362 + + +client:1027:send +4.56% +(0.00%) +34× - + -124 - - -~:0:<built-in method time.sleep> -2.61% -(4.72%) -66× +1377 + + +model:873:get_stats_display +2.50% +(0.00%) +1020× - + + +1377->287 + + +2.44% +60× + + -133 - - -client:1027:send -2.72% -(0.04%) -126× +1447 + + +adapters:613:send +4.65% +(0.00%) +34× - + -136 - - -sessions:673:send -3.31% -(0.01%) -125× +1464 + + +api:14:request +4.57% +(0.00%) + - + -143 - - -__init__:424:get -2.88% -(0.00%) -16× +1597 + + +glances_curses:476:__get_stat_display +2.50% +(0.00%) +30× - + + +1597->1377 + + +2.49% +990× + + -144 - - -decorators:38:inner -0.94% -(0.00%) -122× +1608 + + +glances_curses:1093:update +90.24% +(0.02%) +30× - + + +1608->2393 + + +0.76% + + + -145 - - -client:234:_get -0.85% -(0.00%) -122× +2400 + + +glances_curses:1079:flush +2.63% +(0.02%) +30× + + + + + +1608->2400 + + +2.63% +30× + + + +2385 + + +glances_curses:511:display +2.60% +(0.00%) +30× - + + +2400->2385 + + +2.60% +30× + + -146 - - -decorators:9:wrapped -0.84% -(0.00%) -91× +1609 + + +stats:262:update +11.06% +(0.00%) +31× - + + +1609->2393 + + +1.52% +10× + + -151 - - -_pslinux:1954:memory_info -0.59% -(0.07%) -6955× +1689 + + +~:0:<function socket.close at 0x7ec66a36d3a0> +4.56% +(0.00%) +12× - + -152 - - -_pslinux:1800:_read_status_file -0.80% -(0.02%) -6959× +1690 + + +socket:496:_real_close +4.56% +(0.00%) +12× - + -163 - - -__init__:1093:memory_info -0.64% -(0.01%) -6955× +1695 + + +threading:1153:_wait_for_tstate_lock +3.04% +(0.00%) +37× - + -165 - - -threading:1153:_wait_for_tstate_lock -6.08% -(0.00%) -901× +1702 + + +connection:369:request +4.57% +(0.00%) +34× - + -267 - - -__init__:523:as_dict -4.23% -(0.21%) -6958× +1832 + + +__init__:367:update +2.35% +(0.00%) +18× - + -278 +1849 - -_pslinux:1492:sensors_fans -1.93% -(0.00%) -17× + +_pslinux:1713:wrapper +1.30% +(0.14%) +170567× - + -295 - - -processes:399:update -3.13% -(0.03%) -16× +1850 + + +__init__:77:update +2.19% +(0.00%) +17× - + -304 - - -connection:324:request -2.77% -(0.00%) -125× +1891 + + +processes:539:update +2.19% +(0.01%) +17× - + -305 - - -__init__:215:update -0.53% -(0.00%) -30× +1944 + + +thread:219:shutdown +2.36% +(0.00%) + - + -307 - - -adapters:434:send -3.27% -(0.00%) -125× +1945 + + +glances_batpercent:62:update +0.67% +(0.00%) + - + -326 - - -__init__:385:build_sensors_list -2.47% -(0.01%) -32× +1998 + + +battery:35:__init__ +0.72% +(0.00%) +10× - - -326->64 - - -0.33% - - - + -332 - - -__init__:165:update -2.48% -(0.00%) - +2019 + + +connectionpool:377:_make_request +4.63% +(0.00%) +34× - + -333 - - -battery:106:update -1.22% -(0.00%) - +2280 + + +socket:500:close +4.56% +(0.00%) +12× - + -347 - - -__init__:124:__get_fan_speed -2.41% -(0.00%) - +2282 + + +processes:427:build_process_list +2.01% +(0.01%) +17× - + -348 - - -__init__:140:__get_bat_percent -1.18% -(0.00%) - +2298 + + +standalone:136:__serve_once +100.00% +(0.00%) +30× - + + +2298->1609 + + +5.18% +15× + + -349 - - -__init__:369:__update__ -2.47% -(0.00%) -16× +2300 + + +__init__:2321:sensors_fans +0.99% +(0.00%) +10× - + -350 - - -containers:33:image -0.67% -(0.00%) -60× +2380 + + +standalone:174:serve_n +96.21% +(0.00%) + - - -351 - - -__init__:78:update -3.13% -(0.00%) -16× - - + + +2385->1597 + + +2.50% +30× - - -352 - - -image:234:inspect_image -0.64% -(0.00%) -60× + + +3974 + + +~:0:<method 'getch' of '_curses.window' objects> +88.71% +(84.72%) +538× - + + +3464->3974 + + +0.76% + + + -358 - - -sessions:502:request -3.33% -(0.00%) -125× +2394 + + +standalone:31:__init__ +0.64% +(0.00%) + - - -366->18 - - -19.26% -20× + + +2481 + + +connection:192:_new_conn +4.56% +(0.00%) + + + - + -378 - - -__init__:2327:sensors_fans -2.50% -(0.00%) -17× +2486 + + +connection:275:connect +4.56% +(0.00%) + - + -379 - - -__init__:1483:add -2.64% -(0.00%) -441× +2594 + + +_base:646:__exit__ +2.36% +(0.00%) + - + -382 - - -__init__:776:gids -1.03% -(0.01%) -6956× +2612 + + +__init__:153:run +4.57% +(0.00%) + - + -409 - - -__init__:116:__get_temperature -2.26% -(0.00%) - - - - - - -745 - - -connection:27:create_connection -2.62% -(0.00%) - +3367 + + +api:62:get +4.57% +(0.00%) + - + -998 - - -__init__:154:run -2.62% -(0.00%) - - - - - - -1000 - - -__init__:160:update -1.07% -(0.00%) -30× - - - - - -1450 - - -model:916:get_stats_display -0.89% -(0.00%) -960× - - - - - -1450->22 - - -0.17% - - - - -1534 - - -api:14:request -2.62% -(0.00%) - - - - - - -1640 - - -glances_batpercent:97:update -1.17% -(0.00%) - - - - - - -1647 - - -json_stream:51:split_buffer -50.52% -(0.00%) -65× - - - - - -1678 - - -glances_curses:581:__get_stat_display -0.89% -(0.00%) -30× - - - - - -1678->1450 - - -0.18% -198× - - - -1772 - - -~:0:<method 'connect' of '_socket.socket' objects> -2.62% -(0.13%) -17× - - - - - -2020->1678 - - -0.18% - - - - -2485 - - -connection:192:_new_conn -2.62% -(0.00%) - - - - - - -2493 - - -connection:237:connect -2.62% -(0.00%) - +4248 + + +standalone:181:serve_forever +96.21% +(0.00%) + diff --git a/docs/_static/glances-flame.svg b/docs/_static/glances-flame.svg index 21dea0df16..3ea049462e 100644 --- a/docs/_static/glances-flame.svg +++ b/docs/_static/glances-flame.svg @@ -1,5 +1,5 @@ - \ No newline at end of file diff --git a/docs/_static/glances-memory-profiling-with-history.png b/docs/_static/glances-memory-profiling-with-history.png index 698b939cca..f2d8b65975 100644 Binary files a/docs/_static/glances-memory-profiling-with-history.png and b/docs/_static/glances-memory-profiling-with-history.png differ diff --git a/docs/_static/glances-memory-profiling-without-history.png b/docs/_static/glances-memory-profiling-without-history.png index a2751e9683..ab39aefe82 100644 Binary files a/docs/_static/glances-memory-profiling-without-history.png and b/docs/_static/glances-memory-profiling-without-history.png differ diff --git a/docs/_static/glances-pyinstrument.html b/docs/_static/glances-pyinstrument.html index 0b47ad29b9..a0e2ff0b17 100644 --- a/docs/_static/glances-pyinstrument.html +++ b/docs/_static/glances-pyinstrument.html @@ -6,15 +6,26 @@
- - diff --git a/docs/aoa/containers.rst b/docs/aoa/containers.rst index 591251ccae..2fd97f186d 100644 --- a/docs/aoa/containers.rst +++ b/docs/aoa/containers.rst @@ -31,6 +31,9 @@ under the ``[containers]`` section: #show=showthisone,andthose.* # Define the maximum containers size name (default is 20 chars) max_name_size=20 + # List of stats to disable (not display) + # Following stats can be disabled: name,status,uptime,cpu,mem,diskio,networkio,command + disable_stats=diskio,networkio # Global containers' thresholds for CPU and MEM (in %) cpu_careful=50 cpu_warning=70 diff --git a/docs/aoa/diskio.rst b/docs/aoa/diskio.rst index 0c037a8b31..af8b2df081 100644 --- a/docs/aoa/diskio.rst +++ b/docs/aoa/diskio.rst @@ -42,12 +42,31 @@ Filtering is based on regular expression. Please be sure that your regular expression works as expected. You can use an online tool like `regex101`_ in order to test your regular expression. +It is also possible to define thesholds for bytes read and write per second: + +.. code-block:: ini + + [diskio] + # Alias for sda1 and sdb1 + #alias=sda1:SystemDisk,sdb1:DataDisk + # Set thresholds (in bytes per second) for a given disk name (rx = read / tx = write) + dm-0_rx_careful=4000000000 + dm-0_rx_warning=5000000000 + dm-0_rx_critical=6000000000 + dm-0_rx_log=True + dm-0_tx_careful=700000000 + dm-0_tx_warning=900000000 + dm-0_tx_critical=1000000000 + dm-0_tx_log=True + You also can automatically hide disk with no read or write using the -``hide_zero`` configuration key. +``hide_zero`` configuration key. The optional ``hide_threshold_bytes`` option +can also be used to set a threshold higher than zero. .. code-block:: ini [diskio] hide_zero=True + hide_threshold_bytes=0 .. _regex101: https://regex101.com/ \ No newline at end of file diff --git a/docs/aoa/network.rst b/docs/aoa/network.rst index 671913ffc5..72b81642d7 100644 --- a/docs/aoa/network.rst +++ b/docs/aoa/network.rst @@ -66,12 +66,14 @@ Filtering is based on regular expression. Please be sure that your regular expression works as expected. You can use an online tool like `regex101`_ in order to test your regular expression. -You also can automatically hide intercae with no traffic using the -``hide_zero`` configuration key. +You also can automatically hide interface with no traffic using the +``hide_zero`` configuration key. The optional ``hide_threshold_bytes`` option +can also be used to set a threshold higher than zero. .. code-block:: ini [diskio] hide_zero=True + hide_threshold_bytes=0 .. _regex101: https://regex101.com/ diff --git a/docs/aoa/ps.rst b/docs/aoa/ps.rst index e0a57eecac..a7b603eba8 100644 --- a/docs/aoa/ps.rst +++ b/docs/aoa/ps.rst @@ -123,6 +123,7 @@ Columns display The non-swapped physical memory a process is using (what's currently in the physical memory). ``PID`` Process ID (column is replaced by NPROCS in accumulated mode) +``NPROCS`` Number of process + childs (only in accumulated mode) ``USER`` User ID ``THR`` Threads number of the process ``TIME+`` Cumulative CPU time used by the process diff --git a/docs/aoa/sensors.rst b/docs/aoa/sensors.rst index 5ee5923fa2..bc01ce2054 100644 --- a/docs/aoa/sensors.rst +++ b/docs/aoa/sensors.rst @@ -13,29 +13,44 @@ Glances can display the sensors information using ``psutil``, - hard disk temperature - battery capacity -There is no alert on this information. +Limit values and sensors alias names can be defined in the configuration +file under the ``[sensors]`` section. + +Limit can be defined for a specific sensor, a type of sensor or defineby the system +thresholds (default behavor). + +.. code-block:: ini + + [sensors] + # Sensors core thresholds (in Celsius...) + # By default values are grabbed from the system + # Overwrite thresholds for a specific sensor + temperature_core_Ambient_careful=45 + temperature_core_Ambient_warning=65 + temperature_core_Ambient_critical=80 + temperature_core_Ambient_log=False + # Overwrite thresholds for a specific type of sensor + #temperature_core_careful=45 + #temperature_core_warning=65 + #temperature_core_critical=80 .. note 1:: - Limit values and sensors alias names can be defined in the - configuration file under the ``[sensors]`` section. - -.. note 2:: The support for multiple batteries is only available if you have the batinfo Python lib installed on your system because for the moment PSUtil only support one battery. -.. note 3:: +.. note 2:: If a sensors has temperature and fan speed with the same name unit, it is possible to alias it using: alias=unitname_temperature_core_alias:Alias for temp,unitname_fan_speed_alias:Alias for fan speed -.. note 4:: +.. note 3:: If a sensors has multiple identical features names (see #2280), then Glances will add a suffix to the feature name. For example, if you have one sensor with two Composite features, the second one will be named Composite_1. -.. note 5:: +.. note 4:: The plugin could crash on some operating system (FreeBSD) with the TCP or UDP blackhole option > 0 (see issue #2106). In this case, you should disable the sensors (--disable-plugin sensors or from the diff --git a/docs/aoa/smart.rst b/docs/aoa/smart.rst index 033c34f4e4..c878ab4348 100644 --- a/docs/aoa/smart.rst +++ b/docs/aoa/smart.rst @@ -22,3 +22,26 @@ How to read the information: .. warning:: This plugin needs administrator rights. Please run Glances as root/admin. + +Also, you can hide sensors using regular expressions. + +To hide sensors you should use the hide option: + +.. code-block:: ini + + [smart] + hide=.*Hide_this_driver.* + +It is also possible to configure a white list of devices to display. +Example to only show .*Drive_Temperature.* sensors: + +.. code-block:: ini + + [smart] + show=.*Drive_Temperature.* + +Filtering is based on regular expression. Please be sure that your regular +expression works as expected. You can use an online tool like `regex101`_ in +order to test your regular expression. + +.. _regex101: https://regex101.com/ \ No newline at end of file diff --git a/docs/api.rst b/docs/api.rst index 7457eadd8d..64f25fb87d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -19,6 +19,12 @@ The Glances Restfull/API server could be ran using the following command line: It is also ran automatically when Glances is started in Web server mode (-w). +If you want to enable the Glances Central Browser, use: + +.. code-block:: bash + + # glances -w --browser --disable-webui + API URL ------- @@ -43,14 +49,14 @@ will change the root API URL to ``http://localhost:61208/glances/api/4`` and the API documentation URL --------------------- -The API documentation is embedded in the server and available at the following URL: +The API documentation is embeded in the server and available at the following URL: ``http://localhost:61208/docs#/``. WebUI refresh ------------- It is possible to change the Web UI refresh rate (default is 2 seconds) using the following option in the URL: -``http://localhost:61208/glances/?refresh=5`` +``http://localhost:61208/?refresh=5`` GET API status @@ -93,6 +99,7 @@ Get the plugins list:: "ports", "processcount", "processlist", + "programlist", "psutilversion", "quicklook", "raid", @@ -101,6 +108,7 @@ Get the plugins list:: "system", "uptime", "version", + "vms", "wifi"] GET alert @@ -127,6 +135,17 @@ Fields descriptions: * **sort**: Sort key of the top processes (unit is *string*) * **global_msg**: Global alert message (unit is *string*) +POST clear events +----------------- + +Clear all alarms from the list:: + + # curl -H "Content-Type: application/json" -X POST http://localhost:61208/api/4/events/clear/all + +Clear warning alarms from the list:: + + # curl -H "Content-Type: application/json" -X POST http://localhost:61208/api/4/events/clear/warning + GET amps -------- @@ -141,7 +160,7 @@ Get plugin stats:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.2482151985168457}, + "timer": 0.3096492290496826}, {"count": 0, "countmax": 20.0, "countmin": None, @@ -150,7 +169,7 @@ Get plugin stats:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.24815130233764648}] + "timer": 0.30959320068359375}] Fields descriptions: @@ -169,7 +188,7 @@ Get a specific field:: Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/4/amps/name/Dropbox + # curl http://localhost:61208/api/4/amps/name/value/Dropbox {"Dropbox": [{"count": 0, "countmax": None, "countmin": 1.0, @@ -178,7 +197,7 @@ Get a specific item when field matches the given value:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.2482151985168457}]} + "timer": 0.3096492290496826}]} GET cloud --------- @@ -219,7 +238,26 @@ GET containers Get plugin stats:: # curl http://localhost:61208/api/4/containers - [] + [{"command": "bash start.sh", + "cpu": {"total": 0.0}, + "cpu_percent": 0.0, + "created": "2025-03-02T18:56:49.050583851Z", + "engine": "docker", + "id": "37b79baf008ea602934be8d16d76064f79a032c11e4f6fe6e811b3c46ac20fd0", + "image": ["ghcr.io/open-webui/open-webui:ollama"], + "io": {"cumulative_ior": 620675072, "cumulative_iow": 0}, + "io_rx": None, + "io_wx": None, + "key": "name", + "memory": {"inactive_file": 0, "limit": 16422428672, "usage": 488845312}, + "memory_percent": None, + "memory_usage": 488845312, + "name": "open-webui", + "network": {"cumulative_rx": 454153644, "cumulative_tx": 10910712}, + "network_rx": None, + "network_tx": None, + "status": "running", + "uptime": "a week"}] Fields descriptions: @@ -240,6 +278,38 @@ Fields descriptions: * **pod_name**: Pod name (only with Podman) (unit is *None*) * **pod_id**: Pod ID (only with Podman) (unit is *None*) +Get a specific field:: + + # curl http://localhost:61208/api/4/containers/name + {"name": ["open-webui"]} + +Get a specific item when field matches the given value:: + + # curl http://localhost:61208/api/4/containers/name/value/open-webui + {"open-webui": [{"command": "bash start.sh", + "cpu": {"total": 0.0}, + "cpu_percent": 0.0, + "created": "2025-03-02T18:56:49.050583851Z", + "engine": "docker", + "id": "37b79baf008ea602934be8d16d76064f79a032c11e4f6fe6e811b3c46ac20fd0", + "image": ["ghcr.io/open-webui/open-webui:ollama"], + "io": {"cumulative_ior": 620675072, "cumulative_iow": 0}, + "io_rx": None, + "io_wx": None, + "key": "name", + "memory": {"inactive_file": 0, + "limit": 16422428672, + "usage": 488845312}, + "memory_percent": None, + "memory_usage": 488845312, + "name": "open-webui", + "network": {"cumulative_rx": 454153644, + "cumulative_tx": 10910712}, + "network_rx": None, + "network_tx": None, + "status": "running", + "uptime": "a week"}]} + GET core -------- @@ -265,19 +335,19 @@ Get plugin stats:: # curl http://localhost:61208/api/4/cpu {"cpucore": 16, - "ctx_switches": 493080568, + "ctx_switches": 157096455, "guest": 0.0, - "idle": 86.5, - "interrupts": 420997918, - "iowait": 0.5, + "idle": 92.6, + "interrupts": 107410302, + "iowait": 0.1, "irq": 0.0, "nice": 0.0, - "soft_interrupts": 155707720, + "soft_interrupts": 47855924, "steal": 0.0, "syscalls": 0, - "system": 3.5, - "total": 12.9, - "user": 9.4} + "system": 2.6, + "total": 7.4, + "user": 4.7} Fields descriptions: @@ -310,7 +380,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/cpu/total - {"total": 12.9} + {"total": 7.4} GET diskio ---------- @@ -320,16 +390,16 @@ Get plugin stats:: # curl http://localhost:61208/api/4/diskio [{"disk_name": "nvme0n1", "key": "disk_name", - "read_bytes": 10167291392, - "read_count": 391026, - "write_bytes": 31230641152, - "write_count": 1527146}, + "read_bytes": 8382154240, + "read_count": 370011, + "write_bytes": 71193142272, + "write_count": 1225588}, {"disk_name": "nvme0n1p1", "key": "disk_name", - "read_bytes": 7558144, - "read_count": 605, - "write_bytes": 1024, - "write_count": 2}] + "read_bytes": 18425856, + "read_count": 1233, + "write_bytes": 5120, + "write_count": 3}] Fields descriptions: @@ -360,13 +430,13 @@ Get a specific field:: Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/4/diskio/disk_name/nvme0n1 + # curl http://localhost:61208/api/4/diskio/disk_name/value/nvme0n1 {"nvme0n1": [{"disk_name": "nvme0n1", "key": "disk_name", - "read_bytes": 10167291392, - "read_count": 391026, - "write_bytes": 31230641152, - "write_count": 1527146}]} + "read_bytes": 8382154240, + "read_count": 370011, + "write_bytes": 71193142272, + "write_count": 1225588}]} GET folders ----------- @@ -393,13 +463,21 @@ Get plugin stats:: # curl http://localhost:61208/api/4/fs [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv", - "free": 897378041856, + "free": 825913921536, "fs_type": "ext4", "key": "mnt_point", "mnt_point": "/", - "percent": 5.8, + "percent": 13.3, "size": 1003736440832, - "used": 55295893504}] + "used": 126760013824}, + {"device_name": "zsfpool", + "free": 41811968, + "fs_type": "zfs", + "key": "mnt_point", + "mnt_point": "/zsfpool", + "percent": 0.3, + "size": 41943040, + "used": 131072}] Fields descriptions: @@ -414,19 +492,19 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/fs/mnt_point - {"mnt_point": ["/"]} + {"mnt_point": ["/", "/zsfpool"]} Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/4/fs/mnt_point// + # curl http://localhost:61208/api/4/fs/mnt_point/value// {"/": [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv", - "free": 897378041856, + "free": 825913921536, "fs_type": "ext4", "key": "mnt_point", "mnt_point": "/", - "percent": 5.8, + "percent": 13.3, "size": 1003736440832, - "used": 55295893504}]} + "used": 126760013824}]} GET gpu ------- @@ -459,8 +537,7 @@ GET ip Get plugin stats:: # curl http://localhost:61208/api/4/ip - {"address": "192.168.1.26", - "gateway": "192.168.1.1", + {"address": "192.168.0.28", "mask": "255.255.255.0", "mask_cidr": 24, "public_address": "", @@ -477,8 +554,8 @@ Fields descriptions: Get a specific field:: - # curl http://localhost:61208/api/4/ip/gateway - {"gateway": "192.168.1.1"} + # curl http://localhost:61208/api/4/ip/address + {"address": "192.168.0.28"} GET irq ------- @@ -500,9 +577,9 @@ Get plugin stats:: # curl http://localhost:61208/api/4/load {"cpucore": 16, - "min1": 0.40185546875, - "min15": 0.587890625, - "min5": 0.638671875} + "min1": 1.74560546875, + "min15": 0.80810546875, + "min5": 1.0078125} Fields descriptions: @@ -514,7 +591,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/load/min1 - {"min1": 0.40185546875} + {"min1": 1.74560546875} GET mem ------- @@ -522,16 +599,16 @@ GET mem Get plugin stats:: # curl http://localhost:61208/api/4/mem - {"active": 5094199296, - "available": 10908983296, - "buffers": 180162560, - "cached": 5800796160, - "free": 10908983296, - "inactive": 3735175168, - "percent": 33.6, - "shared": 622718976, - "total": 16422486016, - "used": 5513502720} + {"active": 6679535616, + "available": 6505562112, + "buffers": 185286656, + "cached": 5573652480, + "free": 6505562112, + "inactive": 5449142272, + "percent": 60.4, + "shared": 764145664, + "total": 16422428672, + "used": 9916866560} Fields descriptions: @@ -550,7 +627,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/mem/total - {"total": 16422486016} + {"total": 16422428672} GET memswap ----------- @@ -558,13 +635,13 @@ GET memswap Get plugin stats:: # curl http://localhost:61208/api/4/memswap - {"free": 3836997632, - "percent": 10.7, - "sin": 186925056, - "sout": 1518604288, + {"free": 3455053824, + "percent": 19.6, + "sin": 12197888, + "sout": 854618112, "time_since_update": 1, "total": 4294963200, - "used": 457965568} + "used": 839909376} Fields descriptions: @@ -589,15 +666,26 @@ Get plugin stats:: # curl http://localhost:61208/api/4/network [{"alias": None, "bytes_all": 0, - "bytes_all_gauge": 6286191015, + "bytes_all_gauge": 2622508339, "bytes_recv": 0, - "bytes_recv_gauge": 5977645732, + "bytes_recv_gauge": 2469455038, "bytes_sent": 0, - "bytes_sent_gauge": 308545283, + "bytes_sent_gauge": 153053301, "interface_name": "wlp0s20f3", "key": "interface_name", "speed": 0, - "time_since_update": 0.2501566410064697}] + "time_since_update": 0.31115031242370605}, + {"alias": None, + "bytes_all": 0, + "bytes_all_gauge": 24003803, + "bytes_recv": 0, + "bytes_recv_gauge": 441027, + "bytes_sent": 0, + "bytes_sent_gauge": 23562776, + "interface_name": "mpqemubr0", + "key": "interface_name", + "speed": 10485760000, + "time_since_update": 0.31115031242370605}] Fields descriptions: @@ -619,22 +707,22 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/network/interface_name - {"interface_name": ["wlp0s20f3"]} + {"interface_name": ["wlp0s20f3", "mpqemubr0", "tap-8d309783ee6", "veth7684f4d"]} Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/4/network/interface_name/wlp0s20f3 + # curl http://localhost:61208/api/4/network/interface_name/value/wlp0s20f3 {"wlp0s20f3": [{"alias": None, "bytes_all": 0, - "bytes_all_gauge": 6286191015, + "bytes_all_gauge": 2622508339, "bytes_recv": 0, - "bytes_recv_gauge": 5977645732, + "bytes_recv_gauge": 2469455038, "bytes_sent": 0, - "bytes_sent_gauge": 308545283, + "bytes_sent_gauge": 153053301, "interface_name": "wlp0s20f3", "key": "interface_name", "speed": 0, - "time_since_update": 0.2501566410064697}]} + "time_since_update": 0.31115031242370605}]} GET now ------- @@ -642,7 +730,7 @@ GET now Get plugin stats:: # curl http://localhost:61208/api/4/now - {"custom": "2024-06-29 19:17:41 CEST", "iso": "2024-06-29T19:17:41+02:00"} + {"custom": "2025-03-22 17:41:04 CET", "iso": "2025-03-22T17:41:04+01:00"} Fields descriptions: @@ -652,7 +740,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/now/iso - {"iso": "2024-06-29T19:17:41+02:00"} + {"iso": "2025-03-22T17:41:04+01:00"} GET percpu ---------- @@ -661,9 +749,11 @@ Get plugin stats:: # curl http://localhost:61208/api/4/percpu [{"cpu_number": 0, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 21.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -671,20 +761,22 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 79.0, - "user": 1.0}, + "total": 70.0, + "user": 0.0}, {"cpu_number": 1, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 23.0, + "idle": 22.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 0.0, - "total": 77.0, + "system": 9.0, + "total": 78.0, "user": 0.0}] Fields descriptions: @@ -701,6 +793,8 @@ Fields descriptions: * **guest**: *(Linux)*: percent of time spent running a virtual CPU for guest operating systems under the control of the Linux kernel (unit is *percent*) * **guest_nice**: *(Linux)*: percent of time spent running a niced guest (virtual CPU) (unit is *percent*) * **softirq**: *(Linux)*: percent of time spent handling software interrupts (unit is *percent*) +* **dpc**: *(Windows)*: percent of time spent handling deferred procedure calls (unit is *percent*) +* **interrupt**: *(Windows)*: percent of time spent handling software interrupts (unit is *percent*) Get a specific field:: @@ -714,12 +808,12 @@ Get plugin stats:: # curl http://localhost:61208/api/4/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.006275, + "status": 0.013786, "timeout": 3}] Fields descriptions: @@ -736,19 +830,19 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/ports/host - {"host": ["192.168.1.1"]} + {"host": ["192.168.0.254"]} Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/4/ports/host/192.168.1.1 - {"192.168.1.1": [{"description": "DefaultGateway", - "host": "192.168.1.1", - "indice": "port_0", - "port": 0, - "refresh": 30, - "rtt_warning": None, - "status": 0.006275, - "timeout": 3}]} + # curl http://localhost:61208/api/4/ports/host/value/192.168.0.254 + {"192.168.0.254": [{"description": "DefaultGateway", + "host": "192.168.0.254", + "indice": "port_0", + "port": 0, + "refresh": 30, + "rtt_warning": None, + "status": 0.013786, + "timeout": 3}]} GET processcount ---------------- @@ -756,7 +850,7 @@ GET processcount Get plugin stats:: # curl http://localhost:61208/api/4/processcount - {"pid_max": 0, "running": 2, "sleeping": 277, "thread": 1508, "total": 412} + {"pid_max": 0, "running": 1, "sleeping": 427, "thread": 2030, "total": 567} Fields descriptions: @@ -769,7 +863,7 @@ Fields descriptions: Get a specific field:: # curl http://localhost:61208/api/4/processcount/total - {"total": 412} + {"total": 567} GET processlist --------------- @@ -777,78 +871,334 @@ GET processlist Get plugin stats:: # curl http://localhost:61208/api/4/processlist - [{"cmdline": ["/snap/firefox/4336/usr/lib/firefox/firefox"], + [{"cmdline": ["/snap/code/181/usr/share/code/code", + "--type=utility", + "--utility-sub-type=node.mojom.NodeService", + "--lang=en-US", + "--service-sandbox-type=none", + "--no-sandbox", + "--dns-result-order=ipv4first", + "--inspect-port=0", + "--crashpad-handler-pid=79415", + "--enable-crash-reporter=864d4bb7-dd20-4851-830f-29e81dd93517,no_channel", + "--user-data-dir=/home/nicolargo/.config/Code", + "--standard-schemes=vscode-webview,vscode-file", + "--secure-schemes=vscode-webview,vscode-file", + "--cors-schemes=vscode-webview,vscode-file", + "--fetch-schemes=vscode-webview,vscode-file", + "--service-worker-schemes=vscode-webview", + "--code-cache-schemes=vscode-webview,vscode-file", + "--shared-files=v8_context_snapshot_data:100", + "--field-trial-handle=3,i,1279145526343135898,6861814050030728552,262144", + "--disable-features=CalculateNativeWinOcclusion,PlzDedicatedWorker,SpareRendererForSitePerProcess", + "--variations-seed-version"], "cpu_percent": 0.0, - "cpu_times": {"children_system": 3.08, - "children_user": 3.33, + "cpu_times": {"children_system": 139.72, + "children_user": 50.81, "iowait": 0.0, - "system": 13.75, - "user": 34.7}, + "system": 119.61, + "user": 187.77}, "gids": {"effective": 1000, "real": 1000, "saved": 1000}, - "io_counters": [393136128, 195969024, 0, 0, 0], + "io_counters": [490232832, + 272453632, + 0, + 0, + 0, + 75640832, + 1458176, + 0, + 0, + 0, + 70094848, + 0, + 0, + 0, + 0, + 21813248, + 3252224, + 0, + 0, + 0, + 68483072, + 35565568, + 0, + 0, + 0, + 10318848, + 0, + 0, + 0, + 0, + 46509056, + 1436467200, + 0, + 0, + 0, + 36725760, + 0, + 0, + 0, + 0, + 2703360, + 0, + 0, + 0, + 0, + 615424, + 0, + 0, + 0, + 0, + 5218304, + 0, + 0, + 0, + 0, + 140288, + 0, + 0, + 0, + 0, + 3473408, + 2461696, + 0, + 0, + 0, + 988160, + 0, + 0, + 0, + 0, + 1223680, + 0, + 0, + 0, + 0], "key": "pid", - "memory_info": {"data": 681967616, + "memory_info": {"data": 2457976832, "dirty": 0, "lib": 0, - "rss": 443969536, - "shared": 222199808, - "text": 987136, - "vms": 3721211904}, - "memory_percent": 2.7034246554842674, - "name": "firefox", + "rss": 1292099584, + "shared": 124125184, + "text": 138166272, + "vms": 1272570376192}, + "memory_percent": 7.86789585028316, + "name": "code", "nice": 0, - "num_threads": 120, - "pid": 793506, + "num_threads": 56, + "pid": 79607, "status": "S", "time_since_update": 1, "username": "nicolargo"}, - {"cmdline": ["/snap/firefox/4336/usr/lib/firefox/firefox", - "-contentproc", - "-childID", - "2", - "-isForBrowser", - "-prefsLen", - "28218", - "-prefMapSize", - "244440", - "-jsInitLen", - "231800", - "-parentBuildID", - "20240527194810", - "-greomni", - "/snap/firefox/4336/usr/lib/firefox/omni.ja", - "-appomni", - "/snap/firefox/4336/usr/lib/firefox/browser/omni.ja", - "-appDir", - "/snap/firefox/4336/usr/lib/firefox/browser", - "{bc853380-6b8f-46ad-afe0-9da5ba832e62}", - "793506", - "true", - "tab"], + {"cmdline": ["/snap/multipass/14300/usr/bin/qemu-system-x86_64", + "-bios", + "OVMF.fd", + "--enable-kvm", + "-cpu", + "host", + "-nic", + "tap,ifname=tap-8d309783ee6,script=no,downscript=no,model=virtio-net-pci,mac=52:54:00:c3:48:7b", + "-device", + "virtio-scsi-pci,id=scsi0", + "-drive", + "file=/var/snap/multipass/common/data/multipassd/vault/instances/upstanding-sparrow/ubuntu-24.04-server-cloudimg-amd64.img,if=none,format=qcow2,discard=unmap,id=hda", + "-device", + "scsi-hd,drive=hda,bus=scsi0.0", + "-smp", + "1", + "-m", + "1024M", + "-qmp", + "stdio", + "-chardev", + "null,id=char0", + "-serial", + "chardev:char0", + "-nographic", + "-cdrom", + "/var/snap/multipass/common/data/multipassd/vault/instances/upstanding-sparrow/cloud-init-config.iso"], "cpu_percent": 0.0, "cpu_times": {"children_system": 0.0, "children_user": 0.0, "iowait": 0.0, - "system": 2.54, - "user": 19.25}, - "gids": {"effective": 1000, "real": 1000, "saved": 1000}, - "io_counters": [1827840, 0, 0, 0, 0], + "system": 46.6, + "user": 180.54}, + "gids": {"effective": 0, "real": 0, "saved": 0}, + "io_counters": [0, 0, 0, 0, 0], "key": "pid", - "memory_info": {"data": 412688384, + "memory_info": {"data": 1417625600, "dirty": 0, "lib": 0, - "rss": 440922112, - "shared": 111878144, - "text": 987136, - "vms": 2946224128}, - "memory_percent": 2.684868244493684, - "name": "Isolated Web Co", + "rss": 1103982592, + "shared": 14286848, + "text": 6172672, + "vms": 5118357504}, + "memory_percent": 6.722407592990641, + "name": "qemu-system-x86_64", "nice": 0, - "num_threads": 28, - "pid": 793778, + "num_threads": 5, + "pid": 3297, "status": "S", "time_since_update": 1, - "username": "nicolargo"}] + "username": "root"}] + +Fields descriptions: + +* **pid**: Process identifier (ID) (unit is *number*) +* **name**: Process name (unit is *string*) +* **cmdline**: Command line with arguments (unit is *list*) +* **username**: Process owner (unit is *string*) +* **num_threads**: Number of threads (unit is *number*) +* **cpu_percent**: Process CPU consumption (unit is *percent*) +* **memory_percent**: Process memory consumption (unit is *percent*) +* **memory_info**: Process memory information (dict with rss, vms, shared, text, lib, data, dirty keys) (unit is *byte*) +* **status**: Process status (unit is *string*) +* **nice**: Process nice value (unit is *number*) +* **cpu_times**: Process CPU times (dict with user, system, iowait keys) (unit is *second*) +* **gids**: Process group IDs (dict with real, effective, saved keys) (unit is *number*) +* **io_counters**: Process IO counters (list with read_count, write_count, read_bytes, write_bytes, io_tag keys) (unit is *byte*) + +GET programlist +--------------- + +Get plugin stats:: + + # curl http://localhost:61208/api/4/programlist + [{"childrens": [79607, + 80276, + 80569, + 79491, + 79394, + 80831, + 79581, + 79582, + 79545, + 79970, + 80365, + 80351, + 79467, + 79397, + 79396], + "cmdline": ["code"], + "cpu_percent": 0, + "cpu_times": {"children_system": 140.97, + "children_user": 58.089999999999996, + "system": 242.39999999999995, + "user": 1004.36}, + "io_counters": [490232832, + 272453632, + 0, + 0, + 0, + 75640832, + 1458176, + 0, + 0, + 0, + 70094848, + 0, + 0, + 0, + 0, + 21813248, + 3252224, + 0, + 0, + 0, + 68483072, + 35565568, + 0, + 0, + 0, + 10318848, + 0, + 0, + 0, + 0, + 46509056, + 1436467200, + 0, + 0, + 0, + 36725760, + 0, + 0, + 0, + 0, + 2703360, + 0, + 0, + 0, + 0, + 615424, + 0, + 0, + 0, + 0, + 5218304, + 0, + 0, + 0, + 0, + 140288, + 0, + 0, + 0, + 0, + 3473408, + 2461696, + 0, + 0, + 0, + 988160, + 0, + 0, + 0, + 0, + 1223680, + 0, + 0, + 0, + 0], + "memory_info": {"data": 11900305408, + "rss": 4097130496, + "shared": 1183440896, + "text": 2072494080, + "vms": 13841155485696}, + "memory_percent": 24.948383566345143, + "name": "code", + "nice": 0, + "nprocs": 15, + "num_threads": 266, + "pid": "_", + "status": "S", + "time_since_update": 1, + "username": "nicolargo"}, + {"childrens": [3297], + "cmdline": ["qemu-system-x86_64"], + "cpu_percent": 0, + "cpu_times": {"children_system": 0.0, + "children_user": 0.0, + "iowait": 0.0, + "system": 46.6, + "user": 180.54}, + "io_counters": [0, 0, 0, 0, 0], + "memory_info": {"data": 1417625600, + "dirty": 0, + "lib": 0, + "rss": 1103982592, + "shared": 14286848, + "text": 6172672, + "vms": 5118357504}, + "memory_percent": 6.722407592990641, + "name": "qemu-system-x86_64", + "nice": 0, + "nprocs": 1, + "num_threads": 5, + "pid": "_", + "status": "S", + "time_since_update": 1, + "username": "root"}] Fields descriptions: @@ -872,7 +1222,7 @@ GET psutilversion Get plugin stats:: # curl http://localhost:61208/api/4/psutilversion - "6.0.0" + "7.0.0" GET quicklook ------------- @@ -880,18 +1230,20 @@ GET quicklook Get plugin stats:: # curl http://localhost:61208/api/4/quicklook - {"cpu": 12.9, + {"cpu": 7.4, "cpu_hz": 4475000000.0, - "cpu_hz_current": 1666410624.9999998, + "cpu_hz_current": 1364220562.5, "cpu_log_core": 16, "cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H", "cpu_phys_core": 10, - "load": 3.7, - "mem": 33.6, + "load": 5.1, + "mem": 60.4, "percpu": [{"cpu_number": 0, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 21.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -899,38 +1251,44 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 79.0, - "user": 1.0}, + "total": 70.0, + "user": 0.0}, {"cpu_number": 1, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 23.0, + "idle": 22.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 0.0, - "total": 77.0, + "system": 9.0, + "total": 78.0, "user": 0.0}, {"cpu_number": 2, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 18.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 1.0, - "total": 82.0, - "user": 5.0}, + "system": 0.0, + "total": 70.0, + "user": 0.0}, {"cpu_number": 3, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 22.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -938,90 +1296,104 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 78.0, + "total": 70.0, "user": 0.0}, {"cpu_number": 4, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 8.0, + "idle": 24.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 1.0, - "total": 92.0, - "user": 14.0}, + "system": 3.0, + "total": 76.0, + "user": 1.0}, {"cpu_number": 5, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 23.0, + "idle": 26.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 0.0, - "total": 77.0, - "user": 0.0}, + "system": 3.0, + "total": 74.0, + "user": 1.0}, {"cpu_number": 6, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 3.0, - "iowait": 1.0, + "idle": 21.0, + "interrupt": None, + "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 4.0, - "total": 97.0, - "user": 15.0}, + "system": 2.0, + "total": 79.0, + "user": 8.0}, {"cpu_number": 7, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 23.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 1.0, - "total": 77.0, + "system": 0.0, + "total": 70.0, "user": 0.0}, {"cpu_number": 8, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 19.0, + "idle": 28.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 1.0, - "total": 81.0, + "system": 0.0, + "total": 72.0, "user": 3.0}, {"cpu_number": 9, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 22.0, - "iowait": 1.0, + "idle": 30.0, + "interrupt": None, + "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 78.0, + "total": 70.0, "user": 0.0}, {"cpu_number": 10, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 22.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -1029,12 +1401,14 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 78.0, - "user": 0.0}, + "total": 70.0, + "user": 1.0}, {"cpu_number": 11, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 23.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -1042,25 +1416,29 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 77.0, + "total": 70.0, "user": 0.0}, {"cpu_number": 12, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 21.0, + "idle": 28.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 1.0, - "total": 79.0, - "user": 1.0}, + "system": 0.0, + "total": 72.0, + "user": 2.0}, {"cpu_number": 13, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 23.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -1068,12 +1446,14 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 77.0, + "total": 70.0, "user": 0.0}, {"cpu_number": 14, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 22.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -1081,12 +1461,14 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 78.0, - "user": 0.0}, + "total": 70.0, + "user": 1.0}, {"cpu_number": 15, + "dpc": None, "guest": 0.0, "guest_nice": 0.0, - "idle": 22.0, + "idle": 30.0, + "interrupt": None, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", @@ -1094,9 +1476,9 @@ Get plugin stats:: "softirq": 0.0, "steal": 0.0, "system": 0.0, - "total": 78.0, + "total": 70.0, "user": 0.0}], - "swap": 10.7} + "swap": 19.6} Fields descriptions: @@ -1134,14 +1516,14 @@ Get plugin stats:: "label": "Ambient", "type": "temperature_core", "unit": "C", - "value": 39, + "value": 40, "warning": 0}, {"critical": None, "key": "label", "label": "Ambient 3", "type": "temperature_core", "unit": "C", - "value": 32, + "value": 30, "warning": 0}] Fields descriptions: @@ -1196,13 +1578,13 @@ Get a specific field:: Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/4/sensors/label/Ambient + # curl http://localhost:61208/api/4/sensors/label/value/Ambient {"Ambient": [{"critical": None, "key": "label", "label": "Ambient", "type": "temperature_core", "unit": "C", - "value": 39, + "value": 40, "warning": 0}]} GET smart @@ -1220,10 +1602,10 @@ Get plugin stats:: # curl http://localhost:61208/api/4/system {"hostname": "nicolargo-xps15", - "hr_name": "Ubuntu 24.04 64bit / Linux 6.8.0-31-generic", + "hr_name": "Ubuntu 24.04 64bit / Linux 6.8.0-51-generic", "linux_distro": "Ubuntu 24.04", "os_name": "Linux", - "os_version": "6.8.0-31-generic", + "os_version": "6.8.0-51-generic", "platform": "64bit"} Fields descriptions: @@ -1246,7 +1628,7 @@ GET uptime Get plugin stats:: # curl http://localhost:61208/api/4/uptime - "20 days, 2:20:27" + "7 days, 2:36:15" GET version ----------- @@ -1254,7 +1636,31 @@ GET version Get plugin stats:: # curl http://localhost:61208/api/4/version - "4.2.0_beta01" + "4.3.1" + +GET vms +------- + +Get plugin stats:: + + # curl http://localhost:61208/api/4/vms + {} + +Fields descriptions: + +* **name**: Vm name (unit is *None*) +* **id**: Vm ID (unit is *None*) +* **release**: Vm release (unit is *None*) +* **status**: Vm status (unit is *None*) +* **cpu_count**: Vm CPU count (unit is *None*) +* **memory_usage**: Vm memory usage (unit is *byte*) +* **memory_total**: Vm memory total (unit is *byte*) +* **load_1min**: Vm Load last 1 min (unit is *None*) +* **load_5min**: Vm Load last 5 mins (unit is *None*) +* **load_15min**: Vm Load last 15 mins (unit is *None*) +* **ipv4**: Vm IP v4 address (unit is *None*) +* **engine**: VM engine name (only Mutlipass is currently supported) (unit is *None*) +* **engine_version**: VM engine version (unit is *None*) GET wifi -------- @@ -1263,8 +1669,8 @@ Get plugin stats:: # curl http://localhost:61208/api/4/wifi [{"key": "ssid", - "quality_level": -59.0, - "quality_link": 51.0, + "quality_level": -66.0, + "quality_link": 44.0, "ssid": "wlp0s20f3"}] Get a specific field:: @@ -1274,10 +1680,10 @@ Get a specific field:: Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/4/wifi/ssid/wlp0s20f3 + # curl http://localhost:61208/api/4/wifi/ssid/value/wlp0s20f3 {"wlp0s20f3": [{"key": "ssid", - "quality_level": -59.0, - "quality_link": 51.0, + "quality_level": -66.0, + "quality_link": 44.0, "ssid": "wlp0s20f3"}]} GET all stats @@ -1286,7 +1692,26 @@ GET all stats Get all Glances stats:: # curl http://localhost:61208/api/4/all - Return a very big dictionary (avoid using this request, performances will be poor)... + Return a very big dictionary with all stats + +Note: Update is done automatically every time /all or / is called. + +GET stats of a specific process +------------------------------- + +Get stats for process with PID == 777:: + + # curl http://localhost:61208/api/4/processes/777 + Return stats for process (dict) + +Enable extended stats for process with PID == 777 (only one process at a time can be enabled):: + + # curl -X POST http://localhost:61208/api/4/processes/extended/777 + # curl http://localhost:61208/api/4/all + # curl http://localhost:61208/api/4/processes/777 + Return stats for process (dict) + +Note: Update *is not* done automatically when you call /processes/. GET top n items of a specific plugin ------------------------------------ @@ -1322,34 +1747,34 @@ GET stats history History of a plugin:: # curl http://localhost:61208/api/4/cpu/history - {"system": [["2024-06-29T19:17:42.396270", 3.5], - ["2024-06-29T19:17:43.447397", 0.7], - ["2024-06-29T19:17:44.455830", 0.7]], - "user": [["2024-06-29T19:17:42.396268", 9.4], - ["2024-06-29T19:17:43.447396", 4.0], - ["2024-06-29T19:17:44.455825", 4.0]]} + {"system": [["2025-03-22T17:41:05.581948", 2.6], + ["2025-03-22T17:41:06.651293", 0.8], + ["2025-03-22T17:41:07.669006", 0.8]], + "user": [["2025-03-22T17:41:05.581940", 4.7], + ["2025-03-22T17:41:06.651292", 3.7], + ["2025-03-22T17:41:07.669004", 3.7]]} Limit history to last 2 values:: # curl http://localhost:61208/api/4/cpu/history/2 - {"system": [["2024-06-29T19:17:43.447397", 0.7], - ["2024-06-29T19:17:44.455830", 0.7]], - "user": [["2024-06-29T19:17:43.447396", 4.0], - ["2024-06-29T19:17:44.455825", 4.0]]} + {"system": [["2025-03-22T17:41:06.651293", 0.8], + ["2025-03-22T17:41:07.669006", 0.8]], + "user": [["2025-03-22T17:41:06.651292", 3.7], + ["2025-03-22T17:41:07.669004", 3.7]]} History for a specific field:: # curl http://localhost:61208/api/4/cpu/system/history - {"system": [["2024-06-29T19:17:41.318736", 3.5], - ["2024-06-29T19:17:42.396270", 3.5], - ["2024-06-29T19:17:43.447397", 0.7], - ["2024-06-29T19:17:44.455830", 0.7]]} + {"system": [["2025-03-22T17:41:04.464503", 2.6], + ["2025-03-22T17:41:05.581948", 2.6], + ["2025-03-22T17:41:06.651293", 0.8], + ["2025-03-22T17:41:07.669006", 0.8]]} Limit history for a specific field to last 2 values:: # curl http://localhost:61208/api/4/cpu/system/history - {"system": [["2024-06-29T19:17:43.447397", 0.7], - ["2024-06-29T19:17:44.455830", 0.7]]} + {"system": [["2025-03-22T17:41:06.651293", 0.8], + ["2025-03-22T17:41:07.669006", 0.8]]} GET limits (used for thresholds) -------------------------------- @@ -1389,6 +1814,7 @@ All limits/thresholds:: "history_size": 1200.0}, "diskio": {"diskio_disable": ["False"], "diskio_hide": ["loop.*", "/dev/loop.*"], + "diskio_hide_zero": ["False"], "history_size": 1200.0}, "folders": {"folders_disable": ["False"], "history_size": 1200.0}, "fs": {"fs_careful": 50.0, @@ -1436,6 +1862,7 @@ All limits/thresholds:: "network_hide": ["docker.*", "lo"], "network_hide_no_ip": ["True"], "network_hide_no_up": ["True"], + "network_hide_zero": ["False"], "network_rx_careful": 70.0, "network_rx_critical": 90.0, "network_rx_warning": 80.0, @@ -1508,6 +1935,7 @@ All limits/thresholds:: "17", "18", "19"]}, + "programlist": {"history_size": 1200.0}, "psutilversion": {"history_size": 1200.0}, "quicklook": {"history_size": 1200.0, "quicklook_bar_char": ["|"], @@ -1526,15 +1954,12 @@ All limits/thresholds:: "quicklook_swap_critical": 90.0, "quicklook_swap_warning": 70.0}, "sensors": {"history_size": 1200.0, - "sensors_battery_careful": 80.0, - "sensors_battery_critical": 95.0, - "sensors_battery_warning": 90.0, + "sensors_battery_careful": 70.0, + "sensors_battery_critical": 90.0, + "sensors_battery_warning": 80.0, "sensors_disable": ["False"], "sensors_hide": ["unknown.*"], "sensors_refresh": 6.0, - "sensors_temperature_core_careful": 60.0, - "sensors_temperature_core_critical": 80.0, - "sensors_temperature_core_warning": 70.0, "sensors_temperature_hdd_careful": 45.0, "sensors_temperature_hdd_critical": 60.0, "sensors_temperature_hdd_warning": 52.0}, diff --git a/docs/cmds.rst b/docs/cmds.rst index c23670f52a..d144ef02f5 100644 --- a/docs/cmds.rst +++ b/docs/cmds.rst @@ -120,7 +120,8 @@ Command-Line Options .. option:: --browser - start the client browser (list of servers) + start TUI Central Glances Browser + use --browser -w to start WebUI Central Glances Browser .. option:: --disable-autodiscover diff --git a/docs/conf.py b/docs/conf.py index de8ac1c714..2efcd7dd2a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,7 +58,7 @@ year = datetime.utcfromtimestamp(int(os.environ['SOURCE_DATE_EPOCH'])).year except (KeyError, ValueError): year = datetime.now().year -copyright = '%d, %s' % (year, author) +copyright = f'{year}, {author}' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/config.rst b/docs/config.rst index 0c87a81b91..68f92eebc1 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -17,9 +17,9 @@ Location You can place your ``glances.conf`` file in the following locations: ==================== ============================================================= -``Linux``, ``SunOS`` ~/.config/glances/, /etc/glances/, /usr/share/docs/glances/ -``*BSD`` ~/.config/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/ -``macOS`` ~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/ +``Linux``, ``SunOS`` ~/.config/glances/, /etc/glances/, /usr/share/doc/glances/ +``*BSD`` ~/.config/glances/, /usr/local/etc/glances/, /usr/share/doc/glances/ +``macOS`` ~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/doc/glances/ ``Windows`` %APPDATA%\\glances\\glances.conf ``All`` + /share/doc/glances/ ==================== ============================================================= @@ -45,10 +45,10 @@ A first section (called global) is available: # It is also possible to overwrite it in each plugin section refresh=2 # Should Glances check if a newer version is available on PyPI ? - check_update=false + check_update=true # History size (maximum number of values) - # Default is 28800: 1 day with 1 point every 3 seconds - history_size=28800 + # Default is 1200 values (~1h with the default refresh rate) + history_size=1200 # Set the way Glances should display the date (default is %Y-%m-%d %H:%M:%S %Z) #strftime_format="%Y-%m-%d %H:%M:%S %Z" # Define external directory for loading additional plugins diff --git a/docs/glances.rst b/docs/glances.rst index 3f3a8a5baa..f7d42ba673 100644 --- a/docs/glances.rst +++ b/docs/glances.rst @@ -34,16 +34,17 @@ CONFIGURATION EXAMPLES -------- -Monitor local machine (standalone mode): +Monitor local machine, also called standalone mode, +with the Text-based user interface (TUI): $ glances -To monitor the local machine with the web interface (Web UI), +To monitor the local machine with the Web user interface (WebUI), , run the following command line: $ glances -w -then, open a web browser to the provided URL. +then, open a Web Browser to the provided URL. Monitor local machine and export stats to a CSV file: @@ -71,10 +72,16 @@ Connect to a Glances server and export stats to a StatsD server: $ glances -c --export statsd -Start the client browser (browser mode): +Start the TUI Central Glances Browser: $ glances --browser +Start the WebUI Central Glances Browser (new in Glances 4.3 or higher): + + $ glances --browser -w + +If you do not want to see the local Glances Web Server in the browser list please use --disable-autodiscover option. + AUTHOR ------ diff --git a/docs/man/glances.1 b/docs/man/glances.1 index ef009e0d45..89c7502b90 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -1,3 +1,4 @@ +'\" t .\" Man page generated from reStructuredText. . . @@ -27,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "GLANCES" "1" "Jun 29, 2024" "4.2.0_beta01" "Glances" +.TH "GLANCES" "1" "Mar 22, 2025" "4.3.1" "Glances" .SH NAME glances \- An eye on your system .SH SYNOPSIS @@ -189,7 +190,8 @@ run Glances in server mode .INDENT 0.0 .TP .B \-\-browser -start the client browser (list of servers) +start TUI Central Glances Browser +use –browser \-w to start WebUI Central Glances Browser .UNINDENT .INDENT 0.0 .TP @@ -558,25 +560,24 @@ A template is available in the \fB/usr{,/local}/share/doc/glances\fP .sp You can place your \fBglances.conf\fP file in the following locations: .TS -center; -|l|l|. -_ +box center; +l|l. T{ \fBLinux\fP, \fBSunOS\fP T} T{ -~/.config/glances/, /etc/glances/, /usr/share/docs/glances/ +~/.config/glances/, /etc/glances/, /usr/share/doc/glances/ T} _ T{ \fB*BSD\fP T} T{ -~/.config/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/ +~/.config/glances/, /usr/local/etc/glances/, /usr/share/doc/glances/ T} _ T{ \fBmacOS\fP T} T{ -~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/ +~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/doc/glances/ T} _ T{ @@ -593,7 +594,6 @@ T} T{ /share/doc/glances/ .UNINDENT T} -_ .TE .INDENT 0.0 .IP \(bu 2 @@ -612,25 +612,23 @@ A first section (called global) is available: .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX [global] # Refresh rate (default is a minimum of 2 seconds) # Can be overwritten by the \-t option # It is also possible to overwrite it in each plugin section refresh=2 # Should Glances check if a newer version is available on PyPI ? -check_update=false +check_update=true # History size (maximum number of values) -# Default is 28800: 1 day with 1 point every 3 seconds -history_size=28800 +# Default is 1200 values (~1h with the default refresh rate) +history_size=1200 # Set the way Glances should display the date (default is %Y\-%m\-%d %H:%M:%S %Z) #strftime_format=\(dq%Y\-%m\-%d %H:%M:%S %Z\(dq # Define external directory for loading additional plugins # The layout follows the glances standard for plugin definitions #plugin_dir=/home/user/dev/plugins -.ft P -.fi +.EE .UNINDENT .UNINDENT .sp @@ -638,8 +636,7 @@ than a second one concerning the user interface: .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX [outputs] # Options for all UIs #\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- @@ -676,8 +673,7 @@ max_processes_display=25 # Comma separated list of HTTP request headers that should be supported for cross\-origin requests. # Default is * #cors_headers=* -.ft P -.fi +.EE .UNINDENT .UNINDENT .sp @@ -686,8 +682,7 @@ have a section. Below is an example for the CPU plugin: .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX [cpu] disable=False refresh=3 @@ -703,8 +698,7 @@ system_critical=90 steal_careful=50 steal_warning=70 steal_critical=90 -.ft P -.fi +.EE .UNINDENT .UNINDENT .sp @@ -712,8 +706,7 @@ an InfluxDB export module: .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX [influxdb] # Configuration for the \-\-export influxdb option # https://influxdb.com/ @@ -724,8 +717,7 @@ password=root db=glances prefix=localhost #tags=foo:bar,spam:eggs -.ft P -.fi +.EE .UNINDENT .UNINDENT .sp @@ -733,8 +725,7 @@ or a Nginx AMP: .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX [amp_nginx] # Nginx status page should be enabled (https://easyengine.io/tutorials/nginx/status\-page/) enable=true @@ -742,8 +733,7 @@ regex=\e/usr\e/sbin\e/nginx refresh=60 one_line=false status_url=http://localhost/nginx_status -.ft P -.fi +.EE .UNINDENT .UNINDENT .sp @@ -753,13 +743,11 @@ of an InfluxDB export to the current hostname, use: .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX [influxdb] \&... prefix=\(gahostname\(ga -.ft P -.fi +.EE .UNINDENT .UNINDENT .sp @@ -767,13 +755,11 @@ Or if you want to add the Operating System name as a tag: .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX [influxdb] \&... tags=system:\(gauname \-a\(ga -.ft P -.fi +.EE .UNINDENT .UNINDENT .SH LOGGING @@ -796,8 +782,7 @@ format): .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX { \(dqversion\(dq: 1, \(dqdisable_existing_loggers\(dq: \(dqFalse\(dq, @@ -856,8 +841,7 @@ format): } } } -.ft P -.fi +.EE .UNINDENT .UNINDENT .sp @@ -865,11 +849,9 @@ and start Glances using the following command line: .INDENT 0.0 .INDENT 3.5 .sp -.nf -.ft C +.EX LOG_CFG=/glances.json glances -.ft P -.fi +.EE .UNINDENT .UNINDENT .sp @@ -882,14 +864,15 @@ is hosted. .UNINDENT .SH EXAMPLES .sp -Monitor local machine (standalone mode): +Monitor local machine, also called standalone mode, +with the Text\-based user interface (TUI): .INDENT 0.0 .INDENT 3.5 $ glances .UNINDENT .UNINDENT .sp -To monitor the local machine with the web interface (Web UI), +To monitor the local machine with the Web user interface (WebUI), , run the following command line: .INDENT 0.0 .INDENT 3.5 @@ -897,7 +880,7 @@ $ glances \-w .UNINDENT .UNINDENT .sp -then, open a web browser to the provided URL. +then, open a Web Browser to the provided URL. .sp Monitor local machine and export stats to a CSV file: .INDENT 0.0 @@ -943,16 +926,25 @@ $ glances \-c –export statsd .UNINDENT .UNINDENT .sp -Start the client browser (browser mode): +Start the TUI Central Glances Browser: .INDENT 0.0 .INDENT 3.5 $ glances –browser .UNINDENT .UNINDENT +.sp +Start the WebUI Central Glances Browser (new in Glances 4.3 or higher): +.INDENT 0.0 +.INDENT 3.5 +$ glances –browser \-w +.UNINDENT +.UNINDENT +.sp +If you do not want to see the local Glances Web Server in the browser list please use –disable\-autodiscover option. .SH AUTHOR .sp Nicolas Hennion aka Nicolargo <\X'tty: link mailto:contact@nicolargo.com'\fI\%contact@nicolargo.com\fP\X'tty: link'> .SH COPYRIGHT -2024, Nicolas Hennion +2025, Nicolas Hennion .\" Generated by docutils manpage writer. . diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9bdb5075fa..70d5062e7e 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -88,8 +88,8 @@ available network interfaces) and TCP port is ``61209``. In client/server mode, limits are set by the server side. -Central client -^^^^^^^^^^^^^^ +Central Glances Browser +^^^^^^^^^^^^^^^^^^^^^^^ .. image:: _static/browser.png @@ -102,6 +102,10 @@ Example: .. code-block:: ini [serverlist] + # Define columns (comma separated list of ::()) to grab/display + # Default is: system:hr_name,load:min5,cpu:total,mem:percent + # You can also add stats with key, like sensors:value:Ambient (key is case sensitive) + columns=system:hr_name,load:min5,cpu:total,mem:percent,memswap:percent # Define the static servers list server_1_name=xps server_1_alias=xps @@ -112,22 +116,30 @@ Example: Glances can also detect and display all Glances servers available on your network via the ``zeroconf`` protocol (not available on Windows): -To start the central client, use the following option: +To start the TUI Central Glances Browser, use the following option: .. code-block:: console client$ glances --browser -.. note:: - - Use ``--disable-autodiscover`` to disable the auto-discovery mode. - When the list is displayed, you can navigate through the Glances servers with up/down keys. It is also possible to sort the server using: - '1' is normal (do not sort) - '2' is using sorting with ascending order (ONLINE > SNMP > PROTECTED > OFFLINE > UNKNOWN) - '3' is using sorting with descending order (UNKNOWN > OFFLINE > PROTECTED > SNMP > ONLINE) +To start the WebUI Central Glances Browser (new in Glances 4.3 or higher), use the following option: + +.. code-block:: console + + client$ glances --browser -w + +Open the URL (/browser) and click on the server to display stats. + +.. note:: + + Use ``--disable-autodiscover`` to disable the auto-discovery mode. + SNMP ^^^^ diff --git a/glances/README.txt b/glances/README.txt index 3e6de7eb19..eac53b9cfd 100644 --- a/glances/README.txt +++ b/glances/README.txt @@ -2,36 +2,3 @@ You are in the main Glances source folder. This page is **ONLY** for developers. If you are looking for the user manual, please follow this link: https://glances.readthedocs.io/en/stable/ - -=== - -__init__.py Global module init -__main__.py Entry point for Glances module -config.py Manage the configuration file -globals.py Share variables upon modules -main.py Main script to rule them up... -client.py Glances client -server.py Glances server -webserver.py Glances web server (Based on FastAPI) -autodiscover.py Glances autodiscover module (via zeroconf) -standalone.py Glances standalone (curses interface) -password.py Manage password for Glances client/server -stats.py The stats manager -timer.py The timer class -actions.py Manage trigger actions (via mustache) -snmp.py Glances SNMP client (via pysnmp) -... -plugins - => Glances plugins - ... -outputs - => Glances UI - glances_curses.py The curses interface - glances_restful-api.py The HTTP/API & Web based interface - ... -exports - => Glances exports - ... -amps - => Glances Application Monitoring Processes (AMP) - ... diff --git a/glances/__init__.py b/glances/__init__.py index 057f637fea..f7f6e52f3d 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -19,7 +19,7 @@ # Global name # Version should start and end with a numerical char # See https://packaging.python.org/specifications/core-metadata/#version -__version__ = '4.2.0_beta04' +__version__ = "4.3.2_dev01" __apiversion__ = '4' __author__ = 'Nicolas Hennion ' __license__ = 'LGPLv3' diff --git a/glances/amps_list.py b/glances/amps_list.py index 98fddff97d..de388251ec 100644 --- a/glances/amps_list.py +++ b/glances/amps_list.py @@ -105,7 +105,7 @@ def update(self): amps_list = self._build_amps_list(v, processlist) - if len(amps_list) > 0: + if amps_list: # At least one process is matching the regex logger.debug(f"AMPS: {len(amps_list)} processes {k} detected ({amps_list})") # Call the AMP update method diff --git a/glances/client.py b/glances/client.py index a8f659bd69..dd7867785e 100644 --- a/glances/client.py +++ b/glances/client.py @@ -11,15 +11,20 @@ import sys import time +from defusedxml import xmlrpc + from glances import __version__ -from glances.globals import Fault, ProtocolError, ServerProxy, Transport, json_loads +from glances.globals import json_loads from glances.logger import logger from glances.outputs.glances_curses import GlancesCursesClient from glances.stats_client import GlancesStatsClient from glances.timer import Counter +# Correct issue #1025 by monkey path the xmlrpc lib +xmlrpc.monkey_patch() + -class GlancesClientTransport(Transport): +class GlancesClientTransport(xmlrpc.xmlrpc_client.Transport): """This class overwrite the default XML-RPC transport and manage timeout.""" def set_timeout(self, timeout): @@ -57,7 +62,7 @@ def __init__(self, config=None, args=None, timeout=7, return_to_browser=False): # Configure the server timeout transport.set_timeout(timeout) try: - self.client = ServerProxy(self.uri, transport=transport) + self.client = xmlrpc.xmlrpc_client.ServerProxy(self.uri, transport=transport) except Exception as e: self.log_and_exit(f"Client couldn't create socket {self.uri}: {e}") @@ -101,7 +106,7 @@ def _login_glances(self): print(fall_back_msg) else: logger.info(fall_back_msg) - except ProtocolError as err: + except xmlrpc.xmlrpc_client.ProtocolError as err: # Other errors msg = f"Connection to server {self.uri} failed" if err.errcode == 401: @@ -197,7 +202,7 @@ def update_glances(self): except OSError: # Client cannot get server stats return "Disconnected" - except Fault: + except xmlrpc.xmlrpc_client.Fault: # Client cannot get server stats (issue #375) return "Disconnected" else: diff --git a/glances/client_browser.py b/glances/client_browser.py index d2a2f3ae4c..a6bba83a4e 100644 --- a/glances/client_browser.py +++ b/glances/client_browser.py @@ -1,22 +1,19 @@ # # This file is part of Glances. # -# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# SPDX-FileCopyrightText: 2024 Nicolas Hennion # # SPDX-License-Identifier: LGPL-3.0-only # """Manage the Glances client browser (list of Glances server).""" -import threading +import webbrowser -from glances.autodiscover import GlancesAutoDiscoverServer -from glances.client import GlancesClient, GlancesClientTransport -from glances.globals import Fault, ProtocolError, ServerProxy, json_loads +from glances.client import GlancesClient from glances.logger import LOG_FILENAME, logger from glances.outputs.glances_curses_browser import GlancesCursesBrowser -from glances.password_list import GlancesPasswordList as GlancesPassword -from glances.static_list import GlancesStaticServer +from glances.servers_list import GlancesServersList class GlancesClientBrowser: @@ -26,114 +23,28 @@ def __init__(self, config=None, args=None): # Store the arg/config self.args = args self.config = config - self.static_server = None - self.password = None - # Load the configuration file - self.load() - - # Start the autodiscover mode (Zeroconf listener) - if not self.args.disable_autodiscover: - self.autodiscover_server = GlancesAutoDiscoverServer() - else: - self.autodiscover_server = None + # Init the server list + self.servers_list = GlancesServersList(config=config, args=args) # Init screen self.screen = GlancesCursesBrowser(args=self.args) - def load(self): - """Load server and password list from the configuration file.""" - # Init the static server list (if defined) - self.static_server = GlancesStaticServer(config=self.config) - - # Init the password list (if defined) - self.password = GlancesPassword(config=self.config) - - def get_servers_list(self): - """Return the current server list (list of dict). - - Merge of static + autodiscover servers list. - """ - ret = [] - - if self.args.browser: - ret = self.static_server.get_servers_list() - if self.autodiscover_server is not None: - ret = self.static_server.get_servers_list() + self.autodiscover_server.get_servers_list() - - return ret - - def __get_uri(self, server): - """Return the URI for the given server dict.""" - # Select the connection mode (with or without password) - if server['password'] != "": - if server['status'] == 'PROTECTED': - # Try with the preconfigure password (only if status is PROTECTED) - clear_password = self.password.get_password(server['name']) - if clear_password is not None: - server['password'] = self.password.get_hash(clear_password) - return 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port']) - return 'http://{}:{}'.format(server['ip'], server['port']) - - def __update_stats(self, server): - """Update stats for the given server (picked from the server list)""" - # Get the server URI - uri = self.__get_uri(server) - - # Try to connect to the server - t = GlancesClientTransport() - t.set_timeout(3) - - # Get common stats - try: - s = ServerProxy(uri, transport=t) - except Exception as e: - logger.warning(f"Client browser couldn't create socket ({e})") - else: - # Mandatory stats - try: - # CPU% - # logger.info(f"CPU stats {s.getPlugin('cpu')}") - # logger.info(f"CPU views {s.getPluginView('cpu')}") - server['cpu_percent'] = json_loads(s.getPlugin('cpu'))['total'] - server['cpu_percent_decoration'] = json_loads(s.getPluginView('cpu'))['total']['decoration'] - # MEM% - server['mem_percent'] = json_loads(s.getPlugin('mem'))['percent'] - server['mem_percent_decoration'] = json_loads(s.getPluginView('mem'))['percent']['decoration'] - # OS (Human Readable name) - server['hr_name'] = json_loads(s.getPlugin('system'))['hr_name'] - server['hr_name_decoration'] = 'DEFAULT' - except (OSError, Fault, KeyError) as e: - logger.debug(f"Error while grabbing stats form server ({e})") - server['status'] = 'OFFLINE' - except ProtocolError as e: - if e.errcode == 401: - # Error 401 (Authentication failed) - # Password is not the good one... - server['password'] = None - server['status'] = 'PROTECTED' - else: - server['status'] = 'OFFLINE' - logger.debug(f"Cannot grab stats from server ({e.errcode} {e.errmsg})") - else: - # Status - server['status'] = 'ONLINE' - - # Optional stats (load is not available on Windows OS) - try: - # LOAD - server['load_min5'] = round(json_loads(s.getPlugin('load'))['min5'], 1) - server['load_min5_decoration'] = json_loads(s.getPluginView('load'))['min5']['decoration'] - except Exception as e: - logger.warning(f"Error while grabbing stats form server ({e})") - - return server - def __display_server(self, server): """Connect and display the given server""" # Display the Glances client for the selected server logger.debug(f"Selected server {server}") + if server['protocol'].lower() == 'rest': + # Display a popup + self.screen.display_popup( + 'Open the WebUI {}:{} in a Web Browser'.format(server['name'], server['port']), duration=1 + ) + # Try to open a Webbrowser + webbrowser.open(self.servers_list.get_uri(server), new=2, autoraise=1) + self.screen.active_server = None + return + # Connection can take time # Display a popup self.screen.display_popup('Connect to {}:{}'.format(server['name'], server['port']), duration=1) @@ -141,8 +52,11 @@ def __display_server(self, server): # A password is needed to access to the server's stats if server['password'] is None: # First of all, check if a password is available in the [passwords] section - clear_password = self.password.get_password(server['name']) - if clear_password is None or self.get_servers_list()[self.screen.active_server]['status'] == 'PROTECTED': + clear_password = self.servers_list.password.get_password(server['name']) + if ( + clear_password is None + or self.servers_list.get_servers_list()[self.screen.active_server]['status'] == 'PROTECTED' + ): # Else, the password should be enter by the user # Display a popup to enter password clear_password = self.screen.display_popup( @@ -150,7 +64,7 @@ def __display_server(self, server): ) # Store the password for the selected server if clear_password is not None: - self.set_in_selected('password', self.password.get_hash(clear_password)) + self.set_in_selected('password', self.servers_list.password.get_hash(clear_password)) # Display the Glance client on the selected server logger.info("Connect Glances client to the {} server".format(server['key'])) @@ -168,7 +82,7 @@ def __display_server(self, server): # Test if client and server are in the same major version if not client.login(): self.screen.display_popup( - "Sorry, cannot connect to '{}'\n" "See '{}' for more details".format(server['name'], LOG_FILENAME) + "Sorry, cannot connect to '{}'\nSee '{}' for more details".format(server['name'], LOG_FILENAME) ) # Set the ONLINE status for the selected server @@ -196,31 +110,16 @@ def __display_server(self, server): def __serve_forever(self): """Main client loop.""" # No need to update the server list - # It's done by the GlancesAutoDiscoverListener class (autodiscover.py) - # Or define statically in the configuration file (module static_list.py) - # For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...) - thread_list = {} while not self.screen.is_end: - logger.debug(f"Iter through the following server list: {self.get_servers_list()}") - for v in self.get_servers_list(): - key = v["key"] - thread = thread_list.get(key, None) - if thread is None or thread.is_alive() is False: - thread = threading.Thread(target=self.__update_stats, args=[v]) - thread_list[key] = thread - thread.start() - - # Update the screen (list or Glances client) + # Update the stats in the servers list + self.servers_list.update_servers_stats() + if self.screen.active_server is None: - # Display the Glances browser - self.screen.update(self.get_servers_list()) + # Display Glances browser (servers list) + self.screen.update(self.servers_list.get_servers_list()) else: - # Display the active server - self.__display_server(self.get_servers_list()[self.screen.active_server]) - - # exit key pressed - for thread in thread_list.values(): - thread.join() + # Display selected Glances server + self.__display_server(self.servers_list.get_servers_list()[self.screen.active_server]) def serve_forever(self): """Wrapper to the serve_forever function. @@ -235,13 +134,7 @@ def serve_forever(self): def set_in_selected(self, key, value): """Set the (key, value) for the selected server in the list.""" - # Static list then dynamic one - if self.screen.active_server >= len(self.static_server.get_servers_list()): - self.autodiscover_server.set_server( - self.screen.active_server - len(self.static_server.get_servers_list()), key, value - ) - else: - self.static_server.set_server(self.screen.active_server, key, value) + self.servers_list.set_in_selected(self.screen.active_server, key, value) def end(self): """End of the client browser session.""" diff --git a/glances/config.py b/glances/config.py index 84c6b7ea45..f89b39326f 100644 --- a/glances/config.py +++ b/glances/config.py @@ -81,18 +81,20 @@ def default_config_dir(): - Linux, SunOS, *BSD, macOS: /usr/share/doc (as defined in the setup.py files) - Windows: %APPDATA%\glances """ - path = [] - # Add venv path (solve issue #2803) - if in_virtualenv(): - path.append(os.path.join(sys.prefix, 'share', 'doc', 'glances')) + paths = [] - # Add others system path + # Add system path if LINUX or SUNOS or BSD or MACOS: - path.append('/usr/share/doc') + paths.append(os.path.join(sys.prefix, 'share', 'doc')) else: - path.append(os.environ.get('APPDATA')) + paths.append(os.environ.get('APPDATA')) - return path + # If we are in venv (issue #2803), sys.prefix != sys.base_prefix and we + # already added venv path with sys.prefix. Add base_prefix path too + if in_virtualenv(): + paths.append(os.path.join(sys.base_prefix, 'share', 'doc')) + + return [os.path.join(path, 'glances') if path is not None else '' for path in paths] def in_virtualenv(): @@ -261,9 +263,8 @@ def sections_set_default(self): # Sensors if not self.parser.has_section('sensors'): self.parser.add_section('sensors') - self.set_default_cwc('sensors', 'temperature_core', cwc=['60', '70', '80']) self.set_default_cwc('sensors', 'temperature_hdd', cwc=['45', '52', '60']) - self.set_default_cwc('sensors', 'battery', cwc=['80', '90', '95']) + self.set_default_cwc('sensors', 'battery', cwc=['70', '80', '90']) # Process list if not self.parser.has_section('processlist'): diff --git a/glances/cpu_percent.py b/glances/cpu_percent.py index 0f76e7d850..75eee3aa45 100644 --- a/glances/cpu_percent.py +++ b/glances/cpu_percent.py @@ -8,7 +8,8 @@ """CPU percent stats shared between CPU and Quicklook plugins.""" -from typing import List, Optional, TypedDict +import platform +from typing import Optional, TypedDict import psutil @@ -95,17 +96,18 @@ def get_info(self) -> CpuInfo: def __get_cpu_name() -> str: # Get the CPU name once from the /proc/cpuinfo file # Read the first line with the "model name" ("Model" for Raspberry Pi) + ret = f'CPU {platform.processor()}' try: cpuinfo_lines = open('/proc/cpuinfo').readlines() except (FileNotFoundError, PermissionError): logger.debug("No permission to read '/proc/cpuinfo'") - return 'CPU' + return ret for line in cpuinfo_lines: if line.startswith('model name') or line.startswith('Model') or line.startswith('cpu model'): return line.split(':')[1].strip() - return 'CPU' + return ret def get_cpu(self) -> float: """Update and/or return the CPU using the psutil library.""" @@ -121,7 +123,7 @@ def get_cpu(self) -> float: def _compute_cpu() -> float: return psutil.cpu_percent(interval=0.0) - def get_percpu(self) -> List[PerCpuPercentInfo]: + def get_percpu(self) -> list[PerCpuPercentInfo]: """Update and/or return the per CPU list using the psutil library.""" # Never update more than 1 time per cached_timer_cpu if self.timer_percpu.finished(): @@ -131,7 +133,7 @@ def get_percpu(self) -> List[PerCpuPercentInfo]: self.percpu_percent = self._compute_percpu() return self.percpu_percent - def _compute_percpu(self) -> List[PerCpuPercentInfo]: + def _compute_percpu(self) -> list[PerCpuPercentInfo]: psutil_percpu = enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True)) return [ { diff --git a/glances/exports/export.py b/glances/exports/export.py index dd6fa34639..6e817dfa89 100644 --- a/glances/exports/export.py +++ b/glances/exports/export.py @@ -106,7 +106,7 @@ def load_conf(self, section, mandatories=['host', 'port'], options=None): pass logger.debug(f"Load {section} from the Glances configuration file") - logger.debug(f"{section} parameters: {({opt: getattr(self, opt) for opt in mandatories + options})}") + logger.debug(f"{section} parameters: { ({opt: getattr(self, opt) for opt in mandatories + options}) }") return True diff --git a/glances/exports/glances_graph/__init__.py b/glances/exports/glances_graph/__init__.py index e76bdd6c2a..62ee8b8ec6 100644 --- a/glances/exports/glances_graph/__init__.py +++ b/glances/exports/glances_graph/__init__.py @@ -33,7 +33,7 @@ def __init__(self, config=None, args=None): self.export_enable = self.load_conf('graph', options=['path', 'generate_every', 'width', 'height', 'style']) # Manage options (command line arguments overwrite configuration file) - self.path = args.export_graph_path or self.path + self.path = self.path or args.export_graph_path self.generate_every = int(getattr(self, 'generate_every', 0) or 0) self.width = int(getattr(self, 'width', 800) or 800) self.height = int(getattr(self, 'height', 600) or 600) diff --git a/glances/exports/glances_influxdb/__init__.py b/glances/exports/glances_influxdb/__init__.py index cff8012cb3..0de532c908 100644 --- a/glances/exports/glances_influxdb/__init__.py +++ b/glances/exports/glances_influxdb/__init__.py @@ -98,7 +98,7 @@ def _normalize(self, name, columns, points): # issue1871 - Check if a key exist. If a key exist, the value of # the key should be used as a tag to identify the measurement. keys_list = [k.split('.')[0] for k in columns if k.endswith('.key')] - if len(keys_list) == 0: + if not keys_list: keys_list = [None] for measurement in keys_list: @@ -150,7 +150,7 @@ def export(self, name, columns, points): if self.prefix is not None: name = self.prefix + '.' + name # Write input to the InfluxDB database - if len(points) == 0: + if not points: logger.debug(f"Cannot export empty {name} stats to InfluxDB") else: try: diff --git a/glances/exports/glances_influxdb2/__init__.py b/glances/exports/glances_influxdb2/__init__.py index 62d90a5492..0dc3b3453b 100644 --- a/glances/exports/glances_influxdb2/__init__.py +++ b/glances/exports/glances_influxdb2/__init__.py @@ -106,7 +106,7 @@ def _normalize(self, name, columns, points): # issue1871 - Check if a key exist. If a key exist, the value of # the key should be used as a tag to identify the measurement. keys_list = [k.split('.')[0] for k in columns if k.endswith('.key')] - if len(keys_list) == 0: + if not keys_list: keys_list = [None] for measurement in keys_list: @@ -158,7 +158,7 @@ def export(self, name, columns, points): if self.prefix is not None: name = self.prefix + '.' + name # Write input to the InfluxDB database - if len(points) == 0: + if not points: logger.debug(f"Cannot export empty {name} stats to InfluxDB") else: try: diff --git a/glances/exports/glances_mqtt/__init__.py b/glances/exports/glances_mqtt/__init__.py index 6c3eaec27c..aea77b3552 100755 --- a/glances/exports/glances_mqtt/__init__.py +++ b/glances/exports/glances_mqtt/__init__.py @@ -79,6 +79,25 @@ def init(self): client_id='glances_' + self.devicename, clean_session=False, ) + + def on_connect(client, userdata, flags, reason_code, properties): + """Action to perform when connecting.""" + self.client.publish( + topic='/'.join([self.topic, self.devicename, "availability"]), payload="online", retain=True + ) + + def on_disconnect(client, userdata, flags, reason_code, properties): + """Action to perform when the connection is over.""" + self.client.publish( + topic='/'.join([self.topic, self.devicename, "availability"]), payload="offline", retain=True + ) + + client.on_connect = on_connect + client.on_disconnect = on_disconnect + client.will_set( + topic='/'.join([self.topic, self.devicename, "availability"]), payload="offline", retain=True + ) + client.username_pw_set(username=self.user, password=self.password) if self.tls: client.tls_set(certifi.where()) diff --git a/glances/folder_list.py b/glances/folder_list.py index 67554a4a53..af1ef035c6 100644 --- a/glances/folder_list.py +++ b/glances/folder_list.py @@ -113,13 +113,13 @@ def __get__(self, item, key): def update(self, key='path'): """Update the command result attributed.""" # Only continue if monitor list is not empty - if len(self.__folder_list) == 0: + if not self.__folder_list: return self.__folder_list # Iter upon the folder list for i in range(len(self.get())): # Update folder size - if not self.first_grab and not self.timer_folders[i].finished(): + if not self.first_grab and i in self.timer_folders and not self.timer_folders[i].finished(): continue # Set the key (see issue #2327) self.__folder_list[i]['key'] = key @@ -132,7 +132,8 @@ def update(self, key='path'): ) ) # Reset the timer - self.timer_folders[i].reset() + if i in self.timer_folders: + self.timer_folders[i].reset() # It is no more the first time... self.first_grab = False diff --git a/glances/globals.py b/glances/globals.py index e41c2e6d84..7bd291ce77 100644 --- a/glances/globals.py +++ b/glances/globals.py @@ -24,21 +24,15 @@ import subprocess import sys import weakref +from collections import OrderedDict from configparser import ConfigParser, NoOptionError, NoSectionError from datetime import datetime from operator import itemgetter, methodcaller from statistics import mean -from typing import Any, Dict, List, Union +from typing import Any, Optional, Union from urllib.error import HTTPError, URLError from urllib.parse import urlparse from urllib.request import Request, urlopen -from xmlrpc.client import Fault, ProtocolError, Server, ServerProxy, Transport -from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer - -from defusedxml.xmlrpc import monkey_patch - -# Correct issue #1025 by monkey path the xmlrpc lib -monkey_patch() # Prefer faster libs for JSON (de)serialization # Preference Order: orjson > ujson > json (builtin) @@ -275,19 +269,50 @@ def safe_makedirs(path): raise -def pretty_date(time=False): +def get_time_diffs(ref, now): + if isinstance(ref, int): + diff = now - datetime.fromtimestamp(ref) + elif isinstance(ref, datetime): + diff = now - ref + elif not ref: + diff = 0 + + return diff + + +def get_first_true_val(conds): + return next(key for key, val in conds.items() if val) + + +def maybe_add_plural(count): + return "s" if count > 1 else "" + + +def build_str_when_more_than_seven_days(day_diff, unit): + scale = {'week': 7, 'month': 30, 'year': 365}[unit] + + count = day_diff // scale + + return str(count) + " " + unit + maybe_add_plural(count) + + +def pretty_date(ref, now=None): """ Get a datetime object or a int() Epoch timestamp and return a pretty string like 'an hour ago', 'Yesterday', '3 months ago', 'just now', etc Source: https://stackoverflow.com/questions/1551382/user-friendly-time-format-in-python + + Refactoring done in commit https://github.com/nicolargo/glances/commit/f6279baacd4cf0b27ca10df6dc01f091ea86a40a + break the function. Get back to the old fashion way. """ - now = datetime.now() - if isinstance(time, int): - diff = now - datetime.fromtimestamp(time) - elif isinstance(time, datetime): - diff = now - time - elif not time: + if not now: + now = datetime.now() + if isinstance(ref, int): + diff = now - datetime.fromtimestamp(ref) + elif isinstance(ref, datetime): + diff = now - ref + elif not ref: diff = 0 second_diff = diff.seconds day_diff = diff.days @@ -311,12 +336,15 @@ def pretty_date(time=False): if day_diff == 1: return "yesterday" if day_diff < 7: - return str(day_diff) + " days" + return str(day_diff) + " days" if day_diff > 1 else "a day" if day_diff < 31: - return str(day_diff // 7) + " weeks" + week = day_diff // 7 + return str(week) + " weeks" if week > 1 else "a week" if day_diff < 365: - return str(day_diff // 30) + " months" - return str(day_diff // 365) + " years" + month = day_diff // 30 + return str(month) + " months" if month > 1 else "a month" + year = day_diff // 365 + return str(year) + " years" if year > 1 else "an year" def urlopen_auth(url, username, password): @@ -342,7 +370,7 @@ def json_dumps(data) -> bytes: return b(res) -def json_loads(data: Union[str, bytes, bytearray]) -> Union[Dict, List]: +def json_loads(data: Union[str, bytes, bytearray]) -> Union[dict, list]: """Load a JSON buffer into memory as a Python object""" return json.loads(data) @@ -365,13 +393,22 @@ def dictlist(data, item): return None -def json_dumps_dictlist(data, item): +def dictlist_json_dumps(data, item): dl = dictlist(data, item) if dl is None: return None return json_dumps(dl) +def dictlist_first_key_value(data: list[dict], key, value) -> Optional[dict]: + """In a list of dict, return first item where key=value or none if not found.""" + try: + ret = next(item for item in data if key in item and item[key] == value) + except StopIteration: + ret = None + return ret + + def string_value_to_float(s): """Convert a string with a value and an unit to a float. Example: diff --git a/glances/main.py b/glances/main.py index a31e20e822..8a29fd72a4 100644 --- a/glances/main.py +++ b/glances/main.py @@ -18,7 +18,7 @@ from glances.config import Config from glances.globals import WINDOWS, disable, enable from glances.logger import LOG_FILENAME, logger -from glances.processes import sort_processes_key_list +from glances.processes import sort_processes_stats_list class GlancesMain: @@ -70,9 +70,12 @@ class GlancesMain: Connect Glances to a Glances server and export stats to a StatsD server (client mode): $ glances -c --export statsd - Start the client browser (browser mode): + Start TUI Central Glances Browser: $ glances --browser + Start WebUI Central Glances Browser: + $ glances --browser -w + Display stats to stdout (one stat per line, possible to go inside stats using plugin.attribute): $ glances --stdout now,cpu.user,mem.used,load @@ -93,10 +96,68 @@ class GlancesMain: """ - def __init__(self): + def __init__(self, args_begin_at=1): """Manage the command line arguments.""" - # Read the command line arguments - self.args = self.parse_args() + self.init_glances(args_begin_at) + + def init_glances(self, args_begin_at): + """Main method to init Glances.""" + # Read the command line arguments or parse the one given in parameter (parser) + self.args = self.parse_args(args_begin_at) + + # Load the configuration file, if it exists + # This function should be called after the parse_args + # because the configuration file path can be defined + self.config = Config(self.args.conf_file) + + # Init Glances debug mode + self.init_debug(self.args) + + # Plugins Glances refresh rate + self.init_refresh_rate(self.args) + + # Manage Plugins disable/enable option + self.init_plugins(self.args) + + # Init Glances client/server mode + self.init_client_server(self.args) + + # Init UI mode + self.init_ui_mode(self.args) + + # Init the generate_graph tag + # Should be set to True to generate graphs + self.args.generate_graph = False + + # Export is only available in standalone or client mode (issue #614) + export_tag = self.args.export is not None and any(self.args.export) + if WINDOWS and export_tag: + # On Windows, export is possible but only in quiet mode + # See issue #1038 + logger.info("On Windows OS, export disable the Web interface") + self.args.quiet = True + self.args.webserver = False + elif not (self.is_standalone() or self.is_client()) and export_tag: + logger.critical("Export is only available in standalone or client mode") + sys.exit(2) + + # Filter is only available in standalone mode + if not self.args.process_filter and not self.is_standalone(): + logger.debug("Process filter is only available in standalone mode") + + # Cursor option is only available in standalone mode + if not self.args.disable_cursor and not self.is_standalone(): + logger.debug("Cursor is only available in standalone mode") + + # Let the plugins known the Glances mode + self.args.is_standalone = self.is_standalone() + self.args.is_client = self.is_client() + self.args.is_client_browser = self.is_client_browser() + self.args.is_server = self.is_server() + self.args.is_webserver = self.is_webserver() + + # Check mode compatibility + self.check_mode_compatibility() def version_msg(self): """Return the version message.""" @@ -266,8 +327,8 @@ def init_args(self): parser.add_argument( '--sort-processes', dest='sort_processes_key', - choices=sort_processes_key_list, - help='Sort processes by: {}'.format(', '.join(sort_processes_key_list)), + choices=sort_processes_stats_list, + help='Sort processes by: {}'.format(', '.join(sort_processes_stats_list)), ) # Display processes list by program name and not by thread parser.add_argument( @@ -318,7 +379,7 @@ def init_args(self): action='store_true', default=False, dest='browser', - help='start the client browser (list of servers)', + help='start TUI Central Glances Browser (use --browser -w to start WebUI Central Glances Browser)', ) parser.add_argument( '--disable-autodiscover', @@ -613,8 +674,8 @@ def init_plugins(self, args): args.network_sum = False args.network_cumul = False - # Processlist id updated in processcount - if getattr(args, 'enable_processlist', False): + # Processlist is updated in processcount + if getattr(args, 'enable_processlist', False) or getattr(args, 'enable_programlist', False): enable(args, 'processcount') # Set a default export_process_filter (with all process) when using the stdout mode @@ -725,68 +786,11 @@ def init_ui_mode(self, args): if args.disable_unicode: args.enable_separator = False - def parse_args(self): - """Parse command line arguments.""" - args = self.init_args().parse_args() - - # Load the configuration file, if it exists - # This function should be called after the parse_args - # because the configuration file path can be defined - self.config = Config(args.conf_file) - - # Init Glances debug mode - self.init_debug(args) - - # Plugins Glances refresh rate - self.init_refresh_rate(args) - - # Manage Plugins disable/enable option - self.init_plugins(args) - - # Init Glances client/server mode - self.init_client_server(args) - - # Init UI mode - self.init_ui_mode(args) - - # Init the generate_graph tag - # Should be set to True to generate graphs - args.generate_graph = False - - # Control parameter and exit if it is not OK - self.args = args - - # Export is only available in standalone or client mode (issue #614) - export_tag = self.args.export is not None and any(self.args.export) - if WINDOWS and export_tag: - # On Windows, export is possible but only in quiet mode - # See issue #1038 - logger.info("On Windows OS, export disable the Web interface") - self.args.quiet = True - self.args.webserver = False - elif not (self.is_standalone() or self.is_client()) and export_tag: - logger.critical("Export is only available in standalone or client mode") - sys.exit(2) - - # Filter is only available in standalone mode - if not args.process_filter and not self.is_standalone(): - logger.debug("Process filter is only available in standalone mode") - - # Cursor option is only available in standalone mode - if not args.disable_cursor and not self.is_standalone(): - logger.debug("Cursor is only available in standalone mode") - - # Let the plugins known the Glances mode - self.args.is_standalone = self.is_standalone() - self.args.is_client = self.is_client() - self.args.is_client_browser = self.is_client_browser() - self.args.is_server = self.is_server() - self.args.is_webserver = self.is_webserver() - - # Check mode compatibility - self.check_mode_compatibility() - - return args + def parse_args(self, args_begin_at): + """Parse command line arguments. + Glances args start at position args_begin_at. + """ + return self.init_args().parse_args(sys.argv[args_begin_at:]) def check_mode_compatibility(self): """Check mode compatibility""" @@ -811,11 +815,11 @@ def is_standalone(self): def is_client(self): """Return True if Glances is running in client mode.""" - return (self.args.client or self.args.browser) and not self.args.server + return (self.args.client or self.args.browser) and not self.args.server and not self.args.webserver def is_client_browser(self): """Return True if Glances is running in client browser mode.""" - return self.args.browser and not self.args.server + return self.args.browser and not self.args.server and not self.args.webserver def is_server(self): """Return True if Glances is running in server mode.""" diff --git a/glances/outputs/glances_colors.py b/glances/outputs/glances_colors.py index f006268b20..3cbf9e8da5 100644 --- a/glances/outputs/glances_colors.py +++ b/glances/outputs/glances_colors.py @@ -66,9 +66,9 @@ def __define_colors(self) -> None: curses.init_pair(3, curses.COLOR_GREEN, -1) curses.init_pair(5, curses.COLOR_MAGENTA, -1) else: - curses.init_pair(2, -1, curses.COLOR_RED) - curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_GREEN) - curses.init_pair(5, -1, curses.COLOR_MAGENTA) + curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED) + curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_GREEN) + curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_MAGENTA) curses.init_pair(4, curses.COLOR_BLUE, -1) curses.init_pair(6, curses.COLOR_RED, -1) curses.init_pair(7, curses.COLOR_GREEN, -1) @@ -77,8 +77,6 @@ def __define_colors(self) -> None: # Colors text styles self.DEFAULT = curses.color_pair(1) self.OK_LOG = curses.color_pair(3) | self.A_BOLD - self.NICE = curses.color_pair(8) - self.CPU_TIME = curses.color_pair(8) self.CAREFUL_LOG = curses.color_pair(4) | self.A_BOLD self.WARNING_LOG = curses.color_pair(5) | self.A_BOLD self.CRITICAL_LOG = curses.color_pair(2) | self.A_BOLD @@ -87,6 +85,7 @@ def __define_colors(self) -> None: self.WARNING = curses.color_pair(8) | self.A_BOLD self.CRITICAL = curses.color_pair(6) | self.A_BOLD self.INFO = curses.color_pair(4) + self.CPU_TIME = curses.color_pair(8) self.FILTER = self.A_BOLD self.SELECTED = self.A_BOLD self.SEPARATOR = curses.color_pair(1) @@ -111,13 +110,23 @@ def __define_colors(self) -> None: # Catch exception in TMUX pass + # Overwrite CAREFUL color + try: + if self.args.disable_bg: + curses.init_pair(12, curses.COLOR_BLUE, -1) + else: + curses.init_pair(12, -1, curses.COLOR_BLUE) + except Exception: + pass + else: + self.CAREFUL_LOG = curses.color_pair(12) | self.A_BOLD + def __define_bw(self) -> None: # The screen is NOT compatible with a colored design # switch to B&W text styles # ex: export TERM=xterm-mono self.DEFAULT = -1 self.OK_LOG = -1 - self.NICE = self.A_BOLD self.CPU_TIME = self.A_BOLD self.CAREFUL_LOG = self.A_BOLD self.WARNING_LOG = curses.A_UNDERLINE @@ -144,7 +153,6 @@ def get(self) -> dict: 'PROCESS': self.OK, 'PROCESS_SELECTED': self.OK | curses.A_UNDERLINE, 'STATUS': self.OK, - 'NICE': self.NICE, 'CPU_TIME': self.CPU_TIME, 'CAREFUL': self.CAREFUL, 'WARNING': self.WARNING, diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 89a14b652c..15f6e0c10b 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -16,7 +16,7 @@ from glances.logger import logger from glances.outputs.glances_colors import GlancesColors from glances.outputs.glances_unicode import unicode_message -from glances.processes import glances_processes, sort_processes_key_list +from glances.processes import glances_processes, sort_processes_stats_list from glances.timer import Timer # Import curses library for "normal" operating system @@ -97,16 +97,16 @@ class _GlancesCurses: # 'DOWN' > Down in the server list } - _sort_loop = sort_processes_key_list + _sort_loop = sort_processes_stats_list # Define top menu _top = ['quicklook', 'cpu', 'percpu', 'gpu', 'mem', 'memswap', 'load'] _quicklook_max_width = 58 # Define left sidebar - # This default list is also defined in the glances/outputs/static/js/uiconfig.json - # file for the web interface - # Both can be overwritten by the configuration file ([outputs] left_menu option) + # This variable is used in the make webui task in order to generate the + # glances/outputs/static/js/uiconfig.json file for the web interface + # This lidt can also be overwritten by the configuration file ([outputs] left_menu option) _left_sidebar = [ 'network', 'ports', @@ -124,8 +124,8 @@ class _GlancesCurses: _left_sidebar_min_width = 23 _left_sidebar_max_width = 34 - # Define right sidebar - _right_sidebar = ['vms', 'containers', 'processcount', 'amps', 'processlist', 'alert'] + # Define right sidebar in a method because it depends of self.args.programs + # See def _right_sidebar method def __init__(self, config=None, args=None): # Init @@ -203,6 +203,18 @@ def load_config(self, config): ) # Set the left sidebar list self._left_sidebar = config.get_list_value('outputs', 'left_menu', default=self._left_sidebar) + # Background color + self.args.disable_bg = config.get_bool_value('outputs', 'disable_bg', default=self.args.disable_bg) + + def _right_sidebar(self): + return [ + 'vms', + 'containers', + 'processcount', + 'amps', + 'programlist' if self.args.programs else 'processlist', + 'alert', + ] def _init_history(self): """Init the history option.""" @@ -660,11 +672,10 @@ def __display_header(self, stat_display): self.new_column() self.display_plugin(stat_display["ip"], display_optional=(self.term_window.getmaxyx()[1] >= 100)) self.new_column() - self.display_plugin( - stat_display["uptime"], add_space=-(self.get_stats_display_width(stat_display["cloud"]) != 0) - ) + cloud_width = self.get_stats_display_width(stat_display.get("cloud", 0)) + self.display_plugin(stat_display["uptime"], add_space=-(cloud_width != 0)) self.init_column() - if self.get_stats_display_width(stat_display["cloud"]) != 0: + if cloud_width != 0: # Second line (optional) self.new_line() self.display_plugin(stat_display["cloud"]) @@ -790,16 +801,24 @@ def __display_right(self, stat_display): # Display right sidebar self.new_column() - for p in self._right_sidebar: + for p in self._right_sidebar(): if (hasattr(self.args, 'enable_' + p) or hasattr(self.args, 'disable_' + p)) and p in stat_display: self.new_line() - if p == 'processlist': + if p in ['processlist', 'programlist']: + p_index = self._right_sidebar().index(p) + 1 self.display_plugin( - stat_display['processlist'], + stat_display[p], display_optional=(self.term_window.getmaxyx()[1] > 102), display_additional=(not MACOS), max_y=( - self.term_window.getmaxyx()[0] - self.get_stats_display_height(stat_display['alert']) - 2 + self.term_window.getmaxyx()[0] + - sum( + [ + self.get_stats_display_height(stat_display[i]) + for i in self._right_sidebar()[p_index:] + ] + ) + - 2 ), ) else: @@ -863,7 +882,7 @@ def display_popup( # Add the message for y, m in enumerate(sentence_list): - if len(m) > 0: + if m: popup.addnstr(2 + y, 2, m, len(m)) if popup_type == 'info': @@ -903,11 +922,14 @@ def display_popup( return None if popup_type == 'yesno': - # # Create a sub-window for the text field + # Create a sub-window for the text field sub_pop = popup.derwin(1, 2, len(sentence_list) + 1, len(m) + 2) sub_pop.attron(self.colors_list['FILTER']) # Init the field with the current value - sub_pop.addnstr(0, 0, '', 0) + try: + sub_pop.addnstr(0, 0, '', 0) + except curses.error: + pass # Display the popup popup.refresh() sub_pop.refresh() diff --git a/glances/outputs/glances_curses_browser.py b/glances/outputs/glances_curses_browser.py index 6893755f77..fa111e5f81 100644 --- a/glances/outputs/glances_curses_browser.py +++ b/glances/outputs/glances_curses_browser.py @@ -272,8 +272,8 @@ def __display_header(self, stats, x, y, screen_x, screen_y): msg = 'One Glances server available' else: msg = f'{stats_len} Glances servers available' - if self.args.disable_autodiscover: - msg += ' (auto discover is disabled)' + # if self.args.disable_autodiscover: + # msg += ' (auto discover is disabled)' if screen_y > 1: self.term_window.addnstr(y, x, msg, screen_x - x, self.colors_list['TITLE']) @@ -288,32 +288,49 @@ def __display_header(self, stats, x, y, screen_x, screen_y): return x, y + def __build_column_def(self, current_page): + """Define the column and it size to display in the browser""" + column_def = {'name': 16, 'ip': 15, 'status': 9, 'protocol': 8} + + # Add dynamic columns + for server_stat in current_page: + for k, v in server_stat.items(): + if k.endswith('_decoration'): + column_def[k.split('_decoration')[0]] = 6 + return column_def + def __display_server_list(self, stats, x, y, screen_x, screen_y): - if len(stats) == 0: + if not stats: # No server to display return False - stats_max = screen_y - 3 - - # Table of table - # Item description: [stats_id, column name, column size] - column_def = [ - ['name', 'Name', 16], - ['load_min5', 'LOAD', 6], - ['cpu_percent', 'CPU%', 5], - ['mem_percent', 'MEM%', 5], - ['status', 'STATUS', 9], - ['ip', 'IP', 15], - ['hr_name', 'OS', 16], - ] - y = 2 + stats_list = self._get_stats(stats) + start_line = self._page_max_lines * self._current_page + end_line = start_line + self.get_pagelines(stats_list) + current_page = stats_list[start_line:end_line] + column_def = self.__build_column_def(current_page) # Display table header + stats_max = screen_y - 3 + y = 2 xc = x + 2 - for cpt, c in enumerate(column_def): - if xc < screen_x and y < screen_y and c[1] is not None: - self.term_window.addnstr(y, xc, c[1], screen_x - x, self.colors_list['BOLD']) - xc += c[2] + self.space_between_column + # First line (plugin name) + for k, v in column_def.items(): + k_split = k.split('_') + if len(k_split) == 1: + xc += v + self.space_between_column + continue + if xc < screen_x and y < screen_y and v is not None: + self.term_window.addnstr(y, xc, k_split[0].upper(), screen_x - x, self.colors_list['BOLD']) + xc += v + self.space_between_column + xc = x + 2 + y += 1 + # Second line (for item/key) + for k, v in column_def.items(): + k_split = k.split('_') + if xc < screen_x and y < screen_y and v is not None: + self.term_window.addnstr(y, xc, ' '.join(k_split[1:]).upper(), screen_x - x, self.colors_list['BOLD']) + xc += v + self.space_between_column y += 1 # If a servers has been deleted from the list... @@ -322,11 +339,6 @@ def __display_server_list(self, stats, x, y, screen_x, screen_y): # Set the cursor position to the latest item self.cursor = len(stats) - 1 - stats_list = self._get_stats(stats) - start_line = self._page_max_lines * self._current_page - end_line = start_line + self.get_pagelines(stats_list) - current_page = stats_list[start_line:end_line] - # Display table line = 0 for server_stat in current_page: @@ -345,22 +357,24 @@ def __display_server_list(self, stats, x, y, screen_x, screen_y): # Display the line xc += 2 - for c in column_def: + for k, v in column_def.items(): if xc < screen_x and y < screen_y: # Display server stats - value = server_stat.get(c[0], '?') - if c[0] == 'name' and 'alias' in server_stat and server_stat['alias'] is not None: + value = server_stat.get(k, '?') + if isinstance(value, float): + value = round(value, 1) + if k == 'name' and 'alias' in server_stat and server_stat['alias'] is not None: value = server_stat['alias'] decoration = self.colors_list.get( - server_stat[c[0] + '_decoration'].replace('_LOG', '') - if c[0] + '_decoration' in server_stat + server_stat[k + '_decoration'].replace('_LOG', '') + if k + '_decoration' in server_stat else self.colors_list[server_stat['status']], self.colors_list['DEFAULT'], ) - if c[0] == 'status': + if k == 'status': decoration = self.colors_list[server_stat['status']] - self.term_window.addnstr(y, xc, format(value), c[2], decoration) - xc += c[2] + self.space_between_column + self.term_window.addnstr(y, xc, format(value), v, decoration) + xc += v + self.space_between_column cpt += 1 # Next line, next server... y += 1 diff --git a/glances/outputs/glances_restful_api.py b/glances/outputs/glances_restful_api.py index 1c64f4ac8c..aba6cd823e 100644 --- a/glances/outputs/glances_restful_api.py +++ b/glances/outputs/glances_restful_api.py @@ -9,24 +9,22 @@ """RestFull API interface class.""" import os +import socket import sys import tempfile import webbrowser +from typing import Annotated, Any, Union from urllib.parse import urljoin -from glances.stats import GlancesStats - -try: - from typing import Annotated, Any -except ImportError: - # Only for Python 3.8 - # To be removed when Python 3.8 support will be dropped - from typing_extensions import Annotated - from glances import __apiversion__, __version__ +from glances.events_list import glances_events from glances.globals import json_dumps from glances.logger import logger from glances.password import GlancesPassword +from glances.processes import glances_processes +from glances.servers_list import GlancesServersList +from glances.servers_list_dynamic import GlancesAutoDiscoverClient +from glances.stats import GlancesStats from glances.timer import Timer # FastAPI import @@ -107,6 +105,12 @@ def __init__(self, config=None, args=None): # Will be updated within route self.stats = None + # Init servers list (only for the browser mode) + if self.args.browser: + self.servers_list = GlancesServersList(config=config, args=args) + else: + self.servers_list = None + # cached_time is the minimum time interval between stats updates # i.e. HTTP/RESTful calls will not retrieve updated info until the time # since last update is passed (will retrieve old cached info instead) @@ -155,6 +159,14 @@ def __init__(self, config=None, args=None): # FastAPI Define routes self._app.include_router(self._router()) + # Enable auto discovering of the service + self.autodiscover_client = None + if not self.args.disable_autodiscover: + logger.info('Autodiscover is enabled with service name {}'.format(socket.gethostname().split('.', 1)[0])) + self.autodiscover_client = GlancesAutoDiscoverClient(socket.gethostname().split('.', 1)[0], self.args) + else: + logger.info("Glances autodiscover announce is disabled") + def load_config(self, config): """Load the outputs section of the configuration file.""" # Limit the number of processes to display in the WebUI @@ -169,12 +181,18 @@ def load_config(self, config): self.url_prefix = self.url_prefix.rstrip('/') logger.debug(f'URL prefix: {self.url_prefix}') - def __update__(self): + def __update_stats(self): # Never update more than 1 time per cached_time if self.timer.finished(): self.stats.update() self.timer = Timer(self.args.cached_time) + def __update_servers_list(self): + # Never update more than 1 time per cached_time + if self.timer.finished() and self.servers_list is not None: + self.servers_list.update_servers_stats() + self.timer = Timer(self.args.cached_time) + def authentication(self, creds: Annotated[HTTPBasicCredentials, Depends(security)]): """Check if a username/password combination is valid.""" if creds.username == self.args.username: @@ -195,9 +213,23 @@ def _router(self) -> APIRouter: # Create the main router router = APIRouter(prefix=self.url_prefix) - # REST API + # REST API route definition + # ========================== + + # HEAD router.add_api_route(f'{base_path}/status', self._api_status, methods=['HEAD', 'GET']) + # POST + router.add_api_route(f'{base_path}/events/clear/warning', self._events_clear_warning, methods=['POST']) + router.add_api_route(f'{base_path}/events/clear/all', self._events_clear_all, methods=['POST']) + router.add_api_route( + f'{base_path}/processes/extended/disable', self._api_disable_extended_processes, methods=['POST'] + ) + router.add_api_route( + f'{base_path}/processes/extended/{{pid}}', self._api_set_extended_processes, methods=['POST'] + ) + + # GET route_mapping = { f'{base_path}/config': self._api_config, f'{base_path}/config/{{section}}': self._api_config_section, @@ -209,6 +241,9 @@ def _router(self) -> APIRouter: f'{base_path}/all/limits': self._api_all_limits, f'{base_path}/all/views': self._api_all_views, f'{base_path}/pluginslist': self._api_plugins, + f'{base_path}/serverslist': self._api_servers_list, + f'{base_path}/processes/extended': self._api_get_extended_processes, + f'{base_path}/processes/{{pid}}': self._api_get_processes, f'{plugin_path}': self._api, f'{plugin_path}/history': self._api_history, f'{plugin_path}/history/{{nb}}': self._api_history, @@ -216,25 +251,39 @@ def _router(self) -> APIRouter: f'{plugin_path}/limits': self._api_limits, f'{plugin_path}/views': self._api_views, f'{plugin_path}/{{item}}': self._api_item, + f'{plugin_path}/{{item}}/views': self._api_item_views, f'{plugin_path}/{{item}}/history': self._api_item_history, f'{plugin_path}/{{item}}/history/{{nb}}': self._api_item_history, f'{plugin_path}/{{item}}/description': self._api_item_description, f'{plugin_path}/{{item}}/unit': self._api_item_unit, - f'{plugin_path}/{{item}}/{{value:path}}': self._api_value, + f'{plugin_path}/{{item}}/value/{{value:path}}': self._api_value, + f'{plugin_path}/{{item}}/{{key}}': self._api_key, + f'{plugin_path}/{{item}}/{{key}}/views': self._api_key_views, } - for path, endpoint in route_mapping.items(): router.add_api_route(path, endpoint) - # WEB UI + # Browser WEBUI + if self.args.browser: + # Template for the root browser.html file + router.add_api_route('/browser', self._browser, response_class=HTMLResponse) + + # Statics files + self._app.mount(self.url_prefix + '/static', StaticFiles(directory=self.STATIC_PATH), name="static") + logger.debug(f"The Browser WebUI is enable and got statics files in {self.STATIC_PATH}") + + bindmsg = f'Glances Browser Web User Interface started on {self.bind_url}browser' + logger.info(bindmsg) + print(bindmsg) + + # WEBUI if not self.args.disable_webui: # Template for the root index.html file router.add_api_route('/', self._index, response_class=HTMLResponse) # Statics files self._app.mount(self.url_prefix + '/static', StaticFiles(directory=self.STATIC_PATH), name="static") - - logger.info(f"The WebUI is enable and got statics files in {self.STATIC_PATH}") + logger.debug(f"The WebUI is enable and got statics files in {self.STATIC_PATH}") bindmsg = f'Glances Web User Interface started on {self.bind_url}' logger.info(bindmsg) @@ -284,6 +333,8 @@ def _start_uvicorn(self): def end(self): """End the Web server""" + if not self.args.disable_autodiscover and self.autodiscover_client: + self.autodiscover_client.close() logger.info("Close the Web server") def _index(self, request: Request): @@ -297,11 +348,21 @@ def _index(self, request: Request): refresh_time = request.query_params.get('refresh', default=max(1, int(self.args.time))) # Update the stat - self.__update__() + self.__update_stats() # Display return self._templates.TemplateResponse("index.html", {"request": request, "refresh_time": refresh_time}) + def _browser(self, request: Request): + """Return main browser.html (/browser) file. + + Note: This function is only called the first time the page is loaded. + """ + refresh_time = request.query_params.get('refresh', default=max(1, int(self.args.time))) + + # Display + return self._templates.TemplateResponse("browser.html", {"request": request, "refresh_time": refresh_time}) + def _api_status(self): """Glances API RESTful implementation. @@ -313,6 +374,26 @@ def _api_status(self): return GlancesJSONResponse({'version': __version__}) + def _events_clear_warning(self): + """Glances API RESTful implementation. + + Return a 200 status code. + + It's a post message to clean warning events + """ + glances_events.clean() + return GlancesJSONResponse({}) + + def _events_clear_all(self): + """Glances API RESTful implementation. + + Return a 200 status code. + + It's a post message to clean all events + """ + glances_events.clean(critical=True) + return GlancesJSONResponse({}) + def _api_help(self): """Glances API RESTful implementation. @@ -352,7 +433,7 @@ def _api_plugins(self): HTTP/1.1 404 Not Found """ # Update the stat - self.__update__() + self.__update_stats() try: plist = self.plugins_list @@ -361,6 +442,17 @@ def _api_plugins(self): return GlancesJSONResponse(plist) + def _api_servers_list(self): + """Glances API RESTful implementation. + + Return the JSON representation of the servers list (for browser mode) + HTTP/200 if OK + """ + # Update the servers list (and the stats for all the servers) + self.__update_servers_list() + + return GlancesJSONResponse(self.servers_list.get_servers_list() if self.servers_list else []) + def _api_all(self): """Glances API RESTful implementation. @@ -378,7 +470,7 @@ def _api_all(self): logger.debug(f"Debug file ({fname}) not found") # Update the stat - self.__update__() + self.__update_stats() try: # Get the RAW value of the stat ID @@ -431,7 +523,7 @@ def _api(self, plugin: str): self._check_if_plugin_available(plugin) # Update the stat - self.__update__() + self.__update_stats() try: # Get the RAW value of the stat ID @@ -462,7 +554,7 @@ def _api_top(self, plugin: str, nb: int = 0): self._check_if_plugin_available(plugin) # Update the stat - self.__update__() + self.__update_stats() try: # Get the RAW value of the stat ID @@ -489,7 +581,7 @@ def _api_history(self, plugin: str, nb: int = 0): self._check_if_plugin_available(plugin) # Update the stat - self.__update__() + self.__update_stats() try: # Get the RAW value of the stat ID @@ -549,7 +641,7 @@ def _api_item(self, plugin: str, item: str): self._check_if_plugin_available(plugin) # Update the stat - self.__update__() + self.__update_stats() try: # Get the RAW value of the stat views @@ -562,6 +654,78 @@ def _api_item(self, plugin: str, item: str): return GlancesJSONResponse(ret) + def _api_key(self, plugin: str, item: str, key: str): + """Glances API RESTful implementation. + + Return the JSON representation of plugin/item/key + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + self._check_if_plugin_available(plugin) + + # Update the stat + self.__update_stats() + + try: + # Get the RAW value of the stat views + ret = self.stats.get_plugin(plugin).get_raw_stats_key(item, key) + except Exception as e: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + f"Cannot get item {item} for key {key} in plugin {plugin} ({str(e)})", + ) + + return GlancesJSONResponse(ret) + + def _api_item_views(self, plugin: str, item: str): + """Glances API RESTful implementation. + + Return the JSON view representation of the couple plugin/item + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + self._check_if_plugin_available(plugin) + + # Update the stat + self.__update_stats() + + try: + # Get the RAW value of the stat views + ret = self.stats.get_plugin(plugin).get_views().get(item) + except Exception as e: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + f"Cannot get item {item} in plugin view {plugin} ({str(e)})", + ) + + return GlancesJSONResponse(ret) + + def _api_key_views(self, plugin: str, item: str, key: str): + """Glances API RESTful implementation. + + Return the JSON view representation of plugin/item/key + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + self._check_if_plugin_available(plugin) + + # Update the stat + self.__update_stats() + + try: + # Get the RAW value of the stat views + ret = self.stats.get_plugin(plugin).get_views().get(key).get(item) + except Exception as e: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + f"Cannot get item {item} for key {key} in plugin view {plugin} ({str(e)})", + ) + + return GlancesJSONResponse(ret) + def _api_item_history(self, plugin: str, item: str, nb: int = 0): """Glances API RESTful implementation. @@ -574,7 +738,7 @@ def _api_item_history(self, plugin: str, item: str, nb: int = 0): self._check_if_plugin_available(plugin) # Update the stat - self.__update__() + self.__update_stats() try: # Get the RAW value of the stat history @@ -622,7 +786,7 @@ def _api_item_unit(self, plugin: str, item: str): else: return GlancesJSONResponse(ret) - def _api_value(self, plugin: str, item: str, value: Any): + def _api_value(self, plugin: str, item: str, value: Union[str, int, float]): """Glances API RESTful implementation. Return the process stats (dict) for the given item=value @@ -633,7 +797,7 @@ def _api_value(self, plugin: str, item: str, value: Any): self._check_if_plugin_available(plugin) # Update the stat - self.__update__() + self.__update_stats() try: # Get the RAW value @@ -745,3 +909,62 @@ def _api_args_item(self, item: str): raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args item ({str(e)})") return GlancesJSONResponse(args_json) + + def _api_set_extended_processes(self, pid: str): + """Glances API RESTful implementation. + + Set the extended process stats for the given PID + HTTP/200 if OK + HTTP/400 if PID is not found + HTTP/404 if others error + """ + process_stats = glances_processes.get_stats(int(pid)) + + if not process_stats: + raise HTTPException(status.HTTP_404_NOT_FOUND, f"Unknown PID process {pid}") + + glances_processes.extended_process = process_stats + + return GlancesJSONResponse(True) + + def _api_disable_extended_processes(self): + """Glances API RESTful implementation. + + Disable extended process stats + HTTP/200 if OK + HTTP/400 if PID is not found + HTTP/404 if others error + """ + glances_processes.extended_process = None + + return GlancesJSONResponse(True) + + def _api_get_extended_processes(self): + """Glances API RESTful implementation. + + Get the extended process stats (if set before) + HTTP/200 if OK + HTTP/400 if PID is not found + HTTP/404 if others error + """ + process_stats = glances_processes.get_extended_stats() + + if not process_stats: + process_stats = {} + + return GlancesJSONResponse(process_stats) + + def _api_get_processes(self, pid: str): + """Glances API RESTful implementation. + + Get the process stats for the given PID + HTTP/200 if OK + HTTP/400 if PID is not found + HTTP/404 if others error + """ + process_stats = glances_processes.get_stats(int(pid)) + + if not process_stats: + raise HTTPException(status.HTTP_404_NOT_FOUND, f"Unknown PID process {pid}") + + return GlancesJSONResponse(process_stats) diff --git a/glances/outputs/glances_sparklines.py b/glances/outputs/glances_sparklines.py index b78d40b02a..46798ff02d 100644 --- a/glances/outputs/glances_sparklines.py +++ b/glances/outputs/glances_sparklines.py @@ -79,7 +79,7 @@ def get(self, overwrite=''): ret = sparklines(self.percents, minimum=0, maximum=100)[0] if self.__display_value: percents_without_none = [x for x in self.percents if x is not None] - if len(percents_without_none) > 0: + if percents_without_none: ret = f'{ret}{percents_without_none[-1]:5.1f}{self.__unit_char}' ret = nativestr(ret) if overwrite and len(overwrite) < len(ret) - 6: diff --git a/glances/outputs/glances_stdout_apidoc.py b/glances/outputs/glances_stdout_apidoc.py index f152b90302..95f875851f 100644 --- a/glances/outputs/glances_stdout_apidoc.py +++ b/glances/outputs/glances_stdout_apidoc.py @@ -40,6 +40,12 @@ It is also ran automatically when Glances is started in Web server mode (-w). +If you want to enable the Glances Central Browser, use: + +.. code-block:: bash + + # glances -w --browser --disable-webui + API URL ------- @@ -71,7 +77,7 @@ ------------- It is possible to change the Web UI refresh rate (default is 2 seconds) using the following option in the URL: -``http://localhost:61208/glances/?refresh=5`` +``http://localhost:61208/?refresh=5`` """ @@ -206,7 +212,7 @@ def print_plugin_item_value(plugin, stat, stat_export): if item and value and stat.get_stats_value(item, value): print('Get a specific item when field matches the given value::') print('') - print(f' # curl {API_URL}/{plugin}/{item}/{value}') + print(f' # curl {API_URL}/{plugin}/{item}/value/{value}') print(indent_stat(json.loads(stat.get_stats_value(item, value)))) print('') @@ -219,7 +225,30 @@ def print_all(): print('Get all Glances stats::') print('') print(f' # curl {API_URL}/all') - print(' Return a very big dictionary (avoid using this request, performances will be poor)...') + print(' Return a very big dictionary with all stats') + print('') + print('Note: Update is done automatically every time /all or / is called.') + print('') + + +def print_processes(): + sub_title = 'GET stats of a specific process' + print(sub_title) + print('-' * len(sub_title)) + print('') + print('Get stats for process with PID == 777::') + print('') + print(f' # curl {API_URL}/processes/777') + print(' Return stats for process (dict)') + print('') + print('Enable extended stats for process with PID == 777 (only one process at a time can be enabled)::') + print('') + print(f' # curl -X POST {API_URL}/processes/extended/777') + print(f' # curl {API_URL}/all') + print(f' # curl {API_URL}/processes/777') + print(' Return stats for process (dict)') + print('') + print('Note: Update *is not* done automatically when you call /processes/.') print('') @@ -310,6 +339,21 @@ def print_limits(stats): print('') +def print_plugin_post_events(): + sub_title = 'POST clear events' + print(sub_title) + print('-' * len(sub_title)) + print('') + print('Clear all alarms from the list::') + print('') + print(f' # curl -H "Content-Type: application/json" -X POST {API_URL}/events/clear/all') + print('') + print('Clear warning alarms from the list::') + print('') + print(f' # curl -H "Content-Type: application/json" -X POST {API_URL}/events/clear/warning') + print('') + + class GlancesStdoutApiDoc: """This class manages the fields description display.""" @@ -338,6 +382,8 @@ def update(self, stats, duration=1): stat = stats.get_plugin(plugin) print_plugin_stats(plugin, stat) print_plugin_description(plugin, stat) + if plugin == 'alert': + print_plugin_post_events() stat_export = stat.get_export() if stat_export is None or stat_export == [] or stat_export == {}: @@ -347,6 +393,9 @@ def update(self, stats, duration=1): # Get all stats print_all() + # Get process stats + print_processes() + # Get top stats (only for plugins with a list of items) # Example for processlist plugin: get top 2 processes print_top(stats) diff --git a/glances/outputs/static/css/bootstrap.less b/glances/outputs/static/css/bootstrap.less deleted file mode 100644 index 3ca1d59741..0000000000 --- a/glances/outputs/static/css/bootstrap.less +++ /dev/null @@ -1,56 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -// Core variables and mixins -@import "./variables.less"; -@import "~bootstrap/less/mixins.less"; - -// Reset and dependencies -@import "~bootstrap/less/normalize.less"; -// @import "~bootstrap/less/print.less"; -// @import "~bootstrap/less/glyphicons.less"; - -// Core CSS -@import "~bootstrap/less/scaffolding.less"; -// @import "~bootstrap/less/type.less"; -// @import "~bootstrap/less/code.less"; -@import "~bootstrap/less/grid.less"; -@import "~bootstrap/less/tables.less"; -// @import "~bootstrap/less/forms.less"; -// @import "~bootstrap/less/buttons.less"; - -// Components -// @import "~bootstrap/less/component-animations.less"; -// @import "~bootstrap/less/dropdowns.less"; -// @import "~bootstrap/less/button-groups.less"; -// @import "~bootstrap/less/input-groups.less"; -// @import "~bootstrap/less/navs.less"; -// @import "~bootstrap/less/navbar.less"; -// @import "~bootstrap/less/breadcrumbs.less"; -// @import "~bootstrap/less/pagination.less"; -// @import "~bootstrap/less/pager.less"; -// @import "~bootstrap/less/labels.less"; -// @import "~bootstrap/less/badges.less"; -// @import "~bootstrap/less/jumbotron.less"; -// @import "~bootstrap/less/thumbnails.less"; -// @import "~bootstrap/less/alerts.less"; -@import "~bootstrap/less/progress-bars.less"; -// @import "~bootstrap/less/media.less"; -// @import "~bootstrap/less/list-group.less"; -// @import "~bootstrap/less/panels.less"; -// @import "~bootstrap/less/responsive-embed.less"; -// @import "~bootstrap/less/wells.less"; -// @import "~bootstrap/less/close.less"; - -// Components w/ JavaScript -// @import "~bootstrap/less/modals.less"; -// @import "~bootstrap/less/tooltip.less"; -// @import "~bootstrap/less/popovers.less"; -// @import "~bootstrap/less/carousel.less"; - -// Utility classes -@import "~bootstrap/less/utilities.less"; -@import "~bootstrap/less/responsive-utilities.less"; diff --git a/glances/outputs/static/css/custom.scss b/glances/outputs/static/css/custom.scss new file mode 100644 index 0000000000..a4439a48c9 --- /dev/null +++ b/glances/outputs/static/css/custom.scss @@ -0,0 +1,48 @@ +// Custom.scss + +// Option A: Include all of Bootstrap +// ================================== + +// Include any default variable overrides here (though functions won't be available) + +// @import "../node_modules/bootstrap/scss/bootstrap"; + +// Then add additional custom code here + + + +// // Option B: Include parts of Bootstrap +// // ==================================== + +// // 1. Include functions first (so you can manipulate colors, SVGs, calc, etc) +@import "../node_modules/bootstrap/scss/functions"; + +// // 2. Include any default variable overrides here +// $body-bg: black; + +// // 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets) +@import "../node_modules/bootstrap/scss/variables"; +@import "../node_modules/bootstrap/scss/variables-dark"; + +// // 4. Include any default map overrides here + +// // 5. Include remainder of required parts +@import "../node_modules/bootstrap/scss/maps"; +@import "../node_modules/bootstrap/scss/mixins"; +@import "../node_modules/bootstrap/scss/root"; + +// // 6. Optionally include any other parts as needed +@import "../node_modules/bootstrap/scss/utilities"; +@import "../node_modules/bootstrap/scss/reboot"; +@import "../node_modules/bootstrap/scss/type"; +@import "../node_modules/bootstrap/scss/images"; +@import "../node_modules/bootstrap/scss/containers"; +@import "../node_modules/bootstrap/scss/grid"; +@import "../node_modules/bootstrap/scss/helpers"; +@import "../node_modules/bootstrap/scss/tables"; +@import "../node_modules/bootstrap/scss/progress"; + +// // 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss` +@import "../node_modules/bootstrap/scss/utilities/api"; + +// // 8. Add additional custom code here diff --git a/glances/outputs/static/css/style.scss b/glances/outputs/static/css/style.scss index 2f803a6dbd..ba9d040e7c 100644 --- a/glances/outputs/static/css/style.scss +++ b/glances/outputs/static/css/style.scss @@ -1,172 +1,241 @@ +// Glances theme + +$glances-bg: #000; +$glances-fg: #CCC; +$glances-link-hover-color: #57cb6a; +$glances-fonts: "Lucida Sans Typewriter","Lucida Console",Monaco,"Bitstream Vera Sans Mono",monospace; +$glances-fonts-size: 14px; + +// Define colors and fonts + +// https://getbootstrap.com/docs/5.3/customize/css-variables/#root-variables +:root,[data-bs-theme=dark] { + --bs-body-bg: $glances-bg; + --bs-body-color: $glances-fg; + --bs-body-font-size: $glances-fonts-size; +} + body { - background: black; - color: #BBB; - font-family: "Lucida Sans Typewriter", "Lucida Console", Monaco, "Bitstream Vera Sans Mono", monospace; + background-color: $glances-bg; + color: $glances-fg; + font-family: $glances-fonts; + font-size: $glances-fonts-size; + overflow: hidden; } -.table { - display: table; - width: 100%; - max-width:100%; +.title { + font-weight: bold; } -.table-row-group { - display: table-row-group +.highlight { + font-weight: bold !important; + color: #5D4062 !important; } -.table-row { - display: table-row; +.ok, .status, .process { + color: #3E7B04 !important; } -.table-cell { - display: table-cell; - text-align: right; +.ok_log { + background-color: #3E7B04 !important; + color: white !important; } -.width-60 { - width: 60px; +.max { + color: #3E7B04 !important; + font-weight: bold !important; } -.width-80 { - width: 80px; +.careful { + color: #295183 !important; + font-weight: bold !important; } -.width-100 { - width: 100px; +.careful_log { + background-color: #295183 !important; + color: white !important; + font-weight: bold !important; } - -.plugin { - margin-bottom: 20px; - .table:last-child { - margin-bottom: 0; - } +.warning, .nice { + color: #5D4062 !important; + font-weight: bold !important; } -.plugin.table-row-group .table-row:last-child .table-cell { - padding-bottom: 20px; +.warning_log { + background-color: #5D4062 !important; + color: white !important; + font-weight: bold !important; } - -.underline { - text-decoration: underline +.critical { + color: #A30000 !important; + font-weight: bold !important; } -.bold { - font-weight: bold; +.critical_log { + background-color: #A30000 !important; + color: white !important; + font-weight: bold !important; } -.sort { - font-weight: bold; - color: white; +.error { + color: #EE6600 !important; + font-weight: bold !important; } -.sortable { - cursor: pointer; - text-decoration: underline; +.error_log { + background-color: #EE6600 !important; + color: white !important; + font-weight: bold !important; } -.text-right { - text-align: right; + +// Layout + +.container-fluid { + margin-left: 0px; + margin-right: 0px; + padding-left: 0px; + padding-right: 0px; } -.text-left { - text-align: left; + +.header { + height: 30px; } -.text-truncate { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + +.header-small { + height: 50px; } -.sidebar .table-cell:not(.text-left) { - padding-left: 10px; + +.header-small > div:nth-child(1) > section:nth-child(1) { + margin-bottom: 0em; } -.separator { - overflow:hidden; - height:5px; +.top-min { + height: 100px; } -.separator:after { - content:''; - display:block; - margin:-25px auto 0; - width:100%; - height:24px; - border-radius:125px / 12px; - box-shadow:0 0 8px #555555; +.top-max { + height: 180px; } -/* Theme */ +.sidebar-min { + overflow-y: auto; + height: calc(100vh - 30px - 100px); +} -.title { - font-weight: bold; - color: white; +.sidebar-max { + overflow-y: auto; + height: calc(100vh - 30px - 180px); } -.highlight { - font-weight: bold; - color: #5D4062; + +.inline { + display: inline-block; } -.ok, .status, .process { - color: #3E7B04; - /*font-weight: bold;*/ + +// Table + +.table { + margin-bottom: 0px; } -.ok_log { - background-color: #3E7B04; - color: white; - /*font-weight: bold;*/ + +.margin-top { + margin-top: 0.5em; } -.max { - color: #3E7B04; - font-weight: bold; + +.margin-bottom { + margin-bottom: 0.5em; } -.careful { - color: #295183; - font-weight: bold; + +.table-sm > :not(caption) > * > * { + padding-top: 0em; + padding-right: 0.25rem; + padding-bottom: 0em; + padding-left: 0.25rem; } -.careful_log { - background-color: #295183; - color: white; + +.sort { font-weight: bold; + color: white; } -.warning, .nice { - color: #5D4062; - font-weight: bold; + +.sortable { + cursor: pointer; + text-decoration: underline; } -.warning_log { - background-color: #5D4062; - color: white; - font-weight: bold; + +.text-truncate { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.critical { - color: #A30000; - font-weight: bold; + +#browser .table-hover tbody tr:hover td { + background: $glances-link-hover-color; } -.critical_log { - background-color: #A30000; - color: white; - font-weight: bold; + +// Plugins + +.plugin { + margin-bottom: 1em; } -.error { - color: #EE6600; - font-weight: bold; + +.button { + color: #99CCFF; /* Bleu clair high-tech */ + background: rgba(0, 0, 0, 0.4); /* Fond légèrement transparent */ + border: 1px solid #99CCFF; /* Bordure discrète */ + padding: 5px 10px; + border-radius: 5px; + letter-spacing: 1px; + cursor: pointer; + transition: all 0.2s ease-in-out; + position: relative; + overflow: hidden; } -/* Plugins */ +.button:hover { + background: rgba(153, 204, 255, 0.15); /* Légère coloration au survol */ + border-color: #B0D0FF; + color: #B0D0FF; +} -#processlist-plugin .table-cell { - padding: 0px 5px 0px 5px; - white-space: nowrap; +.button:active { + transform: scale(0.95); /* Légère réduction pour effet de pression */ + box-shadow: 0 0 8px rgba(153, 204, 255, 0.5); /* Flash léger */ } -#vms-plugin .table-cell { - padding: 0px 10px 0px 10px; - white-space: nowrap; +.frequency { + display: inline-block; + width: 8em; } -#containers-plugin .table-cell { - padding: 0px 10px 0px 10px; - white-space: nowrap; +#ip { + span { + padding-left: 10px; + } } #quicklook { + span { + padding: 0; + margin: 0; + padding-left: 10px; + } + span:nth-child(1) { + padding-left: 0px; + } + * > th, td { + margin: 0; + padding: 0; + } + * > th:nth-child(1), td:nth-child(1) { + width: 4em; + } + * > th:nth-last-child(1), td:nth-last-child(1) { + width: 4em; + } + * > td span { + display: inline-block; + width: 4em; + } .progress { - margin-bottom: 0px; min-width: 100px; background-color: #000; - height: 12px; + height: 1.5em; border-radius: 0px; text-align: right; } .progress-bar-ok { background-color: #3E7B04; + } .progress-bar-careful { background-color: #295183; @@ -185,152 +254,210 @@ body { } } -#amps .process-result { - max-width: 300px; - overflow: hidden; - white-space: pre-wrap; - padding-left: 10px; - text-overflow: ellipsis; +#cpu { + * > td span { + display: inline-block; + width: 4em; + } } -#gpu .gpu-name { - white-space: nowrap; - overflow: hidden; - width: 100%; - text-overflow: ellipsis; +#gpu { + * > td span { + display: inline-block; + width: 4em; + } } -#system > span:nth-child(1) { - padding-left: 0px; +#mem { + * > td span { + display: inline-block; + width: 4em; + } } -#system span { - padding-left: 10px; +#memswap { + * > td span { + display: inline-block; + width: 4em; + } } -#ip { - padding-left: 10px; +#load { + * > td span { + display: inline-block; + width: 3em; + } } -#ip span { - padding-left: 10px; +#vms { + span { + padding-left: 10px; + } + span:nth-child(1) { + padding-left: 0px; + } + .table { + margin-bottom: 1em; + } + * > th:not(:last-child), td:not(:last-child) { + width: 5em; + } + * > td:nth-child(2) { + width: 15em; + } + * > td:nth-child(3) { + width: 6em; + } + * > td:nth-child(5) { + text-align: right; + } + * > td:nth-child(7) { + width: 10em; + } + * > td:nth-child(6), td:nth-child(7), td:nth-child(8) { + text-overflow: ellipsis; + white-space: nowrap; + } } -#processcount > span:nth-child(1) { - padding-left: 0px; +#containers { + span { + padding-left: 10px; + } + span:nth-child(1) { + padding-left: 0px; + } + .table { + margin-bottom: 1em; + } + // Default column size + * > td:not(:last-child) { + width: 5em; + } + * > td:nth-child(1) { + width: 10em; + } + * > td:nth-child(2), td:nth-child(3) { + width: 15em; + } + * > td:nth-child(3) { + white-space: nowrap; + overflow: hidden; + } + // Status + * > td:nth-child(4) { + width: 6em; + } + * > td:nth-child(5) { + width: 10em; + text-overflow: ellipsis; + white-space: nowrap; + } + // Some column are align to the right + * > td:nth-child(7), td:nth-child(9), td:nth-child(11) { + text-align: right; + } + // Command + * > td:nth-child(13) { + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + } } -#processcount span { - padding-left: 10px; -} - -// /* Loading page */ - -// #loading-page .glances-logo { -// background: url('../images/glances.png') no-repeat center center; -// background-size: contain; -// } - -// @media (max-width: 750px) { -// #loading-page .glances-logo { -// height: 400px; -// } -// } -// @media (min-width: 750px) { -// #loading-page .glances-logo { -// height: 500px; -// } -// } - - -/* -Loading animation -source : https://github.com/lukehaas/css-loaders -*/ -#loading-page .loader:before, -#loading-page .loader:after, -#loading-page .loader { - border-radius: 50%; - width: 1em; - height: 1em; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation: loader 1.8s infinite ease-in-out; - animation: loader 1.8s infinite ease-in-out; -} -#loading-page .loader { - margin: auto; - font-size: 10px; - position: relative; - text-indent: -9999em; - -webkit-animation-delay: 0.16s; - animation-delay: 0.16s; +#processcount { + span { + padding-left: 10px; + } + span:nth-child(1) { + padding-left: 0px; + } + margin-bottom: 0px; } -#loading-page .loader:before { - left: -3.5em; + +#amps { + .process-result { + max-width: 300px; + overflow: hidden; + white-space: pre-wrap; + padding-left: 10px; + text-overflow: ellipsis; + } + .table { + margin-bottom: 1em; + } + * > td:nth-child(8) { + text-overflow: ellipsis; + white-space: nowrap; + } } -#loading-page .loader:after { - left: 3.5em; - -webkit-animation-delay: 0.32s; - animation-delay: 0.32s; + +#processlist div.extendedstats { + margin-bottom: 1em; + margin-top: 1em; } -#loading-page .loader:before, -#loading-page .loader:after { - content: ''; - position: absolute; - top: 0; + +#processlist div.extendedstats div span:not(:last-child) { + margin-right: 1em; } -@-webkit-keyframes loader { - 0%, 80%, 100% { - box-shadow: 0 2.5em 0 -1.3em #56CA69; + +#processlist { + overflow-y: auto; + height: 600px; + .table { + margin-bottom: 1em; } - 40% { - box-shadow: 0 2.5em 0 0 #56CA69; + + .table-hover tbody tr:hover td { + background: $glances-link-hover-color; } -} -@keyframes loader { - 0%, 80%, 100% { - box-shadow: 0 2.5em 0 -1.3em #56CA69; + + // Default column size + * > td:nth-child(-n+12) { + width: 5em; } - 40% { - box-shadow: 0 2.5em 0 0 #56CA69; + // Some column are align to the right + * > td:nth-child(5), td:nth-child(7), td:nth-child(9), td:nth-child(11) { + text-align: right; + } + // Process user + * > td:nth-child(6) { + text-overflow: ellipsis; + white-space: nowrap; + width: 6em; + } + // Time + * > td:nth-child(7) { + width: 6em; + } + // Nice and Status + * > td:nth-child(9), td:nth-child(10) { + width: 2em; + } + // Process name & Command line + * > td:nth-child(13), td:nth-child(14) { + text-overflow: ellipsis; + white-space: nowrap; } } -/* Help table */ -.divTable{ - display: table; - width: 100%; -} -.divTableRow { - display: table-row; -} -.divTableHeading { - background-color: #EEE; - display: table-header-group; -} -.divTableHead { - border: 0px solid #999999; - display: table-cell; - padding: 3px 10px; - font-weight: bold; -} -.divTableCell { - border: 0px solid #999999; - display: table-cell; - padding: 3px 10px; -} -.divTableHeading { - background-color: #EEE; - display: table-header-group; - font-weight: bold; -} -.divTableFoot { - background-color: #EEE; - display: table-footer-group; - font-weight: bold; -} -.divTableBody { - display: table-row-group; +#alerts { + span { + padding-left: 10px; + } + span:nth-child(1) { + padding-left: 0px; + } + * > td:nth-child(1) { + width: 20em; + } } + +// Central Glances Browser + +#browser { + table { + margin-top: 1em; + } +} \ No newline at end of file diff --git a/glances/outputs/static/css/variables.less b/glances/outputs/static/css/variables.less deleted file mode 100644 index c75804d801..0000000000 --- a/glances/outputs/static/css/variables.less +++ /dev/null @@ -1,4 +0,0 @@ - -@import "~bootstrap/less/variables.less"; - -@grid-columns: 24; diff --git a/glances/outputs/static/js/App.vue b/glances/outputs/static/js/App.vue index 5e99aa98a5..d0e5a1e42b 100644 --- a/glances/outputs/static/js/App.vue +++ b/glances/outputs/static/js/App.vue @@ -4,94 +4,86 @@
-
-
-
-
- -
-
- -
-
- -
-
+ +
+
+
+
-
-
-
-
- -
+ +
+
+
-
-
-
- +
+
+ +
+
+ +
+ +
+ +
+ +
+ +
+ - -
- -
-
- -
+
--> + + +
+ +
+ +
+ +
+ +
+ +
+ +
+
-
-
+ +
- @@ -113,11 +105,11 @@ import GlancesPluginContainers from './components/plugin-containers.vue'; import GlancesPluginFolders from './components/plugin-folders.vue'; import GlancesPluginFs from './components/plugin-fs.vue'; import GlancesPluginGpu from './components/plugin-gpu.vue'; +import GlancesPluginHostname from './components/plugin-hostname.vue'; import GlancesPluginIp from './components/plugin-ip.vue'; import GlancesPluginIrq from './components/plugin-irq.vue'; import GlancesPluginLoad from './components/plugin-load.vue'; import GlancesPluginMem from './components/plugin-mem.vue'; -import GlancesPluginMemMore from './components/plugin-mem-more.vue'; import GlancesPluginMemswap from './components/plugin-memswap.vue'; import GlancesPluginNetwork from './components/plugin-network.vue'; import GlancesPluginNow from './components/plugin-now.vue'; @@ -147,11 +139,11 @@ export default { GlancesPluginFolders, GlancesPluginFs, GlancesPluginGpu, + GlancesPluginHostname, GlancesPluginIp, GlancesPluginIrq, GlancesPluginLoad, GlancesPluginMem, - GlancesPluginMemMore, GlancesPluginMemswap, GlancesPluginNetwork, GlancesPluginNow, diff --git a/glances/outputs/static/js/Browser.vue b/glances/outputs/static/js/Browser.vue new file mode 100644 index 0000000000..7d3e89867d --- /dev/null +++ b/glances/outputs/static/js/Browser.vue @@ -0,0 +1,109 @@ + + + \ No newline at end of file diff --git a/glances/outputs/static/js/app.js b/glances/outputs/static/js/app.js index 24395f803a..b7001d212e 100644 --- a/glances/outputs/static/js/app.js +++ b/glances/outputs/static/js/app.js @@ -3,9 +3,11 @@ if (module.hot) { module.hot.accept(); } -import '../css/bootstrap.less'; +import '../css/custom.scss'; import '../css/style.scss'; +import * as bootstrap from 'bootstrap'; + import { createApp } from 'vue'; import App from './App.vue'; import * as filters from "./filters.js"; diff --git a/glances/outputs/static/js/browser.js b/glances/outputs/static/js/browser.js new file mode 100644 index 0000000000..de6b222614 --- /dev/null +++ b/glances/outputs/static/js/browser.js @@ -0,0 +1,17 @@ +/* global module */ +if (module.hot) { + module.hot.accept(); +} + +import '../css/custom.scss'; +import '../css/style.scss'; + +import * as bootstrap from 'bootstrap'; + +import { createApp } from 'vue'; +import App from './Browser.vue'; +import * as filters from "./filters.js"; + +const app = createApp(App); +app.config.globalProperties.$filters = filters; +app.mount('#browser'); diff --git a/glances/outputs/static/js/components/help.vue b/glances/outputs/static/js/components/help.vue index 029ccf43d4..dde926bfa8 100644 --- a/glances/outputs/static/js/components/help.vue +++ b/glances/outputs/static/js/components/help.vue @@ -2,7 +2,7 @@
-
{{ help.version }} {{ help.psutil_version }}
+
{{ help.version }} {{ help.psutil_version }}
 
@@ -12,249 +12,149 @@
 
-
-
-
-
- {{ help.header_sort.replace(':', '') }} -
-
- {{ help.header_show_hide.replace(':', '') }} -
-
- {{ help.header_toggle.replace(':', '') }} -
-
- {{ help.header_miscellaneous.replace(':', '') }} -
-
-
-
- {{ help.sort_auto }} -
-
- {{ help.show_hide_application_monitoring }} -
-
- {{ help.toggle_bits_bytes }} -
-
- {{ help.misc_erase_process_filter }} -
-
-
-
- {{ help.sort_cpu }} -
-
- {{ help.show_hide_diskio }} -
-
- {{ help.toggle_count_rate }} -
-
- {{ help.misc_generate_history_graphs }} -
-
-
-
- {{ help.sort_io_rate }} -
-
- {{ help.show_hide_vms }} -
-
- {{ help.show_hide_containers }} -
-
- {{ help.toggle_used_free }} -
-
- {{ help.misc_help }} -
-
-
-
- {{ help.sort_mem }} -
-
- {{ help.show_hide_top_extended_stats }} -
-
- {{ help.toggle_bar_sparkline }} -
-
- {{ help.misc_accumulate_processes_by_program }} -
-
-
-
- {{ help.sort_process_name }} -
-
- {{ help.show_hide_filesystem }} -
-
- {{ help.toggle_separate_combined }} -
-
- {{ help.misc_kill_process }} - N/A in WebUI -
-
-
-
- {{ help.sort_cpu_times }} -
-
- {{ help.show_hide_gpu }} -
-
- {{ help.toggle_live_cumulative }} -
-
- {{ help.misc_reset_processes_summary_min_max }} -
-
-
-
- {{ help.sort_user }} -
-
- {{ help.show_hide_ip }} -
-
- {{ help.toggle_linux_percentage }} -
-
{{ help.misc_quit }}
-
-
-
 
-
- {{ help.show_hide_tcp_connection }} -
-
- {{ help.toggle_cpu_individual_combined }} -
-
- {{ help.misc_reset_history }} -
-
-
-
 
-
- {{ help.show_hide_alert }} -
-
- {{ help.toggle_gpu_individual_combined }} -
-
- {{ help.misc_delete_warning_alerts }} -
-
-
-
 
-
- {{ help.show_hide_network }} -
-
- {{ help.toggle_short_full }} -
-
- {{ help.misc_delete_warning_and_critical_alerts }} -
-
-
-
 
-
- {{ help.sort_cpu_times }} -
-
 
-
- {{ help.misc_edit_process_filter_pattern }} - N/A in WebUI -
-
-
-
 
-
- {{ help.show_hide_irq }} -
-
 
-
 
-
-
-
 
-
- {{ help.show_hide_raid_plugin }} -
-
 
-
 
-
-
-
 
-
- {{ help.show_hide_sensors }} -
-
 
-
 
-
-
-
 
-
- {{ help.show_hide_wifi_module }} -
-
 
-
 
-
-
-
 
-
- {{ help.show_hide_processes }} -
-
 
-
 
-
-
-
 
-
- {{ help.show_hide_left_sidebar }} -
-
 
-
 
-
-
-
 
-
- {{ help.show_hide_quick_look }} -
-
 
-
 
-
-
-
 
-
- {{ help.show_hide_cpu_mem_swap }} -
-
 
-
 
-
-
-
 
-
- {{ help.show_hide_all }} -
-
 
-
 
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ help.header_sort.replace(':', '') }}{{ help.header_show_hide.replace(':', '') }}{{ help.header_toggle.replace(':', '') }}{{ help.header_miscellaneous.replace(':', '') }}
{{ help.sort_auto }}{{ help.show_hide_application_monitoring }}{{ help.toggle_bits_bytes }}{{ help.misc_erase_process_filter }}
{{ help.sort_cpu }}{{ help.show_hide_diskio }}{{ help.toggle_count_rate }}{{ help.misc_generate_history_graphs }}
{{ help.sort_io_rate }}{{ help.show_hide_containers }}{{ help.toggle_used_free }}{{ help.misc_help }}
{{ help.sort_mem }}{{ help.show_hide_top_extended_stats }}{{ help.toggle_bar_sparkline }}{{ help.misc_accumulate_processes_by_program }}
{{ help.sort_process_name }}{{ help.show_hide_filesystem }}{{ help.toggle_separate_combined }} 
{{ help.sort_cpu_times }}{{ help.show_hide_gpu }}{{ help.toggle_live_cumulative }}{{ help.misc_reset_processes_summary_min_max }}
{{ help.sort_user }}{{ help.show_hide_ip }}{{ help.toggle_linux_percentage }}{{ help.misc_quit }}
 {{ help.show_hide_tcp_connection }}{{ help.toggle_cpu_individual_combined }}{{ help.misc_reset_history }}
 {{ help.show_hide_alert }}{{ help.toggle_gpu_individual_combined }}{{ help.misc_delete_warning_alerts }}
 {{ help.show_hide_network }}{{ help.toggle_short_full }}{{ help.misc_delete_warning_and_critical_alerts }}
 {{ help.sort_cpu_times }}  
 {{ help.show_hide_irq }}  
 {{ help.show_hide_raid_plugin }}  
 {{ help.show_hide_sensors }}  
 {{ help.show_hide_wifi_module }}  
 {{ help.show_hide_processes }}  
 {{ help.show_hide_left_sidebar }}  
 {{ help.show_hide_quick_look }}  
 {{ help.show_hide_cpu_mem_swap }}  
 {{ help.show_hide_all }}  
+
 

For an exhaustive list of key bindings, - click here. + click here.

+ +
 

Press h to came back to Glances.

diff --git a/glances/outputs/static/js/components/plugin-alert.vue b/glances/outputs/static/js/components/plugin-alert.vue index 5c109f040f..c4eb34145c 100644 --- a/glances/outputs/static/js/components/plugin-alert.vue +++ b/glances/outputs/static/js/components/plugin-alert.vue @@ -1,33 +1,36 @@ diff --git a/glances/outputs/static/js/components/plugin-cpu.vue b/glances/outputs/static/js/components/plugin-cpu.vue index 08ae284629..c1a8448c34 100644 --- a/glances/outputs/static/js/components/plugin-cpu.vue +++ b/glances/outputs/static/js/components/plugin-cpu.vue @@ -1,96 +1,115 @@ diff --git a/glances/outputs/static/js/components/plugin-diskio.vue b/glances/outputs/static/js/components/plugin-diskio.vue index 143879f2df..e1ec24ddcc 100644 --- a/glances/outputs/static/js/components/plugin-diskio.vue +++ b/glances/outputs/static/js/components/plugin-diskio.vue @@ -1,29 +1,37 @@ @@ -50,24 +58,41 @@ export default { stats() { return this.data.stats['diskio']; }, + view() { + return this.data.views['diskio']; + }, disks() { const disks = this.stats.map((diskioData) => { - const timeSinceUpdate = diskioData['time_since_update']; return { name: diskioData['disk_name'], + alias: diskioData['alias'] !== undefined ? diskioData['alias'] : null, bitrate: { - txps: bytes(diskioData['read_bytes'] / timeSinceUpdate), - rxps: bytes(diskioData['write_bytes'] / timeSinceUpdate) + txps: bytes(diskioData['read_bytes_rate_per_sec']), + rxps: bytes(diskioData['write_bytes_rate_per_sec']) }, count: { - txps: bytes(diskioData['read_count'] / timeSinceUpdate), - rxps: bytes(diskioData['write_count'] / timeSinceUpdate) - }, - alias: diskioData['alias'] !== undefined ? diskioData['alias'] : null + txps: bytes(diskioData['read_count_rate_per_sec']), + rxps: bytes(diskioData['write_count_rate_per_sec']) + } }; + }).filter(disk => { + const readBytesRate = this.view[disk.name]['read_bytes_rate_per_sec']; + const writeBytesRate = this.view[disk.name]['write_bytes_rate_per_sec']; + return (!readBytesRate || readBytesRate.hidden === false) && (!writeBytesRate || writeBytesRate.hidden === false); }); return orderBy(disks, ['name']); + }, + hasDisks() { + return this.disks.length > 0; + } + }, + methods: { + getDecoration(diskName, field) { + if (this.view[diskName][field] == undefined) { + return; + } + return this.view[diskName][field].decoration.toLowerCase(); } } }; - \ No newline at end of file + diff --git a/glances/outputs/static/js/components/plugin-folders.vue b/glances/outputs/static/js/components/plugin-folders.vue index 7422229ef5..87a29d6bd8 100644 --- a/glances/outputs/static/js/components/plugin-folders.vue +++ b/glances/outputs/static/js/components/plugin-folders.vue @@ -1,20 +1,24 @@ @@ -40,6 +44,9 @@ export default { critical: folderData['critical'] }; }); + }, + hasFolders() { + return this.folders.length > 0; } }, methods: { diff --git a/glances/outputs/static/js/components/plugin-fs.vue b/glances/outputs/static/js/components/plugin-fs.vue index 3e015fdcb2..2ed907f1c1 100644 --- a/glances/outputs/static/js/components/plugin-fs.vue +++ b/glances/outputs/static/js/components/plugin-fs.vue @@ -1,30 +1,43 @@ @@ -66,6 +79,9 @@ export default { }; }); return orderBy(fileSystems, ['mnt_point']); + }, + hasFs() { + return this.fileSystems.length > 0; } }, methods: { diff --git a/glances/outputs/static/js/components/plugin-gpu.vue b/glances/outputs/static/js/components/plugin-gpu.vue index ff57f28605..733390994d 100644 --- a/glances/outputs/static/js/components/plugin-gpu.vue +++ b/glances/outputs/static/js/components/plugin-gpu.vue @@ -1,73 +1,87 @@ diff --git a/glances/outputs/static/js/components/plugin-irq.vue b/glances/outputs/static/js/components/plugin-irq.vue index 174230f3b7..b42aac2f00 100644 --- a/glances/outputs/static/js/components/plugin-irq.vue +++ b/glances/outputs/static/js/components/plugin-irq.vue @@ -1,17 +1,19 @@ diff --git a/glances/outputs/static/js/components/plugin-load.vue b/glances/outputs/static/js/components/plugin-load.vue index 705319b664..4b4231d1db 100644 --- a/glances/outputs/static/js/components/plugin-load.vue +++ b/glances/outputs/static/js/components/plugin-load.vue @@ -1,28 +1,30 @@ diff --git a/glances/outputs/static/js/components/plugin-mem-more.vue b/glances/outputs/static/js/components/plugin-mem-more.vue deleted file mode 100644 index 12507ec21c..0000000000 --- a/glances/outputs/static/js/components/plugin-mem-more.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - \ No newline at end of file diff --git a/glances/outputs/static/js/components/plugin-mem.vue b/glances/outputs/static/js/components/plugin-mem.vue index 98597d16d8..a4b8d71e3d 100644 --- a/glances/outputs/static/js/components/plugin-mem.vue +++ b/glances/outputs/static/js/components/plugin-mem.vue @@ -1,24 +1,78 @@ @@ -48,6 +102,18 @@ export default { }, free() { return this.stats['free']; + }, + active() { + return this.stats['active']; + }, + inactive() { + return this.stats['inactive']; + }, + buffers() { + return this.stats['buffers']; + }, + cached() { + return this.stats['cached']; } }, methods: { diff --git a/glances/outputs/static/js/components/plugin-memswap.vue b/glances/outputs/static/js/components/plugin-memswap.vue index f98ba66a45..9785072e53 100644 --- a/glances/outputs/static/js/components/plugin-memswap.vue +++ b/glances/outputs/static/js/components/plugin-memswap.vue @@ -1,24 +1,30 @@ diff --git a/glances/outputs/static/js/components/plugin-network.vue b/glances/outputs/static/js/components/plugin-network.vue index 2eb2aedd91..8be193dcf8 100644 --- a/glances/outputs/static/js/components/plugin-network.vue +++ b/glances/outputs/static/js/components/plugin-network.vue @@ -1,48 +1,59 @@ \ No newline at end of file + diff --git a/glances/outputs/static/js/components/plugin-now.vue b/glances/outputs/static/js/components/plugin-now.vue index 907fb2d0e4..2f86245696 100644 --- a/glances/outputs/static/js/components/plugin-now.vue +++ b/glances/outputs/static/js/components/plugin-now.vue @@ -1,8 +1,6 @@ diff --git a/glances/outputs/static/js/components/plugin-percpu.vue b/glances/outputs/static/js/components/plugin-percpu.vue index 795b4fa258..40d00de3dc 100644 --- a/glances/outputs/static/js/components/plugin-percpu.vue +++ b/glances/outputs/static/js/components/plugin-percpu.vue @@ -1,38 +1,32 @@ diff --git a/glances/outputs/static/js/components/plugin-ports.vue b/glances/outputs/static/js/components/plugin-ports.vue index 040a75e5af..06a467f965 100644 --- a/glances/outputs/static/js/components/plugin-ports.vue +++ b/glances/outputs/static/js/components/plugin-ports.vue @@ -1,23 +1,30 @@ @@ -34,6 +41,9 @@ export default { }, ports() { return this.stats; + }, + hasPorts() { + return this.ports.length > 0; } }, methods: { diff --git a/glances/outputs/static/js/components/plugin-processcount.vue b/glances/outputs/static/js/components/plugin-processcount.vue index aaa86b0e59..bf27cdfd30 100644 --- a/glances/outputs/static/js/components/plugin-processcount.vue +++ b/glances/outputs/static/js/components/plugin-processcount.vue @@ -1,5 +1,5 @@