From df8526254c04fed6529bf2ce00f42af2988a2d94 Mon Sep 17 00:00:00 2001 From: illustris Date: Wed, 4 Oct 2023 13:39:33 +0530 Subject: [PATCH] collect disk info --- .gitignore | 3 +- src/pvemon/__init__.py | 10 ++++- src/pvemon/qmblock.py | 89 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/pvemon/qmblock.py diff --git a/.gitignore b/.gitignore index cf52f6e..fa1e439 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ result *~ -*.deb \ No newline at end of file +*.deb +__pycache__ diff --git a/src/pvemon/__init__.py b/src/pvemon/__init__.py index e065896..706f2f2 100644 --- a/src/pvemon/__init__.py +++ b/src/pvemon/__init__.py @@ -14,6 +14,8 @@ import cProfile from concurrent.futures import ThreadPoolExecutor from threading import Lock +import qmblock + DEFAULT_PORT = 9116 DEFAULT_INTERVAL = 10 DEFAULT_PREFIX = "pve" @@ -110,7 +112,6 @@ def extract_nic_info_from_monitor(vm_id): "model": cfg["model"], "macaddr": cfg["macaddr"], "ifname": cfg["ifname"] - } for netdev, cfg in nics_map.items() ] @@ -191,7 +192,14 @@ def collect_kvm_metrics(): gauge = create_or_get_gauge(metric_name, nic_labels.keys()) gauge.labels(**nic_labels).set(value) + def map_disk_proc(id): + for disk_name, disk_info in qmblock.extract_disk_info_from_monitor(id).items(): + disk_labels = {"id": id, "disk_name": disk_name} + prom_disk_info = create_or_get_info("kvm_disk", disk_labels.keys()) + prom_disk_info.labels(**disk_labels).info({k: v for k, v in disk_info.items() if k not in disk_labels.keys()}) + list(executor.map(map_netstat_proc, [ proc[2] for proc in procs ])) + list(executor.map(map_disk_proc, [ proc[2] for proc in procs ])) def main(): parser = argparse.ArgumentParser(description='PVE metrics exporter for Prometheus') diff --git a/src/pvemon/qmblock.py b/src/pvemon/qmblock.py new file mode 100644 index 0000000..f34505c --- /dev/null +++ b/src/pvemon/qmblock.py @@ -0,0 +1,89 @@ +import pexpect +import re +import os + +def get_device(disk_path): + try: + return os.readlink(disk_path).split('/')[-1] + except OSError: + return None + +def extract_disk_info_from_monitor(vm_id): + child = pexpect.spawn(f'qm monitor {vm_id}') + # Wait for the QEMU monitor prompt + child.expect('qm>', timeout=10) + # Execute 'info block' + child.sendline('info block') + # Wait for the prompt again + child.expect('qm>', timeout=10) + # Parse the output + raw_output = child.before.decode('utf-8').strip() + child.close() + disks_map = {} + disks = [x.strip() for x in raw_output.split("drive-")[1:]] + for disk in disks: + data = [x.strip() for x in disk.split("\n")] + pattern = r'(\w+) \(#block(\d+)\): (.+) \(([\w, -]+)\)' + match = re.match(pattern, data[0]) + if not match: + continue + + disk_name, block_id, disk_path, disk_type_and_mode = match.groups() + disk_type = disk_type_and_mode.split(", ")[0] + if "efidisk" in disk_name: # TODO: handle this later + continue + + disks_map[disk_name]={ + "disk_name": disk_name, + "block_id": block_id, + "disk_path": disk_path, + "disk_type": disk_type, + } + if "read-only" in disk_type_and_mode: + disks_map[disk_name]["read_only"] = "true" + if disk_type == "qcow2": + disks_map[disk_name]["vol_name"] = disk_path.split("/")[-1].split(".")[0] + if "/dev/zvol" in disk_path: + disks_map[disk_name]["disk_type"] = "zvol" + disks_map[disk_name]["pool"] = "/".join(disk_path.split("/")[3:-1]) + disks_map[disk_name]["vol_name"] = disk_path.split("/")[-1] + disks_map[disk_name]["device"] = get_device(disk_path) + elif re.match(r'/dev/[^/]+/vm-\d+-disk-\d+', disk_path): # lvm + disks_map[disk_name]["disk_type"] = "lvm" + vg_name, vol_name = re.search(r'/dev/([^/]+)/(vm-\d+-disk-\d+)', disk_path).groups() + disks_map[disk_name]["vg_name"] = vg_name + disks_map[disk_name]["vol_name"] = vol_name + disks_map[disk_name]["device"] = get_device(disk_path) + elif "/dev/rbd-pve" in disk_path: # rbd + disks_map[disk_name]["disk_type"] = "rbd" + rbd_parts = disk_path.split('/') + disks_map[disk_name]["cluster_id"] = rbd_parts[-3] + disks_map[disk_name]["pool_name"] = rbd_parts[-2] + disks_map[disk_name]["vol_name"] = rbd_parts[-1] + disks_map[disk_name]["device"] = get_device(disk_path) + elif "/dev/rbd-pve" in disk_path: + disks_map[disk_name]["disk_type"] = "rbd" + rbd_parts = disk_path.split('/') + pool_name = rbd_parts[-3] + vm_id = rbd_parts[-1].split('-')[1] + disk_number = rbd_parts[-1].split('-')[-1] + disks_map[disk_name]["pool_name"] = pool_name + disks_map[disk_name]["rbd_vm_id"] = vm_id + disks_map[disk_name]["rbd_disk_number"] = disk_number + for line in data[1:-1]: + if "Attached to" in line: + attached_to = line.split(":")[-1].strip() + if "virtio" in attached_to: + attached_to=attached_to.split("/")[3] + disks_map[disk_name]["attached_to"] = attached_to + if "Cache mode" in line: + for cache_mode in line.split(":")[-1].strip().split(", "): + disks_map[disk_name][f"cache_mode_{cache_mode}"] = "true" + if "Detect zeroes" in line: + disks_map[disk_name]["detect_zeroes"] = "on" + return disks_map + +if __name__ == "__main__": + import json + import sys + print(json.dumps(extract_disk_info_from_monitor(sys.argv[1])))