lcm-proj / lcm

Lightweight Communications and Marshalling
GNU Lesser General Public License v2.1
944 stars 385 forks source link

Feature request: lcm-logger disk quota option #464

Open judfs opened 11 months ago

judfs commented 11 months ago

An option to say --disk-quota 50gb and have logger exit if the drive being written to has less than 50gb free would be useful.

Alternatively, if there is an external utility that is generally recommended for this purpose, I think calling it out in --help would be broadly helpful.

judfs commented 11 months ago

fwiw, this is the external script I cobbled together for linux:


import os
import sys
import subprocess
from subprocess import PIPE
import threading
import time as pytime

# ---------------------------
# Sec
POOL_INTERVAL = 60
# GB
MIN_FREE_SPACE = int(os.environ.get("MIN_FREE_SPACE", 50))
START_TIME = pytime.time()
# ---------------------------

def hhmmss(sec:float):
    sec = int(sec)
    hours, rem = divmod(sec, 60*60) # Sec / sec_p_hour
    minutes, seconds = divmod(rem, 60)
    return f'{hours:0>2}:{minutes:0>2}:{int(seconds):0>2}'

def elapsed():
    now = pytime.time()
    delta = now - START_TIME
    print(f"Elapsed time: {hhmmss(delta)}", flush=True)

def free_gb():
    # Not bothering to understand
    # https://docs.python.org/3/library/os.html#os.statvfs
    cmd = 'df --output=avail -h /home'.split()
    p = subprocess.run(cmd, stdout=PIPE)
    # ie
    # b'Avail\n 536G\n'
    avail = p.stdout.decode().split('\n')[1].strip()
    if 'G' not in avail:
        print("Failed to parse df")
        return 0
    # print(f'\n\n----------\n{avail.split("G")[0]}')
    return int(avail.split("G")[0])

# ---------------------------

class State():
    # running = True
    abort_event = threading.Event()
    disk_full_event = threading.Event()

def poll_df(state: State):
    debug_counter = 0
    while not state.abort_event.is_set():
        debug_counter += 1
        if free_gb() < MIN_FREE_SPACE:
            # if debug_counter > 10:
            print("Low disk space. Aborting.", flush=True)
            state.abort_event.set()
            state.disk_full_event.set()
            return
        # print("world")
        state.abort_event.wait(POOL_INTERVAL)

def main():
    cmd = sys.argv[1:]
    # cmd = "ping google.com -i 10".split()
    # print("hello")
    state = State()

    t = threading.Thread(target=poll_df, args=[state])

    try:
        t.start()

        p = subprocess.Popen(cmd)
        state.disk_full_event.wait()

        p.kill()
    except:
        state.abort_event.set()
        print("\nKeyboardInterrupt -- stopping")
        # t.
    finally:
        elapsed()
        sys.exit(1)

    # print("world")
    pass

try:
    main()
except KeyboardInterrupt:
    print("\nKeyboardInterrupt -- stopping")