Skip to content

fix(unraid): report correct CPU utilization on hyper-threaded systems - #6250

Open
junkerderprovinz wants to merge 2 commits into
homarr-labs:devfrom
junkerderprovinz:fix/unraid-cpu-utilization
Open

fix(unraid): report correct CPU utilization on hyper-threaded systems#6250
junkerderprovinz wants to merge 2 commits into
homarr-labs:devfrom
junkerderprovinz:fix/unraid-cpu-utilization

Conversation

@junkerderprovinz

@junkerderprovinz junkerderprovinz commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Closes #6082

Problem

The Unraid integration reports CPU load that is roughly double the real value and can exceed 100% — a reporter with a real ~43% load saw ~93% in Homarr (and occasionally 105–108%).

Cause

const cpuUtilization = systemInfo.metrics.cpu.cpus.reduce((acc, val) => acc + val.percentTotal, 0);
const cpuCount = systemInfo.info.cpu.cores;
...
cpuUtilization: cpuUtilization / cpuCount,

metrics.cpu.cpus[] has one entry per logical CPU (thread), each percentTotal being 0–100. The sum is divided by info.cpu.cores (physical cores). On a hyper-threaded system there are ~2× as many threads as cores (e.g. 24 threads / 12 cores), so the average is divided by the wrong (smaller) number and comes out inflated.

Fix

The GraphQL query already fetches metrics.cpu.percentTotal — the API's own overall CPU utilization (0–100), which is exactly what the Unraid dashboard displays. Use it directly instead of recomputing from the per-thread values:

cpuUtilization: systemInfo.metrics.cpu.percentTotal,

For the reporter's machine this yields ~43%, matching Unraid.

Checklist

  • Builds without errors (tsc --noEmit clean on @homarr/integrations)
  • oxfmt formatted, oxlint clean
  • Targets dev; conventional commit; no shorthand variable names

No unit test added: the integration test suites run only against a real instance (they skipIf without credentials), and after this change the value is a direct pass-through of the API's own aggregate, so there is no computation left to unit-test. Happy to add a mocked test if you'd prefer.

Summary by CodeRabbit

  • Bug Fixes
    • Corrected Unraid CPU utilization reporting to use the overall system CPU metric, improving accuracy on hyper-threaded hardware.
    • Updated Docker CPU usage calculation to reflect machine-wide load on a 0–100% scale, with safer handling of missing or non-increasing CPU deltas.
  • Tests
    • Revised and expanded CPU usage tests to match the updated percentage calculations and verify results stay within expected bounds.

@dokploy-homarr-labs

Copy link
Copy Markdown

🚨 Preview Deployment Blocked - Security Protection

Your pull request was blocked from triggering preview deployments

Why was this blocked?

  • User: junkerderprovinz
  • Repository: homarr
  • Permission Level: read
  • Required Level: write, maintain, or admin

How to resolve this:

Option 1: Get Collaborator Access (Recommended)
Ask a repository maintainer to invite you as a collaborator with write permissions or higher.

Option 2: Request Permission Override
Ask a repository administrator to disable security validation for this specific application if appropriate.

For Repository Administrators:

To disable this security check (⚠️ not recommended for public repositories):
Enter to preview settings and disable the security check.


This security measure protects against malicious code execution in preview deployments. Only trusted collaborators should have the ability to trigger deployments.

🛡️ Learn more about this security feature

This protection prevents unauthorized users from:

  • Executing malicious code on the deployment server
  • Accessing environment variables and secrets
  • Potentially compromising the infrastructure

Preview deployments are powerful but require trust. Only users with repository write access can trigger them.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d0ddf993-7646-4deb-8044-e7778a8b1911

📥 Commits

Reviewing files that changed from the base of the PR and between 1dc4fa0 and 9dcb9f4.

📒 Files selected for processing (3)
  • packages/integrations/src/unraid/unraid-integration.ts
  • packages/request-handler/src/docker.ts
  • packages/request-handler/src/test/docker.spec.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/integrations/src/unraid/unraid-integration.ts
  • packages/request-handler/src/docker.ts
  • packages/request-handler/src/test/docker.spec.ts

📝 Walkthrough

Walkthrough

CPU utilization reporting now uses whole-system percentage calculations. Unraid reads its overall CPU metric directly, while Docker removes per-core scaling and updates validation cases for the revised formula.

Changes

CPU Utilization Calculations

Layer / File(s) Summary
Use Unraid’s overall CPU metric
packages/integrations/src/unraid/unraid-integration.ts
Returns systemInfo.metrics.cpu.percentTotal directly instead of averaging per-CPU values, avoiding hyper-threading double-counting.
Normalize Docker CPU usage
packages/request-handler/src/docker.ts, packages/request-handler/src/test/docker.spec.ts
Reports Docker CPU usage on a whole-machine 0–100% scale, handles invalid deltas, and updates or adds coverage for the revised results.

Possibly related PRs

Suggested reviewers: manuel-rw

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing Unraid CPU utilization reporting on hyper-threaded systems.
Linked Issues check ✅ Passed The PR directly fixes the Unraid CPU calculation issue reported in #6082 and aligns with the expected overall usage reporting behavior.
Out of Scope Changes check ✅ Passed The Docker CPU normalization and updated tests are related to the same CPU-usage reporting fix and do not appear unrelated.
Yagni / Over-Engineering ✅ Passed The PR stays direct: one-line Unraid pass-through, one existing Docker helper revised in place, no new abstractions or deps, and only focused regression tests were added.
Docs Are Up To Date ✅ Passed No widget/integration was added; only existing code/tests changed, and Unraid/Docker docs already exist with no docs-path diffs.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
packages/integrations/src/unraid/unraid-integration.ts (1)

87-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove now-unused cpus { percentTotal } sub-query.

Since cpuUtilization no longer averages per-thread values, the per-CPU cpus { percentTotal } field fetched here is unused dead data in the response.

♻️ Proposed fix
           cpu {
             percentTotal
-            cpus {
-              percentTotal
-            }
           },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/integrations/src/unraid/unraid-integration.ts` around lines 87 - 92,
The Unraid CPU query still fetches unused per-CPU data, so remove the now-dead
cpus percentTotal sub-selection from the query built in unraid-integration.ts.
Update the GraphQL selection around the cpu block so only the top-level cpu
percentTotal is requested, and keep the rest of the response shape unchanged in
the Unraid integration logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/integrations/src/unraid/unraid-integration.ts`:
- Around line 87-92: The Unraid CPU query still fetches unused per-CPU data, so
remove the now-dead cpus percentTotal sub-selection from the query built in
unraid-integration.ts. Update the GraphQL selection around the cpu block so only
the top-level cpu percentTotal is requested, and keep the rest of the response
shape unchanged in the Unraid integration logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a7015b66-fa72-4e1d-80e8-87dac244abd3

📥 Commits

Reviewing files that changed from the base of the PR and between 260b167 and 4b4e692.

📒 Files selected for processing (1)
  • packages/integrations/src/unraid/unraid-integration.ts

@ajnart

ajnart commented Jul 12, 2026

Copy link
Copy Markdown
Member

Somewhat related (although I understand the issue you're solving here), your previous PR with Docker (#6249) made it so that the CPU can go over 100%. Users are complaining that they are not expecting this kind of reading going over 100%.
In Portainer, the CPU usage by a container logic is the same as the current Homarr, but in Beszel for example, it shows the total CPU usage (not on a per-core basis)

I think it might be beneficial to define and think about how we want to show CPU usage across the whole homarr to normalize it. I am of the opinion that seeing the strain on the total system 3.5% makes more sense that a value like 134%
What's your opinion on this @junkerderprovinz ?

@junkerderprovinz

Copy link
Copy Markdown
Contributor Author

@ajnart Really good point, and I agree this is worth settling consistently across homarr rather than per-widget.

The two readings just answer different questions: per-core (can go over 100%) is "how hard is this container working the cores it has", like docker stats or Portainer, so 134% means ~1.34 cores busy, while total-system % (0-100%) is "how much of the whole machine is it using", like Beszel, which is more intuitive at a glance and never surprises anyone.

For a dashboard I think you're right that total-system % is the better default: 3.5% is immediately meaningful and nobody has to wonder why a number went past 100%. My suggestion would be to normalize to total-system % everywhere by default (divide by the core count), and keep the per-core detail as an option or a tooltip if we still want it.

This PR is really just making the per-core number correct on hyper-threaded Unraid hosts in the first place, so it slots in under either choice. I'm happy to adapt it (and revisit #6249) to emit the normalized total-system % so everything lines up. Just say the word and I'll push the change. Thanks for thinking this through with me!

@ajnart

ajnart commented Jul 13, 2026

Copy link
Copy Markdown
Member

@junkerderprovinz feel free to push the change here as well, then we'll merge both inside of this PR

@junkerderprovinz
junkerderprovinz force-pushed the fix/unraid-cpu-utilization branch from 4b4e692 to 1dc4fa0 Compare July 13, 2026 18:08
@junkerderprovinz
junkerderprovinz requested a review from a team as a code owner July 13, 2026 18:08
@junkerderprovinz

Copy link
Copy Markdown
Contributor Author

@ajnart Done! I rebased onto main and pushed the Docker normalization into this PR (1dc4fa0).

The change itself is tiny: system_cpu_usage already accumulates across all cores, so dropping the * online_cpus from the delta ratio directly yields the share of the whole machine. Container CPU is now 0-100% total-system, matching what the Unraid fix in this PR reports, so every CPU reading in homarr sits on the same intuitive scale - no more 134%.

Tests updated accordingly, plus a new case asserting that multiple fully busy cores can never push the reading past 100% (18/18 passing locally). Thanks for pushing for consistency here, I think the dashboard is better for it!

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
packages/request-handler/src/test/docker.spec.ts (1)

79-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a boundary test for exactly 100%.

The test title claims "should never exceed 100%" but only asserts 75% (3 of 4 cores busy). Adding a case where all cores are fully busy (cpuDelta == systemDelta) would verify the function returns exactly 100 at the boundary, strengthening the guarantee.

💡 Suggested additional test
    test("should report exactly 100% when all cores are fully busy", () => {
      const stats = createStats({
        cpu_stats: { online_cpus: 4, cpu_usage: { total_usage: 10000 }, system_cpu_usage: 10000 },
      });
      // All 4 cores busy: (10000 / 10000) * 100 = 100
      expect(calculateCpuUsage(stats)).toBe(100);
    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/request-handler/src/test/docker.spec.ts` around lines 79 - 86, Add a
boundary test alongside the existing calculateCpuUsage coverage in
docker.spec.ts for cpuDelta equal to systemDelta, using fully busy cores and
asserting calculateCpuUsage returns exactly 100. Keep the existing 75%
multi-core test unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/request-handler/src/test/docker.spec.ts`:
- Around line 79-86: Add a boundary test alongside the existing
calculateCpuUsage coverage in docker.spec.ts for cpuDelta equal to systemDelta,
using fully busy cores and asserting calculateCpuUsage returns exactly 100. Keep
the existing 75% multi-core test unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b83551ab-0fb9-466a-94dd-9392d582872e

📥 Commits

Reviewing files that changed from the base of the PR and between 4b4e692 and 1dc4fa0.

📒 Files selected for processing (3)
  • packages/integrations/src/unraid/unraid-integration.ts
  • packages/request-handler/src/docker.ts
  • packages/request-handler/src/test/docker.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/integrations/src/unraid/unraid-integration.ts

The Unraid integration summed the per-thread CPU percentages and divided
the total by the number of physical cores. On hyper-threaded systems there
are more logical CPUs (threads) than physical cores, so the result was
inflated — roughly doubled — and could even exceed 100% (e.g. a real 43%
load reported as ~93%).

Use the overall CPU utilization the Unraid API already reports
(`metrics.cpu.percentTotal`, 0-100), which is the value Unraid itself
displays, instead of recomputing it from the per-thread percentages.

Closes homarr-labs#6082
Drop the docker-stats per-core convention (* online_cpus), which produced
readings like 134% and surprised users. system_cpu_usage accumulates across
all cores, so the plain delta ratio is the share of the whole machine - the
same 0-100% scale the Unraid integration now reports, keeping CPU readings
consistent across homarr.
@junkerderprovinz
junkerderprovinz force-pushed the fix/unraid-cpu-utilization branch from 1dc4fa0 to 9dcb9f4 Compare July 16, 2026 18:01
@junkerderprovinz

Copy link
Copy Markdown
Contributor Author

Rebased onto the latest dev — the calculateCpuUsage conflict is resolved (kept the instantaneous delta-vs-precpu_stats calc; the existing no-precpu tests still pass because the deltas fall back to the cumulative totals). MERGEABLE again and green from my side. Ready whenever you are, @ajnart.

@junkerderprovinz

Copy link
Copy Markdown
Contributor Author

Hi @ajnart — this is rebased onto the latest dev, the calculateCpuUsage conflict is resolved, and checks are green. Ready whenever you'd like to merge both fixes here as you suggested. Thanks!

@junkerderprovinz

Copy link
Copy Markdown
Contributor Author

Hi @ajnart, following up after a couple of weeks. This is still rebased onto dev, conflict-free and green, and the Docker over-100% CPU normalization is folded in as you asked. Happy to adjust anything if it helps, otherwise it's ready whenever you have a moment. Thanks!

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.

bug: Unraid integration CPU calculation is incorrectly reporting CPU load

2 participants