Skip to content

fix(metrics): distinguish unavailable disk stats from a real zero - #91

Open
strausmann wants to merge 1 commit into
Finsys:mainfrom
strausmann:fix/disk-metrics-omitempty
Open

fix(metrics): distinguish unavailable disk stats from a real zero#91
strausmann wants to merge 1 commit into
Finsys:mainfrom
strausmann:fix/disk-metrics-omitempty

Conversation

@strausmann

Copy link
Copy Markdown

Problem

Collector.Collect() reports DiskTotal/DiskUsed/DiskFree as a plain 0 whenever the
statfs call on the Docker data root fails (e.g. the path isn't visible in the agent's mount
namespace, or SKIP_DF_COLLECTION isn't the reason). HostMetrics uses bare uint64 fields
with no omitempty, so the JSON always includes "diskTotal":0 -- indistinguishable from a
disk that is genuinely full or genuinely reports 0 bytes.

Related: Finsys/dockhand#976, Finsys/dockhand#1397

Fix

Change DiskTotal/DiskUsed/DiskFree to *uint64 with omitempty:

  • Stat fails → fields stay nil → omitted from the outgoing JSON entirely.
  • Stat succeeds → fields point at the real value, including a legitimate 0 (e.g. diskFree
    on a full disk), so success is never silently dropped by omitempty.

This is additive on the wire: a consumer still decoding into a plain, required number field
(as strausmann/dockhand's current TS MetricsMessage type does) gets undefined instead of
0 when the field is absent -- not a decode error, and today nothing in Dockhand actually reads
these three fields yet (verified against the current hawser.ts), so there's no existing
consumer to break.

collectDisk() is promoted to an exported, package-level DiskUsage(path string) that no
longer resolves the Docker data root itself -- Collect() now does that and passes the
resolved path in. This makes the statfs call directly unit-testable without a live Docker
client, and lets a later change reuse it (see the follow-up PR extending Standard mode's
/_hawser/info).

Tests

New tests in internal/metrics/collector_test.go and internal/protocol/messages_test.go
(neither package had tests before):

  • DiskUsage() success/error paths (real vs. missing filesystem path)
  • applyDiskMetrics() success/error, including the "legitimate 0" case
  • Collect() with SKIP_DF_COLLECTION set
  • JSON marshal/unmarshal round-trip: nil fields omitted, real fields (incl. 0) present
  • Backward-compat check: a plain, non-pointer legacy decoder still gets 0 for an absent field

go build ./..., go vet ./..., go test ./... -race all pass.

Collect() reported DiskTotal/DiskUsed/DiskFree as a plain 0 whenever the
Docker data-root statfs call failed (e.g. the path isn't visible in the
agent's mount namespace), because HostMetrics used bare uint64 fields with
no omitempty. On the wire, "disk stat failed" was indistinguishable from
"disk is full" or "0 bytes total".

Change the three fields to *uint64 with omitempty: a nil pointer omits the
field entirely on a stat failure, a non-nil pointer marshals the real value
-- including a legitimate 0 -- so success and failure no longer look alike.
This is wire-additive: an unchanged JSON consumer that still expects a
plain "number" gets undefined/absent instead of 0, which is a strictly more
useful signal and does not break decoding.

collectDisk() is split into a standalone, exported DiskUsage(path) that no
longer resolves the data root itself, so the statfs call is unit-testable
without a live Docker client. Collect() now resolves the data root and
calls DiskUsage() directly, applying the result via a small
applyDiskMetrics() helper that is also independently tested.
Copilot AI lite review requested due to automatic review settings August 11, 2026 13:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants