Skip to content

feat(volume): disky free — live statvfs probe, and use it as the fill target - #2

Open
biliboss wants to merge 1 commit into
mainfrom
feat/volume-free-space
Open

biliboss wants to merge 1 commit into
mainfrom
feat/volume-free-space

Conversation

@biliboss

Copy link
Copy Markdown
Owner

Closes #1.

What

disky free [PATH] — live statvfs(2) on the volume holding PATH (default $HOME). No snapshot, no scan.

$ disky free
Path:  /Users/billiboss
Total:      228.3 GB
Used:       202.5 GB  (89%)
Free:        25.8 GB

$ disky free --format json
{"kind":"volume","records":[{"free_bytes":27657216000,"path":"/Users/billiboss",
 "total_bytes":245107195904,"used_bytes":217449979904,"used_pct":88.716}],"schema_version":1}

predict --free-bytes and growth --fill-target now default to the same probe, so disky predict produces a fill-by date on its own instead of fill_at: null + "provide --free-bytes". --fill-target's help already promised this default; the code never implemented it.

Why this and not df

The loop that gates on free space is the one disky exists to serve, and it was the one number disky could not produce. Callers were pasting df -k / | awk '{print $4*1024}' into agent prompts to feed disky its own input.

Decisions worth reading

  • f_bavail, not f_bfree — unprivileged-writable blocks. The honest answer to "can I still write files".
  • f_frsize, not f_bsizef_bsize is the preferred I/O size, the wrong multiplier for these block counts.
  • free_bytes() returns None on failure, never 0 — a failed probe reporting zero reads as a full disk and could trigger a cleanup. Callers must treat it as unknown.
  • used_pct reads 1–2 points above df's Capacity — it divides by total blocks; df divides by used + available, hiding the root reserve. Documented on the field rather than silently picking either convention.

Verification

  • cargo clippy --all-targets clean; cargo test volume → 3 passed.
  • Positive control against df -k at the same instant: 27006492 KiB available = 25.8 GB, matches.
  • Negative control: disky free /nope-xyz → exit 3 with an RFC 9457 problem document, not 0 free.
  • Cross-volume: probed an external mount, distinct totals.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X3Z3sDkDDSUHQRxSw7aDCV

… target

An agent guarding "keep 30 GB free" had no way to ask disky for that number:
`stats` answers what a past scan measured (and needs a ~3 min traversal to
answer at all), while `predict` returned a null fill date until the caller
shelled out to `df -k` and did the unit arithmetic by hand.

- new `volume` module: `probe(path)` + `free_bytes(path)` over `statvfs(2)`.
  `f_bavail * f_frsize` — unprivileged-writable blocks, fragment size as the
  multiplier (`f_bsize` is the preferred I/O size and the wrong unit here).
  `free_bytes` returns None on failure, never 0: a failed probe reporting 0
  would read as a full disk and could trigger a cleanup.
- `disky free [PATH]` emits {path, total_bytes, free_bytes, used_bytes,
  used_pct} under kind="volume". No snapshot, no scan.
- `predict --free-bytes` and `growth --fill-target` now default to the probe.
  The latter's --help already documented this default; the code never had it.

`used_pct` divides by total blocks, so it reads 1-2 points above df's Capacity
column, which divides by used+available and hides the root reserve. Documented
on the field rather than silently matching either one.

Verified against `df -k` at the same instant (27006492 KiB avail = 25.8 GB),
with a negative control for a nonexistent path (exit 3, RFC 9457 problem).

Closes #1

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3Z3sDkDDSUHQRxSw7aDCV

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(volume): disky can't report its own free space

1 participant