diff options
-rw-r--r-- | docs/source/markdown/podman-info.1.md | 6 | ||||
-rw-r--r-- | libpod/define/info.go | 25 | ||||
-rw-r--r-- | libpod/info.go | 75 | ||||
-rw-r--r-- | libpod/info_test.go | 59 |
4 files changed, 149 insertions, 16 deletions
diff --git a/docs/source/markdown/podman-info.1.md b/docs/source/markdown/podman-info.1.md index 4f09913b8..fc2d0fa60 100644 --- a/docs/source/markdown/podman-info.1.md +++ b/docs/source/markdown/podman-info.1.md @@ -39,6 +39,10 @@ host: package: conmon-2.0.29-2.fc34.x86_64 path: /usr/bin/conmon version: 'conmon version 2.0.29, commit: ' + cpu_utilization: + idle_percent: 96.84 + system_percent: 0.71 + user_percent: 2.45 cpus: 8 distribution: distribution: fedora @@ -124,6 +128,8 @@ store: graphDriverName: overlay graphOptions: {} graphRoot: /home/dwalsh/.local/share/containers/storage + graphRootAllocated: 510389125120 + graphRootUsed: 129170714624 graphStatus: Backing Filesystem: extfs Native Overlay Diff: "true" diff --git a/libpod/define/info.go b/libpod/define/info.go index 713129ada..911fa5c03 100644 --- a/libpod/define/info.go +++ b/libpod/define/info.go @@ -1,6 +1,8 @@ package define -import "github.com/containers/storage/pkg/idtools" +import ( + "github.com/containers/storage/pkg/idtools" +) // Info is the overall struct that describes the host system // running libpod/podman @@ -31,6 +33,7 @@ type HostInfo struct { CgroupControllers []string `json:"cgroupControllers"` Conmon *ConmonInfo `json:"conmon"` CPUs int `json:"cpus"` + CPUUtilization *CPUUsage `json:"cpuUtilization"` Distribution DistributionInfo `json:"distribution"` EventLogger string `json:"eventLogger"` Hostname string `json:"hostname"` @@ -108,11 +111,15 @@ type StoreInfo struct { GraphDriverName string `json:"graphDriverName"` GraphOptions map[string]interface{} `json:"graphOptions"` GraphRoot string `json:"graphRoot"` - GraphStatus map[string]string `json:"graphStatus"` - ImageCopyTmpDir string `json:"imageCopyTmpDir"` - ImageStore ImageStore `json:"imageStore"` - RunRoot string `json:"runRoot"` - VolumePath string `json:"volumePath"` + // GraphRootAllocated is how much space the graphroot has in bytes + GraphRootAllocated uint64 `json:"graphRootAllocated"` + // GraphRootUsed is how much of graphroot is used in bytes + GraphRootUsed uint64 `json:"graphRootUsed"` + GraphStatus map[string]string `json:"graphStatus"` + ImageCopyTmpDir string `json:"imageCopyTmpDir"` + ImageStore ImageStore `json:"imageStore"` + RunRoot string `json:"runRoot"` + VolumePath string `json:"volumePath"` } // ImageStore describes the image store. Right now only the number @@ -137,3 +144,9 @@ type Plugins struct { // FIXME what should we do with Authorization, docker seems to return nothing by default // Authorization []string `json:"authorization"` } + +type CPUUsage struct { + UserPercent float64 `json:"userPercent"` + SystemPercent float64 `json:"systemPercent"` + IdlePercent float64 `json:"idlePercent"` +} diff --git a/libpod/info.go b/libpod/info.go index e0b490768..321680a81 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -5,11 +5,13 @@ import ( "bytes" "fmt" "io/ioutil" + "math" "os" "os/exec" "runtime" "strconv" "strings" + "syscall" "time" "github.com/containers/buildah" @@ -115,7 +117,10 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { if err != nil { return nil, errors.Wrapf(err, "error getting available cgroup controllers") } - + cpuUtil, err := getCPUUtilization() + if err != nil { + return nil, err + } info := define.HostInfo{ Arch: runtime.GOARCH, BuildahVersion: buildah.Version, @@ -123,6 +128,7 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { CgroupControllers: availableControllers, Linkmode: linkmode.Linkmode(), CPUs: runtime.NumCPU(), + CPUUtilization: cpuUtil, Distribution: hostDistributionInfo, LogDriver: r.config.Containers.LogDriver, EventLogger: r.eventer.String(), @@ -285,17 +291,25 @@ func (r *Runtime) storeInfo() (*define.StoreInfo, error) { } imageInfo := define.ImageStore{Number: len(images)} + var grStats syscall.Statfs_t + if err := syscall.Statfs(r.store.GraphRoot(), &grStats); err != nil { + return nil, errors.Wrapf(err, "unable to collect graph root usasge for %q", r.store.GraphRoot()) + } + allocated := uint64(grStats.Bsize) * grStats.Blocks info := define.StoreInfo{ - ImageStore: imageInfo, - ImageCopyTmpDir: os.Getenv("TMPDIR"), - ContainerStore: conInfo, - GraphRoot: r.store.GraphRoot(), - RunRoot: r.store.RunRoot(), - GraphDriverName: r.store.GraphDriverName(), - GraphOptions: nil, - VolumePath: r.config.Engine.VolumePath, - ConfigFile: configFile, + ImageStore: imageInfo, + ImageCopyTmpDir: os.Getenv("TMPDIR"), + ContainerStore: conInfo, + GraphRoot: r.store.GraphRoot(), + GraphRootAllocated: allocated, + GraphRootUsed: allocated - (uint64(grStats.Bsize) * grStats.Bfree), + RunRoot: r.store.RunRoot(), + GraphDriverName: r.store.GraphDriverName(), + GraphOptions: nil, + VolumePath: r.config.Engine.VolumePath, + ConfigFile: configFile, } + graphOptions := map[string]interface{}{} for _, o := range r.store.GraphOptions() { split := strings.SplitN(o, "=", 2) @@ -382,3 +396,44 @@ func (r *Runtime) GetHostDistributionInfo() define.DistributionInfo { } return dist } + +// getCPUUtilization Returns a CPUUsage object that summarizes CPU +// usage for userspace, system, and idle time. +func getCPUUtilization() (*define.CPUUsage, error) { + f, err := os.Open("/proc/stat") + if err != nil { + return nil, err + } + defer f.Close() + scanner := bufio.NewScanner(f) + // Read firt line of /proc/stat + for scanner.Scan() { + break + } + // column 1 is user, column 3 is system, column 4 is idle + stats := strings.Split(scanner.Text(), " ") + return statToPercent(stats) +} + +func statToPercent(stats []string) (*define.CPUUsage, error) { + // There is always an extra space between cpu and the first metric + userTotal, err := strconv.ParseFloat(stats[2], 64) + if err != nil { + return nil, errors.Wrapf(err, "unable to parse user value %q", stats[1]) + } + systemTotal, err := strconv.ParseFloat(stats[4], 64) + if err != nil { + return nil, errors.Wrapf(err, "unable to parse system value %q", stats[3]) + } + idleTotal, err := strconv.ParseFloat(stats[5], 64) + if err != nil { + return nil, errors.Wrapf(err, "unable to parse idle value %q", stats[4]) + } + total := userTotal + systemTotal + idleTotal + s := define.CPUUsage{ + UserPercent: math.Round((userTotal/total*100)*100) / 100, + SystemPercent: math.Round((systemTotal/total*100)*100) / 100, + IdlePercent: math.Round((idleTotal/total*100)*100) / 100, + } + return &s, nil +} diff --git a/libpod/info_test.go b/libpod/info_test.go new file mode 100644 index 000000000..909b573c0 --- /dev/null +++ b/libpod/info_test.go @@ -0,0 +1,59 @@ +package libpod + +import ( + "fmt" + "testing" + + "github.com/containers/podman/v4/libpod/define" + "github.com/stretchr/testify/assert" +) + +func Test_statToPercent(t *testing.T) { + type args struct { + in0 []string + } + tests := []struct { + name string + args args + want *define.CPUUsage + wantErr assert.ErrorAssertionFunc + }{ + { + name: "GoodParse", + args: args{in0: []string{"cpu", " ", "33628064", "27537", "9696996", "1314806705", "588142", "4775073", "2789228", "0", "598711", "0"}}, + want: &define.CPUUsage{ + UserPercent: 2.48, + SystemPercent: 0.71, + IdlePercent: 96.81, + }, + wantErr: assert.NoError, + }, + { + name: "BadUserValue", + args: args{in0: []string{"cpu", " ", "k", "27537", "9696996", "1314806705", "588142", "4775073", "2789228", "0", "598711", "0"}}, + want: nil, + wantErr: assert.Error, + }, + { + name: "BadSystemValue", + args: args{in0: []string{"cpu", " ", "33628064", "27537", "k", "1314806705", "588142", "4775073", "2789228", "0", "598711", "0"}}, + want: nil, + wantErr: assert.Error, + }, + { + name: "BadIdleValue", + args: args{in0: []string{"cpu", " ", "33628064", "27537", "9696996", "k", "588142", "4775073", "2789228", "0", "598711", "0"}}, + want: nil, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := statToPercent(tt.args.in0) + if !tt.wantErr(t, err, fmt.Sprintf("statToPercent(%v)", tt.args.in0)) { + return + } + assert.Equalf(t, tt.want, got, "statToPercent(%v)", tt.args.in0) + }) + } +} |