Merge from develop

This commit is contained in:
nicolargo
2023-03-12 09:44:43 +01:00
66 changed files with 4559 additions and 1333 deletions

17
.flake8
View File

@@ -1,20 +1,7 @@
[flake8]
exclude = .git,__pycache__,docs/,build,dist
ignore =
W504 # line break after binary operator
# --- flake8-bugbear plugin
B007 # Loop control variable 'keyword' not used within the loop body. If this is intended, start the name with an underscore.
B014 # Redundant exception types in `except (IOError, OSError) as err:`. Write `except OSError as err:`, which catches exactly the same exceptions.
B008 # Do not perform function calls in argument defaults.
# --- flake8-blind-except plugin
B902 # blind except Exception: statement
# --- flake8-quotes plugin
Q000 # Double quotes found but single quotes preferred
# --- flake8-quotes naming; disable all except N804 and N805
W504, B007, B014, B008, B902, Q000,
N801, N802, N803, N806, N807, N811, N812, N813, N814, N815, N816, N817, N818
# lines should not exceed 120 characters

View File

@@ -96,7 +96,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: ['alpine']
os: ['alpine', 'ubuntu']
tag: ${{ fromJson(needs.create_Docker_builds.outputs.tags) }}
steps:
- name: Checkout
@@ -104,7 +104,7 @@ jobs:
- name: Retrieve Repository Docker metadata
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v4.1.1
uses: crazy-max/ghaction-docker-meta@v4.3.0
with:
images: ${{ env.DEFAULT_DOCKER_IMAGE }}
labels: |
@@ -136,7 +136,7 @@ jobs:
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
push: ${{ env.PUSH_BRANCH == 'true' }}
tags: "${{ env.DEFAULT_DOCKER_IMAGE }}:${{ matrix.os != 'alpine' && format('{0}-', matrix.os) || '' }}${{ matrix.tag.tag }}"

View File

@@ -64,6 +64,9 @@ flake8: venv-dev-upgrade ## Run flake8 linter.
codespell: venv-dev-upgrade ## Run codespell to fix common misspellings in text files
./venv/bin/codespell -S .git,./docs/_build,./Glances.egg-info,./venv,./glances/outputs,*.svg -L hart,bu,te,statics
semgrep: venv-dev-upgrade ## Run semgrep to find bugs and enforce code standards
./venv/bin/semgrep --config=auto --lang python --use-git-ignore ./glances
profiling: ## How to start the profiling of the Glances software
@echo "Please complete and run: sudo ./venv/bin/py-spy record -o ./docs/_static/glances-flame.svg -d 60 -s --pid <GLANCES PID>"
@@ -137,6 +140,11 @@ docker-alpine: ## Generate local docker images (Alpine)
docker build --target minimal -f ./docker-files/alpine.Dockerfile -t glances:local-alpine-minimal .
docker build --target dev -f ./docker-files/alpine.Dockerfile -t glances:local-alpine-dev .
docker-ubuntu: ## Generate local docker images (Ubuntu)
docker build --target full -f ./docker-files/ubuntu.Dockerfile -t glances:local-ubuntu-full .
docker build --target minimal -f ./docker-files/ubuntu.Dockerfile -t glances:local-ubuntu-minimal .
docker build --target dev -f ./docker-files/ubuntu.Dockerfile -t glances:local-ubuntu-dev .
# ===================================================================
# Run
# ===================================================================
@@ -151,13 +159,22 @@ run-local-conf: ## Start Glances in console mode with the system conf file
./venv/bin/python -m glances
run-docker-alpine-minimal: ## Start Glances Alpine Docker minimal in console mode
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-minimal
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-minimal
run-docker-alpine-full: ## Start Glances Alpine Docker full in console mode
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-full
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-full
run-docker-alpine-dev: ## Start Glances Alpine Docker dev in console mode
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-dev
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-dev
run-docker-ubuntu-minimal: ## Start Glances Ubuntu Docker minimal in console mode
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-minimal
run-docker-ubuntu-full: ## Start Glances Ubuntu Docker full in console mode
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-full
run-docker-ubuntu-dev: ## Start Glances Ubuntu Docker dev in console mode
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-dev
run-webserver: ## Start Glances in Web server mode
./venv/bin/python -m glances -C ./conf/glances.conf -w

View File

@@ -2,11 +2,82 @@
Glances changelog
==============================================================================
===============
Version 3.4.0
===============
See roadmap here: https://github.com/nicolargo/glances/issues?q=is%3Aopen+is%3Aissue+milestone%3A%22Glances+3.4.0%22
===============
Version 3.3.1.1
===============
Hard patch on the master branch.
Bug corrected:
* "ModuleNotFoundError: No module named 'ujson'" #2246
* Remove surrounding quotes for quoted command arguments #2247 (related to #2239)
===============
Version 3.3.1
===============
Under development, see milestone https://github.com/nicolargo/glances/milestone/61
Enhancements:
* Minor change on the help screen
* Refactor some loop in the processes function
* Replace json by ujson #2201
Bug corrected:
* Unable to see docker related information #2180
* CSV export dependent on sort order for docker container cpu #2156
* Error when process list is displayed in Programs mode #2209
* Console formatting permanently messed up when other text printed #2211
* API GET uptime returns formatted string, not seconds as the doc says #2158
* Glances UI is breaking for multiline commands #2189
Documentation and CI:
* Add unitary test for memory profiling
* Update memory profile chart
* Add run-docker-ubuntu-* in Makefile
* The open-web-browser option was missing dashes #2219
* Correct regexp in glances.conf file example
* What is CW from network #2222 (related to discussion #2221)
* Change Glances repology URL
* Add example for the date format
* Correct Flake8 configuration file
* Drop UT for Python 3.5 and 3.6 (no more available in Ubuntu 22.04)
* Correct unitary test with Python 3.5
* Update Makefile with comments
* Update Python minimal requirement for py3nvlm
* Update security policy (user can open private issue directly in Github)
* Add a simple run script. Entry point for IDE debuger
Cyber security update:
* Security alert on ujson < 5.4
* Merge pull request #2243 from nicolargo/renovate/nvidia-cuda-12.x
* Merge pull request #2244 from nicolargo/renovate/crazy-max-ghaction-docker-meta-4.x
* Merge pull request #2228 from nicolargo/renovate/zeroconf-0.x
* Merge pull request #2242 from nicolargo/renovate/crazy-max-ghaction-docker-meta-4.x
* Merge pull request #2239 from mfridge/action-command-split
* Merge pull request #2165 from nicolargo/renovate/zeroconf-0.x
* Merge pull request #2199 from nicolargo/renovate/alpine-3.x
* Merge pull request #2202 from chncaption/oscs_fix_cdr0ts8au51t49so8c6g
* Bump loader-utils from 2.0.0 to 2.0.3 in /glances/outputs/static #2187 - Update Web lib
Contributors for this version:
* Nicolargo
* renovate[bot]
* chncaption
* fkwong
* *mfridge
And also a big thanks to @RazCrimson (https://github.com/RazCrimson) for the support to the Glances community !
===============
Version 3.3.0.4

View File

@@ -40,16 +40,22 @@ Glances - An eye on your system
Summary
=======
**Glances** is a cross-platform monitoring tool which aims to present a
large amount of monitoring information through a curses or Web
based interface. The information dynamically adapts depending on the
size of the user interface.
**Glances** is an open-source system cross-platform monitoring tool.
It allows real-time monitoring of various aspects of your system such as
CPU, memory, disk, network usage etc. It also allows monitoring of running processes,
logged in users, temperatures, voltages, fan speeds etc.
It also supports container monitoring, it supports different container management
systems such as Docker, LXC. The information is presented in an easy to read dashboard
and can also be used for remote monitoring of systems via a web interface or command
line interface. It is easy to install and use and can be customized to show only
the information that you are interested in.
.. image:: https://raw.githubusercontent.com/nicolargo/glances/develop/docs/_static/glances-summary.png
It can also work in client/server mode. Remote monitoring could be done
via terminal, Web interface or API (XML-RPC and RESTful). Stats can also
be exported to files or external time/value databases.
In client/server mode, remote monitoring could be done via terminal,
Web interface or API (XML-RPC and RESTful).
Stats can also be exported to files or external time/value databases, CSV or direct
output to STDOUT.
.. image:: https://raw.githubusercontent.com/nicolargo/glances/develop/docs/_static/glances-responsive-webdesign.png
@@ -105,6 +111,7 @@ Optional dependencies:
- ``py-cpuinfo`` (for the Quicklook CPU info module)
- ``pygal`` (for the graph export module)
- ``pymdstat`` (for RAID support) [Linux-only]
- ``pymongo`` (for the MongoDB export module) [Only for Python >= 3.7]
- ``pysnmp`` (for SNMP support)
- ``pySMART.smartx`` (for HDD Smart support) [Linux-only]
- ``pyzmq`` (for the ZeroMQ export module)
@@ -207,7 +214,7 @@ Run last version of Glances container in *console mode*:
.. code-block:: console
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it nicolargo/glances:latest-full
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it nicolargo/glances:latest-full
Additionally, if you want to use your own glances.conf file, you can
create your own Dockerfile:
@@ -224,7 +231,7 @@ variable setting parameters for the glances startup command):
.. code-block:: console
docker run -v `pwd`/glances.conf:/etc/glances.conf -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host -e GLANCES_OPT="-C /etc/glances.conf" -it nicolargo/glances:latest-full
docker run -e TZ="${TZ}" -v `pwd`/glances.conf:/etc/glances.conf -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host -e GLANCES_OPT="-C /etc/glances.conf" -it nicolargo/glances:latest-full
Where \`pwd\`/glances.conf is a local directory containing your glances.conf file.
@@ -232,7 +239,7 @@ Run the container in *Web server mode*:
.. code-block:: console
docker run -d --restart="always" -p 61208-61209:61208-61209 -e GLANCES_OPT="-w" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host nicolargo/glances:latest-full
docker run -d --restart="always" -p 61208-61209:61208-61209 -e TZ="${TZ}" -e GLANCES_OPT="-w" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host nicolargo/glances:latest-full
GNU/Linux
---------
@@ -464,7 +471,7 @@ Glances is distributed under the LGPL version 3 license. See ``COPYING`` for mor
.. _readthedocs: https://glances.readthedocs.io/
.. _forum: https://groups.google.com/forum/?hl=en#!forum/glances-users
.. _wiki: https://github.com/nicolargo/glances/wiki/How-to-contribute-to-Glances-%3F
.. _package: https://repology.org/metapackage/glances/packages
.. _package: https://repology.org/project/glances/versions
.. _sponsors: https://github.com/sponsors/nicolargo
.. _wishlist: https://www.amazon.fr/hz/wishlist/ls/BWAAQKWFR3FI?ref_=wl_share
.. _issue2021: https://github.com/nicolargo/glances/issues/2021#issuecomment-1197831157

View File

@@ -12,6 +12,8 @@ check_update=true
# History size (maximum number of values)
# Default is 1200 values (~1h with the default refresh rate)
history_size=1200
# Set the way Glances should display the date (default is %Y-%m-%d %H:%M:%S %Z)
#strftime_format="%Y-%m-%d %H:%M:%S %Z"
##############################################################################
# User interface
@@ -212,7 +214,7 @@ critical=-85
disable=False
# Define the list of hidden disks (comma-separated regexp)
#hide=sda2,sda5,loop.*
hide=loop.*,/dev/loop*
hide=loop.*,/dev/loop.*
# Define the list of disks to be show (comma-separated)
#show=sda.*
# Alias for sda1
@@ -235,12 +237,12 @@ critical=90
#allow=shm
[irq]
# Documentation: https://glances.readthedocs.io/en/stable/aoa/irq.html
# Documentation: https://glances.readthedocs.io/en/latest/aoa/irq.html
# This plugin is disabled by default
disable=True
[folders]
# Documentation: https://glances.readthedocs.io/en/stable/aoa/folders.html
# Documentation: https://glances.readthedocs.io/en/latest/aoa/folders.html
disable=False
# Define a folder list to monitor
# The list is composed of items (list_#nb <= 10)
@@ -261,13 +263,18 @@ disable=False
#folder_3_path=/nonexisting
#folder_4_path=/root
[cloud]
# Documentation: https://glances.readthedocs.io/en/latest/aoa/cloud.html
# This plugin is disabled by default
disable=True
[raid]
# Documentation: https://glances.readthedocs.io/en/stable/aoa/raid.html
# Documentation: https://glances.readthedocs.io/en/latest/aoa/raid.html
# This plugin is disabled by default
disable=True
[smart]
# Documentation: https://glances.readthedocs.io/en/stable/aoa/smart.html
# Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html
# This plugin is disabled by default
disable=True
@@ -305,6 +312,7 @@ battery_critical=95
#core 0_fans_speed_alias=CPU Core 0 fan
#or
#core 0_alias=CPU Core 0
#core 1_alias=CPU Core 1
[processcount]
disable=False
@@ -581,6 +589,15 @@ db=glances
#user=root
#password=root
[mongodb]
# Configuration for the --export mongodb option
# https://www.mongodb.com
host=localhost
port=27017
db=glances
user=root
password=example
[kafka]
# Configuration for the --export kafka option
# http://kafka.apache.org/

View File

@@ -8,4 +8,6 @@ autoflake
codespell
memory-profiler
matplotlib
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
semgrep
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability

View File

@@ -21,9 +21,11 @@ services:
privileged: true
network_mode: "host"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./glances.conf:/glances/conf/glances.conf"
environment:
- "GLANCES_OPT=-w"
- GLANCES_OPT: "-C /glances/conf/glances.conf -w"
- TZ: "${TZ}"
labels:
- "traefik.port=61208"
- "traefik.frontend.rule=Host:glances.docker.localhost"

View File

@@ -5,13 +5,12 @@ services:
context: ./
dockerfile: Dockerfile
restart: always
ports:
- "61208:61208"
environment:
GLANCES_OPT: "-C /glances/conf/glances.conf -w"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./glances.conf:/glances/conf/glances.conf"
pid: "host"
privileged: true
network_mode: "host"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./glances.conf:/glances/conf/glances.conf"
environment:
- GLANCES_OPT: "-C /glances/conf/glances.conf -w"
- TZ: "${TZ}"

View File

@@ -10,8 +10,10 @@ refresh=2
# Does Glances should check if a newer version is available on PyPI ?
check_update=false
# History size (maximum number of values)
# Default is 3600 seconds (1 hour)
history_size=3600
# Default is 1200 values (~1h with the default refresh rate)
history_size=1200
# Set the way Glances should display the date (default is %Y-%m-%d %H:%M:%S %Z)
#strftime_format="%Y-%m-%d %H:%M:%S %Z"
##############################################################################
# User interface
@@ -212,7 +214,7 @@ critical=-85
disable=False
# Define the list of hidden disks (comma-separated regexp)
#hide=sda2,sda5,loop.*
hide=loop.*,/dev/loop*
hide=loop.*,/dev/loop.*
# Define the list of disks to be show (comma-separated)
#show=sda.*
# Alias for sda1
@@ -235,12 +237,12 @@ critical=90
#allow=shm
[irq]
# Documentation: https://glances.readthedocs.io/en/stable/aoa/irq.html
# Documentation: https://glances.readthedocs.io/en/latest/aoa/irq.html
# This plugin is disabled by default
disable=True
[folders]
# Documentation: https://glances.readthedocs.io/en/stable/aoa/folders.html
# Documentation: https://glances.readthedocs.io/en/latest/aoa/folders.html
disable=False
# Define a folder list to monitor
# The list is composed of items (list_#nb <= 10)
@@ -261,13 +263,18 @@ disable=False
#folder_3_path=/nonexisting
#folder_4_path=/root
[cloud]
# Documentation: https://glances.readthedocs.io/en/latest/aoa/cloud.html
# This plugin is disabled by default
disable=True
[raid]
# Documentation: https://glances.readthedocs.io/en/stable/aoa/raid.html
# Documentation: https://glances.readthedocs.io/en/latest/aoa/raid.html
# This plugin is disabled by default
disable=True
[smart]
# Documentation: https://glances.readthedocs.io/en/stable/aoa/smart.html
# Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html
# This plugin is disabled by default
disable=True
@@ -582,6 +589,15 @@ db=glances
#user=root
#password=root
[mongodb]
# Configuration for the --export mongodb option
# https://www.mongodb.com
host=localhost
port=27017
db=glances
user=root
password=example
[kafka]
# Configuration for the --export kafka option
# http://kafka.apache.org/

View File

@@ -26,7 +26,9 @@ RUN apk add --no-cache \
curl \
lm-sensors \
wireless-tools \
iputils
smartmontools \
iputils \
tzdata
##############################################################################
# Install the dependencies beforehand to make them cacheable
@@ -89,10 +91,13 @@ RUN apk add --no-cache \
python3 \
py3-packaging \
py3-dateutil \
py3-requests \
curl \
lm-sensors \
wireless-tools \
iputils
smartmontools \
iputils \
tzdata
COPY --from=buildRequirements /root/.local/bin /usr/local/bin/
COPY --from=buildRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /usr/lib/python${PYTHON_VERSION}/site-packages/

View File

@@ -0,0 +1,148 @@
#
# Glances Dockerfile (based on Ubuntu)
#
# https://github.com/nicolargo/glances
#
# WARNING: the versions should be set.
# Ex: Python 3.10 for Ubuntu 22.04
# Note: ENV is for future running containers. ARG for building your Docker image.
ARG IMAGE_VERSION=12.1.0-base-ubuntu22.04
ARG PYTHON_VERSION=3.10
ARG PIP_MIRROR=https://mirrors.aliyun.com/pypi/simple/
FROM nvidia/cuda:${IMAGE_VERSION} as build
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3 \
python3-dev \
python3-pip \
python3-wheel \
musl-dev \
build-essential \
libzmq5 \
curl \
lm-sensors \
wireless-tools \
smartmontools \
net-tools \
tzdata \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
##############################################################################
# Install the dependencies beforehand to make them cacheable
FROM build as buildRequirements
ARG PYTHON_VERSION
ARG PIP_MIRROR
ARG PIP_MIRROR=https://mirrors.aliyun.com/pypi/simple/
COPY requirements.txt .
RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --user -r requirements.txt -i ${PIP_MIRROR}
# Minimal means no webui, but it break what is done previously (see #2155)
# So install the webui requirements...
COPY webui-requirements.txt .
RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --user -r webui-requirements.txt -i ${PIP_MIRROR}
# As minimal image we want to monitor others docker containers
RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --user docker -i ${PIP_MIRROR}
# Force install otherwise it could be cached without rerun
ARG CHANGING_ARG
RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --user glances -i ${PIP_MIRROR}
##############################################################################
FROM build as buildOptionalRequirements
ARG PYTHON_VERSION
ARG PIP_MIRROR
COPY requirements.txt .
COPY optional-requirements.txt .
RUN CASS_DRIVER_NO_CYTHON=1 pip3 install --no-cache-dir --user -r optional-requirements.txt -i ${PIP_MIRROR}
##############################################################################
# full image
##############################################################################
FROM build as full
ARG PYTHON_VERSION
COPY --from=buildRequirements /root/.local/bin /root/.local/bin/
COPY --from=buildRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
COPY --from=buildOptionalRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
COPY ./docker-compose/glances.conf /etc/glances.conf
# EXPOSE PORT (XMLRPC / WebUI)
EXPOSE 61209 61208
# Define default command.
WORKDIR /glances
CMD python3 -m glances -C /etc/glances.conf $GLANCES_OPT
##############################################################################
# minimal image
##############################################################################
# Create running images without any building dependency
FROM nvidia/cuda:${IMAGE_VERSION} as minimal
ARG PYTHON_VERSION
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Shanghai
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3 \
python3-packaging \
python3-dateutil \
python3-requests \
curl \
lm-sensors \
wireless-tools \
smartmontools \
net-tools \
tzdata \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY --from=buildRequirements /root/.local/bin /root/.local/bin/
COPY --from=buildRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
COPY ./docker-compose/glances.conf /etc/glances.conf
# EXPOSE PORT (XMLRPC / WebUI)
EXPOSE 61209 61208
# Define default command.
WORKDIR /glances
CMD python3 -m glances -C /etc/glances.conf $GLANCES_OPT
##############################################################################
# dev image
##############################################################################
FROM full as dev
ARG PYTHON_VERSION
COPY --from=buildRequirements /root/.local/bin /root/.local/bin/
COPY --from=buildRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
COPY --from=buildOptionalRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
COPY ./docker-compose/glances.conf /etc/glances.conf
# Copy the current Glances source code
COPY . /glances
# EXPOSE PORT (XMLRPC / WebUI)
EXPOSE 61209 61208
# Forward access and error logs to Docker's log collector
RUN ln -sf /dev/stdout /tmp/glances-root.log \
&& ln -sf /dev/stderr /var/log/error.log
# Define default command.
WORKDIR /glances
CMD python3 -m glances -C /etc/glances.conf $GLANCES_OPT

BIN
docs/_static/cloud.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/_static/processlist-extended.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

15
docs/aoa/cloud.rst Normal file
View File

@@ -0,0 +1,15 @@
.. _cloud:
CLOUD
=====
This plugin diplays information about the cloud provider if your host is running on OpenStack.
The plugin use the standard OpenStack `metadata`_ service to retrieve the information.
This plugin is disable by default, please use the --enable-plugin cloud option
to enable it.
.. image:: ../_static/cloud.png
.. _metadata: https://docs.openstack.org/nova/latest/user/metadata.html

View File

@@ -37,3 +37,9 @@ or another example:
[diskio]
show=sda.*
Filtering is based on regular expression. Please be sure that your regular
expression works as expected. You can use an online tool like `regex101`_ in
order to test your regular expression.
.. _regex101: https://regex101.com/

View File

@@ -47,4 +47,9 @@ under the ``[docker]`` section:
You can use all the variables ({{foo}}) available in the Docker plugin.
Filtering (for hide or show) is based on regular expression. Please be sure that your regular
expression works as expected. You can use an online tool like `regex101`_ in
order to test your regular expression.
.. _regex101: https://regex101.com/
.. _docker-py: https://github.com/docker/docker-py

View File

@@ -53,3 +53,9 @@ Example to only show /dev/sdb mount points:
[fs]
show=/dev/sdb.*
Filtering is based on regular expression. Please be sure that your regular
expression works as expected. You can use an online tool like `regex101`_ in
order to test your regular expression.
.. _regex101: https://regex101.com/

View File

@@ -22,7 +22,9 @@ file under the ``[ip]`` section:
**NOTE:** Setting low values for `public_refresh_interval` will result in frequent
HTTP requests to the IP detection servers. Recommended range: 120-600 seconds
HTTP requests to the IP detection servers. Recommended range: 120-600 seconds.
Glances uses online services in order to get the IP addresses. Your IP address could be
blocked if too many requests are done.
If the Censys options are configured, the public IP address is also analysed (with the same interval)
and additional information is displayed.

View File

@@ -34,6 +34,7 @@ Legend:
fs
irq
folders
cloud
raid
smart
sensors

View File

@@ -53,3 +53,9 @@ virtual docker interface (docker0, docker1, ...):
wlan0_tx_warning=900000
wlan0_tx_critical=1000000
wlan0_tx_log=True
Filtering is based on regular expression. Please be sure that your regular
expression works as expected. You can use an online tool like `regex101`_ in
order to test your regular expression.
.. _regex101: https://regex101.com/

View File

@@ -15,10 +15,15 @@ Filtered view:
.. image:: ../_static/processlist-filter.png
Extended view:
.. image:: ../_static/processlist-extended.png
The process view consists of 3 parts:
- Processes summary
- Monitored processes list (optional)
- Monitored processes list (optional, only in standalone mode)
- Extended stats for the selected process (optional)
- Processes list
The processes summary line displays:
@@ -56,12 +61,15 @@ You can also set the sort key in the UI:
* - c
- --sort-processes cpu_percent
- Sort by CPU
* - e
- N/A
- Pin the process and display extended stats
* - i
- --sort-processes io_counters
- Sort by DISK I/O
* - j
- --programs
- Accumulate processes by program
- Accumulate processes by program (extended stats disable in this mode)
* - m
- --sort-processes memory_percent
- Sort by MEM

View File

@@ -31,6 +31,12 @@ There is no alert on this information.
unitname_fan_speed_alias=Alias for fan speed
.. note 4::
If a sensors has multiple identical features names (see #2280), then
Glances will add a suffix to the feature name.
For example, if you have one sensor with two Composite features, the
second one will be named Composite_1.
.. note 5::
The plugin could crash on some operating system (FreeBSD) with the
TCP or UDP blackhole option > 0 (see issue #2106). In this case, you
should disable the sensors (--disable-plugin sensors or from the

View File

File diff suppressed because it is too large Load Diff

View File

@@ -174,7 +174,7 @@ Command-Line Options
set the server cache time [default: 1 sec]
.. option:: open-web-browser
.. option:: --open-web-browser
try to open the Web UI in the default Web browser

View File

@@ -9,42 +9,35 @@ following:
.. code-block:: ini
[couchdb]
[mongodb]
host=localhost
port=5984
user=root
password=root
port=27017
db=glances
user=root
password=example
and run Glances with:
.. code-block:: console
$ glances --export couchdb
$ glances --export mongodb
Documents are stored in native ``JSON`` format. Glances adds ``"type"``
and ``"time"`` entries:
Documents are stored in native the configured database (glances by default)
with one collection per plugin.
- ``type``: plugin name
- ``time``: timestamp (format: "2016-09-24T16:39:08.524828Z")
Example of Couch Document for the load stats:
Example of MongoDB Document for the load stats:
.. code-block:: json
{
"_id": "36cbbad81453c53ef08804cb2612d5b6",
"_rev": "1-382400899bec5615cabb99aa34df49fb",
"min15": 0.33,
"time": "2016-09-24T16:39:08.524828Z",
"min5": 0.4,
"cpucore": 4,
"load_warning": 1,
"min1": 0.5,
"history_size": 28800,
"load_critical": 5,
"type": "load",
"load_careful": 0.7
_id: ObjectId('63d78ffee5528e543ce5af3a'),
min1: 1.46337890625,
min5: 1.09619140625,
min15: 1.07275390625,
cpucore: 4,
history_size: 1200,
load_disable: 'False',
load_careful: 0.7,
load_warning: 1,
load_critical: 5
}
You can view the result using the CouchDB utils URL: http://127.0.0.1:5984/_utils/database.html?glances.

View File

@@ -18,6 +18,7 @@ to providing stats to multiple services (see list below).
json
kafka
mqtt
mongodb
opentsdb
prometheus
rabbitmq

50
docs/gw/mongodb.rst Normal file
View File

@@ -0,0 +1,50 @@
.. _couchdb:
MongoDB
=======
You can export statistics to a ``MongoDB`` server.
The connection should be defined in the Glances configuration file as
following:
.. code-block:: ini
[couchdb]
host=localhost
port=
user=root
password=example
db=glances
and run Glances with:
.. code-block:: console
$ glances --export couchdb
Documents are stored in native ``JSON`` format. Glances adds ``"type"``
and ``"time"`` entries:
- ``type``: plugin name
- ``time``: timestamp (format: "2016-09-24T16:39:08.524828Z")
Example of Couch Document for the load stats:
.. code-block:: json
{
"_id": "36cbbad81453c53ef08804cb2612d5b6",
"_rev": "1-382400899bec5615cabb99aa34df49fb",
"min15": 0.33,
"time": "2016-09-24T16:39:08.524828Z",
"min5": 0.4,
"cpucore": 4,
"load_warning": 1,
"min1": 0.5,
"history_size": 28800,
"load_critical": 5,
"type": "load",
"load_careful": 0.7
}
You can view the result using the CouchDB utils URL: http://127.0.0.1:5984/_utils/database.html?glances.

View File

@@ -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" "Nov 03, 2022" "3.3.1_beta1" "Glances"
.TH "GLANCES" "1" "Mar 11, 2023" "3.4.0_beta1" "Glances"
.SH NAME
glances \- An eye on your system
.SH SYNOPSIS
@@ -258,7 +258,7 @@ set the server cache time [default: 1 sec]
.UNINDENT
.INDENT 0.0
.TP
.B open\-web\-browser
.B \-\-open\-web\-browser
try to open the Web UI in the default Web browser
.UNINDENT
.INDENT 0.0
@@ -732,60 +732,60 @@ format):
.nf
.ft C
{
"version": 1,
"disable_existing_loggers": "False",
"root": {
"level": "INFO",
"handlers": ["file", "console"]
\(dqversion\(dq: 1,
\(dqdisable_existing_loggers\(dq: \(dqFalse\(dq,
\(dqroot\(dq: {
\(dqlevel\(dq: \(dqINFO\(dq,
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq]
},
"formatters": {
"standard": {
"format": "%(asctime)s \-\- %(levelname)s \-\- %(message)s"
\(dqformatters\(dq: {
\(dqstandard\(dq: {
\(dqformat\(dq: \(dq%(asctime)s \-\- %(levelname)s \-\- %(message)s\(dq
},
"short": {
"format": "%(levelname)s: %(message)s"
\(dqshort\(dq: {
\(dqformat\(dq: \(dq%(levelname)s: %(message)s\(dq
},
"free": {
"format": "%(message)s"
\(dqfree\(dq: {
\(dqformat\(dq: \(dq%(message)s\(dq
}
},
"handlers": {
"file": {
"level": "DEBUG",
"class": "logging.handlers.RotatingFileHandler",
"formatter": "standard",
"filename": "/var/tmp/glances.log"
\(dqhandlers\(dq: {
\(dqfile\(dq: {
\(dqlevel\(dq: \(dqDEBUG\(dq,
\(dqclass\(dq: \(dqlogging.handlers.RotatingFileHandler\(dq,
\(dqformatter\(dq: \(dqstandard\(dq,
\(dqfilename\(dq: \(dq/var/tmp/glances.log\(dq
},
"console": {
"level": "CRITICAL",
"class": "logging.StreamHandler",
"formatter": "free"
\(dqconsole\(dq: {
\(dqlevel\(dq: \(dqCRITICAL\(dq,
\(dqclass\(dq: \(dqlogging.StreamHandler\(dq,
\(dqformatter\(dq: \(dqfree\(dq
}
},
"loggers": {
"debug": {
"handlers": ["file", "console"],
"level": "DEBUG"
\(dqloggers\(dq: {
\(dqdebug\(dq: {
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
\(dqlevel\(dq: \(dqDEBUG\(dq
},
"verbose": {
"handlers": ["file", "console"],
"level": "INFO"
\(dqverbose\(dq: {
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
\(dqlevel\(dq: \(dqINFO\(dq
},
"standard": {
"handlers": ["file"],
"level": "INFO"
\(dqstandard\(dq: {
\(dqhandlers\(dq: [\(dqfile\(dq],
\(dqlevel\(dq: \(dqINFO\(dq
},
"requests": {
"handlers": ["file", "console"],
"level": "ERROR"
\(dqrequests\(dq: {
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
\(dqlevel\(dq: \(dqERROR\(dq
},
"elasticsearch": {
"handlers": ["file", "console"],
"level": "ERROR"
\(dqelasticsearch\(dq: {
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
\(dqlevel\(dq: \(dqERROR\(dq
},
"elasticsearch.trace": {
"handlers": ["file", "console"],
"level": "ERROR"
\(dqelasticsearch.trace\(dq: {
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
\(dqlevel\(dq: \(dqERROR\(dq
}
}
}
@@ -885,6 +885,6 @@ $ glances browser
.sp
Nicolas Hennion aka Nicolargo <\fI\%contact@nicolargo.com\fP>
.SH COPYRIGHT
2022, Nicolas Hennion
2023, Nicolas Hennion
.\" Generated by docutils manpage writer.
.

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Glances - An eye on your system

View File

@@ -172,11 +172,7 @@ class Config(object):
if not self.parser.has_section('global'):
self.parser.add_section('global')
self.set_default('global', 'strftime_format', '')
# check_update
if not self.parser.has_section('global'):
self.parser.add_section('global')
self.set_default('global', 'check_update', 'false')
self.set_default('global', 'check_update', 'true')
# Quicklook
if not self.parser.has_section('quicklook'):

View File

@@ -112,7 +112,6 @@ class Export(GlancesExport):
# Write input to the Cassandra table
try:
stmt = "INSERT INTO {} (plugin, time, stat) VALUES (?, ?, ?)".format(self.table)
query = self.session.prepare(stmt)
self.session.execute(query, (name, uuid_from_time(datetime.now()), data))

View File

@@ -81,10 +81,10 @@ class Export(GlancesExport):
# Loop over plugins to export
for plugin in self.plugins_to_export(stats):
if isinstance(all_stats[plugin], list):
for stat in all_stats[plugin]:
for stat in sorted(all_stats[plugin], key=lambda x: x['key']):
# First line: header
if self.first_line:
csv_header += ('{}_{}_{}'.format(plugin, self.get_item_key(stat), item) for item in stat)
csv_header += ['{}_{}_{}'.format(plugin, self.get_item_key(stat), item) for item in stat]
# Others lines: stats
csv_data += itervalues(stat)
elif isinstance(all_stats[plugin], dict):

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# SPDX-FileCopyrightText: 2023 Nicolas Hennion <nicolas@nicolargo.com>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
"""MongoDB interface class."""
import sys
from datetime import datetime
from glances.logger import logger
from glances.exports.glances_export import GlancesExport
import pymongo
from urllib.parse import quote_plus
class Export(GlancesExport):
"""This class manages the MongoDB export module."""
def __init__(self, config=None, args=None):
"""Init the MongoDB export IF."""
super(Export, self).__init__(config=config, args=args)
# Mandatory configuration keys (additional to host and port)
self.db = None
# Optional configuration keys
self.user = None
self.password = None
# Load the Cassandra configuration file section
self.export_enable = self.load_conf('mongodb', mandatories=['host', 'port', 'db'], options=['user', 'password'])
if not self.export_enable:
sys.exit(2)
# Init the CouchDB client
self.client = self.init()
def init(self):
"""Init the connection to the CouchDB server."""
if not self.export_enable:
return None
server_uri = 'mongodb://%s:%s@%s:%s' % (quote_plus(self.user), quote_plus(self.password), self.host, self.port)
try:
client = pymongo.MongoClient(server_uri)
client.admin.command('ping')
except Exception as e:
logger.critical("Cannot connect to MongoDB server %s:%s (%s)" % (self.host, self.port, e))
sys.exit(2)
else:
logger.info("Connected to the MongoDB server")
return client
def database(self):
"""Return the CouchDB database object"""
return self.client[self.db]
def export(self, name, columns, points):
"""Write the points to the MongoDB server."""
logger.debug("Export {} stats to MongoDB".format(name))
# Create DB input
data = dict(zip(columns, points))
# Write data to the MongoDB database
try:
self.database()[name].insert_one(data)
except Exception as e:
logger.error("Cannot export {} stats to MongoDB ({})".format(name, e))

View File

@@ -235,6 +235,13 @@ Examples of use:
dest='enable_separator',
help='enable separator in the UI',
),
parser.add_argument(
'--disable-cursor',
action='store_true',
default=False,
dest='disable_cursor',
help='disable cursor (process selection) in the UI',
),
# Sort processes list
parser.add_argument(
'--sort-processes',
@@ -699,6 +706,11 @@ Examples of use:
logger.critical("Process filter is only available in standalone mode")
sys.exit(2)
# Cursor option is only available in standalone mode
if not args.disable_cursor and not self.is_standalone():
logger.critical("Cursor is only available in standalone mode")
sys.exit(2)
# Disable HDDTemp if sensors are disabled
if getattr(self.args, 'disable_sensors', False):
disable(self.args, 'hddtemp')

View File

@@ -48,10 +48,15 @@ class Outdated(object):
# Set default value...
self.data = {u'installed_version': __version__, u'latest_version': '0.0', u'refresh_date': datetime.now()}
# Read the configuration file
self.load_config(config)
# Disable update check if `packaging` is not installed
if not PACKAGING_IMPORT:
self.args.disable_check_update = True
# Read the configuration file only if update check is not explicitly disabled
if not self.args.disable_check_update:
self.load_config(config)
logger.debug("Check Glances version up-to-date: {}".format(not self.args.disable_check_update))
# And update !

View File

@@ -126,10 +126,8 @@ class GlancesBottle(object):
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.sha256_hash(password))
pwd = GlancesPassword(username=username, config=self.config)
return pwd.check_password(self.args.password, pwd.get_hash(password))
else:
return False
@@ -161,6 +159,9 @@ class GlancesBottle(object):
'/api/%s/<plugin>/<item>/history/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_item_history
)
self._app.route('/api/%s/<plugin>/<item>/<value>' % self.API_VERSION, method="GET", callback=self._api_value)
self._app.route(
'/api/%s/<plugin>/<item>/<value:path>' % 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)
@@ -228,7 +229,7 @@ class GlancesBottle(object):
"""
response.status = 200
return None
return "Active"
@compress
def _api_help(self):

View File

@@ -267,6 +267,7 @@ class _GlancesCurses(object):
self.ifCAREFUL_color2 = curses.color_pair(8) | A_BOLD
self.ifWARNING_color2 = curses.color_pair(5) | A_BOLD
self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD
self.ifINFO_color = curses.color_pair(8)
self.filter_color = A_BOLD
self.selected_color = A_BOLD
@@ -300,6 +301,7 @@ class _GlancesCurses(object):
self.ifCAREFUL_color2 = curses.A_UNDERLINE
self.ifWARNING_color2 = A_BOLD
self.ifCRITICAL_color2 = curses.A_REVERSE
self.ifINFO_color = A_BOLD
self.filter_color = A_BOLD
self.selected_color = A_BOLD
@@ -327,6 +329,7 @@ class _GlancesCurses(object):
'CRITICAL_LOG': self.ifCRITICAL_color,
'PASSWORD': curses.A_PROTECT,
'SELECTED': self.selected_color,
'INFO': self.ifINFO_color
}
def set_cursor(self, value):
@@ -406,13 +409,15 @@ class _GlancesCurses(object):
elif self.pressedkey == ord('9'):
# '9' > Theme from black to white and reverse
self._init_colors()
elif self.pressedkey == ord('e'):
elif self.pressedkey == ord('e') and not self.args.programs:
# 'e' > Enable/Disable process extended
self.args.enable_process_extended = not self.args.enable_process_extended
if not self.args.enable_process_extended:
glances_processes.disable_extended()
else:
glances_processes.enable_extended()
# When a process is selected (and only in standalone mode), disable the cursor
self.args.disable_cursor = self.args.enable_process_extended and self.args.is_standalone
elif self.pressedkey == ord('E'):
# 'E' > Erase the process filter
glances_processes.process_filter = None
@@ -426,7 +431,7 @@ class _GlancesCurses(object):
elif self.pressedkey == ord('-'):
# '+' > Decrease process nice level
self.decrease_nice_process = not self.decrease_nice_process
elif self.pressedkey == ord('k'):
elif self.pressedkey == ord('k') and not self.args.disable_cursor:
# 'k' > Kill selected process (after confirmation)
self.kill_process = not self.kill_process
elif self.pressedkey == ord('w'):
@@ -450,11 +455,11 @@ class _GlancesCurses(object):
# ">" (right arrow) navigation through process sort
next_sort = (self.loop_position() + 1) % len(self._sort_loop)
glances_processes.set_sort_key(self._sort_loop[next_sort], False)
elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65:
elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65 and not self.args.disable_cursor:
# 'UP' > Up in the server list
if self.args.cursor_position > 0:
self.args.cursor_position -= 1
elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66:
elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66 and not self.args.disable_cursor:
# 'DOWN' > Down in the server list
# if self.args.cursor_position < glances_processes.max_processes - 2:
if self.args.cursor_position < glances_processes.processes_count:
@@ -679,7 +684,6 @@ class _GlancesCurses(object):
new_filter = self.display_popup(
'Process filter pattern: \n\n'
+ 'Examples:\n'
+ '- python\n'
+ '- .*python.*\n'
+ '- /usr/lib.*\n'
+ '- name:.*nautilus.*\n'
@@ -1109,7 +1113,7 @@ class _GlancesCurses(object):
def erase(self):
"""Erase the content of the screen."""
self.term_window.erase()
self.term_window.clear()
def flush(self, stats, cs_status=None):
"""Clear and update the screen.

View File

@@ -77,7 +77,7 @@ class Sparkline(object):
def get(self):
"""Return the sparkline."""
ret = sparklines(self.percents)[0]
ret = sparklines(self.percents, minimum=0, maximum=100)[0]
if self.__with_text:
percents_without_none = [x for x in self.percents if x is not None]
if len(percents_without_none) > 0:

View File

@@ -70,7 +70,7 @@ def print_plugins_list(stat):
print('')
def print_plugin_export(plugin, stat_export):
def print_plugin_stats(plugin, stat):
sub_title = 'GET {}'.format(plugin)
print(sub_title)
print('-' * len(sub_title))
@@ -79,7 +79,7 @@ def print_plugin_export(plugin, stat_export):
print('Get plugin stats::')
print('')
print(' # curl {}/{}'.format(API_URL, plugin))
print(indent_stat(stat_export))
print(indent_stat(json.loads(stat.get_stats())))
print('')
@@ -223,7 +223,7 @@ class GlancesStdoutApiDoc(object):
stat_export = stat.get_export()
if stat_export is None or stat_export == [] or stat_export == {}:
continue
print_plugin_export(plugin, stat_export)
print_plugin_stats(plugin, stat)
print_plugin_description(plugin, stat)
print_plugin_item_value(plugin, stat, stat_export)

View File

@@ -123,6 +123,7 @@
v-if="!args.disable_sensors"
:data="data"
></glances-plugin-sensors>
<glances-plugin-now :data="data"></glances-plugin-now>
</div>
</div>
<div class="col-sm-18">
@@ -138,13 +139,6 @@
</div>
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-sm-24">
<glances-plugin-now :data="data"></glances-plugin-now>
</div>
</div>
</div>
</main>
</template>

View File

@@ -16,12 +16,12 @@ export default {
return this.data.stats['cloud'];
},
provider() {
return this.stats['ami-id'] !== undefined ? 'AWS EC2' : null;
return this.stats['id'] !== undefined ? `${stats['platform']}` : null;
},
instance() {
const { stats } = this;
return this.stats['ami-id'] !== undefined
? `${stats['instance-type']} instance ${stats['instance-id']} (${stats['reggion']})`
return this.stats['id'] !== undefined
? `${stats['type']} instance ${stats['name']} (${stats['region']})`
: null;
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -3,27 +3,27 @@
"dependencies": {
"bootstrap": "^3.4.1",
"favico.js": "^0.3.10",
"hotkeys-js": "^3.10.0",
"hotkeys-js": "^3.10.1",
"lodash": "^4.17.21",
"sanitize-html": "^2.7.2",
"vue": "^3.2.41"
"sanitize-html": "^2.8.1",
"vue": "^3.2.45"
},
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"css-loader": "^6.7.3",
"del": "^7.0.0",
"eslint": "^8.25.0",
"eslint-plugin-vue": "^9.6.0",
"eslint": "^8.32.0",
"eslint-plugin-vue": "^9.9.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"sass": "^1.55.0",
"sass-loader": "^13.1.0",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"url-loader": "^4.1.1",
"vue-loader": "^17.0.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"vue-loader": "^17.0.1",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
},
"scripts": {

View File

Binary file not shown.

View File

@@ -44,17 +44,12 @@ class GlancesPassword(object):
def get_hash(self, plain_password, salt=''):
"""Return the hashed password, salt + pbkdf2_hmac."""
return hashlib.pbkdf2_hmac('sha256',
plain_password.encode(),
salt.encode(),
100000,
dklen=128).hex()
return to_hex(hashlib.pbkdf2_hmac('sha256', plain_password.encode(), salt.encode(), 100000, dklen=128))
def hash_password(self, plain_password):
"""Hash password with a salt based on UUID (universally unique identifier)."""
salt = uuid.uuid4().hex
encrypted_password = self.get_hash(plain_password,
salt=salt)
encrypted_password = self.get_hash(plain_password, salt=salt)
return salt + '$' + encrypted_password
def check_password(self, hashed_password, plain_password):
@@ -63,8 +58,7 @@ class GlancesPassword(object):
Return the comparison with the encrypted_password.
"""
salt, encrypted_password = hashed_password.split('$')
re_encrypted_password = self.get_hash(plain_password,
salt = salt)
re_encrypted_password = self.get_hash(plain_password, salt=salt)
return encrypted_password == re_encrypted_password
def get_password(self, description='', confirm=False, clear=False):

View File

@@ -10,7 +10,8 @@
"""Cloud plugin.
Supported Cloud API:
- OpenStack meta data (class ThreadOpenStack, see below): AWS, OVH...
- OpenStack meta data (class ThreadOpenStack) - Vanilla OpenStack
- OpenStackEC2 meta data (class ThreadOpenStackEC2) - Amazon EC2 compatible
"""
import threading
@@ -53,13 +54,16 @@ class PluginModel(GlancesPluginModel):
# Init thread to grab OpenStack stats asynchronously
self.OPENSTACK = ThreadOpenStack()
self.OPENSTACKEC2 = ThreadOpenStackEC2()
# Run the thread
self.OPENSTACK.start()
self.OPENSTACKEC2.start()
def exit(self):
"""Overwrite the exit method to close threads."""
self.OPENSTACK.stop()
self.OPENSTACKEC2.stop()
# Call the father class
super(PluginModel, self).exit()
@@ -80,12 +84,15 @@ class PluginModel(GlancesPluginModel):
# Update the stats
if self.input_method == 'local':
stats = self.OPENSTACK.stats
if not stats:
stats = self.OPENSTACKEC2.stats
# Example:
# Uncomment to test on physical computer
# stats = {'ami-id': 'ami-id',
# 'instance-id': 'instance-id',
# 'instance-type': 'instance-type',
# 'region': 'placement/availability-zone'}
# Uncomment to test on physical computer (only for test purpose)
# stats = {'id': 'ami-id',
# 'name': 'My VM',
# 'type': 'Gold',
# 'region': 'France',
# 'platform': 'OpenStack'}
# Update the stats
self.stats = stats
@@ -101,13 +108,14 @@ class PluginModel(GlancesPluginModel):
return ret
# Generate the output
if 'instance-type' in self.stats and 'instance-id' in self.stats and 'region' in self.stats:
msg = 'Cloud '
ret.append(self.curse_add_line(msg, "TITLE"))
msg = '{} instance {} ({})'.format(
self.stats['instance-type'], self.stats['instance-id'], self.stats['region']
)
ret.append(self.curse_add_line(msg))
msg = self.stats.get('platform', 'Unknown')
ret.append(self.curse_add_line(msg, "TITLE"))
msg = ' {} instance {} ({})'.format(
self.stats.get('type', 'Unknown'),
self.stats.get('name', 'Unknown'),
self.stats.get('region', 'Unknown')
)
ret.append(self.curse_add_line(msg))
# Return the message with decoration
# logger.info(ret)
@@ -121,13 +129,19 @@ class ThreadOpenStack(threading.Thread):
stats is a dict
"""
# The metadata service provides a way for instances to retrieve
# instance-specific data via a REST API. Instances access this
# service at 169.254.169.254 or at fe80::a9fe:a9fe.
# All types of metadata, be it user-, nova- or vendor-provided,
# can be accessed via this service.
# https://docs.openstack.org/nova/latest/user/metadata-service.html
OPENSTACK_API_URL = 'http://169.254.169.254/latest/meta-data'
OPENSTACK_PLATFORM = "OpenStack"
OPENSTACK_API_URL = 'http://169.254.169.254/openstack/latest/meta-data'
OPENSTACK_API_METADATA = {
'ami-id': 'ami-id',
'instance-id': 'instance-id',
'instance-type': 'instance-type',
'region': 'placement/availability-zone',
'id': 'project_id',
'name': 'name',
'type': 'meta/role',
'region': 'availability_zone',
}
def __init__(self):
@@ -159,6 +173,9 @@ class ThreadOpenStack(threading.Thread):
else:
if r.ok:
self._stats[k] = to_ascii(r.content)
else:
# No break during the loop, so we can set the platform
self._stats['platform'] = self.OPENSTACK_PLATFORM
return True
@@ -180,3 +197,26 @@ class ThreadOpenStack(threading.Thread):
def stopped(self):
"""Return True is the thread is stopped."""
return self._stopper.is_set()
class ThreadOpenStackEC2(ThreadOpenStack):
"""
Specific thread to grab OpenStack EC2 (Amazon cloud) stats.
stats is a dict
"""
# The metadata service provides a way for instances to retrieve
# instance-specific data via a REST API. Instances access this
# service at 169.254.169.254 or at fe80::a9fe:a9fe.
# All types of metadata, be it user-, nova- or vendor-provided,
# can be accessed via this service.
# https://docs.openstack.org/nova/latest/user/metadata-service.html
OPENSTACK_PLATFORM = "Amazon EC2"
OPENSTACK_API_URL = 'http://169.254.169.254/latest/meta-data'
OPENSTACK_API_METADATA = {
'id': 'ami-id',
'name': 'instance-id',
'type': 'instance-type',
'region': 'placement/availability-zone',
}

View File

@@ -110,7 +110,7 @@ class PluginModel(GlancesPluginModel):
('misc_erase_process_filter', msg_col.format('E', 'Erase process filter')),
('misc_generate_history_graphs', msg_col.format('g', 'Generate history graphs')),
('misc_help', msg_col.format('h', 'HELP')),
('misc_accumulate_processes_by_program', msg_col.format('j', 'Accumulate processes by program')),
('misc_accumulate_processes_by_program', msg_col.format('j', 'Display threads or programs')),
('misc_increase_nice_process', msg_col.format('+', 'Increase nice process')),
('misc_decrease_nice_process', msg_col.format('-', 'Decrease nice process (need admin rights)')),
('misc_kill_process', msg_col.format('k', 'Kill process')),

View File

@@ -32,11 +32,16 @@ fields_description = {
'alias': {'description': 'Interface alias name (optional).', 'unit': 'string'},
'rx': {'description': 'The received/input rate (in bit per second).', 'unit': 'bps'},
'tx': {'description': 'The sent/output rate (in bit per second).', 'unit': 'bps'},
'cx': {'description': 'The cumulative received+sent rate (in bit per second).', 'unit': 'bps'},
'cumulative_rx': {
'description': 'The number of bytes received through the interface (cumulative).',
'unit': 'bytes',
},
'cumulative_tx': {'description': 'The number of bytes sent through the interface (cumulative).', 'unit': 'bytes'},
'cumulative_cx': {
'description': 'The cumulative number of bytes reveived and sent through the interface (cumulative).',
'unit': 'bytes',
},
'speed': {
'description': 'Maximum interface speed (in bit per second). Can return 0 on some operating-system.',
'unit': 'bps',

View File

@@ -145,9 +145,10 @@ class PluginModel(GlancesPluginModel):
# Update stats using the standard system lib
# Note: Update is done in the processcount plugin
# Just return the processes list
stats = glances_processes.getlist()
if self.args.programs:
stats = processes_to_programs(stats)
stats = glances_processes.getlist(as_programs=True)
else:
stats = glances_processes.getlist()
elif self.input_method == 'snmp':
# No SNMP grab for processes
@@ -349,14 +350,14 @@ class PluginModel(GlancesPluginModel):
"""Get curses data to display for a process.
- p is the process to display
- selected is a tag=True if the selected process
- selected is a tag=True if p is the selected process
"""
ret = [self.curse_new_line()]
# When a process is selected:
# * display a special character at the beginning of the line
# * underline the command name
if args.is_standalone:
ret.append(self.curse_add_line(unicode_message('PROCESS_SELECTOR') if selected else ' ', 'SELECTED'))
ret.append(self.curse_add_line(unicode_message('PROCESS_SELECTOR') if (selected and not args.disable_cursor) else ' ', 'SELECTED'))
# CPU
ret.append(self._get_process_curses_cpu(p, selected, args))
@@ -403,7 +404,7 @@ class PluginModel(GlancesPluginModel):
cmdline = p.get('cmdline', '?')
try:
process_decoration = 'PROCESS_SELECTED' if (selected and args.is_standalone) else 'PROCESS'
process_decoration = 'PROCESS_SELECTED' if (selected and not args.disable_cursor) else 'PROCESS'
if cmdline:
path, cmd, arguments = split_cmdline(bare_process_name, cmdline)
# Manage end of line in arguments (see #1692)
@@ -427,74 +428,14 @@ class PluginModel(GlancesPluginModel):
logger.debug("Can not decode command line '{}' ({})".format(cmdline, e))
ret.append(self.curse_add_line('', splittable=True))
# Add extended stats but only for the top processes
if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended:
# Left padding
xpad = ' ' * 13
# First line is CPU affinity
if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
ret.append(self.curse_new_line())
msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
ret.append(self.curse_add_line(msg, splittable=True))
# Second line is memory info
if 'memory_info' in p and p['memory_info'] is not None:
ret.append(self.curse_new_line())
msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
if 'memory_swap' in p and p['memory_swap'] is not None:
msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
ret.append(self.curse_add_line(msg, splittable=True))
# Third line is for open files/network sessions
msg = ''
if 'num_threads' in p and p['num_threads'] is not None:
msg += str(p['num_threads']) + ' threads '
if 'num_fds' in p and p['num_fds'] is not None:
msg += str(p['num_fds']) + ' files '
if 'num_handles' in p and p['num_handles'] is not None:
msg += str(p['num_handles']) + ' handles '
if 'tcp' in p and p['tcp'] is not None:
msg += str(p['tcp']) + ' TCP '
if 'udp' in p and p['udp'] is not None:
msg += str(p['udp']) + ' UDP'
if msg != '':
ret.append(self.curse_new_line())
msg = xpad + 'Open: ' + msg
ret.append(self.curse_add_line(msg, splittable=True))
# Fourth line is IO nice level (only Linux and Windows OS)
if 'ionice' in p and p['ionice'] is not None and hasattr(p['ionice'], 'ioclass'):
ret.append(self.curse_new_line())
msg = xpad + 'IO nice: '
k = 'Class is '
v = p['ionice'].ioclass
# Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
# Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
if WINDOWS:
if v == 0:
msg += k + 'Very Low'
elif v == 1:
msg += k + 'Low'
elif v == 2:
msg += 'No specific I/O priority'
else:
msg += k + str(v)
else:
if v == 0:
msg += 'No specific I/O priority'
elif v == 1:
msg += k + 'Real Time'
elif v == 2:
msg += k + 'Best Effort'
elif v == 3:
msg += k + 'IDLE'
else:
msg += k + str(v)
# value is a number which goes from 0 to 7.
# The higher the value, the lower the I/O priority of the process.
if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
msg += ' (value %s/7)' % str(p['ionice'].value)
ret.append(self.curse_add_line(msg, splittable=True))
return ret
def is_selected_process(self, args):
return args.is_standalone and \
self.args.enable_process_extended and \
args.cursor_position is not None and \
glances_processes.extended_process is not None
def msg_curse(self, args=None, max_width=None):
"""Return the dict to display in the curse interface."""
# Init the return message
@@ -506,6 +447,16 @@ class PluginModel(GlancesPluginModel):
# Compute the sort key
process_sort_key = glances_processes.sort_key
processes_list_sorted = self.__sort_stats(process_sort_key)
# Display extended stats for selected process
#############################################
if self.is_selected_process(args):
self.__msg_curse_extended_process(ret, glances_processes.extended_process)
# Display others processes list
###############################
# Header
self.__msg_curse_header(ret, process_sort_key, args)
@@ -514,10 +465,10 @@ class PluginModel(GlancesPluginModel):
# Loop over processes (sorted by the sort key previously compute)
# This is a Glances bottleneck (see flame graph),
# get_process_curses_data should be optimzed
i = 0
for p in self.__sort_stats(process_sort_key):
ret.extend(self.get_process_curses_data(p, i == args.cursor_position, args))
i += 1
for position, process in enumerate(processes_list_sorted):
ret.extend(self.get_process_curses_data(process,
position == args.cursor_position,
args))
# A filter is set Display the stats summaries
if glances_processes.process_filter is not None:
@@ -531,6 +482,116 @@ class PluginModel(GlancesPluginModel):
# Return the message with decoration
return ret
def __msg_curse_extended_process(self, ret, p):
"""Get extended curses data for the selected process (see issue #2225)
The result depends of the process type (process or thread).
Input p is a dict with the following keys:
{'status': 'S',
'memory_info': pmem(rss=466890752, vms=3365347328, shared=68153344, text=659456, lib=0, data=774647808, dirty=0),
'pid': 4980,
'io_counters': [165385216, 0, 165385216, 0, 1],
'num_threads': 20,
'nice': 0,
'memory_percent': 5.958135664449709,
'cpu_percent': 0.0,
'gids': pgids(real=1000, effective=1000, saved=1000),
'cpu_times': pcputimes(user=696.38, system=119.98, children_user=0.0, children_system=0.0, iowait=0.0),
'name': 'WebExtensions',
'key': 'pid',
'time_since_update': 2.1997854709625244,
'cmdline': ['/snap/firefox/2154/usr/lib/firefox/firefox', '-contentproc', '-childID', '...'],
'username': 'nicolargo',
'cpu_min': 0.0,
'cpu_max': 7.0,
'cpu_mean': 3.2}
"""
if self.args.programs:
self.__msg_curse_extended_process_program(ret, p)
else:
self.__msg_curse_extended_process_thread(ret, p)
def __msg_curse_extended_process_program(self, ret, p):
# Title
msg = "Pinned program {} ('e' to unpin)".format(p['name'])
ret.append(self.curse_add_line(msg, "TITLE"))
ret.append(self.curse_new_line())
ret.append(self.curse_new_line())
def __msg_curse_extended_process_thread(self, ret, p):
# Title
ret.append(self.curse_add_line("Pinned thread ", "TITLE"))
ret.append(self.curse_add_line(p['name'], "UNDERLINE"))
ret.append(self.curse_add_line(" ('e' to unpin)"))
# First line is CPU affinity
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(' CPU Min/Max/Mean: '))
msg = '{: >7.1f}{: >7.1f}{: >7.1f}%'.format(p['cpu_min'], p['cpu_max'], p['cpu_mean'])
ret.append(self.curse_add_line(msg, decoration='INFO'))
if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
ret.append(self.curse_add_line(' Affinity: '))
ret.append(self.curse_add_line(str(len(p['cpu_affinity'])), decoration='INFO'))
ret.append(self.curse_add_line(' cores', decoration='INFO'))
if 'ionice' in p and p['ionice'] is not None and hasattr(p['ionice'], 'ioclass'):
msg = ' IO nice: '
k = 'Class is '
v = p['ionice'].ioclass
# Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
# Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
if WINDOWS:
if v == 0:
msg += k + 'Very Low'
elif v == 1:
msg += k + 'Low'
elif v == 2:
msg += 'No specific I/O priority'
else:
msg += k + str(v)
else:
if v == 0:
msg += 'No specific I/O priority'
elif v == 1:
msg += k + 'Real Time'
elif v == 2:
msg += k + 'Best Effort'
elif v == 3:
msg += k + 'IDLE'
else:
msg += k + str(v)
# value is a number which goes from 0 to 7.
# The higher the value, the lower the I/O priority of the process.
if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
msg += ' (value %s/7)' % str(p['ionice'].value)
ret.append(self.curse_add_line(msg, splittable=True))
# Second line is memory info
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(' MEM Min/Max/Mean: '))
msg = '{: >7.1f}{: >7.1f}{: >7.1f}%'.format(p['memory_min'], p['memory_max'], p['memory_mean'])
ret.append(self.curse_add_line(msg, decoration='INFO'))
if 'memory_info' in p and p['memory_info'] is not None:
ret.append(self.curse_add_line(' Memory info: '))
for k in p['memory_info']._asdict():
ret.append(self.curse_add_line(self.auto_unit(p['memory_info']._asdict()[k], low_precision=False), decoration='INFO', splittable=True))
ret.append(self.curse_add_line(' ' + k + ' ', splittable=True))
if 'memory_swap' in p and p['memory_swap'] is not None:
ret.append(self.curse_add_line(self.auto_unit(p['memory_swap'], low_precision=False), decoration='INFO', splittable=True))
ret.append(self.curse_add_line(' swap ', splittable=True))
# Third line is for open files/network sessions
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(' Open: '))
for stat_prefix in ['num_threads', 'num_fds', 'num_handles', 'tcp', 'udp']:
if stat_prefix in p and p[stat_prefix] is not None:
ret.append(self.curse_add_line(str(p[stat_prefix]), decoration='INFO'))
ret.append(self.curse_add_line(' {} '.format(stat_prefix.replace('num_', ''))))
ret.append(self.curse_new_line())
ret.append(self.curse_new_line())
def __msg_curse_header(self, ret, process_sort_key, args=None):
"""Build the header and add it to the ret dict."""
sort_style = 'SORT'
@@ -577,10 +638,15 @@ class PluginModel(GlancesPluginModel):
msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True
)
)
if not self.args.programs:
msg = self.layout_header['command'].format('Command', "('k' to kill)" if args.is_standalone else "")
if args.is_standalone and not args.disable_cursor:
if self.args.programs:
shortkey = "('k' to kill)"
else:
shortkey = "('e' to pin | 'k' to kill)"
else:
msg = self.layout_header['command'].format('Programs', "('k' to kill)" if args.is_standalone else "")
shortkey = ""
msg = self.layout_header['command'].format("Programs" if self.args.programs else "Command",
shortkey)
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):

View File

@@ -175,7 +175,7 @@ class PluginModel(GlancesPluginModel):
# Alert processing
if i['type'] == SENSOR_TEMP_TYPE:
if self.is_limit('critical', stat_name='sensors_temperature_' + i['label']):
# By default use the thresholds confiured in the glances.conf file (see #2058)
# By default use the thresholds configured in the glances.conf file (see #2058)
alert = self.get_alert(current=i['value'], header='temperature_' + i['label'])
else:
# Else use the system thresholds
@@ -197,7 +197,7 @@ class PluginModel(GlancesPluginModel):
self.views[i[self.get_key()]]['value']['decoration'] = alert
def battery_trend(self, stats):
"""Return the trend characterr for the battery"""
"""Return the trend character for the battery"""
if 'status' not in stats:
return ''
if stats['status'].startswith('Charg'):
@@ -331,12 +331,15 @@ class GlancesGrabSensors(object):
else:
return ret
for chip_name, chip in iteritems(input_list):
i = 1
for feature in chip:
label_index = 1
for chip_name_index, feature in enumerate(chip):
sensors_current = {}
# Sensor name
if feature.label == '':
sensors_current['label'] = chip_name + ' ' + str(i)
sensors_current['label'] = chip_name + ' ' + str(chip_name_index)
elif feature.label in [i['label'] for i in ret]:
sensors_current['label'] = feature.label + ' ' + str(label_index)
label_index += 1
else:
sensors_current['label'] = feature.label
# Sensors value, limit and unit
@@ -348,7 +351,6 @@ class GlancesGrabSensors(object):
sensors_current['critical'] = int(system_critical) if system_critical is not None else None
# Add sensor to the list
ret.append(sensors_current)
i += 1
return ret
def get(self, sensor_type=SENSOR_TEMP_TYPE):

View File

@@ -12,6 +12,7 @@ import os
from glances.globals import BSD, LINUX, MACOS, WINDOWS, iterkeys
from glances.timer import Timer, getTimeSinceLastUpdate
from glances.filter import GlancesFilter
from glances.programs import processes_to_programs
from glances.logger import logger
import psutil
@@ -36,6 +37,10 @@ class GlancesProcesses(object):
def __init__(self, cache_timeout=60):
"""Init the class to collect stats about processes."""
# Init the args, coming from the GlancesStandalone class
# Should be set by the set_args method
self.args = None
# Add internals caches because psutil do not cache all the stats
# See: https://github.com/giampaolo/psutil/issues/462
self.username_cache = {}
@@ -69,6 +74,7 @@ class GlancesProcesses(object):
# Extended stats for top process is enable by default
self.disable_extended_tag = False
self.extended_process = None
# Test if the system can grab io_counters
try:
@@ -108,6 +114,10 @@ class GlancesProcesses(object):
self._max_values = {}
self.reset_max_values()
def set_args(self, args):
"""Set args."""
self.args = args
def reset_processcount(self):
"""Reset the global process count"""
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0, 'pid_max': None}
@@ -246,6 +256,91 @@ class GlancesProcesses(object):
for k in self._max_values_list:
self._max_values[k] = 0.0
def get_extended_stats(self, proc):
"""Get the extended stats for the given PID."""
# - cpu_affinity (Linux, Windows, FreeBSD)
# - ionice (Linux and Windows > Vista)
# - num_ctx_switches (not available on Illumos/Solaris)
# - num_fds (Unix-like)
# - num_handles (Windows)
# - memory_maps (only swap, Linux)
# https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
# - connections (TCP and UDP)
# - CPU min/max/mean
# Set the extended stats list (OS dependant)
extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches']
if LINUX:
# num_fds only available on Unix system (see issue #1351)
extended_stats += ['num_fds']
if WINDOWS:
extended_stats += ['num_handles']
ret = {}
try:
# Get the extended stats
selected_process = psutil.Process(proc['pid'])
ret = selected_process.as_dict(attrs=extended_stats, ad_value=None)
if LINUX:
try:
ret['memory_swap'] = sum([v.swap for v in selected_process.memory_maps()])
except (psutil.NoSuchProcess, KeyError):
# (KeyError catch for issue #1551)
pass
except (psutil.AccessDenied, NotImplementedError):
# NotImplementedError: /proc/${PID}/smaps file doesn't exist
# on kernel < 2.6.14 or CONFIG_MMU kernel configuration option
# is not enabled (see psutil #533/glances #413).
ret['memory_swap'] = None
try:
ret['tcp'] = len(selected_process.connections(kind="tcp"))
ret['udp'] = len(selected_process.connections(kind="udp"))
except (psutil.AccessDenied, psutil.NoSuchProcess):
# Manage issue1283 (psutil.AccessDenied)
ret['tcp'] = None
ret['udp'] = None
except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
logger.error('Can not grab extended stats ({})'.format(e))
self.extended_process = None
ret['extended_stats'] = False
else:
logger.debug('Grab extended stats for process {}'.format(proc['pid']))
# Compute CPU and MEM min/max/mean
for stat_prefix in ['cpu', 'memory']:
if stat_prefix + '_min' not in self.extended_process:
ret[stat_prefix + '_min'] = proc[stat_prefix + '_percent']
else:
ret[stat_prefix + '_min'] = proc[stat_prefix + '_percent'] if proc[stat_prefix + '_min'] > proc[stat_prefix + '_percent'] else proc[stat_prefix + '_min']
if stat_prefix + '_max' not in self.extended_process:
ret[stat_prefix + '_max'] = proc[stat_prefix + '_percent']
else:
ret[stat_prefix + '_max'] = proc[stat_prefix + '_percent'] if proc[stat_prefix + '_max'] < proc[stat_prefix + '_percent'] else proc[stat_prefix + '_max']
if stat_prefix + '_mean_sum' not in self.extended_process:
ret[stat_prefix + '_mean_sum'] = proc[stat_prefix + '_percent']
else:
ret[stat_prefix + '_mean_sum'] = proc[stat_prefix + '_mean_sum'] + proc[stat_prefix + '_percent']
if stat_prefix + '_mean_counter' not in self.extended_process:
ret[stat_prefix + '_mean_counter'] = 1
else:
ret[stat_prefix + '_mean_counter'] = proc[stat_prefix + '_mean_counter'] + 1
ret[stat_prefix + '_mean'] = ret[stat_prefix + '_mean_sum'] / ret[stat_prefix + '_mean_counter']
ret['extended_stats'] = True
return ret
def is_selected_extended_process(self, position):
"""Return True if the process is the selected one for extended stats."""
return hasattr(self.args, 'programs') and \
not self.args.programs and \
hasattr(self.args, 'enable_process_extended') and \
self.args.enable_process_extended and \
not self.disable_extended_tag and \
hasattr(self.args, 'cursor_position') and \
position == self.args.cursor_position and \
not self.args.disable_cursor
def update(self):
"""Update the processes stats."""
# Reset the stats
@@ -285,78 +380,44 @@ class GlancesProcesses(object):
is_cached = True
# Build the processes stats list (it is why we need psutil>=5.3.0)
# This is on of the main bottleneck of Glances (see flame graph)
self.processlist = [
p.info
for p in psutil.process_iter(attrs=sorted_attrs, ad_value=None)
# OS-related processes filter
if not (BSD and p.info['name'] == 'idle')
and not (WINDOWS and p.info['name'] == 'System Idle Process')
and not (MACOS and p.info['name'] == 'kernel_task')
and
# Kernel threads filter
not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0)
]
# This is one of the main bottleneck of Glances (see flame graph)
# Filter processes
self.processlist = list(
filter(
lambda p: not (BSD and p.info['name'] == 'idle')
and not (WINDOWS and p.info['name'] == 'System Idle Process')
and not (MACOS and p.info['name'] == 'kernel_task')
and not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0),
psutil.process_iter(attrs=sorted_attrs, ad_value=None),
)
)
# Only get the info key
self.processlist = [p.info for p in self.processlist]
# Sort the processes list by the current sort_key
self.processlist = sort_stats(self.processlist, sorted_by=self.sort_key, reverse=True)
# Update the processcount
self.update_processcount(self.processlist)
# Loop over processes and add metadata
first = True
for proc in self.processlist:
# Get extended stats, only for top processes (see issue #403).
if first and not self.disable_extended_tag:
# - cpu_affinity (Linux, Windows, FreeBSD)
# - ionice (Linux and Windows > Vista)
# - num_ctx_switches (not available on Illumos/Solaris)
# - num_fds (Unix-like)
# - num_handles (Windows)
# - memory_maps (only swap, Linux)
# https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
# - connections (TCP and UDP)
extended = {}
try:
top_process = psutil.Process(proc['pid'])
extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches']
if LINUX:
# num_fds only available on Unix system (see issue #1351)
extended_stats += ['num_fds']
if WINDOWS:
extended_stats += ['num_handles']
# Loop over processes and :
# - add extended stats for selected process
# - add metadata
for position, proc in enumerate(self.processlist):
# Extended stats
################
# Get the extended stats
extended = top_process.as_dict(attrs=extended_stats, ad_value=None)
# Get the selected process when the 'e' key is pressed
if self.is_selected_extended_process(position):
self.extended_process = proc
if LINUX:
try:
extended['memory_swap'] = sum([v.swap for v in top_process.memory_maps()])
except (psutil.NoSuchProcess, KeyError):
# (KeyError catch for issue #1551)
pass
except (psutil.AccessDenied, NotImplementedError):
# NotImplementedError: /proc/${PID}/smaps file doesn't exist
# on kernel < 2.6.14 or CONFIG_MMU kernel configuration option
# is not enabled (see psutil #533/glances #413).
extended['memory_swap'] = None
try:
extended['tcp'] = len(top_process.connections(kind="tcp"))
extended['udp'] = len(top_process.connections(kind="udp"))
except (psutil.AccessDenied, psutil.NoSuchProcess):
# Manage issue1283 (psutil.AccessDenied)
extended['tcp'] = None
extended['udp'] = None
except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
logger.error('Can not grab extended stats ({})'.format(e))
extended['extended_stats'] = False
else:
logger.debug('Grab extended stats for process {}'.format(proc['pid']))
extended['extended_stats'] = True
proc.update(extended)
first = False
# /End of extended stats
# Grab extended stats only for the selected process (see issue #2225)
if self.extended_process is not None and \
proc['pid'] == self.extended_process['pid']:
proc.update(self.get_extended_stats(self.extended_process))
self.extended_process = proc
# Meta data
###########
# PID is the key
proc['key'] = 'pid'
@@ -409,8 +470,8 @@ class GlancesProcesses(object):
# Save values to cache
self.processlist_cache[proc['pid']] = {cached: proc[cached] for cached in cached_attrs}
# Apply filter
self.processlist = [p for p in self.processlist if not (self._filter.is_filtered(p))]
# Apply user filter
self.processlist = list(filter(lambda p: not self._filter.is_filtered(p), self.processlist))
# Compute the maximum value for keys in self._max_values_list: CPU, MEM
# Useful to highlight the processes with maximum values
@@ -423,9 +484,14 @@ class GlancesProcesses(object):
"""Get the number of processes."""
return self.processcount
def getlist(self, sorted_by=None):
"""Get the processlist."""
return self.processlist
def getlist(self, sorted_by=None, as_programs=False):
"""Get the processlist.
By default, return the list of threads.
If as_programs is True, return the list of programs."""
if as_programs:
return processes_to_programs(self.processlist)
else:
return self.processlist
@property
def sort_key(self):

View File

@@ -36,7 +36,7 @@ def processes_to_programs(processes):
'name': p['name'],
'cmdline': [p['name']],
'pid': '_',
'username': p['username'],
'username': p['username'] if 'username' in p else '_',
'nice': p['nice'],
'status': p['status'],
}

View File

@@ -11,6 +11,7 @@
from glances.globals import nativestr
from subprocess import Popen, PIPE
import re
def secure_popen(cmd):
@@ -48,8 +49,9 @@ def __secure_popen(cmd):
p_last = None
# Split by pipe '|'
for sub_cmd in cmd.split('|'):
# Split by space ' '
sub_cmd_split = [i for i in sub_cmd.split(' ') if i]
# Split by space character, but do no split spaces within quotes (remove surrounding quotes, though)
tmp_split = [_ for _ in list(filter(None, re.split(r'(\s+)|(".*?"+?)|(\'.*?\'+?)', sub_cmd))) if _ != ' ']
sub_cmd_split = [_[1:-1] if (_[0] == _[-1] == '"') or (_[0] == _[-1] == '\'') else _ for _ in tmp_split]
p = Popen(sub_cmd_split, shell=False, stdin=sub_cmd_stdin, stdout=PIPE, stderr=PIPE)
if p_last is not None:
# Allow p_last to receive a SIGPIPE if p exits.

View File

@@ -94,7 +94,6 @@ class GlancesXMLRPCServer(SimpleXMLRPCServer, object):
finished = False
def __init__(self, bind_address, bind_port=61209, requestHandler=GlancesXMLRPCHandler, config=None):
self.bind_address = bind_address
self.bind_port = bind_port
self.config = config

View File

@@ -24,7 +24,6 @@ class GlancesSNMPClient(object):
"""SNMP client class (based on pysnmp library)."""
def __init__(self, host='localhost', port=161, version='2c', community='public', user='private', auth=''):
super(GlancesSNMPClient, self).__init__()
self.cmdGen = cmdgen.CommandGenerator()

View File

@@ -14,7 +14,6 @@ import time
from glances.globals import WINDOWS
from glances.logger import logger
from glances.outputs.glances_stdout_json import GlancesStdoutJson
from glances.processes import glances_processes
from glances.stats import GlancesStats
from glances.outputs.glances_curses import GlancesCursesStandalone
@@ -51,6 +50,9 @@ class GlancesStandalone(object):
self.display_modules_list()
sys.exit(0)
# The args is needed to get the selected process in the process list (Curses mode)
glances_processes.set_args(args)
# If process extended stats is disabled by user
if not args.enable_process_extended:
logger.debug("Extended stats for top process are disabled")
@@ -193,3 +195,8 @@ class GlancesStandalone(object):
)
)
print("You should consider upgrading using: pip install --upgrade glances")
print("Disable this warning temporarily using: glances --disable-check-update")
print(
"To disable it permanently, refer config reference at "
"https://glances.readthedocs.io/en/latest/config.html#syntax"
)

View File

@@ -123,7 +123,7 @@ class GlancesStats(object):
if args is not None:
# If the all key is set in the disable_plugin option then look in the enable_plugin option
if getattr(args, 'disable_all', False):
logger.info('%s => %s', name, getattr(args, 'enable_' + name, False))
logger.debug('%s => %s', name, getattr(args, 'enable_' + name, False))
setattr(args, 'disable_' + name, not getattr(args, 'enable_' + name, False))
else:
setattr(args, 'disable_' + name, getattr(args, 'disable_' + name, False))

View File

@@ -22,6 +22,7 @@ potsdb
prometheus_client
pygal
pymdstat
pymongo; python_version >= "3.7"
pysnmp
pySMART.smartx
python-dateutil
@@ -32,5 +33,5 @@ six
sparklines
statsd
wifi
zeroconf==0.19.1; python_version < "3.7"
zeroconf==0.47.3; python_version < "3.7"
zeroconf; python_version >= "3.7"

View File

@@ -3,4 +3,4 @@ defusedxml
packaging
ujson<4; python_version >= "3.5" and python_version < "3.6"
ujson<5; python_version >= "3.6" and python_version < "3.7"
ujson; python_version >= "3.7"
ujson>=5.4.0; python_version >= "3.7"

View File

@@ -37,7 +37,16 @@ def get_data_files():
def get_install_requires():
requires = ['psutil>=5.3.0', 'defusedxml', 'future', 'packaging']
requires = [
'psutil>=5.6.7',
'defusedxml',
'packaging',
'future; python_version < "3.0"',
'ujson<3; python_version < "3.0"',
'ujson<4; python_version >= "3.5" and python_version < "3.6"',
'ujson<5; python_version >= "3.6" and python_version < "3.7"',
'ujson>=5.4.0; python_version >= "3.7"',
]
if sys.platform.startswith('win'):
requires.append('bottle')
requires.append('requests')
@@ -127,13 +136,11 @@ setup(
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Topic :: System :: Monitoring'
]
)

View File

@@ -222,6 +222,16 @@ class TestGlances(unittest.TestCase):
self.assertIsInstance(req.json(), dict)
self.assertIsInstance(req.json()['interface_name'], list)
def test_012_status(self):
"""Check status endpoint."""
method = "status"
print('INFO: [TEST_012] Status')
print("HTTP RESTful request: %s/%s" % (URL, method))
req = self.http_get("%s/%s" % (URL, method))
self.assertTrue(req.ok)
self.assertEqual(req.text, "Active")
def test_999_stop_server(self):
"""Stop the Glances Web Server."""
print('INFO: [TEST_999] Stop the Glances Web Server')