Skip to content

fix: JSON/CPU correctness bugs + portability, validation, and accuracy improvements#1

Merged
dan-n94 merged 12 commits into
mainfrom
fix/portability-and-robustness
Jun 24, 2026
Merged

fix: JSON/CPU correctness bugs + portability, validation, and accuracy improvements#1
dan-n94 merged 12 commits into
mainfrom
fix/portability-and-robustness

Conversation

@dan-n94

@dan-n94 dan-n94 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes two output-correctness bugs and hardens the script for minimal
container environments (BusyBox/Alpine), plus input validation and a more
accurate memory figure.

Bug fixes

  • JSON validity: dropped the broken sed -i cleanup and switched to
    leading-comma separators, so output is valid for any combination of
    --no-cpu/--no-mem/--no-disk (previously --no-disk --output json
    emitted a trailing comma → invalid JSON).
  • CPU in --once: prime an initial sample so a single-shot run reports
    real usage instead of always 0.00.
  • Portable df: use df -P -k instead of GNU-only df -PB1, so disk
    stats work on BusyBox/Alpine (previously reported N/A there).
  • Portable date: fall back to whole seconds when date lacks %N
    (BusyBox emits a literal N with a zero exit, so the old fallback never
    fired and corrupted the CPU delta).
  • Memory working set: subtract inactive_file (reclaimable page cache)
    from memory.current/usage_in_bytes, matching kubectl top / cAdvisor.

Features

  • -p can now be repeated and/or comma-separated; all paths are merged.
  • --output rejects values other than table/json instead of silently
    falling back to table.
  • Missing flag values and non-numeric/out-of-range arguments are now
    validated with clear errors.

Refactor / chore

  • Extracted a single for_each_path helper (deduped the split loops).
  • Renamed misleading DISK_AVAILDISK_USED; fixed a stale comment.
  • Added .gitignore for .DS_Store.

dan-n94 and others added 11 commits June 24, 2026 10:14
JSON output appended a trailing comma after each section and relied on a
broken `sed -i` branch (no file arg, and the comma was already flushed to
stdout) to clean up when --no-disk was set. Result: invalid JSON whenever
a trailing section was disabled.

Switch to leading-comma separators between sections and drop the dead sed
branch, so output is valid for any combination of --no-cpu/--no-mem/--no-disk.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CPU usage is computed as a delta between two samples. With --once the loop
ran a single iteration, so there was never a previous sample and CPU always
reported 0.00. Prime an initial sample and wait one interval before the
single measurement when running once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously each -p overwrote the prior value, so `-p /a -p /b` monitored
only /b. Append instead, so repeated flags and comma-separated lists can be
mixed freely (`-p /a -p /b,/c` → /a,/b,/c). The comma-split consumers are
unchanged. Update --help, the header usage comment, and the README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The comma-split loop over MON_PATHS was duplicated in print_table and
print_json. Extract a single for_each_path helper that splits the paths and
invokes a per-mode callback (disk_row_table / disk_obj_json), so the
splitting logic lives in one place. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`df -PB1` relied on the block-size flag -B, a GNU coreutils extension that
BusyBox df (Alpine and most minimal container images) does not support, so
disk stats reported N/A there despite the README claiming Alpine support.

Use `df -P -k` (POSIX, 1K blocks) and convert KiB to bytes in awk, keeping
all downstream math in bytes. Verified disk numbers now populate on a
non-GNU df.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Value-taking flags did an unconditional `shift 2`, so passing one as the
last argument (e.g. `cgstats.sh -i`) aborted with a raw "can't shift"
error. Non-numeric values (`-i abc`) silently propagated into sleep/awk and
failed opaquely.

Add is_uint/is_num/is_pct helpers and a die() reporter, then validate value
presence and type per flag: -i and --cpu-limit accept numbers (decimals
allowed), --mem-limit an integer, and the warn/crit thresholds 0-100.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
--output previously accepted any string and silently fell back to table
output for anything other than "json", so a typo like `--output jsno` gave
table output with no warning. Validate against table|json at parse time and
error out otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`date +%s.%N` falls back to `date +%s` only when the first command exits
non-zero. BusyBox date instead emits a literal "N" (e.g. 1700000000.N) with
a zero exit, so the fallback never fired and the timestamp fed garbage into
the CPU delta math. Detect a non-numeric timestamp and re-read whole seconds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DISK_AVAIL actually held the used-bytes value (df column 3), which was
misleading; rename to DISK_USED across disk_calc and both render callbacks.
Also correct the stale `# json` comment on OUTPUT_FORMAT. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
memory.current (v2) and memory.usage_in_bytes (v1) include the page cache,
so reported memory and the used% could read far above what processes
actually hold. Subtract inactive_file (total_inactive_file on v1) from
memory.stat to approximate the working set, matching kubectl top / cAdvisor.
Guarded so a missing or oversized value leaves the raw figure untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dan-n94 dan-n94 requested review from antholzer and chjdev June 24, 2026 08:27
Comment thread cgstats.sh Outdated
@antholzer

Copy link
Copy Markdown

Only help docs can be improved otherwise looks good. I tested it locally inside alpine- and archlinux containers and it worked fine.

@dan-n94

dan-n94 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Only help docs can be improved otherwise looks good. I tested it locally inside alpine- and archlinux containers and it worked fine.

good point, updated help message and notes in the beginning of the file.
could you please check again and approve?

@dan-n94 dan-n94 requested a review from antholzer June 24, 2026 09:21
Comment thread cgstats.sh
Usage: $0 [-i SECONDS] [-p PATHS]
cgstats.sh — live CPU/MEM/DISK via cgroups (POSIX sh)

Reports CPU, memory, and disk usage for the current cgroup (v1 or v2) as a

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
Reports CPU, memory, and disk usage for the current cgroup (v1 or v2) as a
Continuously reports CPU, memory, and disk usage for the current cgroup (v1 or v2) as a

@dan-n94 dan-n94 merged commit 97fc0b6 into main Jun 24, 2026
1 check passed
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