diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ddd66c67..f5e4565d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,16 +44,16 @@ jobs: --outdir dist/ - name: Publish distribution package to Test PyPI - uses: pypa/gh-action-pypi-publish@master + 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 + 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@master + uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/Makefile b/Makefile index 4ffc82b9..c8f84a07 100644 --- a/Makefile +++ b/Makefile @@ -80,8 +80,11 @@ test-min: ## Run unit tests in minimal environment test-min-with-upgrade: venv-min-upgrade ## Upgrade deps and run unit tests in minimal environment ./venv-min/bin/python ./unitest.py +test-restful-api: ## Run unit tests of the RESTful API + ./venv/bin/python ./unitest-restful.py + # =================================================================== -# Linters and profilers +# Linters, profilers and cyber security # =================================================================== format: ## Format the code @@ -99,7 +102,7 @@ codespell: ## Run codespell to fix common misspellings in text files ./venv-dev/bin/codespell -S .git,./docs/_build,./Glances.egg-info,./venv*,./glances/outputs,*.svg -L hart,bu,te,statics semgrep: ## Run semgrep to find bugs and enforce code standards - ./venv-dev/bin/semgrep --config=auto --lang python --use-git-ignore ./glances + ./venv-dev/bin/semgrep scan --config=auto profiling: ## How to start the profiling of the Glances software @echo "Please complete and run: sudo ./venv-dev/bin/py-spy record -o ./docs/_static/glances-flame.svg -d 60 -s --pid " @@ -123,6 +126,10 @@ memory-profiling: ## Profile memory usage ./venv-dev/bin/mprof plot --output ./docs/_static/glances-memory-profiling-without-history.png rm -f mprofile_*.dat +# Trivy installation: https://aquasecurity.github.io/trivy/latest/getting-started/installation/ +trivy: ## Run Trivy to find vulnerabilities in container images + trivy fs . + # =================================================================== # Docs # =================================================================== diff --git a/NEWS.rst b/NEWS.rst index e40a63c1..944adf05 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -8,6 +8,17 @@ Version 4.0.0 Under development: https://github.com/nicolargo/glances/issues?q=is%3Aopen+is%3Aissue+milestone%3A%22Glances+4.0.0%22 +**BREAKING CHANGES:** + + * The Glances API version 3 is replaced by the version 4. So Restfull API URL is now /api/4/ #2610 + * Alias definition change in the configuration file #1735 + Glances version 3.x and lower: + sda1_alias=InternalDisk + sdb1_alias=ExternalDisk + Glances version 4.x and higher: + alias=sda1:InternalDisk,sdb1:ExternalDisk + * Alias can now be used to redefine FS name #1735 + =============== Version 3.4.0.3 =============== @@ -899,7 +910,7 @@ Processes list Nice value: [processlist] # Nice priorities range from -20 to 19. - # Configure nice levels using a comma separated list. + # Configure nice levels using a comma-separated list. # # Nice: Example 1, non-zero is warning (default behavior) nice_warning=-20,-19,-18,-17,-16,-15,-14,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 diff --git a/README.rst b/README.rst index 3af03fe4..5192ae9c 100644 --- a/README.rst +++ b/README.rst @@ -92,17 +92,19 @@ Optional dependencies: - ``batinfo`` (for battery monitoring) - ``bernhard`` (for the Riemann export module) -- ``bottle`` (for Web server mode) - ``cassandra-driver`` (for the Cassandra export module) - ``chevron`` (for the action script feature) - ``docker`` (for the Containers Docker monitoring support) - ``elasticsearch`` (for the Elastic Search export module) +- ``FastAPI`` and ``Uvicorn`` (for Web server mode) - ``graphitesender`` (For the Graphite export module) - ``hddtemp`` (for HDD temperature monitoring support) [Linux-only] - ``influxdb`` (for the InfluxDB version 1 export module) - ``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) +- ``orjson`` (fast JSON library, used under the hood by FastAPI) - ``py3nvml`` (for the GPU plugin) - ``pycouchdb`` (for the CouchDB export module) - ``pika`` (for the RabbitMQ/ActiveMQ export module) @@ -207,10 +209,10 @@ Get the Glances container: The following tags are availables: - *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 (Bottle and Docker) +- *latest* for a basic Alpine Glances (latest release) version with minimal dependencies (FastAPI and Docker) - *dev* for a basic Alpine Glances image (based on development branch) with all dependencies (Warning: may be instable) - *ubuntu-latest-full* for a full Ubuntu Glances image (latest release) with all dependencies -- *ubuntu-latest* for a basic Ubuntu Glances (latest release) version with minimal dependencies (Bottle and Docker) +- *ubuntu-latest* for a basic Ubuntu Glances (latest release) version with minimal dependencies (FastAPI and Docker) - *ubuntu-dev* for a basic Ubuntu Glances image (based on development branch) with all dependencies (Warning: may be instable) Run last version of Glances container in *console mode*: @@ -319,7 +321,7 @@ Start Termux on your device and enter: $ apt update $ apt upgrade $ apt install clang python - $ pip install bottle + $ pip install fastapi uvicorn orjson jinja2 $ pip install glances And start Glances: diff --git a/conf/glances.conf b/conf/glances.conf index d957ed3f..b6687573 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -23,7 +23,7 @@ history_size=1200 # Theme name for the Curses interface: black or white curse_theme=black # Limit the number of processes to display (for the WebUI) -max_processes_display=30 +max_processes_display=25 # Set the URL prefix (for the WebUI and the API) # Example: url_prefix=/glances/ => http://localhost/glances/ # The final / is mandatory @@ -167,8 +167,6 @@ tx_critical=90 #hide=docker.*,lo # Define the list of wireless network interfaces to be show (comma-separated) #show=docker.* -# WLAN 0 alias -#wlan0_alias=Wireless # 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 @@ -179,6 +177,8 @@ tx_critical=90 #wlan0_tx_warning=900000 #wlan0_tx_critical=1000000 #wlan0_tx_log=True +# Alias for network interface name +alias=wlp2s0:WIFI [ip] disable=False @@ -218,8 +218,8 @@ disable=False hide=loop.*,/dev/loop.* # Define the list of disks to be show (comma-separated) #show=sda.* -# Alias for sda1 -#sda1_alias=InternalDisk +# Alias for sda1 and sdb1 +alias=sda1:InternalDisk,sdb1:ExternalDisk [fs] disable=False @@ -236,6 +236,8 @@ warning=70 critical=90 # Allow additional file system types (comma-separated FS type) #allow=shm +# Alias for root file system +alias=/:Root [irq] # Documentation: https://glances.readthedocs.io/en/latest/aoa/irq.html @@ -307,13 +309,7 @@ battery_careful=80 battery_warning=90 battery_critical=95 # Sensors alias -#temp1_alias=Motherboard 0 -#temp2_alias=Motherboard 1 -#core 0_temperature_core_alias=CPU Core 0 temp -#core 0_fans_speed_alias=CPU Core 0 fan -#or -#core 0_alias=CPU Core 0 -#core 1_alias=CPU Core 1 +#alias=core 0:CPU Core 0,core 1:CPU Core 1 [processcount] disable=False @@ -336,7 +332,7 @@ mem_warning=70 mem_critical=90 # # Nice priorities range from -20 to 19. -# Configure nice levels using a comma separated list. +# Configure nice levels using a comma-separated list. # # Nice: Example 1, non-zero is warning (default behavior) nice_warning=-20,-19,-18,-17,-16,-15,-14,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 @@ -394,10 +390,10 @@ port_default_gateway=True [containers] disable=False -# Only show specific containers (comma separated list of container name or regular expression) +# Only show specific containers (comma-separated list of container name or regular expression) # Comment this line to display all containers (default configuration) ; show=telegraf -# Hide some containers (comma separated list of container name or regular expression) +# Hide some containers (comma-separated list of container name or regular expression) # Comment this line to display all containers (default configuration) ; hide=telegraf # Define the maximum docker size name (default is 20 chars) @@ -428,7 +424,7 @@ disable=False [alert] disable=False # Maximum number of alerts to display (default is 10) -; max_events=10 +;max_events=10 ############################################################################## # Client/server diff --git a/docker-compose/Dockerfile b/docker-compose/Dockerfile index 81caa69a..51cbff4f 100644 --- a/docker-compose/Dockerfile +++ b/docker-compose/Dockerfile @@ -1,3 +1,3 @@ -FROM nicolargo/glances:latest as glancesminimal +FROM glances:local-alpine-minimal as glancesminimal COPY glances.conf /glances/conf/glances.conf CMD python -m glances -C /glances/conf/glances.conf $GLANCES_OPT diff --git a/docker-compose/docker-compose-with-traefik.yml b/docker-compose/docker-compose-with-traefik.yml index 32e9ff96..1f98d07b 100644 --- a/docker-compose/docker-compose-with-traefik.yml +++ b/docker-compose/docker-compose-with-traefik.yml @@ -25,15 +25,16 @@ services: - "/run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro" - "./glances.conf:/glances/conf/glances.conf" environment: - - GLANCES_OPT: "-C /glances/conf/glances.conf -w" - - TZ: "${TZ}" - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: [gpu] + - TZ=${TZ} + - "GLANCES_OPT=-C /glances/conf/glances.conf -w" + # Uncomment for GPU compatibilty (Nvidia) inside the container + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: 1 + # capabilities: [gpu] labels: - "traefik.port=61208" - "traefik.frontend.rule=Host:glances.docker.localhost" diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index d1e18794..84e4bcf7 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -13,12 +13,13 @@ services: - "/run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro" - "./glances.conf:/glances/conf/glances.conf" environment: - - GLANCES_OPT: "-C /glances/conf/glances.conf -w" - - TZ: "${TZ}" - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: [gpu] \ No newline at end of file + - TZ=${TZ} + - "GLANCES_OPT=-C /glances/conf/glances.conf -w" + # Uncomment for GPU compatibilty (Nvidia) inside the container + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: 1 + # capabilities: [gpu] diff --git a/docker-compose/glances.conf b/docker-compose/glances.conf index ed44d51b..f40eade9 100644 --- a/docker-compose/glances.conf +++ b/docker-compose/glances.conf @@ -23,7 +23,7 @@ history_size=1200 # Theme name for the Curses interface: black or white curse_theme=black # Limit the number of processes to display (for the WebUI) -max_processes_display=30 +max_processes_display=25 # Set the URL prefix (for the WebUI and the API) # Example: url_prefix=/glances/ => http://localhost/glances/ # The final / is mandatory @@ -340,7 +340,7 @@ mem_warning=70 mem_critical=90 # # Nice priorities range from -20 to 19. -# Configure nice levels using a comma separated list. +# Configure nice levels using a comma-separated list. # # Nice: Example 1, non-zero is warning (default behavior) nice_warning=-20,-19,-18,-17,-16,-15,-14,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 @@ -398,10 +398,10 @@ port_default_gateway=True [containers] disable=False -# Only show specific containers (comma separated list of container name or regular expression) +# Only show specific containers (comma-separated list of container name or regular expression) # Comment this line to display all containers (default configuration) ; show=telegraf -# Hide some containers (comma separated list of container name or regular expression) +# Hide some containers (comma-separated list of container name or regular expression) # Comment this line to display all containers (default configuration) ; hide=telegraf # Define the maximum docker size name (default is 20 chars) diff --git a/docker-files/alpine.Dockerfile b/docker-files/alpine.Dockerfile index 133be453..f1eb2455 100644 --- a/docker-files/alpine.Dockerfile +++ b/docker-files/alpine.Dockerfile @@ -96,7 +96,7 @@ EXPOSE 61209 61208 # Define default command. WORKDIR /app -CMD /venv/bin/python3 -m glances -C /etc/glances.conf $GLANCES_OPT +CMD /venv/bin/python3 -m glances $GLANCES_OPT ################################################################################ # RELEASE: minimal diff --git a/docker-files/ubuntu.Dockerfile b/docker-files/ubuntu.Dockerfile index 5313bf54..f4ef7c29 100644 --- a/docker-files/ubuntu.Dockerfile +++ b/docker-files/ubuntu.Dockerfile @@ -90,7 +90,7 @@ EXPOSE 61209 61208 # Define default command. WORKDIR /app -CMD /venv/bin/python3 -m glances -C /etc/glances.conf $GLANCES_OPT +CMD /venv/bin/python3 -m glances $GLANCES_OPT ################################################################################ # RELEASE: minimal diff --git a/docker-requirements.txt b/docker-requirements.txt index 0792572a..c93beffe 100644 --- a/docker-requirements.txt +++ b/docker-requirements.txt @@ -6,5 +6,5 @@ podman; python_version >= "3.6" packaging; python_version >= "3.7" python-dateutil six -urllib3<2.0 # See issue https://github.com/nicolargo/glances/issues/2392 -requests # See issue - https://github.com/nicolargo/glances/issues/2233 +urllib3<2.0 # See issue https://github.com/nicolargo/glances/issues/2617 +requests diff --git a/docs/_static/glances-flame.svg b/docs/_static/glances-flame.svg index 5ab6b70a..7edff6f7 100644 --- a/docs/_static/glances-flame.svg +++ b/docs/_static/glances-flame.svg @@ -1,4 +1,4 @@ - \ No newline at end of file diff --git a/docs/aoa/containers.rst b/docs/aoa/containers.rst index c969b1e8..3750d3cf 100644 --- a/docs/aoa/containers.rst +++ b/docs/aoa/containers.rst @@ -21,11 +21,11 @@ under the ``[containers]`` section: [containers] disable=False - # Only show specific containers (comma separated list of container name or regular expression) + # Only show specific containers (comma-separated list of container name or regular expression) show=thiscontainer,andthisone,andthoseones.* - # Hide some containers (comma separated list of container name or regular expression) + # Hide some containers (comma-separated list of container name or regular expression) hide=donotshowthisone,andthose.* - # Show only specific containers (comma separated list of container name or regular expression) + # Show only specific containers (comma-separated list of container name or regular expression) #show=showthisone,andthose.* # Define the maximum containers size name (default is 20 chars) max_name_size=20 diff --git a/docs/aoa/ps.rst b/docs/aoa/ps.rst index 310c8cd5..7b5d8cf8 100644 --- a/docs/aoa/ps.rst +++ b/docs/aoa/ps.rst @@ -190,7 +190,7 @@ In curses/standalone mode, you can select a process using ``UP`` and ``DOWN`` an .. note:: Limit for CPU and MEM percent values can be overwritten in the configuration file under the ``[processlist]`` section. It is also - possible to define limit for Nice values (comma separated list). + possible to define limit for Nice values (comma-separated list). For example: nice_warning=-20,-19,-18 Accumulated per program — key 'j' diff --git a/docs/api.rst b/docs/api.rst index f88b4715..708a105c 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -12,12 +12,14 @@ The Glances Restfull/API server could be ran using the following command line: API URL ------- -The default root API URL is ``http://localhost:61208/api/3``. +The default root API URL is ``http://localhost:61208/api/4``. The bind address and port could be changed using the ``--bind`` and ``--port`` command line options. It is also possible to define an URL prefix using the ``url_prefix`` option from the [outputs] section -of the Glances configuration file. The url_prefix should always end with a slash (``/``). +of the Glances configuration file. + +Note: The url_prefix should always end with a slash (``/``). For example: @@ -25,19 +27,30 @@ For example: [outputs] url_prefix = /glances/ -will change the root API URL to ``http://localhost:61208/glances/api/3`` and the Web UI URL to +will change the root API URL to ``http://localhost:61208/glances/api/4`` and the Web UI URL to ``http://localhost:61208/glances/`` +API documentation +----------------- + +The API documentation is 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`` + GET API status -------------- This entry point should be used to check the API status. -It will return nothing but a 200 return code if everything is OK. +It will the Glances version and a 200 return code if everything is OK. Get the Rest API status:: - # curl -I http://localhost:61208/api/3/status + # curl -I http://localhost:61208/api/4/status "HTTP/1.0 200 OK" GET plugins list @@ -45,7 +58,7 @@ GET plugins list Get the plugins list:: - # curl http://localhost:61208/api/3/pluginslist + # curl http://localhost:61208/api/4/pluginslist ["alert", "amps", "cloud", @@ -76,14 +89,34 @@ Get the plugins list:: "smart", "system", "uptime", + "version", "wifi"] +GET alert +--------- + +Get plugin stats:: + + # curl http://localhost:61208/api/4/alert + [[1702733581.0, + -1, + "WARNING", + "MEM", + 80.72395821062744, + 80.72395821062744, + 80.72395821062744, + 80.72395821062744, + 1, + [], + "", + "memory_percent"]] + GET amps -------- Get plugin stats:: - # curl http://localhost:61208/api/3/amps + # curl http://localhost:61208/api/4/amps [{"count": 0, "countmax": None, "countmin": 1.0, @@ -92,7 +125,7 @@ Get plugin stats:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.1688997745513916}, + "timer": 0.30402588844299316}, {"count": 0, "countmax": 20.0, "countmin": None, @@ -101,16 +134,16 @@ Get plugin stats:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.1687941551208496}] + "timer": 0.3038666248321533}] Get a specific field:: - # curl http://localhost:61208/api/3/amps/name + # curl http://localhost:61208/api/4/amps/name {"name": ["Dropbox", "Python", "Conntrack", "Nginx", "Systemd", "SystemV"]} Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/3/amps/name/Dropbox + # curl http://localhost:61208/api/4/amps/name/Dropbox {"Dropbox": [{"count": 0, "countmax": None, "countmin": 1.0, @@ -119,19 +152,19 @@ Get a specific item when field matches the given value:: "refresh": 3.0, "regex": True, "result": None, - "timer": 0.1688997745513916}]} + "timer": 0.30402588844299316}]} GET connections --------------- Get plugin stats:: - # curl http://localhost:61208/api/3/connections + # curl http://localhost:61208/api/4/connections {"net_connections_enabled": True, "nf_conntrack_enabled": True} Get a specific field:: - # curl http://localhost:61208/api/3/connections/net_connections_enabled + # curl http://localhost:61208/api/4/connections/net_connections_enabled {"net_connections_enabled": True} GET containers @@ -139,51 +172,72 @@ GET containers Get plugin stats:: - # curl http://localhost:61208/api/3/containers - {"containers": [{"Command": ["/usr/local/bin/entrypoint", "/sbin/init"], - "Created": "2023-09-23T08:45:37.9847178Z", - "Id": "3b375dd5868fa54c30dd67ca296cf27885404194b478f82c20fbfae609c20d85", - "Image": ["gcr.io/k8s-minikube/kicbase:v0.0.40"], - "Status": "running", - "Uptime": "1 weeks", - "cpu": {"total": 0.0}, - "cpu_percent": 0.0, - "engine": "docker", - "io": {"cumulative_ior": 97918976, - "cumulative_iow": 188833792}, - "io_r": None, - "io_w": None, - "key": "name", - "memory": {"cache": None, - "limit": 2306867200, - "max_usage": None, - "rss": None, - "usage": 717176832}, - "memory_usage": 717176832, - "name": "minikube", - "network": {"cumulative_rx": 6072033, - "cumulative_tx": 89450523}, - "network_rx": None, - "network_tx": None}, - {"Command": ["/portainer"], + # curl http://localhost:61208/api/4/containers + {"containers": [{"Command": ["/portainer"], "Created": "2022-10-29T14:59:10.266701439Z", "Id": "3abd51c615968482d9ccff5afc629f267f6dda113ed68b75b432615fae3b49fb", "Image": ["portainer/portainer-ce:2.9.3"], "Status": "running", - "Uptime": "2 weeks", + "Uptime": "3 weeks", "cpu": {"total": 0.0}, "cpu_percent": 0.0, "engine": "docker", - "io": {}, + "io": {"cumulative_ior": 1904640, "cumulative_iow": 2256896}, "io_r": None, "io_w": None, "key": "name", - "memory": {}, - "memory_usage": None, + "memory": {"cache": None, + "limit": 7823585280, + "max_usage": None, + "rss": None, + "usage": 13836288}, + "memory_usage": 13836288, "name": "portainer", - "network": {}, + "network": {"cumulative_rx": 7106388, "cumulative_tx": 0}, "network_rx": None, - "network_tx": None}], + "network_tx": None}, + {"Command": ["top"], + "Created": "2023-12-09T10:45:34.339489876+01:00", + "Id": "481d6ffb7eef284d062628cf350bdd9ce0a803db8a2a505d75565ed24322b714", + "Image": "["docker.io/library/ubuntu:latest"]", + "Status": "running", + "Uptime": "1 weeks", + "cpu": {"total": 6.604897042562196e-07}, + "cpu_percent": 6.604897042562196e-07, + "engine": "podman", + "io": {"ior": 0.0, "iow": 0.0, "time_since_update": 1}, + "io_r": 0.0, + "io_w": 0.0, + "key": "name", + "memory": {"limit": 7823585280.0, "usage": 1441792.0}, + "memory_usage": 1441792.0, + "name": "sad_darwin", + "network": {"rx": 0.0, "time_since_update": 1, "tx": 0.0}, + "network_rx": 0.0, + "network_tx": 0.0, + "pod_id": "8d0f1c783def", + "pod_name": "sad_darwin"}, + {"Command": [], + "Created": "2022-10-22T14:23:03.120912374+02:00", + "Id": "9491515251edcd5bb5dc17205d7ee573c0be96fe0b08b0a12a7e2cea874565ea", + "Image": "["k8s.gcr.io/pause:3.5"]", + "Status": "running", + "Uptime": "1 weeks", + "cpu": {"total": 3.231862023627245e-10}, + "cpu_percent": 3.231862023627245e-10, + "engine": "podman", + "io": {"ior": 0.0, "iow": 0.0, "time_since_update": 1}, + "io_r": 0.0, + "io_w": 0.0, + "key": "name", + "memory": {"limit": 7823585280.0, "usage": 368640.0}, + "memory_usage": 368640.0, + "name": "8d0f1c783def-infra", + "network": {"rx": 0.0, "time_since_update": 1, "tx": 0.0}, + "network_rx": 0.0, + "network_tx": 0.0, + "pod_id": "8d0f1c783def", + "pod_name": "8d0f1c783def-infra"}], "version": {}, "version_podman": {}} @@ -192,7 +246,7 @@ GET core Get plugin stats:: - # curl http://localhost:61208/api/3/core + # curl http://localhost:61208/api/4/core {"log": 4, "phys": 2} Fields descriptions: @@ -202,7 +256,7 @@ Fields descriptions: Get a specific field:: - # curl http://localhost:61208/api/3/core/phys + # curl http://localhost:61208/api/4/core/phys {"phys": 2} GET cpu @@ -210,24 +264,24 @@ GET cpu Get plugin stats:: - # curl http://localhost:61208/api/3/cpu + # curl http://localhost:61208/api/4/cpu {"cpucore": 4, "ctx_switches": 0, "guest": 0.0, "guest_nice": 0.0, - "idle": 66.9, + "idle": 73.7, "interrupts": 0, - "iowait": 0.8, + "iowait": 0.2, "irq": 0.0, "nice": 0.0, "soft_interrupts": 0, - "softirq": 0.8, + "softirq": 0.0, "steal": 0.0, "syscalls": 0, - "system": 7.6, + "system": 2.0, "time_since_update": 1, - "total": 32.2, - "user": 23.7} + "total": 26.2, + "user": 24.2} Fields descriptions: @@ -249,15 +303,15 @@ Fields descriptions: Get a specific field:: - # curl http://localhost:61208/api/3/cpu/total - {"total": 32.2} + # curl http://localhost:61208/api/4/cpu/total + {"total": 26.2} GET diskio ---------- Get plugin stats:: - # curl http://localhost:61208/api/3/diskio + # curl http://localhost:61208/api/4/diskio [{"disk_name": "sda", "key": "disk_name", "read_bytes": 0, @@ -275,12 +329,12 @@ Get plugin stats:: Get a specific field:: - # curl http://localhost:61208/api/3/diskio/disk_name + # curl http://localhost:61208/api/4/diskio/disk_name {"disk_name": ["sda", "sda1", "sda2", "sda5", "dm-0", "dm-1"]} Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/3/diskio/disk_name/sda + # curl http://localhost:61208/api/4/diskio/disk_name/sda {"sda": [{"disk_name": "sda", "key": "disk_name", "read_bytes": 0, @@ -294,69 +348,66 @@ GET fs Get plugin stats:: - # curl http://localhost:61208/api/3/fs + # curl http://localhost:61208/api/4/fs [{"device_name": "/dev/mapper/ubuntu--gnome--vg-root", - "free": 18324307968, + "free": 26169372672, "fs_type": "ext4", "key": "mnt_point", "mnt_point": "/", - "percent": 92.1, + "percent": 88.7, "size": 243334156288, - "used": 212622393344}, + "used": 204777328640}, {"device_name": "zsfpool", - "free": 41811968, + "free": 31195136, "fs_type": "zfs", "key": "mnt_point", "mnt_point": "/zsfpool", - "percent": 0.3, - "size": 41943040, - "used": 131072}] + "percent": 25.4, + "size": 41811968, + "used": 10616832}] Get a specific field:: - # curl http://localhost:61208/api/3/fs/mnt_point + # curl http://localhost:61208/api/4/fs/mnt_point {"mnt_point": ["/", "/zsfpool", "/var/snap/firefox/common/host-hunspell"]} Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/3/fs/mnt_point// + # curl http://localhost:61208/api/4/fs/mnt_point// {"/": [{"device_name": "/dev/mapper/ubuntu--gnome--vg-root", - "free": 18324307968, + "free": 26169372672, "fs_type": "ext4", "key": "mnt_point", "mnt_point": "/", - "percent": 92.1, + "percent": 88.7, "size": 243334156288, - "used": 212622393344}]} + "used": 204777328640}]} GET ip ------ Get plugin stats:: - # curl http://localhost:61208/api/3/ip - {"address": "192.168.0.32", - "gateway": "192.168.0.254", + # curl http://localhost:61208/api/4/ip + {"address": "192.168.1.14", + "gateway": "192.168.1.1", "mask": "255.255.255.0", "mask_cidr": 24, - "public_address": "91.166.228.228", + "public_address": "92.151.148.66", "public_info_human": ""} Get a specific field:: - # curl http://localhost:61208/api/3/ip/gateway - {"gateway": "192.168.0.254"} + # curl http://localhost:61208/api/4/ip/gateway + {"gateway": "192.168.1.1"} GET load -------- Get plugin stats:: - # curl http://localhost:61208/api/3/load - {"cpucore": 4, - "min1": 1.2158203125, - "min15": 1.14794921875, - "min5": 1.13916015625} + # curl http://localhost:61208/api/4/load + {"cpucore": 4, "min1": 1.3134765625, "min15": 1.35009765625, "min5": 1.5234375} Fields descriptions: @@ -367,25 +418,25 @@ Fields descriptions: Get a specific field:: - # curl http://localhost:61208/api/3/load/min1 - {"min1": 1.2158203125} + # curl http://localhost:61208/api/4/load/min1 + {"min1": 1.3134765625} GET mem ------- Get plugin stats:: - # curl http://localhost:61208/api/3/mem - {"active": 2829647872, - "available": 1697103872, - "buffers": 98603008, - "cached": 2016976896, - "free": 1697103872, - "inactive": 3575861248, - "percent": 78.3, - "shared": 469102592, - "total": 7823601664, - "used": 6126497792} + # curl http://localhost:61208/api/4/mem + {"active": 3005665280, + "available": 1508077568, + "buffers": 124248064, + "cached": 1833496576, + "free": 1508077568, + "inactive": 3188097024, + "percent": 80.7, + "shared": 559996928, + "total": 7823585280, + "used": 6315507712} Fields descriptions: @@ -403,22 +454,22 @@ Fields descriptions: Get a specific field:: - # curl http://localhost:61208/api/3/mem/total - {"total": 7823601664} + # curl http://localhost:61208/api/4/mem/total + {"total": 7823585280} GET memswap ----------- Get plugin stats:: - # curl http://localhost:61208/api/3/memswap - {"free": 3605164032, - "percent": 55.4, - "sin": 19573440512, - "sout": 27570544640, + # curl http://localhost:61208/api/4/memswap + {"free": 4914995200, + "percent": 39.2, + "sin": 6458191872, + "sout": 11348365312, "time_since_update": 1, "total": 8082419712, - "used": 4477255680} + "used": 3167424512} Fields descriptions: @@ -432,7 +483,7 @@ Fields descriptions: Get a specific field:: - # curl http://localhost:61208/api/3/memswap/total + # curl http://localhost:61208/api/4/memswap/total {"total": 8082419712} GET network @@ -440,11 +491,11 @@ GET network Get plugin stats:: - # curl http://localhost:61208/api/3/network + # curl http://localhost:61208/api/4/network [{"alias": None, - "cumulative_cx": 1329145058, - "cumulative_rx": 664572529, - "cumulative_tx": 664572529, + "cumulative_cx": 1492376856, + "cumulative_rx": 746188428, + "cumulative_tx": 746188428, "cx": 0, "interface_name": "lo", "is_up": True, @@ -454,17 +505,17 @@ Get plugin stats:: "time_since_update": 1, "tx": 0}, {"alias": None, - "cumulative_cx": 17462918931, - "cumulative_rx": 16928655854, - "cumulative_tx": 534263077, - "cx": 424, + "cumulative_cx": 5862371250, + "cumulative_rx": 5524324325, + "cumulative_tx": 338046925, + "cx": 224, "interface_name": "wlp2s0", "is_up": True, "key": "interface_name", - "rx": 184, + "rx": 98, "speed": 0, "time_since_update": 1, - "tx": 240}] + "tx": 126}] Fields descriptions: @@ -482,29 +533,22 @@ Fields descriptions: Get a specific field:: - # curl http://localhost:61208/api/3/network/interface_name + # curl http://localhost:61208/api/4/network/interface_name {"interface_name": ["lo", "wlp2s0", - "br_grafana", - "docker0", - "veth6cdd773", - "veth0ada394", - "mpqemubr0", - "vboxnet0", - "br-66c7462713f6", - "veth268b5e5", - "veth93720a7", - "vethf4737f4", "br-40875d2e2716", - "veth1df26b1"]} + "docker0", + "br_grafana", + "veth55598fc", + "mpqemubr0"]} Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/3/network/interface_name/lo + # curl http://localhost:61208/api/4/network/interface_name/lo {"lo": [{"alias": None, - "cumulative_cx": 1329145058, - "cumulative_rx": 664572529, - "cumulative_tx": 664572529, + "cumulative_cx": 1492376856, + "cumulative_rx": 746188428, + "cumulative_tx": 746188428, "cx": 0, "interface_name": "lo", "is_up": True, @@ -519,45 +563,45 @@ GET now Get plugin stats:: - # curl http://localhost:61208/api/3/now - "2023-10-07 10:23:53 CEST" + # curl http://localhost:61208/api/4/now + "2023-12-16 14:33:01 CET" GET percpu ---------- Get plugin stats:: - # curl http://localhost:61208/api/3/percpu + # curl http://localhost:61208/api/4/percpu [{"cpu_number": 0, "guest": 0.0, "guest_nice": 0.0, - "idle": 11.0, + "idle": 97.1, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 4.0, - "total": 89.0, - "user": 15.0}, + "system": 1.4, + "total": 2.9, + "user": 1.4}, {"cpu_number": 1, "guest": 0.0, "guest_nice": 0.0, - "idle": 19.0, - "iowait": 1.0, + "idle": 96.4, + "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 1.0, - "total": 81.0, - "user": 8.0}] + "system": 1.4, + "total": 3.6, + "user": 2.2}] Get a specific field:: - # curl http://localhost:61208/api/3/percpu/cpu_number + # curl http://localhost:61208/api/4/percpu/cpu_number {"cpu_number": [0, 1, 2, 3]} GET ports @@ -565,130 +609,130 @@ GET ports Get plugin stats:: - # curl http://localhost:61208/api/3/ports + # curl http://localhost:61208/api/4/ports [{"description": "DefaultGateway", - "host": "192.168.0.254", + "host": "192.168.1.1", "indice": "port_0", "port": 0, "refresh": 30, "rtt_warning": None, - "status": 0.003303, + "status": 0.006756, "timeout": 3}] Get a specific field:: - # curl http://localhost:61208/api/3/ports/host - {"host": ["192.168.0.254"]} + # curl http://localhost:61208/api/4/ports/host + {"host": ["192.168.1.1"]} Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/3/ports/host/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.003303, - "timeout": 3}]} + # 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.006756, + "timeout": 3}]} GET processcount ---------------- Get plugin stats:: - # curl http://localhost:61208/api/3/processcount - {"pid_max": 0, "running": 1, "sleeping": 358, "thread": 2123, "total": 424} + # curl http://localhost:61208/api/4/processcount + {"pid_max": 0, "running": 1, "sleeping": 325, "thread": 1793, "total": 390} Get a specific field:: - # curl http://localhost:61208/api/3/processcount/total - {"total": 424} + # curl http://localhost:61208/api/4/processcount/total + {"total": 390} GET psutilversion ----------------- Get plugin stats:: - # curl http://localhost:61208/api/3/psutilversion - [5, 9, 5] + # curl http://localhost:61208/api/4/psutilversion + "5.9.6" GET quicklook ------------- Get plugin stats:: - # curl http://localhost:61208/api/3/quicklook - {"cpu": 32.2, - "cpu_hz": 3000000000.0, - "cpu_hz_current": 2628401500.0, + # curl http://localhost:61208/api/4/quicklook + {"cpu": 26.2, + "cpu_hz": 2025000000.0, + "cpu_hz_current": 1973989250.0, "cpu_name": "Intel(R) Core(TM) i7-4500U CPU @ 1.80GHz", - "mem": 78.3, + "mem": 80.7, "percpu": [{"cpu_number": 0, "guest": 0.0, "guest_nice": 0.0, - "idle": 11.0, + "idle": 97.1, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 4.0, - "total": 89.0, - "user": 15.0}, + "system": 1.4, + "total": 2.9, + "user": 1.4}, {"cpu_number": 1, "guest": 0.0, "guest_nice": 0.0, - "idle": 19.0, - "iowait": 1.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 1.0, - "total": 81.0, - "user": 8.0}, - {"cpu_number": 2, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 24.0, - "iowait": 1.0, - "irq": 0.0, - "key": "cpu_number", - "nice": 0.0, - "softirq": 0.0, - "steal": 0.0, - "system": 1.0, - "total": 76.0, - "user": 4.0}, - {"cpu_number": 3, - "guest": 0.0, - "guest_nice": 0.0, - "idle": 27.0, + "idle": 96.4, "iowait": 0.0, "irq": 0.0, "key": "cpu_number", "nice": 0.0, "softirq": 0.0, "steal": 0.0, - "system": 2.0, - "total": 73.0, - "user": 2.0}], - "swap": 55.4} + "system": 1.4, + "total": 3.6, + "user": 2.2}, + {"cpu_number": 2, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 34.0, + "iowait": 0.7, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 2.1, + "total": 66.0, + "user": 63.1}, + {"cpu_number": 3, + "guest": 0.0, + "guest_nice": 0.0, + "idle": 67.4, + "iowait": 0.0, + "irq": 0.0, + "key": "cpu_number", + "nice": 0.0, + "softirq": 0.0, + "steal": 0.0, + "system": 2.9, + "total": 32.6, + "user": 29.7}], + "swap": 39.2} Get a specific field:: - # curl http://localhost:61208/api/3/quicklook/cpu - {"cpu": 32.2} + # curl http://localhost:61208/api/4/quicklook/cpu + {"cpu": 26.2} GET sensors ----------- Get plugin stats:: - # curl http://localhost:61208/api/3/sensors + # curl http://localhost:61208/api/4/sensors [{"critical": 105, "key": "label", "label": "acpitz 0", @@ -706,7 +750,7 @@ Get plugin stats:: Get a specific field:: - # curl http://localhost:61208/api/3/sensors/label + # curl http://localhost:61208/api/4/sensors/label {"label": ["acpitz 0", "acpitz 1", "Package id 0", @@ -719,7 +763,7 @@ Get a specific field:: Get a specific item when field matches the given value:: - # curl http://localhost:61208/api/3/sensors/label/acpitz 0 + # curl http://localhost:61208/api/4/sensors/label/acpitz 0 {"acpitz 0": [{"critical": 105, "key": "label", "label": "acpitz 0", @@ -733,17 +777,17 @@ GET system Get plugin stats:: - # curl http://localhost:61208/api/3/system + # curl http://localhost:61208/api/4/system {"hostname": "XPS13-9333", "hr_name": "Ubuntu 22.04 64bit", "linux_distro": "Ubuntu 22.04", "os_name": "Linux", - "os_version": "5.15.0-78-generic", + "os_version": "5.15.0-88-generic", "platform": "64bit"} Get a specific field:: - # curl http://localhost:61208/api/3/system/os_name + # curl http://localhost:61208/api/4/system/os_name {"os_name": "Linux"} GET uptime @@ -751,15 +795,23 @@ GET uptime Get plugin stats:: - # curl http://localhost:61208/api/3/uptime - "61 days, 22:57:43" + # curl http://localhost:61208/api/4/uptime + "21 days, 5:35:08" + +GET version +----------- + +Get plugin stats:: + + # curl http://localhost:61208/api/4/version + "4.0.0_beta01" GET all stats ------------- Get all Glances stats:: - # curl http://localhost:61208/api/3/all + # curl http://localhost:61208/api/4/all Return a very big dictionary (avoid using this request, performances will be poor)... GET top n items of a specific plugin @@ -767,52 +819,76 @@ GET top n items of a specific plugin Get top 2 processes of the processlist plugin:: - # curl http://localhost:61208/api/3/processlist/top/2 - [{"cmdline": ["/snap/firefox/2908/usr/lib/firefox/firefox"], - "cpu_percent": 0.0, - "cpu_times": pcputimes(user=11580.71, system=3577.12, children_user=9078.74, children_system=1390.45, iowait=0.0), - "gids": pgids(real=1000, effective=1000, saved=1000), - "io_counters": [13420777472, 17349627904, 0, 0, 0], - "key": "pid", - "memory_info": pmem(rss=533090304, vms=22503579648, shared=103047168, text=643072, lib=0, data=1594458112, dirty=0), - "memory_percent": 6.813873288730872, - "name": "firefox", - "nice": 0, - "num_threads": 180, - "pid": 6490, - "status": "S", - "time_since_update": 1, - "username": "nicolargo"}, - {"cmdline": ["/snap/firefox/2908/usr/lib/firefox/firefox", + # curl http://localhost:61208/api/4/processlist/top/2 + [{"cmdline": ["/snap/firefox/3206/usr/lib/firefox/firefox", "-contentproc", "-childID", - "1", + "3", "-isForBrowser", "-prefsLen", - "33570", + "41102", "-prefMapSize", - "245480", + "234514", "-jsInitLen", - "240908", + "234236", "-parentBuildID", - "20230710222611", + "20230928054334", + "-greomni", + "/snap/firefox/3206/usr/lib/firefox/omni.ja", + "-appomni", + "/snap/firefox/3206/usr/lib/firefox/browser/omni.ja", "-appDir", - "/snap/firefox/2908/usr/lib/firefox/browser", - "{0ad13ab1-9130-48f6-a388-06a571221c5c}", - "6490", + "/snap/firefox/3206/usr/lib/firefox/browser", + "{912ef42c-455b-4ef1-acbb-dbd1bb6d42d4}", + "7195", "true", "tab"], "cpu_percent": 0.0, - "cpu_times": pcputimes(user=1792.99, system=226.55, children_user=0.0, children_system=0.0, iowait=0.0), - "gids": pgids(real=1000, effective=1000, saved=1000), - "io_counters": [2603199488, 0, 0, 0, 0], + "cpu_times": {"children_system": 0.0, + "children_user": 0.0, + "iowait": 0.0, + "system": 299.32, + "user": 3357.83}, + "gids": {"effective": 1000, "real": 1000, "saved": 1000}, + "io_counters": [405289984, 0, 0, 0, 0], "key": "pid", - "memory_info": pmem(rss=501956608, vms=4198289408, shared=37511168, text=643072, lib=0, data=1584558080, dirty=0), - "memory_percent": 6.415927466114921, + "memory_info": {"data": 1191260160, + "dirty": 0, + "lib": 0, + "rss": 482578432, + "shared": 50585600, + "text": 643072, + "vms": 3809882112}, + "memory_percent": 6.16825169955839, "name": "WebExtensions", "nice": 0, "num_threads": 20, - "pid": 6903, + "pid": 7586, + "status": "S", + "time_since_update": 1, + "username": "nicolargo"}, + {"cmdline": ["/snap/firefox/3206/usr/lib/firefox/firefox"], + "cpu_percent": 0.0, + "cpu_times": {"children_system": 965.5, + "children_user": 6851.05, + "iowait": 0.0, + "system": 2456.28, + "user": 8476.01}, + "gids": {"effective": 1000, "real": 1000, "saved": 1000}, + "io_counters": [6493832192, 9773744128, 0, 0, 0], + "key": "pid", + "memory_info": {"data": 1300467712, + "dirty": 0, + "lib": 0, + "rss": 474091520, + "shared": 117129216, + "text": 643072, + "vms": 13574438912}, + "memory_percent": 6.0597731478936465, + "name": "firefox", + "nice": 0, + "num_threads": 147, + "pid": 7195, "status": "S", "time_since_update": 1, "username": "nicolargo"}] @@ -824,42 +900,42 @@ GET stats history History of a plugin:: - # curl http://localhost:61208/api/3/cpu/history - {"system": [["2023-10-07T10:23:55.322302", 7.6], - ["2023-10-07T10:23:56.352046", 2.5], - ["2023-10-07T10:23:57.502402", 2.5]], - "user": [["2023-10-07T10:23:55.322289", 23.7], - ["2023-10-07T10:23:56.352036", 9.9], - ["2023-10-07T10:23:57.502391", 9.9]]} + # curl http://localhost:61208/api/4/cpu/history + {"system": [["2023-12-16T14:33:03.105389", 2.0], + ["2023-12-16T14:33:04.129951", 1.8], + ["2023-12-16T14:33:05.327322", 1.8]], + "user": [["2023-12-16T14:33:03.105374", 24.2], + ["2023-12-16T14:33:04.129938", 10.0], + ["2023-12-16T14:33:05.327306", 10.0]]} Limit history to last 2 values:: - # curl http://localhost:61208/api/3/cpu/history/2 - {"system": [["2023-10-07T10:23:56.352046", 2.5], - ["2023-10-07T10:23:57.502402", 2.5]], - "user": [["2023-10-07T10:23:56.352036", 9.9], - ["2023-10-07T10:23:57.502391", 9.9]]} + # curl http://localhost:61208/api/4/cpu/history/2 + {"system": [["2023-12-16T14:33:04.129951", 1.8], + ["2023-12-16T14:33:05.327322", 1.8]], + "user": [["2023-12-16T14:33:04.129938", 10.0], + ["2023-12-16T14:33:05.327306", 10.0]]} History for a specific field:: - # curl http://localhost:61208/api/3/cpu/system/history - {"system": [["2023-10-07T10:23:53.734251", 7.6], - ["2023-10-07T10:23:55.322302", 7.6], - ["2023-10-07T10:23:56.352046", 2.5], - ["2023-10-07T10:23:57.502402", 2.5]]} + # curl http://localhost:61208/api/4/cpu/system/history + {"system": [["2023-12-16T14:33:01.240422", 2.0], + ["2023-12-16T14:33:03.105389", 2.0], + ["2023-12-16T14:33:04.129951", 1.8], + ["2023-12-16T14:33:05.327322", 1.8]]} Limit history for a specific field to last 2 values:: - # curl http://localhost:61208/api/3/cpu/system/history - {"system": [["2023-10-07T10:23:56.352046", 2.5], - ["2023-10-07T10:23:57.502402", 2.5]]} + # curl http://localhost:61208/api/4/cpu/system/history + {"system": [["2023-12-16T14:33:04.129951", 1.8], + ["2023-12-16T14:33:05.327322", 1.8]]} GET limits (used for thresholds) -------------------------------- All limits/thresholds:: - # curl http://localhost:61208/api/3/all/limits + # curl http://localhost:61208/api/4/all/limits {"alert": {"alert_disable": ["False"], "history_size": 1200.0}, "amps": {"amps_disable": ["False"], "history_size": 1200.0}, "containers": {"containers_all": ["False"], @@ -1034,6 +1110,7 @@ All limits/thresholds:: "system_disable": ["False"], "system_refresh": 60}, "uptime": {"history_size": 1200.0}, + "version": {"history_size": 1200.0}, "wifi": {"history_size": 1200.0, "wifi_careful": -65.0, "wifi_critical": -85.0, @@ -1042,7 +1119,7 @@ All limits/thresholds:: Limits/thresholds for the cpu plugin:: - # curl http://localhost:61208/api/3/cpu/limits + # curl http://localhost:61208/api/4/cpu/limits {"cpu_ctx_switches_careful": 160000.0, "cpu_ctx_switches_critical": 200000.0, "cpu_ctx_switches_warning": 180000.0, diff --git a/docs/cmds.rst b/docs/cmds.rst index 7ab1ef49..c23670f5 100644 --- a/docs/cmds.rst +++ b/docs/cmds.rst @@ -12,7 +12,7 @@ Command-Line Options .. option:: -V, --version - show program's version number and exit + show the program's version number and exit .. option:: -d, --debug @@ -22,25 +22,29 @@ Command-Line Options path to the configuration file +.. option:: -P PLUGIN_DIRECTORY, --plugins PLUGIN_DIRECTORY + + path to a directory containing additional plugins + .. option:: --modules-list display modules (plugins & exports) list and exit .. option:: --disable-plugin PLUGIN - disable PLUGIN (comma separated list) + disable PLUGIN (comma-separated list) .. option:: --enable-plugin PLUGIN - enable PLUGIN (comma separated list) + enable PLUGIN (comma-separated list) .. option:: --stdout PLUGINS_STATS - display stats to stdout (comma separated list of plugins/plugins.attribute) + display stats to stdout (comma-separated list of plugins/plugins.attribute) .. option:: --export EXPORT - enable EXPORT module (comma separated list) + enable EXPORT module (comma-separated list) .. option:: --export-csv-file EXPORT_CSV_FILE @@ -60,7 +64,7 @@ Command-Line Options .. option:: --light, --enable-light - light mode for Curses UI (disable all but top menu) + light mode for Curses UI (disable all but the top menu) .. option:: -0, --disable-irix @@ -84,7 +88,7 @@ Command-Line Options .. option:: -5, --disable-top - disable top menu (QuickLook, CPU, MEM, SWAP and LOAD) + disable top menu (QuickLook, CPU, MEM, SWAP, and LOAD) .. option:: -6, --meangpu @@ -168,7 +172,7 @@ Command-Line Options .. option:: -w, --webserver - run Glances in web server mode (bottle lib needed) + run Glances in web server mode (FastAPI lib needed) .. option:: --cached-time CACHED_TIME @@ -192,11 +196,11 @@ Command-Line Options .. option:: --hide-kernel-threads - hide kernel threads in process list (not available on Windows) + hide kernel threads in the process list (not available on Windows) .. option:: -b, --byte - display network rate in byte per second + display network rate in bytes per second .. option:: --diskio-show-ramfs @@ -216,11 +220,11 @@ Command-Line Options .. option:: --theme-white - optimize display colors for white background + optimize display colors for a white background .. option:: --disable-check-update - disable online Glances version ckeck + disable online Glances version check Interactive Commands -------------------- @@ -232,7 +236,7 @@ The following commands (key pressed) are supported while in Glances: .. note:: On macOS please use ``CTRL-H`` to delete filter. - Filter is a regular expression pattern: + The filter is a regular expression pattern: - ``gnome``: matches all processes starting with the ``gnome`` string @@ -250,7 +254,7 @@ The following commands (key pressed) are supported while in Glances: - If CPU iowait ``>60%``, sort processes by I/O read and write ``A`` - Enable/disable Application Monitoring Process + Enable/disable the Application Monitoring Process ``b`` Switch between bit/s or Byte/s for network I/O @@ -274,7 +278,7 @@ The following commands (key pressed) are supported while in Glances: Enable/disable top extended stats ``E`` - Erase current process filter + Erase the current process filter ``f`` Show/hide file system and folder monitoring stats @@ -301,7 +305,7 @@ The following commands (key pressed) are supported while in Glances: Increase selected process nice level / Lower the priority (need right) - Only in standalone mode. ``-`` - Decrease selected process nice level / Higher the priority (need right) - Only in standalone mode. + Decrease selected process nice level / Higher the priority (need right) - Only in standalone mode. ``k`` Kill selected process (need right) - Only in standalone mode. @@ -352,7 +356,7 @@ The following commands (key pressed) are supported while in Glances: Sort process by CPU times (TIME+) ``T`` - View network I/O as combination + View network I/O as a combination ``u`` Sort processes by USER @@ -375,13 +379,13 @@ The following commands (key pressed) are supported while in Glances: ``0`` Enable/disable Irix/Solaris mode - Task's CPU usage will be divided by the total number of CPUs + The task's CPU usage will be divided by the total number of CPUs ``1`` Switch between global CPU and per-CPU stats ``2`` - Enable/disable left sidebar + Enable/disable the left sidebar ``3`` Enable/disable the quick look module @@ -390,7 +394,7 @@ The following commands (key pressed) are supported while in Glances: Enable/disable all but quick look and load module ``5`` - Enable/disable top menu (QuickLook, CPU, MEM, SWAP and LOAD) + Enable/disable the top menu (QuickLook, CPU, MEM, SWAP, and LOAD) ``6`` Enable/disable mean GPU mode @@ -405,10 +409,10 @@ The following commands (key pressed) are supported while in Glances: Refresh user interface ``LEFT`` - Navigation left through process sort + Navigation left through the process sort ``RIGHT`` - Navigation right through process sort + Navigation right through the process sort ``UP`` Up in the processes list diff --git a/docs/config.rst b/docs/config.rst index 38c0cd18..4bc30ac2 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -5,7 +5,7 @@ Configuration No configuration file is mandatory to use Glances. -Furthermore a configuration file is needed to access more settings. +Furthermore, a configuration file is needed to access more settings. Location -------- @@ -14,7 +14,7 @@ Location A template is available in the ``/usr{,/local}/share/doc/glances`` (Unix-like) directory or directly on `GitHub`_. -You can put your own ``glances.conf`` file in the following locations: +You can place your ``glances.conf`` file in the following locations: ==================== ============================================================= ``Linux``, ``SunOS`` ~/.config/glances/, /etc/glances/, /usr/share/docs/glances/ @@ -26,13 +26,13 @@ You can put your own ``glances.conf`` file in the following locations: - On Windows XP, ``%APPDATA%`` is: ``C:\Documents and Settings\\Application Data``. - On Windows Vista and later: ``C:\Users\\AppData\Roaming``. -User-specific options override system-wide options and options given on -the command line override either. +User-specific options override system-wide options, and options given on +the command line overrides both. Syntax ------ -Glances reads configuration files in the *ini* syntax. +Glances read configuration files in the *ini* syntax. A first section (called global) is available: @@ -40,17 +40,21 @@ A first section (called global) is available: [global] # Refresh rate (default is a minimum of 2 seconds) - # Can be overwrite by the -t option - # It is also possible to overwrite it in each plugin sections + # Can be overwritten by the -t option + # It is also possible to overwrite it in each plugin section refresh=2 - # Does Glances should check if a newer version is available on PyPI ? + # Should Glances check if a newer version is available on PyPI ? check_update=false # History size (maximum number of values) # Default is 28800: 1 day with 1 point every 3 seconds history_size=28800 + # Define directory external to glances hierarchy for loading additional plugins + # The layout follows the glances standard for plugin definitions + # (see glances/plugins for details) + # plugin_dir=/home/user/dev/plugins -Each plugin, export module and application monitoring process (AMP) can -have a section. Below an example for the CPU plugin: +Each plugin, export module, and application monitoring process (AMP) can +have a section. Below is an example for the CPU plugin: .. code-block:: ini @@ -90,16 +94,16 @@ or a Nginx AMP: .. code-block:: ini [amp_nginx] - # Nginx status page should be enable (https://easyengine.io/tutorials/nginx/status-page/) + # Nginx status page should be enabled (https://easyengine.io/tutorials/nginx/status-page/) enable=true regex=\/usr\/sbin\/nginx refresh=60 one_line=false status_url=http://localhost/nginx_status -With Glances 3.0 or higher it is also possible to use dynamic configuration -value using system command. For example, if you to set the prefix of an -InfluxDB export to the current hostname, use: +With Glances 3.0 or higher, you can use dynamic configuration values +by utilizing system commands. For example, if you want to set the prefix +of an InfluxDB export to the current hostname, use: .. code-block:: ini @@ -120,16 +124,17 @@ Logging Glances logs all of its internal messages to a log file. -``DEBUG`` messages can been logged using the ``-d`` option on the command +``DEBUG`` messages can be logged using the ``-d`` option on the command line. -The location of the Glances depends of your operating system. You could -displayed the Glances log file full path using the``glances -V`` command line. +The location of the Glances log file depends on your operating system. You can +display the full path of the Glances log file using the ``glances -V`` +command line. -The file is automatically rotate when the size is higher than 1 MB. +The file is automatically rotated when its size exceeds 1 MB. If you want to use another system path or change the log message, you -can use your own logger configuration. First of all, you have to create +can use your logger configuration. First of all, you have to create a ``glances.json`` file with, for example, the following content (JSON format): @@ -201,7 +206,7 @@ and start Glances using the following command line: LOG_CFG=/glances.json glances .. note:: - Replace ```` by the folder where your ``glances.json`` file + Replace ```` with the directory where your ``glances.json`` file is hosted. .. _GitHub: https://raw.githubusercontent.com/nicolargo/glances/master/conf/glances.conf diff --git a/docs/docker.rst b/docs/docker.rst index ad91fd3d..76620807 100644 --- a/docs/docker.rst +++ b/docs/docker.rst @@ -3,7 +3,9 @@ Docker ====== -Glances can be installed through Docker, allowing you to run it without installing all the python dependencies directly on your system. Once you have `docker installed `_, you can +Glances can be installed through Docker, allowing you to run it without +installing all the Python dependencies directly on your system. Once you +have `docker installed `_, you can Get the Glances container: @@ -11,7 +13,7 @@ Get the Glances container: docker pull nicolargo/glances: -Available tags (all images are based on both Alpine and Ubuntu Operating System): +Available tags (all images are based on both Alpine and Ubuntu Operating Systems): .. list-table:: :widths: 25 15 25 35 @@ -28,7 +30,7 @@ Available tags (all images are based on both Alpine and Ubuntu Operating System) * - `latest` - Alpine - Latest Release - - Minimal + (Bottle & Docker) + - Minimal + (FastAPI & Docker) * - `dev` - Alpine - develop @@ -40,20 +42,20 @@ Available tags (all images are based on both Alpine and Ubuntu Operating System) * - `ubuntu-latest` - Ubuntu - Latest Release - - Minimal + (Bottle & Docker) + - Minimal + (FastAPI & Docker) * - `ubuntu-dev` - Ubuntu - develop - Full .. warning:: - Tags containing `dev` target the `develop` branch directly and could be unstable. + Tags containing `dev` directly target the `develop` branch and could be unstable. For example, if you want a full Alpine Glances image (latest release) with all dependencies, go for `latest-full`. You can also specify a version (example: 3.4.0). All available versions can be found on `DockerHub`_. -An Example to pull the `latest` tag: +An example of how to pull the `latest` tag: .. code-block:: console @@ -81,7 +83,7 @@ Alternatively, you can specify something along the same lines with docker run op Where \`pwd\`/glances.conf is a local directory containing your glances.conf file. -Glances by default, uses the container's OS information in the UI. If you want to display the host's OS info, you can do that by mounting `/etc/os-release` into the container. +Glances by default uses the container's OS information in the UI. If you want to display the host's OS info, you can do that by mounting `/etc/os-release` into the container. Here is a simple docker run example for that: @@ -97,7 +99,7 @@ Run the container in *Web server mode* (notice the `GLANCES_OPT` environment var Note: if you want to see the network interface stats within the container, add --net=host --privileged -You can also include Glances container in you own `docker-compose.yml`. Here's a realistic example including a "traefik" reverse proxy serving an "whoami" app container plus a Glances container, providing a simple and efficient monitoring webui. +You can also include Glances container in you own `docker-compose.yml`. A realistic example includes a "traefik" reverse proxy serving an "whoami" app container plus a Glances container, providing a simple and efficient monitoring webui. .. code-block:: console diff --git a/docs/glances.rst b/docs/glances.rst index 0d1bde4e..3f3a8a5b 100644 --- a/docs/glances.rst +++ b/docs/glances.rst @@ -11,12 +11,12 @@ SYNOPSIS DESCRIPTION ----------- -**glances** is a cross-platform curses-based monitoring tool which aims -to present a maximum of information in a minimum of space, ideally to -fit in a classical 80x24 terminal or higher to have additional -information. It can adapt dynamically the displayed information -depending on the terminal size. It can also work in client/server mode. -Remote monitoring could be done via terminal or web interface. +**glances** is a cross-platform curses-based monitoring tool that aims +to present a maximum of information in a minimum of space, ideally fitting +in a classic 80x24 terminal or larger for more details. It can adapt +dynamically to the displayed information depending on the terminal size. +It can also work in client/server mode. +Remote monitoring can be performed via a terminal or web interface. **glances** is written in Python and uses the *psutil* library to get information from your system. @@ -38,19 +38,20 @@ Monitor local machine (standalone mode): $ glances -Monitor local machine with the web interface (Web UI), run the following command line: +To monitor the local machine with the web interface (Web UI), +, run the following command line: $ glances -w -and open a Web browser with the returned URL +then, open a web browser to the provided URL. Monitor local machine and export stats to a CSV file: $ glances --export csv --export-csv-file /tmp/glances.csv -Monitor local machine and export stats to a InfluxDB server with 5s +Monitor local machine and export stats to an InfluxDB server with 5s refresh time (also possible to export to OpenTSDB, Cassandra, Statsd, -ElasticSearch, RabbitMQ and Riemann): +ElasticSearch, RabbitMQ, and Riemann): $ glances -t 5 --export influxdb diff --git a/docs/index.rst b/docs/index.rst index f0fcfeb8..61e92a65 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,13 +3,13 @@ Glances .. image:: _static/screenshot-wide.png -Glances is a cross-platform monitoring tool which aims to present a -maximum of information in a minimum of space through a curses or Web -based interface. It can adapt dynamically the displayed information -depending on the terminal size. +Glances is a cross-platform monitoring tool that aims to present +maximum information in minimal space through either a curses-based +or Web-based interface. It can dynamically adapt the displayed +information depending on the terminal size. -It can also work in client/server mode. Remote monitoring could be -done via terminal, Web interface or API (XMLRPC and RESTful). +It can also work in client/server mode. Remote monitoring can be +done via terminal, Web interface, or API (XMLRPC and RESTful). Glances is written in Python and uses the `psutil`_ library to get information from your system. diff --git a/docs/install.rst b/docs/install.rst index 5306db31..a56d69e2 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -3,8 +3,8 @@ Install ======= -Glances is on ``PyPI``. By using PyPI, you are sure to have the latest -stable version. +Glances is available on ``PyPI``. By using PyPI, you are sure to have the +latest stable version. To install, simply use ``pip``: @@ -12,13 +12,13 @@ To install, simply use ``pip``: pip install glances -*Note*: Python headers are required to install `psutil`_. For example, -on Debian/Ubuntu you need to install first the *python-dev* package. -For Fedora/CentOS/RHEL install first *python-devel* package. For Windows, -just install psutil from the binary installation file. +*Note*: Python headers are required to install `psutil`_. For instance, +on Debian/Ubuntu, you must first install the *python-dev* package. +On Fedora/CentOS/RHEL, first, install the *python-devel* package. For Windows, +psutil can be installed from the binary installation file. -You can also install the following libraries in order to use optional -features (like the Web interface, export modules...): +You can also install the following libraries to use the optional +features (such as the web interface, export modules, etc.): .. code-block:: console diff --git a/docs/man/glances.1 b/docs/man/glances.1 index d6451f25..6be4bad6 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -27,7 +27,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" "Oct 07, 2023" "4.0.0_beta01" "Glances" +.TH "GLANCES" "1" "Dec 16, 2023" "4.0.0_beta01" "Glances" .SH NAME glances \- An eye on your system .SH SYNOPSIS @@ -35,12 +35,12 @@ glances \- An eye on your system \fBglances\fP [OPTIONS] .SH DESCRIPTION .sp -\fBglances\fP is a cross\-platform curses\-based monitoring tool which aims -to present a maximum of information in a minimum of space, ideally to -fit in a classical 80x24 terminal or higher to have additional -information. It can adapt dynamically the displayed information -depending on the terminal size. It can also work in client/server mode. -Remote monitoring could be done via terminal or web interface. +\fBglances\fP is a cross\-platform curses\-based monitoring tool that aims +to present a maximum of information in a minimum of space, ideally fitting +in a classic 80x24 terminal or larger for more details. It can adapt +dynamically to the displayed information depending on the terminal size. +It can also work in client/server mode. +Remote monitoring can be performed via a terminal or web interface. .sp \fBglances\fP is written in Python and uses the \fIpsutil\fP library to get information from your system. @@ -54,7 +54,7 @@ show this help message and exit .INDENT 0.0 .TP .B \-V, \-\-version -show program’s version number and exit +show the program’s version number and exit .UNINDENT .INDENT 0.0 .TP @@ -68,28 +68,33 @@ path to the configuration file .UNINDENT .INDENT 0.0 .TP +.B \-P PLUGIN_DIRECTORY, \-\-plugins PLUGIN_DIRECTORY +path to a directory containing additional plugins +.UNINDENT +.INDENT 0.0 +.TP .B \-\-modules\-list display modules (plugins & exports) list and exit .UNINDENT .INDENT 0.0 .TP .B \-\-disable\-plugin PLUGIN -disable PLUGIN (comma separated list) +disable PLUGIN (comma\-separated list) .UNINDENT .INDENT 0.0 .TP .B \-\-enable\-plugin PLUGIN -enable PLUGIN (comma separated list) +enable PLUGIN (comma\-separated list) .UNINDENT .INDENT 0.0 .TP .B \-\-stdout PLUGINS_STATS -display stats to stdout (comma separated list of plugins/plugins.attribute) +display stats to stdout (comma\-separated list of plugins/plugins.attribute) .UNINDENT .INDENT 0.0 .TP .B \-\-export EXPORT -enable EXPORT module (comma separated list) +enable EXPORT module (comma\-separated list) .UNINDENT .INDENT 0.0 .TP @@ -114,7 +119,7 @@ disable the Web UI (only the RESTful API will respond) .INDENT 0.0 .TP .B \-\-light, \-\-enable\-light -light mode for Curses UI (disable all but top menu) +light mode for Curses UI (disable all but the top menu) .UNINDENT .INDENT 0.0 .TP @@ -144,7 +149,7 @@ disable all but quick look and load .INDENT 0.0 .TP .B \-5, \-\-disable\-top -disable top menu (QuickLook, CPU, MEM, SWAP and LOAD) +disable top menu (QuickLook, CPU, MEM, SWAP, and LOAD) .UNINDENT .INDENT 0.0 .TP @@ -249,7 +254,7 @@ set refresh time in seconds [default: 3 sec] .INDENT 0.0 .TP .B \-w, \-\-webserver -run Glances in web server mode (bottle lib needed) +run Glances in web server mode (FastAPI lib needed) .UNINDENT .INDENT 0.0 .TP @@ -279,12 +284,12 @@ force short name for processes name .INDENT 0.0 .TP .B \-\-hide\-kernel\-threads -hide kernel threads in process list (not available on Windows) +hide kernel threads in the process list (not available on Windows) .UNINDENT .INDENT 0.0 .TP .B \-b, \-\-byte -display network rate in byte per second +display network rate in bytes per second .UNINDENT .INDENT 0.0 .TP @@ -309,12 +314,12 @@ display FS free space instead of used .INDENT 0.0 .TP .B \-\-theme\-white -optimize display colors for white background +optimize display colors for a white background .UNINDENT .INDENT 0.0 .TP .B \-\-disable\-check\-update -disable online Glances version ckeck +disable online Glances version check .UNINDENT .SH INTERACTIVE COMMANDS .sp @@ -331,7 +336,7 @@ On macOS please use \fBCTRL\-H\fP to delete filter. .UNINDENT .UNINDENT .sp -Filter is a regular expression pattern: +The filter is a regular expression pattern: .INDENT 7.0 .IP \(bu 2 \fBgnome\fP: matches all processes starting with the \fBgnome\fP @@ -353,7 +358,7 @@ If CPU iowait \fB>60%\fP, sort processes by I/O read and write .UNINDENT .TP .B \fBA\fP -Enable/disable Application Monitoring Process +Enable/disable the Application Monitoring Process .TP .B \fBb\fP Switch between bit/s or Byte/s for network I/O @@ -377,7 +382,7 @@ Enable/disable Docker stats Enable/disable top extended stats .TP .B \fBE\fP -Erase current process filter +Erase the current process filter .TP .B \fBf\fP Show/hide file system and folder monitoring stats @@ -404,7 +409,7 @@ Show/hide IP module Increase selected process nice level / Lower the priority (need right) \- Only in standalone mode. .TP .B \fB\-\fP -Decrease selected process nice level / Higher the priority (need right) \- Only in standalone mode. +Decrease selected process nice level / Higher the priority (need right) \- Only in standalone mode. .TP .B \fBk\fP Kill selected process (need right) \- Only in standalone mode. @@ -455,7 +460,7 @@ Enable/disable spark lines Sort process by CPU times (TIME+) .TP .B \fBT\fP -View network I/O as combination +View network I/O as a combination .TP .B \fBu\fP Sort processes by USER @@ -478,13 +483,13 @@ Show/hide processes stats .B \fB0\fP Enable/disable Irix/Solaris mode .sp -Task’s CPU usage will be divided by the total number of CPUs +The task’s CPU usage will be divided by the total number of CPUs .TP .B \fB1\fP Switch between global CPU and per\-CPU stats .TP .B \fB2\fP -Enable/disable left sidebar +Enable/disable the left sidebar .TP .B \fB3\fP Enable/disable the quick look module @@ -493,7 +498,7 @@ Enable/disable the quick look module Enable/disable all but quick look and load module .TP .B \fB5\fP -Enable/disable top menu (QuickLook, CPU, MEM, SWAP and LOAD) +Enable/disable the top menu (QuickLook, CPU, MEM, SWAP, and LOAD) .TP .B \fB6\fP Enable/disable mean GPU mode @@ -508,10 +513,10 @@ Switch between process command line or command name Refresh user interface .TP .B \fBLEFT\fP -Navigation left through process sort +Navigation left through the process sort .TP .B \fBRIGHT\fP -Navigation right through process sort +Navigation right through the process sort .TP .B \fBUP\fP Up in the processes list @@ -540,7 +545,7 @@ Quit Glances .sp No configuration file is mandatory to use Glances. .sp -Furthermore a configuration file is needed to access more settings. +Furthermore, a configuration file is needed to access more settings. .SH LOCATION .sp \fBNOTE:\fP @@ -551,7 +556,7 @@ A template is available in the \fB/usr{,/local}/share/doc/glances\fP .UNINDENT .UNINDENT .sp -You can put your own \fBglances.conf\fP file in the following locations: +You can place your \fBglances.conf\fP file in the following locations: .TS center; |l|l|. @@ -588,11 +593,11 @@ On Windows XP, \fB%APPDATA%\fP is: \fBC:\eDocuments and Settings\e\eAp On Windows Vista and later: \fBC:\eUsers\e\eAppData\eRoaming\fP\&. .UNINDENT .sp -User\-specific options override system\-wide options and options given on -the command line override either. +User\-specific options override system\-wide options, and options given on +the command line overrides both. .SH SYNTAX .sp -Glances reads configuration files in the \fIini\fP syntax. +Glances read configuration files in the \fIini\fP syntax. .sp A first section (called global) is available: .INDENT 0.0 @@ -602,21 +607,25 @@ A first section (called global) is available: .ft C [global] # Refresh rate (default is a minimum of 2 seconds) -# Can be overwrite by the \-t option -# It is also possible to overwrite it in each plugin sections +# Can be overwritten by the \-t option +# It is also possible to overwrite it in each plugin section refresh=2 -# Does Glances should check if a newer version is available on PyPI ? +# Should Glances check if a newer version is available on PyPI ? check_update=false # History size (maximum number of values) # Default is 28800: 1 day with 1 point every 3 seconds history_size=28800 +# Define directory external to glances hierarchy for loading additional plugins +# The layout follows the glances standard for plugin definitions +# (see glances/plugins for details) +# plugin_dir=/home/user/dev/plugins .ft P .fi .UNINDENT .UNINDENT .sp -Each plugin, export module and application monitoring process (AMP) can -have a section. Below an example for the CPU plugin: +Each plugin, export module, and application monitoring process (AMP) can +have a section. Below is an example for the CPU plugin: .INDENT 0.0 .INDENT 3.5 .sp @@ -670,7 +679,7 @@ or a Nginx AMP: .nf .ft C [amp_nginx] -# Nginx status page should be enable (https://easyengine.io/tutorials/nginx/status\-page/) +# Nginx status page should be enabled (https://easyengine.io/tutorials/nginx/status\-page/) enable=true regex=\e/usr\e/sbin\e/nginx refresh=60 @@ -681,9 +690,9 @@ status_url=http://localhost/nginx_status .UNINDENT .UNINDENT .sp -With Glances 3.0 or higher it is also possible to use dynamic configuration -value using system command. For example, if you to set the prefix of an -InfluxDB export to the current hostname, use: +With Glances 3.0 or higher, you can use dynamic configuration values +by utilizing system commands. For example, if you want to set the prefix +of an InfluxDB export to the current hostname, use: .INDENT 0.0 .INDENT 3.5 .sp @@ -714,16 +723,17 @@ tags=system:\(gauname \-a\(ga .sp Glances logs all of its internal messages to a log file. .sp -\fBDEBUG\fP messages can been logged using the \fB\-d\fP option on the command +\fBDEBUG\fP messages can be logged using the \fB\-d\fP option on the command line. .sp -The location of the Glances depends of your operating system. You could -displayed the Glances log file full path using the\(ga\(gaglances \-V\(ga\(ga command line. +The location of the Glances log file depends on your operating system. You can +display the full path of the Glances log file using the \fBglances \-V\fP +command line. .sp -The file is automatically rotate when the size is higher than 1 MB. +The file is automatically rotated when its size exceeds 1 MB. .sp If you want to use another system path or change the log message, you -can use your own logger configuration. First of all, you have to create +can use your logger configuration. First of all, you have to create a \fBglances.json\fP file with, for example, the following content (JSON format): .INDENT 0.0 @@ -809,7 +819,7 @@ LOG_CFG=/glances.json glances \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 -Replace \fB\fP by the folder where your \fBglances.json\fP file +Replace \fB\fP with the directory where your \fBglances.json\fP file is hosted. .UNINDENT .UNINDENT @@ -822,14 +832,15 @@ $ glances .UNINDENT .UNINDENT .sp -Monitor local machine with the web interface (Web UI), run the following command line: +To monitor the local machine with the web interface (Web UI), +, run the following command line: .INDENT 0.0 .INDENT 3.5 $ glances \-w .UNINDENT .UNINDENT .sp -and open a Web browser with the returned URL +then, open a web browser to the provided URL. .sp Monitor local machine and export stats to a CSV file: .INDENT 0.0 @@ -838,9 +849,9 @@ $ glances –export csv –export\-csv\-file /tmp/glances.csv .UNINDENT .UNINDENT .sp -Monitor local machine and export stats to a InfluxDB server with 5s +Monitor local machine and export stats to an InfluxDB server with 5s refresh time (also possible to export to OpenTSDB, Cassandra, Statsd, -ElasticSearch, RabbitMQ and Riemann): +ElasticSearch, RabbitMQ, and Riemann): .INDENT 0.0 .INDENT 3.5 $ glances \-t 5 –export influxdb diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 1d4ec024..70b047a9 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,8 +3,8 @@ Quickstart ========== -This page gives a good introduction in how to get started with Glances. -Glances offers 3 modes: +This page gives a good introduction to how to get started with Glances. +Glances offers three modes: - Standalone - Client/Server @@ -61,7 +61,7 @@ Note: It will display one line per stat per refresh. Client/Server Mode ------------------ -If you want to remotely monitor a machine, called ``server``, from +If you want to remotely monitor a machine called ``server``, from another one, called ``client``, just run on the server: .. code-block:: console @@ -118,7 +118,7 @@ To start the central client, use the following option: .. note:: - Use ``--disable-autodiscover`` to disable the auto discovery mode. + 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: @@ -137,7 +137,7 @@ client, the latter will try to grab stats using the ``SNMP`` protocol: client$ glances -c @snmpserver .. note:: - Stats grabbed by SNMP request are limited and OS dependent. + Stats grabbed by SNMP request are limited and OS-dependent. A SNMP server should be installed and configured... @@ -152,14 +152,14 @@ Web Server Mode .. image:: _static/screenshot-web.png -If you want to remotely monitor a machine, called ``server``, from any +If you want to remotely monitor a machine called ``server``, from any device with a web browser, just run the server with the ``-w`` option: .. code-block:: console server$ glances -w -then on the client enter the following URL in your favorite web browser: +then, on the client, enter the following URL in your favorite web browser: :: @@ -167,7 +167,7 @@ then on the client enter the following URL in your favorite web browser: where ``@server`` is the IP address or hostname of the server. -To change the refresh rate of the page, just add the period in seconds +To change the refresh rate of the page, add the period in seconds at the end of the URL. For example, to refresh the page every ``10`` seconds: @@ -181,10 +181,10 @@ Here's a screenshot from Chrome on Android: .. image:: _static/screenshot-web2.png -How to protect your server (or Web server) with a login/password ? +How do you protect your server (or Web server) with a login/password ? ------------------------------------------------------------------ -You can set a password to access to the server using the ``--password``. +You can set a password to access the server using the ``--password``. By default, the login is ``glances`` but you can change it with ``--username``. @@ -192,8 +192,8 @@ If you want, the SHA password will be stored in ``.pwd`` file (in the same folder where the Glances configuration file is stored, so ~/.config/glances/ on GNU Linux operating system). -Next time your run the server/client, password will not be asked. To set a -specific username you can use the -u option. +Next time you run the server/client, password will not be asked. To set a +specific username, you can use the -u option. It is also possible to set the default password in the Glances configuration file: diff --git a/docs/support.rst b/docs/support.rst index 086951a0..4de5e4bb 100644 --- a/docs/support.rst +++ b/docs/support.rst @@ -7,7 +7,7 @@ To post a question about Glances use cases, please post it to the official Q&A `forum `_. -To report a bug or a feature request use the GitHub `issue +To report a bug or a feature request, use the GitHub `issue `_ tracker. Feel free to contribute! diff --git a/glances/README.txt b/glances/README.txt index c5fd13eb..3e6de7eb 100644 --- a/glances/README.txt +++ b/glances/README.txt @@ -12,7 +12,7 @@ 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 (Bottle-based) +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 @@ -27,7 +27,7 @@ plugins outputs => Glances UI glances_curses.py The curses interface - glances_bottle.py The web interface + glances_restful-api.py The HTTP/API & Web based interface ... exports => Glances exports diff --git a/glances/__init__.py b/glances/__init__.py index 9ae141aa..23bbd18c 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -21,6 +21,7 @@ import sys # Version should start and end with a numerical char # See https://packaging.python.org/specifications/core-metadata/#version __version__ = '4.0.0_beta01' +__apiversion__ = '4' __author__ = 'Nicolas Hennion ' __license__ = 'LGPLv3' @@ -108,7 +109,7 @@ def start(config, args): # Start the main loop logger.debug("Glances started in {} seconds".format(start_duration.get())) if args.stop_after: - logger.info('Glances will be stopped in ~{} seconds'.format(args.stop_after * args.time * args.memory_leak * 2)) + logger.info('Glances will be stopped in ~{} seconds'.format(args.stop_after * args.time)) if args.memory_leak: print( diff --git a/glances/amps/glances_amp.py b/glances/amps/amp.py similarity index 98% rename from glances/amps/glances_amp.py rename to glances/amps/amp.py index 078e08a9..41881ea8 100644 --- a/glances/amps/glances_amp.py +++ b/glances/amps/amp.py @@ -42,7 +42,7 @@ class GlancesAmp(object): # AMP name (= module name without glances_) if name is None: - self.amp_name = self.__class__.__module__[len('glances_') :] + self.amp_name = self.__class__.__module__ else: self.amp_name = name diff --git a/glances/amps/glances_default.py b/glances/amps/default/__init__.py similarity index 97% rename from glances/amps/glances_default.py rename to glances/amps/default/__init__.py index dac41b38..886f0c8a 100644 --- a/glances/amps/glances_default.py +++ b/glances/amps/default/__init__.py @@ -29,7 +29,7 @@ from subprocess import check_output, STDOUT, CalledProcessError from glances.globals import u, to_ascii from glances.logger import logger -from glances.amps.glances_amp import GlancesAmp +from glances.amps.amp import GlancesAmp class Amp(GlancesAmp): diff --git a/glances/amps/glances_nginx.py b/glances/amps/nginx/__init__.py similarity index 98% rename from glances/amps/glances_nginx.py rename to glances/amps/nginx/__init__.py index 04ff5236..770c4aca 100644 --- a/glances/amps/glances_nginx.py +++ b/glances/amps/nginx/__init__.py @@ -47,7 +47,7 @@ status_url=http://localhost/nginx_status import requests from glances.logger import logger -from glances.amps.glances_amp import GlancesAmp +from glances.amps.amp import GlancesAmp class Amp(GlancesAmp): diff --git a/glances/amps/glances_systemd.py b/glances/amps/systemd/__init__.py similarity index 98% rename from glances/amps/glances_systemd.py rename to glances/amps/systemd/__init__.py index 736666a5..3df7a8ae 100644 --- a/glances/amps/glances_systemd.py +++ b/glances/amps/systemd/__init__.py @@ -39,7 +39,7 @@ from subprocess import check_output, CalledProcessError from glances.logger import logger from glances.globals import iteritems, to_ascii -from glances.amps.glances_amp import GlancesAmp +from glances.amps.amp import GlancesAmp class Amp(GlancesAmp): diff --git a/glances/amps/glances_systemv.py b/glances/amps/systemv/__init__.py similarity index 98% rename from glances/amps/glances_systemv.py rename to glances/amps/systemv/__init__.py index afd8abe0..78df6795 100644 --- a/glances/amps/glances_systemv.py +++ b/glances/amps/systemv/__init__.py @@ -38,7 +38,7 @@ from subprocess import check_output, STDOUT from glances.logger import logger from glances.globals import iteritems -from glances.amps.glances_amp import GlancesAmp +from glances.amps.amp import GlancesAmp class Amp(GlancesAmp): diff --git a/glances/amps_list.py b/glances/amps_list.py index c3c63627..41f82c37 100644 --- a/glances/amps_list.py +++ b/glances/amps_list.py @@ -45,41 +45,30 @@ class AmpsList(object): if self.config is None: return False - # Display a warning (deprecated) message if the monitor section exist - if "monitor" in self.config.sections(): - logger.warning( - "A deprecated [monitor] section exists in the Glances configuration file. You should use the new \ - Applications Monitoring Process module instead \ - (http://glances.readthedocs.io/en/develop/aoa/amps.html)." - ) - - # TODO: Change the way AMP are loaded (use folder/module instead of glances_foo.py file) - # See https://github.com/nicolargo/glances/issues/1930 - header = "glances_" # For each AMP script, call the load_config method for s in self.config.sections(): if s.startswith("amp_"): # An AMP section exists in the configuration file - # If an AMP script exist in the glances/amps folder, use it - amp_conf_name = s[4:] - amp_script = os.path.join(amps_path, header + s[4:] + ".py") - if not os.path.exists(amp_script): + # If an AMP module exist in amps_path (glances/amps) folder then use it + amp_name = s[4:] + amp_module = os.path.join(amps_path, amp_name) + if not os.path.exists(amp_module): # If not, use the default script - amp_script = os.path.join(amps_path, "glances_default.py") + amp_module = os.path.join(amps_path, "default") try: - amp = __import__(os.path.basename(amp_script)[:-3]) + amp = __import__(os.path.basename(amp_module)) except ImportError as e: - logger.warning("Missing Python Lib ({}), cannot load {} AMP".format(e, amp_conf_name)) + logger.warning("Missing Python Lib ({}), cannot load AMP {}".format(e, amp_name)) except Exception as e: - logger.warning("Cannot load {} AMP ({})".format(amp_conf_name, e)) + logger.warning("Cannot load AMP {} ({})".format(amp_name, e)) else: # Add the AMP to the dictionary # The key is the AMP name # for example, the file glances_xxx.py # generate self._amps_list["xxx"] = ... - self.__amps_dict[amp_conf_name] = amp.Amp(name=amp_conf_name, args=self.args) + self.__amps_dict[amp_name] = amp.Amp(name=amp_name, args=self.args) # Load the AMP configuration - self.__amps_dict[amp_conf_name].load_config(self.config) + self.__amps_dict[amp_name].load_config(self.config) # Log AMPs list logger.debug("AMPs list: {}".format(self.getList())) diff --git a/glances/events.py b/glances/events.py index b1a9ba3b..2dbc5100 100644 --- a/glances/events.py +++ b/glances/events.py @@ -105,14 +105,14 @@ class GlancesEvents(object): event_index = self.__event_exist(event_type) if event_index < 0: # Event did not exist, add it - self._create_event(event_state, event_type, event_value, proc_list, proc_desc, peak_time) + self._create_event(event_state, event_type, event_value, proc_desc) else: # Event exist, update it self._update_event(event_index, event_state, event_type, event_value, proc_list, proc_desc, peak_time) return self.len() - def _create_event(self, event_state, event_type, event_value, proc_list, proc_desc, peak_time): + def _create_event(self, event_state, event_type, event_value, proc_desc): """Add a new item in the log list. Item is added only if the criticality (event_state) is WARNING or CRITICAL. diff --git a/glances/exports/couchdb/__init__.py b/glances/exports/couchdb/__init__.py index 7f3a216a..5cc65529 100644 --- a/glances/exports/couchdb/__init__.py +++ b/glances/exports/couchdb/__init__.py @@ -36,9 +36,7 @@ class Export(GlancesExport): # Load the CouchDB configuration file section # User and Password are mandatory with CouchDB 3.0 and higher - self.export_enable = self.load_conf('couchdb', - mandatories=['host', 'port', 'db', - 'user', 'password']) + self.export_enable = self.load_conf('couchdb', mandatories=['host', 'port', 'db', 'user', 'password']) if not self.export_enable: sys.exit(2) @@ -51,8 +49,7 @@ class Export(GlancesExport): return None # @TODO: https - server_uri = 'http://{}:{}@{}:{}/'.format(self.user, self.password, - self.host, self.port) + server_uri = 'http://{}:{}@{}:{}/'.format(self.user, self.password, self.host, self.port) try: s = pycouchdb.Server(server_uri) diff --git a/glances/exports/export.py b/glances/exports/export.py index b4db4790..45a08c77 100644 --- a/glances/exports/export.py +++ b/glances/exports/export.py @@ -36,12 +36,13 @@ class GlancesExport(object): 'processlist', 'psutilversion', 'quicklook', + 'version', ] def __init__(self, config=None, args=None): """Init the export class.""" # Export name (= module name without glances_) - self.export_name = self.__class__.__module__[len('glances_') :] + self.export_name = self.__class__.__module__ logger.debug("Init export module %s" % self.export_name) # Init the config & args @@ -115,7 +116,7 @@ class GlancesExport(object): def parse_tags(self, tags): """Parse tags into a dict. - :param tags: a comma separated list of 'key:value' pairs. Example: foo:bar,spam:eggs + :param tags: a comma-separated list of 'key:value' pairs. Example: foo:bar,spam:eggs :return: a dict of tags. Example: {'foo': 'bar', 'spam': 'eggs'} """ d_tags = {} diff --git a/glances/exports/glances_mongodb.py b/glances/exports/mongodb/__init__.py similarity index 100% rename from glances/exports/glances_mongodb.py rename to glances/exports/mongodb/__init__.py diff --git a/glances/folder_list.py b/glances/folder_list.py index 2dccab93..595c536f 100644 --- a/glances/folder_list.py +++ b/glances/folder_list.py @@ -10,7 +10,6 @@ """Manage the folder list.""" from __future__ import unicode_literals -import os from glances.timer import Timer from glances.globals import nativestr, folder_size @@ -132,11 +131,12 @@ class FolderList(object): # Get folder size self.__folder_list[i]['size'], self.__folder_list[i]['errno'] = folder_size(self.path(i)) if self.__folder_list[i]['errno'] != 0: - logger.debug('Folder size ({} ~ {}) may not be correct. Error: {}'.format( - self.path(i), - self.__folder_list[i]['size'], - self.__folder_list[i]['errno'])) - # Reset the timer + logger.debug( + 'Folder size ({} ~ {}) may not be correct. Error: {}'.format( + self.path(i), self.__folder_list[i]['size'], self.__folder_list[i]['errno'] + ) + ) + # Reset the timer self.timer_folders[i].reset() # It is no more the first time... diff --git a/glances/globals.py b/glances/globals.py index 87a48817..315d0d57 100644 --- a/glances/globals.py +++ b/glances/globals.py @@ -25,6 +25,8 @@ import subprocess from datetime import datetime import re import base64 +import functools +import weakref import queue from configparser import ConfigParser, NoOptionError, NoSectionError @@ -315,10 +317,10 @@ def json_dumps(data): return ujson.dumps(data, ensure_ascii=False) -def json_dumps_dictlist(data, item): +def dictlist(data, item): if isinstance(data, dict): try: - return json_dumps({item: data[item]}) + return {item: data[item]} except (TypeError, IndexError, KeyError): return None elif isinstance(data, list): @@ -326,13 +328,21 @@ def json_dumps_dictlist(data, item): # Source: # http://stackoverflow.com/questions/4573875/python-get-index-of-dictionary-item-in-list # But https://github.com/nicolargo/glances/issues/1401 - return json_dumps({item: list(map(itemgetter(item), data))}) + return {item: list(map(itemgetter(item), data))} except (TypeError, IndexError, KeyError): return None else: return None +def json_dumps_dictlist(data, item): + dl = dictlist(data, item) + if dl is None: + return None + else: + return json_dumps(dl) + + def string_value_to_float(s): """Convert a string with a value and an unit to a float. Example: @@ -398,3 +408,21 @@ def folder_size(path, errno=0): except OSError as e: ret_err = e.errno return ret_size, ret_err + + +def weak_lru_cache(maxsize=128, typed=False): + """LRU Cache decorator that keeps a weak reference to self + Source: https://stackoverflow.com/a/55990799""" + + def wrapper(func): + @functools.lru_cache(maxsize, typed) + def _func(_self, *args, **kwargs): + return func(_self(), *args, **kwargs) + + @functools.wraps(func) + def inner(self, *args, **kwargs): + return _func(weakref.ref(self), *args, **kwargs) + + return inner + + return wrapper diff --git a/glances/main.py b/glances/main.py index aa7c58d6..534bd4a5 100644 --- a/glances/main.py +++ b/glances/main.py @@ -15,7 +15,7 @@ import tempfile from logging import DEBUG from warnings import simplefilter -from glances import __version__, psutil_version +from glances import __version__, psutil_version, __apiversion__ from glances.globals import WINDOWS, disable, enable from glances.config import Config from glances.processes import sort_processes_key_list @@ -83,13 +83,13 @@ Examples of use: Display CSV stats to stdout (all stats in one line): $ glances --stdout-csv now,cpu.user,mem.used,load - Enable some plugins disabled by default (comma separated list): + Enable some plugins disabled by default (comma-separated list): $ glances --enable-plugin sensors - Disable some plugins (comma separated list): + Disable some plugins (comma-separated list): $ glances --disable-plugin network,ports - Disable all plugins except some (comma separated list): + Disable all plugins except some (comma-separated list): $ glances --disable-plugin all --enable-plugin cpu,mem,load """ @@ -99,18 +99,26 @@ Examples of use: # Read the command line arguments self.args = self.parse_args() + def version_msg(self): + """Return the version message.""" + version = 'Glances version:\t{}\n'.format(__version__) + version += 'Glances API version:\t{}\n'.format(__apiversion__) + version += 'PsUtil version:\t\t{}\n'.format(psutil_version) + version += 'Log file:\t\t{}\n'.format(LOG_FILENAME) + return version + def init_args(self): """Init all the command line arguments.""" - version = 'Glances v{} with PsUtil v{}\nLog file: {}'.format(__version__, psutil_version, LOG_FILENAME) parser = argparse.ArgumentParser( prog='glances', conflict_handler='resolve', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=self.example_of_use, ) - parser.add_argument('-V', '--version', action='version', version=version) + parser.add_argument('-V', '--version', action='version', version=self.version_msg()) parser.add_argument('-d', '--debug', action='store_true', default=False, dest='debug', help='enable debug mode') parser.add_argument('-C', '--config', dest='conf_file', help='path to the configuration file') + parser.add_argument('-P', '--plugins', dest='plugin_dir', help='path to additional plugin directory') # Disable plugin parser.add_argument( '--modules-list', @@ -125,7 +133,7 @@ Examples of use: '--disable-plugins', '--disable', dest='disable_plugin', - help='disable plugin (comma separated list or all). If all is used, \ + help='disable plugin (comma-separated list or all). If all is used, \ then you need to configure --enable-plugin.', ) parser.add_argument( @@ -133,7 +141,7 @@ Examples of use: '--enable-plugins', '--enable', dest='enable_plugin', - help='enable plugin (comma separated list)' + help='enable plugin (comma-separated list)', ) parser.add_argument( '--disable-process', @@ -156,7 +164,7 @@ Examples of use: action='store_true', default=False, dest='enable_light', - help='light mode for Curses UI (disable all but top menu)', + help='light mode for Curses UI (disable all but the top menu)', ) parser.add_argument( '-0', @@ -267,7 +275,7 @@ Examples of use: help='Accumulate processes by program', ) # Export modules feature - parser.add_argument('--export', dest='export', help='enable export module (comma separated list)') + parser.add_argument('--export', dest='export', help='enable export module (comma-separated list)') parser.add_argument( '--export-csv-file', default='./glances.csv', dest='export_csv_file', help='file path for CSV exporter' ) @@ -362,7 +370,7 @@ Examples of use: action='store_true', default=False, dest='webserver', - help='run Glances in web server mode (bottle needed)', + help='run Glances in web server mode (FastAPI, Uvicorn, Jinja2 and OrJsonLib needed)', ) parser.add_argument( '--cached-time', @@ -420,19 +428,19 @@ Examples of use: '--stdout', default=None, dest='stdout', - help='display stats to stdout, one stat per line (comma separated list of plugins/plugins.attribute)', + help='display stats to stdout, one stat per line (comma-separated list of plugins/plugins.attribute)', ) parser.add_argument( '--stdout-json', default=None, dest='stdout_json', - help='display stats to stdout, JSON format (comma separated list of plugins/plugins.attribute)', + help='display stats to stdout, JSON format (comma-separated list of plugins/plugins.attribute)', ) parser.add_argument( '--stdout-csv', default=None, dest='stdout_csv', - help='display stats to stdout, CSV format (comma separated list of plugins/plugins.attribute)', + help='display stats to stdout, CSV format (comma-separated list of plugins/plugins.attribute)', ) parser.add_argument( '--issue', @@ -464,7 +472,7 @@ Examples of use: action='store_true', default=False, dest='no_kernel_threads', - help='hide kernel threads in process list (not available on Windows)', + help='hide kernel threads in the process list (not available on Windows)', ) parser.add_argument( '-b', @@ -472,7 +480,7 @@ Examples of use: action='store_true', default=False, dest='byte', - help='display network rate in byte per second', + help='display network rate in bytes per second', ) parser.add_argument( '--diskio-show-ramfs', @@ -521,7 +529,7 @@ Examples of use: action='store_true', default=False, dest='theme_white', - help='optimize display colors for white background', + help='optimize display colors for a white background', ) # Globals options parser.add_argument( diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py deleted file mode 100644 index df6cfc71..00000000 --- a/glances/outputs/glances_bottle.py +++ /dev/null @@ -1,668 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Glances. -# -# SPDX-FileCopyrightText: 2023 Nicolas Hennion -# -# SPDX-License-Identifier: LGPL-3.0-only -# - -"""RestFull API interface class.""" - -import os -import sys -import tempfile -from io import open -import webbrowser -import zlib -import socket -from urllib.parse import urljoin - -from glances.globals import b, json_dumps -from glances.timer import Timer -from glances.logger import logger - -try: - from bottle import Bottle, static_file, abort, response, request, auth_basic, template, TEMPLATE_PATH -except ImportError: - logger.critical('Bottle module not found. Glances cannot start in web server mode.') - sys.exit(2) - - -def compress(func): - """Compress result with deflate algorithm if the client ask for it.""" - - def wrapper(*args, **kwargs): - """Wrapper that take one function and return the compressed result.""" - ret = func(*args, **kwargs) - logger.debug( - 'Receive {} {} request with header: {}'.format( - request.method, - request.url, - ['{}: {}'.format(h, request.headers.get(h)) for h in request.headers.keys()], - ) - ) - if 'deflate' in request.headers.get('Accept-Encoding', ''): - response.headers['Content-Encoding'] = 'deflate' - ret = deflate_compress(ret) - else: - response.headers['Content-Encoding'] = 'identity' - return ret - - def deflate_compress(data, compress_level=6): - """Compress given data using the DEFLATE algorithm""" - # Init compression - zobj = zlib.compressobj( - compress_level, zlib.DEFLATED, zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, zlib.Z_DEFAULT_STRATEGY - ) - - # Return compressed object - return zobj.compress(b(data)) + zobj.flush() - - return wrapper - - -class GlancesBottle(object): - """This class manages the Bottle Web server.""" - - API_VERSION = '3' - - def __init__(self, config=None, args=None): - # Init config - self.config = config - - # Init args - self.args = args - - # Init stats - # Will be updated within Bottle route - self.stats = 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) - self.timer = Timer(0) - - # Load configuration file - self.load_config(config) - - # Set the bind URL (only used for log information purpose) - self.bind_url = urljoin('http://{}:{}/'.format(self.args.bind_address, self.args.port), self.url_prefix) - - # Init Bottle - self._app = Bottle() - # Enable CORS (issue #479) - self._app.install(EnableCors()) - # Password - if args.password != '': - self._app.install(auth_basic(self.check_auth)) - # Define routes - self._route() - - # Path where the statics files are stored - self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/public') - - # Paths for templates - TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/templates')) - - def load_config(self, config): - """Load the outputs section of the configuration file.""" - # Limit the number of processes to display in the WebUI - self.url_prefix = '/' - if config is not None and config.has_section('outputs'): - n = config.get_value('outputs', 'max_processes_display', default=None) - logger.debug('Number of processes to display in the WebUI: {}'.format(n)) - self.url_prefix = config.get_value('outputs', 'url_prefix', default='/') - logger.debug('URL prefix: {}'.format(self.url_prefix)) - - def __update__(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 app(self): - return self._app() - - def check_auth(self, username, password): - """Check if a username/password combination is valid.""" - if username == self.args.username: - from glances.password import GlancesPassword - - pwd = GlancesPassword(username=username, config=self.config) - return pwd.check_password(self.args.password, pwd.get_hash(password)) - else: - return False - - def _route(self): - """Define route.""" - # REST API - self._app.route('/api/%s/status' % self.API_VERSION, method="GET", callback=self._api_status) - self._app.route('/api/%s/config' % self.API_VERSION, method="GET", callback=self._api_config) - self._app.route('/api/%s/config/' % self.API_VERSION, method="GET", callback=self._api_config_item) - self._app.route('/api/%s/args' % self.API_VERSION, method="GET", callback=self._api_args) - self._app.route('/api/%s/args/' % self.API_VERSION, method="GET", callback=self._api_args_item) - self._app.route('/api/%s/help' % self.API_VERSION, method="GET", callback=self._api_help) - self._app.route('/api/%s/pluginslist' % self.API_VERSION, method="GET", callback=self._api_plugins) - self._app.route('/api/%s/all' % self.API_VERSION, method="GET", callback=self._api_all) - self._app.route('/api/%s/all/limits' % self.API_VERSION, method="GET", callback=self._api_all_limits) - self._app.route('/api/%s/all/views' % self.API_VERSION, method="GET", callback=self._api_all_views) - self._app.route('/api/%s/' % self.API_VERSION, method="GET", callback=self._api) - self._app.route('/api/%s//history' % self.API_VERSION, method="GET", callback=self._api_history) - self._app.route( - '/api/%s//history/' % self.API_VERSION, method="GET", callback=self._api_history - ) - self._app.route('/api/%s//top/' % self.API_VERSION, method="GET", callback=self._api_top) - self._app.route('/api/%s//limits' % self.API_VERSION, method="GET", callback=self._api_limits) - self._app.route('/api/%s//views' % self.API_VERSION, method="GET", callback=self._api_views) - self._app.route('/api/%s//' % self.API_VERSION, method="GET", callback=self._api_item) - self._app.route( - '/api/%s///history' % self.API_VERSION, method="GET", callback=self._api_item_history - ) - self._app.route( - '/api/%s///history/' % self.API_VERSION, method="GET", callback=self._api_item_history - ) - self._app.route('/api/%s///' % self.API_VERSION, method="GET", callback=self._api_value) - self._app.route( - '/api/%s///' % self.API_VERSION, method="GET", callback=self._api_value - ) - bindmsg = 'Glances RESTful API Server started on {}api/{}'.format(self.bind_url, self.API_VERSION) - logger.info(bindmsg) - - # WEB UI - if not self.args.disable_webui: - self._app.route('/', method="GET", callback=self._index) - self._app.route('/', method=["GET"], callback=self._index) - self._app.route('/', method="GET", callback=self._resource) - bindmsg = 'Glances Web User Interface started on {}'.format(self.bind_url) - else: - bindmsg = 'The WebUI is disable (--disable-webui)' - - logger.info(bindmsg) - print(bindmsg) - - def start(self, stats): - """Start the bottle.""" - # Init stats - self.stats = stats - - # Init plugin list - self.plugins_list = self.stats.getPluginsList() - - # Bind the Bottle TCP address/port - if self.args.open_web_browser: - # Implementation of the issue #946 - # Try to open the Glances Web UI in the default Web browser if: - # 1) --open-web-browser option is used - # 2) Glances standalone mode is running on Windows OS - webbrowser.open(self.bind_url, new=2, autoraise=1) - - # Run the Web application - if self.url_prefix != '/': - # Create an outer Bottle class instance to manage url_prefix - self.main_app = Bottle() - self.main_app.mount(self.url_prefix, self._app) - try: - self.main_app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug) - except socket.error as e: - logger.critical('Error: Can not ran Glances Web server ({})'.format(e)) - else: - try: - self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug) - except socket.error as e: - logger.critical('Error: Can not ran Glances Web server ({})'.format(e)) - - def end(self): - """End the bottle.""" - logger.info("Close the Web server") - self._app.close() - if self.url_prefix != '/': - self.main_app.close() - - def _index(self, refresh_time=None): - """Bottle callback for index.html (/) file.""" - - if refresh_time is None or refresh_time < 1: - refresh_time = int(self.args.time) - - # Update the stat - self.__update__() - - # Display - return template("index.html", refresh_time=refresh_time) - - def _resource(self, filepath): - """Bottle callback for resources files.""" - # Return the static file - return static_file(filepath, root=self.STATIC_PATH) - - @compress - def _api_status(self): - """Glances API RESTful implementation. - - Return a 200 status code. - This entry point should be used to check the API health. - - See related issue: Web server health check endpoint #1988 - """ - response.status = 200 - - return "Active" - - @compress - def _api_help(self): - """Glances API RESTful implementation. - - Return the help data or 404 error. - """ - response.content_type = 'application/json; charset=utf-8' - - # Update the stat - view_data = self.stats.get_plugin("help").get_view_data() - try: - plist = json_dumps(view_data) - except Exception as e: - abort(404, "Cannot get help view data (%s)" % str(e)) - return plist - - @compress - def _api_plugins(self): - """Glances API RESTFul implementation. - - @api {get} /api/%s/pluginslist Get plugins list - @apiVersion 2.0 - @apiName pluginslist - @apiGroup plugin - - @apiSuccess {String[]} Plugins list. - - @apiSuccessExample Success-Response: - HTTP/1.1 200 OK - [ - "load", - "help", - "ip", - "memswap", - "processlist", - ... - ] - - @apiError Cannot get plugin list. - - @apiErrorExample Error-Response: - HTTP/1.1 404 Not Found - """ - response.content_type = 'application/json; charset=utf-8' - - # Update the stat - self.__update__() - - try: - plist = json_dumps(self.plugins_list) - except Exception as e: - abort(404, "Cannot get plugin list (%s)" % str(e)) - return plist - - @compress - def _api_all(self): - """Glances API RESTful implementation. - - Return the JSON representation of all the plugins - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if self.args.debug: - fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json') - try: - with open(fname) as f: - return f.read() - except IOError: - logger.debug("Debug file (%s) not found" % fname) - - # Update the stat - self.__update__() - - try: - # Get the JSON value of the stat ID - statval = json_dumps(self.stats.getAllAsDict()) - except Exception as e: - abort(404, "Cannot get stats (%s)" % str(e)) - - return statval - - @compress - def _api_all_limits(self): - """Glances API RESTful implementation. - - Return the JSON representation of all the plugins limits - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - try: - # Get the JSON value of the stat limits - limits = json_dumps(self.stats.getAllLimitsAsDict()) - except Exception as e: - abort(404, "Cannot get limits (%s)" % (str(e))) - return limits - - @compress - def _api_all_views(self): - """Glances API RESTful implementation. - - Return the JSON representation of all the plugins views - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - try: - # Get the JSON value of the stat view - limits = json_dumps(self.stats.getAllViewsAsDict()) - except Exception as e: - abort(404, "Cannot get views (%s)" % (str(e))) - return limits - - @compress - def _api(self, plugin): - """Glances API RESTful implementation. - - Return the JSON representation of a given plugin - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - self.__update__() - - try: - # Get the JSON value of the stat ID - statval = self.stats.get_plugin(plugin).get_stats() - except Exception as e: - abort(404, "Cannot get plugin %s (%s)" % (plugin, str(e))) - - return statval - - @compress - def _api_top(self, plugin, nb=0): - """Glances API RESTful implementation. - - Return the JSON representation of a given plugin limited to the top nb items. - It is used to reduce the payload of the HTTP response (example: processlist). - - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - self.__update__() - - try: - # Get the value of the stat ID - statval = self.stats.get_plugin(plugin).get_export() - except Exception as e: - abort(404, "Cannot get plugin %s (%s)" % (plugin, str(e))) - - if isinstance(statval, list): - return json_dumps(statval[:nb]) - else: - return json_dumps(statval) - - @compress - def _api_history(self, plugin, nb=0): - """Glances API RESTful implementation. - - Return the JSON representation of a given plugin history - Limit to the last nb items (all if nb=0) - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - self.__update__() - - try: - # Get the JSON value of the stat ID - statval = self.stats.get_plugin(plugin).get_stats_history(nb=int(nb)) - except Exception as e: - abort(404, "Cannot get plugin history %s (%s)" % (plugin, str(e))) - return statval - - @compress - def _api_limits(self, plugin): - """Glances API RESTful implementation. - - Return the JSON limits of a given plugin - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - # self.__update__() - - try: - # Get the JSON value of the stat limits - ret = self.stats.get_plugin(plugin).limits - except Exception as e: - abort(404, "Cannot get limits for plugin %s (%s)" % (plugin, str(e))) - return ret - - @compress - def _api_views(self, plugin): - """Glances API RESTful implementation. - - Return the JSON views of a given plugin - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - # self.__update__() - - try: - # Get the JSON value of the stat views - ret = self.stats.get_plugin(plugin).get_views() - except Exception as e: - abort(404, "Cannot get views for plugin %s (%s)" % (plugin, str(e))) - return ret - - # No compression see issue #1228 - # @compress - def _api_itemvalue(self, plugin, item, value=None, history=False, nb=0): - """Father method for _api_item and _api_value.""" - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - self.__update__() - - if value is None: - if history: - ret = self.stats.get_plugin(plugin).get_stats_history(item, nb=int(nb)) - else: - ret = self.stats.get_plugin(plugin).get_stats_item(item) - - if ret is None: - abort(404, "Cannot get item %s%s in plugin %s" % (item, 'history ' if history else '', plugin)) - else: - if history: - # Not available - ret = None - else: - ret = self.stats.get_plugin(plugin).get_stats_value(item, value) - - if ret is None: - abort( - 404, "Cannot get item %s(%s=%s) in plugin %s" % ('history ' if history else '', item, value, plugin) - ) - - return ret - - @compress - def _api_item(self, plugin, item): - """Glances API RESTful implementation. - - Return the JSON representation of the couple plugin/item - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - - """ - return self._api_itemvalue(plugin, item) - - @compress - def _api_item_history(self, plugin, item, nb=0): - """Glances API RESTful implementation. - - Return the JSON representation of the couple plugin/history of item - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - - """ - return self._api_itemvalue(plugin, item, history=True, nb=int(nb)) - - @compress - def _api_value(self, plugin, item, value): - """Glances API RESTful implementation. - - Return the process stats (dict) for the given item=value - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - return self._api_itemvalue(plugin, item, value) - - @compress - def _api_config(self): - """Glances API RESTful implementation. - - Return the JSON representation of the Glances configuration file - HTTP/200 if OK - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - try: - # Get the JSON value of the config' dict - args_json = json_dumps(self.config.as_dict()) - except Exception as e: - abort(404, "Cannot get config (%s)" % str(e)) - return args_json - - @compress - def _api_config_item(self, item): - """Glances API RESTful implementation. - - Return the JSON representation of the Glances configuration item - HTTP/200 if OK - HTTP/400 if item is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - config_dict = self.config.as_dict() - if item not in config_dict: - abort(400, "Unknown configuration item %s" % item) - - try: - # Get the JSON value of the config' dict - args_json = json_dumps(config_dict[item]) - except Exception as e: - abort(404, "Cannot get config item (%s)" % str(e)) - return args_json - - @compress - def _api_args(self): - """Glances API RESTful implementation. - - Return the JSON representation of the Glances command line arguments - HTTP/200 if OK - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - try: - # Get the JSON value of the args' dict - # Use vars to convert namespace to dict - # Source: https://docs.python.org/%s/library/functions.html#vars - args_json = json_dumps(vars(self.args)) - except Exception as e: - abort(404, "Cannot get args (%s)" % str(e)) - return args_json - - @compress - def _api_args_item(self, item): - """Glances API RESTful implementation. - - Return the JSON representation of the Glances command line arguments item - HTTP/200 if OK - HTTP/400 if item is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if item not in self.args: - abort(400, "Unknown argument item %s" % item) - - try: - # Get the JSON value of the args' dict - # Use vars to convert namespace to dict - # Source: https://docs.python.org/%s/library/functions.html#vars - args_json = json_dumps(vars(self.args)[item]) - except Exception as e: - abort(404, "Cannot get args item (%s)" % str(e)) - return args_json - - -class EnableCors(object): - name = 'enable_cors' - api = 2 - - def apply(self, fn, context): - def _enable_cors(*args, **kwargs): - # set CORS headers - response.headers['Access-Control-Allow-Origin'] = '*' - response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS' - response.headers[ - 'Access-Control-Allow-Headers' - ] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' - - if request.method != 'OPTIONS': - # actual request; reply with the actual response - return fn(*args, **kwargs) - - return _enable_cors diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 3f53ea77..f4e70c81 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -139,10 +139,21 @@ class _GlancesCurses(object): self.space_between_line = 2 # Init the curses screen - self.screen = curses.initscr() - if not self.screen: - logger.critical("Cannot init the curses library.\n") - sys.exit(1) + try: + self.screen = curses.initscr() + if not self.screen: + logger.critical("Cannot init the curses library.\n") + sys.exit(1) + else: + logger.debug("Curses library initialized with term: {}".format(curses.longname())) + except Exception as e: + if args.export: + logger.info("Cannot init the curses library, quiet mode on and export.") + args.quiet = True + return + else: + logger.critical("Cannot init the curses library ({})".format(e)) + sys.exit(1) # Load the 'outputs' section of the configuration file # - Init the theme (default is black) @@ -236,6 +247,9 @@ class _GlancesCurses(object): if curses.has_colors(): # The screen is compatible with a colored design + # ex: export TERM=xterm-256color + # export TERM=xterm-color + if self.is_theme('white'): # White theme: black ==> white curses.init_pair(1, curses.COLOR_BLACK, -1) @@ -244,35 +258,36 @@ class _GlancesCurses(object): if self.args.disable_bg: curses.init_pair(2, curses.COLOR_RED, -1) curses.init_pair(3, curses.COLOR_GREEN, -1) - curses.init_pair(4, curses.COLOR_BLUE, -1) curses.init_pair(5, curses.COLOR_MAGENTA, -1) else: curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED) curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_GREEN) - curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLUE) 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) - curses.init_pair(8, curses.COLOR_BLUE, -1) + curses.init_pair(8, curses.COLOR_MAGENTA, -1) # Colors text styles self.no_color = curses.color_pair(1) self.default_color = curses.color_pair(3) | A_BOLD - self.nice_color = curses.color_pair(5) - self.cpu_time_color = curses.color_pair(5) + self.nice_color = curses.color_pair(8) + self.cpu_time_color = curses.color_pair(8) self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD self.ifWARNING_color = curses.color_pair(5) | A_BOLD self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD self.default_color2 = curses.color_pair(7) - self.ifCAREFUL_color2 = curses.color_pair(8) | A_BOLD - self.ifWARNING_color2 = curses.color_pair(5) | A_BOLD + self.ifCAREFUL_color2 = curses.color_pair(4) + self.ifWARNING_color2 = curses.color_pair(8) | A_BOLD self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD - self.ifINFO_color = curses.color_pair(8) + self.ifINFO_color = curses.color_pair(4) self.filter_color = A_BOLD self.selected_color = A_BOLD + self.separator = curses.color_pair(1) - if curses.COLOR_PAIRS > 8: - colors_list = [curses.COLOR_MAGENTA, curses.COLOR_CYAN, curses.COLOR_YELLOW] + if curses.COLORS > 8: + # ex: export TERM=xterm-256color + colors_list = [curses.COLOR_CYAN, curses.COLOR_YELLOW] for i in range(0, 3): try: curses.init_pair(i + 9, colors_list[i], -1) @@ -281,29 +296,32 @@ class _GlancesCurses(object): curses.init_pair(i + 9, curses.COLOR_BLACK, -1) else: curses.init_pair(i + 9, curses.COLOR_WHITE, -1) - self.nice_color = curses.color_pair(9) - self.cpu_time_color = curses.color_pair(9) - self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD - self.filter_color = curses.color_pair(10) | A_BOLD - self.selected_color = curses.color_pair(11) | A_BOLD + self.filter_color = curses.color_pair(9) | A_BOLD + self.selected_color = curses.color_pair(10) | A_BOLD + # Define separator line style + curses.init_color(11, 500, 500, 500) + curses.init_pair(11, curses.COLOR_BLACK, -1) + self.separator = curses.color_pair(11) else: # The screen is NOT compatible with a colored design # switch to B&W text styles + # ex: export TERM=xterm-mono self.no_color = curses.A_NORMAL self.default_color = curses.A_NORMAL self.nice_color = A_BOLD self.cpu_time_color = A_BOLD - self.ifCAREFUL_color = curses.A_UNDERLINE - self.ifWARNING_color = A_BOLD + self.ifCAREFUL_color = A_BOLD + self.ifWARNING_color = curses.A_UNDERLINE self.ifCRITICAL_color = curses.A_REVERSE self.default_color2 = curses.A_NORMAL - self.ifCAREFUL_color2 = curses.A_UNDERLINE - self.ifWARNING_color2 = A_BOLD + self.ifCAREFUL_color2 = A_BOLD + self.ifWARNING_color2 = curses.A_UNDERLINE self.ifCRITICAL_color2 = curses.A_REVERSE self.ifINFO_color = A_BOLD self.filter_color = A_BOLD self.selected_color = A_BOLD + self.separator = curses.COLOR_BLACK # Define the colors list (hash table) for stats self.colors_list = { @@ -331,6 +349,7 @@ class _GlancesCurses(object): 'SELECTED': self.selected_color, 'INFO': self.ifINFO_color, 'ERROR': self.selected_color, + 'SEPARATOR': self.separator, } def set_cursor(self, value): @@ -428,9 +447,7 @@ class _GlancesCurses(object): ) def _handle_sort_key(self, hotkey): - glances_processes.set_sort_key( - self._hotkeys[hotkey]['sort_key'], self._hotkeys[hotkey]['sort_key'] == 'auto' - ) + glances_processes.set_sort_key(self._hotkeys[hotkey]['sort_key'], self._hotkeys[hotkey]['sort_key'] == 'auto') def _handle_enter(self): self.edit_filter = not self.edit_filter @@ -578,7 +595,7 @@ class _GlancesCurses(object): """New column in the curses interface.""" self.column = self.next_column - def separator_line(self, color='TITLE'): + def separator_line(self, color='SEPARATOR'): """New separator line in the curses interface.""" if not self.args.enable_separator: return @@ -1240,7 +1257,7 @@ class _GlancesCurses(object): def wait(self, delay=100): """Wait delay in ms""" - curses.napms(100) + curses.napms(delay) def get_stats_display_width(self, curse_msg, without_option=False): """Return the width of the formatted curses message.""" diff --git a/glances/outputs/glances_restful_api.py b/glances/outputs/glances_restful_api.py new file mode 100644 index 00000000..a0c994e6 --- /dev/null +++ b/glances/outputs/glances_restful_api.py @@ -0,0 +1,740 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# SPDX-FileCopyrightText: 2023 Nicolas Hennion +# +# SPDX-License-Identifier: LGPL-3.0-only +# + +"""RestFull API interface class.""" + +import os +import sys +import tempfile +from io import open +import webbrowser +import socket +from urllib.parse import urljoin + +# Replace typing_extensions by typing when Python 3.8 support will be dropped +from typing import Annotated + +from glances import __version__, __apiversion__ +from glances.password import GlancesPassword +from glances.timer import Timer +from glances.logger import logger + +# FastAPI import +try: + from fastapi import FastAPI, Depends, HTTPException, status, APIRouter, Request + from fastapi.security import HTTPBasic, HTTPBasicCredentials + from fastapi.middleware.cors import CORSMiddleware + from fastapi.middleware.gzip import GZipMiddleware + from fastapi.responses import HTMLResponse, ORJSONResponse + from fastapi.templating import Jinja2Templates + from fastapi.staticfiles import StaticFiles +except ImportError: + logger.critical('FastAPI import error. Glances cannot start in web server mode.') + sys.exit(2) + +try: + import uvicorn +except ImportError: + logger.critical('Uvicorn import error. Glances cannot start in web server mode.') + sys.exit(2) + +security = HTTPBasic() + + +class GlancesRestfulApi(object): + """This class manages the Restful API server.""" + + API_VERSION = __apiversion__ + + def __init__(self, config=None, args=None): + # Init config + self.config = config + + # Init args + self.args = args + + # Init stats + # Will be updated within Bottle route + self.stats = 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) + self.timer = Timer(0) + + # Load configuration file + self.load_config(config) + + # Set the bind URL + self.bind_url = urljoin('http://{}:{}/'.format(self.args.bind_address, self.args.port), self.url_prefix) + + # FastAPI Init + if self.args.password: + self._app = FastAPI(dependencies=[Depends(self.authentication)]) + self._password = GlancesPassword(username=args.username, config=config) + + else: + self._app = FastAPI() + self._password = None + + # Change the default root path + if self.url_prefix != '/': + self._app.include_router(APIRouter(prefix=self.url_prefix)) + + # Set path for WebUI + self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/public') + self.TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/templates') + self._templates = Jinja2Templates(directory=self.TEMPLATE_PATH) + + # FastAPI Enable CORS + # https://fastapi.tiangolo.com/tutorial/cors/ + self._app.add_middleware( + CORSMiddleware, + # allow_origins=["*"], + allow_origins=[self.bind_url], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + # FastAPI Enable GZIP compression + # https://fastapi.tiangolo.com/advanced/middleware/ + self._app.add_middleware(GZipMiddleware, minimum_size=1000) + + # FastAPI Define routes + self._app.include_router(self._router()) + + def load_config(self, config): + """Load the outputs section of the configuration file.""" + # Limit the number of processes to display in the WebUI + self.url_prefix = '/' + if config is not None and config.has_section('outputs'): + n = config.get_value('outputs', 'max_processes_display', default=None) + logger.debug('Number of processes to display in the WebUI: {}'.format(n)) + self.url_prefix = config.get_value('outputs', 'url_prefix', default='/') + logger.debug('URL prefix: {}'.format(self.url_prefix)) + + def __update__(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 app(self): + return self._app() + + def authentication(self, creds: Annotated[HTTPBasicCredentials, Depends(security)]): + """Check if a username/password combination is valid.""" + if creds.username == self.args.username: + # check_password and get_hash are (lru) cached to optimize the requests + if self._password.check_password(self.args.password, self._password.get_hash(creds.password)): + return creds.username + + # If the username/password combination is invalid, return an HTTP 401 + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Basic"}, + ) + + def _router(self): + """Define a custom router for Glances path.""" + router = APIRouter() + + # REST API + router.add_api_route( + '/api/%s/status' % self.API_VERSION, + status_code=status.HTTP_200_OK, + response_class=ORJSONResponse, + endpoint=self._api_status, + ) + + router.add_api_route( + '/api/%s/config' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_config + ) + router.add_api_route( + '/api/%s/config/{section}' % self.API_VERSION, + response_class=ORJSONResponse, + endpoint=self._api_config_section, + ) + router.add_api_route( + '/api/%s/config/{section}/{item}' % self.API_VERSION, + response_class=ORJSONResponse, + endpoint=self._api_config_section_item, + ) + + router.add_api_route('/api/%s/args' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_args) + router.add_api_route( + '/api/%s/args/{item}' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_args_item + ) + + router.add_api_route( + '/api/%s/pluginslist' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_plugins + ) + router.add_api_route('/api/%s/all' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_all) + router.add_api_route( + '/api/%s/all/limits' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_all_limits + ) + router.add_api_route( + '/api/%s/all/views' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_all_views + ) + + router.add_api_route('/api/%s/help' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_help) + router.add_api_route('/api/%s/{plugin}' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api) + router.add_api_route( + '/api/%s/{plugin}/history' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_history + ) + router.add_api_route( + '/api/%s/{plugin}/history/{nb}' % self.API_VERSION, + response_class=ORJSONResponse, + endpoint=self._api_history, + ) + router.add_api_route( + '/api/%s/{plugin}/top/{nb}' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_top + ) + router.add_api_route( + '/api/%s/{plugin}/limits' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_limits + ) + router.add_api_route( + '/api/%s/{plugin}/views' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_views + ) + router.add_api_route( + '/api/%s/{plugin}/{item}' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_item + ) + router.add_api_route( + '/api/%s/{plugin}/{item}/history' % self.API_VERSION, + response_class=ORJSONResponse, + endpoint=self._api_item_history, + ) + router.add_api_route( + '/api/%s/{plugin}/{item}/history/{nb}' % self.API_VERSION, + response_class=ORJSONResponse, + endpoint=self._api_item_history, + ) + router.add_api_route( + '/api/%s/{plugin}/{item}/{value}' % self.API_VERSION, + response_class=ORJSONResponse, + endpoint=self._api_value, + ) + + # Restful API + bindmsg = 'Glances RESTful API Server started on {}api/{}'.format(self.bind_url, self.API_VERSION) + logger.info(bindmsg) + + # WEB UI + if not self.args.disable_webui: + # Template for the root index.html file + router.add_api_route('/', response_class=HTMLResponse, endpoint=self._index) + + # Statics files + self._app.mount("/static", StaticFiles(directory=self.STATIC_PATH), name="static") + + bindmsg = 'Glances Web User Interface started on {}'.format(self.bind_url) + else: + bindmsg = 'The WebUI is disable (--disable-webui)' + + logger.info(bindmsg) + print(bindmsg) + + return router + + def start(self, stats): + """Start the bottle.""" + # Init stats + self.stats = stats + + # Init plugin list + self.plugins_list = self.stats.getPluginsList() + + # Bind the Bottle TCP address/port + if self.args.open_web_browser: + # Implementation of the issue #946 + # Try to open the Glances Web UI in the default Web browser if: + # 1) --open-web-browser option is used + # 2) Glances standalone mode is running on Windows OS + webbrowser.open(self.bind_url, new=2, autoraise=1) + + # Run the Web application + try: + uvicorn.run(self._app, host=self.args.bind_address, port=self.args.port, access_log=self.args.debug) + except socket.error as e: + logger.critical('Error: Can not ran Glances Web server ({})'.format(e)) + + def end(self): + """End the Web server""" + logger.info("Close the Web server") + + def _index(self, request: Request): + """Return main index.html (/) file. + + Parameters are available through the request object. + Example: http://localhost:61208/?refresh=5 + + 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))) + + # Update the stat + self.__update__() + + # Display + return self._templates.TemplateResponse( + "index.html", + { + "request": request, + "refresh_time": refresh_time, + }, + ) + + def _api_status(self): + """Glances API RESTful implementation. + + Return a 200 status code. + This entry point should be used to check the API health. + + See related issue: Web server health check endpoint #1988 + """ + + return ORJSONResponse({'version': __version__}) + + def _api_help(self): + """Glances API RESTful implementation. + + Return the help data or 404 error. + """ + try: + plist = self.stats.get_plugin("help").get_view_data() + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get help view data (%s)" % str(e)) + + return ORJSONResponse(plist) + + def _api_plugins(self): + """Glances API RESTFul implementation. + + @api {get} /api/%s/pluginslist Get plugins list + @apiVersion 2.0 + @apiName pluginslist + @apiGroup plugin + + @apiSuccess {String[]} Plugins list. + + @apiSuccessExample Success-Response: + HTTP/1.1 200 OK + [ + "load", + "help", + "ip", + "memswap", + "processlist", + ... + ] + + @apiError Cannot get plugin list. + + @apiErrorExample Error-Response: + HTTP/1.1 404 Not Found + """ + # Update the stat + self.__update__() + + try: + plist = self.plugins_list + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get plugin list (%s)" % str(e)) + + return ORJSONResponse(plist) + + def _api_all(self): + """Glances API RESTful implementation. + + Return the JSON representation of all the plugins + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + if self.args.debug: + fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json') + try: + with open(fname) as f: + return f.read() + except IOError: + logger.debug("Debug file (%s) not found" % fname) + + # Update the stat + self.__update__() + + try: + # Get the RAW value of the stat ID + statval = self.stats.getAllAsDict() + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get stats (%s)" % str(e)) + + return ORJSONResponse(statval) + + def _api_all_limits(self): + """Glances API RESTful implementation. + + Return the JSON representation of all the plugins limits + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + try: + # Get the RAW value of the stat limits + limits = self.stats.getAllLimitsAsDict() + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get limits (%s)" % str(e)) + + return ORJSONResponse(limits) + + def _api_all_views(self): + """Glances API RESTful implementation. + + Return the JSON representation of all the plugins views + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + try: + # Get the RAW value of the stat view + limits = self.stats.getAllViewsAsDict() + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get views (%s)" % str(e)) + + return ORJSONResponse(limits) + + def _api(self, plugin): + """Glances API RESTful implementation. + + Return the JSON representation of a given plugin + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + if plugin not in self.plugins_list: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list), + ) + + # Update the stat + self.__update__() + + try: + # Get the RAW value of the stat ID + statval = self.stats.get_plugin(plugin).get_raw() + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get plugin %s (%s)" % (plugin, str(e)) + ) + + return ORJSONResponse(statval) + + def _api_top(self, plugin, nb: int = 0): + """Glances API RESTful implementation. + + Return the JSON representation of a given plugin limited to the top nb items. + It is used to reduce the payload of the HTTP response (example: processlist). + + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + if plugin not in self.plugins_list: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list), + ) + + # Update the stat + self.__update__() + + try: + # Get the RAW value of the stat ID + statval = self.stats.get_plugin(plugin).get_export() + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get plugin %s (%s)" % (plugin, str(e)) + ) + + if isinstance(statval, list): + statval = statval[:nb] + + return ORJSONResponse(statval) + + def _api_history(self, plugin, nb: int = 0): + """Glances API RESTful implementation. + + Return the JSON representation of a given plugin history + Limit to the last nb items (all if nb=0) + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + if plugin not in self.plugins_list: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list), + ) + + # Update the stat + self.__update__() + + try: + # Get the RAW value of the stat ID + statval = self.stats.get_plugin(plugin).get_raw_history(nb=int(nb)) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get plugin history %s (%s)" % (plugin, str(e)) + ) + + return statval + + def _api_limits(self, plugin): + """Glances API RESTful implementation. + + Return the JSON limits of a given plugin + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + if plugin not in self.plugins_list: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list), + ) + + try: + # Get the RAW value of the stat limits + ret = self.stats.get_plugin(plugin).limits + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get limits for plugin %s (%s)" % (plugin, str(e)) + ) + + return ORJSONResponse(ret) + + def _api_views(self, plugin): + """Glances API RESTful implementation. + + Return the JSON views of a given plugin + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + if plugin not in self.plugins_list: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list), + ) + + try: + # Get the RAW value of the stat views + ret = self.stats.get_plugin(plugin).get_views() + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get views for plugin %s (%s)" % (plugin, str(e)) + ) + + return ORJSONResponse(ret) + + def _api_item(self, plugin, item): + """Glances API RESTful implementation. + + Return the JSON representation of the couple plugin/item + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + if plugin not in self.plugins_list: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list), + ) + + # Update the stat + self.__update__() + + try: + # Get the RAW value of the stat views + ret = self.stats.get_plugin(plugin).get_raw_stats_item(item) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Cannot get item %s in plugin %s (%s)" % (item, plugin, str(e)), + ) + + return ORJSONResponse(ret) + + def _api_item_history(self, plugin, item, nb: int = 0): + """Glances API RESTful implementation. + + Return the JSON representation of the couple plugin/history of item + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + + """ + if plugin not in self.plugins_list: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list), + ) + + # Update the stat + self.__update__() + + try: + # Get the RAW value of the stat history + ret = self.stats.get_plugin(plugin).get_raw_history(item, nb=nb) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get history for plugin %s (%s)" % (plugin, str(e)) + ) + + return ORJSONResponse(ret) + + def _api_value(self, plugin, item, value): + """Glances API RESTful implementation. + + Return the process stats (dict) for the given item=value + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + if plugin not in self.plugins_list: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list), + ) + + # Update the stat + self.__update__() + + try: + # Get the RAW value + ret = self.stats.get_plugin(plugin).get_raw_stats_value(item, value) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Cannot get %s = %s for plugin %s (%s)" % (item, value, plugin, str(e)), + ) + + return ORJSONResponse(ret) + + def _api_config(self): + """Glances API RESTful implementation. + + Return the JSON representation of the Glances configuration file + HTTP/200 if OK + HTTP/404 if others error + """ + try: + # Get the RAW value of the config' dict + args_json = self.config.as_dict() + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get config (%s)" % str(e)) + + return ORJSONResponse(args_json) + + def _api_config_section(self, section): + """Glances API RESTful implementation. + + Return the JSON representation of the Glances configuration section + HTTP/200 if OK + HTTP/400 if item is not found + HTTP/404 if others error + """ + config_dict = self.config.as_dict() + if section not in config_dict: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Unknown configuration item %s" % section + ) + + try: + # Get the RAW value of the config' dict + ret_section = config_dict[section] + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get config section %s (%s)" % (section, str(e)) + ) + + return ORJSONResponse(ret_section) + + def _api_config_section_item(self, section, item): + """Glances API RESTful implementation. + + Return the JSON representation of the Glances configuration section/item + HTTP/200 if OK + HTTP/400 if item is not found + HTTP/404 if others error + """ + config_dict = self.config.as_dict() + if section not in config_dict: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Unknown configuration item %s" % section + ) + + try: + # Get the RAW value of the config' dict section + ret_section = config_dict[section] + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get config section %s (%s)" % (section, str(e)) + ) + + try: + # Get the RAW value of the config' dict item + ret_item = ret_section[item] + except Exception as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Cannot get item %s in config section %s (%s)" % (item, section, str(e)), + ) + + return ORJSONResponse(ret_item) + + def _api_args(self): + """Glances API RESTful implementation. + + Return the JSON representation of the Glances command line arguments + HTTP/200 if OK + HTTP/404 if others error + """ + try: + # Get the RAW value of the args' dict + # Use vars to convert namespace to dict + # Source: https://docs.python.org/%s/library/functions.html#vars + args_json = vars(self.args) + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get args (%s)" % str(e)) + + return ORJSONResponse(args_json) + + def _api_args_item(self, item): + """Glances API RESTful implementation. + + Return the JSON representation of the Glances command line arguments item + HTTP/200 if OK + HTTP/400 if item is not found + HTTP/404 if others error + """ + if item not in self.args: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Unknown argument item %s" % item) + + try: + # Get the RAW value of the args' dict + # Use vars to convert namespace to dict + # Source: https://docs.python.org/%s/library/functions.html#vars + args_json = vars(self.args)[item] + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get args item (%s)" % str(e)) + + return ORJSONResponse(args_json) diff --git a/glances/outputs/glances_stdout_apidoc.py b/glances/outputs/glances_stdout_apidoc.py index ed9c2da1..908b4b16 100644 --- a/glances/outputs/glances_stdout_apidoc.py +++ b/glances/outputs/glances_stdout_apidoc.py @@ -2,7 +2,7 @@ # # This file is part of Glances. # -# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# SPDX-FileCopyrightText: 2023 Nicolas Hennion # # SPDX-License-Identifier: LGPL-3.0-only # @@ -13,10 +13,12 @@ from pprint import pformat import json import time +from glances import __apiversion__ from glances.logger import logger from glances.globals import iteritems -API_URL = "http://localhost:61208/api/3" + +API_URL = "http://localhost:61208/api/{api_version}".format(api_version=__apiversion__) APIDOC_HEADER = """\ .. _api: @@ -33,12 +35,14 @@ The Glances Restfull/API server could be ran using the following command line: API URL ------- -The default root API URL is ``http://localhost:61208/api/3``. +The default root API URL is ``http://localhost:61208/api/{api_version}``. The bind address and port could be changed using the ``--bind`` and ``--port`` command line options. It is also possible to define an URL prefix using the ``url_prefix`` option from the [outputs] section -of the Glances configuration file. The url_prefix should always end with a slash (``/``). +of the Glances configuration file. + +Note: The url_prefix should always end with a slash (``/``). For example: @@ -46,10 +50,23 @@ For example: [outputs] url_prefix = /glances/ -will change the root API URL to ``http://localhost:61208/glances/api/3`` and the Web UI URL to +will change the root API URL to ``http://localhost:61208/glances/api/{api_version}`` and the Web UI URL to ``http://localhost:61208/glances/`` -""" +API documentation +----------------- + +The API documentation is 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`` + +""".format( + api_version=__apiversion__ +) def indent_stat(stat, indent=' '): @@ -67,7 +84,7 @@ def print_api_status(): print('-' * len(sub_title)) print('') print('This entry point should be used to check the API status.') - print('It will return nothing but a 200 return code if everything is OK.') + print('It will the Glances version and a 200 return code if everything is OK.') print('') print('Get the Rest API status::') print('') diff --git a/glances/outputs/static/README.md b/glances/outputs/static/README.md index bac2decb..5e8e7838 100644 --- a/glances/outputs/static/README.md +++ b/glances/outputs/static/README.md @@ -65,7 +65,7 @@ static | |--- public # path where builds are put | -|--- templates (bottle) +|--- templates ``` ## Data diff --git a/glances/outputs/static/css/style.scss b/glances/outputs/static/css/style.scss index 40c8c150..e7656b91 100644 --- a/glances/outputs/static/css/style.scss +++ b/glances/outputs/static/css/style.scss @@ -19,6 +19,15 @@ body { display: table-cell; text-align: right; } +.width-50 { + width: 50px; +} +.width-75 { + width: 75px; +} +.width-100 { + width: 100px; +} .plugin { margin-bottom: 20px; @@ -116,6 +125,7 @@ body { } /* Plugins */ + #processlist-plugin .table-cell { padding: 0px 5px 0px 5px; white-space: nowrap; diff --git a/glances/outputs/static/js/components/help.vue b/glances/outputs/static/js/components/help.vue index 468f1a1c..a08d7667 100644 --- a/glances/outputs/static/js/components/help.vue +++ b/glances/outputs/static/js/components/help.vue @@ -266,7 +266,7 @@ export default { }; }, mounted() { - fetch('api/3/help', { method: 'GET' }) + fetch('api/4/help', { method: 'GET' }) .then((response) => response.json()) .then((response) => (this.help = response)); } diff --git a/glances/outputs/static/js/components/plugin-alert.vue b/glances/outputs/static/js/components/plugin-alert.vue index 73db0502..4a9393af 100644 --- a/glances/outputs/static/js/components/plugin-alert.vue +++ b/glances/outputs/static/js/components/plugin-alert.vue @@ -11,6 +11,7 @@
{{ formatDate(alert.begin) }} + {{ alert.tz }} ({{ alert.ongoing ? 'ongoing' : alert.duration }}) - {{ alert.level }} on @@ -41,10 +42,11 @@ export default { alerts() { return (this.stats || []).map((alertalertStats) => { const alert = {}; + var tzoffset = new Date().getTimezoneOffset(); alert.name = alertalertStats[3]; alert.level = alertalertStats[2]; - alert.begin = alertalertStats[0] * 1000; - alert.end = alertalertStats[1] * 1000; + alert.begin = alertalertStats[0] * 1000 - tzoffset * 60 * 1000; + alert.end = alertalertStats[1] * 1000 - tzoffset * 60 * 1000; alert.ongoing = alertalertStats[1] == -1; alert.min = alertalertStats[6]; alert.mean = alertalertStats[5]; diff --git a/glances/outputs/static/js/components/plugin-fs.vue b/glances/outputs/static/js/components/plugin-fs.vue index f7dd5880..e01bf502 100644 --- a/glances/outputs/static/js/components/plugin-fs.vue +++ b/glances/outputs/static/js/components/plugin-fs.vue @@ -10,8 +10,8 @@
- {{ fs.shortMountPoint }} - + {{ $filters.minSize(fs.alias ? fs.alias : fs.mountPoint, 36, begin=false) }} + ({{ fs.name }})
@@ -55,18 +55,14 @@ export default { }, fileSystems() { const fileSystems = this.stats.map((fsData) => { - let shortMountPoint = fsData['mnt_point']; - if (shortMountPoint.length > 22) { - shortMountPoint = '_' + fsData['mnt_point'].slice(-21); - } return { name: fsData['device_name'], mountPoint: fsData['mnt_point'], - shortMountPoint: shortMountPoint, percent: fsData['percent'], size: fsData['size'], used: fsData['used'], - free: fsData['free'] + free: fsData['free'], + alias: fsData['alias'] !== undefined ? fsData['alias'] : null }; }); return orderBy(fileSystems, ['mnt_point']); diff --git a/glances/outputs/static/js/components/plugin-process.vue b/glances/outputs/static/js/components/plugin-process.vue index 36cf2500..05b053fb 100644 --- a/glances/outputs/static/js/components/plugin-process.vue +++ b/glances/outputs/static/js/components/plugin-process.vue @@ -64,13 +64,13 @@ export default { } function getColumnLabel(value) { const labels = { - io_counters: 'disk IO', cpu_percent: 'CPU consumption', memory_percent: 'memory consumption', - cpu_times: 'process time', username: 'user name', - name: 'process name', timemillis: 'process time', + cpu_times: 'process time', + io_counters: 'disk IO', + name: 'process name', None: 'None' }; return labels[value] || value; diff --git a/glances/outputs/static/js/components/plugin-processlist.vue b/glances/outputs/static/js/components/plugin-processlist.vue index 462fce61..ca9951bc 100644 --- a/glances/outputs/static/js/components/plugin-processlist.vue +++ b/glances/outputs/static/js/components/plugin-processlist.vue @@ -3,112 +3,88 @@
-
+
CPU%
-
+
MEM%
- - -
PID
-
+ + +
PID
+
USER
- - -
NI
-
S
- +
S
+ - -
+
Command
-
-
+
+
{{ process.cpu_percent == -1 ? '?' : $filters.number(process.cpu_percent, 1) }}
-
+
{{ process.memory_percent == -1 ? '?' : $filters.number(process.memory_percent, 1) }}
-