Zpět na domů

Widget qtile: předpověď prostoru na disku

Widget DiskSpaceAlert pro qtile předpovídá vyčerpání prostoru na disku podle lineární regrese. Filtruje virtuální FS, používá historii v deque, rozlišuje aktivní a stabilizující se hrozby. Úplný kód bez závislostí.

qtile widget předpovídá konec /tmp předem
Advertisement 728x90

Předpovídání vyčerpání místa na disku ve widgetu qtile

Vývojáři qtile mohou integrovat widget DiskSpaceAlert pro předpovídání vyčerpání místa na disku pomocí lineární regrese. Analyzuje trend volného místa na reálných souborových systémech, vynechává virtuální FS, a aktivuje se při překročení nuly v rozmezí 24 hodin. Widget dědí z InLoopPollText, dotazuje se každých 5 sekund přes /proc/mounts a os.statvfs, používá deque pro historii dat.

Kód funguje bez externích závislostí, je vhodný pro desktopy bez Promethea. První spuštění nastalo na /tmp v ext2 na zram (16 GB): pozadí logů zaplnilo oddíl, Bash tool spadl bez varování.

Filtrace mountpointů

Widget ignoruje virtuální FS a systémové cesty, zaměřuje se na reálné disky. Konstanty definují výjimky:

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/')

Funkce get_mountpoints parsuje /proc/mounts, přeskočí záznamy podle typu FS a prefixu cesty. Pro tmpfs na zram filtr nefunguje, což umožňuje zachytit kritické scénáře.

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

Lineární regrese pro predikci

Předpověď je implementována metodou nejmenších čtverců (OLS) bez numpy. Aproximuje volné místo y(t) = a·t + b, vypočítá čas do y=0. Vzorce:

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

Čas do vyčerpání: -b / a minus uplynulý čas okna. Pokud slope ≥ 0 nebo 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

Posuvné okno maxlen=120 (10 min při 5s intervalu) vyhlazuje šum od dočasných souborů.

Barevná indikace a logika alertů

Dvě úrovně poplachu:

  • Červená (#FF0000): Aktivní spotřeba (posledních 30s místo ubývá) nebo <10% volno.
  • Oranžová (#FF8800): Předpověď vyčerpání <24h, ale trend se stabilizoval.

Metoda _set_color aktualizuje self.foreground a self.layout.colour bez zbytečných draw(). Text: '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

Vyčištění historie pro odstraněné mountpointy.

Celý kód třídy DiskSpaceAlert

DISK_SPACE_CHECK_INTERVAL = 5
DISK_SPACE_WARN_PERCENT = 10
DISK_SPACE_PREDICT_HOURS = 24

# ... (SKIP_FS a PREFIXES jako výše)

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() jako výše

    # _predict_exhaustion() jako výše

    # _set_color() jako výše

    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)

Integrace do screens: DiskSpaceAlert(font=font, fontsize=fontsize).

Nastavení a ladění

  • CHECK_INTERVAL: 5s (desktop), 30s (server).
  • WARN_PERCENT: 10% pro okamžité červené.
  • PREDICT_HOURS: 24h horizont.
  • maxlen=120: 10 min historie.

Krátké okno — hlučné falešné poplachy, dlouhé — zpožďuje.

Co je důležité

  • Lineární regrese OLS podle historie volného místa předpovídá vyčerpání přesněji než prahové kontroly.
  • Filtrace /proc/mounts vylučuje virtuální FS, zaměřuje se na ext2/zram, reálné disky.
  • Dvě barvy: červená pro aktivní hrozbu, oranžová pro stabilizující se trendy.
  • Bez závislostí, 100 řádků v config.py qtile.
  • Okno 10 min vyhlazuje výkyvy od temp-souborů.

— Editorial Team

Advertisement 728x90

Číst dál