Merge branch 'develop' into issue1985

# Conflicts:
#	optional-requirements.txt
This commit is contained in:
Raz Crimson 2023-04-05 01:11:01 +05:30
commit 50102c1496
42 changed files with 1846 additions and 2132 deletions

View File

@ -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

View File

@ -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

View File

@ -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
---------

View File

@ -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

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

@ -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

View File

@ -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/

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

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

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

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

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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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)
------------------------------

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" "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

View File

@ -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__))

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -230,7 +230,7 @@ class GlancesBottle(object):
"""
response.status = 200
return None
return "Active"
@compress
def _api_help(self):

View File

@ -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:

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

@ -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;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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 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',
}

View File

@ -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:

View File

@ -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

View File

@ -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):

View File

@ -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'],

View File

@ -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):

View File

@ -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):

View File

@ -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"
)

View File

@ -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"

View File

@ -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',

View File

@ -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')