Skip to content

systemdSliceProperties: pmssSystemdCpuQuotaPercent returns null for every user (systemd time-unit output unparsed, wrong period formula) #958

Description

@MagnaCapax

Problem

pmssSystemdCpuQuotaPercent() (scripts/lib/systemdSliceProperties.php:120) returns null for every user slice that has a CPU quota, even though the quota is applied in the kernel. As a result userResourcesList.php reports cpu_quota_percent: null across the board while cpu_weight / io_weight are populated.

Reproduce on any Debian 12 host (cgroup v1 per ADR 0019):

systemctl set-property --runtime user-1000.slice CPUQuota=200%
systemctl show -p CPUQuotaPerSecUSec,CPUQuotaPeriodUSec,CPUQuota user-1000.slice
# CPUQuotaPerSecUSec=2s
# CPUQuotaPeriodUSec=infinity
# (no CPUQuota= line: it is a setting, not a show property)
cat /sys/fs/cgroup/cpu,cpuacct/user.slice/user-1000.slice/cpu.cfs_quota_us   # 200000 (period 100000)

Non-integer quotas print as e.g. 13.600000s.

Root cause

  1. Parsing. CPUQuota is a unit-file setting, not a property systemctl show emits, so the % branch never fires on live data. The fallback requires is_numeric() on both CPUQuotaPerSecUSec and CPUQuotaPeriodUSec. systemd prints time spans with units (2s, 500ms, 13.600000s, 1min 30s) and the period as infinity when unset, so is_numeric() is always false and the function returns null.
  2. Semantics. Even with parseable input the formula PerSecUSec / PeriodUSec * 100 is wrong. CPUQuotaPerSecUSec is CPU time allowed per wall-clock second, so percent = PerSecUSec_in_usec / 10000 (2s = 200%). CPUQuotaPeriodUSec is only the CFS scheduling period (infinity = kernel default) and is not part of the percentage. The characterization test testCpuQuotaPercentFallsBackToPeriodRatio pins this wrong behaviour (50000 / 100000 = 50) and must change with the fix.

Impact

  • userResourcesList (and anything reading cpu_quota_percent) always shows no CPU quota, so an operator or agent concludes "resource controls missing" when they are present.
  • scripts/lib/lighttpd/resourcePlan.php pmssExtractCpuQuotaPercent() never sees the real slice quota, so per-user php-cgi thread plans fall back to the policy default or threads * 85 instead of the user's actual quota.

Expected fix

  • Parse systemd time spans (units us/ms/s/min/h, compound forms like 1min 30s; infinity means no quota) into microseconds. One small helper in systemdSliceProperties.php.
  • percent = round(perSecUsec / 10000); null when infinity / unset / non-positive. Keep the direct CPUQuota=NNN% branch for callers that pass a setting value.
  • Update the characterization tests: 2s → 200, 13.600000s → 1360, 500ms → 50, 1min → 6000, infinity → null, [not set] → null, empty → null.

Behaviour change to review (not read-only)

The fix does not change how quotas are written, but it does change one consumer. pmssLighttpdResourcePlan() sizes the per-user php-cgi pool from this value (ceil(quota/100 * 4) threads, clamped to PMSS_PHP_THREADS_MIN..MAX = 3..48, 6 children per proc). Today every user falls to the policy default or max(200, threads * 85). After the fix a 200% user plans 12 threads (2 procs) and a 1360% user 48 (8 procs), taking effect on the next lighttpd config regeneration. That matches the function's evident intent (slice quota first), but it is a user-visible change: add a pmssLighttpdResourcePlan test fed real systemctl show output (CPUQuotaPerSecUSec=2s, CPUQuotaPeriodUSec=infinity) and note the change in the commit message.

(Two formulas walk into a cgroup; only one counts seconds.)


Tier-3 declared at filing: --why multi-file — GH#631 tier gate. Declared fix size: 70 lines vs 38-line body — size gate (operator directive 2026-07-29). Owner: scripts/lib/systemdSliceProperties.php in MagnaCapax/PMSS — owner gate.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agenticAgent-assisted work (rails, tooling, codex/agents)bugSomething isn't workingcomplete-verifyImplemented/closed; awaiting operator verification on real host

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions