Repository navigation
fix: stop reporting unknown server values as if they were measurements - #1120
Merged
Merged
Conversation
Member
Author
karlitschek
force-pushed
the
fix/unknown-values
branch
from
September 9, 2026 15:11
93693d2 to
9a71341
Compare
kesselb
reviewed
Sep 9, 2026
| const memTotalText = computed(() => formatMegabytes(props.memTotal as number)) | ||
| const memUsed = computed(() => formatMegabytes((props.memTotal as number) - (props.memFree as number))) | ||
| const swapUsed = computed(() => formatMegabytes((props.swapTotal as number) - (props.swapFree as number))) | ||
| const memTotalText = computed(() => formatMegabytesprops.memTotal) |
Collaborator
There was a problem hiding this comment.
Suggested change
| const memTotalText = computed(() => formatMegabytesprops.memTotal) | |
| const memTotalText = computed(() => formatMegabytes(props.memTotal) |
There was a problem hiding this comment.
Suggested change
| const memTotalText = computed(() => formatMegabytesprops.memTotal) | |
| const memTotalText = computed(() => formatMegabytes(props.memTotal)) |
Every "not available" figure in this app is -1, and several paths did
arithmetic on that sentinel or formatted it as though it were a reading:
- SystemStatistics scaled it to kB, so the OCS API answered
mem_total: -1024 to external monitoring tools
- formatBytes() took the absolute value, so PHP's memory_limit = -1
("unlimited") rendered as "1.0 B"
- DatabaseStatistics returned the string 'N/A' for a field typed and
formatted as a number, which reached the page as "NaN B"
- MemoryChartSection and ResourceOverviewSection guarded against an 'N/A'
string the backend never sends, so -1 charted as a real series on a
degenerate 0-to-0 axis
- the OS backends claimed a single core when the count was unknown, and
that 1 became the divisor for the load average: an eight-core host under
load read as "800 %"
Unknown values are now -1 throughout and render as "Unknown", or are left
out where a percentage cannot be computed. PHP's own "unlimited" sentinels
(memory_limit -1, max_execution_time 0) are labelled as such rather than
formatted as sizes. The load average survives a missing core count: it
comes from sys_getloadavg() and is real even when the count is not.
Read CPU, memory and uptime where /proc is hidden
Debian's and Ubuntu's apache2 unit ships ProcSubset=pid and
ProtectProc=invisible, so the web server sees only its own processes in
/proc. On those hosts -- a large share of installs -- this app has never
been able to report CPU, memory or uptime at all, and now reports honestly
that it cannot, which is worse to look at than it needs to be.
Commands still run there, so the Linux backend now falls back:
- threads: nproc, which uses sched_getaffinity rather than /proc
- memory: the per-NUMA-node meminfo files under /sys, which ProcSubset
does not cover, before free(1) -- free reads /proc/meminfo itself and is
bound to fail wherever that file is hidden, so trying /sys first settles
which situation we are in without paying for a fork
- uptime: a process that has only just started carries the system uptime
in its own /proc/<pid>/stat, and a process may always read its own entry
aarch64 and s390x are handled too: they list their processors but carry no
"model name" line, so the count is now taken separately from the name.
Cut the cost of the two-second poll
A poll spends about 1.3 ms collecting data inside a request that costs
hundreds of milliseconds, so the fix is fewer and cheaper requests, not
faster collection:
- useLiveData stops polling while the tab is hidden and polls immediately
on return, so a page left open in the background no longer fetches
numbers nobody can see
- getTime() no longer forks date(1) for something PHP can answer; the
system zone is read from /etc/localtime because Nextcloud runs on UTC
- LiveData no longer calls getCPU() -- which parses /proc/cpuinfo, or forks
nproc -- only to reach sys_getloadavg(), which uses none of it
- the sampled uptime is cached as a boot time and re-measured once a
minute rather than once per poll
Together these leave the poll forking less than it did before: hosts with a
readable /proc now fork nothing at all, where every host used to fork date.
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
Assisted-by: Claude Code:claude-opus-5
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
kesselb
force-pushed
the
fix/unknown-values
branch
from
September 9, 2026 17:23
9a71341 to
987fd83
Compare
kesselb
enabled auto-merge
September 9, 2026 17:23
kesselb
approved these changes
Sep 9, 2026
Collaborator
|
/backport to stable35 please |
|
The backport to # Switch to the target branch and update it
git checkout stable35
git pull origin stable35
# Create the new backport branch
git checkout -b backport/1120/stable35
# Cherry pick the change from the commit sha1 of the change against the default branch
# This might cause conflicts, resolve them
git cherry-pick 987fd83e
# Push the cherry pick commit to the remote repository and open a pull request
git push origin backport/1120/stable35Error: Failed to check for changes with origin/stable35: No changes found in backport branch Learn more about backports at https://docs.nextcloud.com/server/stable/go.php?to=developer-backports. |
Collaborator
|
/backport to stable35 please |
|
The backport to # Switch to the target branch and update it
git checkout stable35
git pull origin stable35
# Create the new backport branch
git checkout -b backport/1120/stable35
# Cherry pick the change from the commit sha1 of the change against the default branch
# This might cause conflicts, resolve them
git cherry-pick 987fd83e
# Push the cherry pick commit to the remote repository and open a pull request
git push origin backport/1120/stable35Error: Failed to check for changes with origin/stable35: No changes found in backport branch Learn more about backports at https://docs.nextcloud.com/server/stable/go.php?to=developer-backports. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
1. Unknown values were rendered as measurements
Every "not available" figure in this app is
-1, and several paths did arithmetic on that sentinel or formatted it as though it were a reading:SystemStatisticsscaled-1to kBmem_total: -1024to monitoring toolsformatBytes()tookMath.abs()memory_limit = -1("unlimited") rendered as "1.0 B"DatabaseStatisticsreturned the string'N/A'for a numeric field'N/A'string the backend never sends-1charted as a real series on a degenerate 0-to-0 axisUnknown values are now
-1throughout and render as "Unknown", or are left out where a percentage cannot be computed. PHP's own "unlimited" sentinels (memory_limit -1,max_execution_time 0) are labelled as such rather than formatted as sizes.The load average deliberately survives a missing core count — it comes from
sys_getloadavg()and is real even when the count is not, so it is still shown, without the percentage it cannot support.2. …and on a lot of hosts there was nothing to report in the first place
Debian's and Ubuntu's
apache2.serviceshipsProcSubset=pidandProtectProc=invisible, so the web server sees only its own processes in/proc. I confirmed on such a host that/proc/uptime,/proc/stat,/proc/1,uptime(1),who -band/run/utmpare all closed to it, withopen_basediranddisable_functionsempty. On those installs this app has never been able to report CPU, memory or uptime.Commands still run there, so the Linux backend now falls back:
nproc, which usessched_getaffinityrather than/procmeminfofiles under/sys, whichProcSubsetdoes not cover, tried beforefree(1):freereads/proc/meminfoitself and is bound to fail wherever that file is hidden, so trying/sysfirst settles which situation we are in without paying for a fork/proc/<pid>/stat, and a process may always read its own entryaarch64 and s390x are handled too: they list their processors but carry no
model nameline, so the count is now taken separately from the name.3. The two-second poll cost more than it needed to
Measured on a live instance:
LiveData::getData()takes 1.31 ms inside an HTTP request that takes 467 ms (median of 24). About 99.7% of a poll is the Nextcloud request cycle, so the lever is fewer and cheaper requests, not faster collection.useLiveDatastops polling while the tab is hidden and polls immediately on return, so a page left open in the background no longer fetches numbers nobody can seegetTime()no longer forksdate(1)for something PHP can answer (that fork was 1.28 ms of the 1.31 ms); the system zone comes from/etc/localtime, since Nextcloud itself runs on UTCLiveDatano longer callsgetCPU()— which parses/proc/cpuinfo, or forksnproc— purely to reachsys_getloadavg(), which uses none of itNet effect: hosts with a readable
/procnow fork nothing per poll, where every host used to forkdate. The new fallbacks never make the fork count worse than it was.Verification
Unit tests: 31 in
LinuxTest(13 new), plus FreeBSD/Dummy/NetInterface green. New fixtures for aarch64cpuinfo,free -kand/sysnode meminfo. Two of the tests assertexecuteCommandis never called, so the removed forks stay removed.php-cs-fixerclean over all 49 files; psalm "No errors found"; eslint clean.Measured end to end against a live NC 35 instance whose Apache cannot read
/proc:/proc/uptimetruth 326639.51 s/updaterequests in 12 s while visible, 0 in 12 s while backgrounded, and one immediately on return rather than after the intervalNaN, no-1024, no1.0 B, no(-1 threads)anywhere on the pageNotes for review
This is three related changes in one commit — honest unknowns, the
/procfallbacks, and the polling cost — because the later two grew directly out of the first and share the-1contract. Happy to split them into separate PRs if you would rather review them apart.sampleUptime()is new onIOperatingSystem; FreeBSD and Dummy return-1. Swap stays unknown on hosts that fall back to/sys, which publishes no swap totals — better than inventing a number.