summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
authorbaude <bbaude@redhat.com>2017-12-04 15:06:06 -0600
committerAtomic Bot <atomic-devel@projectatomic.io>2017-12-13 21:15:09 +0000
commit9cc0c8ae143070c4d4ee691cab6a0fd0ebcaa538 (patch)
tree9736c47af7246c93e2e2b6d6d246653ca57cc316 /libpod
parent61f606e192ad67b4819a909a6f1b18c41e33db2d (diff)
downloadpodman-9cc0c8ae143070c4d4ee691cab6a0fd0ebcaa538.tar.gz
podman-9cc0c8ae143070c4d4ee691cab6a0fd0ebcaa538.tar.bz2
podman-9cc0c8ae143070c4d4ee691cab6a0fd0ebcaa538.zip
kpod stats
Move kpod stats to the libpod backend. Signed-off-by: baude <bbaude@redhat.com> Closes: #113 Approved by: baude
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container.go11
-rw-r--r--libpod/runtime_ctr.go28
-rw-r--r--libpod/stats.go120
-rw-r--r--libpod/util.go20
-rw-r--r--libpod/util_test.go10
5 files changed, 188 insertions, 1 deletions
diff --git a/libpod/container.go b/libpod/container.go
index d53a863c0..604b5fe10 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -11,6 +11,7 @@ import (
"syscall"
"time"
+ "github.com/containerd/cgroups"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/docker/docker/daemon/caps"
@@ -55,6 +56,9 @@ const (
artifactsDir = "artifacts"
)
+// CGroupParent is the prefix to a cgroup path in libpod
+var CGroupParent = "/libpod_parent"
+
// Container is a single OCI container
type Container struct {
config *ContainerConfig
@@ -577,7 +581,7 @@ func (c *Container) Init() (err error) {
// With the spec complete, do an OCI create
// TODO set cgroup parent in a sane fashion
- if err := c.runtime.ociRuntime.createContainer(c, "/libpod_parent"); err != nil {
+ if err := c.runtime.ociRuntime.createContainer(c, CGroupParent); err != nil {
return err
}
@@ -1044,3 +1048,8 @@ func (c *Container) cleanupStorage() error {
return c.save()
}
+
+// CGroupPath returns a cgroups "path" for a given container.
+func (c *Container) CGroupPath() cgroups.Path {
+ return cgroups.StaticPath(filepath.Join(CGroupParent, fmt.Sprintf("libpod-conmon-%s", c.ID())))
+}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index f314b302f..320821b38 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -232,3 +232,31 @@ func (r *Runtime) GetContainers(filters ...ContainerFilter) ([]*Container, error
return ctrsFiltered, nil
}
+
+// GetAllContainers is a helper function for GetContainers
+func (r *Runtime) GetAllContainers() ([]*Container, error) {
+ return r.state.AllContainers()
+}
+
+// GetRunningContainers is a helper function for GetContainers
+func (r *Runtime) GetRunningContainers() ([]*Container, error) {
+ running := func(c *Container) bool {
+ state, _ := c.State()
+ return state == ContainerStateRunning
+ }
+ return r.GetContainers(running)
+}
+
+// GetContainersByList is a helper function for GetContainers
+// which takes a []string of container IDs or names
+func (r *Runtime) GetContainersByList(containers []string) ([]*Container, error) {
+ var ctrs []*Container
+ for _, inputContainer := range containers {
+ ctr, err := r.LookupContainer(inputContainer)
+ if err != nil {
+ return ctrs, errors.Wrapf(err, "unable to lookup container %s", inputContainer)
+ }
+ ctrs = append(ctrs, ctr)
+ }
+ return ctrs, nil
+}
diff --git a/libpod/stats.go b/libpod/stats.go
new file mode 100644
index 000000000..86da0679e
--- /dev/null
+++ b/libpod/stats.go
@@ -0,0 +1,120 @@
+package libpod
+
+import (
+ "strings"
+ "syscall"
+ "time"
+
+ "github.com/containerd/cgroups"
+ "github.com/opencontainers/runc/libcontainer"
+ "github.com/pkg/errors"
+)
+
+// ContainerStats contains the statistics information for a running container
+type ContainerStats struct {
+ ContainerID string
+ CPU float64
+ CPUNano uint64
+ SystemNano uint64
+ MemUsage uint64
+ MemLimit uint64
+ MemPerc float64
+ NetInput uint64
+ NetOutput uint64
+ BlockInput uint64
+ BlockOutput uint64
+ PIDs uint64
+}
+
+// GetContainerStats gets the running stats for a given container
+func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) {
+ stats := new(ContainerStats)
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ if err := c.syncContainer(); err != nil {
+ return stats, errors.Wrapf(err, "error updating container %s state", c.ID())
+ }
+ cgroup, err := cgroups.Load(cgroups.V1, c.CGroupPath())
+ if err != nil {
+ return stats, errors.Wrapf(err, "unable to load cgroup at %+v", c.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")
+ }
+
+ previousCPU := previousStats.CPUNano
+ previousSystem := previousStats.SystemNano
+ stats.ContainerID = c.ID()
+ 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 - 1
+ }
+ stats.BlockInput, stats.BlockOutput = calculateBlockIO(cgroupStats)
+ stats.CPUNano = cgroupStats.Cpu.Usage.Total
+ stats.SystemNano = cgroupStats.Cpu.Usage.Kernel
+ // TODO Figure out where to get the Netout stuff.
+ //stats.NetInput, stats.NetOutput = getContainerNetIO(cgroupStats)
+ 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
+}
+
+// Returns the total number of bytes transmitted and received for the given container stats
+func getContainerNetIO(stats *libcontainer.Stats) (received uint64, transmitted uint64) { //nolint
+ for _, iface := range stats.Interfaces {
+ received += iface.RxBytes
+ transmitted += iface.TxBytes
+ }
+ return
+}
+
+func calculateCPUPercent(stats *cgroups.Stats, 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.Stats) (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/util.go b/libpod/util.go
index 61089b525..de5c3ff31 100644
--- a/libpod/util.go
+++ b/libpod/util.go
@@ -9,6 +9,8 @@ import (
"github.com/containers/image/signature"
"github.com/containers/image/types"
+ "github.com/pkg/errors"
+ "strconv"
)
// Runtime API constants
@@ -76,3 +78,21 @@ func GetPolicyContext(path string) (*signature.PolicyContext, error) {
}
return signature.NewPolicyContext(policy)
}
+
+// RemoveScientificNotationFromFloat returns a float without any
+// scientific notation if the number has any.
+// golang does not handle conversion of float64s that have scientific
+// notation in them and otherwise stinks. please replace this if you have
+// a better implementation.
+func RemoveScientificNotationFromFloat(x float64) (float64, error) {
+ bigNum := strconv.FormatFloat(x, 'g', -1, 64)
+ breakPoint := strings.IndexAny(bigNum, "Ee")
+ if breakPoint > 0 {
+ bigNum = bigNum[:breakPoint]
+ }
+ result, err := strconv.ParseFloat(bigNum, 64)
+ if err != nil {
+ return x, errors.Wrapf(err, "unable to remove scientific number from calculations")
+ }
+ return result, nil
+}
diff --git a/libpod/util_test.go b/libpod/util_test.go
index 24e5fdfac..7b9d19a43 100644
--- a/libpod/util_test.go
+++ b/libpod/util_test.go
@@ -17,3 +17,13 @@ func TestStringInSlice(t *testing.T) {
// string is not in empty slice
assert.False(t, StringInSlice("one", []string{}))
}
+
+func TestRemoveScientificNotationFromFloat(t *testing.T) {
+ numbers := []float64{0.0, .5, 1.99999932, 1.04e+10}
+ results := []float64{0.0, .5, 1.99999932, 1.04}
+ for i, x := range numbers {
+ result, err := RemoveScientificNotationFromFloat(x)
+ assert.NoError(t, err)
+ assert.Equal(t, result, results[i])
+ }
+}