Compare commits

..

1 Commits

Author SHA1 Message Date
illustris
e4573e0835 Add pool information to VM metrics
- Parse /etc/pve/user.cfg to extract pool membership for VMs
- Add pool-related labels to pve_kvm info metrics:
  - pool: Full hierarchical pool name
  - pool_levels: Number of pool hierarchy levels
  - pool1/pool2/pool3: Individual pool hierarchy levels
- Cache pool data based on file modification time to avoid repeated reads
2025-03-08 15:05:29 +05:30
6 changed files with 21 additions and 93 deletions

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Harikrishnan R
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

12
flake.lock generated
View File

@@ -3,11 +3,11 @@
"debBundler": {
"flake": false,
"locked": {
"lastModified": 1746317543,
"narHash": "sha256-1Xph5g1Lazzkc9XuY1nOkG5Fn7+lmSdldAC91boDawY=",
"lastModified": 1725149456,
"narHash": "sha256-rRrSD7itoPm+VIT4bIzSupQ7jw+H4eOjxRiRA89Kxb4=",
"owner": "illustris",
"repo": "flake",
"rev": "e86bd104d76d22b2ba36fede405e7bff290ef489",
"rev": "257a6c986cb9a67c4d6d0e0363507cab7f958b63",
"type": "github"
},
"original": {
@@ -18,11 +18,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1746663147,
"narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=",
"lastModified": 1725103162,
"narHash": "sha256-Ym04C5+qovuQDYL/rKWSR+WESseQBbNAe5DsXNx5trY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54",
"rev": "12228ff1752d7b7624a54e9c1af4b222b3c1073b",
"type": "github"
},
"original": {

View File

@@ -14,7 +14,7 @@ rec {
packages.x86_64-linux = with nixpkgs.legacyPackages.x86_64-linux; rec {
pvemon = python3Packages.buildPythonApplication {
pname = "pvemon";
version = "1.3.3";
version = "1.2.0";
src = ./src;
propagatedBuildInputs = with python3Packages; [
pexpect

View File

@@ -34,7 +34,6 @@ pool_cache = {
DEFAULT_PORT = 9116
DEFAULT_INTERVAL = 10
DEFAULT_PREFIX = "pve"
DEFAULT_HOST = "0.0.0.0"
gauge_settings = [
('kvm_cpu', 'CPU time for VM', ['id', 'mode']),
@@ -354,7 +353,6 @@ class PVECollector(object):
def main():
parser = argparse.ArgumentParser(description='PVE metrics exporter for Prometheus')
parser.add_argument('--port', type=int, default=DEFAULT_PORT, help='Port for the exporter to listen on')
parser.add_argument('--host', type=str, default=DEFAULT_HOST, help='Host address to bind the exporter to')
parser.add_argument('--interval', type=int, default=DEFAULT_INTERVAL, help='THIS OPTION DOES NOTHING')
parser.add_argument('--collect-running-vms', type=str, default='true', help='Enable or disable collecting running VMs metric (true/false)')
parser.add_argument('--collect-storage', type=str, default='true', help='Enable or disable collecting storage info (true/false)')
@@ -390,7 +388,7 @@ def main():
return
else:
REGISTRY.register(PVECollector())
start_http_server(cli_args.port, addr=cli_args.host)
start_http_server(cli_args.port)
while True:
time.sleep(100)

View File

@@ -65,15 +65,9 @@ def parse_storage_cfg(file_path='/etc/pve/storage.cfg'):
else:
# Parse key-value pairs within the current storage
if current_storage:
parts = line.split(None, 1)
key = parts[0].strip()
sanitized_key = sanitize_key(key)
if len(parts) > 1:
# Regular key-value pair
current_storage[sanitized_key] = parts[1].strip()
else:
# Key with no value, set it to True
current_storage[sanitized_key] = True
key, value = line.split(None, 1)
sanitized_key = sanitize_key(key.strip())
current_storage[sanitized_key] = value.strip()
# Append the last storage section to the list if any
if current_storage:
@@ -87,53 +81,15 @@ def parse_storage_cfg(file_path='/etc/pve/storage.cfg'):
def get_storage_size(storage):
try:
if storage["type"] in ["dir", "nfs", "cephfs", "zfspool"]:
if storage["type"] == "zfspool":
if "pool" not in storage:
logging.debug(f"ZFS pool {storage['name']} has no pool name configured")
return None
# Extract the pool name (could be in format like rpool/data)
pool_name = storage["pool"].split("/")[0]
# Use zpool command to get accurate size information
import subprocess
try:
result = subprocess.run(
["zpool", "list", pool_name, "-p"],
capture_output=True,
text=True,
check=True
)
# Parse the output
lines = result.stdout.strip().split("\n")
if len(lines) < 2:
logging.warn(f"Unexpected zpool list output format for {pool_name}")
return None
# Extract values from the second line (the data line)
values = lines[1].split()
if len(values) < 4:
logging.warn(f"Insufficient data in zpool list output for {pool_name}")
return None
# Values are: NAME SIZE ALLOC FREE ...
# We need the SIZE and FREE values (index 1 and 3)
total_size = int(values[1])
free_space = int(values[3])
return {
"total": total_size,
"free": free_space
}
except (subprocess.SubprocessError, ValueError, IndexError) as e:
logging.warn(f"Error running zpool list for {pool_name}: {e}")
return None
elif storage["type"] in ["dir", "nfs", "cephfs"]:
# For non-ZFS storage, use statvfs
path = storage["mountpoint"]
else:
path = storage["path"]
# Get filesystem statistics
stats = os.statvfs(path)
# Calculate total size and free space in bytes
# TODO: find an alternative way to calculate total_size for ZFS
total_size = stats.f_frsize * stats.f_blocks
free_space = stats.f_frsize * stats.f_bavail
return {
@@ -164,12 +120,7 @@ def collect_storage_metrics():
storage_pools = parse_storage_cfg()
for storage in storage_pools:
# Convert any non-string values to strings for InfoMetricFamily
storage_info = {}
for key, value in storage.items():
storage_info[key] = str(value) if not isinstance(value, str) else value
info_dict["node_storage"].add_metric([], storage_info)
info_dict["node_storage"].add_metric([], storage)
size = get_storage_size(storage)
if size != None:
gauge_dict["node_storage_size"].add_metric([storage["name"], storage["type"]], size["total"])

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name='pvemon',
version = "1.3.3",
version = "1.2.0",
packages=find_packages(),
entry_points={
'console_scripts': [