New storage logging script
This commit is contained in:
parent
f0b3b67c68
commit
af60b166ad
3 changed files with 186 additions and 0 deletions
9
systemd/system/log_storage.service
Normal file
9
systemd/system/log_storage.service
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Log the current non-volatile storage info
|
||||||
|
Wants=log_storage.timer
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/log_storage.py /home/emiliko/safe/loggers/storage/storage.csv
|
11
systemd/system/log_storage.timer
Normal file
11
systemd/system/log_storage.timer
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Log the current non-volatile storage
|
||||||
|
Requires=log_storage.service
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
Unit=log_storage.service
|
||||||
|
OnCalendar=*-*-* 0/2:00:00
|
||||||
|
RandomizedDelaySec=10m
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
166
systemd/system/timer-scripts/log_storage.py
Executable file
166
systemd/system/timer-scripts/log_storage.py
Executable file
|
@ -0,0 +1,166 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import PIPE
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog=f"log_storage",
|
||||||
|
description=f"Store storage info into a csv file",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"file",
|
||||||
|
type=Path,
|
||||||
|
help=f"Path to the csv, or - for stdout",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if str(args.file) == "-":
|
||||||
|
args.file = Path("/dev/stdout")
|
||||||
|
|
||||||
|
|
||||||
|
class Disk:
|
||||||
|
columns = [
|
||||||
|
"date",
|
||||||
|
"uuid",
|
||||||
|
"label",
|
||||||
|
"device",
|
||||||
|
"fstype",
|
||||||
|
"partition_number",
|
||||||
|
"parent_device",
|
||||||
|
"used_space", # In bytes
|
||||||
|
"size", # In bytes
|
||||||
|
]
|
||||||
|
|
||||||
|
date = None # This is set later
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.uuid = None
|
||||||
|
self.fstype = None
|
||||||
|
self.device = None
|
||||||
|
self.label = None
|
||||||
|
self.partition_number = None
|
||||||
|
self.parent_device = None
|
||||||
|
self.used_space = None
|
||||||
|
self.size = None
|
||||||
|
|
||||||
|
def as_csv(self):
|
||||||
|
return [getattr(self, c) for c in self.columns]
|
||||||
|
|
||||||
|
|
||||||
|
def read_sysfs(p):
|
||||||
|
with open(p, "r") as f:
|
||||||
|
return f.read().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def mount_line_info(p):
|
||||||
|
with open("/proc/mounts", "r") as f:
|
||||||
|
for line in [l.strip().split() for l in f]:
|
||||||
|
if line[0] == str(p):
|
||||||
|
du = shutil.disk_usage(line[1])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total": du.total,
|
||||||
|
"used": du.used,
|
||||||
|
"fstype": line[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def mount_info(p):
|
||||||
|
# Check if partition is already in /proc/mounts
|
||||||
|
info = mount_line_info(p)
|
||||||
|
|
||||||
|
if info is not None:
|
||||||
|
return info
|
||||||
|
|
||||||
|
# Quickly mount and unmount a partition to populate /proc/mounts
|
||||||
|
tmpd = tempfile.mkdtemp()
|
||||||
|
|
||||||
|
mount = subprocess.run(["mount", p, tmpd], stderr=PIPE)
|
||||||
|
|
||||||
|
if mount.returncode == 0:
|
||||||
|
info = mount_line_info(p)
|
||||||
|
|
||||||
|
subprocess.run(["umount", p], check=True)
|
||||||
|
os.rmdir(tmpd)
|
||||||
|
|
||||||
|
return info
|
||||||
|
else:
|
||||||
|
mount_stderr = mount.stderr.decode("utf-8")
|
||||||
|
m = re.search("unknown filesystem type '([^' ]+)'\.", mount_stderr)
|
||||||
|
|
||||||
|
if m is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return {"fstype": m[1]}
|
||||||
|
|
||||||
|
|
||||||
|
sysfs = "/sys/class/block"
|
||||||
|
|
||||||
|
uuid_path = "/dev/disk/by-uuid"
|
||||||
|
disk_paths = [Path(f"{uuid_path}/{x}") for x in os.listdir(uuid_path)]
|
||||||
|
disk_paths = list(filter(lambda x: x.is_symlink(), disk_paths))
|
||||||
|
|
||||||
|
label_path = "/dev/disk/by-label"
|
||||||
|
|
||||||
|
try:
|
||||||
|
label_paths = [Path(f"{label_path}/{x}") for x in os.listdir(label_path)]
|
||||||
|
except FileNotFoundError:
|
||||||
|
label_paths = list()
|
||||||
|
|
||||||
|
# Get label paths
|
||||||
|
device_to_label = dict()
|
||||||
|
|
||||||
|
for p in label_paths:
|
||||||
|
os.chdir(label_path)
|
||||||
|
device_to_label[str(p.readlink().resolve())] = p.name
|
||||||
|
|
||||||
|
# Build disks
|
||||||
|
disks = list()
|
||||||
|
|
||||||
|
for p in disk_paths:
|
||||||
|
disk = Disk()
|
||||||
|
|
||||||
|
os.chdir(uuid_path)
|
||||||
|
disk.device = p.readlink().resolve()
|
||||||
|
|
||||||
|
sysfs_disk = Path(f"{sysfs}/{disk.device.name}")
|
||||||
|
|
||||||
|
disk.uuid = p.name
|
||||||
|
disk.parent_device = Path("/dev") / sysfs_disk.readlink().parent.resolve().name
|
||||||
|
disk.partition_number = read_sysfs(sysfs_disk / "partition")
|
||||||
|
disk.label = device_to_label.get(str(disk.device))
|
||||||
|
|
||||||
|
info = mount_info(disk.device)
|
||||||
|
|
||||||
|
if info is not None:
|
||||||
|
disk.size = info.get("total")
|
||||||
|
disk.used_space = info.get("used")
|
||||||
|
disk.fstype = info.get("fstype")
|
||||||
|
|
||||||
|
disks.append(disk)
|
||||||
|
|
||||||
|
# Write output to csv
|
||||||
|
Disk.date = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
|
||||||
|
if str(args.file) == "/dev/stdout" or not os.path.exists(args.file):
|
||||||
|
with open(args.file, "w") as csvf:
|
||||||
|
writer = csv.writer(
|
||||||
|
csvf, dialect="unix", delimiter=",", quoting=csv.QUOTE_MINIMAL
|
||||||
|
)
|
||||||
|
writer.writerow(Disk.columns)
|
||||||
|
|
||||||
|
with open(args.file, "a") as csvf:
|
||||||
|
writer = csv.writer(csvf, dialect="unix", delimiter=",", quoting=csv.QUOTE_MINIMAL)
|
||||||
|
for disk in disks:
|
||||||
|
writer.writerow(disk.as_csv())
|
Loading…
Reference in a new issue