Skip to content

fix(worker): discard impossible cgroup v2 cpu samples - #2436

Open
rosetta-livekit-bot[bot] wants to merge 2 commits into
mainfrom
fix/cgroup-v2-impossible-cpu-samples
Open

fix(worker): discard impossible cgroup v2 cpu samples#2436
rosetta-livekit-bot[bot] wants to merge 2 commits into
mainfrom
fix/cgroup-v2-impossible-cpu-samples

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Ports livekit/agents#7113.

Measures elapsed time around cgroup v2 CPU reads and discards deltas that are negative or exceed the physical host CPU ceiling. Discarded reads retain the last valid load so torn hypervisor counters cannot make an idle worker appear full or a busy worker appear idle.

Adds the source regression coverage for measured elapsed time, negative and over-ceiling deltas, first-sample behavior, the reported torn-read sequence, ceiling behavior, and quota bursts. Includes a patch changeset for @livekit/agents.

Source diff coverage

Source diff coverage

  • Adapted: livekit-agents/livekit/agents/utils/hw/cpu.py -> agents/src/cpu.ts. The existing TypeScript cgroup v2 monitor now uses Node’s monotonic performance.now(), os.cpus().length for the physical host ceiling, and the target Pino logger. The source behavior is otherwise preserved: impossible deltas warn and return the last valid sample, while accepted quota bursts clamp to full load.
  • Adapted: tests/test_cpu_monitor.py -> agents/src/cpu.test.ts. All source-added tests are ported with Vitest fake timers and mocked monotonic time in milliseconds. The target worker reports samples directly and has no production MovingAverage counterpart, so the reporter-pattern test computes the same five-sample average locally without introducing unrelated runtime averaging behavior.
  • Not applicable: none.

Validation

  • pnpm test agents (151 files passed; 2,498 tests passed, 5 skipped)
  • pnpm build (39/39 workspace tasks passed)
  • pnpm lint (39/39 workspace tasks passed; existing warning-only findings remain outside changed files)
  • pnpm --filter @livekit/agents typecheck
  • pnpm exec prettier --check agents/src/cpu.ts agents/src/cpu.test.ts .changeset/tidy-cpus-rest.md

Ported from livekit/agents#7113

Original PR description

Problem: On some hypervisors the root cgroup usage_usec counter returns a torn per-cpu sum, and CGroupV2CPUMonitor.cpu_percent turns that one read into a 1.0 or a large negative sample. Four such samples in the five-slot load average mark an idle worker FULL for 2.5 s, and a call that lands in that window gets no server.

Fix: The monitor measures elapsed time around the two reads and discards a delta that is negative or above elapsed x host cpus, since no host can produce either. A discarded sample returns the last good value instead of a fresh number, so the average is unchanged on idle and busy hosts alike.

Fixes #7102. Supersedes #7103.

Context for reviewing and coding agents

How to see it

tests/test_cpu_monitor.py replays the six raw (start, end) counter pairs from the issue log through a scripted _read_cpu_usage, a no-op time.sleep and a scripted time.monotonic on an 8-cpu host. test_reporter_pattern_stays_below_threshold pushes the reporter's sample sequence through utils.MovingAverage(5) and asserts the average stays at the idle fraction on every step; on main the same sequence averages 0.80, the value in the report.

Blast radius

The only caller is _DefaultLoadCalc in worker.py:98, which feeds cpu_percent(interval=0.5) into utils.MovingAverage(5) at worker.py:88. cpu_count, _read_cpu_max, _read_cpu_usage, CGroupV1CPUMonitor and DefaultCPUMonitor are unchanged, so the num_idle_processes defaults at worker.py:208 and worker.py:299 keep their values.

Why the ceiling uses host cpus and not the quota

cpu.max is an average limit and a container can burst above it for one interval, so a quota-based ceiling would discard real samples. psutil.cpu_count() is the physical bound; the quota still divides the accepted sample, so a burst clamps to 1.0 rather than being thrown away.

Alternatives rejected

Clamping to [0, 1] removes the negative case only; an inflated delta still clamps to 1.0 and four of them still average to 0.80. Falling back to psutil.cpu_percent is not safer, because /proc/stat is summed by the kernel from the same per-cpu kcpustat counters as the root cgroup's cpu.stat. Returning 0.0 on a discard, as #7103 does, is the same defect mirrored: on a saturated worker each glitch drops the average by 0.2 and the worker keeps accepting jobs. #7103 also reads the process's own cgroup and walks cpu.max up the ancestor chain, which changes what load means for a systemd-managed worker and belongs in its own discussion.

Where the numbers come from

The reporter's log shows deltas of +5.9e8 and -2.7e9 microseconds over 0.5 s on 8 cpus, against a physical ceiling of 4e6. Four 1.0 samples in a five-slot average give 0.8011 (4.0054 / 5), which matches the load value in the reported log line.

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner September 8, 2026 02:23
@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 42c4fe0

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

This PR includes changesets to release 38 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen 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

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from longcw September 8, 2026 02:24

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

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.

0 participants