Skip to content

Add per-core CPU frequency & governor (D5)#55

Merged
LarsLaskowski merged 3 commits into
mainfrom
claude/issue-17-6tp3xe
Jul 16, 2026
Merged

Add per-core CPU frequency & governor (D5)#55
LarsLaskowski merged 3 commits into
mainfrom
claude/issue-17-6tp3xe

Conversation

@LarsLaskowski

@LarsLaskowski LarsLaskowski commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a new internal/collector/cpufreq.go collector that reads per-core
    CPU clock speed and active scaling governor from sysfs
    (/sys/devices/system/cpu/cpuN/cpufreq/{scaling_cur_freq,scaling_governor}).
  • Wires it into the collection loop as an additive cpu_frequency array on
    the /api/v1/metrics snapshot (docs/API.md updated with the new field
    and its notes).
  • cpufreq availability varies across Pi models/kernels, so the collector
    degrades to an empty reading (no error) when no driver is present, and
    silently skips any core that is offline or exposes only one of the two
    files, rather than failing the whole snapshot.
  • README feature list updated with a short bullet for the new metric.

Related Issue

Closes #17

Verification

  • go build ./..., go vet ./..., go test ./..., and golangci-lint run
    all pass locally.
  • Unit tests for cpufreq.go use synthetic sysfs fixture directories
    (t.TempDir()), covering: normal per-core parsing, sort-by-core-index,
    no-driver-present (empty result, no error), a core missing one of the two
    files (skipped), and malformed scaling_cur_freq content.
  • Not verified against real Raspberry Pi hardware — the dev/CI environment
    used for this change has no cpufreq sysfs tree, so the collector was
    exercised purely through the fixture-based unit tests plus the existing
    collector_test.go integration tests (which tolerate an empty
    cpu_frequency reading, matching how other hardware-optional metrics are
    already tested). Hardware verification on an actual Pi is still
    recommended before relying on this in production.

Checklist

  • Tests added/updated for the change (go test ./... passes locally)
  • go vet ./... and golangci-lint run are clean
  • Documentation updated if this changes the REST API (docs/API.md),
    configuration (README.md, packaging/pimonitor.example.yaml), or
    installation/packaging (packaging/install.sh, systemd units)
  • No breaking change to /api/v1/... response shapes, or a new API
    version was introduced instead

Reads /sys/devices/system/cpu/cpuN/cpufreq/{scaling_cur_freq,scaling_governor}
per core and adds them as an additive cpu_frequency array in the snapshot.
Availability of cpufreq varies across Pi models and kernels, so the
collector degrades to an empty reading (rather than an error) when no
cpufreq driver is present, and silently skips individual cores that are
offline or missing one of the two sysfs files.

@LarsLaskowski LarsLaskowski left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewed against the project's Go/security/API conventions (.claude/skills/review-pr checklist). Everything checks out — this would be an approval, but GitHub doesn't allow approving one's own PR, so submitting as a comment.

Verification on the PR branch (a3545f4): go build ./..., go vet ./..., go test ./..., and golangci-lint run all pass (0 issues).

Checklist results:

  • Error handling: Collect() degrades to an empty reading when no cpufreq sysfs tree exists and skips cores that are offline or missing one of the two files, instead of failing the snapshot — matches how other hardware-optional metrics (CPU temp, vcgencmd) behave. The collection loop logs a warning on error rather than aborting the tick.
  • Resource leaks: only os.ReadFile is used; nothing to leak. No new goroutines/tickers.
  • Parsing robustness: malformed scaling_cur_freq, missing files, no-driver, and out-of-order core dirs are all covered by fixture-based unit tests (t.TempDir() synthetic sysfs), per the project convention that collectors are testable without real /proc//sys.
  • Command execution: none — pure sysfs reads.
  • API stability: cpu_frequency is a new field with omitempty, purely additive to the /api/v1/metrics shape — no breaking change, docs/API.md updated and consistent with the actual JSON (omitted when empty, sorted by core index).
  • Concurrency: a fresh slice is built each tick and assigned to c.latest.CPUFrequency under c.mu, matching the existing Disks/Network pattern; Snapshot() readers are safe.
  • Dependencies/language: stdlib only, all English.

One deliberate deviation from issue #17 worth noting: the issue suggested reusing CoreCount(), but the PR discovers cores by globbing cpu[0-9]*/cpufreq directly. That's the better choice here — it naturally handles offline cores and drivers that only expose cpufreq for some cores, and the glob correctly excludes the non-core /sys/devices/system/cpu/cpufreq policy directory.

One non-blocking doc nit inline on the README feature bullet.

Comment thread README.md Outdated
claude and others added 2 commits July 16, 2026 17:54
Addresses review feedback: the surrounding bullets describe dashboard
behavior, but this metric is not yet rendered in the UI.
@LarsLaskowski
LarsLaskowski merged commit c3eac76 into main Jul 16, 2026
3 checks passed
@LarsLaskowski
LarsLaskowski deleted the claude/issue-17-6tp3xe branch July 16, 2026 17:58
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.

D5: Per-core CPU frequency & governor

2 participants