fix: keep PBS logs and maintenance jobs readable

- Draw ZFS ARC only from recorded history, leaving it out on PBS
- Read the maintenance jobs of a local PBS from its API, with their last state
- Show the OK state of PBS jobs in green whatever its case
- Compress rotated logs at once in the Log2RAM and logrotate profiles
- Offer the Log2RAM 1.8 and logrotate 1.2 update to existing installations
This commit is contained in:
MacRimi
2026-10-20 00:53:21 +02:00
parent d8741a8c5d
commit 0a7a598210
7 changed files with 43 additions and 30 deletions
+2 -1
View File
@@ -54,9 +54,10 @@ function taskState(task: Task): keyof typeof badgeTone {
return "error"
}
// PBS reports "OK", "WARNINGS: n" or the error text of the last run.
function jobState(state?: string): keyof typeof badgeTone {
if (!state) return "muted"
if (state === "ok") return "ok"
if (state.toLowerCase() === "ok") return "ok"
if (state.toLowerCase().startsWith("warn")) return "warn"
return "error"
}
+2 -14
View File
@@ -12242,18 +12242,6 @@ def _compute_node_metrics_payload(timeframe: str) -> dict:
deadline = time.monotonic() + _NODE_METRICS_TIMEOUT
local_node = get_proxmox_node_name()
zfs_arc_size = 0
try:
with open('/proc/spl/kstat/zfs/arcstats', 'r') as f:
for line in f:
if line.startswith('size'):
parts = line.split()
if len(parts) >= 3:
zfs_arc_size = int(parts[2])
break
except (FileNotFoundError, PermissionError, ValueError):
pass
try:
rrd_result = subprocess.run(
_node_rrd_command(local_node, timeframe),
@@ -12322,11 +12310,11 @@ def _compute_node_metrics_payload(timeframe: str) -> dict:
'code': 'metrics_invalid_data',
})
# Only the history the node recorded is drawn. A PBS RRD has no ARC field,
# and the current size repeated over the period is not its history.
for item in rrd_data:
if 'arcsize' in item:
item['zfsarc'] = item['arcsize']
elif zfs_arc_size > 0 and ('zfsarc' not in item or item.get('zfsarc', 0) == 0):
item['zfsarc'] = zfs_arc_size
def _values_from(items, field_key, scale=1.0):
return [item[field_key] * scale for item in items
+17 -3
View File
@@ -279,6 +279,20 @@ def _cli(*arguments):
raise PBSError("Could not read PBS " + " ".join(arguments[:2]) + ". Check its service and permissions.") from exc
def _local_api(path):
"""The local PBS API, which reports what the job configuration lacks: the
state of the last run and the next one."""
try:
result = subprocess.run(["proxmox-backup-debug", "api", "get", path, "--output-format", "json"],
check=True, text=True, capture_output=True, timeout=8)
data = json.loads(result.stdout)
if not isinstance(data, list) or not all(isinstance(row, dict) for row in data):
raise ValueError()
return data
except (OSError, subprocess.SubprocessError, ValueError) as exc:
raise PBSError("Could not read PBS " + path + ". Check its service and permissions.") from exc
def _local_datastores():
stores = _cli("datastore", "list")
result = []
@@ -325,9 +339,9 @@ def get_status(identifier):
services = _local_services()
operations = {"datastores": _local_datastores,
"tasks": lambda: _cli("task", "list", "--all", "true", "--limit", "20"),
"sync": lambda: _cli("sync-job", "list"),
"verify": lambda: _cli("verify-job", "list"),
"prune": lambda: _cli("prune-job", "list")}
"sync": lambda: _local_api("/admin/sync"),
"verify": lambda: _local_api("/admin/verify"),
"prune": lambda: _local_api("/admin/prune")}
else:
with _lock:
server = next((row for row in _load_servers() if row["id"] == identifier), None)
+5 -1
View File
@@ -152,9 +152,11 @@ class PBSManagerTests(unittest.TestCase):
def test_local_status_reads_pbs_commands_and_filesystem_capacity(self):
def run(command, **kwargs):
self.assertNotIn("shell", kwargs)
self.assertEqual(command[0], "proxmox-backup-manager")
self.assertIn(command[0], ("proxmox-backup-manager", "proxmox-backup-debug"))
self.assertEqual(command[-2:], ["--output-format", "json"])
data = [{"name": "backup", "path": str(self.root)}] if command[1:3] == ["datastore", "list"] else []
if command[:4] == ["proxmox-backup-debug", "api", "get", "/admin/verify"]:
data = [{"id": "v-1", "store": "backup", "schedule": "daily", "last-run-state": "OK"}]
return SimpleNamespace(stdout=json.dumps(data), returncode=0)
with mock.patch.object(pbs, "get_platform", return_value={"product": "pbs", "version": "4.2.5"}), \
mock.patch.object(pbs, "_local_services", return_value={key: "active" for key in pbs.PBS_SERVICES}), \
@@ -165,6 +167,8 @@ class PBSManagerTests(unittest.TestCase):
self.assertEqual(result["errors"], {})
self.assertEqual(result["datastores"][0]["used"], 30)
self.assertEqual(set(result["services"]), set(pbs.PBS_SERVICES))
# Jobs come from the API, which carries the state of their last run.
self.assertEqual(result["verify"][0]["last-run-state"], "OK")
def test_pbs_installed_next_to_pve_is_read_locally_with_its_own_version(self):
def run(command, **kwargs):