From 3226561cf3960d76aa633daf134dbfc3ec9e7fa6 Mon Sep 17 00:00:00 2001 From: Ashley Cui Date: Thu, 21 Apr 2022 09:09:49 -0400 Subject: Allow changing of CPUs, Memory, and Disk Size Allow podman machine set to change CPUs, Memory and Disk size of a QEMU machine after its been created. Disk size can only be increased. If one setting fails to be changed, the other settings will still be applied. Signed-off-by: Ashley Cui --- docs/source/markdown/podman-machine-set.1.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/source/markdown/podman-machine-set.1.md b/docs/source/markdown/podman-machine-set.1.md index a4918eacf..de90ee4b0 100644 --- a/docs/source/markdown/podman-machine-set.1.md +++ b/docs/source/markdown/podman-machine-set.1.md @@ -8,17 +8,29 @@ podman\-machine\-set - Sets a virtual machine setting ## DESCRIPTION -Sets an updatable virtual machine setting. - -Options mirror values passed to `podman machine init`. Only a limited -subset can be changed after machine initialization. +Change a machine setting. ## OPTIONS +#### **--cpus**=*number* + +Number of CPUs. +Only supported for QEMU machines. + +#### **--disk-size**=*number* + +Size of the disk for the guest VM in GB. +Can only be increased. Only supported for QEMU machines. + #### **--help** Print usage statement. +#### **--memory**, **-m**=*number* + +Memory (in MB). +Only supported for QEMU machines. + #### **--rootful**=*true|false* Whether this machine should prefer rootful (`true`) or rootless (`false`) -- cgit v1.2.3-54-g00ecf From 4ff6884fb85d8a95f5a90848e92c3ff9ea6bcf57 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 27 Apr 2022 10:39:47 -0400 Subject: Add CreatedSince & CreatedAt format fields to podman image history Fixes: https://github.com/containers/podman/issues/14012 Signed-off-by: Daniel J Walsh --- cmd/podman/images/history.go | 8 ++++++++ docs/source/markdown/podman-history.1.md | 9 +++++---- test/system/110-history.bats | 13 +++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index c05d3475b..26a4b6c3b 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -168,3 +168,11 @@ func (h historyReporter) ID() string { } return h.ImageHistoryLayer.ID } + +func (h historyReporter) CreatedAt() string { + return time.Unix(h.ImageHistoryLayer.Created.Unix(), 0).UTC().String() +} + +func (h historyReporter) CreatedSince() string { + return h.Created() +} diff --git a/docs/source/markdown/podman-history.1.md b/docs/source/markdown/podman-history.1.md index af35814c2..16f1e48e6 100644 --- a/docs/source/markdown/podman-history.1.md +++ b/docs/source/markdown/podman-history.1.md @@ -23,10 +23,11 @@ Valid placeholders for the Go template are listed below: | --------------- | ----------------------------------------------------------------------------- | | .ID | Image ID | | .Created | if --human, time elapsed since creation, otherwise time stamp of creation | -| .CreatedBy | Command used to create the layer | -| .Size | Size of layer on disk | -| .Comment | Comment for the layer | - +| .CreatedAt | Time when the image layer was created | +| .CreatedBy | Command used to create the layer | +| .CreatedSince | Elapsed time since the image layer was created | +| .Size | Size of layer on disk | +| .Comment | Comment for the layer | ## OPTIONS Print the numeric IDs only (default *false*). diff --git a/test/system/110-history.bats b/test/system/110-history.bats index 0f6d75cb3..da6f2177c 100644 --- a/test/system/110-history.bats +++ b/test/system/110-history.bats @@ -55,4 +55,17 @@ size | -\\\?[0-9]\\\+ } +@test "podman image history Created" { + # Values from image LIST + run_podman image list --format '{{.CreatedSince}}--{{.CreatedAt}}' $IMAGE + from_imagelist="$output" + assert "$from_imagelist" =~ "^[0-9].* ago--[0-9]+-[0-9]+-[0-9]+ [0-9:]+ " \ + "CreatedSince and CreatedAt look reasonable" + + # Values from image HISTORY + run_podman image history --format '{{.CreatedSince}}--{{.CreatedAt}}' $IMAGE + assert "${lines[0]}" == "$from_imagelist" \ + "CreatedSince and CreatedAt from image history should == image list" +} + # vim: filetype=sh -- cgit v1.2.3-54-g00ecf From 77f147468c4bf514dd5a2951a29128f226d1186c Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Thu, 28 Apr 2022 09:40:46 +0200 Subject: podman search: truncate by default Truncate by default to avoid long descriptions from rendering the output unreadable. [NO NEW TESTS NEEDED] Fixes: #14044 Signed-off-by: Valentin Rothberg --- cmd/podman/images/search.go | 6 +++--- docs/source/markdown/podman-search.1.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index aa11cf254..7f202cb6a 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -23,6 +23,7 @@ type searchOptionsWrapper struct { Compatible bool // Docker compat TLSVerifyCLI bool // Used to convert to an optional bool later Format string // For go templating + NoTrunc bool } // listEntryTag is a utility structure used for json serialization. @@ -92,7 +93,7 @@ func searchFlags(cmd *cobra.Command) { flags.IntVar(&searchOptions.Limit, limitFlagName, 0, "Limit the number of results") _ = cmd.RegisterFlagCompletionFunc(limitFlagName, completion.AutocompleteNone) - flags.Bool("no-trunc", true, "Do not truncate the output. Default: true") + flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") flags.BoolVar(&searchOptions.Compatible, "compatible", false, "List stars, official and automated columns (Docker compatibility)") authfileFlagName := "authfile" @@ -139,11 +140,10 @@ func imageSearch(cmd *cobra.Command, args []string) error { return nil } - noTrunc, _ := cmd.Flags().GetBool("no-trunc") isJSON := report.IsJSON(searchOptions.Format) for i, element := range searchReport { d := strings.ReplaceAll(element.Description, "\n", " ") - if len(d) > 44 && !(noTrunc || isJSON) { + if len(d) > 44 && !(searchOptions.NoTrunc || isJSON) { d = strings.TrimSpace(d[:44]) + "..." } searchReport[i].Description = d diff --git a/docs/source/markdown/podman-search.1.md b/docs/source/markdown/podman-search.1.md index 81a67d762..5b49d7f8e 100644 --- a/docs/source/markdown/podman-search.1.md +++ b/docs/source/markdown/podman-search.1.md @@ -90,7 +90,7 @@ The result contains the Image name and its tag, one line for every tag associate #### **--no-trunc** -Do not truncate the output (default *true*). +Do not truncate the output (default *false*). #### **--tls-verify** -- cgit v1.2.3-54-g00ecf From e5d6b6b0a2cfdbb21506b8f5e741cf719c149cb4 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Thu, 28 Apr 2022 11:39:12 +0200 Subject: volume: add new option -o o=noquota add a new option to completely disable xfs quota usage for a volume. xfs quota set on a volume, even just for tracking disk usage, can cause weird errors if the volume is later re-used by a container with a different quota projid. More specifically, link(2) and rename(2) might fail with EXDEV if the source file has a projid that is different from the parent directory. To prevent such kind of issues, the volume should be created beforehand with `podman volume create -o o=noquota $ID` Closes: https://github.com/containers/podman/issues/14049 Signed-off-by: Giuseppe Scrivano --- docs/source/markdown/podman-volume-create.1.md | 3 +- libpod/options.go | 13 +++++++++ libpod/runtime_volume_linux.go | 39 +++++++++++++++----------- libpod/volume.go | 3 ++ libpod/volume_internal.go | 3 ++ pkg/domain/infra/abi/parse/parse.go | 5 ++++ test/test_podman_baseline.sh | 22 +++++++++++++++ 7 files changed, 70 insertions(+), 18 deletions(-) (limited to 'docs') diff --git a/docs/source/markdown/podman-volume-create.1.md b/docs/source/markdown/podman-volume-create.1.md index 365a5acac..06fadcaa1 100644 --- a/docs/source/markdown/podman-volume-create.1.md +++ b/docs/source/markdown/podman-volume-create.1.md @@ -38,7 +38,8 @@ The `device` option sets the device to be mounted, and is equivalent to the `dev The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with these exceptions: - The `o` option supports `uid` and `gid` options to set the UID and GID of the created volume that are not normally supported by **mount(8)**. - - The `o` option supports the `size` option to set the maximum size of the created volume and the `inodes` option to set the maximum number of inodes for the volume. Currently these flags are only supported on "xfs" file system mounted with the `prjquota` flag described in the **xfs_quota(8)** man page. + - The `o` option supports the `size` option to set the maximum size of the created volume, the `inodes` option to set the maximum number of inodes for the volume and `noquota` to completely disable quota support even for tracking of disk usage. Currently these flags are only supported on "xfs" file system mounted with the `prjquota` flag described in the **xfs_quota(8)** man page. + - The `o` option supports . - Using volume options other then the UID/GID options with the **local** driver requires root privileges. When not using the **local** driver, the given options are passed directly to the volume plugin. In this case, supported options are dictated by the plugin in question, not Podman. diff --git a/libpod/options.go b/libpod/options.go index 57e2d7cf6..98eb45e76 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1634,6 +1634,19 @@ func WithVolumeNoChown() VolumeCreateOption { } } +// WithVolumeDisableQuota prevents the volume from being assigned a quota. +func WithVolumeDisableQuota() VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.DisableQuota = true + + return nil + } +} + // withSetAnon sets a bool notifying libpod that this volume is anonymous and // should be removed when containers using it are removed and volumes are // specified for removal. diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index 241f6e2f2..f8788e183 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -73,7 +73,7 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE return nil, errors.Wrapf(err, "invalid volume option %s for driver 'local'", key) } } - case "o", "type", "uid", "gid", "size", "inodes": + case "o", "type", "uid", "gid", "size", "inodes", "noquota": // Do nothing, valid keys default: return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key) @@ -111,23 +111,28 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE if err := LabelVolumePath(fullVolPath); err != nil { return nil, err } - projectQuotaSupported := false - - q, err := quota.NewControl(r.config.Engine.VolumePath) - if err == nil { - projectQuotaSupported = true - } - quota := quota.Quota{} - if volume.config.Size > 0 || volume.config.Inodes > 0 { - if !projectQuotaSupported { - return nil, errors.New("Volume options size and inodes not supported. Filesystem does not support Project Quota") + if volume.config.DisableQuota { + if volume.config.Size > 0 || volume.config.Inodes > 0 { + return nil, errors.New("volume options size and inodes cannot be used without quota") } - quota.Size = volume.config.Size - quota.Inodes = volume.config.Inodes - } - if projectQuotaSupported { - if err := q.SetQuota(fullVolPath, quota); err != nil { - return nil, errors.Wrapf(err, "failed to set size quota size=%d inodes=%d for volume directory %q", volume.config.Size, volume.config.Inodes, fullVolPath) + } else { + projectQuotaSupported := false + q, err := quota.NewControl(r.config.Engine.VolumePath) + if err == nil { + projectQuotaSupported = true + } + quota := quota.Quota{} + if volume.config.Size > 0 || volume.config.Inodes > 0 { + if !projectQuotaSupported { + return nil, errors.New("volume options size and inodes not supported. Filesystem does not support Project Quota") + } + quota.Size = volume.config.Size + quota.Inodes = volume.config.Inodes + } + if projectQuotaSupported { + if err := q.SetQuota(fullVolPath, quota); err != nil { + return nil, errors.Wrapf(err, "failed to set size quota size=%d inodes=%d for volume directory %q", volume.config.Size, volume.config.Inodes, fullVolPath) + } } } diff --git a/libpod/volume.go b/libpod/volume.go index bffafdc15..ab461a37f 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -52,6 +52,9 @@ type VolumeConfig struct { Size uint64 `json:"size"` // Inodes maximum of the volume. Inodes uint64 `json:"inodes"` + // DisableQuota indicates that the volume should completely disable using any + // quota tracking. + DisableQuota bool `json:"disableQuota,omitempty"` } // VolumeState holds the volume's mutable state. diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go index 9850c2ea1..e0ebb729d 100644 --- a/libpod/volume_internal.go +++ b/libpod/volume_internal.go @@ -52,6 +52,9 @@ func (v *Volume) needsMount() bool { if _, ok := v.config.Options["SIZE"]; ok { index++ } + if _, ok := v.config.Options["NOQUOTA"]; ok { + index++ + } // when uid or gid is set there is also the "o" option // set so we have to ignore this one as well if index > 0 { diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go index 2d1adab74..3bac2ef99 100644 --- a/pkg/domain/infra/abi/parse/parse.go +++ b/pkg/domain/infra/abi/parse/parse.go @@ -73,6 +73,11 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) finalVal = append(finalVal, o) // set option "GID": "$gid" volumeOptions["GID"] = splitO[1] + case "noquota": + logrus.Debugf("Removing noquota from options and adding WithVolumeDisableQuota") + libpodOptions = append(libpodOptions, libpod.WithVolumeDisableQuota()) + // set option "NOQUOTA": "true" + volumeOptions["NOQUOTA"] = "true" default: finalVal = append(finalVal, o) } diff --git a/test/test_podman_baseline.sh b/test/test_podman_baseline.sh index 5a420fe60..5ef2d1bda 100755 --- a/test/test_podman_baseline.sh +++ b/test/test_podman_baseline.sh @@ -309,6 +309,28 @@ else echo "Overlay test within limits failed" fi +before=`xfs_quota -x -c 'report -N -p' $TMPDIR | grep -c ^#` +podman $PODMANBASE volume create -o o=noquota test-no-quota +after=`xfs_quota -x -c 'report -N -p' $TMPDIR | grep -c ^#` + +if [ $before != $after ]; +then + echo "Test -o=noquota doesn't create a projid failed" +else + echo "Test -o=noquota doesn't create a projid passed" +fi + +before=`xfs_quota -x -c 'report -N -p' $TMPDIR | grep -c ^#` +podman $PODMANBASE volume create -o test-no-quota +after=`xfs_quota -x -c 'report -N -p' $TMPDIR | grep -c ^#` + +if [ $before == $after ]; +then + echo "Test without -o=noquota creates a projid failed" +else + echo "Test without -o=noquota creates a projid passed" +fi + ######## # Expected to fail ######## -- cgit v1.2.3-54-g00ecf From 8842e9b7abde1135b2b4a7b876aa8897a624b397 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Thu, 28 Apr 2022 12:31:17 -0500 Subject: Additional stats for podman info In support of podman machine and its counterpart desktop, we have added new stats to podman info. For storage, we have added GraphRootAllocated and GraphRootUsed in bytes. For CPUs, we have added user, system, and idle percents based on /proc/stat. Fixes: #13876 Signed-off-by: Brent Baude --- docs/source/markdown/podman-info.1.md | 6 +++ libpod/define/info.go | 25 +++++++++--- libpod/info.go | 75 ++++++++++++++++++++++++++++++----- libpod/info_test.go | 59 +++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 libpod/info_test.go (limited to 'docs') 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) + }) + } +} -- cgit v1.2.3-54-g00ecf