diff --git a/README.md b/README.md index 18b3e75..9f0d169 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Features - [ ] Volume modes - [x] `Filesystem` mode - [ ] `Block` mode -- [ ] Volume metrics +- [x] Volume metrics - [ ] Supports fsTypes - [ ] Online expansion: If fs supports it (e.g. ext4, btrfs) - [ ] Online shrinking: If fs supports it (e.g. btrfs) diff --git a/metrics.py b/metrics.py new file mode 100644 index 0000000..90139b8 --- /dev/null +++ b/metrics.py @@ -0,0 +1,61 @@ +import json +import os +import threading +from os.path import basename + +from prometheus_client import Gauge +from prometheus_client.exposition import start_http_server + +import rawfile_util +from rawfile_util import attached_loops +from util import run_out + +VOLUME_ID = "volume_id" + +fs_size = Gauge( + "rawfile_filesystem_size_bytes", "Filesystem size in bytes.", [VOLUME_ID] +) +fs_free = Gauge( + "rawfile_filesystem_avail_bytes", "Filesystem free space in bytes", [VOLUME_ID] +) +dev_size = Gauge("rawfile_device_size_bytes", "Device size in bytes.", [VOLUME_ID]) +dev_free = Gauge( + "rawfile_device_free_bytes", "Device free space in bytes.", [VOLUME_ID] +) + + +def collect_stats(): + blockdevices = json.loads(run_out("lsblk --json").stdout.decode())["blockdevices"] + + def dev_to_mountpoint(dev_name): + dev_name = basename(dev_name) + matches = list(filter(lambda bd: bd["name"] == dev_name, blockdevices)) + if len(matches) == 0: + return None + return matches[0]["mountpoint"] + + for volume_id in rawfile_util.list_all_volumes(): + img_file = rawfile_util.img_file(volume_id) + labels = {VOLUME_ID: volume_id} + dev_stat = img_file.stat() + dev_size.labels(**labels).set(dev_stat.st_size) + dev_free.labels(**labels).set( + dev_stat.st_size - dev_stat.st_blocks * dev_stat.st_blksize + ) + for dev in attached_loops(img_file): + mountpoint = dev_to_mountpoint(dev) + if mountpoint is None: + continue + fs_stat = os.statvfs(mountpoint) + fs_size.labels(**labels).set(fs_stat.f_frsize * fs_stat.f_blocks) + fs_free.labels(**labels).set(fs_stat.f_frsize * fs_stat.f_bfree) + break + + +def expose_metrics(): + def collector_loop(): + collect_stats() + threading.Timer(10, collector_loop).start() + + collector_loop() + start_http_server(9100) diff --git a/rawfile.py b/rawfile.py index a7d0bdb..647a9d0 100755 --- a/rawfile.py +++ b/rawfile.py @@ -7,6 +7,7 @@ import grpc import rawfile_servicer from csi import csi_pb2_grpc +from metrics import expose_metrics @click.group() @@ -17,7 +18,10 @@ def cli(): @cli.command() @click.option("--endpoint", envvar="CSI_ENDPOINT", default="0.0.0.0:5000") @click.option("--nodeid", envvar="NODE_ID") -def csi_driver(endpoint, nodeid): +@click.option("--enable-metrics/--disable-metrics", default=True) +def csi_driver(endpoint, nodeid, enable_metrics): + if enable_metrics: + expose_metrics() server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) csi_pb2_grpc.add_IdentityServicer_to_server( rawfile_servicer.RawFileIdentityServicer(), server diff --git a/rawfile_util.py b/rawfile_util.py index 1d3b734..6376a19 100644 --- a/rawfile_util.py +++ b/rawfile_util.py @@ -1,4 +1,6 @@ +import glob import json +from os.path import basename, dirname from pathlib import Path from consts import DATA_DIR @@ -53,3 +55,8 @@ def attach_loop(file) -> str: return devs[0] next_loop() run(f"losetup --direct-io=on -f {file}") + + +def list_all_volumes(): + metas = glob.glob(f"{DATA_DIR}/*/disk.meta") + return [basename(dirname(meta)) for meta in metas] diff --git a/requirements.in b/requirements.in index 234a251..e56c24c 100644 --- a/requirements.in +++ b/requirements.in @@ -4,3 +4,4 @@ click pyyaml pykube-ng munch +prometheus_client diff --git a/requirements.txt b/requirements.txt index aa3155f..6f449d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ grpcio-tools==1.28.1 # via -r requirements.in grpcio==1.28.1 # via -r requirements.in, grpcio-tools idna==2.9 # via requests munch==2.5.0 # via -r requirements.in +prometheus-client==0.7.1 # via -r requirements.in protobuf==3.11.3 # via grpcio-tools pykube-ng==20.4.1 # via -r requirements.in pyyaml==5.3.1 # via -r requirements.in, pykube-ng