Fix DATA_DIR_LIMIT_GB comparing against whole-volume usage instead of Site Gateway's own footprint
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
<img alt="Docker" src="https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white">
|
<img alt="Docker" src="https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white">
|
||||||
<img alt="Architectures" src="https://img.shields.io/badge/platform-amd64%20%7C%20arm64-5965F2">
|
<img alt="Architectures" src="https://img.shields.io/badge/platform-amd64%20%7C%20arm64-5965F2">
|
||||||
<img alt="Caddy" src="https://img.shields.io/badge/powered%20by-Caddy-1F88C0">
|
<img alt="Caddy" src="https://img.shields.io/badge/powered%20by-Caddy-1F88C0">
|
||||||
<img alt="Version" src="https://img.shields.io/badge/version-0.16.31-62E6A7">
|
<img alt="Version" src="https://img.shields.io/badge/version-0.16.32-62E6A7">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="#why-site-gateway">Why Site Gateway</a> ·
|
<a href="#why-site-gateway">Why Site Gateway</a> ·
|
||||||
|
|||||||
@@ -188,3 +188,5 @@ Roughly in priority order:
|
|||||||
`v0.16.30` fixes two numbers on the System tab's new hero panel (added in v0.16.29) that were technically correct but meant the wrong thing. CPU percent was always computed against either a real Docker `--cpus` quota or, absent one, the *host's total core count* -- so pinning the container to 2 specific cores (`--cpuset-cpus`, Unraid's CPU pinning field) didn't change the denominator at all, since pinning caps which cores can run without capping how much of them can be used, and `cpu.max` stays `max` either way. CPU now checks `cpuset.cpus.effective` (the actual pinned core list, correctly counting ranges like `0-1,4`) whenever there's no real quota, and the hero panel's detail line now says which denominator applies -- "Of N allocated CPUs" for a real `--cpus` quota, "Of N pinned cores" for cpuset pinning with no quota, or "Of host's N cores -- no limit set" when neither is configured -- instead of always claiming "Of this container's CPU quota" even when there wasn't one. Swap had a similar honesty problem: without an explicit `--memory-swap` limit, `memory.swap.max` reads `max` (unbounded, shared with the host's swap) rather than "0," but the panel showed a bare "0 B" that read like a real, enforced cap. It now only shows a percentage when a real swap limit exists; otherwise it shows the actual bytes in use with "Unlimited -- shares host swap" instead of implying a limit that was never set.
|
`v0.16.30` fixes two numbers on the System tab's new hero panel (added in v0.16.29) that were technically correct but meant the wrong thing. CPU percent was always computed against either a real Docker `--cpus` quota or, absent one, the *host's total core count* -- so pinning the container to 2 specific cores (`--cpuset-cpus`, Unraid's CPU pinning field) didn't change the denominator at all, since pinning caps which cores can run without capping how much of them can be used, and `cpu.max` stays `max` either way. CPU now checks `cpuset.cpus.effective` (the actual pinned core list, correctly counting ranges like `0-1,4`) whenever there's no real quota, and the hero panel's detail line now says which denominator applies -- "Of N allocated CPUs" for a real `--cpus` quota, "Of N pinned cores" for cpuset pinning with no quota, or "Of host's N cores -- no limit set" when neither is configured -- instead of always claiming "Of this container's CPU quota" even when there wasn't one. Swap had a similar honesty problem: without an explicit `--memory-swap` limit, `memory.swap.max` reads `max` (unbounded, shared with the host's swap) rather than "0," but the panel showed a bare "0 B" that read like a real, enforced cap. It now only shows a percentage when a real swap limit exists; otherwise it shows the actual bytes in use with "Unlimited -- shares host swap" instead of implying a limit that was never set.
|
||||||
|
|
||||||
`v0.16.31` adds the two remaining items from the System tab hero panel's fix list. First, the panel now keeps itself current while you're actually looking at it: a lightweight timer polls `/api/system/health` directly every 7 seconds whenever the System tab is the visible admin panel, separate from the app's full `refresh()` (which also refetches sites, proxies, certificates, and everything else) so it stays cheap on a fast interval, and it's a no-op the moment you navigate away rather than continuing to poll in the background. Previously the hero panel only updated on initial page load or whenever *anything else* in the app happened to trigger a `refresh()` -- sitting on the tab watching it did nothing. Second, a new `DATA_DIR_LIMIT_GB` environment variable lets an operator tell the Disk stat what's actually assigned to this deployment -- a dedicated share or zvol smaller than the whole host volume, for instance -- instead of always showing usage against the full underlying filesystem size. This is necessarily display-only, since Docker has no real per-container disk-space quota the way it does for CPU (`cpu.max`) or memory (`memory.max`); actual usage and free space still come straight from `statfs` on the real volume, only the percentage's denominator and the "used of X assigned" label change. Set past 100%, the stat turns red rather than silently capping, since exceeding an assigned allowance is a real, meaningful warning rather than a display bug.
|
`v0.16.31` adds the two remaining items from the System tab hero panel's fix list. First, the panel now keeps itself current while you're actually looking at it: a lightweight timer polls `/api/system/health` directly every 7 seconds whenever the System tab is the visible admin panel, separate from the app's full `refresh()` (which also refetches sites, proxies, certificates, and everything else) so it stays cheap on a fast interval, and it's a no-op the moment you navigate away rather than continuing to poll in the background. Previously the hero panel only updated on initial page load or whenever *anything else* in the app happened to trigger a `refresh()` -- sitting on the tab watching it did nothing. Second, a new `DATA_DIR_LIMIT_GB` environment variable lets an operator tell the Disk stat what's actually assigned to this deployment -- a dedicated share or zvol smaller than the whole host volume, for instance -- instead of always showing usage against the full underlying filesystem size. This is necessarily display-only, since Docker has no real per-container disk-space quota the way it does for CPU (`cpu.max`) or memory (`memory.max`); actual usage and free space still come straight from `statfs` on the real volume, only the percentage's denominator and the "used of X assigned" label change. Set past 100%, the stat turns red rather than silently capping, since exceeding an assigned allowance is a real, meaningful warning rather than a display bug.
|
||||||
|
|
||||||
|
`v0.16.32` fixes a real bug in v0.16.31's `DATA_DIR_LIMIT_GB` disk allowance: the percentage it computed compared an assigned per-app allowance (e.g. 30 GB) against `statfs`'s used-space figure for the *entire filesystem* behind `/data` -- which on a shared array, cache pool, or any volume with other things living on it, has nothing to do with how much Site Gateway itself has actually written. A container assigned 30 GB sitting on a host volume that's 160 GB full of unrelated data showed as "534% used," which is a meaningless number dressed up as a warning. When `DATA_DIR_LIMIT_GB` is set, the Disk stat now compares against Site Gateway's own actual footprint instead -- the same recursive `/data` walk (`directorySize()`) the System tab's storage breakdown already performs -- so the percentage reflects what this app has actually written, not what else happens to share its disk. That walk only runs when the environment variable is actually set, since it isn't free and the whole-volume `statfs` numbers (used with no assigned limit configured) don't need it.
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "site-gateway",
|
"name": "site-gateway",
|
||||||
"version": "0.16.31",
|
"version": "0.16.32",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
|
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<title>Site Gateway</title>
|
<title>Site Gateway</title>
|
||||||
<meta name="description" content="Host sites, proxy services, and manage HTTPS from one simple dashboard.">
|
<meta name="description" content="Host sites, proxy services, and manage HTTPS from one simple dashboard.">
|
||||||
<link rel="icon" type="image/png" href="/site-gateway-icon-approved.png">
|
<link rel="icon" type="image/png" href="/site-gateway-icon-approved.png">
|
||||||
<link rel="stylesheet" href="/styles.css?v=0.16.31">
|
<link rel="stylesheet" href="/styles.css?v=0.16.32">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<!-- ================================================================
|
<!-- ================================================================
|
||||||
@@ -434,6 +434,6 @@
|
|||||||
<div id="toast" class="toast" role="status"></div>
|
<div id="toast" class="toast" role="status"></div>
|
||||||
<div id="update-banner" class="update-banner hidden" role="status"><span>A new version of Site Gateway is available.</span><div class="update-banner-actions"><button id="update-banner-refresh" class="button primary">Refresh</button><button id="update-banner-dismiss" class="text-button">Dismiss</button></div></div>
|
<div id="update-banner" class="update-banner hidden" role="status"><span>A new version of Site Gateway is available.</span><div class="update-banner-actions"><button id="update-banner-refresh" class="button primary">Refresh</button><button id="update-banner-dismiss" class="text-button">Dismiss</button></div></div>
|
||||||
<!-- App scripts: core (app.js) then extended views/admin (features.js) -->
|
<!-- App scripts: core (app.js) then extended views/admin (features.js) -->
|
||||||
<script src="/app.js?v=0.16.31" defer></script><script src="/features.js?v=0.16.31" defer></script><script src="/select-enhance.js?v=0.16.31" defer></script>
|
<script src="/app.js?v=0.16.32" defer></script><script src="/features.js?v=0.16.32" defer></script><script src="/select-enhance.js?v=0.16.32" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+13
-4
@@ -189,25 +189,34 @@ sampleNetworkInterfaces();
|
|||||||
// container-scoped (cgroup v2 + this container's network namespace); disk reuses the same
|
// container-scoped (cgroup v2 + this container's network namespace); disk reuses the same
|
||||||
// statfs-on-the-data-volume approach as /api/system/storage.
|
// statfs-on-the-data-volume approach as /api/system/storage.
|
||||||
async function systemHealthSnapshot() {
|
async function systemHealthSnapshot() {
|
||||||
const [cpu, memory, swap, disk] = await Promise.all([
|
const assignedLimitGb = numberEnv("DATA_DIR_LIMIT_GB", null);
|
||||||
|
const assignedLimitBytes = assignedLimitGb && assignedLimitGb > 0 ? assignedLimitGb * 1024 ** 3 : null;
|
||||||
|
const [cpu, memory, swap, disk, appUsedBytes] = await Promise.all([
|
||||||
cgroupCpuPercent(),
|
cgroupCpuPercent(),
|
||||||
cgroupMemory(),
|
cgroupMemory(),
|
||||||
cgroupSwap(),
|
cgroupSwap(),
|
||||||
fsp.statfs(dataDir).catch(() => null),
|
fsp.statfs(dataDir).catch(() => null),
|
||||||
|
// Only walk /data (the same directorySize() the storage breakdown below already uses) when
|
||||||
|
// an assigned limit is actually configured -- it's the one case that needs it, and the walk
|
||||||
|
// isn't free, so skip it when the panel is just going to show whole-volume stats anyway.
|
||||||
|
assignedLimitBytes !== null ? directorySize(dataDir) : Promise.resolve(null),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
cpu,
|
cpu,
|
||||||
memory,
|
memory,
|
||||||
swap,
|
swap,
|
||||||
disk: disk ? (() => {
|
disk: disk ? (() => {
|
||||||
const totalBytes = disk.blocks * disk.bsize, freeBytes = disk.bfree * disk.bsize, availableBytes = disk.bavail * disk.bsize, usedBytes = totalBytes - freeBytes;
|
const totalBytes = disk.blocks * disk.bsize, freeBytes = disk.bfree * disk.bsize, availableBytes = disk.bavail * disk.bsize, volumeUsedBytes = totalBytes - freeBytes;
|
||||||
// DATA_DIR_LIMIT_GB lets an operator tell the hero panel what's actually assigned to this
|
// DATA_DIR_LIMIT_GB lets an operator tell the hero panel what's actually assigned to this
|
||||||
// deployment (e.g. a dedicated share/zvol sized smaller than the whole host volume), since
|
// deployment (e.g. a dedicated share/zvol sized smaller than the whole host volume), since
|
||||||
// Docker has no real per-container disk-space quota to read the way it does for CPU/memory.
|
// Docker has no real per-container disk-space quota to read the way it does for CPU/memory.
|
||||||
// Purely a display denominator -- it doesn't enforce anything -- so usage over 100% is a
|
// Purely a display denominator -- it doesn't enforce anything -- so usage over 100% is a
|
||||||
// real, meaningful warning rather than a bug: it means actual usage has exceeded what was assigned.
|
// real, meaningful warning rather than a bug: it means actual usage has exceeded what was assigned.
|
||||||
const assignedLimitGb = numberEnv("DATA_DIR_LIMIT_GB", null);
|
// Critically, comparing against an assigned allowance has to use Site Gateway's own actual
|
||||||
const assignedLimitBytes = assignedLimitGb && assignedLimitGb > 0 ? assignedLimitGb * 1024 ** 3 : null;
|
// footprint (appUsedBytes, a real walk of /data), not the whole filesystem's used space --
|
||||||
|
// statfs reports usage for the entire volume behind /data, which on a shared array or pool
|
||||||
|
// includes everything else living on that mount, not just what this app has written.
|
||||||
|
const usedBytes = assignedLimitBytes !== null ? appUsedBytes : volumeUsedBytes;
|
||||||
const denominatorBytes = assignedLimitBytes || totalBytes;
|
const denominatorBytes = assignedLimitBytes || totalBytes;
|
||||||
return { totalBytes, freeBytes, availableBytes, usedBytes, assignedLimitBytes, percent: (usedBytes / denominatorBytes) * 100 };
|
return { totalBytes, freeBytes, availableBytes, usedBytes, assignedLimitBytes, percent: (usedBytes / denominatorBytes) * 100 };
|
||||||
})() : null,
|
})() : null,
|
||||||
|
|||||||
Reference in New Issue
Block a user