From 06ed38ff458d9016aaa93805d8f4fa4a8b9cc96a Mon Sep 17 00:00:00 2001 From: GT610 <79314033+GT-610@users.noreply.github.com> Date: Tue, 6 Jan 2026 23:44:54 +0800 Subject: [PATCH] fix(container): Fix Podman 5.x Network Traffic Statistics Not Displaying (#991) * fix(container): Added version parameter to accommodate Podman 5.x network statistics format Modified the parseStats method to accept a version parameter, handling changes in Podman 5.x's network statistics data structure. When the version is 5.x, network traffic data is retrieved from the RxBytes/TxBytes fields of the Network interface. * fix(container): Fixed Podman version detection logic to correctly retrieve network statistics Addressed Podman version number parsing issues and improved version comparison logic to support all 4.x and below versions as well as 5.x and above versions * fix(container): Resolved display formatting issues for network and disk I/O statistics Handled default values when NetIO and BlockIO are null, and reformatted display strings to distinguish upstream/downstream traffic and read/write operations. * fix: Why did I mess up the tag order? --- lib/data/model/container/ps.dart | 44 +++++++++++++++++++++++++------- lib/data/provider/container.dart | 2 +- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/lib/data/model/container/ps.dart b/lib/data/model/container/ps.dart index e29858db..2bc4be90 100644 --- a/lib/data/model/container/ps.dart +++ b/lib/data/model/container/ps.dart @@ -20,7 +20,7 @@ sealed class ContainerPs { factory ContainerPs.fromRaw(String s, ContainerType typ) => typ.ps(s); - void parseStats(String s); + void parseStats(String s, [String? version]); } final class PodmanPs implements ContainerPs { @@ -55,7 +55,7 @@ final class PodmanPs implements ContainerPs { ContainerStatus get status => ContainerStatus.fromPodmanExited(exited); @override - void parseStats(String s) { + void parseStats(String s, [String? version]) { final stats = json.decode(s); final cpuD = (stats['CPU'] as double? ?? 0).toStringAsFixed(1); final cpuAvgD = (stats['AvgCPU'] as double? ?? 0).toStringAsFixed(1); @@ -63,12 +63,32 @@ final class PodmanPs implements ContainerPs { final memLimit = (stats['MemLimit'] as int? ?? 0).bytes2Str; final memUsage = (stats['MemUsage'] as int? ?? 0).bytes2Str; mem = '$memUsage / $memLimit'; - final netIn = (stats['NetInput'] as int? ?? 0).bytes2Str; - final netOut = (stats['NetOutput'] as int? ?? 0).bytes2Str; - net = '↓ $netIn / ↑ $netOut'; + + int netIn = 0; + int netOut = 0; + final majorVersion = version?.split('.').firstOrNull; + final majorVersionNum = majorVersion != null ? int.tryParse(majorVersion) : null; + + // Podman 4.x and earlier use top-level NetInput/NetOutput fields. + // Podman 5.x changed network backend (Netavark) and uses nested + // Network.{iface}.RxBytes/TxBytes structure instead. + if (majorVersionNum == null || majorVersionNum <= 4) { + netIn = stats['NetInput'] as int? ?? 0; + netOut = stats['NetOutput'] as int? ?? 0; + } else if (majorVersionNum >= 5) { + final network = stats['Network'] as Map?; + if (network != null) { + for (final interface in network.values) { + netIn += interface['RxBytes'] as int? ?? 0; + netOut += interface['TxBytes'] as int? ?? 0; + } + } + } + net = '↓ ${netIn.bytes2Str} / ↑ ${netOut.bytes2Str}'; + final diskIn = (stats['BlockInput'] as int? ?? 0).bytes2Str; final diskOut = (stats['BlockOutput'] as int? ?? 0).bytes2Str; - disk = '${l10n.read} $diskOut / ${l10n.write} $diskIn'; + disk = '${l10n.read} $diskIn / ${l10n.write} $diskOut'; } factory PodmanPs.fromRawJson(String str) => PodmanPs.fromJson(json.decode(str)); @@ -125,12 +145,18 @@ final class DockerPs implements ContainerPs { ContainerStatus get status => ContainerStatus.fromDockerState(state); @override - void parseStats(String s) { + void parseStats(String s, [String? version]) { final stats = json.decode(s); cpu = stats['CPUPerc']; mem = stats['MemUsage']; - net = stats['NetIO']; - disk = stats['BlockIO']; + + final netIO = stats['NetIO'] as String? ?? '0B / 0B'; + final netParts = netIO.split(' / '); + net = '↓ ${netParts.firstOrNull ?? '0B'} / ↑ ${netParts.length > 1 ? netParts[1] : '0B'}'; + + final blockIO = stats['BlockIO'] as String? ?? '0B / 0B'; + final blockParts = blockIO.split(' / '); + disk = '${l10n.read} ${blockParts.firstOrNull ?? '0B'} / ${l10n.write} ${blockParts.length > 1 ? blockParts[1] : '0B'}'; } /// CONTAINER ID NAMES IMAGE STATUS diff --git a/lib/data/provider/container.dart b/lib/data/provider/container.dart index 3c6a4507..5ccc83e7 100644 --- a/lib/data/provider/container.dart +++ b/lib/data/provider/container.dart @@ -186,7 +186,7 @@ class ContainerNotifier extends _$ContainerNotifier { (element) => element.contains(id.substring(0, 5)), ); if (statsLine == null) continue; - item.parseStats(statsLine); + item.parseStats(statsLine, state.version); } } catch (e, trace) { state = state.copyWith(