aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2022-09-14 14:21:30 +0200
committerGitHub <noreply@github.com>2022-09-14 14:21:30 +0200
commit82651e5974741aec9d9a0b8926c8ff2f715eed8c (patch)
treee326843e191473539f2fa1a3d008af85c9c19ffc
parentae20f19351c03398d10ba94a31f21dcf3d86f31c (diff)
parentb47ce9a6e0e9fa8e137c8338a08dffa256081b48 (diff)
downloadpodman-82651e5974741aec9d9a0b8926c8ff2f715eed8c.tar.gz
podman-82651e5974741aec9d9a0b8926c8ff2f715eed8c.tar.bz2
podman-82651e5974741aec9d9a0b8926c8ff2f715eed8c.zip
Merge pull request #15769 from dfr/freebsd-stats
Add support for 'podman stats' on FreeBSD
-rw-r--r--libpod/stats_common.go49
-rw-r--r--libpod/stats_freebsd.go153
-rw-r--r--libpod/stats_linux.go (renamed from libpod/stats.go)50
-rw-r--r--libpod/stats_unsupported.go4
-rw-r--r--pkg/rctl/rctl.go47
5 files changed, 265 insertions, 38 deletions
diff --git a/libpod/stats_common.go b/libpod/stats_common.go
new file mode 100644
index 000000000..122160bda
--- /dev/null
+++ b/libpod/stats_common.go
@@ -0,0 +1,49 @@
+//go:build linux || freebsd
+// +build linux freebsd
+
+package libpod
+
+import (
+ "fmt"
+
+ "github.com/containers/podman/v4/libpod/define"
+)
+
+// GetContainerStats gets the running stats for a given container.
+// The previousStats is used to correctly calculate cpu percentages. You
+// should pass nil if there is no previous stat for this container.
+func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) {
+ stats := new(define.ContainerStats)
+ stats.ContainerID = c.ID()
+ stats.Name = c.Name()
+
+ if c.config.NoCgroups {
+ return nil, fmt.Errorf("cannot run top on container %s as it did not create a cgroup: %w", c.ID(), define.ErrNoCgroups)
+ }
+
+ if !c.batched {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ if err := c.syncContainer(); err != nil {
+ return stats, err
+ }
+ }
+
+ // returns stats with the fields' default values respective of their type
+ if c.state.State != define.ContainerStateRunning && c.state.State != define.ContainerStatePaused {
+ return stats, nil
+ }
+
+ if previousStats == nil {
+ previousStats = &define.ContainerStats{
+ // if we have no prev stats use the container start time as prev time
+ // otherwise we cannot correctly calculate the CPU percentage
+ SystemNano: uint64(c.state.StartedTime.UnixNano()),
+ }
+ }
+
+ if err := c.getPlatformContainerStats(stats, previousStats); err != nil {
+ return nil, err
+ }
+ return stats, nil
+}
diff --git a/libpod/stats_freebsd.go b/libpod/stats_freebsd.go
new file mode 100644
index 000000000..53bc3f19a
--- /dev/null
+++ b/libpod/stats_freebsd.go
@@ -0,0 +1,153 @@
+package libpod
+
+import (
+ "fmt"
+ "math"
+ "strings"
+ "time"
+
+ "github.com/containers/common/pkg/cgroups"
+ "github.com/containers/podman/v4/libpod/define"
+ "github.com/containers/podman/v4/pkg/rctl"
+ "github.com/containers/storage/pkg/system"
+ "github.com/sirupsen/logrus"
+)
+
+// getPlatformContainerStats gets the platform-specific running stats
+// for a given container. The previousStats is used to correctly
+// calculate cpu percentages. You should pass nil if there is no
+// previous stat for this container.
+func (c *Container) getPlatformContainerStats(stats *define.ContainerStats, previousStats *define.ContainerStats) error {
+ now := uint64(time.Now().UnixNano())
+
+ jailName := c.ID()
+ if c.state.NetNS != nil {
+ jailName = c.state.NetNS.Name + "." + jailName
+ }
+ entries, err := rctl.GetRacct("jail:" + jailName)
+ if err != nil {
+ return fmt.Errorf("unable to read accounting for %s: %w", jailName, err)
+ }
+
+ // If the current total usage is less than what was previously
+ // recorded then it means the container was restarted and runs
+ // in a new jail
+ if dur, ok := entries["wallclock"]; ok {
+ if previousStats.Duration > dur*1000000000 {
+ previousStats = &define.ContainerStats{}
+ }
+ }
+
+ for key, val := range entries {
+ switch key {
+ case "cputime": // CPU time, in seconds
+ stats.CPUNano = val * 1000000000
+ stats.AvgCPU = calculateCPUPercent(stats.CPUNano, 0, now, uint64(c.state.StartedTime.UnixNano()))
+ case "datasize": // data size, in bytes
+ case "stacksize": // stack size, in bytes
+ case "coredumpsize": // core dump size, in bytes
+ case "memoryuse": // resident set size, in bytes
+ stats.MemUsage = val
+ case "memorylocked": // locked memory, in bytes
+ case "maxproc": // number of processes
+ stats.PIDs = val
+ case "openfiles": // file descriptor table size
+ case "vmemoryuse": // address space limit, in bytes
+ case "pseudoterminals": // number of PTYs
+ case "swapuse": // swap space that may be reserved or used, in bytes
+ case "nthr": // number of threads
+ case "msgqqueued": // number of queued SysV messages
+ case "msgqsize": // SysV message queue size, in bytes
+ case "nmsgq": // number of SysV message queues
+ case "nsem": // number of SysV semaphores
+ case "nsemop": // number of SysV semaphores modified in a single semop(2) call
+ case "nshm": // number of SysV shared memory segments
+ case "shmsize": // SysV shared memory size, in bytes
+ case "wallclock": // wallclock time, in seconds
+ stats.Duration = val * 1000000000
+ stats.UpTime = time.Duration(stats.Duration)
+ case "pcpu": // %CPU, in percents of a single CPU core
+ stats.CPU = float64(val)
+ case "readbps": // filesystem reads, in bytes per second
+ stats.BlockInput = val
+ case "writebps": // filesystem writes, in bytes per second
+ stats.BlockOutput = val
+ case "readiops": // filesystem reads, in operations per second
+ case "writeiops": // filesystem writes, in operations per second
+ }
+ }
+ stats.MemLimit = c.getMemLimit()
+ stats.SystemNano = now
+
+ netStats, err := getContainerNetIO(c)
+ if err != nil {
+ return err
+ }
+
+ // 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 nil
+}
+
+// getMemory limit returns the memory limit for a container
+func (c *Container) getMemLimit() uint64 {
+ memLimit := uint64(math.MaxUint64)
+
+ if c.config.Spec.Linux != nil && c.config.Spec.Linux.Resources != nil &&
+ c.config.Spec.Linux.Resources.Memory != nil && c.config.Spec.Linux.Resources.Memory.Limit != nil {
+ memLimit = uint64(*c.config.Spec.Linux.Resources.Memory.Limit)
+ }
+
+ mi, err := system.ReadMemInfo()
+ if err != nil {
+ logrus.Errorf("ReadMemInfo error: %v", err)
+ return 0
+ }
+
+ //nolint:unconvert
+ physicalLimit := uint64(mi.MemTotal)
+
+ if memLimit <= 0 || memLimit > physicalLimit {
+ return physicalLimit
+ }
+
+ return memLimit
+}
+
+// calculateCPUPercent calculates the cpu usage using the latest measurement in stats.
+// previousCPU is the last value of stats.CPU.Usage.Total measured at the time previousSystem.
+//
+// (now - previousSystem) is the time delta in nanoseconds, between the measurement in previousCPU
+//
+// and the updated value in stats.
+func calculateCPUPercent(currentCPU, previousCPU, now, previousSystem uint64) float64 {
+ var (
+ cpuPercent = 0.0
+ cpuDelta = float64(currentCPU - previousCPU)
+ systemDelta = float64(now - previousSystem)
+ )
+ if systemDelta > 0.0 && cpuDelta > 0.0 {
+ // gets a ratio of container cpu usage total, and multiplies that by 100 to get a percentage
+ cpuPercent = (cpuDelta / systemDelta) * 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
+}
diff --git a/libpod/stats.go b/libpod/stats_linux.go
index c7e9e5128..ad8f33c91 100644
--- a/libpod/stats.go
+++ b/libpod/stats_linux.go
@@ -16,57 +16,33 @@ import (
"github.com/containers/podman/v4/libpod/define"
)
-// GetContainerStats gets the running stats for a given container.
-// The previousStats is used to correctly calculate cpu percentages. You
-// should pass nil if there is no previous stat for this container.
-func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) {
- stats := new(define.ContainerStats)
- stats.ContainerID = c.ID()
- stats.Name = c.Name()
-
+// getPlatformContainerStats gets the platform-specific running stats
+// for a given container. The previousStats is used to correctly
+// calculate cpu percentages. You should pass nil if there is no
+// previous stat for this container.
+func (c *Container) getPlatformContainerStats(stats *define.ContainerStats, previousStats *define.ContainerStats) error {
if c.config.NoCgroups {
- return nil, fmt.Errorf("cannot run top on container %s as it did not create a cgroup: %w", c.ID(), define.ErrNoCgroups)
- }
-
- if !c.batched {
- c.lock.Lock()
- defer c.lock.Unlock()
- if err := c.syncContainer(); err != nil {
- return stats, err
- }
- }
-
- // returns stats with the fields' default values respective of their type
- if c.state.State != define.ContainerStateRunning && c.state.State != define.ContainerStatePaused {
- return stats, nil
- }
-
- if previousStats == nil {
- previousStats = &define.ContainerStats{
- // if we have no prev stats use the container start time as prev time
- // otherwise we cannot correctly calculate the CPU percentage
- SystemNano: uint64(c.state.StartedTime.UnixNano()),
- }
+ return fmt.Errorf("cannot run top on container %s as it did not create a cgroup: %w", c.ID(), define.ErrNoCgroups)
}
cgroupPath, err := c.cGroupPath()
if err != nil {
- return nil, err
+ return err
}
cgroup, err := cgroups.Load(cgroupPath)
if err != nil {
- return stats, fmt.Errorf("unable to load cgroup at %s: %w", cgroupPath, err)
+ return fmt.Errorf("unable to load cgroup at %s: %w", cgroupPath, err)
}
// Ubuntu does not have swap memory in cgroups because swap is often not enabled.
cgroupStats, err := cgroup.Stat()
if err != nil {
- return stats, fmt.Errorf("unable to obtain cgroup stats: %w", err)
+ return fmt.Errorf("unable to obtain cgroup stats: %w", err)
}
conState := c.state.State
netStats, err := getContainerNetIO(c)
if err != nil {
- return nil, err
+ return err
}
// If the current total usage in the cgroup is less than what was previously
@@ -103,7 +79,7 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de
stats.NetOutput = 0
}
- return stats, nil
+ return nil
}
// getMemory limit returns the memory limit for a container
@@ -133,7 +109,9 @@ func (c *Container) getMemLimit() uint64 {
// calculateCPUPercent calculates the cpu usage using the latest measurement in stats.
// previousCPU is the last value of stats.CPU.Usage.Total measured at the time previousSystem.
-// (now - previousSystem) is the time delta in nanoseconds, between the measurement in previousCPU
+//
+// (now - previousSystem) is the time delta in nanoseconds, between the measurement in previousCPU
+//
// and the updated value in stats.
func calculateCPUPercent(stats *runccgroup.Stats, previousCPU, now, previousSystem uint64) float64 {
var (
diff --git a/libpod/stats_unsupported.go b/libpod/stats_unsupported.go
index b23333c2e..3094e2eaa 100644
--- a/libpod/stats_unsupported.go
+++ b/libpod/stats_unsupported.go
@@ -1,5 +1,5 @@
-//go:build !linux
-// +build !linux
+//go:build !linux && !freebsd
+// +build !linux,!freebsd
package libpod
diff --git a/pkg/rctl/rctl.go b/pkg/rctl/rctl.go
new file mode 100644
index 000000000..135cc60cb
--- /dev/null
+++ b/pkg/rctl/rctl.go
@@ -0,0 +1,47 @@
+//go:build freebsd
+// +build freebsd
+
+package rctl
+
+// #include <sys/rctl.h>
+import "C"
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+ "strings"
+ "syscall"
+ "unsafe"
+
+ "github.com/sirupsen/logrus"
+)
+
+func GetRacct(filter string) (map[string]uint64, error) {
+ bp, err := syscall.ByteSliceFromString(filter)
+ if err != nil {
+ return nil, err
+ }
+ var buf [1024]byte
+ _, _, errno := syscall.Syscall6(syscall.SYS_RCTL_GET_RACCT,
+ uintptr(unsafe.Pointer(&bp[0])),
+ uintptr(len(bp)),
+ uintptr(unsafe.Pointer(&buf[0])),
+ uintptr(len(buf)), 0, 0)
+ if errno != 0 {
+ return nil, fmt.Errorf("error calling rctl_get_racct with filter %s: %v", errno)
+ }
+ len := bytes.IndexByte(buf[:], byte(0))
+ entries := strings.Split(string(buf[:len]), ",")
+ res := make(map[string]uint64)
+ for _, entry := range entries {
+ kv := strings.SplitN(entry, "=", 2)
+ key := kv[0]
+ val, err := strconv.ParseUint(kv[1], 10, 0)
+ if err != nil {
+ logrus.Warnf("unexpected rctl entry, ignoring: %s", entry)
+ }
+ res[key] = val
+ }
+ return res, nil
+}