From 89821b479b85f34b4fb2bd18af7d237f8c03eda6 Mon Sep 17 00:00:00 2001 From: Ruakij Date: Sun, 25 Oct 2026 15:09:50 +0000 Subject: [PATCH] zfs_host: zed and pool capacity and scrub metrics for the node exporter textfile collector --- zfs_host/defaults/main.yml | 4 ++ zfs_host/tasks/main.yml | 41 ++++++++++++++++ zfs_host/templates/zfs-pool-metrics.j2 | 49 +++++++++++++++++++ .../templates/zfs-pool-metrics.service.j2 | 6 +++ zfs_host/templates/zfs-pool-metrics.timer.j2 | 10 ++++ 5 files changed, 110 insertions(+) create mode 100644 zfs_host/defaults/main.yml create mode 100644 zfs_host/tasks/main.yml create mode 100644 zfs_host/templates/zfs-pool-metrics.j2 create mode 100644 zfs_host/templates/zfs-pool-metrics.service.j2 create mode 100644 zfs_host/templates/zfs-pool-metrics.timer.j2 diff --git a/zfs_host/defaults/main.yml b/zfs_host/defaults/main.yml new file mode 100644 index 0000000..8d6d519 --- /dev/null +++ b/zfs_host/defaults/main.yml @@ -0,0 +1,4 @@ +--- +# Directory the node exporter reads textfile metrics from (the Debian default). +zfs_host_textfile_dir: /var/lib/prometheus/node-exporter +zfs_host_metrics_interval: 5min diff --git a/zfs_host/tasks/main.yml b/zfs_host/tasks/main.yml new file mode 100644 index 0000000..dca5b8a --- /dev/null +++ b/zfs_host/tasks/main.yml @@ -0,0 +1,41 @@ +--- +# ZFS event daemon and pool metrics for the node exporter. zed keeps its default zed.rc and +# logs to the journal, which journal_upload ships. +- name: Install the ZFS event daemon + ansible.builtin.apt: + name: zfs-zed + install_recommends: false + state: present + +- name: Enable the ZFS event daemon + ansible.builtin.systemd: + name: zfs-zed + enabled: true + state: started + # Check mode does not install the package. + ignore_errors: "{{ ansible_check_mode }}" + +- name: Install the pool metrics script + ansible.builtin.template: + src: zfs-pool-metrics.j2 + dest: /usr/local/sbin/zfs-pool-metrics + mode: "0755" + +- name: Install the pool metrics units + ansible.builtin.template: + src: "{{ item }}.j2" + dest: /etc/systemd/system/{{ item }} + mode: "0644" + loop: + - zfs-pool-metrics.service + - zfs-pool-metrics.timer + register: zfs_host_units + +- name: Enable the pool metrics timer + ansible.builtin.systemd: + name: zfs-pool-metrics.timer + enabled: true + state: started + daemon_reload: "{{ zfs_host_units.changed }}" + # Check mode does not write the units. + ignore_errors: "{{ ansible_check_mode }}" diff --git a/zfs_host/templates/zfs-pool-metrics.j2 b/zfs_host/templates/zfs-pool-metrics.j2 new file mode 100644 index 0000000..069bb14 --- /dev/null +++ b/zfs_host/templates/zfs-pool-metrics.j2 @@ -0,0 +1,49 @@ +#!/bin/bash +# Pool capacity and the last scrub for the node exporter textfile collector. +# A scrub in progress or cancelled keeps the previous end time: the pool has no newer completed scrub. +set -euo pipefail + +dir={{ zfs_host_textfile_dir }} +out=$dir/zfs_pool.prom +tmp=$(mktemp "$dir/.zfs_pool.XXXXXX") +trap 'rm -f "$tmp"' EXIT + +# Value of a metric line of the previous run, 0 if there is none. +previous() { + awk -v m="$1" -v p="$2" 'index($0, m "{pool=\"" p "\"}") == 1 {v = $2} END {print v + 0}' "$out" 2>/dev/null || echo 0 +} + +status=$(zpool status -p) +{ + echo '# TYPE zfs_pool_size_bytes gauge' + echo '# TYPE zfs_pool_allocated_bytes gauge' + echo '# TYPE zfs_pool_fragmentation_ratio gauge' + zpool list -Hp -o name,size,allocated,fragmentation | while IFS=$'\t' read -r pool size alloc frag; do + echo "zfs_pool_size_bytes{pool=\"$pool\"} $size" + echo "zfs_pool_allocated_bytes{pool=\"$pool\"} $alloc" + if [[ $frag =~ ^[0-9]+$ ]]; then + echo "zfs_pool_fragmentation_ratio{pool=\"$pool\"} $(awk -v f="$frag" 'BEGIN {print f / 100}')" + fi + done + + echo '# TYPE zfs_pool_scrub_end_timestamp_seconds gauge' + echo '# TYPE zfs_pool_scrub_errors gauge' + zpool list -Hp -o name | while read -r pool; do + scan=$(awk -v p="$pool" '$1 == "pool:" {cur = $2} cur == p && $1 == "scan:" {sub(/^[ \t]*scan:[ \t]*/, ""); print; exit}' <<< "$status") + end=0 + errors=0 + if [[ $scan =~ ^scrub\ repaired\ .*\ with\ ([0-9]+)\ errors\ on\ (.+)$ ]]; then + errors=${BASH_REMATCH[1]} + end=$(date -d "${BASH_REMATCH[2]}" +%s) + elif [[ $scan =~ ^scrub\ (in\ progress|canceled) ]]; then + end=$(previous zfs_pool_scrub_end_timestamp_seconds "$pool") + errors=$(previous zfs_pool_scrub_errors "$pool") + fi + echo "zfs_pool_scrub_end_timestamp_seconds{pool=\"$pool\"} $end" + echo "zfs_pool_scrub_errors{pool=\"$pool\"} $errors" + done +} > "$tmp" + +chmod 0644 "$tmp" +mv "$tmp" "$out" +trap - EXIT diff --git a/zfs_host/templates/zfs-pool-metrics.service.j2 b/zfs_host/templates/zfs-pool-metrics.service.j2 new file mode 100644 index 0000000..c5fb6ab --- /dev/null +++ b/zfs_host/templates/zfs-pool-metrics.service.j2 @@ -0,0 +1,6 @@ +[Unit] +Description=ZFS pool metrics for the node exporter textfile collector + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/zfs-pool-metrics diff --git a/zfs_host/templates/zfs-pool-metrics.timer.j2 b/zfs_host/templates/zfs-pool-metrics.timer.j2 new file mode 100644 index 0000000..511c684 --- /dev/null +++ b/zfs_host/templates/zfs-pool-metrics.timer.j2 @@ -0,0 +1,10 @@ +[Unit] +Description=ZFS pool metrics for the node exporter textfile collector + +[Timer] +OnBootSec=1min +OnUnitActiveSec={{ zfs_host_metrics_interval }} +AccuracySec=10s + +[Install] +WantedBy=timers.target