mirror of
https://github.com/containers/podman.git
synced 2025-06-26 12:56:45 +08:00

This results in some functionality changes: If a ErrCtrStateInvalid is returned to GetPodStats, the container is ommitted from the stats. As such, if an empty slice of Container stats are returned to GetPodStats in varlink, an error will occur. GetContainerStats will return the ErrCtrStateInvalid as well. Finally, if ErrCtrStateInvalid is returned to the podman stats call, the container will be ommitted from the stats. Signed-off-by: haircommander <pehunt@redhat.com> Closes: #1319 Approved by: baude
123 lines
3.3 KiB
Go
123 lines
3.3 KiB
Go
// +build linux
|
|
|
|
package libpod
|
|
|
|
import (
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/containerd/cgroups"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// GetContainerStats gets the running stats for a given container
|
|
func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) {
|
|
stats := new(ContainerStats)
|
|
stats.ContainerID = c.ID()
|
|
stats.Name = c.Name()
|
|
|
|
if !c.batched {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
if err := c.syncContainer(); err != nil {
|
|
return stats, err
|
|
}
|
|
}
|
|
|
|
if c.state.State != ContainerStateRunning {
|
|
return stats, ErrCtrStateInvalid
|
|
}
|
|
|
|
cgroupPath, err := c.CGroupPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cgroup, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(cgroupPath))
|
|
if err != nil {
|
|
return stats, errors.Wrapf(err, "unable to load cgroup at %s", cgroupPath)
|
|
}
|
|
|
|
cgroupStats, err := cgroup.Stat()
|
|
if err != nil {
|
|
return stats, errors.Wrapf(err, "unable to obtain cgroup stats")
|
|
}
|
|
conState := c.state.State
|
|
if err != nil {
|
|
return stats, errors.Wrapf(err, "unable to determine container state")
|
|
}
|
|
|
|
netStats, err := getContainerNetIO(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
previousCPU := previousStats.CPUNano
|
|
previousSystem := previousStats.SystemNano
|
|
stats.CPU = calculateCPUPercent(cgroupStats, previousCPU, previousSystem)
|
|
stats.MemUsage = cgroupStats.Memory.Usage.Usage
|
|
stats.MemLimit = getMemLimit(cgroupStats.Memory.Usage.Limit)
|
|
stats.MemPerc = (float64(stats.MemUsage) / float64(stats.MemLimit)) * 100
|
|
stats.PIDs = 0
|
|
if conState == ContainerStateRunning {
|
|
stats.PIDs = cgroupStats.Pids.Current
|
|
}
|
|
stats.BlockInput, stats.BlockOutput = calculateBlockIO(cgroupStats)
|
|
stats.CPUNano = cgroupStats.CPU.Usage.Total
|
|
stats.SystemNano = cgroupStats.CPU.Usage.Kernel
|
|
// Handle case where the container is not in a network namespace
|
|
if netStats != nil {
|
|
stats.NetInput = netStats.TxBytes
|
|
stats.NetOutput = netStats.RxBytes
|
|
} else {
|
|
stats.NetInput = 0
|
|
stats.NetOutput = 0
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// getMemory limit returns the memory limit for a given cgroup
|
|
// If the configured memory limit is larger than the total memory on the sys, the
|
|
// physical system memory size is returned
|
|
func getMemLimit(cgroupLimit uint64) uint64 {
|
|
si := &syscall.Sysinfo_t{}
|
|
err := syscall.Sysinfo(si)
|
|
if err != nil {
|
|
return cgroupLimit
|
|
}
|
|
|
|
physicalLimit := uint64(si.Totalram)
|
|
if cgroupLimit > physicalLimit {
|
|
return physicalLimit
|
|
}
|
|
return cgroupLimit
|
|
}
|
|
|
|
func calculateCPUPercent(stats *cgroups.Metrics, previousCPU, previousSystem uint64) float64 {
|
|
var (
|
|
cpuPercent = 0.0
|
|
cpuDelta = float64(stats.CPU.Usage.Total - previousCPU)
|
|
systemDelta = float64(uint64(time.Now().UnixNano()) - previousSystem)
|
|
)
|
|
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
|
// gets a ratio of container cpu usage total, multiplies it by the number of cores (4 cores running
|
|
// at 100% utilization should be 400% utilization), and multiplies that by 100 to get a percentage
|
|
cpuPercent = (cpuDelta / systemDelta) * float64(len(stats.CPU.Usage.PerCPU)) * 100
|
|
}
|
|
return cpuPercent
|
|
}
|
|
|
|
func calculateBlockIO(stats *cgroups.Metrics) (read uint64, write uint64) {
|
|
for _, blkIOEntry := range stats.Blkio.IoServiceBytesRecursive {
|
|
switch strings.ToLower(blkIOEntry.Op) {
|
|
case "read":
|
|
read += blkIOEntry.Value
|
|
case "write":
|
|
write += blkIOEntry.Value
|
|
}
|
|
}
|
|
return
|
|
}
|