feat: add disk usage summary
Uses du to show per-directory disk usage and highlights the top 5 largest directories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ae891357cc
commit
9cba2be54e
1 changed files with 63 additions and 0 deletions
63
luminos_lib/disk.py
Normal file
63
luminos_lib/disk.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
"""Disk usage summary using du."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def get_disk_usage(target, show_hidden=False):
|
||||||
|
"""Get per-directory disk usage via du.
|
||||||
|
|
||||||
|
Returns a list of dicts: {path, size_bytes, size_human}.
|
||||||
|
"""
|
||||||
|
cmd = ["du", "-b", "--max-depth=2", target]
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd, capture_output=True, text=True, timeout=30,
|
||||||
|
)
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
return []
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for line in result.stdout.strip().split("\n"):
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
parts = line.split("\t", 1)
|
||||||
|
if len(parts) != 2:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
size = int(parts[0])
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
path = parts[1]
|
||||||
|
|
||||||
|
# Skip hidden directories if not requested
|
||||||
|
if not show_hidden:
|
||||||
|
segments = path.replace(target, "").split("/")
|
||||||
|
if any(s.startswith(".") and s != "." for s in segments):
|
||||||
|
continue
|
||||||
|
|
||||||
|
entries.append({
|
||||||
|
"path": path,
|
||||||
|
"size_bytes": size,
|
||||||
|
"size_human": _human_size(size),
|
||||||
|
})
|
||||||
|
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def _human_size(nbytes):
|
||||||
|
"""Convert bytes to human-readable string."""
|
||||||
|
for unit in ("B", "KB", "MB", "GB", "TB"):
|
||||||
|
if nbytes < 1024:
|
||||||
|
if unit == "B":
|
||||||
|
return f"{nbytes} {unit}"
|
||||||
|
return f"{nbytes:.1f} {unit}"
|
||||||
|
nbytes /= 1024
|
||||||
|
return f"{nbytes:.1f} PB"
|
||||||
|
|
||||||
|
|
||||||
|
def top_directories(usage, n=5):
|
||||||
|
"""Return the top n largest directories from usage data."""
|
||||||
|
# Exclude the root entry (last line of du is usually the total)
|
||||||
|
sorted_entries = sorted(usage, key=lambda x: x["size_bytes"], reverse=True)
|
||||||
|
return sorted_entries[:n]
|
||||||
Loading…
Reference in a new issue