Skip to content

fix: Use cgroup-aware CPU monitoring inside Docker containers - #1099

Merged
toubatbrian merged 4 commits into
mainfrom
brian/fix-cpu-counts
Mar 7, 2026
Merged

fix: Use cgroup-aware CPU monitoring inside Docker containers#1099
toubatbrian merged 4 commits into
mainfrom
brian/fix-cpu-counts

Conversation

@toubatbrian

@toubatbrian toubatbrian commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • defaultCpuLoad in worker.ts used os.cpus() which returns host CPU counters inside containers, producing inflated load values that caused the LiveKit server to intermittently reject dispatches with "no servers available"

  • Replace with a cgroup-aware CPU monitor that auto-detects cgroup v2/v1 and reads usage directly from /sys/fs/cgroup/, falling back to os.cpus() on bare metal

  • Supports NUM_CPUS environment variable override for cases where cgroup quotas don't reflect actual allocation

How it works

Environment Monitor selected CPU count source
Docker / Kubernetes (cgroup v2) CGroupV2CpuMonitor /sys/fs/cgroup/cpu.max quota/period
Older Docker / cgroup v1 CGroupV1CpuMonitor /sys/fs/cgroup/cpu/cpu.cfs_quota_us
macOS / bare metal / Windows DefaultCpuMonitor os.cpus().length

Verified in Docker

os.cpus().length (host value): 14
Selected monitor: CGroupV2CpuMonitor
cpuCount(): 2 # from cgroup quota 200000/100000
cpuPercent(): 0.001143 # correct idle load
Load in [0, 1]: PASS

defaultCpuLoad used os.cpus() which returns host CPU counters inside
containers, causing inflated load values that made the LiveKit server
reject job dispatches with "no servers available".

Replace with a cgroup-aware monitor (ported from the Python agents
framework) that reads usage from /sys/fs/cgroup/ when available,
falling back to os.cpus() on bare metal.
@changeset-bot

changeset-bot Bot commented Mar 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: aa4fe7a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@toubatbrian
toubatbrian requested a review from a team March 6, 2026 01:00
devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@toubatbrian

Copy link
Copy Markdown
Contributor Author

Resolves #1082

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment thread agents/src/cpu.ts
#readCpuMax(): [string, number] {
try {
const data = readFileSync('/sys/fs/cgroup/cpu.max', 'utf-8').trim().split(/\s+/);
const quota = data[0] ?? 'max';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Nullish coalescing (??) doesn't guard against empty-string quota, causing cpuCount() to return NaN

In CGroupV2CpuMonitor.#readCpuMax() at agents/src/cpu.ts:85, the expression data[0] ?? 'max' uses the nullish coalescing operator, which only falls back for null/undefined, not for empty strings. If /sys/fs/cgroup/cpu.max contains unexpected content (e.g., empty or whitespace-only), data[0] will be "", which passes the ?? check unchanged. Then at agents/src/cpu.ts:59, parseInt("") returns NaN, so cpuCount() returns NaN. This propagates through cpuPercent() where usageSeconds / (intervalSeconds * NaN) = NaN, and Math.max(Math.min(NaN, 1), 0) still evaluates to NaN. The NaN load value is then sent to the server in the updateWorker message (agents/src/worker.ts:655), and the NaN >= threshold check (agents/src/worker.ts:636) always evaluates to false, so the worker would be marked as available despite having an unknown load.

Suggested change
const quota = data[0] ?? 'max';
const quota = data[0] || 'max';
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@toubatbrian
toubatbrian merged commit 22fc4e6 into main Mar 7, 2026
8 checks passed
@toubatbrian
toubatbrian deleted the brian/fix-cpu-counts branch March 7, 2026 01:14
This was referenced Mar 7, 2026
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