Skip to content

Commit 6e0decb

Browse files
committed
Send container stats over API on a per-interface basis
This mirrors how the Docker API handles things, allowing us to be more compatible with Docker and more verbose on the Libpod API. Stats are given as per network interface in the container, but still aggregated for `podman stats` and `podman pod stats` display (so the CLI does not change, only the Libpod and Compat APIs). Signed-off-by: Matt Heon <[email protected]>
1 parent 22b1650 commit 6e0decb

File tree

11 files changed

+107
-104
lines changed

11 files changed

+107
-104
lines changed

cmd/podman/containers/stats.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,15 @@ func (s *containerStats) MemPerc() string {
215215
}
216216

217217
func (s *containerStats) NetIO() string {
218-
return combineHumanValues(s.NetInput, s.NetOutput)
218+
var netInput uint64
219+
var netOutput uint64
220+
221+
for _, net := range s.Network {
222+
netInput += net.RxBytes
223+
netOutput += net.TxBytes
224+
}
225+
226+
return combineHumanValues(netInput, netOutput)
219227
}
220228

221229
func (s *containerStats) BlockIO() string {

docs/source/markdown/podman-stats.1.md.in

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@ Valid placeholders for the Go template are listed below:
5050
| .MemUsage | Memory usage |
5151
| .MemUsageBytes | Memory usage (IEC) |
5252
| .Name | Container Name |
53-
| .NetInput | Network Input |
5453
| .NetIO | Network IO |
55-
| .NetOutput | Network Output |
54+
| .Network | Network I/O, separated by network interface |
5655
| .PerCPU | CPU time consumed by all tasks [1] |
5756
| .PIDs | Number of PIDs |
5857
| .PIDS | Number of PIDs (yes, we know this is a dup) |

libpod/define/containerstate.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,23 @@ type ContainerStats struct {
141141
MemUsage uint64
142142
MemLimit uint64
143143
MemPerc float64
144-
NetInput uint64
145-
NetOutput uint64
146-
BlockInput uint64
147-
BlockOutput uint64
148-
PIDs uint64
149-
UpTime time.Duration
150-
Duration uint64
144+
// Map of interface name to network statistics for that interface.
145+
Network map[string]ContainerNetworkStats
146+
BlockInput uint64
147+
BlockOutput uint64
148+
PIDs uint64
149+
UpTime time.Duration
150+
Duration uint64
151+
}
152+
153+
// Statistics for an individual container network interface
154+
type ContainerNetworkStats struct {
155+
RxBytes uint64
156+
RxDropped uint64
157+
RxErrors uint64
158+
RxPackets uint64
159+
TxBytes uint64
160+
TxDropped uint64
161+
TxErrors uint64
162+
TxPackets uint64
151163
}

libpod/networking_freebsd.go

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/containers/buildah/pkg/jail"
1515
"github.com/containers/common/libnetwork/types"
16+
"github.com/containers/podman/v4/libpod/define"
1617
"github.com/containers/storage/pkg/lockfile"
1718
"github.com/sirupsen/logrus"
1819
)
@@ -45,33 +46,6 @@ type NetstatAddress struct {
4546
Collisions uint64 `json:"collisions"`
4647
}
4748

48-
// copied from github.com/vishvanada/netlink which does not build on freebsd
49-
type LinkStatistics64 struct {
50-
RxPackets uint64
51-
TxPackets uint64
52-
RxBytes uint64
53-
TxBytes uint64
54-
RxErrors uint64
55-
TxErrors uint64
56-
RxDropped uint64
57-
TxDropped uint64
58-
Multicast uint64
59-
Collisions uint64
60-
RxLengthErrors uint64
61-
RxOverErrors uint64
62-
RxCrcErrors uint64
63-
RxFrameErrors uint64
64-
RxFifoErrors uint64
65-
RxMissedErrors uint64
66-
TxAbortedErrors uint64
67-
TxCarrierErrors uint64
68-
TxFifoErrors uint64
69-
TxHeartbeatErrors uint64
70-
TxWindowErrors uint64
71-
RxCompressed uint64
72-
TxCompressed uint64
73-
}
74-
7549
type RootlessNetNS struct {
7650
dir string
7751
Lock *lockfile.LockFile
@@ -223,7 +197,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error {
223197

224198
// TODO (5.0): return the statistics per network interface
225199
// This would allow better compat with docker.
226-
func getContainerNetIO(ctr *Container) (*LinkStatistics64, error) {
200+
func getContainerNetIO(ctr *Container) (map[string]define.ContainerNetworkStats, error) {
227201
if ctr.state.NetNS == "" {
228202
// If NetNS is nil, it was set as none, and no netNS
229203
// was set up this is a valid state and thus return no
@@ -249,8 +223,9 @@ func getContainerNetIO(ctr *Container) (*LinkStatistics64, error) {
249223
return nil, err
250224
}
251225

226+
res := make(map[string]define.ContainerNetworkStats)
227+
252228
// Sum all the interface stats - in practice only Tx/TxBytes are needed
253-
res := &LinkStatistics64{}
254229
for _, ifaddr := range stats.Statistics.Interface {
255230
// Each interface has two records, one for link-layer which has
256231
// an MTU field and one for IP which doesn't. We only want the
@@ -260,14 +235,16 @@ func getContainerNetIO(ctr *Container) (*LinkStatistics64, error) {
260235
// if we move to per-interface stats in future, this can be
261236
// reported separately.
262237
if ifaddr.Mtu > 0 {
263-
res.RxPackets += ifaddr.ReceivedPackets
264-
res.TxPackets += ifaddr.SentPackets
265-
res.RxBytes += ifaddr.ReceivedBytes
266-
res.TxBytes += ifaddr.SentBytes
267-
res.RxErrors += ifaddr.ReceivedErrors
268-
res.TxErrors += ifaddr.SentErrors
269-
res.RxDropped += ifaddr.DroppedPackets
270-
res.Collisions += ifaddr.Collisions
238+
linkStats := define.ContainerNetworkStats{
239+
RxPackets: ifaddr.ReceivedPackets,
240+
TxPackets: ifaddr.SentPackets,
241+
RxBytes: ifaddr.ReceivedBytes,
242+
TxBytes: ifaddr.SentBytes,
243+
RxErrors: ifaddr.ReceivedErrors,
244+
TxErrors: ifaddr.SentErrors,
245+
RxDropped: ifaddr.DroppedPackets,
246+
}
247+
res[ifaddr.Name] = linkStats
271248
}
272249
}
273250

libpod/networking_linux.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/containers/common/libnetwork/types"
1414
netUtil "github.com/containers/common/libnetwork/util"
1515
"github.com/containers/common/pkg/netns"
16+
"github.com/containers/podman/v4/libpod/define"
1617
"github.com/containers/podman/v4/pkg/rootless"
1718
"github.com/opencontainers/runtime-spec/specs-go"
1819
"github.com/sirupsen/logrus"
@@ -186,10 +187,9 @@ func getContainerNetNS(ctr *Container) (string, *Container, error) {
186187
return "", nil, nil
187188
}
188189

189-
// TODO (5.0): return the statistics per network interface
190-
// This would allow better compat with docker.
191-
func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) {
192-
var netStats *netlink.LinkStatistics
190+
// Returns a map of interface name to statistics for that interface.
191+
func getContainerNetIO(ctr *Container) (map[string]define.ContainerNetworkStats, error) {
192+
perNetworkStats := make(map[string]define.ContainerNetworkStats)
193193

194194
netNSPath, otherCtr, netPathErr := getContainerNetNS(ctr)
195195
if netPathErr != nil {
@@ -222,21 +222,26 @@ func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) {
222222
if err != nil {
223223
return err
224224
}
225-
if netStats == nil {
226-
netStats = link.Attrs().Statistics
227-
continue
228-
}
229-
// Currently only Tx/RxBytes are used.
230-
// In the future we should return all stats per interface so that
231-
// api users have a better options.
232225
stats := link.Attrs().Statistics
233-
netStats.TxBytes += stats.TxBytes
234-
netStats.RxBytes += stats.RxBytes
226+
if stats != nil {
227+
newStats := define.ContainerNetworkStats{
228+
RxBytes: stats.RxBytes,
229+
RxDropped: stats.RxDropped,
230+
RxErrors: stats.RxErrors,
231+
RxPackets: stats.RxPackets,
232+
TxBytes: stats.TxBytes,
233+
TxDropped: stats.TxDropped,
234+
TxErrors: stats.TxErrors,
235+
TxPackets: stats.TxPackets,
236+
}
237+
238+
perNetworkStats[dev] = newStats
239+
}
235240
}
236241
}
237242
return nil
238243
})
239-
return netStats, err
244+
return perNetworkStats, err
240245
}
241246

242247
// joinedNetworkNSPath returns netns path and bool if netns was set

libpod/stats_common.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de
4141
}
4242
}
4343

44+
netStats, err := getContainerNetIO(c)
45+
if err != nil {
46+
return nil, err
47+
}
48+
stats.Network = netStats
49+
4450
if err := c.getPlatformContainerStats(stats, previousStats); err != nil {
4551
return nil, err
4652
}

libpod/stats_freebsd.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,6 @@ func (c *Container) getPlatformContainerStats(stats *define.ContainerStats, prev
8080
stats.MemLimit = c.getMemLimit()
8181
stats.SystemNano = now
8282

83-
netStats, err := getContainerNetIO(c)
84-
if err != nil {
85-
return err
86-
}
87-
88-
// Handle case where the container is not in a network namespace
89-
if netStats != nil {
90-
stats.NetInput = netStats.RxBytes
91-
stats.NetOutput = netStats.TxBytes
92-
} else {
93-
stats.NetInput = 0
94-
stats.NetOutput = 0
95-
}
96-
9783
return nil
9884
}
9985

libpod/stats_linux.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ func (c *Container) getPlatformContainerStats(stats *define.ContainerStats, prev
3939
return fmt.Errorf("unable to obtain cgroup stats: %w", err)
4040
}
4141
conState := c.state.State
42-
netStats, err := getContainerNetIO(c)
43-
if err != nil {
44-
return err
45-
}
4642

4743
// If the current total usage in the cgroup is less than what was previously
4844
// recorded then it means the container was restarted and runs in a new cgroup
@@ -69,14 +65,6 @@ func (c *Container) getPlatformContainerStats(stats *define.ContainerStats, prev
6965
stats.CPUSystemNano = cgroupStats.CpuStats.CpuUsage.UsageInKernelmode
7066
stats.SystemNano = now
7167
stats.PerCPU = cgroupStats.CpuStats.CpuUsage.PercpuUsage
72-
// Handle case where the container is not in a network namespace
73-
if netStats != nil {
74-
stats.NetInput = netStats.RxBytes
75-
stats.NetOutput = netStats.TxBytes
76-
} else {
77-
stats.NetInput = 0
78-
stats.NetOutput = 0
79-
}
8068

8169
return nil
8270
}

pkg/api/handlers/compat/containers_stats_linux.go

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -119,23 +119,20 @@ streamLabel: // A label to flatten the scope
119119
return
120120
}
121121

122-
// FIXME: network inspection does not yet work entirely
123122
net := make(map[string]docker.NetworkStats)
124-
networkName := inspect.NetworkSettings.EndpointID
125-
if networkName == "" {
126-
networkName = "network"
127-
}
128-
net[networkName] = docker.NetworkStats{
129-
RxBytes: stats.NetInput,
130-
RxPackets: 0,
131-
RxErrors: 0,
132-
RxDropped: 0,
133-
TxBytes: stats.NetOutput,
134-
TxPackets: 0,
135-
TxErrors: 0,
136-
TxDropped: 0,
137-
EndpointID: inspect.NetworkSettings.EndpointID,
138-
InstanceID: "",
123+
for netName, netStats := range stats.Network {
124+
net[netName] = docker.NetworkStats{
125+
RxBytes: netStats.RxBytes,
126+
RxPackets: netStats.RxPackets,
127+
RxErrors: netStats.RxErrors,
128+
RxDropped: netStats.RxDropped,
129+
TxBytes: netStats.TxBytes,
130+
TxPackets: netStats.TxPackets,
131+
TxErrors: netStats.TxErrors,
132+
TxDropped: netStats.TxDropped,
133+
EndpointID: inspect.NetworkSettings.EndpointID,
134+
InstanceID: "",
135+
}
139136
}
140137

141138
resources := ctnr.LinuxResources()

pkg/domain/infra/abi/pods_stats.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,19 @@ func (ic *ContainerEngine) podsToStatsReport(pods []*libpod.Pod) ([]*entities.Po
4444
}
4545
podID := pods[i].ID()[:12]
4646
for j := range podStats {
47+
var podNetInput uint64
48+
var podNetOutput uint64
49+
for _, stats := range podStats[j].Network {
50+
podNetInput += stats.RxBytes
51+
podNetOutput += stats.TxBytes
52+
}
53+
4754
r := entities.PodStatsReport{
4855
CPU: floatToPercentString(podStats[j].CPU),
4956
MemUsage: combineHumanValues(podStats[j].MemUsage, podStats[j].MemLimit),
5057
MemUsageBytes: combineBytesValues(podStats[j].MemUsage, podStats[j].MemLimit),
5158
Mem: floatToPercentString(podStats[j].MemPerc),
52-
NetIO: combineHumanValues(podStats[j].NetInput, podStats[j].NetOutput),
59+
NetIO: combineHumanValues(podNetInput, podNetOutput),
5360
BlockIO: combineHumanValues(podStats[j].BlockInput, podStats[j].BlockOutput),
5461
PIDS: pidsToString(podStats[j].PIDs),
5562
CID: podStats[j].ContainerID[:12],

0 commit comments

Comments
 (0)