Powrót do strony głównej

Widget qtile: przewidywanie przestrzeni dyskowej

Widget DiskSpaceAlert dla qtile przewiduje wyczerpanie przestrzeni dyskowej po regresji liniowej. Filtruje wirtualne FS, używa historii w deque, rozróżnia aktywne i stabilizujące się zagrożenia. Pełny kod bez zależności.

qtile widget przewiduje koniec /tmp z wyprzedzeniem
Advertisement 728x90

Przewidywanie wyczerpania miejsca na dysku w widżecie qtile

Programiści qtile mogą zintegrować widżet DiskSpaceAlert do przewidywania wyczerpania miejsca na dysku za pomocą regresji liniowej. Analizuje on trend wolnego miejsca na rzeczywistych systemach plików, pomijając wirtualne FS, i aktywuje się po przekroczeniu zera w ciągu 24 godzin. Widżet dziedziczy po InLoopPollText, odpytuje co 5 sekund przez /proc/mounts i os.statvfs, wykorzystuje deque do historii danych.

Kod działa bez zewnętrznych zależności, nadaje się na komputery stacjonarne bez Prometheusa. Pierwsze uruchomienie nastąpiło na /tmp w ext2 na zram (16 GB): logi w tle wypełniły partycję, narzędzie Bash padło bez ostrzeżenia.

Filtrowanie punktów montowania

Widżet ignoruje wirtualne FS i ścieżki systemowe, skupiając się na rzeczywistych dyskach. Stałe definiują wyjątki:

Google AdInline article slot
DISK_SPACE_SKIP_FS = {
    'sysfs', 'proc', 'devtmpfs', 'devpts', 'tmpfs', 'securityfs',
    'cgroup', 'cgroup2', 'pstore', 'debugfs', 'hugetlbfs', 'mqueue',
    'configfs', 'fusectl', 'efivarfs', 'binfmt_misc', 'autofs',
    'fuse.portal', 'nsfs', 'tracefs', 'bpf', 'ramfs', 'rpc_pipefs',
    'nfsd', 'overlay', 'fuse.gvfsd-fuse',
}
DISK_SPACE_SKIP_PREFIXES = ('/sys/', '/proc/', '/dev/', '/run/')

Funkcja get_mountpoints parsuje /proc/mounts, pomijając wpisy według typu FS i prefiksu ścieżki. Dla tmpfs na zram filtr nie działa, co pozwala łapać krytyczne scenariusze.

def get_mountpoints(self):
    mounts = []
    try:
        with open('/proc/mounts') as f:
            for line in f:
                parts = line.split()
                if len(parts) < 3:
                    continue
                mountpoint, fstype = parts[1], parts[2]
                if fstype in DISK_SPACE_SKIP_FS:
                    continue
                if any(mountpoint.startswith(p)
                       for p in DISK_SPACE_SKIP_PREFIXES):
                    continue
                mounts.append(mountpoint)
    except OSError:
        pass
    return mounts

Regresja liniowa do prognozowania

Przewidywanie zaimplementowano metodą najmniejszych kwadratów (OLS) bez numpy. Aproksymuje wolne miejsce y(t) = a·t + b, oblicza czas do y=0. Wzory:

  • a = (n∑(t_i y_i) - ∑t_i ∑y_i) / (n∑t_i² - (∑t_i)²)
  • b = (∑y_i - a ∑t_i) / n

Czas do wyczerpania: -b / a minus elapsed czas okna. Jeśli slope ≥ 0 lub seconds_left ≤ 0 — None.

Google AdInline article slot
@staticmethod
def _predict_exhaustion(history, now):
    n = len(history)
    t = [h[0] - history[0][0] for h in history]
    y = [h[1] for h in history]
    sum_t = sum(t)
    sum_y = sum(y)
    sum_tt = sum(ti * ti for ti in t)
    sum_ty = sum(ti * yi for ti, yi in zip(t, y))
    denom = n * sum_tt - sum_t * sum_t
    if denom == 0:
        return None
    slope = (n * sum_ty - sum_t * sum_y) / denom
    intercept = (sum_y - slope * sum_t) / n
    if slope >= 0:
        return None
    time_to_zero = -intercept / slope
    elapsed = now - history[0][0]
    seconds_left = time_to_zero - elapsed
    if seconds_left > 0:
        return seconds_left / 3600
    return None

Okno przesuwne maxlen=120 (10 min przy 5s interwale) wygładza szum z plików tymczasowych.

Wskaźnik kolorowy i logika alertów

Dwa poziomy alarmu:

  • Czerwony (#FF0000): Aktywne zużycie (ostatnie 30s miejsce maleje) lub <10% wolnego.
  • Pomarańczowy (#FF8800): Prognoza wyczerpania <24h, ale trend się ustabilizował.

Metoda _set_color aktualizuje self.foreground i self.layout.colour bez zbędnych draw(). Tekst: 'DISK /tmp: 8%free, 2h left'.

Google AdInline article slot
def _set_color(self, color):
    self.foreground = color
    if self.layout:
        self.layout.colour = color

Czyszczenie historii dla usuniętych mountpoints.

Pełny kod klasy DiskSpaceAlert

DISK_SPACE_CHECK_INTERVAL = 5
DISK_SPACE_WARN_PERCENT = 10
DISK_SPACE_PREDICT_HOURS = 24

# ... (SKIP_FS i PREFIXES jak wyżej)

class DiskSpaceAlert(base.InLoopPollText):

    COLOR_RED = '#FF0000'
    COLOR_ORANGE = '#FF8800'

    def __init__(self, **config):
        config.setdefault('foreground', self.COLOR_RED)
        config.setdefault('update_interval', DISK_SPACE_CHECK_INTERVAL)
        super().__init__(**config)
        self.space_history: dict[str, deque] = {}

    # get_mountpoints() jak wyżej

    # _predict_exhaustion() jak wyżej

    # _set_color() jak wyżej

    def poll(self):
        now = time()
        alerts = []
        any_active = False
        for mp in self.get_mountpoints():
            try:
                st = os.statvfs(mp)
            except OSError:
                continue
            if st.f_blocks == 0:
                continue
            free_pct = st.f_bfree / st.f_blocks * 100
            free_bytes = st.f_bfree * st.f_frsize

            history = self.space_history.setdefault(
                mp, deque(maxlen=120))
            history.append((now, free_bytes))

            reasons = []
            if free_pct < DISK_SPACE_WARN_PERCENT:
                reasons.append(f'{free_pct:.0f}%free')
                any_active = True

            if len(history) >= 2 and now - history[0][0] > 60:
                hours_left = self._predict_exhaustion(
                    history, now)
                if (hours_left is not None
                        and hours_left < DISK_SPACE_PREDICT_HOURS):
                    recent = [h for h in history
                              if now - h[0] < 30]
                    if (len(recent) >= 2
                            and recent[-1][1] <= recent[0][1]):
                        any_active = True
                    reasons.append(f'{hours_left:.0f}h left')

            if reasons:
                label = mp if mp != '/' else '/'
                alerts.append(
                    f'DISK {label}: {', '.join(reasons)}')

        self._set_color(
            self.COLOR_RED if any_active
            else self.COLOR_ORANGE)

        current = set(self.get_mountpoints())
        for mp in list(self.space_history):
            if mp not in current:
                del self.space_history[mp]

        return ' | '.join(alerts)

Integracja w screens: DiskSpaceAlert(font=font, fontsize=fontsize).

Ustawienia i dostrajanie

  • CHECK_INTERVAL: 5s (komputer stacjonarny), 30s (serwer).
  • WARN_PERCENT: 10% dla natychmiastowego czerwonego.
  • PREDICT_HOURS: 24h horyzont.
  • maxlen=120: 10 min historii.

Krótkie okno — szumne fałszywe alarmy, długie — opóźnia.

Co jest ważne

  • Regresja liniowa OLS na historii wolnego miejsca przewiduje wyczerpanie dokładniej niż sprawdzanie progów.
  • Filtrowanie /proc/mounts pomija wirtualne FS, skupia się na ext2/zram, rzeczywistych dyskach.
  • Dwa kolory: czerwony dla aktywnego zagrożenia, pomarańczowy dla stabilizujących się trendów.
  • Bez zależności, 100 linii w config.py qtile.
  • Okno 10 min wygładza skoki z plików tymczasowych.

— Editorial Team

Advertisement 728x90

Czytaj dalej