mirror of
https://github.com/nicolargo/glances.git
synced 2024-08-17 00:10:48 +03:00
Compare commits
195 Commits
763ae52553
...
dc24e35786
Author | SHA1 | Date | |
---|---|---|---|
|
dc24e35786 | ||
|
6af2340c5c | ||
|
46abd4b4c9 | ||
|
2801add7e2 | ||
|
9b853d857b | ||
|
99e65079a0 | ||
|
0e1d462c69 | ||
|
f4c7ca01fe | ||
|
60591630df | ||
|
48103f167e | ||
|
154ae8e610 | ||
|
86c2cd1d4a | ||
|
452fd6497e | ||
|
ccfd8f0aa9 | ||
|
d91bfa2f8b | ||
|
582ca07716 | ||
|
036eb976e9 | ||
|
2c5a1150bd | ||
|
f6066e5d46 | ||
|
cff2e9fc4a | ||
|
e5d5351d31 | ||
|
be89ee0025 | ||
|
f4cd350221 | ||
|
3928007169 | ||
|
eebd769c46 | ||
|
2ee3c86e2b | ||
|
f5ecbf7a1f | ||
|
267e7e2ff0 | ||
|
1e2e36af23 | ||
|
c153e71aa3 | ||
|
f9915d93e8 | ||
|
69c3a948ba | ||
|
8c520e1d94 | ||
|
1edaed52de | ||
|
752bd40a04 | ||
|
fce3aac34e | ||
|
9c482196f3 | ||
|
3beb08b66e | ||
|
0874e13f1d | ||
|
8b4ef8c235 | ||
|
993ef0107a | ||
|
0bdda36143 | ||
|
7cffdcbff7 | ||
|
986c865718 | ||
|
0227d221da | ||
|
3f39522487 | ||
|
78b4518547 | ||
|
ea4ce83d83 | ||
|
051006e12f | ||
|
4862264a2a | ||
|
0791c7e9da | ||
|
12dafcf856 | ||
|
4b071403da | ||
|
a6b7de8a4b | ||
|
e371c8f39d | ||
|
810b0ec488 | ||
|
f224cab225 | ||
|
9b0d0a7ff0 | ||
|
8d3b3691e5 | ||
|
13e2f73738 | ||
|
6077bb4a55 | ||
|
5ad9e89658 | ||
|
65f84fd1e9 | ||
|
b2e5cb58c4 | ||
|
4a60e826b5 | ||
|
d0de547e41 | ||
|
d00be34255 | ||
|
6410ccab9b | ||
|
350318fb6e | ||
|
5a78e72419 | ||
|
2379ec6b55 | ||
|
a55970af83 | ||
|
273a405a10 | ||
|
c187c14b5f | ||
|
a129c1b337 | ||
|
e5e5cf4c01 | ||
|
092ecf5cf8 | ||
|
4b48304324 | ||
|
79d9aa742b | ||
|
8d6fabc510 | ||
|
23cd99d11e | ||
|
6d2ecf5cf0 | ||
|
c61d10bec7 | ||
|
60d6be1718 | ||
|
9ab3199e13 | ||
|
f6ddc5cf49 | ||
|
c5ea3d292c | ||
|
e07bacf9a3 | ||
|
6743eea32d | ||
|
53621f413b | ||
|
f0fc27f952 | ||
|
ba88263b85 | ||
|
d2f1fa10d5 | ||
|
15b0a9bbab | ||
|
1183b4dabd | ||
|
74f9606e65 | ||
|
a99bf33b4b | ||
|
4f38bf384b | ||
|
5262524db3 | ||
|
3306fa0118 | ||
|
30ec25fa0a | ||
|
6a0889851c | ||
|
f31b767e0e | ||
|
14efbac82d | ||
|
00c662f870 | ||
|
cd69c4a0e8 | ||
|
28e7e5b74a | ||
|
108ffcdfb6 | ||
|
f9a06a31f0 | ||
|
1f628642d4 | ||
|
b4c2a4c24a | ||
|
d392caf6f6 | ||
|
9890749d81 | ||
|
acb5ef585f | ||
|
396e78c415 | ||
|
6583109c73 | ||
|
986cd99fbd | ||
|
98f2bb7fbe | ||
|
05bc1449db | ||
|
d095e4ae34 | ||
|
6acd0da704 | ||
|
f6545580db | ||
|
d94b0b4e92 | ||
|
0447e210e5 | ||
|
d8c5a72387 | ||
|
25ae493517 | ||
|
3aa9225da0 | ||
|
7a25f7165e | ||
|
c66c651205 | ||
|
127f2e45ed | ||
|
c98b7fcf1d | ||
|
8c1b74993d | ||
|
371885c34c | ||
|
6d4e0884cd | ||
|
fc63162515 | ||
|
516b3ab478 | ||
|
b54fbda064 | ||
|
7fbc598d5a | ||
|
ddfa809280 | ||
|
4bfe5f2c81 | ||
|
afb06cb230 | ||
|
4ff7c82559 | ||
|
4a4ca76505 | ||
|
374bf0944b | ||
|
d9056e6027 | ||
|
562780f7f0 | ||
|
e9abf1c9af | ||
|
364a3ef884 | ||
|
35015f7c19 | ||
|
e7a3de5038 | ||
|
b8304d8796 | ||
|
893c1452a3 | ||
|
ada7f248ba | ||
|
4ddff48c70 | ||
|
f2d7e13cd8 | ||
|
fd3625826b | ||
|
af4765db8a | ||
|
8e8d4ce89b | ||
|
ee826a1e67 | ||
|
fb10c7f36d | ||
|
370da44744 | ||
|
b97606247d | ||
|
a695e1a70e | ||
|
a3e08acdd2 | ||
|
048e1fc6de | ||
|
2e6842cc18 | ||
|
5e777f0f42 | ||
|
e7b0cf09dd | ||
|
81b93e9f16 | ||
|
34f0e90130 | ||
|
82e8d7bac7 | ||
|
551c4d686b | ||
|
0cc3dcbaa0 | ||
|
1470cd9f63 | ||
|
f360404a3c | ||
|
ac4da6fd5b | ||
|
40fd7fe7c7 | ||
|
87b8300cc6 | ||
|
28101e20fa | ||
|
5bb1047151 | ||
|
f3cf3aa77b | ||
|
dbed127987 | ||
|
ed46b6a3e2 | ||
|
1d86888f76 | ||
|
2da2cb714e | ||
|
ef57106fac | ||
|
3874a16e03 | ||
|
3e367235d6 | ||
|
c3c0cb6e6c | ||
|
5f7e713254 | ||
|
6d7d3a914d | ||
|
fef2d86940 | ||
|
2c5df09983 | ||
|
cb9dba3c18 | ||
|
230c2fe437 |
8
.flake8
8
.flake8
@ -1,8 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
exclude = .git,__pycache__,docs/,build,dist
|
|
||||||
ignore =
|
|
||||||
W504, B007, B014, B008, B902, Q000,
|
|
||||||
N801, N802, N803, N806, N807, N811, N812, N813, N814, N815, N816, N817, N818
|
|
||||||
|
|
||||||
# lines should not exceed 120 characters
|
|
||||||
max-line-length = 120
|
|
9
.github/ISSUE_TEMPLATE/bug_report.md
vendored
9
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -6,6 +6,9 @@ labels: ''
|
|||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
**Check the bug**
|
||||||
|
Before filling this bug report, please search if a similar issue already exists.
|
||||||
|
In this case, just add a comment on this existing issue.
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
@ -26,11 +29,7 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
- Operating System (lsb_release -a or OS name/version): `To be completed with result of: lsb_release -a`
|
- Operating System (lsb_release -a or OS name/version): `To be completed with result of: lsb_release -a`
|
||||||
- Glances & psutil versions: `To be completed with result of: glances -V`
|
- Glances & psutil versions: `To be completed with result of: glances -V`
|
||||||
- How do you install Glances (Pypi package, script, package manager, source): `To be completed`
|
- How do you install Glances (Pypi package, script, package manager, source): `To be completed`
|
||||||
- Glances test (only available with Glances 3.1.7 or higher):
|
- Glances test: ` To be completed with result of: glances --issue`
|
||||||
|
|
||||||
```
|
|
||||||
To be completed with result of: glances --issue
|
|
||||||
```
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@ -123,19 +123,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve Repository Docker metadata
|
- name: Retrieve Repository Docker metadata
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
uses: crazy-max/ghaction-docker-meta@v5.0.0
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.DEFAULT_DOCKER_IMAGE }}
|
images: ${{ env.DEFAULT_DOCKER_IMAGE }}
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.url=https://nicolargo.github.io/glances/
|
org.opencontainers.image.url=https://nicolargo.github.io/glances/
|
||||||
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ env.NODE_ENV }}-${{ matrix.os }}-${{ matrix.tag.tag }}
|
|
||||||
restore-keys: ${{ runner.os }}-buildx-${{ env.NODE_ENV }}-${{ matrix.os }}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
@ -166,5 +159,7 @@ jobs:
|
|||||||
platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }}
|
platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }}
|
||||||
target: ${{ matrix.tag.target }}
|
target: ${{ matrix.tag.target }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
# GHA default behaviour overwrites last build cache. Causes alpine and ubuntu cache to overwrite each other.
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
|
# Use `scope` with the os name to prevent that
|
||||||
|
cache-from: 'type=gha,scope=${{ matrix.os }}'
|
||||||
|
cache-to: 'type=gha,mode=max,scope=${{ matrix.os }}'
|
||||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -15,13 +15,13 @@ jobs:
|
|||||||
uses: ./.github/workflows/test.yml
|
uses: ./.github/workflows/test.yml
|
||||||
needs: [quality]
|
needs: [quality]
|
||||||
webui:
|
webui:
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request' && !contains(github.ref, 'refs/tags/')
|
||||||
uses: ./.github/workflows/webui.yml
|
uses: ./.github/workflows/webui.yml
|
||||||
needs: [quality, test]
|
needs: [quality, test]
|
||||||
cyber:
|
cyber:
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: ./.github/workflows/cyber.yml
|
uses: ./.github/workflows/cyber.yml
|
||||||
needs: [quality, test, webui]
|
needs: [quality, test]
|
||||||
build:
|
build:
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
@ -30,4 +30,4 @@ jobs:
|
|||||||
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
|
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
|
||||||
needs: [quality, test, webui, cyber]
|
needs: [quality, test]
|
||||||
|
22
.github/workflows/inactive_issues.yml
vendored
Normal file
22
.github/workflows/inactive_issues.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Label inactive issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v5
|
||||||
|
with:
|
||||||
|
days-before-issue-stale: 90
|
||||||
|
days-before-issue-close: -1
|
||||||
|
stale-issue-label: "inactive"
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 3 months with no activity."
|
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 30 days since being marked as stale."
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-pr-close: -1
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
22
.github/workflows/needs_contributor.yml
vendored
Normal file
22
.github/workflows/needs_contributor.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Add a message when needs contributor tag is used
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- labeled
|
||||||
|
jobs:
|
||||||
|
add-comment:
|
||||||
|
if: github.event.label.name == 'needs contributor'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
steps:
|
||||||
|
- name: Add comment
|
||||||
|
run: gh issue comment "$NUMBER" --body "$BODY"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
NUMBER: ${{ github.event.issue.number }}
|
||||||
|
BODY: >
|
||||||
|
This issue is available for anyone to work on.
|
||||||
|
**Make sure to reference this issue in your pull request.**
|
||||||
|
:sparkles: Thank you for your contribution ! :sparkles:
|
98
.github/workflows/test.yml
vendored
98
.github/workflows/test.yml
vendored
@ -7,8 +7,32 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
source-code-checks:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check formatting with Ruff
|
||||||
|
uses: chartboost/ruff-action@v1
|
||||||
|
with:
|
||||||
|
args: 'format --check'
|
||||||
|
|
||||||
|
- name: Check linting with Ruff
|
||||||
|
uses: chartboost/ruff-action@v1
|
||||||
|
with:
|
||||||
|
args: 'check'
|
||||||
|
|
||||||
|
- name: Static type check
|
||||||
|
run: |
|
||||||
|
echo "Skipping static type check for the moment, too much error...";
|
||||||
|
# pip install pyright
|
||||||
|
# pyright glances
|
||||||
|
|
||||||
|
|
||||||
test-linux:
|
test-linux:
|
||||||
|
|
||||||
|
needs: source-code-checks
|
||||||
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
|
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
@ -17,36 +41,22 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install flake8
|
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
|
||||||
|
|
||||||
- name: Lint with flake8
|
- name: Unitary tests
|
||||||
run: |
|
run: |
|
||||||
# Stop the build if there are Python syntax errors or undefined names
|
python ./unittest-core.py
|
||||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=.git,./docs,./glances/outputs/static
|
|
||||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
|
||||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=.git,./docs,./glances/outputs/static
|
|
||||||
|
|
||||||
- name: Static type check
|
|
||||||
run: |
|
|
||||||
echo "Skipping static type check for the moment, too much error...";
|
|
||||||
# pip install pyright
|
|
||||||
# pyright glances
|
|
||||||
|
|
||||||
- name: Unitary tests
|
|
||||||
run: |
|
|
||||||
python ./unitest.py
|
|
||||||
|
|
||||||
# Error appear with h11, not related to Glances
|
# Error appear with h11, not related to Glances
|
||||||
# Should be tested if correction is done
|
# Should be tested if correction is done
|
||||||
@ -80,34 +90,36 @@ jobs:
|
|||||||
|
|
||||||
# - name: Unitary tests
|
# - name: Unitary tests
|
||||||
# run: |
|
# run: |
|
||||||
# python ./unitest.py
|
# python ./unittest-core.py
|
||||||
|
|
||||||
test-macos:
|
test-macos:
|
||||||
|
|
||||||
|
needs: source-code-checks
|
||||||
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
|
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
# Only test the latest stable version
|
||||||
|
python-version: ["3.12"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||||
|
|
||||||
- name: Unitary tests
|
- name: Unitary tests
|
||||||
run: |
|
run: |
|
||||||
python ./unitest.py
|
python ./unittest-core.py
|
||||||
|
|
||||||
# Error when trying to implement #2749
|
# Error when trying to implement #2749
|
||||||
# pkg: No packages available to install matching 'py-pip' have been found in the repositories
|
# pkg: No packages available to install matching 'py-pip' have been found in the repositories
|
||||||
@ -128,4 +140,4 @@ jobs:
|
|||||||
# run: |
|
# run: |
|
||||||
# set -e -x
|
# set -e -x
|
||||||
# python3 -m pip install --user -r requirements.txt
|
# python3 -m pip install --user -r requirements.txt
|
||||||
# python ./unitest.py
|
# python ./unittest-core.py
|
||||||
|
22
.pre-commit-config.yaml
Normal file
22
.pre-commit-config.yaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: check-ast
|
||||||
|
- id: check-docstring-first
|
||||||
|
- id: check-json
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: check-shebang-scripts-are-executable
|
||||||
|
- id: check-toml
|
||||||
|
- id: check-yaml
|
||||||
|
- id: debug-statements
|
||||||
|
- id: detect-private-key
|
||||||
|
- id: mixed-line-ending
|
||||||
|
- id: requirements-txt-fixer
|
||||||
|
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.4.4
|
||||||
|
hooks:
|
||||||
|
- id: ruff-format
|
||||||
|
- id: ruff
|
||||||
|
args: [--fix, --exit-non-zero-on-fix]
|
34
.readthedocs.yaml
Normal file
34
.readthedocs.yaml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Read the Docs configuration file for Glances projects
|
||||||
|
|
||||||
|
# Required
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
# Set the OS, Python version and other tools you might need
|
||||||
|
build:
|
||||||
|
os: ubuntu-22.04
|
||||||
|
tools:
|
||||||
|
python: "3.12"
|
||||||
|
# You can also specify other tool versions:
|
||||||
|
# nodejs: "20"
|
||||||
|
# rust: "1.70"
|
||||||
|
# golang: "1.20"
|
||||||
|
|
||||||
|
# Build documentation in the "docs/" directory with Sphinx
|
||||||
|
sphinx:
|
||||||
|
configuration: docs/conf.py
|
||||||
|
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
|
||||||
|
# builder: "dirhtml"
|
||||||
|
# Fail on all warnings to avoid broken references
|
||||||
|
# fail_on_warning: true
|
||||||
|
|
||||||
|
# Optionally build your docs in additional formats such as PDF and ePub
|
||||||
|
# formats:
|
||||||
|
# - pdf
|
||||||
|
# - epub
|
||||||
|
|
||||||
|
# Optional but recommended, declare the Python requirements required
|
||||||
|
# to build your documentation
|
||||||
|
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- requirements: doc-requirements.txt
|
4
AUTHORS
4
AUTHORS
@ -64,3 +64,7 @@ http://www.macports.org/ports.php?by=name&substr=glances
|
|||||||
|
|
||||||
John Kirkham for the conda package (at conda-forge)
|
John Kirkham for the conda package (at conda-forge)
|
||||||
https://github.com/conda-forge/glances-feedstock
|
https://github.com/conda-forge/glances-feedstock
|
||||||
|
|
||||||
|
Rui Chen for the Homebrew package
|
||||||
|
https://chenrui.dev/
|
||||||
|
https://formulae.brew.sh/formula/glances
|
||||||
|
@ -6,7 +6,7 @@ In the interest of fostering an open and welcoming environment, we as
|
|||||||
contributors and maintainers pledge to making participation in our project and
|
contributors and maintainers pledge to making participation in our project and
|
||||||
our community a harassment-free experience for everyone, regardless of age, body
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||||
education, socio-economic status, nationality, personal appearance, race,
|
education, socioeconomic status, nationality, personal appearance, race,
|
||||||
religion, or sexual identity and orientation.
|
religion, or sexual identity and orientation.
|
||||||
|
|
||||||
## Our Standards
|
## Our Standards
|
||||||
|
@ -10,7 +10,6 @@ the developers managing and developing this open source project. In return,
|
|||||||
they should reciprocate that respect in addressing your issue or assessing
|
they should reciprocate that respect in addressing your issue or assessing
|
||||||
patches and features.
|
patches and features.
|
||||||
|
|
||||||
|
|
||||||
## Using the issue tracker
|
## Using the issue tracker
|
||||||
|
|
||||||
The [issue tracker](https://github.com/nicolargo/glances/issues) is
|
The [issue tracker](https://github.com/nicolargo/glances/issues) is
|
||||||
@ -24,7 +23,6 @@ restrictions:
|
|||||||
* Please **do not** derail or troll issues. Keep the discussion on topic and
|
* Please **do not** derail or troll issues. Keep the discussion on topic and
|
||||||
respect the opinions of others.
|
respect the opinions of others.
|
||||||
|
|
||||||
|
|
||||||
## Bug reports
|
## Bug reports
|
||||||
|
|
||||||
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||||
@ -65,19 +63,17 @@ Example:
|
|||||||
> causing the bug, and potential solutions (and your opinions on their
|
> causing the bug, and potential solutions (and your opinions on their
|
||||||
> merits).
|
> merits).
|
||||||
>
|
>
|
||||||
> You can also run Glances in debug mode (-d) and paste/bin the glances.conf file (https://glances.readthedocs.io/en/latest/config.html).
|
> You can also run Glances in debug mode (-d) and paste/bin the glances.conf file (<https://glances.readthedocs.io/en/latest/config.html>).
|
||||||
>
|
>
|
||||||
> Glances 3.2.0 or higher have also a --issue option to run a simple test. Please use it and copy/paste the output.
|
> Glances 3.2.0 or higher have also a --issue option to run a simple test. Please use it and copy/paste the output.
|
||||||
|
|
||||||
|
|
||||||
## Feature requests
|
## Feature requests
|
||||||
|
|
||||||
Feature requests are welcome. But take a moment to find out whether your idea
|
Feature requests are welcome. But take a moment to find out whether your idea
|
||||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
fits with the scope and aims of the project. It's up to _you* to make a strong
|
||||||
case to convince the project's developers of the merits of this feature. Please
|
case to convince the project's developers of the merits of this feature. Please
|
||||||
provide as much detail and context as possible.
|
provide as much detail and context as possible.
|
||||||
|
|
||||||
|
|
||||||
## Pull requests
|
## Pull requests
|
||||||
|
|
||||||
Good pull requests—patches, improvements, new features—are a fantastic
|
Good pull requests—patches, improvements, new features—are a fantastic
|
||||||
@ -133,6 +129,7 @@ included in the project:
|
|||||||
|
|
||||||
5. Test you code using the Makefile:
|
5. Test you code using the Makefile:
|
||||||
|
|
||||||
|
* make format ==> Format your code thanks to the Ruff linter
|
||||||
* make run ==> Run Glances
|
* make run ==> Run Glances
|
||||||
* make run-webserver ==> Run a Glances Web Server
|
* make run-webserver ==> Run a Glances Web Server
|
||||||
* make test ==> Run unit tests
|
* make test ==> Run unit tests
|
||||||
|
@ -3,7 +3,9 @@ include CONTRIBUTING.md
|
|||||||
include COPYING
|
include COPYING
|
||||||
include NEWS.rst
|
include NEWS.rst
|
||||||
include README.rst
|
include README.rst
|
||||||
|
include SECURITY.md
|
||||||
include conf/glances.conf
|
include conf/glances.conf
|
||||||
|
include requirements.txt
|
||||||
recursive-include docs *
|
recursive-include docs *
|
||||||
recursive-include glances *.py
|
recursive-include glances *.py
|
||||||
recursive-include glances/outputs/static *
|
recursive-include glances/outputs/static *
|
||||||
|
46
Makefile
46
Makefile
@ -52,6 +52,7 @@ venv-dev-python: ## Install Python 3 venv
|
|||||||
venv-dev: venv-python ## Install Python 3 dev dependencies
|
venv-dev: venv-python ## Install Python 3 dev dependencies
|
||||||
./venv-dev/bin/pip install -r dev-requirements.txt
|
./venv-dev/bin/pip install -r dev-requirements.txt
|
||||||
./venv-dev/bin/pip install -r doc-requirements.txt
|
./venv-dev/bin/pip install -r doc-requirements.txt
|
||||||
|
./venv-dev/bin/pre-commit install --hook-type pre-commit
|
||||||
|
|
||||||
venv-dev-upgrade: ## Upgrade Python 3 dev dependencies
|
venv-dev-upgrade: ## Upgrade Python 3 dev dependencies
|
||||||
./venv-dev/bin/pip install --upgrade pip
|
./venv-dev/bin/pip install --upgrade pip
|
||||||
@ -62,44 +63,37 @@ venv-dev-upgrade: ## Upgrade Python 3 dev dependencies
|
|||||||
# Tests
|
# Tests
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
test: ## Run unit tests
|
test-core: ## Run core unit tests
|
||||||
./venv/bin/python ./unitest.py
|
./venv/bin/python ./unittest-core.py
|
||||||
./venv/bin/python ./unitest-restful.py
|
|
||||||
./venv/bin/python ./unitest-xmlrpc.py
|
|
||||||
./venv-dev/bin/python -m black ./glances --check --exclude outputs/static
|
|
||||||
|
|
||||||
test-with-upgrade: venv-upgrade venv-dev-upgrade ## Upgrade deps and run unit tests
|
test-restful: ## Run Restful unit tests
|
||||||
./venv/bin/python ./unitest.py
|
./venv/bin/python ./unittest-restful.py
|
||||||
./venv/bin/python ./unitest-restful.py
|
|
||||||
./venv/bin/python ./unitest-xmlrpc.py
|
|
||||||
./venv/bin-dev/python -m black ./glances --check --exclude outputs/static
|
|
||||||
|
|
||||||
test-min: ## Run unit tests in minimal environment
|
test-xmlrpc: ## Run XMLRPC unit tests
|
||||||
./venv-min/bin/python ./unitest.py
|
./venv/bin/python ./unittest-xmlrpc.py
|
||||||
|
|
||||||
|
test: test-core test-restful test-xmlrpc ## Run unit tests
|
||||||
|
|
||||||
|
test-with-upgrade: venv-upgrade venv-dev-upgrade test ## Upgrade deps and run unit tests
|
||||||
|
|
||||||
|
test-min: ## Run core unit tests in minimal environment
|
||||||
|
./venv-min/bin/python ./unittest-core.py
|
||||||
|
|
||||||
test-min-with-upgrade: venv-min-upgrade ## Upgrade deps and run unit tests in minimal environment
|
test-min-with-upgrade: venv-min-upgrade ## Upgrade deps and run unit tests in minimal environment
|
||||||
./venv-min/bin/python ./unitest.py
|
./venv-min/bin/python ./unittest-core.py
|
||||||
|
|
||||||
test-restful-api: ## Run unit tests of the RESTful API
|
|
||||||
./venv/bin/python ./unitest-restful.py
|
|
||||||
|
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
# Linters, profilers and cyber security
|
# Linters, profilers and cyber security
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
format: ## Format the code
|
format: ## Format the code
|
||||||
@git ls-files 'glances/*.py' | xargs ./venv-dev/bin/python -m autopep8 --in-place --jobs 0 --global-config=.flake8
|
./venv-dev/bin/python -m ruff format .
|
||||||
@git ls-files 'glances/*.py' | xargs ./venv-dev/bin/python -m autoflake --in-place --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys --exclude="compat.py,globals.py"
|
|
||||||
./venv-dev/bin/python -m black ./glances --exclude outputs/static
|
|
||||||
|
|
||||||
flake8: ## Run flake8 linter.
|
lint: ## Lint the code.
|
||||||
@git ls-files 'glances/ *.py' | xargs ./venv-dev/bin/python -m flake8 --config=.flake8
|
./venv-dev/bin/python -m ruff check . --fix
|
||||||
|
|
||||||
ruff: ## Run Ruff (fastest) linter.
|
|
||||||
./venv-dev/bin/python -m ruff check . --config=./pyproject.toml
|
|
||||||
|
|
||||||
codespell: ## Run codespell to fix common misspellings in text files
|
codespell: ## Run codespell to fix common misspellings in text files
|
||||||
./venv-dev/bin/codespell -S .git,./docs/_build,./Glances.egg-info,./venv*,./glances/outputs,*.svg -L hart,bu,te,statics
|
./venv-dev/bin/codespell -S .git,./docs/_build,./Glances.egg-info,./venv*,./glances/outputs,*.svg -L hart,bu,te,statics -w
|
||||||
|
|
||||||
semgrep: ## Run semgrep to find bugs and enforce code standards
|
semgrep: ## Run semgrep to find bugs and enforce code standards
|
||||||
./venv-dev/bin/semgrep scan --config=auto
|
./venv-dev/bin/semgrep scan --config=auto
|
||||||
@ -191,7 +185,7 @@ flatpak: venv-dev-upgrade ## Generate FlatPack JSON file
|
|||||||
rm -rf ./flatpak-builder-tools
|
rm -rf ./flatpak-builder-tools
|
||||||
@echo "Now follow: https://github.com/flathub/flathub/wiki/App-Submission"
|
@echo "Now follow: https://github.com/flathub/flathub/wiki/App-Submission"
|
||||||
|
|
||||||
# Snap package is automaticaly build on the Snapcraft.io platform
|
# Snap package is automatically build on the Snapcraft.io platform
|
||||||
# https://snapcraft.io/glances
|
# https://snapcraft.io/glances
|
||||||
# But you can try an offline build with the following command
|
# But you can try an offline build with the following command
|
||||||
snapcraft:
|
snapcraft:
|
||||||
|
150
NEWS.rst
150
NEWS.rst
@ -2,11 +2,124 @@
|
|||||||
Glances ChangeLog
|
Glances ChangeLog
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.2.0
|
||||||
|
===============
|
||||||
|
|
||||||
|
Under development, see roadmap here: https://github.com/nicolargo/glances/milestone/73
|
||||||
|
|
||||||
|
Contributors are welcome !
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.1.1
|
||||||
|
===============
|
||||||
|
|
||||||
|
Bug corrected:
|
||||||
|
|
||||||
|
* Sensors data is not exported using InfluxDB2 exporter #2856
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.1.0
|
||||||
|
===============
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
|
||||||
|
* Call process_iter.clear_cache() (PsUtil 6+) when Glances user force a refresh (F5 or CTRL-R) #2753
|
||||||
|
* PsUtil 6+ no longer check PID reused #2755
|
||||||
|
* Add support for automatically hiding network interfaces that are down or that don't have any IP addresses #2799
|
||||||
|
|
||||||
|
Bug corrected:
|
||||||
|
|
||||||
|
* API: Network module is disabled but appears in endpoint "all" #2815
|
||||||
|
* API is not compatible with requests containing special/encoding char #2820
|
||||||
|
* 'j' hot key crashes Glances #2831
|
||||||
|
* Raspberry PI - CPU info is not correct #2616
|
||||||
|
* Graph export is broken if there is no graph section in Glances configuration file #2839
|
||||||
|
* Glances API status check returns Error 405 - Method Not Allowed #2841
|
||||||
|
* Rootless podman containers cause glances to fail with KeyError #2827
|
||||||
|
* --export-process-filter Filter using complete command #2824
|
||||||
|
* Exception when Glances is ran with limited plugin list #2822
|
||||||
|
* Disable separator option do not work #2823
|
||||||
|
|
||||||
|
Continuous integration and documentation:
|
||||||
|
|
||||||
|
* test test_107_fs_plugin_method fails on aarch64-linux #2819
|
||||||
|
|
||||||
|
Thanks to all contributors and bug reporters !
|
||||||
|
|
||||||
|
Special thanks to:
|
||||||
|
|
||||||
|
* Bharath Vignesh J K
|
||||||
|
* RazCrimson
|
||||||
|
* Vadim Small
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.0.8
|
||||||
|
===============
|
||||||
|
|
||||||
|
* Make CORS option configurable security webui #2812
|
||||||
|
* When Glances is installed via venv, default configuration file is not used documentation packaging #2803
|
||||||
|
* GET /1272f6e9e8f9d6bfd6de.png results in 404 bug webui #2781 by Emporea was closed May 25, 2024
|
||||||
|
* Screen frequently flickers when outputting to local display bug needs test #2490
|
||||||
|
* Retire ujson for being in maintenance mode dependencies enhancement #2791
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.0.7
|
||||||
|
===============
|
||||||
|
|
||||||
|
* cpu_hz_current not available on NetBSD #2792
|
||||||
|
* SensorType change in REST API breaks compatibility in 4.0.4 #2788
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.0.6
|
||||||
|
===============
|
||||||
|
|
||||||
|
* No GPU info on Web View #2796
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.0.5
|
||||||
|
===============
|
||||||
|
|
||||||
|
* SensorType change in REST API breaks compatibility in 4.0.4 #2788
|
||||||
|
* Please make pydantic optional dependency, not required one #2777
|
||||||
|
* Update the Grafana dashboard #2780
|
||||||
|
* 4.0.4 - On Glances startup "ERROR -- Can not init battery class #2776
|
||||||
|
* In codeSpace (with Python 3.8), an error occurs in ./unittest-restful.py #2773
|
||||||
|
|
||||||
|
Use Ruff as default Linter.
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.0.4
|
||||||
|
===============
|
||||||
|
|
||||||
|
Hostfix release for support sensors plugin on python 3.8
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.0.3
|
||||||
|
===============
|
||||||
|
|
||||||
|
Additional fixes for Sensor plugin
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.0.2
|
||||||
|
===============
|
||||||
|
|
||||||
|
* hotfix: plugin(sensors) - race conditions btw fan_speed & temperature… #2766
|
||||||
|
* fix: include requirements.txt and SECURITY.md for pypi dist #2761
|
||||||
|
|
||||||
|
Thanks to RazCrimson for the sensors patch !
|
||||||
|
|
||||||
|
===============
|
||||||
|
Version 4.0.1
|
||||||
|
===============
|
||||||
|
|
||||||
|
Correct issue with CI (miss pydantic dep).
|
||||||
|
|
||||||
===============
|
===============
|
||||||
Version 4.0.0
|
Version 4.0.0
|
||||||
===============
|
===============
|
||||||
|
|
||||||
See release note in Wiki format here: https://github.com/nicolargo/glances/wiki/Glances-4.0-Release-Note
|
See release note in Wiki format: https://github.com/nicolargo/glances/wiki/Glances-4.0-Release-Note
|
||||||
|
|
||||||
**BREAKING CHANGES:**
|
**BREAKING CHANGES:**
|
||||||
|
|
||||||
@ -27,6 +140,26 @@ Glances version 4.x and higher:
|
|||||||
* Alert data model change from a list of list to a list of dict #2633
|
* Alert data model change from a list of list to a list of dict #2633
|
||||||
* Docker memory usage uses the same algorithm than docker stats #2637
|
* Docker memory usage uses the same algorithm than docker stats #2637
|
||||||
|
|
||||||
|
Special notes for package maintainers:
|
||||||
|
|
||||||
|
Minimal requirements for Glances version 4 are:
|
||||||
|
|
||||||
|
* psutil
|
||||||
|
* defusedxml
|
||||||
|
* packaging
|
||||||
|
* ujson
|
||||||
|
* pydantic
|
||||||
|
* fastapi (for WebUI / RestFull API)
|
||||||
|
* uvicorn (for WebUI / RestFull API)
|
||||||
|
* jinja2 (for WebUI / RestFull API)
|
||||||
|
|
||||||
|
Majors changes between Glances version 3 and version 4:
|
||||||
|
|
||||||
|
* Bottle has been replaced by FastAPI and Uvicorn
|
||||||
|
* CouchDB has been replaced by PyCouchDB
|
||||||
|
* nvidia-ml-py has been replaced by py3nvml
|
||||||
|
* pysnmp has been replaced by pysnmp-lextudio
|
||||||
|
|
||||||
Enhancements:
|
Enhancements:
|
||||||
|
|
||||||
* Export individual processes stats #794
|
* Export individual processes stats #794
|
||||||
@ -48,6 +181,7 @@ Enhancements:
|
|||||||
* [WebUI] Added smart plugin support #2435
|
* [WebUI] Added smart plugin support #2435
|
||||||
* No more threshold display in the WebUI cpu/mem and memswap plugins #2420
|
* No more threshold display in the WebUI cpu/mem and memswap plugins #2420
|
||||||
* Refactor Glances curses code #2580
|
* Refactor Glances curses code #2580
|
||||||
|
* Hide password in the Glances browser form #503
|
||||||
* Replace Bottle by FastAPI #2181
|
* Replace Bottle by FastAPI #2181
|
||||||
* Replace py3nvml with nvidia-ml-py #2688
|
* Replace py3nvml with nvidia-ml-py #2688
|
||||||
|
|
||||||
@ -92,7 +226,7 @@ Many thinks to the contributors:
|
|||||||
* Christoph Zimmermann
|
* Christoph Zimmermann
|
||||||
* RazCrimson
|
* RazCrimson
|
||||||
* Robin Candau
|
* Robin Candau
|
||||||
* Github GPG acces
|
* Github GPG access
|
||||||
* Continuous Integration
|
* Continuous Integration
|
||||||
* Georgiy Timchenko
|
* Georgiy Timchenko
|
||||||
* turbocrime
|
* turbocrime
|
||||||
@ -266,7 +400,7 @@ Documentation and CI:
|
|||||||
* Update Makefile with comments
|
* Update Makefile with comments
|
||||||
* Update Python minimal requirement for py3nvlm
|
* Update Python minimal requirement for py3nvlm
|
||||||
* Update security policy (user can open private issue directly in Github)
|
* Update security policy (user can open private issue directly in Github)
|
||||||
* Add a simple run script. Entry point for IDE debuger
|
* Add a simple run script. Entry point for IDE debugger
|
||||||
|
|
||||||
Cyber security update:
|
Cyber security update:
|
||||||
|
|
||||||
@ -295,7 +429,7 @@ And also a big thanks to @RazCrimson (https://github.com/RazCrimson) for the sup
|
|||||||
Version 3.3.0.4
|
Version 3.3.0.4
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Refactor the Docker images factory, from now, only Alpine image wll be provided.
|
Refactor the Docker images factory, from now, only Alpine image will be provided.
|
||||||
|
|
||||||
The following Docker images (nicolargo/glances) are availables:
|
The following Docker images (nicolargo/glances) are availables:
|
||||||
|
|
||||||
@ -343,9 +477,9 @@ Bug corrected:
|
|||||||
* Correct issue with the regexp filter (use fullmatch instead of match)
|
* Correct issue with the regexp filter (use fullmatch instead of match)
|
||||||
* Errors when running Glances as web service #1702
|
* Errors when running Glances as web service #1702
|
||||||
* Apply alias to Duplicate sensor name #1686
|
* Apply alias to Duplicate sensor name #1686
|
||||||
* Make the hide function in sensors section compliant with lower/upercase #1590
|
* Make the hide function in sensors section compliant with lower/uppercase #1590
|
||||||
* Web UI truncates the days part of CPU time counter of the process list #2108
|
* Web UI truncates the days part of CPU time counter of the process list #2108
|
||||||
* Correct alignement issue with the diskio plugin (Console UI)
|
* Correct alignment issue with the diskio plugin (Console UI)
|
||||||
|
|
||||||
Documentation and CI:
|
Documentation and CI:
|
||||||
|
|
||||||
@ -612,7 +746,7 @@ Bugs corrected:
|
|||||||
* Docker containers information missing with Docker 20.10.x #1878
|
* Docker containers information missing with Docker 20.10.x #1878
|
||||||
* Get system sensors temperatures thresholds #1864
|
* Get system sensors temperatures thresholds #1864
|
||||||
|
|
||||||
Contibutors for this version:
|
Contributors for this version:
|
||||||
|
|
||||||
* Nicolargo
|
* Nicolargo
|
||||||
* Markus Pöschl
|
* Markus Pöschl
|
||||||
@ -1219,7 +1353,7 @@ Enhancements and new features:
|
|||||||
* Add ZeroMQ exporter (issue #939)
|
* Add ZeroMQ exporter (issue #939)
|
||||||
* Add CouchDB exporter (issue #928)
|
* Add CouchDB exporter (issue #928)
|
||||||
* Add hotspot Wifi information (issue #937)
|
* Add hotspot Wifi information (issue #937)
|
||||||
* Add default interface speed and automatic rate thresolds (issue #718)
|
* Add default interface speed and automatic rate thresholds (issue #718)
|
||||||
* Highlight max stats in the processes list (issue #878)
|
* Highlight max stats in the processes list (issue #878)
|
||||||
* Docker alerts and actions (issue #875)
|
* Docker alerts and actions (issue #875)
|
||||||
* Glances API returns the processes PPID (issue #926)
|
* Glances API returns the processes PPID (issue #926)
|
||||||
|
15
README.rst
15
README.rst
@ -2,7 +2,7 @@
|
|||||||
Glances - An eye on your system
|
Glances - An eye on your system
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
| |pypi| |test| |contibutors| |quality|
|
| |pypi| |test| |contributors| |quality|
|
||||||
| |starts| |docker| |pypistat|
|
| |starts| |docker| |pypistat|
|
||||||
| |sponsors| |twitter|
|
| |sponsors| |twitter|
|
||||||
|
|
||||||
@ -25,9 +25,9 @@ Glances - An eye on your system
|
|||||||
:target: https://github.com/nicolargo/glances/actions
|
:target: https://github.com/nicolargo/glances/actions
|
||||||
:alt: Linux tests (GitHub Actions)
|
:alt: Linux tests (GitHub Actions)
|
||||||
|
|
||||||
.. |contibutors| image:: https://img.shields.io/github/contributors/nicolargo/glances
|
.. |contributors| image:: https://img.shields.io/github/contributors/nicolargo/glances
|
||||||
:target: https://github.com/nicolargo/glances/issues?q=is%3Aissue+is%3Aopen+label%3A%22needs+contributor%22
|
:target: https://github.com/nicolargo/glances/issues?q=is%3Aissue+is%3Aopen+label%3A%22needs+contributor%22
|
||||||
:alt: Contibutors
|
:alt: Contributors
|
||||||
|
|
||||||
.. |quality| image:: https://scrutinizer-ci.com/g/nicolargo/glances/badges/quality-score.png?b=develop
|
.. |quality| image:: https://scrutinizer-ci.com/g/nicolargo/glances/badges/quality-score.png?b=develop
|
||||||
:target: https://scrutinizer-ci.com/g/nicolargo/glances/?branch=develop
|
:target: https://scrutinizer-ci.com/g/nicolargo/glances/?branch=develop
|
||||||
@ -86,8 +86,7 @@ Requirements
|
|||||||
- ``psutil`` (better with latest version)
|
- ``psutil`` (better with latest version)
|
||||||
- ``defusedxml`` (in order to monkey patch xmlrpc)
|
- ``defusedxml`` (in order to monkey patch xmlrpc)
|
||||||
- ``packaging`` (for the version comparison)
|
- ``packaging`` (for the version comparison)
|
||||||
- ``ujson`` (an optimized alternative to the standard json module)
|
- ``orjson`` (an optimized alternative to the standard json module)
|
||||||
- ``pydantic`` (for the data validation support)
|
|
||||||
|
|
||||||
*Note for Python 2 users*
|
*Note for Python 2 users*
|
||||||
|
|
||||||
@ -110,14 +109,12 @@ Optional dependencies:
|
|||||||
- ``jinja2`` (for templating, used under the hood by FastAPI)
|
- ``jinja2`` (for templating, used under the hood by FastAPI)
|
||||||
- ``kafka-python`` (for the Kafka export module)
|
- ``kafka-python`` (for the Kafka export module)
|
||||||
- ``netifaces`` (for the IP plugin)
|
- ``netifaces`` (for the IP plugin)
|
||||||
- ``orjson`` (fast JSON library, used under the hood by FastAPI)
|
|
||||||
- ``nvidia-ml-py`` (for the GPU plugin)
|
- ``nvidia-ml-py`` (for the GPU plugin)
|
||||||
- ``pycouchdb`` (for the CouchDB export module)
|
- ``pycouchdb`` (for the CouchDB export module)
|
||||||
- ``pika`` (for the RabbitMQ/ActiveMQ export module)
|
- ``pika`` (for the RabbitMQ/ActiveMQ export module)
|
||||||
- ``podman`` (for the Containers Podman monitoring support)
|
- ``podman`` (for the Containers Podman monitoring support)
|
||||||
- ``potsdb`` (for the OpenTSDB export module)
|
- ``potsdb`` (for the OpenTSDB export module)
|
||||||
- ``prometheus_client`` (for the Prometheus export module)
|
- ``prometheus_client`` (for the Prometheus export module)
|
||||||
- ``py-cpuinfo`` (for the Quicklook CPU info module)
|
|
||||||
- ``pygal`` (for the graph export module)
|
- ``pygal`` (for the graph export module)
|
||||||
- ``pymdstat`` (for RAID support) [Linux-only]
|
- ``pymdstat`` (for RAID support) [Linux-only]
|
||||||
- ``pymongo`` (for the MongoDB export module)
|
- ``pymongo`` (for the MongoDB export module)
|
||||||
@ -144,7 +141,7 @@ stable version.
|
|||||||
To install Glances, simply use the ``pip`` command line.
|
To install Glances, simply use the ``pip`` command line.
|
||||||
|
|
||||||
Warning: on modern Linux operating systems, you may have an externally-managed-environment
|
Warning: on modern Linux operating systems, you may have an externally-managed-environment
|
||||||
error message when you try to use ``pip``. In this case, go to the the PipX section bellow.
|
error message when you try to use ``pip``. In this case, go to the the PipX section below.
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
@ -325,7 +322,7 @@ Start Termux on your device and enter:
|
|||||||
$ apt update
|
$ apt update
|
||||||
$ apt upgrade
|
$ apt upgrade
|
||||||
$ apt install clang python
|
$ apt install clang python
|
||||||
$ pip install fastapi uvicorn orjson jinja2
|
$ pip install fastapi uvicorn jinja2
|
||||||
$ pip install glances
|
$ pip install glances
|
||||||
|
|
||||||
And start Glances:
|
And start Glances:
|
||||||
|
@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
Use this section to tell people about which versions of your project are
|
|
||||||
currently being supported with security updates.
|
|
||||||
|
|
||||||
| Version | Support security updates |
|
| Version | Support security updates |
|
||||||
| ------- | ------------------------ |
|
| ------- | ------------------------ |
|
||||||
| 3.x | :white_check_mark: |
|
| 4.x | :white_check_mark: |
|
||||||
| < 3.0 | :x: |
|
| < 4.0 | :x: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
@ -31,4 +28,3 @@ If there are any vulnerabilities in {{cookiecutter.project_name}}, don't hesitat
|
|||||||
4. Please do not disclose the vulnerability publicly until a fix is released!
|
4. Please do not disclose the vulnerability publicly until a fix is released!
|
||||||
|
|
||||||
Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it.
|
Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it.
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -23,12 +23,16 @@ history_size=1200
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
[outputs]
|
[outputs]
|
||||||
|
# Options for all UIs
|
||||||
|
#--------------------
|
||||||
# Separator in the Curses and WebUI interface (between top and others plugins)
|
# Separator in the Curses and WebUI interface (between top and others plugins)
|
||||||
separator=True
|
#separator=True
|
||||||
# Set the the Curses and WebUI interface left menu plugin list (comma-separated)
|
# Set the the Curses and WebUI interface left menu plugin list (comma-separated)
|
||||||
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
|
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
|
||||||
# Limit the number of processes to display (in the WebUI)
|
# Limit the number of processes to display (in the WebUI)
|
||||||
max_processes_display=25
|
max_processes_display=25
|
||||||
|
# Options for WebUI
|
||||||
|
#------------------
|
||||||
# Set URL prefix for the WebUI and the API
|
# Set URL prefix for the WebUI and the API
|
||||||
# Example: url_prefix=/glances/ => http://localhost/glances/
|
# Example: url_prefix=/glances/ => http://localhost/glances/
|
||||||
# Note: The final / is mandatory
|
# Note: The final / is mandatory
|
||||||
@ -41,9 +45,22 @@ max_processes_display=25
|
|||||||
# then configure this folder with the webui_root_path key
|
# then configure this folder with the webui_root_path key
|
||||||
# Default is folder where glances_restfull_api.py is hosted
|
# Default is folder where glances_restfull_api.py is hosted
|
||||||
#webui_root_path=
|
#webui_root_path=
|
||||||
|
# CORS options
|
||||||
|
# Comma separated list of origins that should be permitted to make cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_origins=*
|
||||||
|
# Indicate that cookies should be supported for cross-origin requests.
|
||||||
|
# Default is True
|
||||||
|
#cors_credentials=True
|
||||||
|
# Comma separated list of HTTP methods that should be allowed for cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_methods=*
|
||||||
|
# Comma separated list of HTTP request headers that should be supported for cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_headers=*
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# plugins
|
# Plugins
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
[quicklook]
|
[quicklook]
|
||||||
@ -199,6 +216,10 @@ tx_critical=90
|
|||||||
hide=docker.*,lo
|
hide=docker.*,lo
|
||||||
# Define the list of wireless network interfaces to be show (comma-separated)
|
# Define the list of wireless network interfaces to be show (comma-separated)
|
||||||
#show=docker.*
|
#show=docker.*
|
||||||
|
# Automatically hide interface not up (default is False)
|
||||||
|
hide_no_up=True
|
||||||
|
# Automatically hide interface with no IP address (default is False)
|
||||||
|
hide_no_ip=True
|
||||||
# It is possible to overwrite the bitrate thresholds per interface
|
# It is possible to overwrite the bitrate thresholds per interface
|
||||||
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
|
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
|
||||||
#wlan0_rx_careful=4000000
|
#wlan0_rx_careful=4000000
|
||||||
@ -317,7 +338,7 @@ disable=True
|
|||||||
[raid]
|
[raid]
|
||||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/raid.html
|
# Documentation: https://glances.readthedocs.io/en/latest/aoa/raid.html
|
||||||
# This plugin is disabled by default
|
# This plugin is disabled by default
|
||||||
disable=False
|
disable=True
|
||||||
|
|
||||||
[smart]
|
[smart]
|
||||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html
|
# Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html
|
||||||
@ -354,6 +375,8 @@ temperature_hdd_critical=60
|
|||||||
battery_careful=80
|
battery_careful=80
|
||||||
battery_warning=90
|
battery_warning=90
|
||||||
battery_critical=95
|
battery_critical=95
|
||||||
|
# Fan speed threshold in RPM
|
||||||
|
#fan_speed_careful=100
|
||||||
# Sensors alias
|
# Sensors alias
|
||||||
#alias=core 0:CPU Core 0,core 1:CPU Core 1
|
#alias=core 0:CPU Core 0,core 1:CPU Core 1
|
||||||
|
|
||||||
@ -763,13 +786,13 @@ refresh=3
|
|||||||
countmax=20
|
countmax=20
|
||||||
|
|
||||||
[amp_conntrack]
|
[amp_conntrack]
|
||||||
# Use comma separated for multiple commands (no space around the comma)
|
# Use && separator for multiple commands
|
||||||
# If the regex key is not defined, the AMP will be executed every refresh second
|
# If the regex key is not defined, the AMP will be executed every refresh second
|
||||||
# and the process count will not be displayed (countmin and countmax will be ignore)
|
# and the process count will not be displayed (countmin and countmax will be ignore)
|
||||||
enable=false
|
enable=false
|
||||||
refresh=30
|
refresh=30
|
||||||
one_line=false
|
one_line=false
|
||||||
command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max
|
command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max
|
||||||
|
|
||||||
[amp_nginx]
|
[amp_nginx]
|
||||||
# Use the NGinx AMP
|
# Use the NGinx AMP
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
py-spy
|
|
||||||
gprof2dot
|
|
||||||
black
|
|
||||||
pyright
|
|
||||||
requirements-parser
|
|
||||||
flake8
|
|
||||||
autopep8
|
|
||||||
autoflake
|
|
||||||
ruff
|
|
||||||
codespell
|
codespell
|
||||||
memory-profiler
|
fonttools>=4.43.0 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||||
|
gprof2dot
|
||||||
matplotlib
|
matplotlib
|
||||||
semgrep
|
memory-profiler
|
||||||
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
|
||||||
numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability
|
numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||||
pillow>=10.0.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
pillow>=10.0.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||||
fonttools>=4.43.0 # not directly required, pinned by Snyk to avoid a vulnerability
|
pre-commit
|
||||||
|
py-spy
|
||||||
|
pyright
|
||||||
|
requirements-parser
|
||||||
|
ruff
|
||||||
|
semgrep
|
||||||
|
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
psutil
|
||||||
|
defusedxml
|
||||||
|
orjson
|
||||||
|
reuse
|
||||||
|
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||||
sphinx
|
sphinx
|
||||||
sphinx_rtd_theme
|
sphinx_rtd_theme
|
||||||
ujson
|
|
||||||
reuse
|
|
||||||
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
|
@ -27,7 +27,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
- "GLANCES_OPT=-C /glances/conf/glances.conf -w"
|
- "GLANCES_OPT=-C /glances/conf/glances.conf -w"
|
||||||
# Uncomment for GPU compatibilty (Nvidia) inside the container
|
# Uncomment for GPU compatibility (Nvidia) inside the container
|
||||||
# deploy:
|
# deploy:
|
||||||
# resources:
|
# resources:
|
||||||
# reservations:
|
# reservations:
|
||||||
|
@ -15,7 +15,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
- "GLANCES_OPT=-C /glances/conf/glances.conf -w"
|
- "GLANCES_OPT=-C /glances/conf/glances.conf -w"
|
||||||
# Uncomment for GPU compatibilty (Nvidia) inside the container
|
# Uncomment for GPU compatibility (Nvidia) inside the container
|
||||||
# deploy:
|
# deploy:
|
||||||
# resources:
|
# resources:
|
||||||
# reservations:
|
# reservations:
|
||||||
|
@ -23,12 +23,16 @@ history_size=1200
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
[outputs]
|
[outputs]
|
||||||
|
# Options for all UIs
|
||||||
|
#--------------------
|
||||||
# Separator in the Curses and WebUI interface (between top and others plugins)
|
# Separator in the Curses and WebUI interface (between top and others plugins)
|
||||||
separator=True
|
#separator=True
|
||||||
# Set the the Curses and WebUI interface left menu plugin list (comma-separated)
|
# Set the the Curses and WebUI interface left menu plugin list (comma-separated)
|
||||||
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
|
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
|
||||||
# Limit the number of processes to display (in the WebUI)
|
# Limit the number of processes to display (in the WebUI)
|
||||||
max_processes_display=25
|
max_processes_display=25
|
||||||
|
# Options for WebUI
|
||||||
|
#------------------
|
||||||
# Set URL prefix for the WebUI and the API
|
# Set URL prefix for the WebUI and the API
|
||||||
# Example: url_prefix=/glances/ => http://localhost/glances/
|
# Example: url_prefix=/glances/ => http://localhost/glances/
|
||||||
# Note: The final / is mandatory
|
# Note: The final / is mandatory
|
||||||
@ -41,9 +45,22 @@ max_processes_display=25
|
|||||||
# then configure this folder with the webui_root_path key
|
# then configure this folder with the webui_root_path key
|
||||||
# Default is folder where glances_restfull_api.py is hosted
|
# Default is folder where glances_restfull_api.py is hosted
|
||||||
#webui_root_path=
|
#webui_root_path=
|
||||||
|
# CORS options
|
||||||
|
# Comma separated list of origins that should be permitted to make cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_origins=*
|
||||||
|
# Indicate that cookies should be supported for cross-origin requests.
|
||||||
|
# Default is True
|
||||||
|
#cors_credentials=True
|
||||||
|
# Comma separated list of HTTP methods that should be allowed for cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_methods=*
|
||||||
|
# Comma separated list of HTTP request headers that should be supported for cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_headers=*
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# plugins
|
# Plugins
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
[quicklook]
|
[quicklook]
|
||||||
@ -199,6 +216,10 @@ tx_critical=90
|
|||||||
#hide=docker.*,lo
|
#hide=docker.*,lo
|
||||||
# Define the list of wireless network interfaces to be show (comma-separated)
|
# Define the list of wireless network interfaces to be show (comma-separated)
|
||||||
#show=docker.*
|
#show=docker.*
|
||||||
|
# Automatically hide interface not up (default is False)
|
||||||
|
hide_no_up=True
|
||||||
|
# Automatically hide interface with no IP address (default is False)
|
||||||
|
hide_no_ip=True
|
||||||
# It is possible to overwrite the bitrate thresholds per interface
|
# It is possible to overwrite the bitrate thresholds per interface
|
||||||
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
|
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
|
||||||
#wlan0_rx_careful=4000000
|
#wlan0_rx_careful=4000000
|
||||||
@ -354,6 +375,8 @@ temperature_hdd_critical=60
|
|||||||
battery_careful=80
|
battery_careful=80
|
||||||
battery_warning=90
|
battery_warning=90
|
||||||
battery_critical=95
|
battery_critical=95
|
||||||
|
# Fan speed threshold in RPM
|
||||||
|
#fan_speed_careful=100
|
||||||
# Sensors alias
|
# Sensors alias
|
||||||
#alias=core 0:CPU Core 0,core 1:CPU Core 1
|
#alias=core 0:CPU Core 0,core 1:CPU Core 1
|
||||||
|
|
||||||
@ -763,13 +786,13 @@ refresh=3
|
|||||||
countmax=20
|
countmax=20
|
||||||
|
|
||||||
[amp_conntrack]
|
[amp_conntrack]
|
||||||
# Use comma separated for multiple commands (no space around the comma)
|
# Use && separator for multiple commands
|
||||||
# If the regex key is not defined, the AMP will be executed every refresh second
|
# If the regex key is not defined, the AMP will be executed every refresh second
|
||||||
# and the process count will not be displayed (countmin and countmax will be ignore)
|
# and the process count will not be displayed (countmin and countmax will be ignore)
|
||||||
enable=false
|
enable=false
|
||||||
refresh=30
|
refresh=30
|
||||||
one_line=false
|
one_line=false
|
||||||
command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max
|
command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max
|
||||||
|
|
||||||
[amp_nginx]
|
[amp_nginx]
|
||||||
# Use the NGinx AMP
|
# Use the NGinx AMP
|
||||||
|
@ -61,6 +61,9 @@ RUN apk add --no-cache \
|
|||||||
RUN python${PYTHON_VERSION} -m venv venv-build
|
RUN python${PYTHON_VERSION} -m venv venv-build
|
||||||
RUN /venv-build/bin/python${PYTHON_VERSION} -m pip install --upgrade pip
|
RUN /venv-build/bin/python${PYTHON_VERSION} -m pip install --upgrade pip
|
||||||
|
|
||||||
|
RUN python${PYTHON_VERSION} -m venv venv-build
|
||||||
|
RUN /venv-build/bin/python${PYTHON_VERSION} -m pip install --upgrade pip
|
||||||
|
|
||||||
RUN python${PYTHON_VERSION} -m venv --without-pip venv
|
RUN python${PYTHON_VERSION} -m venv --without-pip venv
|
||||||
|
|
||||||
COPY requirements.txt docker-requirements.txt webui-requirements.txt optional-requirements.txt ./
|
COPY requirements.txt docker-requirements.txt webui-requirements.txt optional-requirements.txt ./
|
||||||
@ -105,6 +108,11 @@ COPY docker-bin.sh /usr/local/bin/glances
|
|||||||
RUN chmod a+x /usr/local/bin/glances
|
RUN chmod a+x /usr/local/bin/glances
|
||||||
ENV PATH="/venv/bin:$PATH"
|
ENV PATH="/venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Copy binary and update PATH
|
||||||
|
COPY docker-bin.sh /usr/local/bin/glances
|
||||||
|
RUN chmod a+x /usr/local/bin/glances
|
||||||
|
ENV PATH="/venv/bin:$PATH"
|
||||||
|
|
||||||
# EXPOSE PORT (XMLRPC / WebUI)
|
# EXPOSE PORT (XMLRPC / WebUI)
|
||||||
EXPOSE 61209 61208
|
EXPOSE 61209 61208
|
||||||
|
|
||||||
|
@ -92,6 +92,11 @@ COPY docker-bin.sh /usr/local/bin/glances
|
|||||||
RUN chmod a+x /usr/local/bin/glances
|
RUN chmod a+x /usr/local/bin/glances
|
||||||
ENV PATH="/venv/bin:$PATH"
|
ENV PATH="/venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Copy binary and update PATH
|
||||||
|
COPY docker-bin.sh /usr/local/bin/glances
|
||||||
|
RUN chmod a+x /usr/local/bin/glances
|
||||||
|
ENV PATH="/venv/bin:$PATH"
|
||||||
|
|
||||||
# EXPOSE PORT (XMLRPC / WebUI)
|
# EXPOSE PORT (XMLRPC / WebUI)
|
||||||
EXPOSE 61209 61208
|
EXPOSE 61209 61208
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
# install with base requirements file
|
# install with base requirements file
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
docker>=6.1.1; python_version >= "3.7"
|
docker>=6.1.1
|
||||||
podman; python_version >= "3.6"
|
podman
|
||||||
packaging; python_version >= "3.7"
|
|
||||||
python-dateutil
|
python-dateutil
|
||||||
|
requests
|
||||||
six
|
six
|
||||||
urllib3
|
urllib3
|
||||||
requests
|
|
||||||
|
2292
docs/_static/glances-cgraph.svg
vendored
2292
docs/_static/glances-cgraph.svg
vendored
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 91 KiB |
2
docs/_static/glances-pyinstrument.html
vendored
2
docs/_static/glances-pyinstrument.html
vendored
File diff suppressed because one or more lines are too long
@ -61,9 +61,11 @@ For example:
|
|||||||
enable=false
|
enable=false
|
||||||
refresh=30
|
refresh=30
|
||||||
one_line=false
|
one_line=false
|
||||||
command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max
|
command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max
|
||||||
|
|
||||||
For security reason, pipe is not directly allowed in a AMP command but you create a sheel
|
Note: for multiple command, please use the '&&'' separator.
|
||||||
|
|
||||||
|
For security reason, pipe is not directly allowed in a AMP command but you create a shell
|
||||||
script with your command:
|
script with your command:
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
CLOUD
|
CLOUD
|
||||||
=====
|
=====
|
||||||
|
|
||||||
This plugin diplays information about the cloud provider if your host is running on OpenStack.
|
This plugin displays information about the cloud provider if your host is running on OpenStack.
|
||||||
|
|
||||||
The plugin use the standard OpenStack `metadata`_ service to retrieve the information.
|
The plugin use the standard OpenStack `metadata`_ service to retrieve the information.
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ To switch to per-CPU stats, just hit the ``1`` key:
|
|||||||
.. image:: ../_static/per-cpu.png
|
.. image:: ../_static/per-cpu.png
|
||||||
|
|
||||||
In this case, Glances will show on line per logical CPU on the system.
|
In this case, Glances will show on line per logical CPU on the system.
|
||||||
If you have multiple core, it is possible to define the maximun number
|
If you have multiple core, it is possible to define the maximum number
|
||||||
of CPU to display. The top 'max_cpu_display' will be display and an
|
of CPU to display. The top 'max_cpu_display' will be display and an
|
||||||
extra line with the mean of all others CPU will be added.
|
extra line with the mean of all others CPU will be added.
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ Example:
|
|||||||
|
|
||||||
**NOTE:** Setting low values for `public_refresh_interval` will result in frequent
|
**NOTE:** Setting low values for `public_refresh_interval` will result in frequent
|
||||||
HTTP requests to the onlive service defined in public_api. Recommended range: 120-600 seconds.
|
HTTP requests to the onlive service defined in public_api. Recommended range: 120-600 seconds.
|
||||||
Glances uses online services in order to get the IP addresses and the additional informations.
|
Glances uses online services in order to get the IP addresses and the additional information.
|
||||||
Your IP address could be blocked if too many requests are done.
|
Your IP address could be blocked if too many requests are done.
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ Trend Status
|
|||||||
======== ==============================================================
|
======== ==============================================================
|
||||||
``-`` Mean 15 lasts values equal mean 15 previous values
|
``-`` Mean 15 lasts values equal mean 15 previous values
|
||||||
``↓`` Mean 15 lasts values is lower mean 15 previous values
|
``↓`` Mean 15 lasts values is lower mean 15 previous values
|
||||||
``↑`` Mean 15 lasts values is higher mean 15 previous valuess
|
``↑`` Mean 15 lasts values is higher mean 15 previous values
|
||||||
======== ==============================================================
|
======== ==============================================================
|
||||||
|
|
||||||
Legend:
|
Legend:
|
||||||
|
@ -49,7 +49,7 @@ Trend Status
|
|||||||
======== ==============================================================
|
======== ==============================================================
|
||||||
``-`` Mean 15 lasts values equal mean 15 previous values
|
``-`` Mean 15 lasts values equal mean 15 previous values
|
||||||
``↓`` Mean 15 lasts values is lower mean 15 previous values
|
``↓`` Mean 15 lasts values is lower mean 15 previous values
|
||||||
``↑`` Mean 15 lasts values is higher mean 15 previous valuess
|
``↑`` Mean 15 lasts values is higher mean 15 previous values
|
||||||
======== ==============================================================
|
======== ==============================================================
|
||||||
|
|
||||||
Alerts are only set for used memory and used swap.
|
Alerts are only set for used memory and used swap.
|
||||||
|
@ -17,6 +17,8 @@ In this case thresholds values are define in bps.
|
|||||||
Additionally, you can define:
|
Additionally, you can define:
|
||||||
|
|
||||||
- a list of network interfaces to hide
|
- a list of network interfaces to hide
|
||||||
|
- automatically hide interfaces not up
|
||||||
|
- automatically hide interfaces without IP address
|
||||||
- per-interface limit values
|
- per-interface limit values
|
||||||
- aliases for interface name
|
- aliases for interface name
|
||||||
|
|
||||||
@ -41,8 +43,12 @@ virtual docker interface (docker0, docker1, ...):
|
|||||||
hide=docker.*,lo
|
hide=docker.*,lo
|
||||||
# Define the list of network interfaces to show (comma-separated regexp)
|
# Define the list of network interfaces to show (comma-separated regexp)
|
||||||
#show=eth0,eth1
|
#show=eth0,eth1
|
||||||
|
# Automatically hide interface not up (default is False)
|
||||||
|
hide_no_up=True
|
||||||
|
# Automatically hide interface with no IP address (default is False)
|
||||||
|
hide_no_ip=True
|
||||||
# WLAN 0 alias
|
# WLAN 0 alias
|
||||||
wlan0_alias=Wireless IF
|
alias=wlan0:Wireless IF
|
||||||
# It is possible to overwrite the bitrate thresholds per interface
|
# It is possible to overwrite the bitrate thresholds per interface
|
||||||
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
|
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
|
||||||
wlan0_rx_careful=4000000
|
wlan0_rx_careful=4000000
|
||||||
@ -58,4 +64,4 @@ Filtering is based on regular expression. Please be sure that your regular
|
|||||||
expression works as expected. You can use an online tool like `regex101`_ in
|
expression works as expected. You can use an online tool like `regex101`_ in
|
||||||
order to test your regular expression.
|
order to test your regular expression.
|
||||||
|
|
||||||
.. _regex101: https://regex101.com/
|
.. _regex101: https://regex101.com/
|
||||||
|
@ -8,7 +8,14 @@ RAID
|
|||||||
*Dependency: this plugin uses the optional pymdstat Python lib*
|
*Dependency: this plugin uses the optional pymdstat Python lib*
|
||||||
|
|
||||||
This plugin is disable by default, please use the --enable-plugin raid option
|
This plugin is disable by default, please use the --enable-plugin raid option
|
||||||
to enable it.
|
to enable it or enable it in the glances.conf file:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[raid]
|
||||||
|
# Documentation: https://glances.readthedocs.io/en/latest/aoa/raid.html
|
||||||
|
# This plugin is disabled by default
|
||||||
|
disable=False
|
||||||
|
|
||||||
In the terminal interface, click on ``R`` to enable/disable it.
|
In the terminal interface, click on ``R`` to enable/disable it.
|
||||||
|
|
||||||
|
@ -27,8 +27,7 @@ There is no alert on this information.
|
|||||||
.. note 3::
|
.. note 3::
|
||||||
If a sensors has temperature and fan speed with the same name unit,
|
If a sensors has temperature and fan speed with the same name unit,
|
||||||
it is possible to alias it using:
|
it is possible to alias it using:
|
||||||
unitname_temperature_core_alias=Alias for temp
|
alias=unitname_temperature_core_alias:Alias for temp,unitname_fan_speed_alias:Alias for fan speed
|
||||||
unitname_fan_speed_alias=Alias for fan speed
|
|
||||||
|
|
||||||
.. note 4::
|
.. note 4::
|
||||||
If a sensors has multiple identical features names (see #2280), then
|
If a sensors has multiple identical features names (see #2280), then
|
||||||
|
412
docs/api.rst
412
docs/api.rst
@ -141,7 +141,7 @@ Get plugin stats::
|
|||||||
"refresh": 3.0,
|
"refresh": 3.0,
|
||||||
"regex": True,
|
"regex": True,
|
||||||
"result": None,
|
"result": None,
|
||||||
"timer": 0.3574647903442383},
|
"timer": 0.2482151985168457},
|
||||||
{"count": 0,
|
{"count": 0,
|
||||||
"countmax": 20.0,
|
"countmax": 20.0,
|
||||||
"countmin": None,
|
"countmin": None,
|
||||||
@ -150,7 +150,7 @@ Get plugin stats::
|
|||||||
"refresh": 3.0,
|
"refresh": 3.0,
|
||||||
"regex": True,
|
"regex": True,
|
||||||
"result": None,
|
"result": None,
|
||||||
"timer": 0.35736703872680664}]
|
"timer": 0.24815130233764648}]
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ Get a specific item when field matches the given value::
|
|||||||
"refresh": 3.0,
|
"refresh": 3.0,
|
||||||
"regex": True,
|
"regex": True,
|
||||||
"result": None,
|
"result": None,
|
||||||
"timer": 0.3574647903442383}]}
|
"timer": 0.2482151985168457}]}
|
||||||
|
|
||||||
GET cloud
|
GET cloud
|
||||||
---------
|
---------
|
||||||
@ -219,23 +219,7 @@ GET containers
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/containers
|
# curl http://localhost:61208/api/4/containers
|
||||||
[{"command": "tail -f /dev/null",
|
[]
|
||||||
"cpu": {"total": 0.0},
|
|
||||||
"cpu_percent": 0.0,
|
|
||||||
"created": "2024-05-06T08:20:31.859934699Z",
|
|
||||||
"engine": "docker",
|
|
||||||
"id": "f8d78b334f789955ab6dd0739c0bbb7e26ae3f24ed9e42c4a0d218a30377d2c8",
|
|
||||||
"image": ["catthehacker/ubuntu:act-22.04"],
|
|
||||||
"io": {"cumulative_ior": 100855808, "cumulative_iow": 0},
|
|
||||||
"key": "name",
|
|
||||||
"memory": {"inactive_file": 1704656896,
|
|
||||||
"limit": 16422473728,
|
|
||||||
"usage": 1984933888},
|
|
||||||
"memory_usage": 1984933888,
|
|
||||||
"name": "act-test-test-freebsd-700362a4fd49fe930f7ab89909c5ca853cd3a832c27b6ac4e363947b0dd29bef",
|
|
||||||
"network": {},
|
|
||||||
"status": "running",
|
|
||||||
"uptime": "yesterday"}]
|
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -256,35 +240,6 @@ Fields descriptions:
|
|||||||
* **pod_name**: Pod name (only with Podman) (unit is *None*)
|
* **pod_name**: Pod name (only with Podman) (unit is *None*)
|
||||||
* **pod_id**: Pod ID (only with Podman) (unit is *None*)
|
* **pod_id**: Pod ID (only with Podman) (unit is *None*)
|
||||||
|
|
||||||
Get a specific field::
|
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/containers/name
|
|
||||||
{"name": ["act-test-test-freebsd-700362a4fd49fe930f7ab89909c5ca853cd3a832c27b6ac4e363947b0dd29bef"]}
|
|
||||||
|
|
||||||
Get a specific item when field matches the given value::
|
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/containers/name/act-test-test-freebsd-700362a4fd49fe930f7ab89909c5ca853cd3a832c27b6ac4e363947b0dd29bef
|
|
||||||
{"act-test-test-freebsd-700362a4fd49fe930f7ab89909c5ca853cd3a832c27b6ac4e363947b0dd29bef": [{"command": "tail "
|
|
||||||
"-f "
|
|
||||||
"/dev/null",
|
|
||||||
"cpu": {"total": 0.0},
|
|
||||||
"cpu_percent": 0.0,
|
|
||||||
"created": "2024-05-06T08:20:31.859934699Z",
|
|
||||||
"engine": "docker",
|
|
||||||
"id": "f8d78b334f789955ab6dd0739c0bbb7e26ae3f24ed9e42c4a0d218a30377d2c8",
|
|
||||||
"image": ["catthehacker/ubuntu:act-22.04"],
|
|
||||||
"io": {"cumulative_ior": 100855808,
|
|
||||||
"cumulative_iow": 0},
|
|
||||||
"key": "name",
|
|
||||||
"memory": {"inactive_file": 1704656896,
|
|
||||||
"limit": 16422473728,
|
|
||||||
"usage": 1984933888},
|
|
||||||
"memory_usage": 1984933888,
|
|
||||||
"name": "act-test-test-freebsd-700362a4fd49fe930f7ab89909c5ca853cd3a832c27b6ac4e363947b0dd29bef",
|
|
||||||
"network": {},
|
|
||||||
"status": "running",
|
|
||||||
"uptime": "yesterday"}]}
|
|
||||||
|
|
||||||
GET core
|
GET core
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@ -310,19 +265,19 @@ Get plugin stats::
|
|||||||
|
|
||||||
# curl http://localhost:61208/api/4/cpu
|
# curl http://localhost:61208/api/4/cpu
|
||||||
{"cpucore": 16,
|
{"cpucore": 16,
|
||||||
"ctx_switches": 100084028,
|
"ctx_switches": 493080568,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"idle": 3.0,
|
"idle": 86.5,
|
||||||
"interrupts": 99754405,
|
"interrupts": 420997918,
|
||||||
"iowait": 0.0,
|
"iowait": 0.5,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"nice": 0.0,
|
"nice": 0.0,
|
||||||
"soft_interrupts": 30928307,
|
"soft_interrupts": 155707720,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"syscalls": 0,
|
"syscalls": 0,
|
||||||
"system": 1.0,
|
"system": 3.5,
|
||||||
"total": 25.0,
|
"total": 12.9,
|
||||||
"user": 0.0}
|
"user": 9.4}
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -355,7 +310,7 @@ Fields descriptions:
|
|||||||
Get a specific field::
|
Get a specific field::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/cpu/total
|
# curl http://localhost:61208/api/4/cpu/total
|
||||||
{"total": 25.0}
|
{"total": 12.9}
|
||||||
|
|
||||||
GET diskio
|
GET diskio
|
||||||
----------
|
----------
|
||||||
@ -365,14 +320,14 @@ Get plugin stats::
|
|||||||
# curl http://localhost:61208/api/4/diskio
|
# curl http://localhost:61208/api/4/diskio
|
||||||
[{"disk_name": "nvme0n1",
|
[{"disk_name": "nvme0n1",
|
||||||
"key": "disk_name",
|
"key": "disk_name",
|
||||||
"read_bytes": 5494942208,
|
"read_bytes": 10167291392,
|
||||||
"read_count": 276248,
|
"read_count": 391026,
|
||||||
"write_bytes": 12631118848,
|
"write_bytes": 31230641152,
|
||||||
"write_count": 434825},
|
"write_count": 1527146},
|
||||||
{"disk_name": "nvme0n1p1",
|
{"disk_name": "nvme0n1p1",
|
||||||
"key": "disk_name",
|
"key": "disk_name",
|
||||||
"read_bytes": 7484416,
|
"read_bytes": 7558144,
|
||||||
"read_count": 592,
|
"read_count": 605,
|
||||||
"write_bytes": 1024,
|
"write_bytes": 1024,
|
||||||
"write_count": 2}]
|
"write_count": 2}]
|
||||||
|
|
||||||
@ -408,10 +363,10 @@ Get a specific item when field matches the given value::
|
|||||||
# curl http://localhost:61208/api/4/diskio/disk_name/nvme0n1
|
# curl http://localhost:61208/api/4/diskio/disk_name/nvme0n1
|
||||||
{"nvme0n1": [{"disk_name": "nvme0n1",
|
{"nvme0n1": [{"disk_name": "nvme0n1",
|
||||||
"key": "disk_name",
|
"key": "disk_name",
|
||||||
"read_bytes": 5494942208,
|
"read_bytes": 10167291392,
|
||||||
"read_count": 276248,
|
"read_count": 391026,
|
||||||
"write_bytes": 12631118848,
|
"write_bytes": 31230641152,
|
||||||
"write_count": 434825}]}
|
"write_count": 1527146}]}
|
||||||
|
|
||||||
GET folders
|
GET folders
|
||||||
-----------
|
-----------
|
||||||
@ -438,13 +393,13 @@ Get plugin stats::
|
|||||||
|
|
||||||
# curl http://localhost:61208/api/4/fs
|
# curl http://localhost:61208/api/4/fs
|
||||||
[{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
|
[{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
|
||||||
"free": 906483245056,
|
"free": 897378041856,
|
||||||
"fs_type": "ext4",
|
"fs_type": "ext4",
|
||||||
"key": "mnt_point",
|
"key": "mnt_point",
|
||||||
"mnt_point": "/",
|
"mnt_point": "/",
|
||||||
"percent": 4.8,
|
"percent": 5.8,
|
||||||
"size": 1003736440832,
|
"size": 1003736440832,
|
||||||
"used": 46190690304}]
|
"used": 55295893504}]
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -465,13 +420,13 @@ Get a specific item when field matches the given value::
|
|||||||
|
|
||||||
# curl http://localhost:61208/api/4/fs/mnt_point//
|
# curl http://localhost:61208/api/4/fs/mnt_point//
|
||||||
{"/": [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
|
{"/": [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
|
||||||
"free": 906483245056,
|
"free": 897378041856,
|
||||||
"fs_type": "ext4",
|
"fs_type": "ext4",
|
||||||
"key": "mnt_point",
|
"key": "mnt_point",
|
||||||
"mnt_point": "/",
|
"mnt_point": "/",
|
||||||
"percent": 4.8,
|
"percent": 5.8,
|
||||||
"size": 1003736440832,
|
"size": 1003736440832,
|
||||||
"used": 46190690304}]}
|
"used": 55295893504}]}
|
||||||
|
|
||||||
GET gpu
|
GET gpu
|
||||||
-------
|
-------
|
||||||
@ -544,7 +499,10 @@ GET load
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/load
|
# curl http://localhost:61208/api/4/load
|
||||||
{"cpucore": 16, "min1": 0.52685546875, "min15": 1.25390625, "min5": 1.544921875}
|
{"cpucore": 16,
|
||||||
|
"min1": 0.40185546875,
|
||||||
|
"min15": 0.587890625,
|
||||||
|
"min5": 0.638671875}
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -556,7 +514,7 @@ Fields descriptions:
|
|||||||
Get a specific field::
|
Get a specific field::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/load/min1
|
# curl http://localhost:61208/api/4/load/min1
|
||||||
{"min1": 0.52685546875}
|
{"min1": 0.40185546875}
|
||||||
|
|
||||||
GET mem
|
GET mem
|
||||||
-------
|
-------
|
||||||
@ -564,16 +522,16 @@ GET mem
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/mem
|
# curl http://localhost:61208/api/4/mem
|
||||||
{"active": 7115952128,
|
{"active": 5094199296,
|
||||||
"available": 8999600128,
|
"available": 10908983296,
|
||||||
"buffers": 268685312,
|
"buffers": 180162560,
|
||||||
"cached": 9109741568,
|
"cached": 5800796160,
|
||||||
"free": 8999600128,
|
"free": 10908983296,
|
||||||
"inactive": 5429415936,
|
"inactive": 3735175168,
|
||||||
"percent": 45.2,
|
"percent": 33.6,
|
||||||
"shared": 1431875584,
|
"shared": 622718976,
|
||||||
"total": 16422473728,
|
"total": 16422486016,
|
||||||
"used": 7422873600}
|
"used": 5513502720}
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -592,7 +550,7 @@ Fields descriptions:
|
|||||||
Get a specific field::
|
Get a specific field::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/mem/total
|
# curl http://localhost:61208/api/4/mem/total
|
||||||
{"total": 16422473728}
|
{"total": 16422486016}
|
||||||
|
|
||||||
GET memswap
|
GET memswap
|
||||||
-----------
|
-----------
|
||||||
@ -600,13 +558,13 @@ GET memswap
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/memswap
|
# curl http://localhost:61208/api/4/memswap
|
||||||
{"free": 4293652480,
|
{"free": 3836997632,
|
||||||
"percent": 0.0,
|
"percent": 10.7,
|
||||||
"sin": 0,
|
"sin": 186925056,
|
||||||
"sout": 131072,
|
"sout": 1518604288,
|
||||||
"time_since_update": 1,
|
"time_since_update": 1,
|
||||||
"total": 4294963200,
|
"total": 4294963200,
|
||||||
"used": 1310720}
|
"used": 457965568}
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -631,15 +589,15 @@ Get plugin stats::
|
|||||||
# curl http://localhost:61208/api/4/network
|
# curl http://localhost:61208/api/4/network
|
||||||
[{"alias": None,
|
[{"alias": None,
|
||||||
"bytes_all": 0,
|
"bytes_all": 0,
|
||||||
"bytes_all_gauge": 2153845053,
|
"bytes_all_gauge": 6286191015,
|
||||||
"bytes_recv": 0,
|
"bytes_recv": 0,
|
||||||
"bytes_recv_gauge": 2054902380,
|
"bytes_recv_gauge": 5977645732,
|
||||||
"bytes_sent": 0,
|
"bytes_sent": 0,
|
||||||
"bytes_sent_gauge": 98942673,
|
"bytes_sent_gauge": 308545283,
|
||||||
"interface_name": "wlp0s20f3",
|
"interface_name": "wlp0s20f3",
|
||||||
"key": "interface_name",
|
"key": "interface_name",
|
||||||
"speed": 0,
|
"speed": 0,
|
||||||
"time_since_update": 0.37114882469177246}]
|
"time_since_update": 0.2501566410064697}]
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -668,15 +626,15 @@ Get a specific item when field matches the given value::
|
|||||||
# curl http://localhost:61208/api/4/network/interface_name/wlp0s20f3
|
# curl http://localhost:61208/api/4/network/interface_name/wlp0s20f3
|
||||||
{"wlp0s20f3": [{"alias": None,
|
{"wlp0s20f3": [{"alias": None,
|
||||||
"bytes_all": 0,
|
"bytes_all": 0,
|
||||||
"bytes_all_gauge": 2153845053,
|
"bytes_all_gauge": 6286191015,
|
||||||
"bytes_recv": 0,
|
"bytes_recv": 0,
|
||||||
"bytes_recv_gauge": 2054902380,
|
"bytes_recv_gauge": 5977645732,
|
||||||
"bytes_sent": 0,
|
"bytes_sent": 0,
|
||||||
"bytes_sent_gauge": 98942673,
|
"bytes_sent_gauge": 308545283,
|
||||||
"interface_name": "wlp0s20f3",
|
"interface_name": "wlp0s20f3",
|
||||||
"key": "interface_name",
|
"key": "interface_name",
|
||||||
"speed": 0,
|
"speed": 0,
|
||||||
"time_since_update": 0.37114882469177246}]}
|
"time_since_update": 0.2501566410064697}]}
|
||||||
|
|
||||||
GET now
|
GET now
|
||||||
-------
|
-------
|
||||||
@ -684,7 +642,7 @@ GET now
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/now
|
# curl http://localhost:61208/api/4/now
|
||||||
{"custom": "2024-05-07 11:46:44 CEST", "iso": "2024-05-07T11:46:44+02:00"}
|
{"custom": "2024-06-29 19:17:41 CEST", "iso": "2024-06-29T19:17:41+02:00"}
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -694,7 +652,7 @@ Fields descriptions:
|
|||||||
Get a specific field::
|
Get a specific field::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/now/iso
|
# curl http://localhost:61208/api/4/now/iso
|
||||||
{"iso": "2024-05-07T11:46:44+02:00"}
|
{"iso": "2024-06-29T19:17:41+02:00"}
|
||||||
|
|
||||||
GET percpu
|
GET percpu
|
||||||
----------
|
----------
|
||||||
@ -705,7 +663,7 @@ Get plugin stats::
|
|||||||
[{"cpu_number": 0,
|
[{"cpu_number": 0,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 21.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -713,12 +671,12 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 100.0,
|
"total": 79.0,
|
||||||
"user": 0.0},
|
"user": 1.0},
|
||||||
{"cpu_number": 1,
|
{"cpu_number": 1,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 1.0,
|
"idle": 23.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -726,7 +684,7 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 99.0,
|
"total": 77.0,
|
||||||
"user": 0.0}]
|
"user": 0.0}]
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
@ -761,7 +719,7 @@ Get plugin stats::
|
|||||||
"port": 0,
|
"port": 0,
|
||||||
"refresh": 30,
|
"refresh": 30,
|
||||||
"rtt_warning": None,
|
"rtt_warning": None,
|
||||||
"status": 0.008626,
|
"status": 0.006275,
|
||||||
"timeout": 3}]
|
"timeout": 3}]
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
@ -789,7 +747,7 @@ Get a specific item when field matches the given value::
|
|||||||
"port": 0,
|
"port": 0,
|
||||||
"refresh": 30,
|
"refresh": 30,
|
||||||
"rtt_warning": None,
|
"rtt_warning": None,
|
||||||
"status": 0.008626,
|
"status": 0.006275,
|
||||||
"timeout": 3}]}
|
"timeout": 3}]}
|
||||||
|
|
||||||
GET processcount
|
GET processcount
|
||||||
@ -798,7 +756,7 @@ GET processcount
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/processcount
|
# curl http://localhost:61208/api/4/processcount
|
||||||
{"pid_max": 0, "running": 1, "sleeping": 287, "thread": 1710, "total": 432}
|
{"pid_max": 0, "running": 2, "sleeping": 277, "thread": 1508, "total": 412}
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -811,7 +769,7 @@ Fields descriptions:
|
|||||||
Get a specific field::
|
Get a specific field::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/processcount/total
|
# curl http://localhost:61208/api/4/processcount/total
|
||||||
{"total": 432}
|
{"total": 412}
|
||||||
|
|
||||||
GET processlist
|
GET processlist
|
||||||
---------------
|
---------------
|
||||||
@ -819,7 +777,78 @@ GET processlist
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/processlist
|
# curl http://localhost:61208/api/4/processlist
|
||||||
[]
|
[{"cmdline": ["/snap/firefox/4336/usr/lib/firefox/firefox"],
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"cpu_times": {"children_system": 3.08,
|
||||||
|
"children_user": 3.33,
|
||||||
|
"iowait": 0.0,
|
||||||
|
"system": 13.75,
|
||||||
|
"user": 34.7},
|
||||||
|
"gids": {"effective": 1000, "real": 1000, "saved": 1000},
|
||||||
|
"io_counters": [393136128, 195969024, 0, 0, 0],
|
||||||
|
"key": "pid",
|
||||||
|
"memory_info": {"data": 681967616,
|
||||||
|
"dirty": 0,
|
||||||
|
"lib": 0,
|
||||||
|
"rss": 443969536,
|
||||||
|
"shared": 222199808,
|
||||||
|
"text": 987136,
|
||||||
|
"vms": 3721211904},
|
||||||
|
"memory_percent": 2.7034246554842674,
|
||||||
|
"name": "firefox",
|
||||||
|
"nice": 0,
|
||||||
|
"num_threads": 120,
|
||||||
|
"pid": 793506,
|
||||||
|
"status": "S",
|
||||||
|
"time_since_update": 1,
|
||||||
|
"username": "nicolargo"},
|
||||||
|
{"cmdline": ["/snap/firefox/4336/usr/lib/firefox/firefox",
|
||||||
|
"-contentproc",
|
||||||
|
"-childID",
|
||||||
|
"2",
|
||||||
|
"-isForBrowser",
|
||||||
|
"-prefsLen",
|
||||||
|
"28218",
|
||||||
|
"-prefMapSize",
|
||||||
|
"244440",
|
||||||
|
"-jsInitLen",
|
||||||
|
"231800",
|
||||||
|
"-parentBuildID",
|
||||||
|
"20240527194810",
|
||||||
|
"-greomni",
|
||||||
|
"/snap/firefox/4336/usr/lib/firefox/omni.ja",
|
||||||
|
"-appomni",
|
||||||
|
"/snap/firefox/4336/usr/lib/firefox/browser/omni.ja",
|
||||||
|
"-appDir",
|
||||||
|
"/snap/firefox/4336/usr/lib/firefox/browser",
|
||||||
|
"{bc853380-6b8f-46ad-afe0-9da5ba832e62}",
|
||||||
|
"793506",
|
||||||
|
"true",
|
||||||
|
"tab"],
|
||||||
|
"cpu_percent": 0.0,
|
||||||
|
"cpu_times": {"children_system": 0.0,
|
||||||
|
"children_user": 0.0,
|
||||||
|
"iowait": 0.0,
|
||||||
|
"system": 2.54,
|
||||||
|
"user": 19.25},
|
||||||
|
"gids": {"effective": 1000, "real": 1000, "saved": 1000},
|
||||||
|
"io_counters": [1827840, 0, 0, 0, 0],
|
||||||
|
"key": "pid",
|
||||||
|
"memory_info": {"data": 412688384,
|
||||||
|
"dirty": 0,
|
||||||
|
"lib": 0,
|
||||||
|
"rss": 440922112,
|
||||||
|
"shared": 111878144,
|
||||||
|
"text": 987136,
|
||||||
|
"vms": 2946224128},
|
||||||
|
"memory_percent": 2.684868244493684,
|
||||||
|
"name": "Isolated Web Co",
|
||||||
|
"nice": 0,
|
||||||
|
"num_threads": 28,
|
||||||
|
"pid": 793778,
|
||||||
|
"status": "S",
|
||||||
|
"time_since_update": 1,
|
||||||
|
"username": "nicolargo"}]
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -843,7 +872,7 @@ GET psutilversion
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/psutilversion
|
# curl http://localhost:61208/api/4/psutilversion
|
||||||
"5.9.8"
|
"6.0.0"
|
||||||
|
|
||||||
GET quicklook
|
GET quicklook
|
||||||
-------------
|
-------------
|
||||||
@ -851,18 +880,18 @@ GET quicklook
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/quicklook
|
# curl http://localhost:61208/api/4/quicklook
|
||||||
{"cpu": 25.0,
|
{"cpu": 12.9,
|
||||||
"cpu_hz": 4475000000.0,
|
"cpu_hz": 4475000000.0,
|
||||||
"cpu_hz_current": 1527240625.0000005,
|
"cpu_hz_current": 1666410624.9999998,
|
||||||
"cpu_log_core": 16,
|
"cpu_log_core": 16,
|
||||||
"cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H",
|
"cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H",
|
||||||
"cpu_phys_core": 10,
|
"cpu_phys_core": 10,
|
||||||
"load": 7.8,
|
"load": 3.7,
|
||||||
"mem": 45.2,
|
"mem": 33.6,
|
||||||
"percpu": [{"cpu_number": 0,
|
"percpu": [{"cpu_number": 0,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 21.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -870,12 +899,12 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 100.0,
|
"total": 79.0,
|
||||||
"user": 0.0},
|
"user": 1.0},
|
||||||
{"cpu_number": 1,
|
{"cpu_number": 1,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 1.0,
|
"idle": 23.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -883,25 +912,25 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 99.0,
|
"total": 77.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 2,
|
{"cpu_number": 2,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 18.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
"nice": 0.0,
|
"nice": 0.0,
|
||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 1.0,
|
||||||
"total": 100.0,
|
"total": 82.0,
|
||||||
"user": 0.0},
|
"user": 5.0},
|
||||||
{"cpu_number": 3,
|
{"cpu_number": 3,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 22.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -909,25 +938,25 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 100.0,
|
"total": 78.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 4,
|
{"cpu_number": 4,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 8.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
"nice": 0.0,
|
"nice": 0.0,
|
||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 1.0,
|
||||||
"total": 100.0,
|
"total": 92.0,
|
||||||
"user": 0.0},
|
"user": 14.0},
|
||||||
{"cpu_number": 5,
|
{"cpu_number": 5,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 23.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -935,64 +964,64 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 100.0,
|
"total": 77.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 6,
|
{"cpu_number": 6,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 3.0,
|
||||||
"iowait": 0.0,
|
"iowait": 1.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
"nice": 0.0,
|
"nice": 0.0,
|
||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 4.0,
|
||||||
"total": 100.0,
|
"total": 97.0,
|
||||||
"user": 0.0},
|
"user": 15.0},
|
||||||
{"cpu_number": 7,
|
{"cpu_number": 7,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 23.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
"nice": 0.0,
|
"nice": 0.0,
|
||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 1.0,
|
||||||
"total": 100.0,
|
"total": 77.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 8,
|
{"cpu_number": 8,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 19.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
"nice": 0.0,
|
"nice": 0.0,
|
||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 1.0,
|
||||||
"total": 100.0,
|
"total": 81.0,
|
||||||
"user": 0.0},
|
"user": 3.0},
|
||||||
{"cpu_number": 9,
|
{"cpu_number": 9,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 1.0,
|
"idle": 22.0,
|
||||||
"iowait": 0.0,
|
"iowait": 1.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
"nice": 0.0,
|
"nice": 0.0,
|
||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 99.0,
|
"total": 78.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 10,
|
{"cpu_number": 10,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 22.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -1000,12 +1029,12 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 100.0,
|
"total": 78.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 11,
|
{"cpu_number": 11,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 23.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -1013,25 +1042,25 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 100.0,
|
"total": 77.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 12,
|
{"cpu_number": 12,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 21.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
"nice": 0.0,
|
"nice": 0.0,
|
||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 1.0,
|
||||||
"total": 100.0,
|
"total": 79.0,
|
||||||
"user": 0.0},
|
"user": 1.0},
|
||||||
{"cpu_number": 13,
|
{"cpu_number": 13,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 23.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -1039,12 +1068,12 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 100.0,
|
"total": 77.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 14,
|
{"cpu_number": 14,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 0.0,
|
"idle": 22.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -1052,12 +1081,12 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 100.0,
|
"total": 78.0,
|
||||||
"user": 0.0},
|
"user": 0.0},
|
||||||
{"cpu_number": 15,
|
{"cpu_number": 15,
|
||||||
"guest": 0.0,
|
"guest": 0.0,
|
||||||
"guest_nice": 0.0,
|
"guest_nice": 0.0,
|
||||||
"idle": 1.0,
|
"idle": 22.0,
|
||||||
"iowait": 0.0,
|
"iowait": 0.0,
|
||||||
"irq": 0.0,
|
"irq": 0.0,
|
||||||
"key": "cpu_number",
|
"key": "cpu_number",
|
||||||
@ -1065,9 +1094,9 @@ Get plugin stats::
|
|||||||
"softirq": 0.0,
|
"softirq": 0.0,
|
||||||
"steal": 0.0,
|
"steal": 0.0,
|
||||||
"system": 0.0,
|
"system": 0.0,
|
||||||
"total": 99.0,
|
"total": 78.0,
|
||||||
"user": 0.0}],
|
"user": 0.0}],
|
||||||
"swap": 0.0}
|
"swap": 10.7}
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
|
|
||||||
@ -1105,14 +1134,14 @@ Get plugin stats::
|
|||||||
"label": "Ambient",
|
"label": "Ambient",
|
||||||
"type": "temperature_core",
|
"type": "temperature_core",
|
||||||
"unit": "C",
|
"unit": "C",
|
||||||
"value": 35,
|
"value": 39,
|
||||||
"warning": 0},
|
"warning": 0},
|
||||||
{"critical": None,
|
{"critical": None,
|
||||||
"key": "label",
|
"key": "label",
|
||||||
"label": "Ambient 3",
|
"label": "Ambient 3",
|
||||||
"type": "temperature_core",
|
"type": "temperature_core",
|
||||||
"unit": "C",
|
"unit": "C",
|
||||||
"value": 29,
|
"value": 32,
|
||||||
"warning": 0}]
|
"warning": 0}]
|
||||||
|
|
||||||
Fields descriptions:
|
Fields descriptions:
|
||||||
@ -1173,7 +1202,7 @@ Get a specific item when field matches the given value::
|
|||||||
"label": "Ambient",
|
"label": "Ambient",
|
||||||
"type": "temperature_core",
|
"type": "temperature_core",
|
||||||
"unit": "C",
|
"unit": "C",
|
||||||
"value": 35,
|
"value": 39,
|
||||||
"warning": 0}]}
|
"warning": 0}]}
|
||||||
|
|
||||||
GET smart
|
GET smart
|
||||||
@ -1204,7 +1233,7 @@ Fields descriptions:
|
|||||||
* **platform**: Platform (32 or 64 bits) (unit is *None*)
|
* **platform**: Platform (32 or 64 bits) (unit is *None*)
|
||||||
* **linux_distro**: Linux distribution (unit is *None*)
|
* **linux_distro**: Linux distribution (unit is *None*)
|
||||||
* **os_version**: Operating system version (unit is *None*)
|
* **os_version**: Operating system version (unit is *None*)
|
||||||
* **hr_name**: Human readable operating sytem name (unit is *None*)
|
* **hr_name**: Human readable operating system name (unit is *None*)
|
||||||
|
|
||||||
Get a specific field::
|
Get a specific field::
|
||||||
|
|
||||||
@ -1217,7 +1246,7 @@ GET uptime
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/uptime
|
# curl http://localhost:61208/api/4/uptime
|
||||||
"1 day, 2:23:04"
|
"20 days, 2:20:27"
|
||||||
|
|
||||||
GET version
|
GET version
|
||||||
-----------
|
-----------
|
||||||
@ -1225,7 +1254,7 @@ GET version
|
|||||||
Get plugin stats::
|
Get plugin stats::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/version
|
# curl http://localhost:61208/api/4/version
|
||||||
"4.0.0_rc02"
|
"4.2.0_beta01"
|
||||||
|
|
||||||
GET wifi
|
GET wifi
|
||||||
--------
|
--------
|
||||||
@ -1234,8 +1263,8 @@ Get plugin stats::
|
|||||||
|
|
||||||
# curl http://localhost:61208/api/4/wifi
|
# curl http://localhost:61208/api/4/wifi
|
||||||
[{"key": "ssid",
|
[{"key": "ssid",
|
||||||
"quality_level": -61.0,
|
"quality_level": -59.0,
|
||||||
"quality_link": 49.0,
|
"quality_link": 51.0,
|
||||||
"ssid": "wlp0s20f3"}]
|
"ssid": "wlp0s20f3"}]
|
||||||
|
|
||||||
Get a specific field::
|
Get a specific field::
|
||||||
@ -1247,8 +1276,8 @@ Get a specific item when field matches the given value::
|
|||||||
|
|
||||||
# curl http://localhost:61208/api/4/wifi/ssid/wlp0s20f3
|
# curl http://localhost:61208/api/4/wifi/ssid/wlp0s20f3
|
||||||
{"wlp0s20f3": [{"key": "ssid",
|
{"wlp0s20f3": [{"key": "ssid",
|
||||||
"quality_level": -61.0,
|
"quality_level": -59.0,
|
||||||
"quality_link": 49.0,
|
"quality_link": 51.0,
|
||||||
"ssid": "wlp0s20f3"}]}
|
"ssid": "wlp0s20f3"}]}
|
||||||
|
|
||||||
GET all stats
|
GET all stats
|
||||||
@ -1293,34 +1322,34 @@ GET stats history
|
|||||||
History of a plugin::
|
History of a plugin::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/cpu/history
|
# curl http://localhost:61208/api/4/cpu/history
|
||||||
{"system": [["2024-05-07T11:46:45.322880", 1.0],
|
{"system": [["2024-06-29T19:17:42.396270", 3.5],
|
||||||
["2024-05-07T11:46:46.376121", 0.0],
|
["2024-06-29T19:17:43.447397", 0.7],
|
||||||
["2024-05-07T11:46:47.453787", 0.0]],
|
["2024-06-29T19:17:44.455830", 0.7]],
|
||||||
"user": [["2024-05-07T11:46:45.322868", 0.0],
|
"user": [["2024-06-29T19:17:42.396268", 9.4],
|
||||||
["2024-05-07T11:46:46.376117", 0.0],
|
["2024-06-29T19:17:43.447396", 4.0],
|
||||||
["2024-05-07T11:46:47.453775", 0.0]]}
|
["2024-06-29T19:17:44.455825", 4.0]]}
|
||||||
|
|
||||||
Limit history to last 2 values::
|
Limit history to last 2 values::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/cpu/history/2
|
# curl http://localhost:61208/api/4/cpu/history/2
|
||||||
{"system": [["2024-05-07T11:46:46.376121", 0.0],
|
{"system": [["2024-06-29T19:17:43.447397", 0.7],
|
||||||
["2024-05-07T11:46:47.453787", 0.0]],
|
["2024-06-29T19:17:44.455830", 0.7]],
|
||||||
"user": [["2024-05-07T11:46:46.376117", 0.0],
|
"user": [["2024-06-29T19:17:43.447396", 4.0],
|
||||||
["2024-05-07T11:46:47.453775", 0.0]]}
|
["2024-06-29T19:17:44.455825", 4.0]]}
|
||||||
|
|
||||||
History for a specific field::
|
History for a specific field::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/cpu/system/history
|
# curl http://localhost:61208/api/4/cpu/system/history
|
||||||
{"system": [["2024-05-07T11:46:44.106241", 1.0],
|
{"system": [["2024-06-29T19:17:41.318736", 3.5],
|
||||||
["2024-05-07T11:46:45.322880", 1.0],
|
["2024-06-29T19:17:42.396270", 3.5],
|
||||||
["2024-05-07T11:46:46.376121", 0.0],
|
["2024-06-29T19:17:43.447397", 0.7],
|
||||||
["2024-05-07T11:46:47.453787", 0.0]]}
|
["2024-06-29T19:17:44.455830", 0.7]]}
|
||||||
|
|
||||||
Limit history for a specific field to last 2 values::
|
Limit history for a specific field to last 2 values::
|
||||||
|
|
||||||
# curl http://localhost:61208/api/4/cpu/system/history
|
# curl http://localhost:61208/api/4/cpu/system/history
|
||||||
{"system": [["2024-05-07T11:46:46.376121", 0.0],
|
{"system": [["2024-06-29T19:17:43.447397", 0.7],
|
||||||
["2024-05-07T11:46:47.453787", 0.0]]}
|
["2024-06-29T19:17:44.455830", 0.7]]}
|
||||||
|
|
||||||
GET limits (used for thresholds)
|
GET limits (used for thresholds)
|
||||||
--------------------------------
|
--------------------------------
|
||||||
@ -1405,6 +1434,8 @@ All limits/thresholds::
|
|||||||
"network": {"history_size": 1200.0,
|
"network": {"history_size": 1200.0,
|
||||||
"network_disable": ["False"],
|
"network_disable": ["False"],
|
||||||
"network_hide": ["docker.*", "lo"],
|
"network_hide": ["docker.*", "lo"],
|
||||||
|
"network_hide_no_ip": ["True"],
|
||||||
|
"network_hide_no_up": ["True"],
|
||||||
"network_rx_careful": 70.0,
|
"network_rx_careful": 70.0,
|
||||||
"network_rx_critical": 90.0,
|
"network_rx_critical": 90.0,
|
||||||
"network_rx_warning": 80.0,
|
"network_rx_warning": 80.0,
|
||||||
@ -1494,7 +1525,6 @@ All limits/thresholds::
|
|||||||
"quicklook_swap_careful": 50.0,
|
"quicklook_swap_careful": 50.0,
|
||||||
"quicklook_swap_critical": 90.0,
|
"quicklook_swap_critical": 90.0,
|
||||||
"quicklook_swap_warning": 70.0},
|
"quicklook_swap_warning": 70.0},
|
||||||
"raid": {"history_size": 1200.0, "raid_disable": ["False"]},
|
|
||||||
"sensors": {"history_size": 1200.0,
|
"sensors": {"history_size": 1200.0,
|
||||||
"sensors_battery_careful": 80.0,
|
"sensors_battery_careful": 80.0,
|
||||||
"sensors_battery_critical": 95.0,
|
"sensors_battery_critical": 95.0,
|
||||||
|
39
docs/conf.py
39
docs/conf.py
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# Glances documentation build configuration file, created by
|
# Glances documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Tue Mar 1 10:53:59 2016.
|
# sphinx-quickstart on Tue Mar 1 10:53:59 2016.
|
||||||
@ -12,8 +11,8 @@
|
|||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
@ -27,7 +26,6 @@ sys.path.insert(0, os.path.abspath('..'))
|
|||||||
# WARNING: Do not move this import before the sys.path.insert() call.
|
# WARNING: Do not move this import before the sys.path.insert() call.
|
||||||
from glances import __version__
|
from glances import __version__
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
@ -125,8 +123,7 @@ html_theme = 'sphinx_rtd_theme'
|
|||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
html_theme_options = {
|
html_theme_options = {}
|
||||||
}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
# html_theme_path = []
|
# html_theme_path = []
|
||||||
@ -166,14 +163,7 @@ html_static_path = ['_static']
|
|||||||
# html_use_smartypants = True
|
# html_use_smartypants = True
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
# Custom sidebar templates, maps document names to template names.
|
||||||
html_sidebars = {
|
html_sidebars = {'**': ['about.html', 'navigation.html', 'links.html', 'searchbox.html']}
|
||||||
'**': [
|
|
||||||
'about.html',
|
|
||||||
'navigation.html',
|
|
||||||
'links.html',
|
|
||||||
'searchbox.html'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
# template names.
|
# template names.
|
||||||
@ -227,13 +217,10 @@ htmlhelp_basename = 'Glancesdoc'
|
|||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
# 'papersize': 'letterpaper',
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
# 'pointsize': '10pt',
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
# 'preamble': '',
|
# 'preamble': '',
|
||||||
|
|
||||||
# Latex figure (float) alignment
|
# Latex figure (float) alignment
|
||||||
# 'figure_align': 'htbp',
|
# 'figure_align': 'htbp',
|
||||||
}
|
}
|
||||||
@ -242,8 +229,7 @@ latex_elements = {
|
|||||||
# (source start file, target name, title,
|
# (source start file, target name, title,
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
(master_doc, 'Glances.tex', 'Glances Documentation',
|
(master_doc, 'Glances.tex', 'Glances Documentation', 'Nicolas Hennion', 'manual'),
|
||||||
'Nicolas Hennion', 'manual'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
@ -271,10 +257,7 @@ latex_documents = [
|
|||||||
|
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [('glances', 'glances', 'An eye on your system', '', 1)]
|
||||||
('glances', 'glances', 'An eye on your system',
|
|
||||||
'', 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
# man_show_urls = False
|
# man_show_urls = False
|
||||||
@ -286,9 +269,15 @@ man_pages = [
|
|||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
(master_doc, 'Glances', 'Glances Documentation',
|
(
|
||||||
author, 'Glances', 'One line description of project.',
|
master_doc,
|
||||||
'Miscellaneous'),
|
'Glances',
|
||||||
|
'Glances Documentation',
|
||||||
|
author,
|
||||||
|
'Glances',
|
||||||
|
'One line description of project.',
|
||||||
|
'Miscellaneous',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
|
@ -21,6 +21,7 @@ You can place your ``glances.conf`` file in the following locations:
|
|||||||
``*BSD`` ~/.config/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/
|
``*BSD`` ~/.config/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/
|
||||||
``macOS`` ~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/
|
``macOS`` ~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/
|
||||||
``Windows`` %APPDATA%\\glances\\glances.conf
|
``Windows`` %APPDATA%\\glances\\glances.conf
|
||||||
|
``All`` + <venv_root_folder>/share/doc/glances/
|
||||||
==================== =============================================================
|
==================== =============================================================
|
||||||
|
|
||||||
- On Windows XP, ``%APPDATA%`` is: ``C:\Documents and Settings\<USERNAME>\Application Data``.
|
- On Windows XP, ``%APPDATA%`` is: ``C:\Documents and Settings\<USERNAME>\Application Data``.
|
||||||
@ -59,17 +60,41 @@ than a second one concerning the user interface:
|
|||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
[outputs]
|
[outputs]
|
||||||
|
# Options for all UIs
|
||||||
|
#--------------------
|
||||||
# Separator in the Curses and WebUI interface (between top and others plugins)
|
# Separator in the Curses and WebUI interface (between top and others plugins)
|
||||||
separator=True
|
separator=True
|
||||||
# Set the the Curses and WebUI interface left menu plugin list (comma-separated)
|
# Set the the Curses and WebUI interface left menu plugin list (comma-separated)
|
||||||
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
|
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
|
||||||
# Limit the number of processes to display (for the WebUI)
|
# Limit the number of processes to display (in the WebUI)
|
||||||
max_processes_display=25
|
max_processes_display=25
|
||||||
# Set the URL prefix (for the WebUI and the API)
|
# Options for WebUI
|
||||||
|
#------------------
|
||||||
|
# Set URL prefix for the WebUI and the API
|
||||||
# Example: url_prefix=/glances/ => http://localhost/glances/
|
# Example: url_prefix=/glances/ => http://localhost/glances/
|
||||||
# The final / is mandatory
|
# Note: The final / is mandatory
|
||||||
# Default is no prefix (/)
|
# Default is no prefix (/)
|
||||||
#url_prefix=/glances/
|
#url_prefix=/glances/
|
||||||
|
# Set root path for WebUI statics files
|
||||||
|
# Why ? On Debian system, WebUI statics files are not provided.
|
||||||
|
# You can download it in a specific folder
|
||||||
|
# thanks to https://github.com/nicolargo/glances/issues/2021
|
||||||
|
# then configure this folder with the webui_root_path key
|
||||||
|
# Default is folder where glances_restfull_api.py is hosted
|
||||||
|
#webui_root_path=
|
||||||
|
# CORS options
|
||||||
|
# Comma separated list of origins that should be permitted to make cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_origins=*
|
||||||
|
# Indicate that cookies should be supported for cross-origin requests.
|
||||||
|
# Default is True
|
||||||
|
#cors_credentials=True
|
||||||
|
# Comma separated list of HTTP methods that should be allowed for cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_methods=*
|
||||||
|
# Comma separated list of HTTP request headers that should be supported for cross-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_headers=*
|
||||||
|
|
||||||
Each plugin, export module, and application monitoring process (AMP) can
|
Each plugin, export module, and application monitoring process (AMP) can
|
||||||
have a section. Below is an example for the CPU plugin:
|
have a section. Below is an example for the CPU plugin:
|
||||||
|
@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|||||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||||
..
|
..
|
||||||
.TH "GLANCES" "1" "May 07, 2024" "4.0.0_rc02" "Glances"
|
.TH "GLANCES" "1" "Jun 29, 2024" "4.2.0_beta01" "Glances"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
glances \- An eye on your system
|
glances \- An eye on your system
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -585,6 +585,15 @@ T} T{
|
|||||||
%APPDATA%\eglances\eglances.conf
|
%APPDATA%\eglances\eglances.conf
|
||||||
T}
|
T}
|
||||||
_
|
_
|
||||||
|
T{
|
||||||
|
\fBAll\fP
|
||||||
|
T} T{
|
||||||
|
.INDENT 0.0
|
||||||
|
.IP \(bu 2
|
||||||
|
<venv_root_folder>/share/doc/glances/
|
||||||
|
.UNINDENT
|
||||||
|
T}
|
||||||
|
_
|
||||||
.TE
|
.TE
|
||||||
.INDENT 0.0
|
.INDENT 0.0
|
||||||
.IP \(bu 2
|
.IP \(bu 2
|
||||||
@ -632,17 +641,41 @@ than a second one concerning the user interface:
|
|||||||
.nf
|
.nf
|
||||||
.ft C
|
.ft C
|
||||||
[outputs]
|
[outputs]
|
||||||
|
# Options for all UIs
|
||||||
|
#\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
|
||||||
# Separator in the Curses and WebUI interface (between top and others plugins)
|
# Separator in the Curses and WebUI interface (between top and others plugins)
|
||||||
separator=True
|
separator=True
|
||||||
# Set the the Curses and WebUI interface left menu plugin list (comma\-separated)
|
# Set the the Curses and WebUI interface left menu plugin list (comma\-separated)
|
||||||
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
|
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
|
||||||
# Limit the number of processes to display (for the WebUI)
|
# Limit the number of processes to display (in the WebUI)
|
||||||
max_processes_display=25
|
max_processes_display=25
|
||||||
# Set the URL prefix (for the WebUI and the API)
|
# Options for WebUI
|
||||||
|
#\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
|
||||||
|
# Set URL prefix for the WebUI and the API
|
||||||
# Example: url_prefix=/glances/ => http://localhost/glances/
|
# Example: url_prefix=/glances/ => http://localhost/glances/
|
||||||
# The final / is mandatory
|
# Note: The final / is mandatory
|
||||||
# Default is no prefix (/)
|
# Default is no prefix (/)
|
||||||
#url_prefix=/glances/
|
#url_prefix=/glances/
|
||||||
|
# Set root path for WebUI statics files
|
||||||
|
# Why ? On Debian system, WebUI statics files are not provided.
|
||||||
|
# You can download it in a specific folder
|
||||||
|
# thanks to https://github.com/nicolargo/glances/issues/2021
|
||||||
|
# then configure this folder with the webui_root_path key
|
||||||
|
# Default is folder where glances_restfull_api.py is hosted
|
||||||
|
#webui_root_path=
|
||||||
|
# CORS options
|
||||||
|
# Comma separated list of origins that should be permitted to make cross\-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_origins=*
|
||||||
|
# Indicate that cookies should be supported for cross\-origin requests.
|
||||||
|
# Default is True
|
||||||
|
#cors_credentials=True
|
||||||
|
# Comma separated list of HTTP methods that should be allowed for cross\-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_methods=*
|
||||||
|
# Comma separated list of HTTP request headers that should be supported for cross\-origin requests.
|
||||||
|
# Default is *
|
||||||
|
#cors_headers=*
|
||||||
.ft P
|
.ft P
|
||||||
.fi
|
.fi
|
||||||
.UNINDENT
|
.UNINDENT
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -11,16 +10,16 @@
|
|||||||
"""Init the Glances software."""
|
"""Init the Glances software."""
|
||||||
|
|
||||||
# Import system libs
|
# Import system libs
|
||||||
import tracemalloc
|
|
||||||
import locale
|
import locale
|
||||||
import platform
|
import platform
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
import tracemalloc
|
||||||
|
|
||||||
# Global name
|
# Global name
|
||||||
# Version should start and end with a numerical char
|
# Version should start and end with a numerical char
|
||||||
# See https://packaging.python.org/specifications/core-metadata/#version
|
# See https://packaging.python.org/specifications/core-metadata/#version
|
||||||
__version__ = '4.0.0_rc02'
|
__version__ = '4.2.0_beta01'
|
||||||
__apiversion__ = '4'
|
__apiversion__ = '4'
|
||||||
__author__ = 'Nicolas Hennion <nicolas@nicolargo.com>'
|
__author__ = 'Nicolas Hennion <nicolas@nicolargo.com>'
|
||||||
__license__ = 'LGPLv3'
|
__license__ = 'LGPLv3'
|
||||||
@ -44,11 +43,6 @@ try:
|
|||||||
except locale.Error:
|
except locale.Error:
|
||||||
print("Warning: Unable to set locale. Expect encoding problems.")
|
print("Warning: Unable to set locale. Expect encoding problems.")
|
||||||
|
|
||||||
# Check Python version
|
|
||||||
if sys.version_info < (3, 4):
|
|
||||||
print('Glances requires at least Python 3.4 to run.')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Check psutil version
|
# Check psutil version
|
||||||
psutil_min_version = (5, 3, 0)
|
psutil_min_version = (5, 3, 0)
|
||||||
psutil_version_info = tuple([int(num) for num in psutil_version.split('.')])
|
psutil_version_info = tuple([int(num) for num in psutil_version.split('.')])
|
||||||
@ -56,11 +50,12 @@ if psutil_version_info < psutil_min_version:
|
|||||||
print('psutil 5.3.0 or higher is needed. Glances cannot start.')
|
print('psutil 5.3.0 or higher is needed. Glances cannot start.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# Trac malloc is only available on Python 3.4 or higher
|
# Trac malloc is only available on Python 3.4 or higher
|
||||||
|
|
||||||
|
|
||||||
def __signal_handler(signal, frame):
|
def __signal_handler(signal, frame):
|
||||||
logger.debug("Signal {} catched".format(signal))
|
logger.debug(f"Signal {signal} caught")
|
||||||
end()
|
end()
|
||||||
|
|
||||||
|
|
||||||
@ -103,20 +98,16 @@ def start(config, args):
|
|||||||
from glances.webserver import GlancesWebServer as GlancesMode
|
from glances.webserver import GlancesWebServer as GlancesMode
|
||||||
|
|
||||||
# Init the mode
|
# Init the mode
|
||||||
logger.info("Start {} mode".format(GlancesMode.__name__))
|
logger.info(f"Start {GlancesMode.__name__} mode")
|
||||||
mode = GlancesMode(config=config, args=args)
|
mode = GlancesMode(config=config, args=args)
|
||||||
|
|
||||||
# Start the main loop
|
# Start the main loop
|
||||||
logger.debug("Glances started in {} seconds".format(start_duration.get()))
|
logger.debug(f"Glances started in {start_duration.get()} seconds")
|
||||||
if args.stop_after:
|
if args.stop_after:
|
||||||
logger.info('Glances will be stopped in ~{} seconds'.format(args.stop_after * args.time))
|
logger.info(f'Glances will be stopped in ~{args.stop_after * args.time} seconds')
|
||||||
|
|
||||||
if args.memory_leak:
|
if args.memory_leak:
|
||||||
print(
|
print(f'Memory leak detection, please wait ~{args.stop_after * args.time * args.memory_leak * 2} seconds...')
|
||||||
'Memory leak detection, please wait ~{} seconds...'.format(
|
|
||||||
args.stop_after * args.time * args.memory_leak * 2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# First run without dump to fill the memory
|
# First run without dump to fill the memory
|
||||||
mode.serve_n(args.stop_after)
|
mode.serve_n(args.stop_after)
|
||||||
# Then start the memory-leak loop
|
# Then start the memory-leak loop
|
||||||
@ -133,7 +124,7 @@ def start(config, args):
|
|||||||
snapshot_end = tracemalloc.take_snapshot()
|
snapshot_end = tracemalloc.take_snapshot()
|
||||||
snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename')
|
snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename')
|
||||||
memory_leak = sum([s.size_diff for s in snapshot_diff])
|
memory_leak = sum([s.size_diff for s in snapshot_diff])
|
||||||
print("Memory consumption: {0:.1f}KB (see log for details)".format(memory_leak / 1000))
|
print(f"Memory consumption: {memory_leak / 1000:.1f}KB (see log for details)")
|
||||||
logger.info("Memory consumption (top 5):")
|
logger.info("Memory consumption (top 5):")
|
||||||
for stat in snapshot_diff[:5]:
|
for stat in snapshot_diff[:5]:
|
||||||
logger.info(stat)
|
logger.info(stat)
|
||||||
@ -165,12 +156,10 @@ def main():
|
|||||||
signal.signal(sig, __signal_handler)
|
signal.signal(sig, __signal_handler)
|
||||||
|
|
||||||
# Log Glances and psutil version
|
# Log Glances and psutil version
|
||||||
logger.info('Start Glances {}'.format(__version__))
|
logger.info(f'Start Glances {__version__}')
|
||||||
logger.info(
|
python_impl = platform.python_implementation()
|
||||||
'{} {} ({}) and psutil {} detected'.format(
|
python_ver = platform.python_version()
|
||||||
platform.python_implementation(), platform.python_version(), sys.executable, psutil_version
|
logger.info(f'{python_impl} {python_ver} ({sys.executable}) and psutil {psutil_version} detected')
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Share global var
|
# Share global var
|
||||||
global core
|
global core
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# Glances - An eye on your system
|
# Glances - An eye on your system
|
||||||
#
|
#
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -10,8 +9,8 @@
|
|||||||
"""Manage on alert actions."""
|
"""Manage on alert actions."""
|
||||||
|
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
from glances.timer import Timer
|
|
||||||
from glances.secure import secure_popen
|
from glances.secure import secure_popen
|
||||||
|
from glances.timer import Timer
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import chevron
|
import chevron
|
||||||
@ -22,7 +21,7 @@ else:
|
|||||||
chevron_tag = True
|
chevron_tag = True
|
||||||
|
|
||||||
|
|
||||||
class GlancesActions(object):
|
class GlancesActions:
|
||||||
"""This class manage action if an alert is reached."""
|
"""This class manage action if an alert is reached."""
|
||||||
|
|
||||||
def __init__(self, args=None):
|
def __init__(self, args=None):
|
||||||
@ -80,13 +79,13 @@ class GlancesActions(object):
|
|||||||
else:
|
else:
|
||||||
cmd_full = cmd
|
cmd_full = cmd
|
||||||
# Execute the action
|
# Execute the action
|
||||||
logger.info("Action triggered for {} ({}): {}".format(stat_name, criticality, cmd_full))
|
logger.info(f"Action triggered for {stat_name} ({criticality}): {cmd_full}")
|
||||||
try:
|
try:
|
||||||
ret = secure_popen(cmd_full)
|
ret = secure_popen(cmd_full)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logger.error("Action error for {} ({}): {}".format(stat_name, criticality, e))
|
logger.error(f"Action error for {stat_name} ({criticality}): {e}")
|
||||||
else:
|
else:
|
||||||
logger.debug("Action result for {} ({}): {}".format(stat_name, criticality, ret))
|
logger.debug(f"Action result for {stat_name} ({criticality}): {ret}")
|
||||||
|
|
||||||
self.set(stat_name, criticality)
|
self.set(stat_name, criticality)
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -23,11 +22,11 @@ If the *one_line* var is true then the AMP will be displayed in one line.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from glances.globals import u
|
from glances.globals import u
|
||||||
from glances.timer import Timer
|
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
|
from glances.timer import Timer
|
||||||
|
|
||||||
|
|
||||||
class GlancesAmp(object):
|
class GlancesAmp:
|
||||||
"""Main class for Glances AMP."""
|
"""Main class for Glances AMP."""
|
||||||
|
|
||||||
NAME = '?'
|
NAME = '?'
|
||||||
@ -38,7 +37,7 @@ class GlancesAmp(object):
|
|||||||
|
|
||||||
def __init__(self, name=None, args=None):
|
def __init__(self, name=None, args=None):
|
||||||
"""Init AMP class."""
|
"""Init AMP class."""
|
||||||
logger.debug("AMP - Init {} version {}".format(self.NAME, self.VERSION))
|
logger.debug(f"AMP - Init {self.NAME} version {self.VERSION}")
|
||||||
|
|
||||||
# AMP name (= module name without glances_)
|
# AMP name (= module name without glances_)
|
||||||
if name is None:
|
if name is None:
|
||||||
@ -74,7 +73,7 @@ class GlancesAmp(object):
|
|||||||
|
|
||||||
amp_section = 'amp_' + self.amp_name
|
amp_section = 'amp_' + self.amp_name
|
||||||
if hasattr(config, 'has_section') and config.has_section(amp_section):
|
if hasattr(config, 'has_section') and config.has_section(amp_section):
|
||||||
logger.debug("AMP - {}: Load configuration".format(self.NAME))
|
logger.debug(f"AMP - {self.NAME}: Load configuration")
|
||||||
for param, _ in config.items(amp_section):
|
for param, _ in config.items(amp_section):
|
||||||
try:
|
try:
|
||||||
self.configs[param] = config.get_float_value(amp_section, param)
|
self.configs[param] = config.get_float_value(amp_section, param)
|
||||||
@ -82,9 +81,9 @@ class GlancesAmp(object):
|
|||||||
self.configs[param] = config.get_value(amp_section, param).split(',')
|
self.configs[param] = config.get_value(amp_section, param).split(',')
|
||||||
if len(self.configs[param]) == 1:
|
if len(self.configs[param]) == 1:
|
||||||
self.configs[param] = self.configs[param][0]
|
self.configs[param] = self.configs[param][0]
|
||||||
logger.debug("AMP - {}: Load parameter: {} = {}".format(self.NAME, param, self.configs[param]))
|
logger.debug(f"AMP - {self.NAME}: Load parameter: {param} = {self.configs[param]}")
|
||||||
else:
|
else:
|
||||||
logger.debug("AMP - {}: Can not find section {} in the configuration file".format(self.NAME, self.amp_name))
|
logger.debug(f"AMP - {self.NAME}: Can not find section {self.amp_name} in the configuration file")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.enable():
|
if self.enable():
|
||||||
@ -92,13 +91,12 @@ class GlancesAmp(object):
|
|||||||
for k in ['refresh']:
|
for k in ['refresh']:
|
||||||
if k not in self.configs:
|
if k not in self.configs:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"AMP - {}: Can not find configuration key {} in section {} (the AMP will be disabled)".format(
|
f"AMP - {self.NAME}: Can not find configuration key {k} in section {self.amp_name} "
|
||||||
self.NAME, k, self.amp_name
|
f"(the AMP will be disabled)"
|
||||||
)
|
|
||||||
)
|
)
|
||||||
self.configs['enable'] = 'false'
|
self.configs['enable'] = 'false'
|
||||||
else:
|
else:
|
||||||
logger.debug("AMP - {} is disabled".format(self.NAME))
|
logger.debug(f"AMP - {self.NAME} is disabled")
|
||||||
|
|
||||||
# Init the count to 0
|
# Init the count to 0
|
||||||
self.configs['count'] = 0
|
self.configs['count'] = 0
|
||||||
@ -109,16 +107,14 @@ class GlancesAmp(object):
|
|||||||
"""Generic method to get the item in the AMP configuration"""
|
"""Generic method to get the item in the AMP configuration"""
|
||||||
if key in self.configs:
|
if key in self.configs:
|
||||||
return self.configs[key]
|
return self.configs[key]
|
||||||
else:
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
"""Return True|False if the AMP is enabled in the configuration file (enable=true|false)."""
|
"""Return True|False if the AMP is enabled in the configuration file (enable=true|false)."""
|
||||||
ret = self.get('enable')
|
ret = self.get('enable')
|
||||||
if ret is None:
|
if ret is None:
|
||||||
return False
|
return False
|
||||||
else:
|
return ret.lower().startswith('true')
|
||||||
return ret.lower().startswith('true')
|
|
||||||
|
|
||||||
def regex(self):
|
def regex(self):
|
||||||
"""Return regular expression used to identified the current application."""
|
"""Return regular expression used to identified the current application."""
|
||||||
@ -133,8 +129,7 @@ class GlancesAmp(object):
|
|||||||
ret = self.get('one_line')
|
ret = self.get('one_line')
|
||||||
if ret is None:
|
if ret is None:
|
||||||
return False
|
return False
|
||||||
else:
|
return ret.lower().startswith('true')
|
||||||
return ret.lower().startswith('true')
|
|
||||||
|
|
||||||
def time_until_refresh(self):
|
def time_until_refresh(self):
|
||||||
"""Return time in seconds until refresh."""
|
"""Return time in seconds until refresh."""
|
||||||
@ -193,5 +188,4 @@ class GlancesAmp(object):
|
|||||||
# Call the children update method
|
# Call the children update method
|
||||||
if self.should_update():
|
if self.should_update():
|
||||||
return self.update(process_list)
|
return self.update(process_list)
|
||||||
else:
|
return self.result()
|
||||||
return self.result()
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -25,11 +24,9 @@ one_line=false
|
|||||||
command=foo status
|
command=foo status
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from subprocess import check_output, STDOUT, CalledProcessError
|
|
||||||
|
|
||||||
from glances.globals import u, to_ascii
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.amps.amp import GlancesAmp
|
from glances.amps.amp import GlancesAmp
|
||||||
|
from glances.logger import logger
|
||||||
|
from glances.secure import secure_popen
|
||||||
|
|
||||||
|
|
||||||
class Amp(GlancesAmp):
|
class Amp(GlancesAmp):
|
||||||
@ -44,7 +41,7 @@ class Amp(GlancesAmp):
|
|||||||
def __init__(self, name=None, args=None):
|
def __init__(self, name=None, args=None):
|
||||||
"""Init the AMP."""
|
"""Init the AMP."""
|
||||||
self.NAME = name.capitalize()
|
self.NAME = name.capitalize()
|
||||||
super(Amp, self).__init__(name=name, args=args)
|
super().__init__(name=name, args=args)
|
||||||
|
|
||||||
def update(self, process_list):
|
def update(self, process_list):
|
||||||
"""Update the AMP"""
|
"""Update the AMP"""
|
||||||
@ -54,7 +51,7 @@ class Amp(GlancesAmp):
|
|||||||
try:
|
try:
|
||||||
res = self.get('command')
|
res = self.get('command')
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logger.debug('{}: Error while executing command ({})'.format(self.NAME, e))
|
logger.debug(f'{self.NAME}: Error while executing command ({e})')
|
||||||
return self.result()
|
return self.result()
|
||||||
# No command found, use default message
|
# No command found, use default message
|
||||||
if res is None:
|
if res is None:
|
||||||
@ -69,10 +66,7 @@ class Amp(GlancesAmp):
|
|||||||
# Run command(s)
|
# Run command(s)
|
||||||
# Comma separated commands can be executed
|
# Comma separated commands can be executed
|
||||||
try:
|
try:
|
||||||
msg = ''
|
self.set_result(secure_popen(res).rstrip())
|
||||||
for cmd in res.split(';'):
|
except Exception as e:
|
||||||
msg += u(check_output(cmd.split(), stderr=STDOUT))
|
|
||||||
self.set_result(to_ascii(msg.rstrip()))
|
|
||||||
except CalledProcessError as e:
|
|
||||||
self.set_result(e.output)
|
self.set_result(e.output)
|
||||||
return self.result()
|
return self.result()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -46,8 +45,8 @@ status_url=http://localhost/nginx_status
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.amps.amp import GlancesAmp
|
from glances.amps.amp import GlancesAmp
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Amp(GlancesAmp):
|
class Amp(GlancesAmp):
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -35,11 +34,11 @@ one_line=true
|
|||||||
systemctl_cmd=/usr/bin/systemctl --plain
|
systemctl_cmd=/usr/bin/systemctl --plain
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from subprocess import check_output, CalledProcessError
|
from subprocess import CalledProcessError, check_output
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.globals import iteritems, to_ascii
|
|
||||||
from glances.amps.amp import GlancesAmp
|
from glances.amps.amp import GlancesAmp
|
||||||
|
from glances.globals import iteritems, to_ascii
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Amp(GlancesAmp):
|
class Amp(GlancesAmp):
|
||||||
@ -62,7 +61,7 @@ class Amp(GlancesAmp):
|
|||||||
try:
|
try:
|
||||||
res = check_output(self.get('systemctl_cmd').split())
|
res = check_output(self.get('systemctl_cmd').split())
|
||||||
except (OSError, CalledProcessError) as e:
|
except (OSError, CalledProcessError) as e:
|
||||||
logger.debug('{}: Error while executing systemctl ({})'.format(self.NAME, e))
|
logger.debug(f'{self.NAME}: Error while executing systemctl ({e})')
|
||||||
else:
|
else:
|
||||||
status = {}
|
status = {}
|
||||||
# For each line
|
# For each line
|
||||||
@ -79,7 +78,7 @@ class Amp(GlancesAmp):
|
|||||||
# Build the output (string) message
|
# Build the output (string) message
|
||||||
output = 'Services\n'
|
output = 'Services\n'
|
||||||
for k, v in iteritems(status):
|
for k, v in iteritems(status):
|
||||||
output += '{}: {}\n'.format(k, v)
|
output += f'{k}: {v}\n'
|
||||||
self.set_result(output, separator=' ')
|
self.set_result(output, separator=' ')
|
||||||
|
|
||||||
return self.result()
|
return self.result()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -34,11 +33,10 @@ one_line=true
|
|||||||
service_cmd=/usr/bin/service --status-all
|
service_cmd=/usr/bin/service --status-all
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from subprocess import check_output, STDOUT
|
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.globals import iteritems
|
|
||||||
from glances.amps.amp import GlancesAmp
|
from glances.amps.amp import GlancesAmp
|
||||||
|
from glances.globals import iteritems
|
||||||
|
from glances.logger import logger
|
||||||
|
from glances.secure import secure_popen
|
||||||
|
|
||||||
|
|
||||||
class Amp(GlancesAmp):
|
class Amp(GlancesAmp):
|
||||||
@ -59,9 +57,10 @@ class Amp(GlancesAmp):
|
|||||||
# Get the systemctl status
|
# Get the systemctl status
|
||||||
logger.debug('{}: Update stats using service {}'.format(self.NAME, self.get('service_cmd')))
|
logger.debug('{}: Update stats using service {}'.format(self.NAME, self.get('service_cmd')))
|
||||||
try:
|
try:
|
||||||
res = check_output(self.get('service_cmd').split(), stderr=STDOUT).decode('utf-8')
|
# res = check_output(self.get('service_cmd').split(), stderr=STDOUT).decode('utf-8')
|
||||||
except OSError as e:
|
res = secure_popen(self.get('service_cmd'))
|
||||||
logger.debug('{}: Error while executing service ({})'.format(self.NAME, e))
|
except Exception as e:
|
||||||
|
logger.debug(f'{self.NAME}: Error while executing service ({e})')
|
||||||
else:
|
else:
|
||||||
status = {'running': 0, 'stopped': 0, 'upstart': 0}
|
status = {'running': 0, 'stopped': 0, 'upstart': 0}
|
||||||
# For each line
|
# For each line
|
||||||
@ -79,7 +78,7 @@ class Amp(GlancesAmp):
|
|||||||
# Build the output (string) message
|
# Build the output (string) message
|
||||||
output = 'Services\n'
|
output = 'Services\n'
|
||||||
for k, v in iteritems(status):
|
for k, v in iteritems(status):
|
||||||
output += '{}: {}\n'.format(k, v)
|
output += f'{k}: {v}\n'
|
||||||
self.set_result(output, separator=' ')
|
self.set_result(output, separator=' ')
|
||||||
|
|
||||||
return self.result()
|
return self.result()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -13,12 +12,12 @@ import os
|
|||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from glances.globals import listkeys, iteritems, amps_path
|
from glances.globals import amps_path, iteritems, listkeys
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
from glances.processes import glances_processes
|
from glances.processes import glances_processes
|
||||||
|
|
||||||
|
|
||||||
class AmpsList(object):
|
class AmpsList:
|
||||||
"""This class describes the optional application monitoring process list.
|
"""This class describes the optional application monitoring process list.
|
||||||
|
|
||||||
The AMP list is a list of processes with a specific monitoring action.
|
The AMP list is a list of processes with a specific monitoring action.
|
||||||
@ -57,9 +56,9 @@ class AmpsList(object):
|
|||||||
try:
|
try:
|
||||||
amp = __import__(os.path.basename(amp_module))
|
amp = __import__(os.path.basename(amp_module))
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.warning("Missing Python Lib ({}), cannot load AMP {}".format(e, amp_name))
|
logger.warning(f"Missing Python Lib ({e}), cannot load AMP {amp_name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Cannot load AMP {} ({})".format(amp_name, e))
|
logger.warning(f"Cannot load AMP {amp_name} ({e})")
|
||||||
else:
|
else:
|
||||||
# Add the AMP to the dictionary
|
# Add the AMP to the dictionary
|
||||||
# The key is the AMP name
|
# The key is the AMP name
|
||||||
@ -69,7 +68,7 @@ class AmpsList(object):
|
|||||||
# Load the AMP configuration
|
# Load the AMP configuration
|
||||||
self.__amps_dict[amp_name].load_config(self.config)
|
self.__amps_dict[amp_name].load_config(self.config)
|
||||||
# Log AMPs list
|
# Log AMPs list
|
||||||
logger.debug("AMPs list: {}".format(self.getList()))
|
logger.debug(f"AMPs list: {self.getList()}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -108,7 +107,7 @@ class AmpsList(object):
|
|||||||
|
|
||||||
if len(amps_list) > 0:
|
if len(amps_list) > 0:
|
||||||
# At least one process is matching the regex
|
# At least one process is matching the regex
|
||||||
logger.debug("AMPS: {} processes {} detected ({})".format(len(amps_list), k, amps_list))
|
logger.debug(f"AMPS: {len(amps_list)} processes {k} detected ({amps_list})")
|
||||||
# Call the AMP update method
|
# Call the AMP update method
|
||||||
thread = threading.Thread(target=v.update_wrapper, args=[amps_list])
|
thread = threading.Thread(target=v.update_wrapper, args=[amps_list])
|
||||||
thread.start()
|
thread.start()
|
||||||
@ -140,7 +139,7 @@ class AmpsList(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except (TypeError, KeyError) as e:
|
except (TypeError, KeyError) as e:
|
||||||
logger.debug("Can not build AMPS list ({})".format(e))
|
logger.debug(f"Can not build AMPS list ({e})")
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,7 +11,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class GlancesAttribute(object):
|
class GlancesAttribute:
|
||||||
def __init__(self, name, description='', history_max_size=None):
|
def __init__(self, name, description='', history_max_size=None):
|
||||||
"""Init the attribute
|
"""Init the attribute
|
||||||
|
|
||||||
@ -66,8 +65,7 @@ class GlancesAttribute(object):
|
|||||||
def value(self):
|
def value(self):
|
||||||
if self.history_len() > 0:
|
if self.history_len() > 0:
|
||||||
return (self._value[1] - self.history_value()[1]) / (self._value[0] - self.history_value()[0])
|
return (self._value[1] - self.history_value()[1]) / (self._value[0] - self.history_value()[0])
|
||||||
else:
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def value(self, new_value):
|
def value(self, new_value):
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -16,7 +15,8 @@ from glances.globals import BSD
|
|||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from zeroconf import __version__ as __zeroconf_version, ServiceBrowser, ServiceInfo, Zeroconf
|
from zeroconf import ServiceBrowser, ServiceInfo, Zeroconf
|
||||||
|
from zeroconf import __version__ as __zeroconf_version
|
||||||
|
|
||||||
zeroconf_tag = True
|
zeroconf_tag = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -26,7 +26,7 @@ except ImportError:
|
|||||||
if zeroconf_tag:
|
if zeroconf_tag:
|
||||||
zeroconf_min_version = (0, 17, 0)
|
zeroconf_min_version = (0, 17, 0)
|
||||||
zeroconf_version = tuple([int(num) for num in __zeroconf_version.split('.')])
|
zeroconf_version = tuple([int(num) for num in __zeroconf_version.split('.')])
|
||||||
logger.debug("Zeroconf version {} detected.".format(__zeroconf_version))
|
logger.debug(f"Zeroconf version {__zeroconf_version} detected.")
|
||||||
if zeroconf_version < zeroconf_min_version:
|
if zeroconf_version < zeroconf_min_version:
|
||||||
logger.critical("Please install zeroconf 0.17 or higher.")
|
logger.critical("Please install zeroconf 0.17 or higher.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@ -34,10 +34,10 @@ if zeroconf_tag:
|
|||||||
# Global var
|
# Global var
|
||||||
# Recent versions of the zeroconf python package doesn't like a zeroconf type that ends with '._tcp.'.
|
# Recent versions of the zeroconf python package doesn't like a zeroconf type that ends with '._tcp.'.
|
||||||
# Correct issue: zeroconf problem with zeroconf_type = "_%s._tcp." % 'glances' #888
|
# Correct issue: zeroconf problem with zeroconf_type = "_%s._tcp." % 'glances' #888
|
||||||
zeroconf_type = "_%s._tcp.local." % 'glances'
|
zeroconf_type = "_{}._tcp.local.".format('glances')
|
||||||
|
|
||||||
|
|
||||||
class AutoDiscovered(object):
|
class AutoDiscovered:
|
||||||
"""Class to manage the auto discovered servers dict."""
|
"""Class to manage the auto discovered servers dict."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -66,7 +66,7 @@ class AutoDiscovered(object):
|
|||||||
'type': 'DYNAMIC',
|
'type': 'DYNAMIC',
|
||||||
} # Server type: 'STATIC' or 'DYNAMIC'
|
} # Server type: 'STATIC' or 'DYNAMIC'
|
||||||
self._server_list.append(new_server)
|
self._server_list.append(new_server)
|
||||||
logger.debug("Updated servers list (%s servers): %s" % (len(self._server_list), self._server_list))
|
logger.debug(f"Updated servers list ({len(self._server_list)} servers): {self._server_list}")
|
||||||
|
|
||||||
def remove_server(self, name):
|
def remove_server(self, name):
|
||||||
"""Remove a server from the dict."""
|
"""Remove a server from the dict."""
|
||||||
@ -74,13 +74,13 @@ class AutoDiscovered(object):
|
|||||||
if i['key'] == name:
|
if i['key'] == name:
|
||||||
try:
|
try:
|
||||||
self._server_list.remove(i)
|
self._server_list.remove(i)
|
||||||
logger.debug("Remove server %s from the list" % name)
|
logger.debug(f"Remove server {name} from the list")
|
||||||
logger.debug("Updated servers list (%s servers): %s" % (len(self._server_list), self._server_list))
|
logger.debug(f"Updated servers list ({len(self._server_list)} servers): {self._server_list}")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.error("Cannot remove server %s from the list" % name)
|
logger.error(f"Cannot remove server {name} from the list")
|
||||||
|
|
||||||
|
|
||||||
class GlancesAutoDiscoverListener(object):
|
class GlancesAutoDiscoverListener:
|
||||||
"""Zeroconf listener for Glances server."""
|
"""Zeroconf listener for Glances server."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -104,7 +104,7 @@ class GlancesAutoDiscoverListener(object):
|
|||||||
"""
|
"""
|
||||||
if srv_type != zeroconf_type:
|
if srv_type != zeroconf_type:
|
||||||
return False
|
return False
|
||||||
logger.debug("Check new Zeroconf server: %s / %s" % (srv_type, srv_name))
|
logger.debug(f"Check new Zeroconf server: {srv_type} / {srv_name}")
|
||||||
info = zeroconf.get_service_info(srv_type, srv_name)
|
info = zeroconf.get_service_info(srv_type, srv_name)
|
||||||
if info and (info.addresses or info.parsed_addresses):
|
if info and (info.addresses or info.parsed_addresses):
|
||||||
address = info.addresses[0] if info.addresses else info.parsed_addresses[0]
|
address = info.addresses[0] if info.addresses else info.parsed_addresses[0]
|
||||||
@ -113,7 +113,7 @@ class GlancesAutoDiscoverListener(object):
|
|||||||
|
|
||||||
# Add server to the global dict
|
# Add server to the global dict
|
||||||
self.servers.add_server(srv_name, new_server_ip, new_server_port)
|
self.servers.add_server(srv_name, new_server_ip, new_server_port)
|
||||||
logger.info("New Glances server detected (%s from %s:%s)" % (srv_name, new_server_ip, new_server_port))
|
logger.info(f"New Glances server detected ({srv_name} from {new_server_ip}:{new_server_port})")
|
||||||
else:
|
else:
|
||||||
logger.warning("New Glances server detected, but failed to be get Zeroconf ServiceInfo ")
|
logger.warning("New Glances server detected, but failed to be get Zeroconf ServiceInfo ")
|
||||||
return True
|
return True
|
||||||
@ -121,10 +121,10 @@ class GlancesAutoDiscoverListener(object):
|
|||||||
def remove_service(self, zeroconf, srv_type, srv_name):
|
def remove_service(self, zeroconf, srv_type, srv_name):
|
||||||
"""Remove the server from the list."""
|
"""Remove the server from the list."""
|
||||||
self.servers.remove_server(srv_name)
|
self.servers.remove_server(srv_name)
|
||||||
logger.info("Glances server %s removed from the autodetect list" % srv_name)
|
logger.info(f"Glances server {srv_name} removed from the autodetect list")
|
||||||
|
|
||||||
|
|
||||||
class GlancesAutoDiscoverServer(object):
|
class GlancesAutoDiscoverServer:
|
||||||
"""Implementation of the Zeroconf protocol (server side for the Glances client)."""
|
"""Implementation of the Zeroconf protocol (server side for the Glances client)."""
|
||||||
|
|
||||||
def __init__(self, args=None):
|
def __init__(self, args=None):
|
||||||
@ -132,8 +132,8 @@ class GlancesAutoDiscoverServer(object):
|
|||||||
logger.info("Init autodiscover mode (Zeroconf protocol)")
|
logger.info("Init autodiscover mode (Zeroconf protocol)")
|
||||||
try:
|
try:
|
||||||
self.zeroconf = Zeroconf()
|
self.zeroconf = Zeroconf()
|
||||||
except socket.error as e:
|
except OSError as e:
|
||||||
logger.error("Cannot start Zeroconf (%s)" % e)
|
logger.error(f"Cannot start Zeroconf ({e})")
|
||||||
self.zeroconf_enable_tag = False
|
self.zeroconf_enable_tag = False
|
||||||
else:
|
else:
|
||||||
self.listener = GlancesAutoDiscoverListener()
|
self.listener = GlancesAutoDiscoverListener()
|
||||||
@ -147,8 +147,7 @@ class GlancesAutoDiscoverServer(object):
|
|||||||
"""Return the current server list (dict of dict)."""
|
"""Return the current server list (dict of dict)."""
|
||||||
if zeroconf_tag and self.zeroconf_enable_tag:
|
if zeroconf_tag and self.zeroconf_enable_tag:
|
||||||
return self.listener.get_servers_list()
|
return self.listener.get_servers_list()
|
||||||
else:
|
return []
|
||||||
return []
|
|
||||||
|
|
||||||
def set_server(self, server_pos, key, value):
|
def set_server(self, server_pos, key, value):
|
||||||
"""Set the key to the value for the server_pos (position in the list)."""
|
"""Set the key to the value for the server_pos (position in the list)."""
|
||||||
@ -160,7 +159,7 @@ class GlancesAutoDiscoverServer(object):
|
|||||||
self.zeroconf.close()
|
self.zeroconf.close()
|
||||||
|
|
||||||
|
|
||||||
class GlancesAutoDiscoverClient(object):
|
class GlancesAutoDiscoverClient:
|
||||||
"""Implementation of the zeroconf protocol (client side for the Glances server)."""
|
"""Implementation of the zeroconf protocol (client side for the Glances server)."""
|
||||||
|
|
||||||
def __init__(self, hostname, args=None):
|
def __init__(self, hostname, args=None):
|
||||||
@ -168,8 +167,8 @@ class GlancesAutoDiscoverClient(object):
|
|||||||
zeroconf_bind_address = args.bind_address
|
zeroconf_bind_address = args.bind_address
|
||||||
try:
|
try:
|
||||||
self.zeroconf = Zeroconf()
|
self.zeroconf = Zeroconf()
|
||||||
except socket.error as e:
|
except OSError as e:
|
||||||
logger.error("Cannot start zeroconf: {}".format(e))
|
logger.error(f"Cannot start zeroconf: {e}")
|
||||||
|
|
||||||
# XXX *BSDs: Segmentation fault (core dumped)
|
# XXX *BSDs: Segmentation fault (core dumped)
|
||||||
# -- https://bitbucket.org/al45tair/netifaces/issues/15
|
# -- https://bitbucket.org/al45tair/netifaces/issues/15
|
||||||
@ -192,7 +191,7 @@ class GlancesAutoDiscoverClient(object):
|
|||||||
try:
|
try:
|
||||||
self.info = ServiceInfo(
|
self.info = ServiceInfo(
|
||||||
zeroconf_type,
|
zeroconf_type,
|
||||||
'{}:{}.{}'.format(hostname, args.port, zeroconf_type),
|
f'{hostname}:{args.port}.{zeroconf_type}',
|
||||||
address=socket.inet_pton(address_family, zeroconf_bind_address),
|
address=socket.inet_pton(address_family, zeroconf_bind_address),
|
||||||
port=args.port,
|
port=args.port,
|
||||||
weight=0,
|
weight=0,
|
||||||
@ -205,7 +204,7 @@ class GlancesAutoDiscoverClient(object):
|
|||||||
# address (only one address) is replaced by addresses (list of addresses)
|
# address (only one address) is replaced by addresses (list of addresses)
|
||||||
self.info = ServiceInfo(
|
self.info = ServiceInfo(
|
||||||
zeroconf_type,
|
zeroconf_type,
|
||||||
name='{}:{}.{}'.format(hostname, args.port, zeroconf_type),
|
name=f'{hostname}:{args.port}.{zeroconf_type}',
|
||||||
addresses=[socket.inet_pton(address_family, zeroconf_bind_address)],
|
addresses=[socket.inet_pton(address_family, zeroconf_bind_address)],
|
||||||
port=args.port,
|
port=args.port,
|
||||||
weight=0,
|
weight=0,
|
||||||
@ -216,9 +215,9 @@ class GlancesAutoDiscoverClient(object):
|
|||||||
try:
|
try:
|
||||||
self.zeroconf.register_service(self.info)
|
self.zeroconf.register_service(self.info)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error while announcing Glances server: {}".format(e))
|
logger.error(f"Error while announcing Glances server: {e}")
|
||||||
else:
|
else:
|
||||||
print("Announce the Glances server on the LAN (using {} IP address)".format(zeroconf_bind_address))
|
print(f"Announce the Glances server on the LAN (using {zeroconf_bind_address} IP address)")
|
||||||
else:
|
else:
|
||||||
logger.error("Cannot announce Glances server on the network: zeroconf library not found.")
|
logger.error("Cannot announce Glances server on the network: zeroconf library not found.")
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,16 +8,16 @@
|
|||||||
|
|
||||||
"""Manage the Glances client."""
|
"""Manage the Glances client."""
|
||||||
|
|
||||||
import ujson
|
|
||||||
import socket
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import orjson
|
||||||
|
|
||||||
from glances import __version__
|
from glances import __version__
|
||||||
from glances.globals import Fault, ProtocolError, ServerProxy, Transport
|
from glances.globals import Fault, ProtocolError, ServerProxy, Transport
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
from glances.stats_client import GlancesStatsClient
|
|
||||||
from glances.outputs.glances_curses import GlancesCursesClient
|
from glances.outputs.glances_curses import GlancesCursesClient
|
||||||
|
from glances.stats_client import GlancesStatsClient
|
||||||
from glances.timer import Counter
|
from glances.timer import Counter
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ class GlancesClientTransport(Transport):
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
|
|
||||||
class GlancesClient(object):
|
class GlancesClient:
|
||||||
"""This class creates and manages the TCP client."""
|
"""This class creates and manages the TCP client."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None, timeout=7, return_to_browser=False):
|
def __init__(self, config=None, args=None, timeout=7, return_to_browser=False):
|
||||||
@ -48,10 +47,12 @@ class GlancesClient(object):
|
|||||||
|
|
||||||
# Build the URI
|
# Build the URI
|
||||||
if args.password != "":
|
if args.password != "":
|
||||||
self.uri = 'http://{}:{}@{}:{}'.format(args.username, args.password, args.client, args.port)
|
self.uri = f'http://{args.username}:{args.password}@{args.client}:{args.port}'
|
||||||
else:
|
else:
|
||||||
self.uri = 'http://{}:{}'.format(args.client, args.port)
|
self.uri = f'http://{args.client}:{args.port}'
|
||||||
logger.debug("Try to connect to {}".format(self.uri))
|
|
||||||
|
# Avoid logging user credentials
|
||||||
|
logger.debug(f"Try to connect to 'http://{args.client}:{args.port}'")
|
||||||
|
|
||||||
# Try to connect to the URI
|
# Try to connect to the URI
|
||||||
transport = GlancesClientTransport()
|
transport = GlancesClientTransport()
|
||||||
@ -60,7 +61,7 @@ class GlancesClient(object):
|
|||||||
try:
|
try:
|
||||||
self.client = ServerProxy(self.uri, transport=transport)
|
self.client = ServerProxy(self.uri, transport=transport)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_and_exit("Client couldn't create socket {}: {}".format(self.uri, e))
|
self.log_and_exit(f"Client couldn't create socket {self.uri}: {e}")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def quiet(self):
|
def quiet(self):
|
||||||
@ -93,10 +94,10 @@ class GlancesClient(object):
|
|||||||
client_version = None
|
client_version = None
|
||||||
try:
|
try:
|
||||||
client_version = self.client.init()
|
client_version = self.client.init()
|
||||||
except socket.error as err:
|
except OSError as err:
|
||||||
# Fallback to SNMP
|
# Fallback to SNMP
|
||||||
self.client_mode = 'snmp'
|
self.client_mode = 'snmp'
|
||||||
logger.error("Connection to Glances server failed ({} {})".format(err.errno, err.strerror))
|
logger.error(f"Connection to Glances server failed ({err.errno} {err.strerror})")
|
||||||
fall_back_msg = 'No Glances server found. Trying fallback to SNMP...'
|
fall_back_msg = 'No Glances server found. Trying fallback to SNMP...'
|
||||||
if not self.return_to_browser:
|
if not self.return_to_browser:
|
||||||
print(fall_back_msg)
|
print(fall_back_msg)
|
||||||
@ -104,11 +105,11 @@ class GlancesClient(object):
|
|||||||
logger.info(fall_back_msg)
|
logger.info(fall_back_msg)
|
||||||
except ProtocolError as err:
|
except ProtocolError as err:
|
||||||
# Other errors
|
# Other errors
|
||||||
msg = "Connection to server {} failed".format(self.uri)
|
msg = f"Connection to server {self.uri} failed"
|
||||||
if err.errcode == 401:
|
if err.errcode == 401:
|
||||||
msg += " (Bad username/password)"
|
msg += " (Bad username/password)"
|
||||||
else:
|
else:
|
||||||
msg += " ({} {})".format(err.errcode, err.errmsg)
|
msg += f" ({err.errcode} {err.errmsg})"
|
||||||
self.log_and_exit(msg)
|
self.log_and_exit(msg)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -117,14 +118,12 @@ class GlancesClient(object):
|
|||||||
if __version__.split('.')[0] == client_version.split('.')[0]:
|
if __version__.split('.')[0] == client_version.split('.')[0]:
|
||||||
# Init stats
|
# Init stats
|
||||||
self.stats = GlancesStatsClient(config=self.config, args=self.args)
|
self.stats = GlancesStatsClient(config=self.config, args=self.args)
|
||||||
self.stats.set_plugins(ujson.loads(self.client.getAllPlugins()))
|
self.stats.set_plugins(orjson.loads(self.client.getAllPlugins()))
|
||||||
logger.debug("Client version: {} / Server version: {}".format(__version__, client_version))
|
logger.debug(f"Client version: {__version__} / Server version: {client_version}")
|
||||||
else:
|
else:
|
||||||
self.log_and_exit(
|
self.log_and_exit(
|
||||||
(
|
'Client and server not compatible: '
|
||||||
'Client and server not compatible: '
|
f'Client version: {__version__} / Server version: {client_version}'
|
||||||
'Client version: {} / Server version: {}'.format(__version__, client_version)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -180,12 +179,12 @@ class GlancesClient(object):
|
|||||||
"""Update stats from Glances/SNMP server."""
|
"""Update stats from Glances/SNMP server."""
|
||||||
if self.client_mode == 'glances':
|
if self.client_mode == 'glances':
|
||||||
return self.update_glances()
|
return self.update_glances()
|
||||||
elif self.client_mode == 'snmp':
|
if self.client_mode == 'snmp':
|
||||||
return self.update_snmp()
|
return self.update_snmp()
|
||||||
else:
|
|
||||||
self.end()
|
self.end()
|
||||||
logger.critical("Unknown server mode: {}".format(self.client_mode))
|
logger.critical(f"Unknown server mode: {self.client_mode}")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
def update_glances(self):
|
def update_glances(self):
|
||||||
"""Get stats from Glances server.
|
"""Get stats from Glances server.
|
||||||
@ -196,8 +195,8 @@ class GlancesClient(object):
|
|||||||
"""
|
"""
|
||||||
# Update the stats
|
# Update the stats
|
||||||
try:
|
try:
|
||||||
server_stats = ujson.loads(self.client.getAll())
|
server_stats = orjson.loads(self.client.getAll())
|
||||||
except socket.error:
|
except OSError:
|
||||||
# Client cannot get server stats
|
# Client cannot get server stats
|
||||||
return "Disconnected"
|
return "Disconnected"
|
||||||
except Fault:
|
except Fault:
|
||||||
@ -240,12 +239,12 @@ class GlancesClient(object):
|
|||||||
# Update the stats
|
# Update the stats
|
||||||
counter = Counter()
|
counter = Counter()
|
||||||
cs_status = self.update()
|
cs_status = self.update()
|
||||||
logger.debug('Stats updated duration: {} seconds'.format(counter.get()))
|
logger.debug(f'Stats updated duration: {counter.get()} seconds')
|
||||||
|
|
||||||
# Export stats using export modules
|
# Export stats using export modules
|
||||||
counter_export = Counter()
|
counter_export = Counter()
|
||||||
self.stats.export(self.stats)
|
self.stats.export(self.stats)
|
||||||
logger.debug('Stats exported duration: {} seconds'.format(counter_export.get()))
|
logger.debug(f'Stats exported duration: {counter_export.get()} seconds')
|
||||||
|
|
||||||
# Patch for issue1326 to avoid < 0 refresh
|
# Patch for issue1326 to avoid < 0 refresh
|
||||||
adapted_refresh = self.refresh_time - counter.get()
|
adapted_refresh = self.refresh_time - counter.get()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,20 +8,20 @@
|
|||||||
|
|
||||||
"""Manage the Glances client browser (list of Glances server)."""
|
"""Manage the Glances client browser (list of Glances server)."""
|
||||||
|
|
||||||
import ujson
|
|
||||||
import socket
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from glances.globals import Fault, ProtocolError, ServerProxy
|
import orjson
|
||||||
|
|
||||||
|
from glances.autodiscover import GlancesAutoDiscoverServer
|
||||||
from glances.client import GlancesClient, GlancesClientTransport
|
from glances.client import GlancesClient, GlancesClientTransport
|
||||||
from glances.logger import logger, LOG_FILENAME
|
from glances.globals import Fault, ProtocolError, ServerProxy
|
||||||
|
from glances.logger import LOG_FILENAME, logger
|
||||||
|
from glances.outputs.glances_curses_browser import GlancesCursesBrowser
|
||||||
from glances.password_list import GlancesPasswordList as GlancesPassword
|
from glances.password_list import GlancesPasswordList as GlancesPassword
|
||||||
from glances.static_list import GlancesStaticServer
|
from glances.static_list import GlancesStaticServer
|
||||||
from glances.autodiscover import GlancesAutoDiscoverServer
|
|
||||||
from glances.outputs.glances_curses_browser import GlancesCursesBrowser
|
|
||||||
|
|
||||||
|
|
||||||
class GlancesClientBrowser(object):
|
class GlancesClientBrowser:
|
||||||
"""This class creates and manages the TCP client browser (servers list)."""
|
"""This class creates and manages the TCP client browser (servers list)."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
@ -76,8 +75,7 @@ class GlancesClientBrowser(object):
|
|||||||
if clear_password is not None:
|
if clear_password is not None:
|
||||||
server['password'] = self.password.get_hash(clear_password)
|
server['password'] = self.password.get_hash(clear_password)
|
||||||
return 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port'])
|
return 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port'])
|
||||||
else:
|
return 'http://{}:{}'.format(server['ip'], server['port'])
|
||||||
return 'http://{}:{}'.format(server['ip'], server['port'])
|
|
||||||
|
|
||||||
def __update_stats(self, server):
|
def __update_stats(self, server):
|
||||||
"""Update stats for the given server (picked from the server list)"""
|
"""Update stats for the given server (picked from the server list)"""
|
||||||
@ -92,19 +90,23 @@ class GlancesClientBrowser(object):
|
|||||||
try:
|
try:
|
||||||
s = ServerProxy(uri, transport=t)
|
s = ServerProxy(uri, transport=t)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Client browser couldn't create socket ({})".format(e))
|
logger.warning(f"Client browser couldn't create socket ({e})")
|
||||||
else:
|
else:
|
||||||
# Mandatory stats
|
# Mandatory stats
|
||||||
try:
|
try:
|
||||||
# CPU%
|
# CPU%
|
||||||
cpu_percent = 100 - ujson.loads(s.getCpu())['idle']
|
# logger.info(f"CPU stats {s.getPlugin('cpu')}")
|
||||||
server['cpu_percent'] = '{:.1f}'.format(cpu_percent)
|
# logger.info(f"CPU views {s.getPluginView('cpu')}")
|
||||||
|
server['cpu_percent'] = orjson.loads(s.getPlugin('cpu'))['total']
|
||||||
|
server['cpu_percent_decoration'] = orjson.loads(s.getPluginView('cpu'))['total']['decoration']
|
||||||
# MEM%
|
# MEM%
|
||||||
server['mem_percent'] = ujson.loads(s.getMem())['percent']
|
server['mem_percent'] = orjson.loads(s.getPlugin('mem'))['percent']
|
||||||
|
server['mem_percent_decoration'] = orjson.loads(s.getPluginView('mem'))['percent']['decoration']
|
||||||
# OS (Human Readable name)
|
# OS (Human Readable name)
|
||||||
server['hr_name'] = ujson.loads(s.getSystem())['hr_name']
|
server['hr_name'] = orjson.loads(s.getPlugin('system'))['hr_name']
|
||||||
except (socket.error, Fault, KeyError) as e:
|
server['hr_name_decoration'] = 'DEFAULT'
|
||||||
logger.debug("Error while grabbing stats form server ({})".format(e))
|
except (OSError, Fault, KeyError) as e:
|
||||||
|
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||||
server['status'] = 'OFFLINE'
|
server['status'] = 'OFFLINE'
|
||||||
except ProtocolError as e:
|
except ProtocolError as e:
|
||||||
if e.errcode == 401:
|
if e.errcode == 401:
|
||||||
@ -114,7 +116,7 @@ class GlancesClientBrowser(object):
|
|||||||
server['status'] = 'PROTECTED'
|
server['status'] = 'PROTECTED'
|
||||||
else:
|
else:
|
||||||
server['status'] = 'OFFLINE'
|
server['status'] = 'OFFLINE'
|
||||||
logger.debug("Cannot grab stats from server ({} {})".format(e.errcode, e.errmsg))
|
logger.debug(f"Cannot grab stats from server ({e.errcode} {e.errmsg})")
|
||||||
else:
|
else:
|
||||||
# Status
|
# Status
|
||||||
server['status'] = 'ONLINE'
|
server['status'] = 'ONLINE'
|
||||||
@ -122,17 +124,17 @@ class GlancesClientBrowser(object):
|
|||||||
# Optional stats (load is not available on Windows OS)
|
# Optional stats (load is not available on Windows OS)
|
||||||
try:
|
try:
|
||||||
# LOAD
|
# LOAD
|
||||||
load_min5 = ujson.loads(s.getLoad())['min5']
|
server['load_min5'] = round(orjson.loads(s.getPlugin('load'))['min5'], 1)
|
||||||
server['load_min5'] = '{:.2f}'.format(load_min5)
|
server['load_min5_decoration'] = orjson.loads(s.getPluginView('load'))['min5']['decoration']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Error while grabbing stats form server ({})".format(e))
|
logger.warning(f"Error while grabbing stats form server ({e})")
|
||||||
|
|
||||||
return server
|
return server
|
||||||
|
|
||||||
def __display_server(self, server):
|
def __display_server(self, server):
|
||||||
"""Connect and display the given server"""
|
"""Connect and display the given server"""
|
||||||
# Display the Glances client for the selected server
|
# Display the Glances client for the selected server
|
||||||
logger.debug("Selected server {}".format(server))
|
logger.debug(f"Selected server {server}")
|
||||||
|
|
||||||
# Connection can take time
|
# Connection can take time
|
||||||
# Display a popup
|
# Display a popup
|
||||||
@ -146,7 +148,7 @@ class GlancesClientBrowser(object):
|
|||||||
# Else, the password should be enter by the user
|
# Else, the password should be enter by the user
|
||||||
# Display a popup to enter password
|
# Display a popup to enter password
|
||||||
clear_password = self.screen.display_popup(
|
clear_password = self.screen.display_popup(
|
||||||
'Password needed for {}: '.format(server['name']), is_input=True
|
'Password needed for {}: '.format(server['name']), popup_type='input', is_password=True
|
||||||
)
|
)
|
||||||
# Store the password for the selected server
|
# Store the password for the selected server
|
||||||
if clear_password is not None:
|
if clear_password is not None:
|
||||||
@ -201,7 +203,7 @@ class GlancesClientBrowser(object):
|
|||||||
# For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...)
|
# For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...)
|
||||||
thread_list = {}
|
thread_list = {}
|
||||||
while not self.screen.is_end:
|
while not self.screen.is_end:
|
||||||
logger.debug("Iter through the following server list: {}".format(self.get_servers_list()))
|
logger.debug(f"Iter through the following server list: {self.get_servers_list()}")
|
||||||
for v in self.get_servers_list():
|
for v in self.get_servers_list():
|
||||||
key = v["key"]
|
key = v["key"]
|
||||||
thread = thread_list.get(key, None)
|
thread = thread_list.get(key, None)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,13 +8,13 @@
|
|||||||
|
|
||||||
"""Manage the configuration file."""
|
"""Manage the configuration file."""
|
||||||
|
|
||||||
import os
|
import builtins
|
||||||
import sys
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
from io import open
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
from glances.globals import ConfigParser, NoOptionError, NoSectionError, system_exec, BSD, LINUX, MACOS, SUNOS, WINDOWS
|
from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS, ConfigParser, NoOptionError, NoSectionError, system_exec
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
@ -82,19 +81,32 @@ def default_config_dir():
|
|||||||
- Linux, SunOS, *BSD, macOS: /usr/share/doc (as defined in the setup.py files)
|
- Linux, SunOS, *BSD, macOS: /usr/share/doc (as defined in the setup.py files)
|
||||||
- Windows: %APPDATA%\glances
|
- Windows: %APPDATA%\glances
|
||||||
"""
|
"""
|
||||||
|
path = []
|
||||||
|
# Add venv path (solve issue #2803)
|
||||||
|
if in_virtualenv():
|
||||||
|
path.append(os.path.join(sys.prefix, 'share', 'doc', 'glances'))
|
||||||
|
|
||||||
|
# Add others system path
|
||||||
if LINUX or SUNOS or BSD or MACOS:
|
if LINUX or SUNOS or BSD or MACOS:
|
||||||
path = '/usr/share/doc'
|
path.append('/usr/share/doc')
|
||||||
else:
|
else:
|
||||||
path = os.environ.get('APPDATA')
|
path.append(os.environ.get('APPDATA'))
|
||||||
if path is None:
|
|
||||||
path = ''
|
|
||||||
else:
|
|
||||||
path = os.path.join(path, 'glances')
|
|
||||||
|
|
||||||
return [path]
|
return path
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
def in_virtualenv():
|
||||||
|
# Source: https://stackoverflow.com/questions/1871549/how-to-determine-if-python-is-running-inside-a-virtualenv/1883251#1883251
|
||||||
|
return sys.prefix != get_base_prefix_compat()
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_prefix_compat():
|
||||||
|
"""Get base/real prefix, or sys.prefix if there is none."""
|
||||||
|
# Source: https://stackoverflow.com/questions/1871549/how-to-determine-if-python-is-running-inside-a-virtualenv/1883251#1883251
|
||||||
|
return getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
"""This class is used to access/read config file, if it exists.
|
"""This class is used to access/read config file, if it exists.
|
||||||
|
|
||||||
:param config_dir: the path to search for config file
|
:param config_dir: the path to search for config file
|
||||||
@ -105,6 +117,7 @@ class Config(object):
|
|||||||
self.config_dir = config_dir
|
self.config_dir = config_dir
|
||||||
self.config_filename = 'glances.conf'
|
self.config_filename = 'glances.conf'
|
||||||
self._loaded_config_file = None
|
self._loaded_config_file = None
|
||||||
|
self._config_file_paths = self.config_file_paths()
|
||||||
|
|
||||||
# Re pattern for optimize research of `foo`
|
# Re pattern for optimize research of `foo`
|
||||||
self.re_pattern = re.compile(r'(\`.+?\`)')
|
self.re_pattern = re.compile(r'(\`.+?\`)')
|
||||||
@ -152,16 +165,16 @@ class Config(object):
|
|||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
"""Read the config file, if it exists. Using defaults otherwise."""
|
"""Read the config file, if it exists. Using defaults otherwise."""
|
||||||
for config_file in self.config_file_paths():
|
for config_file in self._config_file_paths:
|
||||||
logger.debug('Search glances.conf file in {}'.format(config_file))
|
logger.debug(f'Search glances.conf file in {config_file}')
|
||||||
if os.path.exists(config_file):
|
if os.path.exists(config_file):
|
||||||
try:
|
try:
|
||||||
with open(config_file, encoding='utf-8') as f:
|
with builtins.open(config_file, encoding='utf-8') as f:
|
||||||
self.parser.read_file(f)
|
self.parser.read_file(f)
|
||||||
self.parser.read(f)
|
self.parser.read(f)
|
||||||
logger.info("Read configuration file '{}'".format(config_file))
|
logger.info(f"Read configuration file '{config_file}'")
|
||||||
except UnicodeDecodeError as err:
|
except UnicodeDecodeError as err:
|
||||||
logger.error("Can not read configuration file '{}': {}".format(config_file, err))
|
logger.error(f"Can not read configuration file '{config_file}': {err}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
# Save the loaded configuration file path (issue #374)
|
# Save the loaded configuration file path (issue #374)
|
||||||
self._loaded_config_file = config_file
|
self._loaded_config_file = config_file
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,47 +8,68 @@
|
|||||||
|
|
||||||
"""CPU percent stats shared between CPU and Quicklook plugins."""
|
"""CPU percent stats shared between CPU and Quicklook plugins."""
|
||||||
|
|
||||||
from glances.logger import logger
|
from typing import List, Optional, TypedDict
|
||||||
from glances.timer import Timer
|
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
|
from glances.logger import logger
|
||||||
|
from glances.timer import Timer
|
||||||
|
|
||||||
class CpuPercent(object):
|
__all__ = ["cpu_percent"]
|
||||||
|
|
||||||
|
|
||||||
|
class CpuInfo(TypedDict):
|
||||||
|
cpu_name: str
|
||||||
|
cpu_hz: Optional[float]
|
||||||
|
cpu_hz_current: Optional[float]
|
||||||
|
|
||||||
|
|
||||||
|
class PerCpuPercentInfo(TypedDict):
|
||||||
|
key: str
|
||||||
|
cpu_number: int
|
||||||
|
total: float
|
||||||
|
user: float
|
||||||
|
system: float
|
||||||
|
idle: float
|
||||||
|
nice: Optional[float]
|
||||||
|
iowait: Optional[float]
|
||||||
|
irq: Optional[float]
|
||||||
|
softirq: Optional[float]
|
||||||
|
steal: Optional[float]
|
||||||
|
guest: Optional[float]
|
||||||
|
guest_nice: Optional[float]
|
||||||
|
dpc: Optional[float]
|
||||||
|
interrupt: Optional[float]
|
||||||
|
|
||||||
|
|
||||||
|
class CpuPercent:
|
||||||
"""Get and store the CPU percent."""
|
"""Get and store the CPU percent."""
|
||||||
|
|
||||||
def __init__(self, cached_timer_cpu=3):
|
def __init__(self, cached_timer_cpu: int = 2):
|
||||||
self.cpu_info = {'cpu_name': None, 'cpu_hz_current': None, 'cpu_hz': None}
|
|
||||||
self.cpu_percent = 0
|
|
||||||
self.percpu_percent = []
|
|
||||||
|
|
||||||
# Get CPU name
|
|
||||||
self.__get_cpu_name()
|
|
||||||
|
|
||||||
# cached_timer_cpu is the minimum time interval between stats updates
|
# cached_timer_cpu is the minimum time interval between stats updates
|
||||||
# since last update is passed (will retrieve old cached info instead)
|
# since last update is passed (will retrieve old cached info instead)
|
||||||
self.cached_timer_cpu = cached_timer_cpu
|
self.cached_timer_cpu = cached_timer_cpu
|
||||||
self.timer_cpu = Timer(0)
|
|
||||||
self.timer_percpu = Timer(0)
|
|
||||||
|
|
||||||
# psutil.cpu_freq() consumes lots of CPU
|
# psutil.cpu_freq() consumes lots of CPU
|
||||||
# So refresh the stats every refresh*2 (6 seconds)
|
# So refresh CPU frequency stats every refresh * 2
|
||||||
self.cached_timer_cpu_info = cached_timer_cpu * 2
|
self.cached_timer_cpu_info = cached_timer_cpu * 2
|
||||||
|
|
||||||
|
# Get CPU name
|
||||||
self.timer_cpu_info = Timer(0)
|
self.timer_cpu_info = Timer(0)
|
||||||
|
self.cpu_info: CpuInfo = {'cpu_name': self.__get_cpu_name(), 'cpu_hz_current': None, 'cpu_hz': None}
|
||||||
|
|
||||||
|
# Warning from PsUtil documentation
|
||||||
|
# The first time this function is called with interval = 0.0 or None
|
||||||
|
# it will return a meaningless 0.0 value which you are supposed to ignore.
|
||||||
|
self.timer_cpu = Timer(0)
|
||||||
|
self.cpu_percent = self._compute_cpu()
|
||||||
|
self.timer_percpu = Timer(0)
|
||||||
|
self.percpu_percent = self._compute_percpu()
|
||||||
|
|
||||||
def get_key(self):
|
def get_key(self):
|
||||||
"""Return the key of the per CPU list."""
|
"""Return the key of the per CPU list."""
|
||||||
return 'cpu_number'
|
return 'cpu_number'
|
||||||
|
|
||||||
def get(self, percpu=False):
|
def get_info(self) -> CpuInfo:
|
||||||
"""Update and/or return the CPU using the psutil library.
|
|
||||||
If percpu, return the percpu stats"""
|
|
||||||
if percpu:
|
|
||||||
return self.__get_percpu()
|
|
||||||
else:
|
|
||||||
return self.__get_cpu()
|
|
||||||
|
|
||||||
def get_info(self):
|
|
||||||
"""Get additional information about the CPU"""
|
"""Get additional information about the CPU"""
|
||||||
# Never update more than 1 time per cached_timer_cpu_info
|
# Never update more than 1 time per cached_timer_cpu_info
|
||||||
if self.timer_cpu_info.finished() and hasattr(psutil, 'cpu_freq'):
|
if self.timer_cpu_info.finished() and hasattr(psutil, 'cpu_freq'):
|
||||||
@ -57,7 +77,7 @@ class CpuPercent(object):
|
|||||||
try:
|
try:
|
||||||
cpu_freq = psutil.cpu_freq()
|
cpu_freq = psutil.cpu_freq()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug('Can not grab CPU information ({})'.format(e))
|
logger.debug(f'Can not grab CPU information ({e})')
|
||||||
else:
|
else:
|
||||||
if hasattr(cpu_freq, 'current'):
|
if hasattr(cpu_freq, 'current'):
|
||||||
self.cpu_info['cpu_hz_current'] = cpu_freq.current
|
self.cpu_info['cpu_hz_current'] = cpu_freq.current
|
||||||
@ -71,59 +91,69 @@ class CpuPercent(object):
|
|||||||
self.timer_cpu_info.reset(duration=self.cached_timer_cpu_info)
|
self.timer_cpu_info.reset(duration=self.cached_timer_cpu_info)
|
||||||
return self.cpu_info
|
return self.cpu_info
|
||||||
|
|
||||||
def __get_cpu_name(self):
|
@staticmethod
|
||||||
|
def __get_cpu_name() -> str:
|
||||||
# Get the CPU name once from the /proc/cpuinfo file
|
# Get the CPU name once from the /proc/cpuinfo file
|
||||||
# TODO: Multisystem...
|
# Read the first line with the "model name" ("Model" for Raspberry Pi)
|
||||||
try:
|
try:
|
||||||
self.cpu_info['cpu_name'] = open('/proc/cpuinfo', 'r').readlines()[4].split(':')[1].strip()
|
cpuinfo_lines = open('/proc/cpuinfo').readlines()
|
||||||
except (FileNotFoundError, PermissionError, IndexError, KeyError, AttributeError):
|
except (FileNotFoundError, PermissionError):
|
||||||
self.cpu_info['cpu_name'] = 'CPU'
|
logger.debug("No permission to read '/proc/cpuinfo'")
|
||||||
return self.cpu_info['cpu_name']
|
return 'CPU'
|
||||||
|
|
||||||
def __get_cpu(self):
|
for line in cpuinfo_lines:
|
||||||
|
if line.startswith('model name') or line.startswith('Model') or line.startswith('cpu model'):
|
||||||
|
return line.split(':')[1].strip()
|
||||||
|
|
||||||
|
return 'CPU'
|
||||||
|
|
||||||
|
def get_cpu(self) -> float:
|
||||||
"""Update and/or return the CPU using the psutil library."""
|
"""Update and/or return the CPU using the psutil library."""
|
||||||
# Never update more than 1 time per cached_timer_cpu
|
# Never update more than 1 time per cached_timer_cpu
|
||||||
if self.timer_cpu.finished():
|
if self.timer_cpu.finished():
|
||||||
self.cpu_percent = psutil.cpu_percent(interval=0.0)
|
|
||||||
# Reset timer for cache
|
# Reset timer for cache
|
||||||
self.timer_cpu.reset(duration=self.cached_timer_cpu)
|
self.timer_cpu.reset(duration=self.cached_timer_cpu)
|
||||||
|
# Update the stats
|
||||||
|
self.cpu_percent = self._compute_cpu()
|
||||||
return self.cpu_percent
|
return self.cpu_percent
|
||||||
|
|
||||||
def __get_percpu(self):
|
@staticmethod
|
||||||
|
def _compute_cpu() -> float:
|
||||||
|
return psutil.cpu_percent(interval=0.0)
|
||||||
|
|
||||||
|
def get_percpu(self) -> List[PerCpuPercentInfo]:
|
||||||
"""Update and/or return the per CPU list using the psutil library."""
|
"""Update and/or return the per CPU list using the psutil library."""
|
||||||
# Never update more than 1 time per cached_timer_cpu
|
# Never update more than 1 time per cached_timer_cpu
|
||||||
if self.timer_percpu.finished():
|
if self.timer_percpu.finished():
|
||||||
self.percpu_percent = []
|
# Reset timer for cache
|
||||||
for cpu_number, cputimes in enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True)):
|
self.timer_percpu.reset(duration=self.cached_timer_cpu)
|
||||||
cpu = {
|
# Update stats
|
||||||
'key': self.get_key(),
|
self.percpu_percent = self._compute_percpu()
|
||||||
'cpu_number': cpu_number,
|
|
||||||
'total': round(100 - cputimes.idle, 1),
|
|
||||||
'user': cputimes.user,
|
|
||||||
'system': cputimes.system,
|
|
||||||
'idle': cputimes.idle,
|
|
||||||
}
|
|
||||||
# The following stats are for API purposes only
|
|
||||||
if hasattr(cputimes, 'nice'):
|
|
||||||
cpu['nice'] = cputimes.nice
|
|
||||||
if hasattr(cputimes, 'iowait'):
|
|
||||||
cpu['iowait'] = cputimes.iowait
|
|
||||||
if hasattr(cputimes, 'irq'):
|
|
||||||
cpu['irq'] = cputimes.irq
|
|
||||||
if hasattr(cputimes, 'softirq'):
|
|
||||||
cpu['softirq'] = cputimes.softirq
|
|
||||||
if hasattr(cputimes, 'steal'):
|
|
||||||
cpu['steal'] = cputimes.steal
|
|
||||||
if hasattr(cputimes, 'guest'):
|
|
||||||
cpu['guest'] = cputimes.guest
|
|
||||||
if hasattr(cputimes, 'guest_nice'):
|
|
||||||
cpu['guest_nice'] = cputimes.guest_nice
|
|
||||||
# Append new CPU to the list
|
|
||||||
self.percpu_percent.append(cpu)
|
|
||||||
# Reset timer for cache
|
|
||||||
self.timer_percpu.reset(duration=self.cached_timer_cpu)
|
|
||||||
return self.percpu_percent
|
return self.percpu_percent
|
||||||
|
|
||||||
|
def _compute_percpu(self) -> List[PerCpuPercentInfo]:
|
||||||
|
psutil_percpu = enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True))
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'key': self.get_key(),
|
||||||
|
'cpu_number': cpu_number,
|
||||||
|
'total': round(100 - cpu_times.idle, 1),
|
||||||
|
'user': cpu_times.user,
|
||||||
|
'system': cpu_times.system,
|
||||||
|
'idle': cpu_times.idle,
|
||||||
|
'nice': cpu_times.nice if hasattr(cpu_times, 'nice') else None,
|
||||||
|
'iowait': cpu_times.iowait if hasattr(cpu_times, 'iowait') else None,
|
||||||
|
'irq': cpu_times.irq if hasattr(cpu_times, 'irq') else None,
|
||||||
|
'softirq': cpu_times.softirq if hasattr(cpu_times, 'softirq') else None,
|
||||||
|
'steal': cpu_times.steal if hasattr(cpu_times, 'steal') else None,
|
||||||
|
'guest': cpu_times.guest if hasattr(cpu_times, 'guest') else None,
|
||||||
|
'guest_nice': cpu_times.steal if hasattr(cpu_times, 'guest_nice') else None,
|
||||||
|
'dpc': cpu_times.dpc if hasattr(cpu_times, 'dpc') else None,
|
||||||
|
'interrupt': cpu_times.interrupt if hasattr(cpu_times, 'interrupt') else None,
|
||||||
|
}
|
||||||
|
for cpu_number, cpu_times in psutil_percpu
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# CpuPercent instance shared between plugins
|
# CpuPercent instance shared between plugins
|
||||||
cpu_percent = CpuPercent()
|
cpu_percent = CpuPercent()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -8,7 +7,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
"""Manage Glances event class
|
"""Manage Glances event class
|
||||||
This class is a Pydantic data class for the Glances event.
|
|
||||||
|
This class is a data class for the Glances event.
|
||||||
|
|
||||||
event_state = "OK|CAREFUL|WARNING|CRITICAL"
|
event_state = "OK|CAREFUL|WARNING|CRITICAL"
|
||||||
event_type = "CPU*|LOAD|MEM|MON"
|
event_type = "CPU*|LOAD|MEM|MON"
|
||||||
@ -32,7 +32,13 @@ Item (or event) is defined by:
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pydantic.dataclasses import dataclass
|
from glances.logger import logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pydantic.dataclasses import dataclass
|
||||||
|
except ImportError as e:
|
||||||
|
logger.warning(f"Missing Python Lib ({e}), EventList will be skipping data validation")
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from glances.processes import sort_stats
|
from glances.processes import sort_stats
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -10,12 +9,12 @@
|
|||||||
"""Manage Glances events list (previously Glances logs in Glances < 3.1)."""
|
"""Manage Glances events list (previously Glances logs in Glances < 3.1)."""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from dataclasses import asdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pydantic import RootModel
|
|
||||||
|
|
||||||
|
from glances.event import GlancesEvent
|
||||||
from glances.processes import glances_processes
|
from glances.processes import glances_processes
|
||||||
from glances.thresholds import glances_thresholds
|
from glances.thresholds import glances_thresholds
|
||||||
from glances.event import GlancesEvent
|
|
||||||
|
|
||||||
# Static decision tree for the global alert message
|
# Static decision tree for the global alert message
|
||||||
# - msg: Message to be displayed (result of the decision tree)
|
# - msg: Message to be displayed (result of the decision tree)
|
||||||
@ -158,11 +157,10 @@ def build_global_message():
|
|||||||
if themax['weight'] >= themax['thresholds_min']:
|
if themax['weight'] >= themax['thresholds_min']:
|
||||||
# Check if the weight is > to the minimal threshold value
|
# Check if the weight is > to the minimal threshold value
|
||||||
return themax['msg']
|
return themax['msg']
|
||||||
else:
|
return tree[0]['msg']
|
||||||
return tree[0]['msg']
|
|
||||||
|
|
||||||
|
|
||||||
class GlancesEventsList(object):
|
class GlancesEventsList:
|
||||||
"""This class manages events inside the Glances software.
|
"""This class manages events inside the Glances software.
|
||||||
GlancesEventsList is a list of GlancesEvent.
|
GlancesEventsList is a list of GlancesEvent.
|
||||||
GlancesEvent is defined in the event.py file
|
GlancesEvent is defined in the event.py file
|
||||||
@ -201,7 +199,7 @@ class GlancesEventsList(object):
|
|||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
"""Return the RAW events list."""
|
"""Return the RAW events list."""
|
||||||
return [RootModel[GlancesEvent](e).model_dump() for e in self.events_list]
|
return [asdict(e) for e in self.events_list]
|
||||||
|
|
||||||
def len(self):
|
def len(self):
|
||||||
"""Return the number of events in the logs list."""
|
"""Return the number of events in the logs list."""
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -13,13 +12,12 @@ I am your father...
|
|||||||
...for all Glances exports IF.
|
...for all Glances exports IF.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from glances.globals import json_dumps
|
from glances.globals import NoOptionError, NoSectionError, iteritems, iterkeys, json_dumps
|
||||||
from glances.globals import NoOptionError, NoSectionError, iteritems, iterkeys
|
|
||||||
from glances.timer import Counter
|
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
|
from glances.timer import Counter
|
||||||
|
|
||||||
|
|
||||||
class GlancesExport(object):
|
class GlancesExport:
|
||||||
"""Main class for Glances export IF."""
|
"""Main class for Glances export IF."""
|
||||||
|
|
||||||
# List of non exportable plugins
|
# List of non exportable plugins
|
||||||
@ -40,7 +38,7 @@ class GlancesExport(object):
|
|||||||
"""Init the export class."""
|
"""Init the export class."""
|
||||||
# Export name
|
# Export name
|
||||||
self.export_name = self.__class__.__module__
|
self.export_name = self.__class__.__module__
|
||||||
logger.debug("Init export module %s" % self.export_name)
|
logger.debug(f"Init export module {self.export_name}")
|
||||||
|
|
||||||
# Init the config & args
|
# Init the config & args
|
||||||
self.config = config
|
self.config = config
|
||||||
@ -64,18 +62,16 @@ class GlancesExport(object):
|
|||||||
counter = Counter()
|
counter = Counter()
|
||||||
ret = fct(*args, **kw)
|
ret = fct(*args, **kw)
|
||||||
duration = counter.get()
|
duration = counter.get()
|
||||||
logger.debug(
|
class_name = args[0].__class__.__name__
|
||||||
"{} {} {} return {} in {} seconds".format(
|
class_module = args[0].__class__.__module__
|
||||||
args[0].__class__.__name__, args[0].__class__.__module__, fct.__name__, ret, duration
|
logger.debug(f"{class_name} {class_module} {fct.__name__} return {ret} in {duration} seconds")
|
||||||
)
|
|
||||||
)
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Close the export module."""
|
"""Close the export module."""
|
||||||
logger.debug("Finalise export interface %s" % self.export_name)
|
logger.debug(f"Finalise export interface {self.export_name}")
|
||||||
|
|
||||||
def load_conf(self, section, mandatories=['host', 'port'], options=None):
|
def load_conf(self, section, mandatories=['host', 'port'], options=None):
|
||||||
"""Load the export <section> configuration in the Glances configuration file.
|
"""Load the export <section> configuration in the Glances configuration file.
|
||||||
@ -96,10 +92,10 @@ class GlancesExport(object):
|
|||||||
for opt in mandatories:
|
for opt in mandatories:
|
||||||
setattr(self, opt, self.config.get_value(section, opt))
|
setattr(self, opt, self.config.get_value(section, opt))
|
||||||
except NoSectionError:
|
except NoSectionError:
|
||||||
logger.error("No {} configuration found".format(section))
|
logger.error(f"No {section} configuration found")
|
||||||
return False
|
return False
|
||||||
except NoOptionError as e:
|
except NoOptionError as e:
|
||||||
logger.error("Error in the {} configuration ({})".format(section, e))
|
logger.error(f"Error in the {section} configuration ({e})")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Load options
|
# Load options
|
||||||
@ -109,8 +105,8 @@ class GlancesExport(object):
|
|||||||
except NoOptionError:
|
except NoOptionError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
logger.debug("Load {} from the Glances configuration file".format(section))
|
logger.debug(f"Load {section} from the Glances configuration file")
|
||||||
logger.debug("{} parameters: {}".format(section, {opt: getattr(self, opt) for opt in mandatories + options}))
|
logger.debug(f"{section} parameters: {({opt: getattr(self, opt) for opt in mandatories + options})}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -120,11 +116,10 @@ class GlancesExport(object):
|
|||||||
try:
|
try:
|
||||||
ret = item[item['key']]
|
ret = item[item['key']]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.error("No 'key' available in {}".format(item))
|
logger.error(f"No 'key' available in {item}")
|
||||||
if isinstance(ret, list):
|
if isinstance(ret, list):
|
||||||
return ret[0]
|
return ret[0]
|
||||||
else:
|
return ret
|
||||||
return ret
|
|
||||||
|
|
||||||
def parse_tags(self, tags):
|
def parse_tags(self, tags):
|
||||||
"""Parse tags into a dict.
|
"""Parse tags into a dict.
|
||||||
@ -201,11 +196,10 @@ class GlancesExport(object):
|
|||||||
for key, value in sorted(iteritems(stats)):
|
for key, value in sorted(iteritems(stats)):
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
value = json_dumps(value)
|
value = json_dumps(value)
|
||||||
|
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
try:
|
value = ' '.join([str(v) for v in value])
|
||||||
value = value[0]
|
|
||||||
except IndexError:
|
|
||||||
value = ''
|
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
item_names, item_values = self.build_export(value)
|
item_names, item_values = self.build_export(value)
|
||||||
item_names = [pre_key + key.lower() + str(i) for i in item_names]
|
item_names = [pre_key + key.lower() + str(i) for i in item_names]
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -13,13 +12,13 @@ import sys
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
from glances.logger import logger
|
from cassandra import InvalidRequest
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
from cassandra.auth import PlainTextAuthProvider
|
from cassandra.auth import PlainTextAuthProvider
|
||||||
from cassandra.cluster import Cluster
|
from cassandra.cluster import Cluster
|
||||||
from cassandra.util import uuid_from_time
|
from cassandra.util import uuid_from_time
|
||||||
from cassandra import InvalidRequest
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -27,7 +26,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the Cassandra export IF."""
|
"""Init the Cassandra export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.keyspace = None
|
self.keyspace = None
|
||||||
@ -69,53 +68,52 @@ class Export(GlancesExport):
|
|||||||
)
|
)
|
||||||
session = cluster.connect()
|
session = cluster.connect()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Cannot connect to Cassandra cluster '%s:%s' (%s)" % (self.host, self.port, e))
|
logger.critical(f"Cannot connect to Cassandra cluster '{self.host}:{self.port}' ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
# Keyspace
|
# Keyspace
|
||||||
try:
|
try:
|
||||||
session.set_keyspace(self.keyspace)
|
session.set_keyspace(self.keyspace)
|
||||||
except InvalidRequest:
|
except InvalidRequest:
|
||||||
logger.info("Create keyspace {} on the Cassandra cluster".format(self.keyspace))
|
logger.info(f"Create keyspace {self.keyspace} on the Cassandra cluster")
|
||||||
c = "CREATE KEYSPACE %s WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '%s' }" % (
|
c = (
|
||||||
self.keyspace,
|
f"CREATE KEYSPACE {self.keyspace} WITH "
|
||||||
self.replication_factor,
|
f"replication = {{ 'class': 'SimpleStrategy', 'replication_factor': '{self.replication_factor}' }}"
|
||||||
)
|
)
|
||||||
session.execute(c)
|
session.execute(c)
|
||||||
session.set_keyspace(self.keyspace)
|
session.set_keyspace(self.keyspace)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Stats will be exported to Cassandra cluster {} ({}) in keyspace {}".format(
|
f"Stats will be exported to Cassandra cluster {cluster.metadata.cluster_name} "
|
||||||
cluster.metadata.cluster_name, cluster.metadata.all_hosts(), self.keyspace
|
f"({cluster.metadata.all_hosts()}) in keyspace {self.keyspace}"
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Table
|
# Table
|
||||||
try:
|
try:
|
||||||
session.execute(
|
session.execute(
|
||||||
"CREATE TABLE %s (plugin text, time timeuuid, stat map<text,float>, PRIMARY KEY (plugin, time)) \
|
f"CREATE TABLE {self.table} "
|
||||||
WITH CLUSTERING ORDER BY (time DESC)"
|
f"(plugin text, time timeuuid, stat map<text,float>, PRIMARY KEY (plugin, time)) "
|
||||||
% self.table
|
f"WITH CLUSTERING ORDER BY (time DESC)"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.debug("Cassandra table %s already exist" % self.table)
|
logger.debug(f"Cassandra table {self.table} already exist")
|
||||||
|
|
||||||
return cluster, session
|
return cluster, session
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Write the points to the Cassandra cluster."""
|
"""Write the points to the Cassandra cluster."""
|
||||||
logger.debug("Export {} stats to Cassandra".format(name))
|
logger.debug(f"Export {name} stats to Cassandra")
|
||||||
|
|
||||||
# Remove non number stats and convert all to float (for Boolean)
|
# Remove non number stats and convert all to float (for Boolean)
|
||||||
data = {k: float(v) for (k, v) in dict(zip(columns, points)).iteritems() if isinstance(v, Number)}
|
data = {k: float(v) for (k, v) in dict(zip(columns, points)).iteritems() if isinstance(v, Number)}
|
||||||
|
|
||||||
# Write input to the Cassandra table
|
# Write input to the Cassandra table
|
||||||
try:
|
try:
|
||||||
stmt = "INSERT INTO {} (plugin, time, stat) VALUES (?, ?, ?)".format(self.table)
|
stmt = f"INSERT INTO {self.table} (plugin, time, stat) VALUES (?, ?, ?)"
|
||||||
query = self.session.prepare(stmt)
|
query = self.session.prepare(stmt)
|
||||||
self.session.execute(query, (name, uuid_from_time(datetime.now()), data))
|
self.session.execute(query, (name, uuid_from_time(datetime.now()), data))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot export {} stats to Cassandra ({})".format(name, e))
|
logger.error(f"Cannot export {name} stats to Cassandra ({e})")
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Close the Cassandra export module."""
|
"""Close the Cassandra export module."""
|
||||||
@ -123,4 +121,4 @@ class Export(GlancesExport):
|
|||||||
self.session.shutdown()
|
self.session.shutdown()
|
||||||
self.cluster.shutdown()
|
self.cluster.shutdown()
|
||||||
# Call the father method
|
# Call the father method
|
||||||
super(Export, self).exit()
|
super().exit()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -20,18 +19,18 @@
|
|||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
import pycouchdb
|
import pycouchdb
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the CouchDB export module."""
|
"""This class manages the CouchDB export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the CouchDB export IF."""
|
"""Init the CouchDB export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Load the CouchDB configuration file section
|
# Load the CouchDB configuration file section
|
||||||
# User and Password are mandatory with CouchDB 3.0 and higher
|
# User and Password are mandatory with CouchDB 3.0 and higher
|
||||||
@ -48,15 +47,15 @@ class Export(GlancesExport):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# @TODO: https
|
# @TODO: https
|
||||||
server_uri = 'http://{}:{}@{}:{}/'.format(self.user, self.password, self.host, self.port)
|
server_uri = f'http://{self.user}:{self.password}@{self.host}:{self.port}/'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
s = pycouchdb.Server(server_uri)
|
s = pycouchdb.Server(server_uri)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Cannot connect to CouchDB server (%s)" % e)
|
logger.critical(f"Cannot connect to CouchDB server ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
logger.info("Connected to the CouchDB server version %s" % s.info()['version'])
|
logger.info("Connected to the CouchDB server version {}".format(s.info()['version']))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
s.database(self.db)
|
s.database(self.db)
|
||||||
@ -64,15 +63,15 @@ class Export(GlancesExport):
|
|||||||
# Database did not exist
|
# Database did not exist
|
||||||
# Create it...
|
# Create it...
|
||||||
s.create(self.db)
|
s.create(self.db)
|
||||||
logger.info("Create CouchDB database %s" % self.db)
|
logger.info(f"Create CouchDB database {self.db}")
|
||||||
else:
|
else:
|
||||||
logger.info("CouchDB database %s already exist" % self.db)
|
logger.info(f"CouchDB database {self.db} already exist")
|
||||||
|
|
||||||
return s.database(self.db)
|
return s.database(self.db)
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Write the points to the CouchDB server."""
|
"""Write the points to the CouchDB server."""
|
||||||
logger.debug("Export {} stats to CouchDB".format(name))
|
logger.debug(f"Export {name} stats to CouchDB")
|
||||||
|
|
||||||
# Create DB input
|
# Create DB input
|
||||||
data = dict(zip(columns, points))
|
data = dict(zip(columns, points))
|
||||||
@ -85,4 +84,4 @@ class Export(GlancesExport):
|
|||||||
try:
|
try:
|
||||||
self.client.save(data)
|
self.client.save(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot export {} stats to CouchDB ({})".format(name, e))
|
logger.error(f"Cannot export {name} stats to CouchDB ({e})")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,13 +8,13 @@
|
|||||||
|
|
||||||
"""CSV interface class."""
|
"""CSV interface class."""
|
||||||
|
|
||||||
import os.path
|
|
||||||
import csv
|
import csv
|
||||||
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -23,7 +22,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the CSV export IF."""
|
"""Init the CSV export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# CSV file name
|
# CSV file name
|
||||||
self.csv_filename = args.export_csv_file
|
self.csv_filename = args.export_csv_file
|
||||||
@ -42,8 +41,8 @@ class Export(GlancesExport):
|
|||||||
try:
|
try:
|
||||||
self.csv_file = open_csv_file(self.csv_filename, 'r')
|
self.csv_file = open_csv_file(self.csv_filename, 'r')
|
||||||
reader = csv.reader(self.csv_file)
|
reader = csv.reader(self.csv_file)
|
||||||
except IOError as e:
|
except OSError as e:
|
||||||
logger.critical("Cannot open existing CSV file: {}".format(e))
|
logger.critical(f"Cannot open existing CSV file: {e}")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
self.old_header = next(reader, None)
|
self.old_header = next(reader, None)
|
||||||
self.csv_file.close()
|
self.csv_file.close()
|
||||||
@ -51,11 +50,11 @@ class Export(GlancesExport):
|
|||||||
try:
|
try:
|
||||||
self.csv_file = open_csv_file(self.csv_filename, file_mode)
|
self.csv_file = open_csv_file(self.csv_filename, file_mode)
|
||||||
self.writer = csv.writer(self.csv_file)
|
self.writer = csv.writer(self.csv_file)
|
||||||
except IOError as e:
|
except OSError as e:
|
||||||
logger.critical("Cannot create the CSV file: {}".format(e))
|
logger.critical(f"Cannot create the CSV file: {e}")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
logger.info("Stats exported to CSV file: {}".format(self.csv_filename))
|
logger.info(f"Stats exported to CSV file: {self.csv_filename}")
|
||||||
|
|
||||||
self.export_enable = True
|
self.export_enable = True
|
||||||
|
|
||||||
@ -63,7 +62,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Close the CSV file."""
|
"""Close the CSV file."""
|
||||||
logger.debug("Finalise export interface %s" % self.export_name)
|
logger.debug(f"Finalise export interface {self.export_name}")
|
||||||
self.csv_file.close()
|
self.csv_file.close()
|
||||||
|
|
||||||
def update(self, stats):
|
def update(self, stats):
|
||||||
@ -95,8 +94,8 @@ class Export(GlancesExport):
|
|||||||
if self.old_header != csv_header and self.old_header is not None:
|
if self.old_header != csv_header and self.old_header is not None:
|
||||||
# Header are different, log an error and do not write data
|
# Header are different, log an error and do not write data
|
||||||
logger.error("Cannot append data to existing CSV file. Headers are different.")
|
logger.error("Cannot append data to existing CSV file. Headers are different.")
|
||||||
logger.debug("Old header: {}".format(self.old_header))
|
logger.debug(f"Old header: {self.old_header}")
|
||||||
logger.debug("New header: {}".format(csv_header))
|
logger.debug(f"New header: {csv_header}")
|
||||||
else:
|
else:
|
||||||
# Header are equals, ready to write data
|
# Header are equals, ready to write data
|
||||||
self.old_header = None
|
self.old_header = None
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,18 +11,18 @@
|
|||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
from elasticsearch import Elasticsearch, helpers
|
from elasticsearch import Elasticsearch, helpers
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the ElasticSearch (ES) export module."""
|
"""This class manages the ElasticSearch (ES) export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the ES export IF."""
|
"""Init the ES export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.index = None
|
self.index = None
|
||||||
@ -44,24 +43,22 @@ class Export(GlancesExport):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
es = Elasticsearch(hosts=['{}://{}:{}'.format(self.scheme, self.host, self.port)])
|
es = Elasticsearch(hosts=[f'{self.scheme}://{self.host}:{self.port}'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(
|
logger.critical(f"Cannot connect to ElasticSearch server {self.scheme}://{self.host}:{self.port} ({e})")
|
||||||
"Cannot connect to ElasticSearch server %s://%s:%s (%s)" % (self.scheme, self.host, self.port, e)
|
|
||||||
)
|
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
if not es.ping():
|
if not es.ping():
|
||||||
logger.critical("Cannot ping the ElasticSearch server %s://%s:%s" % (self.scheme, self.host, self.port))
|
logger.critical(f"Cannot ping the ElasticSearch server {self.scheme}://{self.host}:{self.port}")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
logger.info("Connected to the ElasticSearch server %s://%s:%s" % (self.scheme, self.host, self.port))
|
logger.info(f"Connected to the ElasticSearch server {self.scheme}://{self.host}:{self.port}")
|
||||||
|
|
||||||
return es
|
return es
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Write the points to the ES server."""
|
"""Write the points to the ES server."""
|
||||||
logger.debug("Export {} stats to ElasticSearch".format(name))
|
logger.debug(f"Export {name} stats to ElasticSearch")
|
||||||
|
|
||||||
# Generate index name with the index field + current day
|
# Generate index name with the index field + current day
|
||||||
index = '{}-{}'.format(self.index, datetime.utcnow().strftime("%Y.%m.%d"))
|
index = '{}-{}'.format(self.index, datetime.utcnow().strftime("%Y.%m.%d"))
|
||||||
@ -72,17 +69,17 @@ class Export(GlancesExport):
|
|||||||
dt_now = datetime.utcnow().isoformat('T')
|
dt_now = datetime.utcnow().isoformat('T')
|
||||||
action = {
|
action = {
|
||||||
"_index": index,
|
"_index": index,
|
||||||
"_id": '{}.{}'.format(name, dt_now),
|
"_id": f'{name}.{dt_now}',
|
||||||
"_type": 'glances-{}'.format(name),
|
"_type": f'glances-{name}',
|
||||||
"_source": {"plugin": name, "timestamp": dt_now},
|
"_source": {"plugin": name, "timestamp": dt_now},
|
||||||
}
|
}
|
||||||
action['_source'].update(zip(columns, [str(p) for p in points]))
|
action['_source'].update(zip(columns, [str(p) for p in points]))
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
|
|
||||||
logger.debug("Exporting the following object to elasticsearch: {}".format(action))
|
logger.debug(f"Exporting the following object to elasticsearch: {action}")
|
||||||
|
|
||||||
# Write input to the ES index
|
# Write input to the ES index
|
||||||
try:
|
try:
|
||||||
helpers.bulk(self.client, actions)
|
helpers.bulk(self.client, actions)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot export {} stats to ElasticSearch ({})".format(name, e))
|
logger.error(f"Cannot export {name} stats to ElasticSearch ({e})")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,17 +8,18 @@
|
|||||||
|
|
||||||
"""Graph exporter interface class."""
|
"""Graph exporter interface class."""
|
||||||
|
|
||||||
from pygal import DateTimeLine
|
|
||||||
import pygal.style
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
import errno
|
import errno
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import pygal.style
|
||||||
|
from pygal import DateTimeLine
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.globals import iteritems, time_serie_subsample
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
from glances.timer import Timer
|
from glances.timer import Timer
|
||||||
from glances.globals import iteritems, time_serie_subsample
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -27,36 +27,38 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the export IF."""
|
"""Init the export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Load the Graph configuration file section (is exists)
|
# Load the Graph configuration file section (is exists)
|
||||||
self.export_enable = self.load_conf('graph', options=['path', 'generate_every', 'width', 'height', 'style'])
|
self.export_enable = self.load_conf('graph', options=['path', 'generate_every', 'width', 'height', 'style'])
|
||||||
|
|
||||||
# Manage options (command line arguments overwrite configuration file)
|
# Manage options (command line arguments overwrite configuration file)
|
||||||
self.path = args.export_graph_path or self.path
|
self.path = args.export_graph_path or self.path
|
||||||
self.generate_every = int(getattr(self, 'generate_every', 0))
|
self.generate_every = int(getattr(self, 'generate_every', 0) or 0)
|
||||||
self.width = int(getattr(self, 'width', 800))
|
self.width = int(getattr(self, 'width', 800) or 800)
|
||||||
self.height = int(getattr(self, 'height', 600))
|
self.height = int(getattr(self, 'height', 600) or 600)
|
||||||
self.style = getattr(pygal.style, getattr(self, 'style', 'DarkStyle'), pygal.style.DarkStyle)
|
self.style = (
|
||||||
|
getattr(pygal.style, getattr(self, 'style', 'DarkStyle'), pygal.style.DarkStyle) or pygal.style.DarkStyle
|
||||||
|
)
|
||||||
|
|
||||||
# Create export folder
|
# Create export folder
|
||||||
try:
|
try:
|
||||||
os.makedirs(self.path)
|
os.makedirs(self.path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno != errno.EEXIST:
|
if e.errno != errno.EEXIST:
|
||||||
logger.critical("Cannot create the Graph output folder {} ({})".format(self.path, e))
|
logger.critical(f"Cannot create the Graph output folder {self.path} ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
# Check if output folder is writeable
|
# Check if output folder is writeable
|
||||||
try:
|
try:
|
||||||
tempfile.TemporaryFile(dir=self.path)
|
tempfile.TemporaryFile(dir=self.path)
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.critical("Graph output folder {} is not writeable".format(self.path))
|
logger.critical(f"Graph output folder {self.path} is not writeable")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
logger.info("Graphs will be created in the {} folder".format(self.path))
|
logger.info(f"Graphs will be created in the {self.path} folder")
|
||||||
if self.generate_every != 0:
|
if self.generate_every != 0:
|
||||||
logger.info("Graphs will be created automatically every {} seconds".format(self.generate_every))
|
logger.info(f"Graphs will be created automatically every {self.generate_every} seconds")
|
||||||
logger.info("or when 'g' key is pressed (only through the CLI interface)")
|
logger.info("or when 'g' key is pressed (only through the CLI interface)")
|
||||||
# Start the timer
|
# Start the timer
|
||||||
self._timer = Timer(self.generate_every)
|
self._timer = Timer(self.generate_every)
|
||||||
@ -66,7 +68,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Close the files."""
|
"""Close the files."""
|
||||||
logger.debug("Finalise export interface %s" % self.export_name)
|
logger.debug(f"Finalise export interface {self.export_name}")
|
||||||
|
|
||||||
def update(self, stats):
|
def update(self, stats):
|
||||||
"""Generate Graph file in the output folder."""
|
"""Generate Graph file in the output folder."""
|
||||||
@ -84,7 +86,7 @@ class Export(GlancesExport):
|
|||||||
if plugin_name in self.plugins_to_export(stats):
|
if plugin_name in self.plugins_to_export(stats):
|
||||||
self.export(plugin_name, plugin.get_export_history())
|
self.export(plugin_name, plugin.get_export_history())
|
||||||
|
|
||||||
logger.info("Graphs created in {}".format(self.path))
|
logger.info(f"Graphs created in {self.path}")
|
||||||
self.args.generate_graph = False
|
self.args.generate_graph = False
|
||||||
|
|
||||||
def export(self, title, data):
|
def export(self, title, data):
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,18 +11,18 @@
|
|||||||
import sys
|
import sys
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
from graphitesend import GraphiteClient
|
from graphitesend import GraphiteClient
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the Graphite export module."""
|
"""This class manages the Graphite export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the Graphite export IF."""
|
"""Init the Graphite export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
# N/A
|
# N/A
|
||||||
@ -74,25 +73,25 @@ class Export(GlancesExport):
|
|||||||
debug=self.debug,
|
debug=self.debug,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Can not write data to Graphite server: {}:{} ({})".format(self.host, self.port, e))
|
logger.error(f"Can not write data to Graphite server: {self.host}:{self.port} ({e})")
|
||||||
client = None
|
client = None
|
||||||
else:
|
else:
|
||||||
logger.info("Stats will be exported to Graphite server: {}:{}".format(self.host, self.port))
|
logger.info(f"Stats will be exported to Graphite server: {self.host}:{self.port}")
|
||||||
return client
|
return client
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Export the stats to the Graphite server."""
|
"""Export the stats to the Graphite server."""
|
||||||
if self.client is None:
|
if self.client is None:
|
||||||
return False
|
return False
|
||||||
before_filtering_dict = dict(zip([normalize('{}.{}'.format(name, i)) for i in columns], points))
|
before_filtering_dict = dict(zip([normalize(f'{name}.{i}') for i in columns], points))
|
||||||
after_filtering_dict = dict(filter(lambda i: isinstance(i[1], Number), before_filtering_dict.items()))
|
after_filtering_dict = dict(filter(lambda i: isinstance(i[1], Number), before_filtering_dict.items()))
|
||||||
try:
|
try:
|
||||||
self.client.send_dict(after_filtering_dict)
|
self.client.send_dict(after_filtering_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Can not export stats to Graphite (%s)" % e)
|
logger.error(f"Can not export stats to Graphite ({e})")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logger.debug("Export {} stats to Graphite".format(name))
|
logger.debug(f"Export {name} stats to Graphite")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -100,6 +99,4 @@ def normalize(name):
|
|||||||
"""Normalize name for the Graphite convention"""
|
"""Normalize name for the Graphite convention"""
|
||||||
|
|
||||||
# Name should not contain space
|
# Name should not contain space
|
||||||
ret = name.replace(' ', '_')
|
return name.replace(' ', '_')
|
||||||
|
|
||||||
return ret
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,13 +11,13 @@
|
|||||||
import sys
|
import sys
|
||||||
from platform import node
|
from platform import node
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
from influxdb import InfluxDBClient
|
from influxdb import InfluxDBClient
|
||||||
from influxdb.client import InfluxDBClientError
|
from influxdb.client import InfluxDBClientError
|
||||||
|
|
||||||
FIELD_TO_TAG = ['name', 'cmdline']
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
FIELD_TO_TAG = ['name', 'cmdline', 'type']
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -26,7 +25,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the InfluxDB export IF."""
|
"""Init the InfluxDB export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.user = None
|
self.user = None
|
||||||
@ -75,13 +74,13 @@ class Export(GlancesExport):
|
|||||||
)
|
)
|
||||||
get_all_db = [i['name'] for i in db.get_list_database()]
|
get_all_db = [i['name'] for i in db.get_list_database()]
|
||||||
except InfluxDBClientError as e:
|
except InfluxDBClientError as e:
|
||||||
logger.critical("Cannot connect to InfluxDB database '%s' (%s)" % (self.db, e))
|
logger.critical(f"Cannot connect to InfluxDB database '{self.db}' ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
if self.db in get_all_db:
|
if self.db in get_all_db:
|
||||||
logger.info("Stats will be exported to InfluxDB server: {}".format(db._baseurl))
|
logger.info(f"Stats will be exported to InfluxDB server: {db._baseurl}")
|
||||||
else:
|
else:
|
||||||
logger.critical("InfluxDB database '%s' did not exist. Please create it" % self.db)
|
logger.critical(f"InfluxDB database '{self.db}' did not exist. Please create it")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
return db
|
return db
|
||||||
@ -106,9 +105,7 @@ class Export(GlancesExport):
|
|||||||
# Manage field
|
# Manage field
|
||||||
if measurement is not None:
|
if measurement is not None:
|
||||||
fields = {
|
fields = {
|
||||||
k.replace('{}.'.format(measurement), ''): data_dict[k]
|
k.replace(f'{measurement}.', ''): data_dict[k] for k in data_dict if k.startswith(f'{measurement}.')
|
||||||
for k in data_dict
|
|
||||||
if k.startswith('{}.'.format(measurement))
|
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
fields = data_dict
|
fields = data_dict
|
||||||
@ -142,8 +139,7 @@ class Export(GlancesExport):
|
|||||||
if k in fields:
|
if k in fields:
|
||||||
tags[k] = str(fields[k])
|
tags[k] = str(fields[k])
|
||||||
# Remove it from the field list (can not be a field and a tag)
|
# Remove it from the field list (can not be a field and a tag)
|
||||||
if k in fields:
|
fields.pop(k)
|
||||||
fields.pop(fields[k])
|
|
||||||
# Add the measurement to the list
|
# Add the measurement to the list
|
||||||
ret.append({'measurement': name, 'tags': tags, 'fields': fields})
|
ret.append({'measurement': name, 'tags': tags, 'fields': fields})
|
||||||
return ret
|
return ret
|
||||||
@ -155,12 +151,12 @@ class Export(GlancesExport):
|
|||||||
name = self.prefix + '.' + name
|
name = self.prefix + '.' + name
|
||||||
# Write input to the InfluxDB database
|
# Write input to the InfluxDB database
|
||||||
if len(points) == 0:
|
if len(points) == 0:
|
||||||
logger.debug("Cannot export empty {} stats to InfluxDB".format(name))
|
logger.debug(f"Cannot export empty {name} stats to InfluxDB")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.client.write_points(self._normalize(name, columns, points), time_precision="s")
|
self.client.write_points(self._normalize(name, columns, points), time_precision="s")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log level set to debug instead of error (see: issue #1561)
|
# Log level set to warning instead of error (see: issue #1561)
|
||||||
logger.debug("Cannot export {} stats to InfluxDB ({})".format(name, e))
|
logger.warning(f"Cannot export {name} stats to InfluxDB ({e})")
|
||||||
else:
|
else:
|
||||||
logger.debug("Export {} stats to InfluxDB".format(name))
|
logger.debug(f"Export {name} stats to InfluxDB")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,12 +11,12 @@
|
|||||||
import sys
|
import sys
|
||||||
from platform import node
|
from platform import node
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
from influxdb_client import InfluxDBClient, WriteOptions
|
from influxdb_client import InfluxDBClient, WriteOptions
|
||||||
|
|
||||||
FIELD_TO_TAG = ['name', 'cmdline']
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
FIELD_TO_TAG = ['name', 'cmdline', 'type']
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -25,7 +24,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the InfluxDB export IF."""
|
"""Init the InfluxDB export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.org = None
|
self.org = None
|
||||||
@ -58,7 +57,7 @@ class Export(GlancesExport):
|
|||||||
self.interval = 0
|
self.interval = 0
|
||||||
# and should be set to the Glances refresh time if the value is 0
|
# and should be set to the Glances refresh time if the value is 0
|
||||||
self.interval = self.interval if self.interval > 0 else self.args.time
|
self.interval = self.interval if self.interval > 0 else self.args.time
|
||||||
logger.debug("InfluxDB export interval is set to {} seconds".format(self.interval))
|
logger.debug(f"InfluxDB export interval is set to {self.interval} seconds")
|
||||||
|
|
||||||
# The hostname is always add as a tag
|
# The hostname is always add as a tag
|
||||||
self.hostname = node().split('.')[0]
|
self.hostname = node().split('.')[0]
|
||||||
@ -71,20 +70,18 @@ class Export(GlancesExport):
|
|||||||
if not self.export_enable:
|
if not self.export_enable:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
url = '{}://{}:{}'.format(self.protocol, self.host, self.port)
|
url = f'{self.protocol}://{self.host}:{self.port}'
|
||||||
try:
|
try:
|
||||||
# See docs: https://influxdb-client.readthedocs.io/en/stable/api.html#influxdbclient
|
# See docs: https://influxdb-client.readthedocs.io/en/stable/api.html#influxdbclient
|
||||||
client = InfluxDBClient(url=url, enable_gzip=False, verify_ssl=False, org=self.org, token=self.token)
|
client = InfluxDBClient(url=url, enable_gzip=False, verify_ssl=False, org=self.org, token=self.token)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Cannot connect to InfluxDB server '%s' (%s)" % (url, e))
|
logger.critical(f"Cannot connect to InfluxDB server '{url}' ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(f"Connected to InfluxDB server version {client.health().version} ({client.health().message})")
|
||||||
"Connected to InfluxDB server version {} ({})".format(client.health().version, client.health().message)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create the write client
|
# Create the write client
|
||||||
write_client = client.write_api(
|
return client.write_api(
|
||||||
write_options=WriteOptions(
|
write_options=WriteOptions(
|
||||||
batch_size=500,
|
batch_size=500,
|
||||||
flush_interval=self.interval * 1000,
|
flush_interval=self.interval * 1000,
|
||||||
@ -95,7 +92,6 @@ class Export(GlancesExport):
|
|||||||
exponential_base=2,
|
exponential_base=2,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return write_client
|
|
||||||
|
|
||||||
def _normalize(self, name, columns, points):
|
def _normalize(self, name, columns, points):
|
||||||
"""Normalize data for the InfluxDB's data model.
|
"""Normalize data for the InfluxDB's data model.
|
||||||
@ -117,9 +113,7 @@ class Export(GlancesExport):
|
|||||||
# Manage field
|
# Manage field
|
||||||
if measurement is not None:
|
if measurement is not None:
|
||||||
fields = {
|
fields = {
|
||||||
k.replace('{}.'.format(measurement), ''): data_dict[k]
|
k.replace(f'{measurement}.', ''): data_dict[k] for k in data_dict if k.startswith(f'{measurement}.')
|
||||||
for k in data_dict
|
|
||||||
if k.startswith('{}.'.format(measurement))
|
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
fields = data_dict
|
fields = data_dict
|
||||||
@ -153,8 +147,7 @@ class Export(GlancesExport):
|
|||||||
if k in fields:
|
if k in fields:
|
||||||
tags[k] = str(fields[k])
|
tags[k] = str(fields[k])
|
||||||
# Remove it from the field list (can not be a field and a tag)
|
# Remove it from the field list (can not be a field and a tag)
|
||||||
if k in fields:
|
fields.pop(k)
|
||||||
fields.pop(fields[k])
|
|
||||||
# Add the measurement to the list
|
# Add the measurement to the list
|
||||||
ret.append({'measurement': name, 'tags': tags, 'fields': fields})
|
ret.append({'measurement': name, 'tags': tags, 'fields': fields})
|
||||||
return ret
|
return ret
|
||||||
@ -166,12 +159,12 @@ class Export(GlancesExport):
|
|||||||
name = self.prefix + '.' + name
|
name = self.prefix + '.' + name
|
||||||
# Write input to the InfluxDB database
|
# Write input to the InfluxDB database
|
||||||
if len(points) == 0:
|
if len(points) == 0:
|
||||||
logger.debug("Cannot export empty {} stats to InfluxDB".format(name))
|
logger.debug(f"Cannot export empty {name} stats to InfluxDB")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.client.write(self.bucket, self.org, self._normalize(name, columns, points), time_precision="s")
|
self.client.write(self.bucket, self.org, self._normalize(name, columns, points), time_precision="s")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log level set to debug instead of error (see: issue #1561)
|
# Log level set to warning instead of error (see: issue #1561)
|
||||||
logger.debug("Cannot export {} stats to InfluxDB ({})".format(name, e))
|
logger.warning(f"Cannot export {name} stats to InfluxDB ({e})")
|
||||||
else:
|
else:
|
||||||
logger.debug("Export {} stats to InfluxDB".format(name))
|
logger.debug(f"Export {name} stats to InfluxDB")
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from glances.globals import listkeys, json_dumps
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.globals import json_dumps, listkeys
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -12,7 +12,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the JSON export IF."""
|
"""Init the JSON export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# JSON file name
|
# JSON file name
|
||||||
self.json_filename = args.export_json_file
|
self.json_filename = args.export_json_file
|
||||||
@ -21,11 +21,11 @@ class Export(GlancesExport):
|
|||||||
try:
|
try:
|
||||||
self.json_file = open(self.json_filename, 'w')
|
self.json_file = open(self.json_filename, 'w')
|
||||||
self.json_file.close()
|
self.json_file.close()
|
||||||
except IOError as e:
|
except OSError as e:
|
||||||
logger.critical("Cannot create the JSON file: {}".format(e))
|
logger.critical(f"Cannot create the JSON file: {e}")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
logger.info("Exporting stats to file: {}".format(self.json_filename))
|
logger.info(f"Exporting stats to file: {self.json_filename}")
|
||||||
|
|
||||||
self.export_enable = True
|
self.export_enable = True
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Close the JSON file."""
|
"""Close the JSON file."""
|
||||||
logger.debug("Finalise export interface %s" % self.export_name)
|
logger.debug(f"Finalise export interface {self.export_name}")
|
||||||
self.json_file.close()
|
self.json_file.close()
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
@ -44,11 +44,11 @@ class Export(GlancesExport):
|
|||||||
if name == self.last_exported_list()[0] and self.buffer != {}:
|
if name == self.last_exported_list()[0] and self.buffer != {}:
|
||||||
# One whole loop has been completed
|
# One whole loop has been completed
|
||||||
# Flush stats to file
|
# Flush stats to file
|
||||||
logger.debug("Exporting stats ({}) to JSON file ({})".format(listkeys(self.buffer), self.json_filename))
|
logger.debug(f"Exporting stats ({listkeys(self.buffer)}) to JSON file ({self.json_filename})")
|
||||||
|
|
||||||
# Export stats to JSON file
|
# Export stats to JSON file
|
||||||
with open(self.json_filename, "w") as self.json_file:
|
with open(self.json_filename, "w") as self.json_file:
|
||||||
self.json_file.write("{}\n".format(json_dumps(self.buffer)))
|
self.json_file.write(f"{json_dumps(self.buffer)}\n")
|
||||||
|
|
||||||
# Reset buffer
|
# Reset buffer
|
||||||
self.buffer = {}
|
self.buffer = {}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -11,19 +10,19 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.globals import json_dumps
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
from kafka import KafkaProducer
|
from kafka import KafkaProducer
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.globals import json_dumps
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the Kafka export module."""
|
"""This class manages the Kafka export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the Kafka export IF."""
|
"""Init the Kafka export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.topic = None
|
self.topic = None
|
||||||
@ -48,7 +47,7 @@ class Export(GlancesExport):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Build the server URI with host and port
|
# Build the server URI with host and port
|
||||||
server_uri = '{}:{}'.format(self.host, self.port)
|
server_uri = f'{self.host}:{self.port}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
s = KafkaProducer(
|
s = KafkaProducer(
|
||||||
@ -57,16 +56,16 @@ class Export(GlancesExport):
|
|||||||
compression_type=self.compression,
|
compression_type=self.compression,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Cannot connect to Kafka server %s (%s)" % (server_uri, e))
|
logger.critical(f"Cannot connect to Kafka server {server_uri} ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
logger.info("Connected to the Kafka server %s" % server_uri)
|
logger.info(f"Connected to the Kafka server {server_uri}")
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Write the points to the kafka server."""
|
"""Write the points to the kafka server."""
|
||||||
logger.debug("Export {} stats to Kafka".format(name))
|
logger.debug(f"Export {name} stats to Kafka")
|
||||||
|
|
||||||
# Create DB input
|
# Create DB input
|
||||||
data = dict(zip(columns, points))
|
data = dict(zip(columns, points))
|
||||||
@ -84,7 +83,7 @@ class Export(GlancesExport):
|
|||||||
value=data,
|
value=data,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot export {} stats to Kafka ({})".format(name, e))
|
logger.error(f"Cannot export {name} stats to Kafka ({e})")
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Close the Kafka export module."""
|
"""Close the Kafka export module."""
|
||||||
@ -92,4 +91,4 @@ class Export(GlancesExport):
|
|||||||
self.client.flush()
|
self.client.flush()
|
||||||
self.client.close()
|
self.client.close()
|
||||||
# Call the father method
|
# Call the father method
|
||||||
super(Export, self).exit()
|
super().exit()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -10,12 +9,12 @@
|
|||||||
"""MongoDB interface class."""
|
"""MongoDB interface class."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from urllib.parse import quote_plus
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
import pymongo
|
import pymongo
|
||||||
from urllib.parse import quote_plus
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -23,7 +22,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the MongoDB export IF."""
|
"""Init the MongoDB export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.db = None
|
self.db = None
|
||||||
@ -45,13 +44,13 @@ class Export(GlancesExport):
|
|||||||
if not self.export_enable:
|
if not self.export_enable:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
server_uri = 'mongodb://%s:%s@%s:%s' % (quote_plus(self.user), quote_plus(self.password), self.host, self.port)
|
server_uri = f'mongodb://{quote_plus(self.user)}:{quote_plus(self.password)}@{self.host}:{self.port}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = pymongo.MongoClient(server_uri)
|
client = pymongo.MongoClient(server_uri)
|
||||||
client.admin.command('ping')
|
client.admin.command('ping')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Cannot connect to MongoDB server %s:%s (%s)" % (self.host, self.port, e))
|
logger.critical(f"Cannot connect to MongoDB server {self.host}:{self.port} ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
logger.info("Connected to the MongoDB server")
|
logger.info("Connected to the MongoDB server")
|
||||||
@ -64,7 +63,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Write the points to the MongoDB server."""
|
"""Write the points to the MongoDB server."""
|
||||||
logger.debug("Export {} stats to MongoDB".format(name))
|
logger.debug(f"Export {name} stats to MongoDB")
|
||||||
|
|
||||||
# Create DB input
|
# Create DB input
|
||||||
data = dict(zip(columns, points))
|
data = dict(zip(columns, points))
|
||||||
@ -73,4 +72,4 @@ class Export(GlancesExport):
|
|||||||
try:
|
try:
|
||||||
self.database()[name].insert_one(data)
|
self.database()[name].insert_one(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot export {} stats to MongoDB ({})".format(name, e))
|
logger.error(f"Cannot export {name} stats to MongoDB ({e})")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -13,21 +12,21 @@ import socket
|
|||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
from glances.globals import json_dumps
|
|
||||||
|
|
||||||
# Import paho for MQTT
|
# Import paho for MQTT
|
||||||
import certifi
|
import certifi
|
||||||
import paho.mqtt.client as paho
|
import paho.mqtt.client as paho
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.globals import json_dumps
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the MQTT export module."""
|
"""This class manages the MQTT export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the MQTT export IF."""
|
"""Init the MQTT export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.user = None
|
self.user = None
|
||||||
@ -87,7 +86,7 @@ class Export(GlancesExport):
|
|||||||
client.loop_start()
|
client.loop_start()
|
||||||
return client
|
return client
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Connection to MQTT server %s:%s failed with error: %s " % (self.host, self.port, e))
|
logger.critical(f"Connection to MQTT server {self.host}:{self.port} failed with error: {e} ")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
@ -109,14 +108,14 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
self.client.publish(topic, value)
|
self.client.publish(topic, value)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Can not export stats to MQTT server (%s)" % e)
|
logger.error(f"Can not export stats to MQTT server ({e})")
|
||||||
elif self.topic_structure == 'per-plugin':
|
elif self.topic_structure == 'per-plugin':
|
||||||
try:
|
try:
|
||||||
topic = '/'.join([self.topic, self.devicename, name])
|
topic = '/'.join([self.topic, self.devicename, name])
|
||||||
sensor_values = dict(zip(columns, points))
|
sensor_values = dict(zip(columns, points))
|
||||||
|
|
||||||
# Build the value to output
|
# Build the value to output
|
||||||
output_value = dict()
|
output_value = {}
|
||||||
for key in sensor_values:
|
for key in sensor_values:
|
||||||
split_key = key.split('.')
|
split_key = key.split('.')
|
||||||
|
|
||||||
@ -124,7 +123,7 @@ class Export(GlancesExport):
|
|||||||
current_level = output_value
|
current_level = output_value
|
||||||
for depth in range(len(split_key) - 1):
|
for depth in range(len(split_key) - 1):
|
||||||
if split_key[depth] not in current_level:
|
if split_key[depth] not in current_level:
|
||||||
current_level[split_key[depth]] = dict()
|
current_level[split_key[depth]] = {}
|
||||||
current_level = current_level[split_key[depth]]
|
current_level = current_level[split_key[depth]]
|
||||||
|
|
||||||
# Add the value
|
# Add the value
|
||||||
@ -133,4 +132,4 @@ class Export(GlancesExport):
|
|||||||
json_value = json_dumps(output_value)
|
json_value = json_dumps(output_value)
|
||||||
self.client.publish(topic, json_value)
|
self.client.publish(topic, json_value)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Can not export stats to MQTT server (%s)" % e)
|
logger.error(f"Can not export stats to MQTT server ({e})")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,18 +11,18 @@
|
|||||||
import sys
|
import sys
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
import potsdb
|
import potsdb
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the OpenTSDB export module."""
|
"""This class manages the OpenTSDB export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the OpenTSDB export IF."""
|
"""Init the OpenTSDB export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
# N/A
|
# N/A
|
||||||
@ -52,7 +51,7 @@ class Export(GlancesExport):
|
|||||||
try:
|
try:
|
||||||
db = potsdb.Client(self.host, port=int(self.port), check_host=True)
|
db = potsdb.Client(self.host, port=int(self.port), check_host=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Cannot connect to OpenTSDB server %s:%s (%s)" % (self.host, self.port, e))
|
logger.critical(f"Cannot connect to OpenTSDB server {self.host}:{self.port} ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
return db
|
return db
|
||||||
@ -62,18 +61,18 @@ class Export(GlancesExport):
|
|||||||
for i in range(len(columns)):
|
for i in range(len(columns)):
|
||||||
if not isinstance(points[i], Number):
|
if not isinstance(points[i], Number):
|
||||||
continue
|
continue
|
||||||
stat_name = '{}.{}.{}'.format(self.prefix, name, columns[i])
|
stat_name = f'{self.prefix}.{name}.{columns[i]}'
|
||||||
stat_value = points[i]
|
stat_value = points[i]
|
||||||
tags = self.parse_tags(self.tags)
|
tags = self.parse_tags(self.tags)
|
||||||
try:
|
try:
|
||||||
self.client.send(stat_name, stat_value, **tags)
|
self.client.send(stat_name, stat_value, **tags)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Can not export stats %s to OpenTSDB (%s)" % (name, e))
|
logger.error(f"Can not export stats {name} to OpenTSDB ({e})")
|
||||||
logger.debug("Export {} stats to OpenTSDB".format(name))
|
logger.debug(f"Export {name} stats to OpenTSDB")
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Close the OpenTSDB export module."""
|
"""Close the OpenTSDB export module."""
|
||||||
# Waits for all outstanding metrics to be sent and background thread closes
|
# Waits for all outstanding metrics to be sent and background thread closes
|
||||||
self.client.wait()
|
self.client.wait()
|
||||||
# Call the father method
|
# Call the father method
|
||||||
super(Export, self).exit()
|
super().exit()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,11 +11,11 @@
|
|||||||
import sys
|
import sys
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
from glances.logger import logger
|
from prometheus_client import Gauge, start_http_server
|
||||||
|
|
||||||
from glances.exports.export import GlancesExport
|
from glances.exports.export import GlancesExport
|
||||||
from glances.globals import iteritems, listkeys
|
from glances.globals import iteritems, listkeys
|
||||||
|
from glances.logger import logger
|
||||||
from prometheus_client import start_http_server, Gauge
|
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -26,7 +25,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the Prometheus export IF."""
|
"""Init the Prometheus export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Load the Prometheus configuration file section
|
# Load the Prometheus configuration file section
|
||||||
self.export_enable = self.load_conf('prometheus', mandatories=['host', 'port', 'labels'], options=['prefix'])
|
self.export_enable = self.load_conf('prometheus', mandatories=['host', 'port', 'labels'], options=['prefix'])
|
||||||
@ -52,14 +51,14 @@ class Export(GlancesExport):
|
|||||||
try:
|
try:
|
||||||
start_http_server(port=int(self.port), addr=self.host)
|
start_http_server(port=int(self.port), addr=self.host)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Can not start Prometheus exporter on {}:{} ({})".format(self.host, self.port, e))
|
logger.critical(f"Can not start Prometheus exporter on {self.host}:{self.port} ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
logger.info("Start Prometheus exporter on {}:{}".format(self.host, self.port))
|
logger.info(f"Start Prometheus exporter on {self.host}:{self.port}")
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Write the points to the Prometheus exporter using Gauge."""
|
"""Write the points to the Prometheus exporter using Gauge."""
|
||||||
logger.debug("Export {} stats to Prometheus exporter".format(name))
|
logger.debug(f"Export {name} stats to Prometheus exporter")
|
||||||
|
|
||||||
# Remove non number stats and convert all to float (for Boolean)
|
# Remove non number stats and convert all to float (for Boolean)
|
||||||
data = {k: float(v) for (k, v) in iteritems(dict(zip(columns, points))) if isinstance(v, Number)}
|
data = {k: float(v) for (k, v) in iteritems(dict(zip(columns, points))) if isinstance(v, Number)}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -14,19 +13,19 @@ import socket
|
|||||||
import sys
|
import sys
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
# Import pika for RabbitMQ
|
# Import pika for RabbitMQ
|
||||||
import pika
|
import pika
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the rabbitMQ export module."""
|
"""This class manages the rabbitMQ export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the RabbitMQ export IF."""
|
"""Init the RabbitMQ export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.user = None
|
self.user = None
|
||||||
@ -67,10 +66,9 @@ class Export(GlancesExport):
|
|||||||
self.protocol + '://' + self.user + ':' + self.password + '@' + self.host + ':' + self.port + '/'
|
self.protocol + '://' + self.user + ':' + self.password + '@' + self.host + ':' + self.port + '/'
|
||||||
)
|
)
|
||||||
connection = pika.BlockingConnection(parameters)
|
connection = pika.BlockingConnection(parameters)
|
||||||
channel = connection.channel()
|
return connection.channel()
|
||||||
return channel
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Connection to rabbitMQ server %s:%s failed. %s" % (self.host, self.port, e))
|
logger.critical(f"Connection to rabbitMQ server {self.host}:{self.port} failed. {e}")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
@ -79,10 +77,10 @@ class Export(GlancesExport):
|
|||||||
for i in range(len(columns)):
|
for i in range(len(columns)):
|
||||||
if not isinstance(points[i], Number):
|
if not isinstance(points[i], Number):
|
||||||
continue
|
continue
|
||||||
else:
|
data += ", " + columns[i] + "=" + str(points[i])
|
||||||
data += ", " + columns[i] + "=" + str(points[i])
|
|
||||||
logger.debug(data)
|
logger.debug(data)
|
||||||
try:
|
try:
|
||||||
self.client.basic_publish(exchange='', routing_key=self.queue, body=data)
|
self.client.basic_publish(exchange='', routing_key=self.queue, body=data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Can not export stats to RabbitMQ (%s)" % e)
|
logger.error(f"Can not export stats to RabbitMQ ({e})")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,12 +8,11 @@
|
|||||||
|
|
||||||
"""RESTful interface class."""
|
"""RESTful interface class."""
|
||||||
|
|
||||||
|
from requests import post
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
from glances.globals import listkeys
|
from glances.globals import listkeys
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
from requests import post
|
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
@ -23,7 +21,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the RESTful export IF."""
|
"""Init the RESTful export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.protocol = None
|
self.protocol = None
|
||||||
@ -46,15 +44,15 @@ class Export(GlancesExport):
|
|||||||
if not self.export_enable:
|
if not self.export_enable:
|
||||||
return None
|
return None
|
||||||
# Build the RESTful URL where the stats will be posted
|
# Build the RESTful URL where the stats will be posted
|
||||||
url = '{}://{}:{}{}'.format(self.protocol, self.host, self.port, self.path)
|
url = f'{self.protocol}://{self.host}:{self.port}{self.path}'
|
||||||
logger.info("Stats will be exported to the RESTful endpoint {}".format(url))
|
logger.info(f"Stats will be exported to the RESTful endpoint {url}")
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Export the stats to the Statsd server."""
|
"""Export the stats to the Statsd server."""
|
||||||
if name == self.last_exported_list()[0] and self.buffer != {}:
|
if name == self.last_exported_list()[0] and self.buffer != {}:
|
||||||
# One complete loop have been done
|
# One complete loop have been done
|
||||||
logger.debug("Export stats ({}) to RESTful endpoint ({})".format(listkeys(self.buffer), self.client))
|
logger.debug(f"Export stats ({listkeys(self.buffer)}) to RESTful endpoint ({self.client})")
|
||||||
# Export stats
|
# Export stats
|
||||||
post(self.client, json=self.buffer, allow_redirects=True)
|
post(self.client, json=self.buffer, allow_redirects=True)
|
||||||
# Reset buffer
|
# Reset buffer
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,19 +11,19 @@
|
|||||||
import socket
|
import socket
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
# Import bernhard for Riemann
|
# Import bernhard for Riemann
|
||||||
import bernhard
|
import bernhard
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the Riemann export module."""
|
"""This class manages the Riemann export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the Riemann export IF."""
|
"""Init the Riemann export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
# N/A
|
# N/A
|
||||||
@ -48,10 +47,9 @@ class Export(GlancesExport):
|
|||||||
if not self.export_enable:
|
if not self.export_enable:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
client = bernhard.Client(host=self.host, port=self.port)
|
return bernhard.Client(host=self.host, port=self.port)
|
||||||
return client
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Connection to Riemann failed : %s " % e)
|
logger.critical(f"Connection to Riemann failed : {e} ")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
@ -59,10 +57,10 @@ class Export(GlancesExport):
|
|||||||
for i in range(len(columns)):
|
for i in range(len(columns)):
|
||||||
if not isinstance(points[i], Number):
|
if not isinstance(points[i], Number):
|
||||||
continue
|
continue
|
||||||
else:
|
|
||||||
data = {'host': self.hostname, 'service': name + " " + columns[i], 'metric': points[i]}
|
data = {'host': self.hostname, 'service': name + " " + columns[i], 'metric': points[i]}
|
||||||
logger.debug(data)
|
logger.debug(data)
|
||||||
try:
|
try:
|
||||||
self.client.send(data)
|
self.client.send(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot export stats to Riemann (%s)" % e)
|
logger.error(f"Cannot export stats to Riemann ({e})")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -11,18 +10,18 @@
|
|||||||
|
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
|
|
||||||
from statsd import StatsClient
|
from statsd import StatsClient
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the Statsd export module."""
|
"""This class manages the Statsd export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the Statsd export IF."""
|
"""Init the Statsd export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
# N/A
|
# N/A
|
||||||
@ -46,7 +45,7 @@ class Export(GlancesExport):
|
|||||||
"""Init the connection to the Statsd server."""
|
"""Init the connection to the Statsd server."""
|
||||||
if not self.export_enable:
|
if not self.export_enable:
|
||||||
return None
|
return None
|
||||||
logger.info("Stats will be exported to StatsD server: {}:{}".format(self.host, self.port))
|
logger.info(f"Stats will be exported to StatsD server: {self.host}:{self.port}")
|
||||||
return StatsClient(self.host, int(self.port), prefix=self.prefix)
|
return StatsClient(self.host, int(self.port), prefix=self.prefix)
|
||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
@ -54,13 +53,13 @@ class Export(GlancesExport):
|
|||||||
for i in range(len(columns)):
|
for i in range(len(columns)):
|
||||||
if not isinstance(points[i], Number):
|
if not isinstance(points[i], Number):
|
||||||
continue
|
continue
|
||||||
stat_name = '{}.{}'.format(name, columns[i])
|
stat_name = f'{name}.{columns[i]}'
|
||||||
stat_value = points[i]
|
stat_value = points[i]
|
||||||
try:
|
try:
|
||||||
self.client.gauge(normalize(stat_name), stat_value)
|
self.client.gauge(normalize(stat_name), stat_value)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Can not export stats to Statsd (%s)" % e)
|
logger.error(f"Can not export stats to Statsd ({e})")
|
||||||
logger.debug("Export {} stats to Statsd".format(name))
|
logger.debug(f"Export {name} stats to Statsd")
|
||||||
|
|
||||||
|
|
||||||
def normalize(name):
|
def normalize(name):
|
||||||
@ -69,6 +68,4 @@ def normalize(name):
|
|||||||
# Name should not contain some specials chars (issue #1068)
|
# Name should not contain some specials chars (issue #1068)
|
||||||
ret = name.replace(':', '')
|
ret = name.replace(':', '')
|
||||||
ret = ret.replace('%', '')
|
ret = ret.replace('%', '')
|
||||||
ret = ret.replace(' ', '_')
|
return ret.replace(' ', '_')
|
||||||
|
|
||||||
return ret
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -11,21 +10,20 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from glances.globals import b
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.exports.export import GlancesExport
|
|
||||||
from glances.globals import json_dumps
|
|
||||||
|
|
||||||
import zmq
|
import zmq
|
||||||
from zmq.utils.strtypes import asbytes
|
from zmq.utils.strtypes import asbytes
|
||||||
|
|
||||||
|
from glances.exports.export import GlancesExport
|
||||||
|
from glances.globals import b, json_dumps
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Export(GlancesExport):
|
class Export(GlancesExport):
|
||||||
"""This class manages the ZeroMQ export module."""
|
"""This class manages the ZeroMQ export module."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
"""Init the ZeroMQ export IF."""
|
"""Init the ZeroMQ export IF."""
|
||||||
super(Export, self).__init__(config=config, args=args)
|
super().__init__(config=config, args=args)
|
||||||
|
|
||||||
# Mandatory configuration keys (additional to host and port)
|
# Mandatory configuration keys (additional to host and port)
|
||||||
self.prefix = None
|
self.prefix = None
|
||||||
@ -47,17 +45,17 @@ class Export(GlancesExport):
|
|||||||
if not self.export_enable:
|
if not self.export_enable:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
server_uri = 'tcp://{}:{}'.format(self.host, self.port)
|
server_uri = f'tcp://{self.host}:{self.port}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.context = zmq.Context()
|
self.context = zmq.Context()
|
||||||
publisher = self.context.socket(zmq.PUB)
|
publisher = self.context.socket(zmq.PUB)
|
||||||
publisher.bind(server_uri)
|
publisher.bind(server_uri)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Cannot connect to ZeroMQ server %s (%s)" % (server_uri, e))
|
logger.critical(f"Cannot connect to ZeroMQ server {server_uri} ({e})")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
logger.info("Connected to the ZeroMQ server %s" % server_uri)
|
logger.info(f"Connected to the ZeroMQ server {server_uri}")
|
||||||
|
|
||||||
return publisher
|
return publisher
|
||||||
|
|
||||||
@ -70,7 +68,7 @@ class Export(GlancesExport):
|
|||||||
|
|
||||||
def export(self, name, columns, points):
|
def export(self, name, columns, points):
|
||||||
"""Write the points to the ZeroMQ server."""
|
"""Write the points to the ZeroMQ server."""
|
||||||
logger.debug("Export {} stats to ZeroMQ".format(name))
|
logger.debug(f"Export {name} stats to ZeroMQ")
|
||||||
|
|
||||||
# Create DB input
|
# Create DB input
|
||||||
data = dict(zip(columns, points))
|
data = dict(zip(columns, points))
|
||||||
@ -90,6 +88,6 @@ class Export(GlancesExport):
|
|||||||
try:
|
try:
|
||||||
self.client.send_multipart(message)
|
self.client.send_multipart(message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot export {} stats to ZeroMQ ({})".format(name, e))
|
logger.error(f"Cannot export {name} stats to ZeroMQ ({e})")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,7 +11,7 @@ import re
|
|||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class GlancesFilterList(object):
|
class GlancesFilterList:
|
||||||
"""Manage a lis of GlancesFilter objects
|
"""Manage a lis of GlancesFilter objects
|
||||||
|
|
||||||
>>> fl = GlancesFilterList()
|
>>> fl = GlancesFilterList()
|
||||||
@ -55,7 +54,7 @@ class GlancesFilterList(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class GlancesFilter(object):
|
class GlancesFilter:
|
||||||
"""Allow Glances to filter processes
|
"""Allow Glances to filter processes
|
||||||
|
|
||||||
>>> f = GlancesFilter()
|
>>> f = GlancesFilter()
|
||||||
@ -127,9 +126,9 @@ class GlancesFilter(object):
|
|||||||
# Compute the regular expression
|
# Compute the regular expression
|
||||||
try:
|
try:
|
||||||
self._filter_re = re.compile(self.filter)
|
self._filter_re = re.compile(self.filter)
|
||||||
logger.debug("Filter regex compilation OK: {}".format(self.filter))
|
logger.debug(f"Filter regex compilation OK: {self.filter}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot compile filter regex: {} ({})".format(self.filter, e))
|
logger.error(f"Cannot compile filter regex: {self.filter} ({e})")
|
||||||
self._filter = None
|
self._filter = None
|
||||||
self._filter_re = None
|
self._filter_re = None
|
||||||
self._filter_key = None
|
self._filter_key = None
|
||||||
@ -156,9 +155,9 @@ class GlancesFilter(object):
|
|||||||
if self.filter_key is None:
|
if self.filter_key is None:
|
||||||
# Apply filter on command line and process name
|
# Apply filter on command line and process name
|
||||||
return self._is_process_filtered(process, key='name') or self._is_process_filtered(process, key='cmdline')
|
return self._is_process_filtered(process, key='name') or self._is_process_filtered(process, key='cmdline')
|
||||||
else:
|
|
||||||
# Apply filter on <key>
|
# Apply filter on <key>
|
||||||
return self._is_process_filtered(process)
|
return self._is_process_filtered(process)
|
||||||
|
|
||||||
def _is_process_filtered(self, process, key=None):
|
def _is_process_filtered(self, process, key=None):
|
||||||
"""Return True if the process[key] should be filtered according to the current filter"""
|
"""Return True if the process[key] should be filtered according to the current filter"""
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -8,15 +7,13 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
"""Manage the folder list."""
|
"""Manage the folder list."""
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
|
from glances.globals import folder_size, nativestr
|
||||||
from glances.timer import Timer
|
|
||||||
from glances.globals import nativestr, folder_size
|
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
|
from glances.timer import Timer
|
||||||
|
|
||||||
|
|
||||||
class FolderList(object):
|
class FolderList:
|
||||||
"""This class describes the optional monitored folder list.
|
"""This class describes the optional monitored folder list.
|
||||||
|
|
||||||
The folder list is a list of 'important' folder to monitor.
|
The folder list is a list of 'important' folder to monitor.
|
||||||
@ -67,8 +64,7 @@ class FolderList(object):
|
|||||||
value['path'] = self.config.get_value(section, key + 'path')
|
value['path'] = self.config.get_value(section, key + 'path')
|
||||||
if value['path'] is None:
|
if value['path'] is None:
|
||||||
continue
|
continue
|
||||||
else:
|
value['path'] = nativestr(value['path'])
|
||||||
value['path'] = nativestr(value['path'])
|
|
||||||
|
|
||||||
# Optional conf keys
|
# Optional conf keys
|
||||||
# Refresh time
|
# Refresh time
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# ruff: noqa: F401
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -13,29 +13,27 @@
|
|||||||
# GLOBAL IMPORTS
|
# GLOBAL IMPORTS
|
||||||
################
|
################
|
||||||
|
|
||||||
import errno
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import platform
|
|
||||||
import ujson
|
|
||||||
from operator import itemgetter, methodcaller
|
|
||||||
import unicodedata
|
|
||||||
import types
|
|
||||||
import subprocess
|
|
||||||
from datetime import datetime
|
|
||||||
import re
|
|
||||||
import base64
|
import base64
|
||||||
|
import errno
|
||||||
import functools
|
import functools
|
||||||
import weakref
|
import os
|
||||||
|
import platform
|
||||||
import queue
|
import queue
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import weakref
|
||||||
from configparser import ConfigParser, NoOptionError, NoSectionError
|
from configparser import ConfigParser, NoOptionError, NoSectionError
|
||||||
|
from datetime import datetime
|
||||||
|
from operator import itemgetter, methodcaller
|
||||||
from statistics import mean
|
from statistics import mean
|
||||||
from xmlrpc.client import Fault, ProtocolError, ServerProxy, Transport, Server
|
|
||||||
from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
|
|
||||||
from urllib.request import urlopen, Request
|
|
||||||
from urllib.error import HTTPError, URLError
|
from urllib.error import HTTPError, URLError
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
from xmlrpc.client import Fault, ProtocolError, Server, ServerProxy, Transport
|
||||||
|
from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
|
||||||
|
|
||||||
|
import orjson
|
||||||
|
|
||||||
# Correct issue #1025 by monkey path the xmlrpc lib
|
# Correct issue #1025 by monkey path the xmlrpc lib
|
||||||
from defusedxml.xmlrpc import monkey_patch
|
from defusedxml.xmlrpc import monkey_patch
|
||||||
@ -91,7 +89,7 @@ def printandflush(string):
|
|||||||
|
|
||||||
def to_ascii(s):
|
def to_ascii(s):
|
||||||
"""Convert the bytes string to a ASCII string
|
"""Convert the bytes string to a ASCII string
|
||||||
Usefull to remove accent (diacritics)"""
|
Useful to remove accent (diacritics)"""
|
||||||
if isinstance(s, binary_type):
|
if isinstance(s, binary_type):
|
||||||
return s.decode()
|
return s.decode()
|
||||||
return s.encode('ascii', 'ignore').decode()
|
return s.encode('ascii', 'ignore').decode()
|
||||||
@ -136,10 +134,9 @@ def b(s, errors='replace'):
|
|||||||
def nativestr(s, errors='replace'):
|
def nativestr(s, errors='replace'):
|
||||||
if isinstance(s, text_type):
|
if isinstance(s, text_type):
|
||||||
return s
|
return s
|
||||||
elif isinstance(s, (int, float)):
|
if isinstance(s, (int, float)):
|
||||||
return s.__str__()
|
return s.__str__()
|
||||||
else:
|
return s.decode('utf-8', errors=errors)
|
||||||
return s.decode('utf-8', errors=errors)
|
|
||||||
|
|
||||||
|
|
||||||
def system_exec(command):
|
def system_exec(command):
|
||||||
@ -147,7 +144,7 @@ def system_exec(command):
|
|||||||
try:
|
try:
|
||||||
res = subprocess.run(command.split(' '), stdout=subprocess.PIPE).stdout.decode('utf-8')
|
res = subprocess.run(command.split(' '), stdout=subprocess.PIPE).stdout.decode('utf-8')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
res = 'ERROR: {}'.format(e)
|
res = f'ERROR: {e}'
|
||||||
return res.rstrip()
|
return res.rstrip()
|
||||||
|
|
||||||
|
|
||||||
@ -156,7 +153,7 @@ def subsample(data, sampling):
|
|||||||
|
|
||||||
Data should be a list of numerical itervalues
|
Data should be a list of numerical itervalues
|
||||||
|
|
||||||
Return a subsampled list of sampling lenght
|
Return a subsampled list of sampling length
|
||||||
"""
|
"""
|
||||||
if len(data) <= sampling:
|
if len(data) <= sampling:
|
||||||
return data
|
return data
|
||||||
@ -204,7 +201,7 @@ def is_admin():
|
|||||||
try:
|
try:
|
||||||
return ctypes.windll.shell32.IsUserAnAdmin()
|
return ctypes.windll.shell32.IsUserAnAdmin()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Admin check failed with error: %s" % e)
|
print(f"Admin check failed with error: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -219,13 +216,13 @@ def key_exist_value_not_none(k, d):
|
|||||||
return k in d and d[k] is not None
|
return k in d and d[k] is not None
|
||||||
|
|
||||||
|
|
||||||
def key_exist_value_not_none_not_v(k, d, value='', lengh=None):
|
def key_exist_value_not_none_not_v(k, d, value='', length=None):
|
||||||
# Return True if:
|
# Return True if:
|
||||||
# - key k exists
|
# - key k exists
|
||||||
# - d[k] is not None
|
# - d[k] is not None
|
||||||
# - d[k] != value
|
# - d[k] != value
|
||||||
# - if lengh is not None and len(d[k]) >= lengh
|
# - if length is not None and len(d[k]) >= length
|
||||||
return k in d and d[k] is not None and d[k] != value and (lengh is None or len(d[k]) >= lengh)
|
return k in d and d[k] is not None and d[k] != value and (length is None or len(d[k]) >= length)
|
||||||
|
|
||||||
|
|
||||||
def disable(class_name, var):
|
def disable(class_name, var):
|
||||||
@ -301,7 +298,7 @@ def urlopen_auth(url, username, password):
|
|||||||
return urlopen(
|
return urlopen(
|
||||||
Request(
|
Request(
|
||||||
url,
|
url,
|
||||||
headers={'Authorization': 'Basic ' + base64.b64encode(('%s:%s' % (username, password)).encode()).decode()},
|
headers={'Authorization': 'Basic ' + base64.b64encode((f'{username}:{password}').encode()).decode()},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -312,9 +309,9 @@ def json_dumps(data):
|
|||||||
Manage the issue #815 for Windows OS with UnicodeDecodeError catching.
|
Manage the issue #815 for Windows OS with UnicodeDecodeError catching.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return ujson.dumps(data)
|
return orjson.dumps(data)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return ujson.dumps(data, ensure_ascii=False)
|
return orjson.dumps(data, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
def dictlist(data, item):
|
def dictlist(data, item):
|
||||||
@ -339,8 +336,7 @@ def json_dumps_dictlist(data, item):
|
|||||||
dl = dictlist(data, item)
|
dl = dictlist(data, item)
|
||||||
if dl is None:
|
if dl is None:
|
||||||
return None
|
return None
|
||||||
else:
|
return json_dumps(dl)
|
||||||
return json_dumps(dl)
|
|
||||||
|
|
||||||
|
|
||||||
def string_value_to_float(s):
|
def string_value_to_float(s):
|
||||||
@ -429,12 +425,12 @@ def weak_lru_cache(maxsize=128, typed=False):
|
|||||||
|
|
||||||
|
|
||||||
def namedtuple_to_dict(data):
|
def namedtuple_to_dict(data):
|
||||||
"""Convert a namedtuple to a dict, using the _asdict() method embeded in PsUtil stats."""
|
"""Convert a namedtuple to a dict, using the _asdict() method embedded in PsUtil stats."""
|
||||||
return {k: (v._asdict() if hasattr(v, '_asdict') else v) for k, v in data.items()}
|
return {k: (v._asdict() if hasattr(v, '_asdict') else v) for k, v in data.items()}
|
||||||
|
|
||||||
|
|
||||||
def list_of_namedtuple_to_list_of_dict(data):
|
def list_of_namedtuple_to_list_of_dict(data):
|
||||||
"""Convert a list of namedtuples to a dict, using the _asdict() method embeded in PsUtil stats."""
|
"""Convert a list of namedtuples to a dict, using the _asdict() method embedded in PsUtil stats."""
|
||||||
return [namedtuple_to_dict(d) for d in data]
|
return [namedtuple_to_dict(d) for d in data]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,7 +11,7 @@
|
|||||||
from glances.attribute import GlancesAttribute
|
from glances.attribute import GlancesAttribute
|
||||||
|
|
||||||
|
|
||||||
class GlancesHistory(object):
|
class GlancesHistory:
|
||||||
"""This class manage a dict of GlancesAttribute
|
"""This class manage a dict of GlancesAttribute
|
||||||
- key: stats name
|
- key: stats name
|
||||||
- value: GlancesAttribute"""
|
- value: GlancesAttribute"""
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,13 +8,12 @@
|
|||||||
|
|
||||||
"""Custom logger class."""
|
"""Custom logger class."""
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import getpass
|
import getpass
|
||||||
import tempfile
|
import json
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import logging.config
|
import logging.config
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from glances.globals import safe_makedirs
|
from glances.globals import safe_makedirs
|
||||||
|
|
||||||
@ -37,7 +35,7 @@ elif os.path.isdir(_XDG_CACHE_HOME) and os.access(_XDG_CACHE_HOME, os.W_OK):
|
|||||||
safe_makedirs(os.path.join(_XDG_CACHE_HOME, 'glances'))
|
safe_makedirs(os.path.join(_XDG_CACHE_HOME, 'glances'))
|
||||||
LOG_FILENAME = os.path.join(_XDG_CACHE_HOME, 'glances', 'glances.log')
|
LOG_FILENAME = os.path.join(_XDG_CACHE_HOME, 'glances', 'glances.log')
|
||||||
else:
|
else:
|
||||||
LOG_FILENAME = os.path.join(tempfile.gettempdir(), 'glances-{}.log'.format(getpass.getuser()))
|
LOG_FILENAME = os.path.join(tempfile.gettempdir(), f'glances-{getpass.getuser()}.log')
|
||||||
|
|
||||||
# Define the logging configuration
|
# Define the logging configuration
|
||||||
LOGGING_CFG = {
|
LOGGING_CFG = {
|
||||||
@ -89,7 +87,7 @@ def glances_logger(env_key='LOG_CFG'):
|
|||||||
user_file = os.getenv(env_key, None)
|
user_file = os.getenv(env_key, None)
|
||||||
if user_file and os.path.exists(user_file):
|
if user_file and os.path.exists(user_file):
|
||||||
# A user file as been defined. Use it...
|
# A user file as been defined. Use it...
|
||||||
with open(user_file, 'rt') as f:
|
with open(user_file) as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
||||||
# Load the configuration
|
# Load the configuration
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -15,14 +14,14 @@ import tempfile
|
|||||||
from logging import DEBUG
|
from logging import DEBUG
|
||||||
from warnings import simplefilter
|
from warnings import simplefilter
|
||||||
|
|
||||||
from glances import __version__, psutil_version, __apiversion__
|
from glances import __apiversion__, __version__, psutil_version
|
||||||
from glances.globals import WINDOWS, disable, enable
|
|
||||||
from glances.config import Config
|
from glances.config import Config
|
||||||
|
from glances.globals import WINDOWS, disable, enable
|
||||||
|
from glances.logger import LOG_FILENAME, logger
|
||||||
from glances.processes import sort_processes_key_list
|
from glances.processes import sort_processes_key_list
|
||||||
from glances.logger import logger, LOG_FILENAME
|
|
||||||
|
|
||||||
|
|
||||||
class GlancesMain(object):
|
class GlancesMain:
|
||||||
"""Main class to manage Glances instance."""
|
"""Main class to manage Glances instance."""
|
||||||
|
|
||||||
# Default stats' minimum refresh time is 2 seconds
|
# Default stats' minimum refresh time is 2 seconds
|
||||||
@ -101,10 +100,10 @@ Examples of use:
|
|||||||
|
|
||||||
def version_msg(self):
|
def version_msg(self):
|
||||||
"""Return the version message."""
|
"""Return the version message."""
|
||||||
version = 'Glances version:\t{}\n'.format(__version__)
|
version = f'Glances version:\t{__version__}\n'
|
||||||
version += 'Glances API version:\t{}\n'.format(__apiversion__)
|
version += f'Glances API version:\t{__apiversion__}\n'
|
||||||
version += 'PsUtil version:\t\t{}\n'.format(psutil_version)
|
version += f'PsUtil version:\t\t{psutil_version}\n'
|
||||||
version += 'Log file:\t\t{}\n'.format(LOG_FILENAME)
|
version += f'Log file:\t\t{LOG_FILENAME}\n'
|
||||||
return version
|
return version
|
||||||
|
|
||||||
def init_args(self):
|
def init_args(self):
|
||||||
@ -241,7 +240,7 @@ Examples of use:
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--enable-irq', action='store_true', default=False, dest='enable_irq', help='enable IRQ module'
|
'--enable-irq', action='store_true', default=False, dest='enable_irq', help='enable IRQ module'
|
||||||
),
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--enable-process-extended',
|
'--enable-process-extended',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
@ -255,14 +254,14 @@ Examples of use:
|
|||||||
default=True,
|
default=True,
|
||||||
dest='enable_separator',
|
dest='enable_separator',
|
||||||
help='disable separator in the UI (between top and others modules)',
|
help='disable separator in the UI (between top and others modules)',
|
||||||
),
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--disable-cursor',
|
'--disable-cursor',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
dest='disable_cursor',
|
dest='disable_cursor',
|
||||||
help='disable cursor (process selection) in the UI',
|
help='disable cursor (process selection) in the UI',
|
||||||
),
|
)
|
||||||
# Sort processes list
|
# Sort processes list
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--sort-processes',
|
'--sort-processes',
|
||||||
@ -334,7 +333,7 @@ Examples of use:
|
|||||||
default=None,
|
default=None,
|
||||||
type=int,
|
type=int,
|
||||||
dest='port',
|
dest='port',
|
||||||
help='define the client/server TCP port [default: {}]'.format(self.server_port),
|
help=f'define the client/server TCP port [default: {self.server_port}]',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-B',
|
'-B',
|
||||||
@ -374,7 +373,7 @@ Examples of use:
|
|||||||
default=self.DEFAULT_REFRESH_TIME,
|
default=self.DEFAULT_REFRESH_TIME,
|
||||||
type=float,
|
type=float,
|
||||||
dest='time',
|
dest='time',
|
||||||
help='set minimum refresh rate in seconds [default: {} sec]'.format(self.DEFAULT_REFRESH_TIME),
|
help=f'set minimum refresh rate in seconds [default: {self.DEFAULT_REFRESH_TIME} sec]',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-w',
|
'-w',
|
||||||
@ -382,14 +381,14 @@ Examples of use:
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
dest='webserver',
|
dest='webserver',
|
||||||
help='run Glances in web server mode (FastAPI, Uvicorn, Jinja2 and OrJsonLib needed)',
|
help='run Glances in web server mode (FastAPI, Uvicorn, Jinja2 libs needed)',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--cached-time',
|
'--cached-time',
|
||||||
default=self.cached_time,
|
default=self.cached_time,
|
||||||
type=int,
|
type=int,
|
||||||
dest='cached_time',
|
dest='cached_time',
|
||||||
help='set the server cache time [default: {} sec]'.format(self.cached_time),
|
help=f'set the server cache time [default: {self.cached_time} sec]',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--stop-after',
|
'--stop-after',
|
||||||
@ -577,7 +576,7 @@ Examples of use:
|
|||||||
if args.time == self.DEFAULT_REFRESH_TIME:
|
if args.time == self.DEFAULT_REFRESH_TIME:
|
||||||
args.time = global_refresh
|
args.time = global_refresh
|
||||||
|
|
||||||
logger.debug('Global refresh rate is set to {} seconds'.format(args.time))
|
logger.debug(f'Global refresh rate is set to {args.time} seconds')
|
||||||
|
|
||||||
def init_plugins(self, args):
|
def init_plugins(self, args):
|
||||||
"""Init Glances plugins"""
|
"""Init Glances plugins"""
|
||||||
@ -585,7 +584,7 @@ Examples of use:
|
|||||||
for s in self.config.sections():
|
for s in self.config.sections():
|
||||||
if self.config.has_section(s) and (self.config.get_bool_value(s, 'disable', False)):
|
if self.config.has_section(s) and (self.config.get_bool_value(s, 'disable', False)):
|
||||||
disable(args, s)
|
disable(args, s)
|
||||||
logger.debug('{} disabled by the configuration file'.format(s))
|
logger.debug(f'{s} disabled by the configuration file')
|
||||||
# The configuration key can be overwrite from the command line
|
# The configuration key can be overwrite from the command line
|
||||||
if args and args.disable_plugin and 'all' in args.disable_plugin.split(','):
|
if args and args.disable_plugin and 'all' in args.disable_plugin.split(','):
|
||||||
if not args.enable_plugin:
|
if not args.enable_plugin:
|
||||||
@ -664,19 +663,19 @@ Examples of use:
|
|||||||
# Interactive or file password
|
# Interactive or file password
|
||||||
if args.server:
|
if args.server:
|
||||||
args.password = self.__get_password(
|
args.password = self.__get_password(
|
||||||
description='Define the Glances server password ({} username): '.format(args.username),
|
description=f'Define the Glances server password ({args.username} username): ',
|
||||||
confirm=True,
|
confirm=True,
|
||||||
username=args.username,
|
username=args.username,
|
||||||
)
|
)
|
||||||
elif args.webserver:
|
elif args.webserver:
|
||||||
args.password = self.__get_password(
|
args.password = self.__get_password(
|
||||||
description='Define the Glances webserver password ({} username): '.format(args.username),
|
description=f'Define the Glances webserver password ({args.username} username): ',
|
||||||
confirm=True,
|
confirm=True,
|
||||||
username=args.username,
|
username=args.username,
|
||||||
)
|
)
|
||||||
elif args.client:
|
elif args.client:
|
||||||
args.password = self.__get_password(
|
args.password = self.__get_password(
|
||||||
description='Enter the Glances server password ({} username): '.format(args.username),
|
description=f'Enter the Glances server password ({args.username} username): ',
|
||||||
clear=True,
|
clear=True,
|
||||||
username=args.username,
|
username=args.username,
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,16 +8,16 @@
|
|||||||
|
|
||||||
"""Manage Glances update."""
|
"""Manage Glances update."""
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import threading
|
|
||||||
import json
|
import json
|
||||||
import pickle
|
|
||||||
import os
|
import os
|
||||||
|
import pickle
|
||||||
|
import threading
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from ssl import CertificateError
|
from ssl import CertificateError
|
||||||
|
|
||||||
from glances import __version__
|
from glances import __version__
|
||||||
from glances.globals import nativestr, urlopen, HTTPError, URLError, safe_makedirs
|
|
||||||
from glances.config import user_cache_dir
|
from glances.config import user_cache_dir
|
||||||
|
from glances.globals import HTTPError, URLError, nativestr, safe_makedirs, urlopen
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -26,13 +25,13 @@ try:
|
|||||||
|
|
||||||
PACKAGING_IMPORT = True
|
PACKAGING_IMPORT = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Unable to import 'packaging' module ({}). Glances cannot check for updates.".format(e))
|
logger.warning(f"Unable to import 'packaging' module ({e}). Glances cannot check for updates.")
|
||||||
PACKAGING_IMPORT = False
|
PACKAGING_IMPORT = False
|
||||||
|
|
||||||
PYPI_API_URL = 'https://pypi.python.org/pypi/Glances/json'
|
PYPI_API_URL = 'https://pypi.python.org/pypi/Glances/json'
|
||||||
|
|
||||||
|
|
||||||
class Outdated(object):
|
class Outdated:
|
||||||
"""
|
"""
|
||||||
This class aims at providing methods to warn the user when a new Glances
|
This class aims at providing methods to warn the user when a new Glances
|
||||||
version is available on the PyPI repository (https://pypi.python.org/pypi/Glances/).
|
version is available on the PyPI repository (https://pypi.python.org/pypi/Glances/).
|
||||||
@ -46,7 +45,7 @@ class Outdated(object):
|
|||||||
self.cache_file = os.path.join(self.cache_dir, 'glances-version.db')
|
self.cache_file = os.path.join(self.cache_dir, 'glances-version.db')
|
||||||
|
|
||||||
# Set default value...
|
# Set default value...
|
||||||
self.data = {u'installed_version': __version__, u'latest_version': '0.0', u'refresh_date': datetime.now()}
|
self.data = {'installed_version': __version__, 'latest_version': '0.0', 'refresh_date': datetime.now()}
|
||||||
|
|
||||||
# Disable update check if `packaging` is not installed
|
# Disable update check if `packaging` is not installed
|
||||||
if not PACKAGING_IMPORT:
|
if not PACKAGING_IMPORT:
|
||||||
@ -56,7 +55,7 @@ class Outdated(object):
|
|||||||
if not self.args.disable_check_update:
|
if not self.args.disable_check_update:
|
||||||
self.load_config(config)
|
self.load_config(config)
|
||||||
|
|
||||||
logger.debug("Check Glances version up-to-date: {}".format(not self.args.disable_check_update))
|
logger.debug(f"Check Glances version up-to-date: {not self.args.disable_check_update}")
|
||||||
|
|
||||||
# And update !
|
# And update !
|
||||||
self.get_pypi_version()
|
self.get_pypi_version()
|
||||||
@ -68,7 +67,7 @@ class Outdated(object):
|
|||||||
if hasattr(config, 'has_section') and config.has_section(global_section):
|
if hasattr(config, 'has_section') and config.has_section(global_section):
|
||||||
self.args.disable_check_update = config.get_value(global_section, 'check_update').lower() == 'false'
|
self.args.disable_check_update = config.get_value(global_section, 'check_update').lower() == 'false'
|
||||||
else:
|
else:
|
||||||
logger.debug("Cannot find section {} in the configuration file".format(global_section))
|
logger.debug(f"Cannot find section {global_section} in the configuration file")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -110,9 +109,7 @@ class Outdated(object):
|
|||||||
# Check is disabled by configuration
|
# Check is disabled by configuration
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(f"Check Glances version (installed: {self.installed_version()} / latest: {self.latest_version()})")
|
||||||
"Check Glances version (installed: {} / latest: {})".format(self.installed_version(), self.latest_version())
|
|
||||||
)
|
|
||||||
return Version(self.latest_version()) > Version(self.installed_version())
|
return Version(self.latest_version()) > Version(self.installed_version())
|
||||||
|
|
||||||
def _load_cache(self):
|
def _load_cache(self):
|
||||||
@ -124,7 +121,7 @@ class Outdated(object):
|
|||||||
with open(self.cache_file, 'rb') as f:
|
with open(self.cache_file, 'rb') as f:
|
||||||
cached_data = pickle.load(f)
|
cached_data = pickle.load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug("Cannot read version from cache file: {} ({})".format(self.cache_file, e))
|
logger.debug(f"Cannot read version from cache file: {self.cache_file} ({e})")
|
||||||
else:
|
else:
|
||||||
logger.debug("Read version from cache file")
|
logger.debug("Read version from cache file")
|
||||||
if (
|
if (
|
||||||
@ -147,21 +144,21 @@ class Outdated(object):
|
|||||||
with open(self.cache_file, 'wb') as f:
|
with open(self.cache_file, 'wb') as f:
|
||||||
pickle.dump(self.data, f)
|
pickle.dump(self.data, f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Cannot write version to cache file {} ({})".format(self.cache_file, e))
|
logger.error(f"Cannot write version to cache file {self.cache_file} ({e})")
|
||||||
|
|
||||||
def _update_pypi_version(self):
|
def _update_pypi_version(self):
|
||||||
"""Get the latest PyPI version (as a string) via the RESTful JSON API"""
|
"""Get the latest PyPI version (as a string) via the RESTful JSON API"""
|
||||||
logger.debug("Get latest Glances version from the PyPI RESTful API ({})".format(PYPI_API_URL))
|
logger.debug(f"Get latest Glances version from the PyPI RESTful API ({PYPI_API_URL})")
|
||||||
|
|
||||||
# Update the current time
|
# Update the current time
|
||||||
self.data[u'refresh_date'] = datetime.now()
|
self.data['refresh_date'] = datetime.now()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = urlopen(PYPI_API_URL, timeout=3).read()
|
res = urlopen(PYPI_API_URL, timeout=3).read()
|
||||||
except (HTTPError, URLError, CertificateError) as e:
|
except (HTTPError, URLError, CertificateError) as e:
|
||||||
logger.debug("Cannot get Glances version from the PyPI RESTful API ({})".format(e))
|
logger.debug(f"Cannot get Glances version from the PyPI RESTful API ({e})")
|
||||||
else:
|
else:
|
||||||
self.data[u'latest_version'] = json.loads(nativestr(res))['info']['version']
|
self.data['latest_version'] = json.loads(nativestr(res))['info']['version']
|
||||||
logger.debug("Save Glances version to the cache file")
|
logger.debug("Save Glances version to the cache file")
|
||||||
|
|
||||||
# Save result to the cache file
|
# Save result to the cache file
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,12 +8,10 @@
|
|||||||
|
|
||||||
"""Manage bars for Glances output."""
|
"""Manage bars for Glances output."""
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
|
|
||||||
from math import modf
|
from math import modf
|
||||||
|
|
||||||
|
|
||||||
class Bar(object):
|
class Bar:
|
||||||
"""Manage bar (progression or status).
|
"""Manage bar (progression or status).
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@ -76,6 +73,7 @@ class Bar(object):
|
|||||||
return self.__size
|
return self.__size
|
||||||
if self.__display_value:
|
if self.__display_value:
|
||||||
return self.__size - 6
|
return self.__size - 6
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def percent(self):
|
def percent(self):
|
||||||
@ -114,7 +112,7 @@ class Bar(object):
|
|||||||
ret, '>' if self.percent > self.max_value else ' ', self.max_value, self.__unit_char
|
ret, '>' if self.percent > self.max_value else ' ', self.max_value, self.__unit_char
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ret = '{}{:5.1f}{}'.format(ret, self.percent, self.__unit_char)
|
ret = f'{ret}{self.percent:5.1f}{self.__unit_char}'
|
||||||
|
|
||||||
# Add overlay
|
# Add overlay
|
||||||
if overlay and len(overlay) < len(ret) - 6:
|
if overlay and len(overlay) < len(ret) - 6:
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -8,15 +7,15 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
"""Curses interface class."""
|
"""Curses interface class."""
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
|
import getpass
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from glances.globals import MACOS, WINDOWS, nativestr, u, itervalues, enable, disable
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.events_list import glances_events
|
from glances.events_list import glances_events
|
||||||
from glances.processes import glances_processes, sort_processes_key_list
|
from glances.globals import MACOS, WINDOWS, disable, enable, itervalues, nativestr, u
|
||||||
|
from glances.logger import logger
|
||||||
from glances.outputs.glances_unicode import unicode_message
|
from glances.outputs.glances_unicode import unicode_message
|
||||||
|
from glances.processes import glances_processes, sort_processes_key_list
|
||||||
from glances.timer import Timer
|
from glances.timer import Timer
|
||||||
|
|
||||||
# Import curses library for "normal" operating system
|
# Import curses library for "normal" operating system
|
||||||
@ -31,7 +30,7 @@ except ImportError:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
class _GlancesCurses(object):
|
class _GlancesCurses:
|
||||||
"""This class manages the curses display (and key pressed).
|
"""This class manages the curses display (and key pressed).
|
||||||
|
|
||||||
Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser.
|
Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser.
|
||||||
@ -146,15 +145,15 @@ class _GlancesCurses(object):
|
|||||||
logger.critical("Cannot init the curses library.\n")
|
logger.critical("Cannot init the curses library.\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
logger.debug("Curses library initialized with term: {}".format(curses.longname()))
|
logger.debug(f"Curses library initialized with term: {curses.longname()}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if args.export:
|
if args.export:
|
||||||
logger.info("Cannot init the curses library, quiet mode on and export.")
|
logger.info("Cannot init the curses library, quiet mode on and export.")
|
||||||
args.quiet = True
|
args.quiet = True
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
logger.critical("Cannot init the curses library ({})".format(e))
|
logger.critical(f"Cannot init the curses library ({e})")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Load configuration file
|
# Load configuration file
|
||||||
self.load_config(config)
|
self.load_config(config)
|
||||||
@ -163,7 +162,7 @@ class _GlancesCurses(object):
|
|||||||
self._init_cursor()
|
self._init_cursor()
|
||||||
|
|
||||||
# Init the colors
|
# Init the colors
|
||||||
self._init_colors()
|
self.colors_list = build_colors_list(args)
|
||||||
|
|
||||||
# Init main window
|
# Init main window
|
||||||
self.term_window = self.screen.subwin(0, 0)
|
self.term_window = self.screen.subwin(0, 0)
|
||||||
@ -196,8 +195,10 @@ class _GlancesCurses(object):
|
|||||||
"""Load the outputs section of the configuration file."""
|
"""Load the outputs section of the configuration file."""
|
||||||
if config is not None and config.has_section('outputs'):
|
if config is not None and config.has_section('outputs'):
|
||||||
logger.debug('Read the outputs section in the configuration file')
|
logger.debug('Read the outputs section in the configuration file')
|
||||||
# Separator ?
|
# Separator
|
||||||
self.args.enable_separator = config.get_bool_value('outputs', 'separator', default=True)
|
self.args.enable_separator = config.get_bool_value(
|
||||||
|
'outputs', 'separator', default=self.args.enable_separator
|
||||||
|
)
|
||||||
# Set the left sidebar list
|
# Set the left sidebar list
|
||||||
self._left_sidebar = config.get_list_value('outputs', 'left_menu', default=self._left_sidebar)
|
self._left_sidebar = config.get_list_value('outputs', 'left_menu', default=self._left_sidebar)
|
||||||
|
|
||||||
@ -215,130 +216,6 @@ class _GlancesCurses(object):
|
|||||||
curses.cbreak()
|
curses.cbreak()
|
||||||
self.set_cursor(0)
|
self.set_cursor(0)
|
||||||
|
|
||||||
def _init_colors(self):
|
|
||||||
"""Init the Curses color layout."""
|
|
||||||
|
|
||||||
# Set curses options
|
|
||||||
try:
|
|
||||||
if hasattr(curses, 'start_color'):
|
|
||||||
curses.start_color()
|
|
||||||
logger.debug('Curses interface compatible with {} colors'.format(curses.COLORS))
|
|
||||||
if hasattr(curses, 'use_default_colors'):
|
|
||||||
curses.use_default_colors()
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning('Error initializing terminal color ({})'.format(e))
|
|
||||||
|
|
||||||
# Init colors
|
|
||||||
if self.args.disable_bold:
|
|
||||||
A_BOLD = 0
|
|
||||||
self.args.disable_bg = True
|
|
||||||
else:
|
|
||||||
A_BOLD = curses.A_BOLD
|
|
||||||
|
|
||||||
self.title_color = A_BOLD
|
|
||||||
self.title_underline_color = A_BOLD | curses.A_UNDERLINE
|
|
||||||
self.help_color = A_BOLD
|
|
||||||
|
|
||||||
if curses.has_colors():
|
|
||||||
# The screen is compatible with a colored design
|
|
||||||
# ex: export TERM=xterm-256color
|
|
||||||
# export TERM=xterm-color
|
|
||||||
|
|
||||||
curses.init_pair(1, -1, -1)
|
|
||||||
if self.args.disable_bg:
|
|
||||||
curses.init_pair(2, curses.COLOR_RED, -1)
|
|
||||||
curses.init_pair(3, curses.COLOR_GREEN, -1)
|
|
||||||
curses.init_pair(5, curses.COLOR_MAGENTA, -1)
|
|
||||||
else:
|
|
||||||
curses.init_pair(2, -1, curses.COLOR_RED)
|
|
||||||
curses.init_pair(3, -1, curses.COLOR_GREEN)
|
|
||||||
curses.init_pair(5, -1, curses.COLOR_MAGENTA)
|
|
||||||
curses.init_pair(4, curses.COLOR_BLUE, -1)
|
|
||||||
curses.init_pair(6, curses.COLOR_RED, -1)
|
|
||||||
curses.init_pair(7, curses.COLOR_GREEN, -1)
|
|
||||||
curses.init_pair(8, curses.COLOR_MAGENTA, -1)
|
|
||||||
|
|
||||||
# Colors text styles
|
|
||||||
self.no_color = curses.color_pair(1)
|
|
||||||
self.default_color = curses.color_pair(3) | A_BOLD
|
|
||||||
self.nice_color = curses.color_pair(8)
|
|
||||||
self.cpu_time_color = curses.color_pair(8)
|
|
||||||
self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD
|
|
||||||
self.ifWARNING_color = curses.color_pair(5) | A_BOLD
|
|
||||||
self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD
|
|
||||||
self.default_color2 = curses.color_pair(7)
|
|
||||||
self.ifCAREFUL_color2 = curses.color_pair(4)
|
|
||||||
self.ifWARNING_color2 = curses.color_pair(8) | A_BOLD
|
|
||||||
self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD
|
|
||||||
self.ifINFO_color = curses.color_pair(4)
|
|
||||||
self.filter_color = A_BOLD
|
|
||||||
self.selected_color = A_BOLD
|
|
||||||
self.separator = curses.color_pair(1)
|
|
||||||
|
|
||||||
if curses.COLORS > 8:
|
|
||||||
# ex: export TERM=xterm-256color
|
|
||||||
colors_list = [curses.COLOR_CYAN, curses.COLOR_YELLOW]
|
|
||||||
for i in range(0, 3):
|
|
||||||
try:
|
|
||||||
curses.init_pair(i + 9, colors_list[i], -1)
|
|
||||||
except Exception:
|
|
||||||
curses.init_pair(i + 9, -1, -1)
|
|
||||||
self.filter_color = curses.color_pair(9) | A_BOLD
|
|
||||||
self.selected_color = curses.color_pair(10) | A_BOLD
|
|
||||||
# Define separator line style
|
|
||||||
curses.init_color(11, 500, 500, 500)
|
|
||||||
curses.init_pair(11, curses.COLOR_BLACK, -1)
|
|
||||||
self.separator = curses.color_pair(11)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# The screen is NOT compatible with a colored design
|
|
||||||
# switch to B&W text styles
|
|
||||||
# ex: export TERM=xterm-mono
|
|
||||||
self.no_color = -1
|
|
||||||
self.default_color = -1
|
|
||||||
self.nice_color = A_BOLD
|
|
||||||
self.cpu_time_color = A_BOLD
|
|
||||||
self.ifCAREFUL_color = A_BOLD
|
|
||||||
self.ifWARNING_color = curses.A_UNDERLINE
|
|
||||||
self.ifCRITICAL_color = curses.A_REVERSE
|
|
||||||
self.default_color2 = -1
|
|
||||||
self.ifCAREFUL_color2 = A_BOLD
|
|
||||||
self.ifWARNING_color2 = curses.A_UNDERLINE
|
|
||||||
self.ifCRITICAL_color2 = curses.A_REVERSE
|
|
||||||
self.ifINFO_color = A_BOLD
|
|
||||||
self.filter_color = A_BOLD
|
|
||||||
self.selected_color = A_BOLD
|
|
||||||
self.separator = -1
|
|
||||||
|
|
||||||
# Define the colors list (hash table) for stats
|
|
||||||
self.colors_list = {
|
|
||||||
'DEFAULT': self.no_color,
|
|
||||||
'UNDERLINE': curses.A_UNDERLINE,
|
|
||||||
'BOLD': A_BOLD,
|
|
||||||
'SORT': curses.A_UNDERLINE | A_BOLD,
|
|
||||||
'OK': self.default_color2,
|
|
||||||
'MAX': self.default_color2 | A_BOLD,
|
|
||||||
'FILTER': self.filter_color,
|
|
||||||
'TITLE': self.title_color,
|
|
||||||
'PROCESS': self.default_color2,
|
|
||||||
'PROCESS_SELECTED': self.default_color2 | curses.A_UNDERLINE,
|
|
||||||
'STATUS': self.default_color2,
|
|
||||||
'NICE': self.nice_color,
|
|
||||||
'CPU_TIME': self.cpu_time_color,
|
|
||||||
'CAREFUL': self.ifCAREFUL_color2,
|
|
||||||
'WARNING': self.ifWARNING_color2,
|
|
||||||
'CRITICAL': self.ifCRITICAL_color2,
|
|
||||||
'OK_LOG': self.default_color,
|
|
||||||
'CAREFUL_LOG': self.ifCAREFUL_color,
|
|
||||||
'WARNING_LOG': self.ifWARNING_color,
|
|
||||||
'CRITICAL_LOG': self.ifCRITICAL_color,
|
|
||||||
'PASSWORD': curses.A_PROTECT,
|
|
||||||
'SELECTED': self.selected_color,
|
|
||||||
'INFO': self.ifINFO_color,
|
|
||||||
'ERROR': self.selected_color,
|
|
||||||
'SEPARATOR': self.separator,
|
|
||||||
}
|
|
||||||
|
|
||||||
def set_cursor(self, value):
|
def set_cursor(self, value):
|
||||||
"""Configure the curse cursor appearance.
|
"""Configure the curse cursor appearance.
|
||||||
|
|
||||||
@ -354,8 +231,7 @@ class _GlancesCurses(object):
|
|||||||
|
|
||||||
def get_key(self, window):
|
def get_key(self, window):
|
||||||
# TODO: Check issue #163
|
# TODO: Check issue #163
|
||||||
ret = window.getch()
|
return window.getch()
|
||||||
return ret
|
|
||||||
|
|
||||||
def __catch_key(self, return_to_browser=False):
|
def __catch_key(self, return_to_browser=False):
|
||||||
# Catch the pressed key
|
# Catch the pressed key
|
||||||
@ -364,7 +240,7 @@ class _GlancesCurses(object):
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
# Actions (available in the global hotkey dict)...
|
# Actions (available in the global hotkey dict)...
|
||||||
logger.debug("Keypressed (code: {})".format(self.pressedkey))
|
logger.debug(f"Keypressed (code: {self.pressedkey})")
|
||||||
for hotkey in self._hotkeys:
|
for hotkey in self._hotkeys:
|
||||||
if self.pressedkey == ord(hotkey) and 'switch' in self._hotkeys[hotkey]:
|
if self.pressedkey == ord(hotkey) and 'switch' in self._hotkeys[hotkey]:
|
||||||
self._handle_switch(hotkey)
|
self._handle_switch(hotkey)
|
||||||
@ -491,10 +367,10 @@ class _GlancesCurses(object):
|
|||||||
if return_to_browser:
|
if return_to_browser:
|
||||||
logger.info("Stop Glances client and return to the browser")
|
logger.info("Stop Glances client and return to the browser")
|
||||||
else:
|
else:
|
||||||
logger.info("Stop Glances (keypressed: {})".format(self.pressedkey))
|
logger.info(f"Stop Glances (keypressed: {self.pressedkey})")
|
||||||
|
|
||||||
def _handle_refresh(self):
|
def _handle_refresh(self):
|
||||||
pass
|
glances_processes.reset_internal_cache()
|
||||||
|
|
||||||
def loop_position(self):
|
def loop_position(self):
|
||||||
"""Return the current sort in the loop"""
|
"""Return the current sort in the loop"""
|
||||||
@ -569,13 +445,14 @@ class _GlancesCurses(object):
|
|||||||
self.new_line()
|
self.new_line()
|
||||||
self.line -= 1
|
self.line -= 1
|
||||||
line_width = self.term_window.getmaxyx()[1] - self.column
|
line_width = self.term_window.getmaxyx()[1] - self.column
|
||||||
self.term_window.addnstr(
|
if self.line >= 0:
|
||||||
self.line,
|
self.term_window.addnstr(
|
||||||
self.column,
|
self.line,
|
||||||
unicode_message('MEDIUM_LINE', self.args) * line_width,
|
self.column,
|
||||||
line_width,
|
unicode_message('MEDIUM_LINE', self.args) * line_width,
|
||||||
self.colors_list[color],
|
line_width,
|
||||||
)
|
self.colors_list[color],
|
||||||
|
)
|
||||||
|
|
||||||
def __get_stat_display(self, stats, layer):
|
def __get_stat_display(self, stats, layer):
|
||||||
"""Return a dict of dict with all the stats display.
|
"""Return a dict of dict with all the stats display.
|
||||||
@ -712,7 +589,7 @@ class _GlancesCurses(object):
|
|||||||
# Display graph generation popup
|
# Display graph generation popup
|
||||||
if self.args.generate_graph:
|
if self.args.generate_graph:
|
||||||
if 'graph' in stats.getExportsList():
|
if 'graph' in stats.getExportsList():
|
||||||
self.display_popup('Generate graph in {}'.format(self.args.export_graph_path))
|
self.display_popup(f'Generate graph in {self.args.export_graph_path}')
|
||||||
else:
|
else:
|
||||||
logger.warning('Graph export module is disable. Run Glances with --export graph to enable it.')
|
logger.warning('Graph export module is disable. Run Glances with --export graph to enable it.')
|
||||||
self.args.generate_graph = False
|
self.args.generate_graph = False
|
||||||
@ -731,7 +608,7 @@ class _GlancesCurses(object):
|
|||||||
:param process
|
:param process
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
logger.debug("Selected process to kill: {}".format(process))
|
logger.debug(f"Selected process to kill: {process}")
|
||||||
|
|
||||||
if 'childrens' in process:
|
if 'childrens' in process:
|
||||||
pid_to_kill = process['childrens']
|
pid_to_kill = process['childrens']
|
||||||
@ -751,9 +628,9 @@ class _GlancesCurses(object):
|
|||||||
try:
|
try:
|
||||||
ret_kill = glances_processes.kill(pid)
|
ret_kill = glances_processes.kill(pid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Can not kill process {} ({})'.format(pid, e))
|
logger.error(f'Can not kill process {pid} ({e})')
|
||||||
else:
|
else:
|
||||||
logger.info('Kill signal has been sent to process {} (return code: {})'.format(pid, ret_kill))
|
logger.info(f'Kill signal has been sent to process {pid} (return code: {ret_kill})')
|
||||||
|
|
||||||
def __display_header(self, stat_display):
|
def __display_header(self, stat_display):
|
||||||
"""Display the firsts lines (header) in the Curses interface.
|
"""Display the firsts lines (header) in the Curses interface.
|
||||||
@ -825,7 +702,7 @@ class _GlancesCurses(object):
|
|||||||
max_width=quicklook_width, args=self.args
|
max_width=quicklook_width, args=self.args
|
||||||
)
|
)
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.debug("Quicklook plugin not available (%s)" % e)
|
logger.debug(f"Quicklook plugin not available ({e})")
|
||||||
else:
|
else:
|
||||||
plugin_widths['quicklook'] = self.get_stats_display_width(stat_display["quicklook"])
|
plugin_widths['quicklook'] = self.get_stats_display_width(stat_display["quicklook"])
|
||||||
stats_width = sum(itervalues(plugin_widths)) + 1
|
stats_width = sum(itervalues(plugin_widths)) + 1
|
||||||
@ -920,12 +797,20 @@ class _GlancesCurses(object):
|
|||||||
self.display_plugin(stat_display[p])
|
self.display_plugin(stat_display[p])
|
||||||
|
|
||||||
def display_popup(
|
def display_popup(
|
||||||
self, message, size_x=None, size_y=None, duration=3, popup_type='info', input_size=30, input_value=None
|
self,
|
||||||
|
message,
|
||||||
|
size_x=None,
|
||||||
|
size_y=None,
|
||||||
|
duration=3,
|
||||||
|
popup_type='info',
|
||||||
|
input_size=30,
|
||||||
|
input_value=None,
|
||||||
|
is_password=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Display a centered popup.
|
Display a centered popup.
|
||||||
|
|
||||||
popup_type: ='info'
|
popup_type: ='info'
|
||||||
Just an information popup, no user interaction
|
Just an information popup, no user interaction
|
||||||
Display a centered popup with the given message during duration seconds
|
Display a centered popup with the given message during duration seconds
|
||||||
If size_x and size_y: set the popup size
|
If size_x and size_y: set the popup size
|
||||||
@ -977,7 +862,10 @@ class _GlancesCurses(object):
|
|||||||
popup.refresh()
|
popup.refresh()
|
||||||
self.wait(duration * 1000)
|
self.wait(duration * 1000)
|
||||||
return True
|
return True
|
||||||
elif popup_type == 'input':
|
|
||||||
|
if popup_type == 'input':
|
||||||
|
logger.info(popup_type)
|
||||||
|
logger.info(is_password)
|
||||||
# Create a sub-window for the text field
|
# Create a sub-window for the text field
|
||||||
sub_pop = popup.derwin(1, input_size, 2, 2 + len(m))
|
sub_pop = popup.derwin(1, input_size, 2, 2 + len(m))
|
||||||
sub_pop.attron(self.colors_list['FILTER'])
|
sub_pop.attron(self.colors_list['FILTER'])
|
||||||
@ -990,17 +878,22 @@ class _GlancesCurses(object):
|
|||||||
# Create the textbox inside the sub-windows
|
# Create the textbox inside the sub-windows
|
||||||
self.set_cursor(2)
|
self.set_cursor(2)
|
||||||
self.term_window.keypad(1)
|
self.term_window.keypad(1)
|
||||||
|
if is_password:
|
||||||
|
textbox = getpass.getpass('')
|
||||||
|
self.set_cursor(0)
|
||||||
|
if textbox != '':
|
||||||
|
return textbox
|
||||||
|
return None
|
||||||
|
|
||||||
|
# No password
|
||||||
textbox = GlancesTextbox(sub_pop, insert_mode=True)
|
textbox = GlancesTextbox(sub_pop, insert_mode=True)
|
||||||
textbox.edit()
|
textbox.edit()
|
||||||
self.set_cursor(0)
|
self.set_cursor(0)
|
||||||
# self.term_window.keypad(0)
|
|
||||||
if textbox.gather() != '':
|
if textbox.gather() != '':
|
||||||
logger.debug("User enters the following string: %s" % textbox.gather())
|
|
||||||
return textbox.gather()[:-1]
|
return textbox.gather()[:-1]
|
||||||
else:
|
return None
|
||||||
logger.debug("User enters an empty string")
|
|
||||||
return None
|
if popup_type == 'yesno':
|
||||||
elif popup_type == 'yesno':
|
|
||||||
# # Create a sub-window for the text field
|
# # Create a sub-window for the text field
|
||||||
sub_pop = popup.derwin(1, 2, len(sentence_list) + 1, len(m) + 2)
|
sub_pop = popup.derwin(1, 2, len(sentence_list) + 1, len(m) + 2)
|
||||||
sub_pop.attron(self.colors_list['FILTER'])
|
sub_pop.attron(self.colors_list['FILTER'])
|
||||||
@ -1018,6 +911,8 @@ class _GlancesCurses(object):
|
|||||||
# self.term_window.keypad(0)
|
# self.term_window.keypad(0)
|
||||||
return textbox.gather()
|
return textbox.gather()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def display_plugin(self, plugin_stats, display_optional=True, display_additional=True, max_y=65535, add_space=0):
|
def display_plugin(self, plugin_stats, display_optional=True, display_additional=True, max_y=65535, add_space=0):
|
||||||
"""Display the plugin_stats on the screen.
|
"""Display the plugin_stats on the screen.
|
||||||
|
|
||||||
@ -1112,6 +1007,7 @@ class _GlancesCurses(object):
|
|||||||
|
|
||||||
# Have empty lines after the plugins
|
# Have empty lines after the plugins
|
||||||
self.next_line += add_space
|
self.next_line += add_space
|
||||||
|
return None
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Erase the content of the screen.
|
"""Erase the content of the screen.
|
||||||
@ -1194,8 +1090,7 @@ class _GlancesCurses(object):
|
|||||||
if isexitkey and self.args.help_tag:
|
if isexitkey and self.args.help_tag:
|
||||||
# Quit from help should return to main screen, not exit #1874
|
# Quit from help should return to main screen, not exit #1874
|
||||||
self.args.help_tag = not self.args.help_tag
|
self.args.help_tag = not self.args.help_tag
|
||||||
isexitkey = False
|
return False
|
||||||
return isexitkey
|
|
||||||
|
|
||||||
if not isexitkey and pressedkey > -1:
|
if not isexitkey and pressedkey > -1:
|
||||||
# Redraw display
|
# Redraw display
|
||||||
@ -1236,7 +1131,7 @@ class _GlancesCurses(object):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug('ERROR: Can not compute plugin width ({})'.format(e))
|
logger.debug(f'ERROR: Can not compute plugin width ({e})')
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return c
|
return c
|
||||||
@ -1249,7 +1144,7 @@ class _GlancesCurses(object):
|
|||||||
try:
|
try:
|
||||||
c = [i['msg'] for i in curse_msg['msgdict']].count('\n')
|
c = [i['msg'] for i in curse_msg['msgdict']].count('\n')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug('ERROR: Can not compute plugin height ({})'.format(e))
|
logger.debug(f'ERROR: Can not compute plugin height ({e})')
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return c + 1
|
return c + 1
|
||||||
@ -1263,21 +1158,146 @@ class GlancesCursesClient(_GlancesCurses):
|
|||||||
"""Class for the Glances curse client."""
|
"""Class for the Glances curse client."""
|
||||||
|
|
||||||
|
|
||||||
class GlancesTextbox(Textbox, object):
|
class GlancesTextbox(Textbox):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(GlancesTextbox, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def do_command(self, ch):
|
def do_command(self, ch):
|
||||||
if ch == 10: # Enter
|
if ch == 10: # Enter
|
||||||
return 0
|
return 0
|
||||||
if ch == 127: # Back
|
if ch == 127: # Back
|
||||||
return 8
|
return 8
|
||||||
return super(GlancesTextbox, self).do_command(ch)
|
return super().do_command(ch)
|
||||||
|
|
||||||
|
|
||||||
class GlancesTextboxYesNo(Textbox, object):
|
class GlancesTextboxYesNo(Textbox):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(GlancesTextboxYesNo, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def do_command(self, ch):
|
def do_command(self, ch):
|
||||||
return super(GlancesTextboxYesNo, self).do_command(ch)
|
return super().do_command(ch)
|
||||||
|
|
||||||
|
|
||||||
|
def build_colors_list(args):
|
||||||
|
"""Init the Curses color layout."""
|
||||||
|
# Set curses options
|
||||||
|
try:
|
||||||
|
if hasattr(curses, 'start_color'):
|
||||||
|
curses.start_color()
|
||||||
|
logger.debug(f'Curses interface compatible with {curses.COLORS} colors')
|
||||||
|
if hasattr(curses, 'use_default_colors'):
|
||||||
|
curses.use_default_colors()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f'Error initializing terminal color ({e})')
|
||||||
|
|
||||||
|
# Init colors
|
||||||
|
if args.disable_bold:
|
||||||
|
A_BOLD = 0
|
||||||
|
args.disable_bg = True
|
||||||
|
else:
|
||||||
|
A_BOLD = curses.A_BOLD
|
||||||
|
|
||||||
|
title_color = A_BOLD
|
||||||
|
|
||||||
|
if curses.has_colors():
|
||||||
|
# The screen is compatible with a colored design
|
||||||
|
# ex: export TERM=xterm-256color
|
||||||
|
# export TERM=xterm-color
|
||||||
|
|
||||||
|
curses.init_pair(1, -1, -1)
|
||||||
|
if args.disable_bg:
|
||||||
|
curses.init_pair(2, curses.COLOR_RED, -1)
|
||||||
|
curses.init_pair(3, curses.COLOR_GREEN, -1)
|
||||||
|
curses.init_pair(5, curses.COLOR_MAGENTA, -1)
|
||||||
|
else:
|
||||||
|
curses.init_pair(2, -1, curses.COLOR_RED)
|
||||||
|
curses.init_pair(3, 0, curses.COLOR_GREEN)
|
||||||
|
curses.init_pair(5, -1, curses.COLOR_MAGENTA)
|
||||||
|
curses.init_pair(4, curses.COLOR_BLUE, -1)
|
||||||
|
curses.init_pair(6, curses.COLOR_RED, -1)
|
||||||
|
curses.init_pair(7, curses.COLOR_GREEN, -1)
|
||||||
|
curses.init_pair(8, curses.COLOR_MAGENTA, -1)
|
||||||
|
|
||||||
|
# Colors text styles
|
||||||
|
no_color = curses.color_pair(1)
|
||||||
|
default_color = curses.color_pair(3) | A_BOLD
|
||||||
|
nice_color = curses.color_pair(8)
|
||||||
|
cpu_time_color = curses.color_pair(8)
|
||||||
|
ifCAREFUL_color = curses.color_pair(4) | A_BOLD
|
||||||
|
ifWARNING_color = curses.color_pair(5) | A_BOLD
|
||||||
|
ifCRITICAL_color = curses.color_pair(2) | A_BOLD
|
||||||
|
default_color2 = curses.color_pair(7)
|
||||||
|
ifCAREFUL_color2 = curses.color_pair(4)
|
||||||
|
ifWARNING_color2 = curses.color_pair(8) | A_BOLD
|
||||||
|
ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD
|
||||||
|
ifINFO_color = curses.color_pair(4)
|
||||||
|
filter_color = A_BOLD
|
||||||
|
selected_color = A_BOLD
|
||||||
|
separator = curses.color_pair(1)
|
||||||
|
|
||||||
|
if curses.COLORS > 8:
|
||||||
|
# ex: export TERM=xterm-256color
|
||||||
|
colors_list = [curses.COLOR_CYAN, curses.COLOR_YELLOW]
|
||||||
|
for i in range(0, 3):
|
||||||
|
try:
|
||||||
|
curses.init_pair(i + 9, colors_list[i], -1)
|
||||||
|
except Exception:
|
||||||
|
curses.init_pair(i + 9, -1, -1)
|
||||||
|
filter_color = curses.color_pair(9) | A_BOLD
|
||||||
|
selected_color = curses.color_pair(10) | A_BOLD
|
||||||
|
# Define separator line style
|
||||||
|
try:
|
||||||
|
curses.init_color(11, 500, 500, 500)
|
||||||
|
curses.init_pair(11, curses.COLOR_BLACK, -1)
|
||||||
|
separator = curses.color_pair(11)
|
||||||
|
except Exception:
|
||||||
|
# Catch exception in TMUX
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# The screen is NOT compatible with a colored design
|
||||||
|
# switch to B&W text styles
|
||||||
|
# ex: export TERM=xterm-mono
|
||||||
|
no_color = -1
|
||||||
|
default_color = -1
|
||||||
|
nice_color = A_BOLD
|
||||||
|
cpu_time_color = A_BOLD
|
||||||
|
ifCAREFUL_color = A_BOLD
|
||||||
|
ifWARNING_color = curses.A_UNDERLINE
|
||||||
|
ifCRITICAL_color = curses.A_REVERSE
|
||||||
|
default_color2 = -1
|
||||||
|
ifCAREFUL_color2 = A_BOLD
|
||||||
|
ifWARNING_color2 = curses.A_UNDERLINE
|
||||||
|
ifCRITICAL_color2 = curses.A_REVERSE
|
||||||
|
ifINFO_color = A_BOLD
|
||||||
|
filter_color = A_BOLD
|
||||||
|
selected_color = A_BOLD
|
||||||
|
separator = -1
|
||||||
|
|
||||||
|
# Define the colors list (hash table) for stats
|
||||||
|
return {
|
||||||
|
'DEFAULT': no_color,
|
||||||
|
'UNDERLINE': curses.A_UNDERLINE,
|
||||||
|
'BOLD': A_BOLD,
|
||||||
|
'SORT': curses.A_UNDERLINE | A_BOLD,
|
||||||
|
'OK': default_color2,
|
||||||
|
'MAX': default_color2 | A_BOLD,
|
||||||
|
'FILTER': filter_color,
|
||||||
|
'TITLE': title_color,
|
||||||
|
'PROCESS': default_color2,
|
||||||
|
'PROCESS_SELECTED': default_color2 | curses.A_UNDERLINE,
|
||||||
|
'STATUS': default_color2,
|
||||||
|
'NICE': nice_color,
|
||||||
|
'CPU_TIME': cpu_time_color,
|
||||||
|
'CAREFUL': ifCAREFUL_color2,
|
||||||
|
'WARNING': ifWARNING_color2,
|
||||||
|
'CRITICAL': ifCRITICAL_color2,
|
||||||
|
'OK_LOG': default_color,
|
||||||
|
'CAREFUL_LOG': ifCAREFUL_color,
|
||||||
|
'WARNING_LOG': ifWARNING_color,
|
||||||
|
'CRITICAL_LOG': ifCRITICAL_color,
|
||||||
|
'PASSWORD': curses.A_PROTECT,
|
||||||
|
'SELECTED': selected_color,
|
||||||
|
'INFO': ifINFO_color,
|
||||||
|
'ERROR': selected_color,
|
||||||
|
'SEPARATOR': separator,
|
||||||
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,11 +8,11 @@
|
|||||||
|
|
||||||
"""Curses browser interface class ."""
|
"""Curses browser interface class ."""
|
||||||
|
|
||||||
import math
|
|
||||||
import curses
|
import curses
|
||||||
from glances.outputs.glances_curses import _GlancesCurses
|
import math
|
||||||
|
|
||||||
from glances.logger import logger
|
from glances.logger import logger
|
||||||
|
from glances.outputs.glances_curses import _GlancesCurses
|
||||||
from glances.timer import Timer
|
from glances.timer import Timer
|
||||||
|
|
||||||
|
|
||||||
@ -22,14 +21,14 @@ class GlancesCursesBrowser(_GlancesCurses):
|
|||||||
|
|
||||||
def __init__(self, args=None):
|
def __init__(self, args=None):
|
||||||
"""Init the father class."""
|
"""Init the father class."""
|
||||||
super(GlancesCursesBrowser, self).__init__(args=args)
|
super().__init__(args=args)
|
||||||
|
|
||||||
_colors_list = {
|
_colors_list = {
|
||||||
'UNKNOWN': self.no_color,
|
'UNKNOWN': self.colors_list['DEFAULT'],
|
||||||
'SNMP': self.default_color2,
|
'SNMP': self.colors_list['OK'],
|
||||||
'ONLINE': self.default_color2,
|
'ONLINE': self.colors_list['OK'],
|
||||||
'OFFLINE': self.ifCRITICAL_color2,
|
'OFFLINE': self.colors_list['CRITICAL'],
|
||||||
'PROTECTED': self.ifWARNING_color2,
|
'PROTECTED': self.colors_list['WARNING'],
|
||||||
}
|
}
|
||||||
self.colors_list.update(_colors_list)
|
self.colors_list.update(_colors_list)
|
||||||
|
|
||||||
@ -151,7 +150,7 @@ class GlancesCursesBrowser(_GlancesCurses):
|
|||||||
self.pressedkey = self.get_key(self.term_window)
|
self.pressedkey = self.get_key(self.term_window)
|
||||||
refresh = False
|
refresh = False
|
||||||
if self.pressedkey != -1:
|
if self.pressedkey != -1:
|
||||||
logger.debug("Key pressed. Code=%s" % self.pressedkey)
|
logger.debug(f"Key pressed. Code={self.pressedkey}")
|
||||||
|
|
||||||
# Actions...
|
# Actions...
|
||||||
if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
|
if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
|
||||||
@ -163,23 +162,23 @@ class GlancesCursesBrowser(_GlancesCurses):
|
|||||||
elif self.pressedkey == 10:
|
elif self.pressedkey == 10:
|
||||||
# 'ENTER' > Run Glances on the selected server
|
# 'ENTER' > Run Glances on the selected server
|
||||||
self.active_server = self._current_page * self._page_max_lines + self.cursor_position
|
self.active_server = self._current_page * self._page_max_lines + self.cursor_position
|
||||||
logger.debug("Server {}/{} selected".format(self.active_server, len(stats)))
|
logger.debug(f"Server {self.active_server}/{len(stats)} selected")
|
||||||
elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65:
|
elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65:
|
||||||
# 'UP' > Up in the server list
|
# 'UP' > Up in the server list
|
||||||
self.cursor_up(stats)
|
self.cursor_up(stats)
|
||||||
logger.debug("Server {}/{} selected".format(self.cursor + 1, len(stats)))
|
logger.debug(f"Server {self.cursor + 1}/{len(stats)} selected")
|
||||||
elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66:
|
elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66:
|
||||||
# 'DOWN' > Down in the server list
|
# 'DOWN' > Down in the server list
|
||||||
self.cursor_down(stats)
|
self.cursor_down(stats)
|
||||||
logger.debug("Server {}/{} selected".format(self.cursor + 1, len(stats)))
|
logger.debug(f"Server {self.cursor + 1}/{len(stats)} selected")
|
||||||
elif self.pressedkey == curses.KEY_PPAGE:
|
elif self.pressedkey == curses.KEY_PPAGE:
|
||||||
# 'Page UP' > Prev page in the server list
|
# 'Page UP' > Prev page in the server list
|
||||||
self.cursor_pageup(stats)
|
self.cursor_pageup(stats)
|
||||||
logger.debug("PageUP: Server ({}/{}) pages.".format(self._current_page + 1, self._page_max))
|
logger.debug(f"PageUP: Server ({self._current_page + 1}/{self._page_max}) pages.")
|
||||||
elif self.pressedkey == curses.KEY_NPAGE:
|
elif self.pressedkey == curses.KEY_NPAGE:
|
||||||
# 'Page Down' > Next page in the server list
|
# 'Page Down' > Next page in the server list
|
||||||
self.cursor_pagedown(stats)
|
self.cursor_pagedown(stats)
|
||||||
logger.debug("PageDown: Server {}/{} pages".format(self._current_page + 1, self._page_max))
|
logger.debug(f"PageDown: Server {self._current_page + 1}/{self._page_max} pages")
|
||||||
elif self.pressedkey == ord('1'):
|
elif self.pressedkey == ord('1'):
|
||||||
self._stats_list = None
|
self._stats_list = None
|
||||||
refresh = True
|
refresh = True
|
||||||
@ -211,7 +210,7 @@ class GlancesCursesBrowser(_GlancesCurses):
|
|||||||
:param return_to_browser:
|
:param return_to_browser:
|
||||||
"""
|
"""
|
||||||
# Flush display
|
# Flush display
|
||||||
logger.debug('Servers list: {}'.format(stats))
|
logger.debug(f'Servers list: {stats}')
|
||||||
self.flush(stats)
|
self.flush(stats)
|
||||||
|
|
||||||
# Wait
|
# Wait
|
||||||
@ -250,15 +249,19 @@ class GlancesCursesBrowser(_GlancesCurses):
|
|||||||
screen_x = self.screen.getmaxyx()[1]
|
screen_x = self.screen.getmaxyx()[1]
|
||||||
screen_y = self.screen.getmaxyx()[0]
|
screen_y = self.screen.getmaxyx()[0]
|
||||||
stats_max = screen_y - 3
|
stats_max = screen_y - 3
|
||||||
stats_len = len(stats)
|
|
||||||
|
|
||||||
self._page_max_lines = stats_max
|
self._page_max_lines = stats_max
|
||||||
self._page_max = int(math.ceil(stats_len / stats_max))
|
self._page_max = int(math.ceil(len(stats) / stats_max))
|
||||||
# Init position
|
|
||||||
x = 0
|
|
||||||
y = 0
|
|
||||||
|
|
||||||
# Display top header
|
# Display header
|
||||||
|
x, y = self.__display_header(stats, 0, 0, screen_x, screen_y)
|
||||||
|
|
||||||
|
# Display Glances server list
|
||||||
|
# ================================
|
||||||
|
return self.__display_server_list(stats, x, y, screen_x, screen_y)
|
||||||
|
|
||||||
|
def __display_header(self, stats, x, y, screen_x, screen_y):
|
||||||
|
stats_len = len(stats)
|
||||||
|
stats_max = screen_y - 3
|
||||||
if stats_len == 0:
|
if stats_len == 0:
|
||||||
if self.first_scan and not self.args.disable_autodiscover:
|
if self.first_scan and not self.args.disable_autodiscover:
|
||||||
msg = 'Glances is scanning your network. Please wait...'
|
msg = 'Glances is scanning your network. Please wait...'
|
||||||
@ -268,38 +271,39 @@ class GlancesCursesBrowser(_GlancesCurses):
|
|||||||
elif len(stats) == 1:
|
elif len(stats) == 1:
|
||||||
msg = 'One Glances server available'
|
msg = 'One Glances server available'
|
||||||
else:
|
else:
|
||||||
msg = '{} Glances servers available'.format(stats_len)
|
msg = f'{stats_len} Glances servers available'
|
||||||
if self.args.disable_autodiscover:
|
if self.args.disable_autodiscover:
|
||||||
msg += ' (auto discover is disabled)'
|
msg += ' (auto discover is disabled)'
|
||||||
if screen_y > 1:
|
if screen_y > 1:
|
||||||
self.term_window.addnstr(y, x, msg, screen_x - x, self.colors_list['TITLE'])
|
self.term_window.addnstr(y, x, msg, screen_x - x, self.colors_list['TITLE'])
|
||||||
|
|
||||||
msg = '{}'.format(self._get_status_count(stats))
|
msg = f'{self._get_status_count(stats)}'
|
||||||
self.term_window.addnstr(y + 1, x, msg, screen_x - x)
|
self.term_window.addnstr(y + 1, x, msg, screen_x - x)
|
||||||
|
|
||||||
if stats_len > stats_max and screen_y > 2:
|
if stats_len > stats_max and screen_y > 2:
|
||||||
msg = '{} servers displayed.({}/{}) {}'.format(
|
page_lines = self.get_pagelines(stats)
|
||||||
self.get_pagelines(stats), self._current_page + 1, self._page_max, self._get_status_count(stats)
|
status_count = self._get_status_count(stats)
|
||||||
)
|
msg = f'{page_lines} servers displayed.({self._current_page + 1}/{self._page_max}) {status_count}'
|
||||||
self.term_window.addnstr(y + 1, x, msg, screen_x - x)
|
self.term_window.addnstr(y + 1, x, msg, screen_x - x)
|
||||||
|
|
||||||
if stats_len == 0:
|
return x, y
|
||||||
|
|
||||||
|
def __display_server_list(self, stats, x, y, screen_x, screen_y):
|
||||||
|
if len(stats) == 0:
|
||||||
|
# No server to display
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Display the Glances server list
|
stats_max = screen_y - 3
|
||||||
# ================================
|
|
||||||
|
|
||||||
# Table of table
|
# Table of table
|
||||||
# Item description: [stats_id, column name, column size]
|
# Item description: [stats_id, column name, column size]
|
||||||
column_def = [
|
column_def = [
|
||||||
['name', 'Name', 16],
|
['name', 'Name', 16],
|
||||||
['alias', None, None],
|
|
||||||
['load_min5', 'LOAD', 6],
|
['load_min5', 'LOAD', 6],
|
||||||
['cpu_percent', 'CPU%', 5],
|
['cpu_percent', 'CPU%', 5],
|
||||||
['mem_percent', 'MEM%', 5],
|
['mem_percent', 'MEM%', 5],
|
||||||
['status', 'STATUS', 9],
|
['status', 'STATUS', 9],
|
||||||
['ip', 'IP', 15],
|
['ip', 'IP', 15],
|
||||||
# ['port', 'PORT', 5],
|
|
||||||
['hr_name', 'OS', 16],
|
['hr_name', 'OS', 16],
|
||||||
]
|
]
|
||||||
y = 2
|
y = 2
|
||||||
@ -325,24 +329,10 @@ class GlancesCursesBrowser(_GlancesCurses):
|
|||||||
|
|
||||||
# Display table
|
# Display table
|
||||||
line = 0
|
line = 0
|
||||||
for v in current_page:
|
for server_stat in current_page:
|
||||||
# Limit the number of displayed server (see issue #1256)
|
# Limit the number of displayed server (see issue #1256)
|
||||||
if line >= stats_max:
|
if line >= stats_max:
|
||||||
continue
|
continue
|
||||||
# Get server stats
|
|
||||||
server_stat = {}
|
|
||||||
for c in column_def:
|
|
||||||
try:
|
|
||||||
server_stat[c[0]] = v[c[0]]
|
|
||||||
except KeyError as e:
|
|
||||||
logger.debug("Cannot grab stats {} from server (KeyError: {})".format(c[0], e))
|
|
||||||
server_stat[c[0]] = '?'
|
|
||||||
# Display alias instead of name
|
|
||||||
try:
|
|
||||||
if c[0] == 'alias' and v[c[0]] is not None:
|
|
||||||
server_stat['name'] = v[c[0]]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Display line for server stats
|
# Display line for server stats
|
||||||
cpt = 0
|
cpt = 0
|
||||||
@ -356,9 +346,20 @@ class GlancesCursesBrowser(_GlancesCurses):
|
|||||||
# Display the line
|
# Display the line
|
||||||
xc += 2
|
xc += 2
|
||||||
for c in column_def:
|
for c in column_def:
|
||||||
if xc < screen_x and y < screen_y and c[1] is not None:
|
if xc < screen_x and y < screen_y:
|
||||||
# Display server stats
|
# Display server stats
|
||||||
self.term_window.addnstr(y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']])
|
value = format(server_stat.get(c[0], '?'))
|
||||||
|
if c[0] == 'name' and 'alias' in server_stat:
|
||||||
|
value = server_stat['alias']
|
||||||
|
decoration = self.colors_list.get(
|
||||||
|
server_stat[c[0] + '_decoration'].replace('_LOG', '')
|
||||||
|
if c[0] + '_decoration' in server_stat
|
||||||
|
else self.colors_list[server_stat['status']],
|
||||||
|
self.colors_list['DEFAULT'],
|
||||||
|
)
|
||||||
|
if c[0] == 'status':
|
||||||
|
decoration = self.colors_list[server_stat['status']]
|
||||||
|
self.term_window.addnstr(y, xc, value, c[2], decoration)
|
||||||
xc += c[2] + self.space_between_column
|
xc += c[2] + self.space_between_column
|
||||||
cpt += 1
|
cpt += 1
|
||||||
# Next line, next server...
|
# Next line, next server...
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -12,27 +11,30 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from io import open
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
# Replace typing_extensions by typing when Python 3.8 support will be dropped
|
try:
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
except ImportError:
|
||||||
|
# Only for Python 3.8
|
||||||
|
# To be removed when Python 3.8 support will be dropped
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
from glances import __version__, __apiversion__
|
from glances import __apiversion__, __version__
|
||||||
|
from glances.logger import logger
|
||||||
from glances.password import GlancesPassword
|
from glances.password import GlancesPassword
|
||||||
from glances.timer import Timer
|
from glances.timer import Timer
|
||||||
from glances.logger import logger
|
|
||||||
|
|
||||||
# FastAPI import
|
# FastAPI import
|
||||||
try:
|
try:
|
||||||
from fastapi import FastAPI, Depends, HTTPException, status, APIRouter, Request
|
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status
|
||||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.middleware.gzip import GZipMiddleware
|
from fastapi.middleware.gzip import GZipMiddleware
|
||||||
from fastapi.responses import HTMLResponse, ORJSONResponse
|
from fastapi.responses import HTMLResponse, ORJSONResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.critical('FastAPI import error. Glances cannot start in web server mode.')
|
logger.critical('FastAPI import error. Glances cannot start in web server mode.')
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
@ -42,6 +44,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
logger.critical('Uvicorn import error. Glances cannot start in web server mode.')
|
logger.critical('Uvicorn import error. Glances cannot start in web server mode.')
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
import builtins
|
||||||
import contextlib
|
import contextlib
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@ -73,7 +76,7 @@ class GlancesUvicornServer(uvicorn.Server):
|
|||||||
thread.join()
|
thread.join()
|
||||||
|
|
||||||
|
|
||||||
class GlancesRestfulApi(object):
|
class GlancesRestfulApi:
|
||||||
"""This class manages the Restful API server."""
|
"""This class manages the Restful API server."""
|
||||||
|
|
||||||
API_VERSION = __apiversion__
|
API_VERSION = __apiversion__
|
||||||
@ -98,7 +101,7 @@ class GlancesRestfulApi(object):
|
|||||||
self.load_config(config)
|
self.load_config(config)
|
||||||
|
|
||||||
# Set the bind URL
|
# Set the bind URL
|
||||||
self.bind_url = urljoin('http://{}:{}/'.format(self.args.bind_address, self.args.port), self.url_prefix)
|
self.bind_url = urljoin(f'http://{self.args.bind_address}:{self.args.port}/', self.url_prefix)
|
||||||
|
|
||||||
# FastAPI Init
|
# FastAPI Init
|
||||||
if self.args.password:
|
if self.args.password:
|
||||||
@ -127,11 +130,11 @@ class GlancesRestfulApi(object):
|
|||||||
# https://fastapi.tiangolo.com/tutorial/cors/
|
# https://fastapi.tiangolo.com/tutorial/cors/
|
||||||
self._app.add_middleware(
|
self._app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
# allow_origins=["*"],
|
# Related to https://github.com/nicolargo/glances/issues/2812
|
||||||
allow_origins=[self.bind_url],
|
allow_origins=config.get_list_value('outputs', 'cors_origins', default=["*"]),
|
||||||
allow_credentials=True,
|
allow_credentials=config.get_bool_value('outputs', 'cors_credentials', default=True),
|
||||||
allow_methods=["*"],
|
allow_methods=config.get_list_value('outputs', 'cors_methods', default=["*"]),
|
||||||
allow_headers=["*"],
|
allow_headers=config.get_list_value('outputs', 'cors_headers', default=["*"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
# FastAPI Enable GZIP compression
|
# FastAPI Enable GZIP compression
|
||||||
@ -147,9 +150,9 @@ class GlancesRestfulApi(object):
|
|||||||
self.url_prefix = '/'
|
self.url_prefix = '/'
|
||||||
if config is not None and config.has_section('outputs'):
|
if config is not None and config.has_section('outputs'):
|
||||||
n = config.get_value('outputs', 'max_processes_display', default=None)
|
n = config.get_value('outputs', 'max_processes_display', default=None)
|
||||||
logger.debug('Number of processes to display in the WebUI: {}'.format(n))
|
logger.debug(f'Number of processes to display in the WebUI: {n}')
|
||||||
self.url_prefix = config.get_value('outputs', 'url_prefix', default='/')
|
self.url_prefix = config.get_value('outputs', 'url_prefix', default='/')
|
||||||
logger.debug('URL prefix: {}'.format(self.url_prefix))
|
logger.debug(f'URL prefix: {self.url_prefix}')
|
||||||
|
|
||||||
def __update__(self):
|
def __update__(self):
|
||||||
# Never update more than 1 time per cached_time
|
# Never update more than 1 time per cached_time
|
||||||
@ -180,92 +183,93 @@ class GlancesRestfulApi(object):
|
|||||||
|
|
||||||
# REST API
|
# REST API
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/status' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/status',
|
||||||
status_code=status.HTTP_200_OK,
|
status_code=status.HTTP_200_OK,
|
||||||
|
methods=['HEAD', 'GET'],
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_status,
|
endpoint=self._api_status,
|
||||||
)
|
)
|
||||||
|
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/config' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_config
|
f'/api/{self.API_VERSION}/config', response_class=ORJSONResponse, endpoint=self._api_config
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/config/{section}' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/config/{{section}}',
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_config_section,
|
endpoint=self._api_config_section,
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/config/{section}/{item}' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/config/{{section}}/{{item}}',
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_config_section_item,
|
endpoint=self._api_config_section_item,
|
||||||
)
|
)
|
||||||
|
|
||||||
router.add_api_route('/api/%s/args' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_args)
|
router.add_api_route(f'/api/{self.API_VERSION}/args', response_class=ORJSONResponse, endpoint=self._api_args)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/args/{item}' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_args_item
|
f'/api/{self.API_VERSION}/args/{{item}}', response_class=ORJSONResponse, endpoint=self._api_args_item
|
||||||
)
|
)
|
||||||
|
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/pluginslist' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_plugins
|
f'/api/{self.API_VERSION}/pluginslist', response_class=ORJSONResponse, endpoint=self._api_plugins
|
||||||
)
|
)
|
||||||
router.add_api_route('/api/%s/all' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_all)
|
router.add_api_route(f'/api/{self.API_VERSION}/all', response_class=ORJSONResponse, endpoint=self._api_all)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/all/limits' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_all_limits
|
f'/api/{self.API_VERSION}/all/limits', response_class=ORJSONResponse, endpoint=self._api_all_limits
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/all/views' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_all_views
|
f'/api/{self.API_VERSION}/all/views', response_class=ORJSONResponse, endpoint=self._api_all_views
|
||||||
)
|
)
|
||||||
|
|
||||||
router.add_api_route('/api/%s/help' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_help)
|
router.add_api_route(f'/api/{self.API_VERSION}/help', response_class=ORJSONResponse, endpoint=self._api_help)
|
||||||
router.add_api_route('/api/%s/{plugin}' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api)
|
router.add_api_route(f'/api/{self.API_VERSION}/{{plugin}}', response_class=ORJSONResponse, endpoint=self._api)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/history' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_history
|
f'/api/{self.API_VERSION}/{{plugin}}/history', response_class=ORJSONResponse, endpoint=self._api_history
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/history/{nb}' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/{{plugin}}/history/{{nb}}',
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_history,
|
endpoint=self._api_history,
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/top/{nb}' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_top
|
f'/api/{self.API_VERSION}/{{plugin}}/top/{{nb}}', response_class=ORJSONResponse, endpoint=self._api_top
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/limits' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_limits
|
f'/api/{self.API_VERSION}/{{plugin}}/limits', response_class=ORJSONResponse, endpoint=self._api_limits
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/views' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_views
|
f'/api/{self.API_VERSION}/{{plugin}}/views', response_class=ORJSONResponse, endpoint=self._api_views
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/{item}' % self.API_VERSION, response_class=ORJSONResponse, endpoint=self._api_item
|
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}', response_class=ORJSONResponse, endpoint=self._api_item
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/{item}/history' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/history',
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_item_history,
|
endpoint=self._api_item_history,
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/{item}/history/{nb}' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/history/{{nb}}',
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_item_history,
|
endpoint=self._api_item_history,
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/{item}/description' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/description',
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_item_description,
|
endpoint=self._api_item_description,
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/{item}/unit' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/unit',
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_item_unit,
|
endpoint=self._api_item_unit,
|
||||||
)
|
)
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
'/api/%s/{plugin}/{item}/{value}' % self.API_VERSION,
|
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/{{value:path}}',
|
||||||
response_class=ORJSONResponse,
|
response_class=ORJSONResponse,
|
||||||
endpoint=self._api_value,
|
endpoint=self._api_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restful API
|
# Restful API
|
||||||
bindmsg = 'Glances RESTful API Server started on {}api/{}'.format(self.bind_url, self.API_VERSION)
|
bindmsg = f'Glances RESTful API Server started on {self.bind_url}api/{self.API_VERSION}'
|
||||||
logger.info(bindmsg)
|
logger.info(bindmsg)
|
||||||
|
|
||||||
# WEB UI
|
# WEB UI
|
||||||
@ -276,9 +280,9 @@ class GlancesRestfulApi(object):
|
|||||||
# Statics files
|
# Statics files
|
||||||
self._app.mount("/static", StaticFiles(directory=self.STATIC_PATH), name="static")
|
self._app.mount("/static", StaticFiles(directory=self.STATIC_PATH), name="static")
|
||||||
|
|
||||||
logger.info("Get WebUI in {}".format(self.STATIC_PATH))
|
logger.info(f"Get WebUI in {self.STATIC_PATH}")
|
||||||
|
|
||||||
bindmsg = 'Glances Web User Interface started on {}'.format(self.bind_url)
|
bindmsg = f'Glances Web User Interface started on {self.bind_url}'
|
||||||
else:
|
else:
|
||||||
bindmsg = 'The WebUI is disable (--disable-webui)'
|
bindmsg = 'The WebUI is disable (--disable-webui)'
|
||||||
|
|
||||||
@ -313,7 +317,7 @@ class GlancesRestfulApi(object):
|
|||||||
try:
|
try:
|
||||||
self.uvicorn_server = GlancesUvicornServer(config=uvicorn_config)
|
self.uvicorn_server = GlancesUvicornServer(config=uvicorn_config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical('Error: Can not ran Glances Web server ({})'.format(e))
|
logger.critical(f'Error: Can not ran Glances Web server ({e})')
|
||||||
self.uvicorn_server = None
|
self.uvicorn_server = None
|
||||||
else:
|
else:
|
||||||
with self.uvicorn_server.run_in_thread():
|
with self.uvicorn_server.run_in_thread():
|
||||||
@ -365,7 +369,7 @@ class GlancesRestfulApi(object):
|
|||||||
try:
|
try:
|
||||||
plist = self.stats.get_plugin("help").get_view_data()
|
plist = self.stats.get_plugin("help").get_view_data()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get help view data (%s)" % str(e))
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get help view data ({str(e)})")
|
||||||
|
|
||||||
return ORJSONResponse(plist)
|
return ORJSONResponse(plist)
|
||||||
|
|
||||||
@ -401,7 +405,7 @@ class GlancesRestfulApi(object):
|
|||||||
try:
|
try:
|
||||||
plist = self.plugins_list
|
plist = self.plugins_list
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get plugin list (%s)" % str(e))
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin list ({str(e)})")
|
||||||
|
|
||||||
return ORJSONResponse(plist)
|
return ORJSONResponse(plist)
|
||||||
|
|
||||||
@ -416,10 +420,10 @@ class GlancesRestfulApi(object):
|
|||||||
if self.args.debug:
|
if self.args.debug:
|
||||||
fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json')
|
fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json')
|
||||||
try:
|
try:
|
||||||
with open(fname) as f:
|
with builtins.open(fname) as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
except IOError:
|
except OSError:
|
||||||
logger.debug("Debug file (%s) not found" % fname)
|
logger.debug(f"Debug file ({fname}) not found")
|
||||||
|
|
||||||
# Update the stat
|
# Update the stat
|
||||||
self.__update__()
|
self.__update__()
|
||||||
@ -428,7 +432,7 @@ class GlancesRestfulApi(object):
|
|||||||
# Get the RAW value of the stat ID
|
# Get the RAW value of the stat ID
|
||||||
statval = self.stats.getAllAsDict()
|
statval = self.stats.getAllAsDict()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get stats (%s)" % str(e))
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get stats ({str(e)})")
|
||||||
|
|
||||||
return ORJSONResponse(statval)
|
return ORJSONResponse(statval)
|
||||||
|
|
||||||
@ -444,7 +448,7 @@ class GlancesRestfulApi(object):
|
|||||||
# Get the RAW value of the stat limits
|
# Get the RAW value of the stat limits
|
||||||
limits = self.stats.getAllLimitsAsDict()
|
limits = self.stats.getAllLimitsAsDict()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get limits (%s)" % str(e))
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get limits ({str(e)})")
|
||||||
|
|
||||||
return ORJSONResponse(limits)
|
return ORJSONResponse(limits)
|
||||||
|
|
||||||
@ -460,7 +464,7 @@ class GlancesRestfulApi(object):
|
|||||||
# Get the RAW value of the stat view
|
# Get the RAW value of the stat view
|
||||||
limits = self.stats.getAllViewsAsDict()
|
limits = self.stats.getAllViewsAsDict()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get views (%s)" % str(e))
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get views ({str(e)})")
|
||||||
|
|
||||||
return ORJSONResponse(limits)
|
return ORJSONResponse(limits)
|
||||||
|
|
||||||
@ -475,7 +479,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the stat
|
# Update the stat
|
||||||
@ -485,9 +489,7 @@ class GlancesRestfulApi(object):
|
|||||||
# Get the RAW value of the stat ID
|
# Get the RAW value of the stat ID
|
||||||
statval = self.stats.get_plugin(plugin).get_raw()
|
statval = self.stats.get_plugin(plugin).get_raw()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin {plugin} ({str(e)})")
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get plugin %s (%s)" % (plugin, str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
return ORJSONResponse(statval)
|
return ORJSONResponse(statval)
|
||||||
|
|
||||||
@ -504,7 +506,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the stat
|
# Update the stat
|
||||||
@ -514,9 +516,7 @@ class GlancesRestfulApi(object):
|
|||||||
# Get the RAW value of the stat ID
|
# Get the RAW value of the stat ID
|
||||||
statval = self.stats.get_plugin(plugin).get_raw()
|
statval = self.stats.get_plugin(plugin).get_raw()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin {plugin} ({str(e)})")
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get plugin %s (%s)" % (plugin, str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
print(statval)
|
print(statval)
|
||||||
|
|
||||||
@ -537,7 +537,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the stat
|
# Update the stat
|
||||||
@ -548,7 +548,7 @@ class GlancesRestfulApi(object):
|
|||||||
statval = self.stats.get_plugin(plugin).get_raw_history(nb=int(nb))
|
statval = self.stats.get_plugin(plugin).get_raw_history(nb=int(nb))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get plugin history %s (%s)" % (plugin, str(e))
|
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin history {plugin} ({str(e)})"
|
||||||
)
|
)
|
||||||
|
|
||||||
return statval
|
return statval
|
||||||
@ -564,7 +564,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -572,7 +572,7 @@ class GlancesRestfulApi(object):
|
|||||||
ret = self.stats.get_plugin(plugin).limits
|
ret = self.stats.get_plugin(plugin).limits
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get limits for plugin %s (%s)" % (plugin, str(e))
|
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get limits for plugin {plugin} ({str(e)})"
|
||||||
)
|
)
|
||||||
|
|
||||||
return ORJSONResponse(ret)
|
return ORJSONResponse(ret)
|
||||||
@ -588,7 +588,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -596,7 +596,7 @@ class GlancesRestfulApi(object):
|
|||||||
ret = self.stats.get_plugin(plugin).get_views()
|
ret = self.stats.get_plugin(plugin).get_views()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get views for plugin %s (%s)" % (plugin, str(e))
|
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get views for plugin {plugin} ({str(e)})"
|
||||||
)
|
)
|
||||||
|
|
||||||
return ORJSONResponse(ret)
|
return ORJSONResponse(ret)
|
||||||
@ -612,7 +612,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the stat
|
# Update the stat
|
||||||
@ -624,7 +624,7 @@ class GlancesRestfulApi(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="Cannot get item %s in plugin %s (%s)" % (item, plugin, str(e)),
|
detail=f"Cannot get item {item} in plugin {plugin} ({str(e)})",
|
||||||
)
|
)
|
||||||
|
|
||||||
return ORJSONResponse(ret)
|
return ORJSONResponse(ret)
|
||||||
@ -641,7 +641,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the stat
|
# Update the stat
|
||||||
@ -652,7 +652,7 @@ class GlancesRestfulApi(object):
|
|||||||
ret = self.stats.get_plugin(plugin).get_raw_history(item, nb=nb)
|
ret = self.stats.get_plugin(plugin).get_raw_history(item, nb=nb)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get history for plugin %s (%s)" % (plugin, str(e))
|
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get history for plugin {plugin} ({str(e)})"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return ORJSONResponse(ret)
|
return ORJSONResponse(ret)
|
||||||
@ -668,7 +668,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -677,7 +677,7 @@ class GlancesRestfulApi(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="Cannot get %s description for plugin %s (%s)" % (item, plugin, str(e)),
|
detail=f"Cannot get {item} description for plugin {plugin} ({str(e)})",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return ORJSONResponse(ret)
|
return ORJSONResponse(ret)
|
||||||
@ -693,7 +693,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -702,7 +702,7 @@ class GlancesRestfulApi(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="Cannot get %s unit for plugin %s (%s)" % (item, plugin, str(e)),
|
detail=f"Cannot get {item} unit for plugin {plugin} ({str(e)})",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return ORJSONResponse(ret)
|
return ORJSONResponse(ret)
|
||||||
@ -718,7 +718,7 @@ class GlancesRestfulApi(object):
|
|||||||
if plugin not in self.plugins_list:
|
if plugin not in self.plugins_list:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list),
|
detail=f"Unknown plugin {plugin} (available plugins: {self.plugins_list})",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the stat
|
# Update the stat
|
||||||
@ -730,7 +730,7 @@ class GlancesRestfulApi(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="Cannot get %s = %s for plugin %s (%s)" % (item, value, plugin, str(e)),
|
detail=f"Cannot get {item} = {value} for plugin {plugin} ({str(e)})",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return ORJSONResponse(ret)
|
return ORJSONResponse(ret)
|
||||||
@ -746,7 +746,7 @@ class GlancesRestfulApi(object):
|
|||||||
# Get the RAW value of the config' dict
|
# Get the RAW value of the config' dict
|
||||||
args_json = self.config.as_dict()
|
args_json = self.config.as_dict()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get config (%s)" % str(e))
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config ({str(e)})")
|
||||||
else:
|
else:
|
||||||
return ORJSONResponse(args_json)
|
return ORJSONResponse(args_json)
|
||||||
|
|
||||||
@ -760,16 +760,14 @@ class GlancesRestfulApi(object):
|
|||||||
"""
|
"""
|
||||||
config_dict = self.config.as_dict()
|
config_dict = self.config.as_dict()
|
||||||
if section not in config_dict:
|
if section not in config_dict:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Unknown configuration item {section}")
|
||||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Unknown configuration item %s" % section
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the RAW value of the config' dict
|
# Get the RAW value of the config' dict
|
||||||
ret_section = config_dict[section]
|
ret_section = config_dict[section]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get config section %s (%s)" % (section, str(e))
|
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config section {section} ({str(e)})"
|
||||||
)
|
)
|
||||||
|
|
||||||
return ORJSONResponse(ret_section)
|
return ORJSONResponse(ret_section)
|
||||||
@ -784,16 +782,14 @@ class GlancesRestfulApi(object):
|
|||||||
"""
|
"""
|
||||||
config_dict = self.config.as_dict()
|
config_dict = self.config.as_dict()
|
||||||
if section not in config_dict:
|
if section not in config_dict:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Unknown configuration item {section}")
|
||||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Unknown configuration item %s" % section
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the RAW value of the config' dict section
|
# Get the RAW value of the config' dict section
|
||||||
ret_section = config_dict[section]
|
ret_section = config_dict[section]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get config section %s (%s)" % (section, str(e))
|
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config section {section} ({str(e)})"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -802,7 +798,7 @@ class GlancesRestfulApi(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="Cannot get item %s in config section %s (%s)" % (item, section, str(e)),
|
detail=f"Cannot get item {item} in config section {section} ({str(e)})",
|
||||||
)
|
)
|
||||||
|
|
||||||
return ORJSONResponse(ret_item)
|
return ORJSONResponse(ret_item)
|
||||||
@ -820,7 +816,7 @@ class GlancesRestfulApi(object):
|
|||||||
# Source: https://docs.python.org/%s/library/functions.html#vars
|
# Source: https://docs.python.org/%s/library/functions.html#vars
|
||||||
args_json = vars(self.args)
|
args_json = vars(self.args)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get args (%s)" % str(e))
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get args ({str(e)})")
|
||||||
|
|
||||||
return ORJSONResponse(args_json)
|
return ORJSONResponse(args_json)
|
||||||
|
|
||||||
@ -833,7 +829,7 @@ class GlancesRestfulApi(object):
|
|||||||
HTTP/404 if others error
|
HTTP/404 if others error
|
||||||
"""
|
"""
|
||||||
if item not in self.args:
|
if item not in self.args:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Unknown argument item %s" % item)
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Unknown argument item {item}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the RAW value of the args' dict
|
# Get the RAW value of the args' dict
|
||||||
@ -841,6 +837,6 @@ class GlancesRestfulApi(object):
|
|||||||
# Source: https://docs.python.org/%s/library/functions.html#vars
|
# Source: https://docs.python.org/%s/library/functions.html#vars
|
||||||
args_json = vars(self.args)[item]
|
args_json = vars(self.args)[item]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cannot get args item (%s)" % str(e))
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get args item ({str(e)})")
|
||||||
|
|
||||||
return ORJSONResponse(args_json)
|
return ORJSONResponse(args_json)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,28 +8,27 @@
|
|||||||
|
|
||||||
"""Manage sparklines for Glances output."""
|
"""Manage sparklines for Glances output."""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
from __future__ import division
|
|
||||||
import sys
|
import sys
|
||||||
from glances.logger import logger
|
|
||||||
from glances.globals import nativestr
|
from glances.globals import nativestr
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
sparklines_module = True
|
sparklines_module = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from sparklines import sparklines
|
from sparklines import sparklines
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.warning("Sparklines module not found ({})".format(e))
|
logger.warning(f"Sparklines module not found ({e})")
|
||||||
sparklines_module = False
|
sparklines_module = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
'┌┬┐╔╦╗╒╤╕╓╥╖│║─═├┼┤╠╬╣╞╪╡╟╫╢└┴┘╚╩╝╘╧╛╙╨╜'.encode(sys.stdout.encoding)
|
'┌┬┐╔╦╗╒╤╕╓╥╖│║─═├┼┤╠╬╣╞╪╡╟╫╢└┴┘╚╩╝╘╧╛╙╨╜'.encode(sys.stdout.encoding)
|
||||||
except (UnicodeEncodeError, TypeError) as e:
|
except (UnicodeEncodeError, TypeError) as e:
|
||||||
logger.warning("UTF-8 is mandatory for sparklines ({})".format(e))
|
logger.warning(f"UTF-8 is mandatory for sparklines ({e})")
|
||||||
sparklines_module = False
|
sparklines_module = False
|
||||||
|
|
||||||
|
|
||||||
class Sparkline(object):
|
class Sparkline:
|
||||||
"""Manage sparklines (see https://pypi.org/project/sparklines/)."""
|
"""Manage sparklines (see https://pypi.org/project/sparklines/)."""
|
||||||
|
|
||||||
def __init__(self, size, pre_char='[', post_char=']', unit_char='%', display_value=True):
|
def __init__(self, size, pre_char='[', post_char=']', unit_char='%', display_value=True):
|
||||||
@ -58,6 +56,7 @@ class Sparkline(object):
|
|||||||
return self.__size
|
return self.__size
|
||||||
if self.__display_value:
|
if self.__display_value:
|
||||||
return self.__size - 6
|
return self.__size - 6
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def percents(self):
|
def percents(self):
|
||||||
@ -81,7 +80,7 @@ class Sparkline(object):
|
|||||||
if self.__display_value:
|
if self.__display_value:
|
||||||
percents_without_none = [x for x in self.percents if x is not None]
|
percents_without_none = [x for x in self.percents if x is not None]
|
||||||
if len(percents_without_none) > 0:
|
if len(percents_without_none) > 0:
|
||||||
ret = '{}{:5.1f}{}'.format(ret, percents_without_none[-1], self.__unit_char)
|
ret = f'{ret}{percents_without_none[-1]:5.1f}{self.__unit_char}'
|
||||||
ret = nativestr(ret)
|
ret = nativestr(ret)
|
||||||
if overwrite and len(overwrite) < len(ret) - 6:
|
if overwrite and len(overwrite) < len(ret) - 6:
|
||||||
ret = overwrite + ret[len(overwrite) :]
|
ret = overwrite + ret[len(overwrite) :]
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -11,11 +10,11 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from glances.logger import logger
|
|
||||||
from glances.globals import printandflush
|
from glances.globals import printandflush
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class GlancesStdout(object):
|
class GlancesStdout:
|
||||||
"""This class manages the Stdout display."""
|
"""This class manages the Stdout display."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
@ -65,9 +64,9 @@ class GlancesStdout(object):
|
|||||||
# With attribute
|
# With attribute
|
||||||
if isinstance(stat, dict):
|
if isinstance(stat, dict):
|
||||||
try:
|
try:
|
||||||
printandflush("{}.{}: {}".format(plugin, attribute, stat[attribute]))
|
printandflush(f"{plugin}.{attribute}: {stat[attribute]}")
|
||||||
except KeyError as err:
|
except KeyError as err:
|
||||||
logger.error("Can not display stat {}.{} ({})".format(plugin, attribute, err))
|
logger.error(f"Can not display stat {plugin}.{attribute} ({err})")
|
||||||
elif isinstance(stat, list):
|
elif isinstance(stat, list):
|
||||||
for i in stat:
|
for i in stat:
|
||||||
if key is None:
|
if key is None:
|
||||||
@ -77,12 +76,12 @@ class GlancesStdout(object):
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
printandflush("{}.{}.{}: {}".format(plugin, i_key, attribute, i[attribute]))
|
printandflush(f"{plugin}.{i_key}.{attribute}: {i[attribute]}")
|
||||||
except KeyError as err:
|
except KeyError as err:
|
||||||
logger.error("Can not display stat {}.{} ({})".format(plugin, attribute, err))
|
logger.error(f"Can not display stat {plugin}.{attribute} ({err})")
|
||||||
else:
|
else:
|
||||||
# Without attribute
|
# Without attribute
|
||||||
printandflush("{}: {}".format(plugin, stat))
|
printandflush(f"{plugin}: {stat}")
|
||||||
|
|
||||||
# Wait until next refresh
|
# Wait until next refresh
|
||||||
if duration > 0:
|
if duration > 0:
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -9,24 +8,23 @@
|
|||||||
|
|
||||||
"""Fields description interface class."""
|
"""Fields description interface class."""
|
||||||
|
|
||||||
from pprint import pformat
|
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
from glances import __apiversion__
|
from glances import __apiversion__
|
||||||
from glances.logger import logger
|
|
||||||
from glances.globals import iteritems
|
from glances.globals import iteritems
|
||||||
|
from glances.logger import logger
|
||||||
|
|
||||||
|
API_URL = f"http://localhost:61208/api/{__apiversion__}"
|
||||||
|
|
||||||
API_URL = "http://localhost:61208/api/{api_version}".format(api_version=__apiversion__)
|
APIDOC_HEADER = f"""\
|
||||||
|
|
||||||
APIDOC_HEADER = """\
|
|
||||||
.. _api:
|
.. _api:
|
||||||
|
|
||||||
API (Restfull/JSON) documentation
|
API (Restfull/JSON) documentation
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
This documentation describes the Glances API version {api_version} (Restfull/JSON) interface.
|
This documentation describes the Glances API version {__apiversion__} (Restfull/JSON) interface.
|
||||||
|
|
||||||
For Glances version 3, please have a look on:
|
For Glances version 3, please have a look on:
|
||||||
``https://github.com/nicolargo/glances/blob/support/glancesv3/docs/api.rst``
|
``https://github.com/nicolargo/glances/blob/support/glancesv3/docs/api.rst``
|
||||||
@ -45,7 +43,7 @@ It is also ran automatically when Glances is started in Web server mode (-w).
|
|||||||
API URL
|
API URL
|
||||||
-------
|
-------
|
||||||
|
|
||||||
The default root API URL is ``http://localhost:61208/api/{api_version}``.
|
The default root API URL is ``http://localhost:61208/api/{__apiversion__}``.
|
||||||
|
|
||||||
The bind address and port could be changed using the ``--bind`` and ``--port`` command line options.
|
The bind address and port could be changed using the ``--bind`` and ``--port`` command line options.
|
||||||
|
|
||||||
@ -60,7 +58,7 @@ For example:
|
|||||||
[outputs]
|
[outputs]
|
||||||
url_prefix = /glances/
|
url_prefix = /glances/
|
||||||
|
|
||||||
will change the root API URL to ``http://localhost:61208/glances/api/{api_version}`` and the Web UI URL to
|
will change the root API URL to ``http://localhost:61208/glances/api/{__apiversion__}`` and the Web UI URL to
|
||||||
``http://localhost:61208/glances/``
|
``http://localhost:61208/glances/``
|
||||||
|
|
||||||
API documentation URL
|
API documentation URL
|
||||||
@ -75,9 +73,7 @@ WebUI refresh
|
|||||||
It is possible to change the Web UI refresh rate (default is 2 seconds) using the following option in the URL:
|
It is possible to change the Web UI refresh rate (default is 2 seconds) using the following option in the URL:
|
||||||
``http://localhost:61208/glances/?refresh=5``
|
``http://localhost:61208/glances/?refresh=5``
|
||||||
|
|
||||||
""".format(
|
"""
|
||||||
api_version=__apiversion__
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def indent_stat(stat, indent=' '):
|
def indent_stat(stat, indent=' '):
|
||||||
@ -85,8 +81,7 @@ def indent_stat(stat, indent=' '):
|
|||||||
if isinstance(stat, list) and len(stat) > 1 and isinstance(stat[0], dict):
|
if isinstance(stat, list) and len(stat) > 1 and isinstance(stat[0], dict):
|
||||||
# Only display two first items
|
# Only display two first items
|
||||||
return indent + pformat(stat[0:2]).replace('\n', '\n' + indent).replace("'", '"')
|
return indent + pformat(stat[0:2]).replace('\n', '\n' + indent).replace("'", '"')
|
||||||
else:
|
return indent + pformat(stat).replace('\n', '\n' + indent).replace("'", '"')
|
||||||
return indent + pformat(stat).replace('\n', '\n' + indent).replace("'", '"')
|
|
||||||
|
|
||||||
|
|
||||||
def print_api_status():
|
def print_api_status():
|
||||||
@ -99,7 +94,7 @@ def print_api_status():
|
|||||||
print('')
|
print('')
|
||||||
print('Get the Rest API status::')
|
print('Get the Rest API status::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl -I {}/status'.format(API_URL))
|
print(f' # curl -I {API_URL}/status')
|
||||||
print(indent_stat('HTTP/1.0 200 OK'))
|
print(indent_stat('HTTP/1.0 200 OK'))
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
@ -111,20 +106,20 @@ def print_plugins_list(stat):
|
|||||||
print('')
|
print('')
|
||||||
print('Get the plugins list::')
|
print('Get the plugins list::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/pluginslist'.format(API_URL))
|
print(f' # curl {API_URL}/pluginslist')
|
||||||
print(indent_stat(stat))
|
print(indent_stat(stat))
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
|
|
||||||
def print_plugin_stats(plugin, stat):
|
def print_plugin_stats(plugin, stat):
|
||||||
sub_title = 'GET {}'.format(plugin)
|
sub_title = f'GET {plugin}'
|
||||||
print(sub_title)
|
print(sub_title)
|
||||||
print('-' * len(sub_title))
|
print('-' * len(sub_title))
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
print('Get plugin stats::')
|
print('Get plugin stats::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/{}'.format(API_URL, plugin))
|
print(f' # curl {API_URL}/{plugin}')
|
||||||
print(indent_stat(json.loads(stat.get_stats())))
|
print(indent_stat(json.loads(stat.get_stats())))
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
@ -183,7 +178,7 @@ def print_plugin_description(plugin, stat):
|
|||||||
|
|
||||||
print('')
|
print('')
|
||||||
else:
|
else:
|
||||||
logger.error('No fields_description variable defined for plugin {}'.format(plugin))
|
logger.error(f'No fields_description variable defined for plugin {plugin}')
|
||||||
|
|
||||||
|
|
||||||
def print_plugin_item_value(plugin, stat, stat_export):
|
def print_plugin_item_value(plugin, stat, stat_export):
|
||||||
@ -205,13 +200,13 @@ def print_plugin_item_value(plugin, stat, stat_export):
|
|||||||
value = stat_item[item]
|
value = stat_item[item]
|
||||||
print('Get a specific field::')
|
print('Get a specific field::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/{}/{}'.format(API_URL, plugin, item))
|
print(f' # curl {API_URL}/{plugin}/{item}')
|
||||||
print(indent_stat(stat_item))
|
print(indent_stat(stat_item))
|
||||||
print('')
|
print('')
|
||||||
if item and value and stat.get_stats_value(item, value):
|
if item and value and stat.get_stats_value(item, value):
|
||||||
print('Get a specific item when field matches the given value::')
|
print('Get a specific item when field matches the given value::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/{}/{}/{}'.format(API_URL, plugin, item, value))
|
print(f' # curl {API_URL}/{plugin}/{item}/{value}')
|
||||||
print(indent_stat(json.loads(stat.get_stats_value(item, value))))
|
print(indent_stat(json.loads(stat.get_stats_value(item, value))))
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
@ -223,7 +218,7 @@ def print_all():
|
|||||||
print('')
|
print('')
|
||||||
print('Get all Glances stats::')
|
print('Get all Glances stats::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/all'.format(API_URL))
|
print(f' # curl {API_URL}/all')
|
||||||
print(' Return a very big dictionary (avoid using this request, performances will be poor)...')
|
print(' Return a very big dictionary (avoid using this request, performances will be poor)...')
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
@ -237,7 +232,7 @@ def print_top(stats):
|
|||||||
print('')
|
print('')
|
||||||
print('Get top 2 processes of the processlist plugin::')
|
print('Get top 2 processes of the processlist plugin::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/processlist/top/2'.format(API_URL))
|
print(f' # curl {API_URL}/processlist/top/2')
|
||||||
print(indent_stat(stats.get_plugin('processlist').get_export()[:2]))
|
print(indent_stat(stats.get_plugin('processlist').get_export()[:2]))
|
||||||
print('')
|
print('')
|
||||||
print('Note: Only work for plugin with a list of items')
|
print('Note: Only work for plugin with a list of items')
|
||||||
@ -250,7 +245,7 @@ def print_fields_info(stats):
|
|||||||
print('-' * len(sub_title))
|
print('-' * len(sub_title))
|
||||||
print('Get item description (human readable) for a specific plugin/item::')
|
print('Get item description (human readable) for a specific plugin/item::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/diskio/read_bytes/description'.format(API_URL))
|
print(f' # curl {API_URL}/diskio/read_bytes/description')
|
||||||
print(indent_stat(stats.get_plugin('diskio').get_item_info('read_bytes', 'description')))
|
print(indent_stat(stats.get_plugin('diskio').get_item_info('read_bytes', 'description')))
|
||||||
print('')
|
print('')
|
||||||
print('Note: the description is defined in the fields_description variable of the plugin.')
|
print('Note: the description is defined in the fields_description variable of the plugin.')
|
||||||
@ -260,7 +255,7 @@ def print_fields_info(stats):
|
|||||||
print('-' * len(sub_title))
|
print('-' * len(sub_title))
|
||||||
print('Get item unit for a specific plugin/item::')
|
print('Get item unit for a specific plugin/item::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/diskio/read_bytes/unit'.format(API_URL))
|
print(f' # curl {API_URL}/diskio/read_bytes/unit')
|
||||||
print(indent_stat(stats.get_plugin('diskio').get_item_info('read_bytes', 'unit')))
|
print(indent_stat(stats.get_plugin('diskio').get_item_info('read_bytes', 'unit')))
|
||||||
print('')
|
print('')
|
||||||
print('Note: the description is defined in the fields_description variable of the plugin.')
|
print('Note: the description is defined in the fields_description variable of the plugin.')
|
||||||
@ -278,22 +273,22 @@ def print_history(stats):
|
|||||||
print('')
|
print('')
|
||||||
print('History of a plugin::')
|
print('History of a plugin::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/cpu/history'.format(API_URL))
|
print(f' # curl {API_URL}/cpu/history')
|
||||||
print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=3))))
|
print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=3))))
|
||||||
print('')
|
print('')
|
||||||
print('Limit history to last 2 values::')
|
print('Limit history to last 2 values::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/cpu/history/2'.format(API_URL))
|
print(f' # curl {API_URL}/cpu/history/2')
|
||||||
print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=2))))
|
print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=2))))
|
||||||
print('')
|
print('')
|
||||||
print('History for a specific field::')
|
print('History for a specific field::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/cpu/system/history'.format(API_URL))
|
print(f' # curl {API_URL}/cpu/system/history')
|
||||||
print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system'))))
|
print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system'))))
|
||||||
print('')
|
print('')
|
||||||
print('Limit history for a specific field to last 2 values::')
|
print('Limit history for a specific field to last 2 values::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/cpu/system/history'.format(API_URL))
|
print(f' # curl {API_URL}/cpu/system/history')
|
||||||
print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system', nb=2))))
|
print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system', nb=2))))
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
@ -305,17 +300,17 @@ def print_limits(stats):
|
|||||||
print('')
|
print('')
|
||||||
print('All limits/thresholds::')
|
print('All limits/thresholds::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/all/limits'.format(API_URL))
|
print(f' # curl {API_URL}/all/limits')
|
||||||
print(indent_stat(stats.getAllLimitsAsDict()))
|
print(indent_stat(stats.getAllLimitsAsDict()))
|
||||||
print('')
|
print('')
|
||||||
print('Limits/thresholds for the cpu plugin::')
|
print('Limits/thresholds for the cpu plugin::')
|
||||||
print('')
|
print('')
|
||||||
print(' # curl {}/cpu/limits'.format(API_URL))
|
print(f' # curl {API_URL}/cpu/limits')
|
||||||
print(indent_stat(stats.get_plugin('cpu').limits))
|
print(indent_stat(stats.get_plugin('cpu').limits))
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
|
|
||||||
class GlancesStdoutApiDoc(object):
|
class GlancesStdoutApiDoc:
|
||||||
"""This class manages the fields description display."""
|
"""This class manages the fields description display."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -14,7 +13,7 @@ import time
|
|||||||
from glances.globals import printandflush
|
from glances.globals import printandflush
|
||||||
|
|
||||||
|
|
||||||
class GlancesStdoutCsv(object):
|
class GlancesStdoutCsv:
|
||||||
"""This class manages the StdoutCsv display."""
|
"""This class manages the StdoutCsv display."""
|
||||||
|
|
||||||
separator = ','
|
separator = ','
|
||||||
@ -53,18 +52,18 @@ class GlancesStdoutCsv(object):
|
|||||||
line = ''
|
line = ''
|
||||||
|
|
||||||
if attribute is not None:
|
if attribute is not None:
|
||||||
line += '{}.{}{}'.format(plugin, attribute, self.separator)
|
line += f'{plugin}.{attribute}{self.separator}'
|
||||||
else:
|
else:
|
||||||
if isinstance(stat, dict):
|
if isinstance(stat, dict):
|
||||||
for k in stat.keys():
|
for k in stat.keys():
|
||||||
line += '{}.{}{}'.format(plugin, str(k), self.separator)
|
line += f'{plugin}.{str(k)}{self.separator}'
|
||||||
elif isinstance(stat, list):
|
elif isinstance(stat, list):
|
||||||
for i in stat:
|
for i in stat:
|
||||||
if isinstance(i, dict) and 'key' in i:
|
if isinstance(i, dict) and 'key' in i:
|
||||||
for k in i.keys():
|
for k in i.keys():
|
||||||
line += '{}.{}.{}{}'.format(plugin, str(i[i['key']]), str(k), self.separator)
|
line += '{}.{}.{}{}'.format(plugin, str(i[i['key']]), str(k), self.separator)
|
||||||
else:
|
else:
|
||||||
line += '{}{}'.format(plugin, self.separator)
|
line += f'{plugin}{self.separator}'
|
||||||
|
|
||||||
return line
|
return line
|
||||||
|
|
||||||
@ -73,18 +72,18 @@ class GlancesStdoutCsv(object):
|
|||||||
line = ''
|
line = ''
|
||||||
|
|
||||||
if attribute is not None:
|
if attribute is not None:
|
||||||
line += '{}{}'.format(str(stat.get(attribute, self.na)), self.separator)
|
line += f'{str(stat.get(attribute, self.na))}{self.separator}'
|
||||||
else:
|
else:
|
||||||
if isinstance(stat, dict):
|
if isinstance(stat, dict):
|
||||||
for v in stat.values():
|
for v in stat.values():
|
||||||
line += '{}{}'.format(str(v), self.separator)
|
line += f'{str(v)}{self.separator}'
|
||||||
elif isinstance(stat, list):
|
elif isinstance(stat, list):
|
||||||
for i in stat:
|
for i in stat:
|
||||||
if isinstance(i, dict) and 'key' in i:
|
if isinstance(i, dict) and 'key' in i:
|
||||||
for v in i.values():
|
for v in i.values():
|
||||||
line += '{}{}'.format(str(v), self.separator)
|
line += f'{str(v)}{self.separator}'
|
||||||
else:
|
else:
|
||||||
line += '{}{}'.format(str(stat), self.separator)
|
line += f'{str(stat)}{self.separator}'
|
||||||
|
|
||||||
return line
|
return line
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -10,16 +9,16 @@
|
|||||||
"""Issue interface class."""
|
"""Issue interface class."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import platform
|
import platform
|
||||||
import time
|
|
||||||
import pprint
|
import pprint
|
||||||
|
import sys
|
||||||
from glances.timer import Counter
|
import time
|
||||||
from glances import __version__, psutil_version
|
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
import glances
|
import glances
|
||||||
|
from glances import __version__, psutil_version
|
||||||
|
from glances.timer import Counter
|
||||||
|
|
||||||
TERMINAL_WIDTH = 79
|
TERMINAL_WIDTH = 79
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ class colors:
|
|||||||
self.NO = ''
|
self.NO = ''
|
||||||
|
|
||||||
|
|
||||||
class GlancesStdoutIssue(object):
|
class GlancesStdoutIssue:
|
||||||
"""This class manages the Issue display."""
|
"""This class manages the Issue display."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
@ -52,18 +51,14 @@ class GlancesStdoutIssue(object):
|
|||||||
|
|
||||||
def print_version(self):
|
def print_version(self):
|
||||||
sys.stdout.write('=' * TERMINAL_WIDTH + '\n')
|
sys.stdout.write('=' * TERMINAL_WIDTH + '\n')
|
||||||
sys.stdout.write(
|
sys.stdout.write(f'Glances {colors.BLUE + __version__ + colors.NO} ({os.path.realpath(glances.__file__)})\n')
|
||||||
'Glances {} ({})\n'.format(colors.BLUE + __version__ + colors.NO, os.path.realpath(glances.__file__))
|
sys.stdout.write(f'Python {colors.BLUE + platform.python_version() + colors.NO} ({sys.executable})\n')
|
||||||
)
|
sys.stdout.write(f'PsUtil {colors.BLUE + psutil_version + colors.NO} ({os.path.realpath(psutil.__file__)})\n')
|
||||||
sys.stdout.write('Python {} ({})\n'.format(colors.BLUE + platform.python_version() + colors.NO, sys.executable))
|
|
||||||
sys.stdout.write(
|
|
||||||
'PsUtil {} ({})\n'.format(colors.BLUE + psutil_version + colors.NO, os.path.realpath(psutil.__file__))
|
|
||||||
)
|
|
||||||
sys.stdout.write('=' * TERMINAL_WIDTH + '\n')
|
sys.stdout.write('=' * TERMINAL_WIDTH + '\n')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
def print_issue(self, plugin, result, message):
|
def print_issue(self, plugin, result, message):
|
||||||
sys.stdout.write('{}{}{}'.format(colors.BLUE + plugin, result, message))
|
sys.stdout.write(f'{colors.BLUE + plugin}{result}{message}')
|
||||||
sys.stdout.write(colors.NO + '\n')
|
sys.stdout.write(colors.NO + '\n')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
@ -108,9 +103,7 @@ class GlancesStdoutIssue(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
stat_error = e
|
stat_error = e
|
||||||
if stat_error is None:
|
if stat_error is None:
|
||||||
result = (colors.GREEN + '[OK] ' + colors.BLUE + ' {:.5f}s '.format(counter.get())).rjust(
|
result = (colors.GREEN + '[OK] ' + colors.BLUE + f' {counter.get():.5f}s ').rjust(41 - len(plugin))
|
||||||
41 - len(plugin)
|
|
||||||
)
|
|
||||||
if isinstance(stat, list) and len(stat) > 0 and 'key' in stat[0]:
|
if isinstance(stat, list) and len(stat) > 0 and 'key' in stat[0]:
|
||||||
key = 'key={} '.format(stat[0]['key'])
|
key = 'key={} '.format(stat[0]['key'])
|
||||||
stat_output = pprint.pformat([stat[0]], compact=True, width=120, depth=3)
|
stat_output = pprint.pformat([stat[0]], compact=True, width=120, depth=3)
|
||||||
@ -118,9 +111,7 @@ class GlancesStdoutIssue(object):
|
|||||||
else:
|
else:
|
||||||
message = '\n' + colors.NO + pprint.pformat(stat, compact=True, width=120, depth=2)
|
message = '\n' + colors.NO + pprint.pformat(stat, compact=True, width=120, depth=2)
|
||||||
else:
|
else:
|
||||||
result = (colors.RED + '[ERROR]' + colors.BLUE + ' {:.5f}s '.format(counter.get())).rjust(
|
result = (colors.RED + '[ERROR]' + colors.BLUE + f' {counter.get():.5f}s ').rjust(41 - len(plugin))
|
||||||
41 - len(plugin)
|
|
||||||
)
|
|
||||||
message = colors.NO + str(stat_error)[0 : TERMINAL_WIDTH - 41]
|
message = colors.NO + str(stat_error)[0 : TERMINAL_WIDTH - 41]
|
||||||
|
|
||||||
# Display the result
|
# Display the result
|
||||||
@ -128,7 +119,7 @@ class GlancesStdoutIssue(object):
|
|||||||
|
|
||||||
# Display total time need to update all plugins
|
# Display total time need to update all plugins
|
||||||
sys.stdout.write('=' * TERMINAL_WIDTH + '\n')
|
sys.stdout.write('=' * TERMINAL_WIDTH + '\n')
|
||||||
print("Total time to update all stats: {}{:.5f}s{}".format(colors.BLUE, counter_total.get(), colors.NO))
|
print(f"Total time to update all stats: {colors.BLUE}{counter_total.get():.5f}s{colors.NO}")
|
||||||
sys.stdout.write('=' * TERMINAL_WIDTH + '\n')
|
sys.stdout.write('=' * TERMINAL_WIDTH + '\n')
|
||||||
|
|
||||||
# Return True to exit directly (no refresh)
|
# Return True to exit directly (no refresh)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -14,7 +13,7 @@ import time
|
|||||||
from glances.globals import printandflush
|
from glances.globals import printandflush
|
||||||
|
|
||||||
|
|
||||||
class GlancesStdoutJson(object):
|
class GlancesStdoutJson:
|
||||||
"""This class manages the Stdout JSON display."""
|
"""This class manages the Stdout JSON display."""
|
||||||
|
|
||||||
def __init__(self, config=None, args=None):
|
def __init__(self, config=None, args=None):
|
||||||
@ -47,7 +46,7 @@ class GlancesStdoutJson(object):
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
# Display stats
|
# Display stats
|
||||||
printandflush('{}: {}'.format(plugin, stat))
|
printandflush(f'{plugin}: {stat}')
|
||||||
|
|
||||||
# Wait until next refresh
|
# Wait until next refresh
|
||||||
if duration > 0:
|
if duration > 0:
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Glances.
|
# This file is part of Glances.
|
||||||
#
|
#
|
||||||
@ -10,14 +9,14 @@
|
|||||||
"""Manage unicode message for Glances output."""
|
"""Manage unicode message for Glances output."""
|
||||||
|
|
||||||
_unicode_message = {
|
_unicode_message = {
|
||||||
'ARROW_LEFT': [u'\u2190', u'<'],
|
'ARROW_LEFT': ['\u2190', '<'],
|
||||||
'ARROW_RIGHT': [u'\u2192', u'>'],
|
'ARROW_RIGHT': ['\u2192', '>'],
|
||||||
'ARROW_UP': [u'\u2191', u'^'],
|
'ARROW_UP': ['\u2191', '^'],
|
||||||
'ARROW_DOWN': [u'\u2193', u'v'],
|
'ARROW_DOWN': ['\u2193', 'v'],
|
||||||
'CHECK': [u'\u2713', u''],
|
'CHECK': ['\u2713', ''],
|
||||||
'PROCESS_SELECTOR': [u'>', u'>'],
|
'PROCESS_SELECTOR': ['>', '>'],
|
||||||
'MEDIUM_LINE': [u'\u23AF', u'-'],
|
'MEDIUM_LINE': ['\u23af', '-'],
|
||||||
'LOW_LINE': [u'\u2581', u'_'],
|
'LOW_LINE': ['\u2581', '_'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -25,5 +24,4 @@ def unicode_message(key, args=None):
|
|||||||
"""Return the unicode message for the given key."""
|
"""Return the unicode message for the given key."""
|
||||||
if args and hasattr(args, 'disable_unicode') and args.disable_unicode:
|
if args and hasattr(args, 'disable_unicode') and args.disable_unicode:
|
||||||
return _unicode_message[key][1]
|
return _unicode_message[key][1]
|
||||||
else:
|
return _unicode_message[key][0]
|
||||||
return _unicode_message[key][0]
|
|
||||||
|
@ -218,23 +218,23 @@ body {
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loading page */
|
// /* Loading page */
|
||||||
|
|
||||||
#loading-page .glances-logo {
|
// #loading-page .glances-logo {
|
||||||
background: url('../images/glances.png') no-repeat center center;
|
// background: url('../images/glances.png') no-repeat center center;
|
||||||
background-size: contain;
|
// background-size: contain;
|
||||||
}
|
// }
|
||||||
|
|
||||||
@media (max-width: 750px) {
|
// @media (max-width: 750px) {
|
||||||
#loading-page .glances-logo {
|
// #loading-page .glances-logo {
|
||||||
height: 400px;
|
// height: 400px;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
@media (min-width: 750px) {
|
// @media (min-width: 750px) {
|
||||||
#loading-page .glances-logo {
|
// #loading-page .glances-logo {
|
||||||
height: 500px;
|
// height: 500px;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!dataLoaded" class="container-fluid" id="loading-page">
|
<div v-if="!dataLoaded" class="container-fluid" id="loading-page">
|
||||||
<div class="glances-logo"></div>
|
<div class="loader">Glances is loading...</div>
|
||||||
<div class="loader">Loading...</div>
|
|
||||||
</div>
|
</div>
|
||||||
<glances-help v-else-if="args.help_tag"></glances-help>
|
<glances-help v-else-if="args.help_tag"></glances-help>
|
||||||
<main v-else>
|
<main v-else>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user