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
- 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.
- 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.
Problem
pmssSystemdCpuQuotaPercent()(scripts/lib/systemdSliceProperties.php:120) returnsnullfor every user slice that has a CPU quota, even though the quota is applied in the kernel. As a resultuserResourcesList.phpreportscpu_quota_percent: nullacross the board whilecpu_weight/io_weightare populated.Reproduce on any Debian 12 host (cgroup v1 per ADR 0019):
Non-integer quotas print as e.g.
13.600000s.Root cause
CPUQuotais a unit-file setting, not a propertysystemctl showemits, so the%branch never fires on live data. The fallback requiresis_numeric()on bothCPUQuotaPerSecUSecandCPUQuotaPeriodUSec. systemd prints time spans with units (2s,500ms,13.600000s,1min 30s) and the period asinfinitywhen unset, sois_numeric()is always false and the function returnsnull.PerSecUSec / PeriodUSec * 100is wrong.CPUQuotaPerSecUSecis CPU time allowed per wall-clock second, so percent =PerSecUSec_in_usec / 10000(2s= 200%).CPUQuotaPeriodUSecis only the CFS scheduling period (infinity= kernel default) and is not part of the percentage. The characterization testtestCpuQuotaPercentFallsBackToPeriodRatiopins this wrong behaviour (50000 / 100000 = 50) and must change with the fix.Impact
userResourcesList(and anything readingcpu_quota_percent) always shows no CPU quota, so an operator or agent concludes "resource controls missing" when they are present.scripts/lib/lighttpd/resourcePlan.phppmssExtractCpuQuotaPercent()never sees the real slice quota, so per-user php-cgi thread plans fall back to the policy default orthreads * 85instead of the user's actual quota.Expected fix
1min 30s;infinitymeans no quota) into microseconds. One small helper insystemdSliceProperties.php.round(perSecUsec / 10000);nullwhen infinity / unset / non-positive. Keep the directCPUQuota=NNN%branch for callers that pass a setting value.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 toPMSS_PHP_THREADS_MIN..MAX= 3..48, 6 children per proc). Today every user falls to the policy default ormax(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 apmssLighttpdResourcePlantest fed realsystemctl showoutput (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:70lines vs 38-line body — size gate (operator directive 2026-07-29). Owner:scripts/lib/systemdSliceProperties.phpin MagnaCapax/PMSS — owner gate.