mirror of
https://github.com/nicolargo/glances.git
synced 2024-12-24 09:44:48 +03:00
Merge branch 'develop' into issue1985
# Conflicts: # optional-requirements.txt
This commit is contained in:
commit
50102c1496
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -8,8 +8,10 @@ env:
|
||||
DEFAULT_DOCKER_IMAGE: nicolargo/glances
|
||||
NODE_ENV: ${{ (contains('refs/heads/master', github.ref) || startsWith(github.ref, 'refs/tags/v')) && 'prod' || 'dev' }}
|
||||
PUSH_BRANCH: ${{ 'refs/heads/develop' == github.ref || 'refs/heads/master' == github.ref || startsWith(github.ref, 'refs/tags/v') }}
|
||||
# linux/arm/v6 support following issue #2120
|
||||
# linux/arm/v7 (drop support for v6) support following issue - See issue #2120
|
||||
DOCKER_PLATFORMS: linux/amd64,linux/arm/v7,linux/arm64,linux/386
|
||||
# Ubuntu image only support linux/amd64 and linux/arm64 - See issue #2185
|
||||
DOCKER_PLATFORMS_UBUNTU: linux/amd64,linux/arm64
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@ -96,7 +98,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ['alpine']
|
||||
os: ['alpine', 'ubuntu']
|
||||
tag: ${{ fromJson(needs.create_Docker_builds.outputs.tags) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -144,7 +146,7 @@ jobs:
|
||||
CHANGING_ARG=${{ github.sha }}
|
||||
context: .
|
||||
file: "docker-files/${{ matrix.os }}.Dockerfile"
|
||||
platforms: ${{env.DOCKER_PLATFORMS}}
|
||||
platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }}
|
||||
target: ${{ matrix.tag.target }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
|
14
Makefile
14
Makefile
@ -133,7 +133,7 @@ flatpak: venv-dev-upgrade ## Generate FlatPack JSON file
|
||||
# Docker
|
||||
# ===================================================================
|
||||
|
||||
docker: docker-alpine ## Generate local docker images
|
||||
docker: docker-alpine docker-ubuntu## Generate local docker images
|
||||
|
||||
docker-alpine: ## Generate local docker images (Alpine)
|
||||
docker build --target full -f ./docker-files/alpine.Dockerfile -t glances:local-alpine-full .
|
||||
@ -159,22 +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 -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-minimal
|
||||
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 -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-full
|
||||
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 -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-dev
|
||||
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
|
||||
|
@ -220,7 +220,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:
|
||||
@ -237,7 +237,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.
|
||||
|
||||
@ -245,7 +245,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
|
||||
---------
|
||||
|
@ -237,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)
|
||||
@ -263,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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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}"
|
||||
|
@ -237,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)
|
||||
@ -263,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
|
||||
|
||||
|
@ -27,7 +27,8 @@ RUN apk add --no-cache \
|
||||
lm-sensors \
|
||||
wireless-tools \
|
||||
smartmontools \
|
||||
iputils
|
||||
iputils \
|
||||
tzdata
|
||||
|
||||
##############################################################################
|
||||
# Install the dependencies beforehand to make them cacheable
|
||||
@ -95,7 +96,8 @@ RUN apk add --no-cache \
|
||||
lm-sensors \
|
||||
wireless-tools \
|
||||
smartmontools \
|
||||
iputils
|
||||
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/
|
||||
|
@ -8,11 +8,13 @@
|
||||
# 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.0.1-base-ubuntu22.04
|
||||
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
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
@ -27,6 +29,7 @@ RUN apt-get update \
|
||||
wireless-tools \
|
||||
smartmontools \
|
||||
net-tools \
|
||||
tzdata \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@ -92,7 +95,6 @@ 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 \
|
||||
@ -105,6 +107,7 @@ RUN apt-get update \
|
||||
wireless-tools \
|
||||
smartmontools \
|
||||
net-tools \
|
||||
tzdata \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
BIN
docs/_static/cloud.png
vendored
Normal file
BIN
docs/_static/cloud.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
docs/_static/glances-architecture.png
vendored
Normal file
BIN
docs/_static/glances-architecture.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
BIN
docs/_static/processlist-extended.png
vendored
Normal file
BIN
docs/_static/processlist-extended.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
15
docs/aoa/cloud.rst
Normal file
15
docs/aoa/cloud.rst
Normal 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
|
@ -34,6 +34,7 @@ Legend:
|
||||
fs
|
||||
irq
|
||||
folders
|
||||
cloud
|
||||
raid
|
||||
smart
|
||||
sensors
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
982
docs/api.rst
982
docs/api.rst
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,20 @@ Get the Glances container:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker pull nicolargo/glances
|
||||
docker pull nicolargo/glances:<version>
|
||||
|
||||
Available tags (all images are based on the Alpine Operating System):
|
||||
|
||||
- `latest` for a minimal Glances image (latest release) version with Console, WebUI and Docker dependencies (Recommended)
|
||||
- `latest-full` for a full Glances image (latest release) with all dependencies
|
||||
- `dev` for a full Glances image (development branch) with all dependencies (may be instable)
|
||||
You can also specify a version (example: 3.3.0.4). All available versions can be found on `DockerHub`_.
|
||||
|
||||
An Example to pull the `latest` tag:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker pull nicolargo/glances:latest
|
||||
|
||||
Run the container in *console mode*:
|
||||
|
||||
@ -152,3 +165,6 @@ You can add a ``[passwords]`` block to the Glances configuration file as mention
|
||||
# Additionally (and optionally) a default password could be defined
|
||||
localhost=mylocalhostpassword
|
||||
default=mydefaultpassword
|
||||
|
||||
|
||||
.. _DockerHub: https://hub.docker.com/r/nicolargo/glances/tags
|
||||
|
@ -31,9 +31,13 @@ Glances InfluxDB data model:
|
||||
| | time_since_update... | |
|
||||
| | | |
|
||||
+---------------+-----------------------+-----------------------+
|
||||
| docker | cpu_percent | hostname |
|
||||
| docker | cpu_percent | hostname |
|
||||
| | memory_usage... | name |
|
||||
+---------------+-----------------------+-----------------------+
|
||||
| gpu | proc | hostname |
|
||||
| | mem | gpu_id |
|
||||
| | temperature... | |
|
||||
+---------------+-----------------------+-----------------------+
|
||||
|
||||
InfluxDB (up to version 1.7.x)
|
||||
------------------------------
|
||||
|
@ -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" "Jan 30, 2023" "3.4.0_beta1" "Glances"
|
||||
.TH "GLANCES" "1" "Mar 11, 2023" "3.4.0_beta1" "Glances"
|
||||
.SH NAME
|
||||
glances \- An eye on your system
|
||||
.SH SYNOPSIS
|
||||
|
@ -61,7 +61,7 @@ if PY3:
|
||||
|
||||
|
||||
def __signal_handler(signal, frame):
|
||||
"""Callback for CTRL-C."""
|
||||
logger.debug("Signal {} catched".format(signal))
|
||||
end()
|
||||
|
||||
|
||||
@ -74,7 +74,7 @@ def end():
|
||||
# ...after starting the server mode (issue #1175)
|
||||
pass
|
||||
|
||||
logger.info("Glances stopped (key pressed: CTRL-C)")
|
||||
logger.info("Glances stopped gracefully")
|
||||
|
||||
# The end...
|
||||
sys.exit(0)
|
||||
@ -156,8 +156,9 @@ def main():
|
||||
Select the mode (standalone, client or server)
|
||||
Run it...
|
||||
"""
|
||||
# Catch the CTRL-C signal
|
||||
signal.signal(signal.SIGINT, __signal_handler)
|
||||
# Catch the kill signal
|
||||
for sig in (signal.SIGTERM, signal.SIGINT, signal.SIGHUP):
|
||||
signal.signal(sig, __signal_handler)
|
||||
|
||||
# Log Glances and psutil version
|
||||
logger.info('Start Glances {}'.format(__version__))
|
||||
|
@ -99,7 +99,7 @@ class GlancesClient(object):
|
||||
# Fallback to SNMP
|
||||
self.client_mode = 'snmp'
|
||||
logger.error("Connection to Glances server failed ({} {})".format(err.errno, err.strerror))
|
||||
fall_back_msg = 'No Glances server found on {}. Trying fallback to SNMP...'.format(self.uri)
|
||||
fall_back_msg = 'No Glances server found. Trying fallback to SNMP...'
|
||||
if not self.return_to_browser:
|
||||
print(fall_back_msg)
|
||||
else:
|
||||
|
@ -93,7 +93,7 @@ class GlancesClientBrowser(object):
|
||||
try:
|
||||
s = ServerProxy(uri, transport=t)
|
||||
except Exception as e:
|
||||
logger.warning("Client browser couldn't create socket {}: {}".format(uri, e))
|
||||
logger.warning("Client browser couldn't create socket ({})".format(e))
|
||||
else:
|
||||
# Mandatory stats
|
||||
try:
|
||||
@ -105,7 +105,7 @@ class GlancesClientBrowser(object):
|
||||
# OS (Human Readable name)
|
||||
server['hr_name'] = ujson.loads(s.getSystem())['hr_name']
|
||||
except (socket.error, Fault, KeyError) as e:
|
||||
logger.debug("Error while grabbing stats form {}: {}".format(uri, e))
|
||||
logger.debug("Error while grabbing stats form server ({})".format(e))
|
||||
server['status'] = 'OFFLINE'
|
||||
except ProtocolError as e:
|
||||
if e.errcode == 401:
|
||||
@ -115,7 +115,7 @@ class GlancesClientBrowser(object):
|
||||
server['status'] = 'PROTECTED'
|
||||
else:
|
||||
server['status'] = 'OFFLINE'
|
||||
logger.debug("Cannot grab stats from {} ({} {})".format(uri, e.errcode, e.errmsg))
|
||||
logger.debug("Cannot grab stats from server ({} {})".format(e.errcode, e.errmsg))
|
||||
else:
|
||||
# Status
|
||||
server['status'] = 'ONLINE'
|
||||
@ -126,7 +126,7 @@ class GlancesClientBrowser(object):
|
||||
load_min5 = ujson.loads(s.getLoad())['min5']
|
||||
server['load_min5'] = '{:.2f}'.format(load_min5)
|
||||
except Exception as e:
|
||||
logger.warning("Error while grabbing stats form {}: {}".format(uri, e))
|
||||
logger.warning("Error while grabbing stats form server ({})".format(e))
|
||||
|
||||
return server
|
||||
|
||||
|
@ -236,6 +236,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',
|
||||
@ -542,7 +549,6 @@ Examples of use:
|
||||
logger.setLevel(DEBUG)
|
||||
else:
|
||||
from warnings import simplefilter
|
||||
|
||||
simplefilter("ignore")
|
||||
|
||||
# Plugins refresh rate
|
||||
@ -696,9 +702,12 @@ Examples of use:
|
||||
sys.exit(2)
|
||||
|
||||
# Filter is only available in standalone mode
|
||||
if args.process_filter is not None and not self.is_standalone():
|
||||
logger.critical("Process filter is only available in standalone mode")
|
||||
sys.exit(2)
|
||||
if not args.process_filter and not self.is_standalone():
|
||||
logger.debug("Process filter is only available in standalone mode")
|
||||
|
||||
# Cursor option is only available in standalone mode
|
||||
if not args.disable_cursor and not self.is_standalone():
|
||||
logger.debug("Cursor is only available in standalone mode")
|
||||
|
||||
# Disable HDDTemp if sensors are disabled
|
||||
if getattr(self.args, 'disable_sensors', False):
|
||||
@ -727,8 +736,17 @@ Examples of use:
|
||||
self.args.is_server = self.is_server()
|
||||
self.args.is_webserver = self.is_webserver()
|
||||
|
||||
# Check mode compatibility
|
||||
self.check_mode_compatibility()
|
||||
|
||||
return args
|
||||
|
||||
def check_mode_compatibility(self):
|
||||
"""Check mode compatibility"""
|
||||
if self.args.is_server and self.args.is_webserver:
|
||||
logger.critical("Server and Web server mode are incompatible")
|
||||
sys.exit(2)
|
||||
|
||||
def is_standalone(self):
|
||||
"""Return True if Glances is running in standalone mode."""
|
||||
return not self.args.client and not self.args.browser and not self.args.server and not self.args.webserver
|
||||
|
@ -230,7 +230,7 @@ class GlancesBottle(object):
|
||||
"""
|
||||
response.status = 200
|
||||
|
||||
return None
|
||||
return "Active"
|
||||
|
||||
@compress
|
||||
def _api_help(self):
|
||||
|
@ -268,6 +268,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
|
||||
|
||||
@ -301,6 +302,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
|
||||
|
||||
@ -328,6 +330,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):
|
||||
@ -407,13 +410,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
|
||||
@ -427,7 +432,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'):
|
||||
@ -451,11 +456,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:
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
2219
glances/outputs/static/package-lock.json
generated
2219
glances/outputs/static/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
6
glances/outputs/static/public/glances.js
vendored
6
glances/outputs/static/public/glances.js
vendored
File diff suppressed because one or more lines are too long
@ -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 Plugin(GlancesPlugin):
|
||||
|
||||
# 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(Plugin, self).exit()
|
||||
|
||||
@ -80,12 +84,15 @@ class Plugin(GlancesPlugin):
|
||||
# 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 Plugin(GlancesPlugin):
|
||||
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',
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ from __future__ import unicode_literals
|
||||
import operator
|
||||
|
||||
from glances.compat import u, nativestr, PermissionError
|
||||
from glances.logger import logger
|
||||
from glances.plugins.glances_plugin import GlancesPlugin
|
||||
|
||||
import psutil
|
||||
@ -95,14 +96,26 @@ class Plugin(GlancesPlugin):
|
||||
try:
|
||||
fs_stat = psutil.disk_partitions(all=False)
|
||||
except (UnicodeDecodeError, PermissionError):
|
||||
logger.debug("Plugin - fs: PsUtil fetch failed")
|
||||
return self.stats
|
||||
|
||||
# Optional hack to allow logical mounts points (issue #448)
|
||||
for fs_type in self.get_conf_value('allow'):
|
||||
allowed_fs_types = self.get_conf_value('allow')
|
||||
if allowed_fs_types:
|
||||
# Avoid Psutil call unless mounts need to be allowed
|
||||
try:
|
||||
fs_stat += [f for f in psutil.disk_partitions(all=True) if f.fstype.find(fs_type) >= 0]
|
||||
except UnicodeDecodeError:
|
||||
return self.stats
|
||||
all_mounted_fs = psutil.disk_partitions(all=True)
|
||||
except (UnicodeDecodeError, PermissionError):
|
||||
logger.debug("Plugin - fs: PsUtil extended fetch failed")
|
||||
else:
|
||||
# Discard duplicates (#2299) and add entries matching allowed fs types
|
||||
tracked_mnt_points = set(f.mountpoint for f in fs_stat)
|
||||
for f in all_mounted_fs:
|
||||
if (
|
||||
any(f.fstype.find(fs_type) >= 0 for fs_type in allowed_fs_types)
|
||||
and f.mountpoint not in tracked_mnt_points
|
||||
):
|
||||
fs_stat.append(f)
|
||||
|
||||
# Loop over fs
|
||||
for fs in fs_stat:
|
||||
|
@ -84,7 +84,8 @@ class Plugin(GlancesPlugin):
|
||||
# "name": "Fake GeForce GTX",
|
||||
# "mem": 5.792331695556641,
|
||||
# "proc": 4,
|
||||
# "temperature": 26
|
||||
# "temperature": 26,
|
||||
# "fan_speed": 30
|
||||
# }
|
||||
# ]
|
||||
# Two GPU sample:
|
||||
@ -95,7 +96,8 @@ class Plugin(GlancesPlugin):
|
||||
# "name": "Fake GeForce GTX1",
|
||||
# "mem": 5.792331695556641,
|
||||
# "proc": 4,
|
||||
# "temperature": 26
|
||||
# "temperature": 26,
|
||||
# "fan_speed": 30
|
||||
# },
|
||||
# {
|
||||
# "key": "gpu_id",
|
||||
@ -103,7 +105,8 @@ class Plugin(GlancesPlugin):
|
||||
# "name": "Fake GeForce GTX2",
|
||||
# "mem": 15,
|
||||
# "proc": 8,
|
||||
# "temperature": 65
|
||||
# "temperature": 65,
|
||||
# "fan_speed": 75
|
||||
# }
|
||||
# ]
|
||||
return self.stats
|
||||
@ -274,6 +277,8 @@ class Plugin(GlancesPlugin):
|
||||
device_stats['proc'] = get_proc(device_handle)
|
||||
# Processor temperature in °C
|
||||
device_stats['temperature'] = get_temperature(device_handle)
|
||||
# Fan speed in %
|
||||
device_stats['fan_speed'] = get_fan_speed(device_handle)
|
||||
stats.append(device_stats)
|
||||
|
||||
return stats
|
||||
@ -324,8 +329,16 @@ def get_proc(device_handle):
|
||||
|
||||
|
||||
def get_temperature(device_handle):
|
||||
"""Get GPU device CPU consumption in percent."""
|
||||
"""Get GPU device CPU temperature in Celsius."""
|
||||
try:
|
||||
return pynvml.nvmlDeviceGetTemperature(device_handle, pynvml.NVML_TEMPERATURE_GPU)
|
||||
except pynvml.NVMLError:
|
||||
return None
|
||||
|
||||
|
||||
def get_fan_speed(device_handle):
|
||||
"""Get GPU device fan speed in percent."""
|
||||
try:
|
||||
return pynvml.nvmlDeviceGetFanSpeed(device_handle)
|
||||
except pynvml.NVMLError:
|
||||
return None
|
||||
|
@ -19,7 +19,7 @@ from glances.processes import glances_processes, sort_stats
|
||||
from glances.outputs.glances_unicode import unicode_message
|
||||
from glances.plugins.glances_core import Plugin as CorePlugin
|
||||
from glances.plugins.glances_plugin import GlancesPlugin
|
||||
from glances.programs import processes_to_programs
|
||||
from glances.outputs.glances_bars import Bar
|
||||
|
||||
|
||||
def seconds_to_hms(input_seconds):
|
||||
@ -146,9 +146,10 @@ class Plugin(GlancesPlugin):
|
||||
# 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
|
||||
@ -353,11 +354,11 @@ class Plugin(GlancesPlugin):
|
||||
- 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))
|
||||
@ -404,7 +405,7 @@ class Plugin(GlancesPlugin):
|
||||
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)
|
||||
@ -428,74 +429,14 @@ class Plugin(GlancesPlugin):
|
||||
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
|
||||
@ -507,6 +448,16 @@ class Plugin(GlancesPlugin):
|
||||
|
||||
# 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)
|
||||
@ -515,10 +466,10 @@ class Plugin(GlancesPlugin):
|
||||
# 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:
|
||||
@ -532,6 +483,116 @@ class Plugin(GlancesPlugin):
|
||||
# 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'
|
||||
@ -578,10 +639,15 @@ class Plugin(GlancesPlugin):
|
||||
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):
|
||||
|
@ -92,6 +92,8 @@ class Plugin(GlancesPlugin):
|
||||
# New line
|
||||
ret.append(self.curse_new_line())
|
||||
# Display the current status
|
||||
if not isinstance(self.stats[array], dict):
|
||||
continue
|
||||
status = self.raid_alert(
|
||||
self.stats[array]['status'],
|
||||
self.stats[array]['used'],
|
||||
|
@ -175,7 +175,7 @@ class Plugin(GlancesPlugin):
|
||||
# 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 Plugin(GlancesPlugin):
|
||||
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):
|
||||
|
@ -13,6 +13,7 @@ from glances.compat import iterkeys
|
||||
from glances.globals import BSD, LINUX, MACOS, WINDOWS
|
||||
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
|
||||
@ -37,6 +38,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 = {}
|
||||
@ -70,6 +75,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:
|
||||
@ -109,6 +115,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}
|
||||
@ -247,6 +257,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
|
||||
@ -305,59 +400,25 @@ class GlancesProcesses(object):
|
||||
# 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'
|
||||
@ -424,9 +485,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):
|
||||
|
@ -50,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,5 +196,7 @@ 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")
|
||||
print(
|
||||
"To disable it permanently, refer config reference at "
|
||||
"https://glances.readthedocs.io/en/latest/config.html#syntax"
|
||||
)
|
||||
|
@ -34,5 +34,5 @@ six
|
||||
sparklines
|
||||
statsd
|
||||
wifi
|
||||
zeroconf==0.47.3; python_version < "3.7"
|
||||
zeroconf>=0.19.1; python_version >= "3.7"
|
||||
zeroconf==0.54.0; python_version < "3.7"
|
||||
zeroconf; python_version >= "3.7"
|
||||
|
2
setup.py
2
setup.py
@ -61,7 +61,7 @@ def get_install_requires():
|
||||
def get_install_extras_require():
|
||||
extras_require = {
|
||||
'action': ['chevron'],
|
||||
'browser': ['zeroconf==0.47.3' if PY2 else 'zeroconf>=0.19.1'],
|
||||
'browser': ['zeroconf==0.54.0' if PY2 else 'zeroconf>=0.19.1'],
|
||||
'cloud': ['requests'],
|
||||
'docker': ['docker>=2.0.0', 'python-dateutil', 'six'],
|
||||
'export': ['bernhard', 'cassandra-driver', 'couchdb', 'elasticsearch',
|
||||
|
@ -221,6 +221,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')
|
||||
|
Loading…
Reference in New Issue
Block a user