import json import os import subprocess from prometheus_client.core import REGISTRY from prometheus_client.exposition import start_http_server from prometheus_client.metrics_core import GaugeMetricFamily import rawfile_util from rawfile_util import attached_loops class VolumeStatsCollector(object): def collect(self): VOLUME_ID = "volume_id" fs_size = GaugeMetricFamily( "rawfile_filesystem_size_bytes", "Filesystem size in bytes.", labels=[VOLUME_ID], ) fs_free = GaugeMetricFamily( "rawfile_filesystem_avail_bytes", "Filesystem free space in bytes", labels=[VOLUME_ID], ) dev_size = GaugeMetricFamily( "rawfile_device_size_bytes", "Device size in bytes.", labels=[VOLUME_ID] ) dev_free = GaugeMetricFamily( "rawfile_device_free_bytes", "Device free space in bytes.", labels=[VOLUME_ID], ) for volume_id in rawfile_util.list_all_volumes(): img_file = rawfile_util.img_file(volume_id) labels = [volume_id] dev_stat = img_file.stat() dev_size.add_metric(labels, dev_stat.st_size) dev_free.add_metric(labels, dev_stat.st_size - dev_stat.st_blocks * 512) mountpoint = volume_to_mountpoint(img_file) if mountpoint is not None: fs_stat = os.statvfs(mountpoint) fs_size.add_metric(labels, fs_stat.f_frsize * fs_stat.f_blocks) fs_free.add_metric(labels, fs_stat.f_frsize * fs_stat.f_bfree) return [fs_size, fs_free, dev_size, dev_free] def volume_to_mountpoint(img_file): for dev in attached_loops(img_file): mountpoint = dev_to_mountpoint(dev) if mountpoint is not None: return mountpoint return None def dev_to_mountpoint(dev_name): try: output = subprocess.run( f"findmnt --json --first-only {dev_name}", shell=True, check=True, capture_output=True, ).stdout.decode() data = json.loads(output) return data["filesystems"][0]["target"] except subprocess.CalledProcessError: return None def expose_metrics(): REGISTRY.register(VolumeStatsCollector()) start_http_server(9100)