From b5a235df900a6471895111b4de5f80732f7f563a Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 4 May 2020 14:10:30 -0500 Subject: v2 podman stats Signed-off-by: baude --- cmd/podman/containers/ps.go | 2 +- cmd/podman/containers/stats.go | 244 ++++++++++++++++++++++++++++ libpod/define/containerstate.go | 19 +++ libpod/pod.go | 10 +- libpod/stats.go | 4 +- libpod/stats_config.go | 20 --- libpod/stats_unsupported.go | 2 +- libpod/util.go | 20 --- libpod/util_test.go | 3 +- pkg/api/handlers/compat/containers_stats.go | 2 +- pkg/domain/entities/containers.go | 11 ++ pkg/domain/entities/engine_container.go | 1 + pkg/domain/infra/abi/containers.go | 78 ++++++++- pkg/domain/infra/abi/pods_stats.go | 3 +- pkg/domain/infra/tunnel/containers.go | 4 + pkg/varlinkapi/containers.go | 2 +- pkg/varlinkapi/pods.go | 8 +- pkg/varlinkapi/remote_client.go | 6 +- test/e2e/stats_test.go | 1 - utils/utils.go | 19 +++ 20 files changed, 396 insertions(+), 63 deletions(-) create mode 100644 cmd/podman/containers/stats.go delete mode 100644 libpod/stats_config.go diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index c5696a158..fd0b68195 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -206,7 +206,7 @@ func ps(cmd *cobra.Command, args []string) error { return err } if err := tmpl.Execute(w, responses); err != nil { - return nil + return err } if err := w.Flush(); err != nil { return err diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go new file mode 100644 index 000000000..3f9db671f --- /dev/null +++ b/cmd/podman/containers/stats.go @@ -0,0 +1,244 @@ +package containers + +import ( + "fmt" + "os" + "strings" + "text/tabwriter" + "text/template" + + tm "github.com/buger/goterm" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/utils" + "github.com/docker/go-units" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers." + statsCommand = &cobra.Command{ + Use: "stats [flags] [CONTAINER...]", + Short: "Display a live stream of container resource usage statistics", + Long: statsDescription, + RunE: stats, + Args: checkStatOptions, + Example: `podman stats --all --no-stream + podman stats ctrID + podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, + } + + containerStatsCommand = &cobra.Command{ + Use: statsCommand.Use, + Short: statsCommand.Short, + Long: statsCommand.Long, + RunE: statsCommand.RunE, + Args: checkStatOptions, + Example: `podman container stats --all --no-stream + podman container stats ctrID + podman container stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, + } +) + +var ( + statsOptions entities.ContainerStatsOptions + defaultStatsRow = "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" + defaultStatsHeader = "ID\tNAME\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET IO\tBLOCK IO\tPIDS\n" +) + +func statFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&statsOptions.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false") + flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + flags.BoolVarP(&statsOptions.Latest, "latest", "l", false, "Act on the latest container Podman is aware of") + flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen between intervals") + flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: statsCommand, + }) + flags := statsCommand.Flags() + statFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerStatsCommand, + Parent: containerCmd, + }) + + containerStatsFlags := containerStatsCommand.Flags() + statFlags(containerStatsFlags) +} + +// stats is different in that it will assume running containers if +// no input is given, so we need to validate differently +func checkStatOptions(cmd *cobra.Command, args []string) error { + opts := 0 + if statsOptions.All { + opts += 1 + } + if statsOptions.Latest { + opts += 1 + } + if len(args) > 0 { + opts += 1 + } + if opts > 1 { + return errors.Errorf("--all, --latest and containers cannot be used together") + } + return nil +} + +func stats(cmd *cobra.Command, args []string) error { + if rootless.IsRootless() { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return err + } + if !unified { + return errors.New("stats is not supported in rootless mode without cgroups v2") + } + } + statsOptions.StatChan = make(chan []*define.ContainerStats, 1) + go func() { + for reports := range statsOptions.StatChan { + if err := outputStats(reports); err != nil { + logrus.Error(err) + } + } + }() + return registry.ContainerEngine().ContainerStats(registry.Context(), args, statsOptions) +} + +func outputStats(reports []*define.ContainerStats) error { + if len(statsOptions.Format) < 1 && !statsOptions.NoReset { + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + } + var stats []*containerStats + for _, r := range reports { + stats = append(stats, &containerStats{r}) + } + if statsOptions.Format == "json" { + return outputJSON(stats) + } + format := defaultStatsRow + if len(statsOptions.Format) > 0 { + format = statsOptions.Format + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + } + format = "{{range . }}" + format + "{{end}}" + if len(statsOptions.Format) < 1 { + format = defaultStatsHeader + format + } + tmpl, err := template.New("stats").Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + if err := tmpl.Execute(w, stats); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + return nil +} + +type containerStats struct { + *define.ContainerStats +} + +func (s *containerStats) ID() string { + return s.ContainerID[0:12] +} + +func (s *containerStats) CPUPerc() string { + return floatToPercentString(s.CPU) +} + +func (s *containerStats) MemPerc() string { + return floatToPercentString(s.ContainerStats.MemPerc) +} + +func (s *containerStats) NetIO() string { + return combineHumanValues(s.NetInput, s.NetOutput) +} + +func (s *containerStats) BlockIO() string { + return combineHumanValues(s.BlockInput, s.BlockOutput) +} + +func (s *containerStats) PIDS() string { + if s.PIDs == 0 { + // If things go bazinga, return a safe value + return "--" + } + return fmt.Sprintf("%d", s.PIDs) +} +func (s *containerStats) MemUsage() string { + return combineHumanValues(s.ContainerStats.MemUsage, s.ContainerStats.MemLimit) +} + +func floatToPercentString(f float64) string { + strippedFloat, err := utils.RemoveScientificNotationFromFloat(f) + if err != nil || strippedFloat == 0 { + // If things go bazinga, return a safe value + return "--" + } + return fmt.Sprintf("%.2f", strippedFloat) + "%" +} + +func combineHumanValues(a, b uint64) string { + if a == 0 && b == 0 { + return "-- / --" + } + return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) +} + +func outputJSON(stats []*containerStats) error { + type jstat struct { + Id string `json:"id"` + Name string `json:"name"` + CpuPercent string `json:"cpu_percent"` + MemUsage string `json:"mem_usage"` + MemPerc string `json:"mem_percent"` + NetIO string `json:"net_io"` + BlockIO string `json:"block_io"` + Pids string `json:"pids"` + } + var jstats []jstat + for _, j := range stats { + jstats = append(jstats, jstat{ + Id: j.ID(), + Name: j.Name, + CpuPercent: j.CPUPerc(), + MemUsage: j.MemPerc(), + MemPerc: j.MemUsage(), + NetIO: j.NetIO(), + BlockIO: j.BlockIO(), + Pids: j.PIDS(), + }) + } + + b, err := json.MarshalIndent(jstats, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} diff --git a/libpod/define/containerstate.go b/libpod/define/containerstate.go index 6da49a594..825e77387 100644 --- a/libpod/define/containerstate.go +++ b/libpod/define/containerstate.go @@ -112,3 +112,22 @@ func (s ContainerExecStatus) String() string { return "bad state" } } + +// ContainerStats contains the statistics information for a running container +type ContainerStats struct { + ContainerID string + Name string + PerCPU []uint64 + CPU float64 + CPUNano uint64 + CPUSystemNano uint64 + SystemNano uint64 + MemUsage uint64 + MemLimit uint64 + MemPerc float64 + NetInput uint64 + NetOutput uint64 + BlockInput uint64 + BlockOutput uint64 + PIDs uint64 +} diff --git a/libpod/pod.go b/libpod/pod.go index b5a14c165..8eb06ae2f 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -247,14 +247,14 @@ func (p *Pod) InfraContainerID() (string, error) { // PodContainerStats is an organization struct for pods and their containers type PodContainerStats struct { Pod *Pod - ContainerStats map[string]*ContainerStats + ContainerStats map[string]*define.ContainerStats } // GetPodStats returns the stats for each of its containers -func (p *Pod) GetPodStats(previousContainerStats map[string]*ContainerStats) (map[string]*ContainerStats, error) { +func (p *Pod) GetPodStats(previousContainerStats map[string]*define.ContainerStats) (map[string]*define.ContainerStats, error) { var ( ok bool - prevStat *ContainerStats + prevStat *define.ContainerStats ) p.lock.Lock() defer p.lock.Unlock() @@ -266,10 +266,10 @@ func (p *Pod) GetPodStats(previousContainerStats map[string]*ContainerStats) (ma if err != nil { return nil, err } - newContainerStats := make(map[string]*ContainerStats) + newContainerStats := make(map[string]*define.ContainerStats) for _, c := range containers { if prevStat, ok = previousContainerStats[c.ID()]; !ok { - prevStat = &ContainerStats{} + prevStat = &define.ContainerStats{} } newStats, err := c.GetContainerStats(prevStat) // If the container wasn't running, don't include it diff --git a/libpod/stats.go b/libpod/stats.go index 6f42afd18..9f4986144 100644 --- a/libpod/stats.go +++ b/libpod/stats.go @@ -13,8 +13,8 @@ import ( ) // GetContainerStats gets the running stats for a given container -func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) { - stats := new(ContainerStats) +func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) { + stats := new(define.ContainerStats) stats.ContainerID = c.ID() stats.Name = c.Name() diff --git a/libpod/stats_config.go b/libpod/stats_config.go deleted file mode 100644 index 91d3d1493..000000000 --- a/libpod/stats_config.go +++ /dev/null @@ -1,20 +0,0 @@ -package libpod - -// ContainerStats contains the statistics information for a running container -type ContainerStats struct { - ContainerID string - Name string - PerCPU []uint64 - CPU float64 - CPUNano uint64 - CPUSystemNano uint64 - SystemNano uint64 - MemUsage uint64 - MemLimit uint64 - MemPerc float64 - NetInput uint64 - NetOutput uint64 - BlockInput uint64 - BlockOutput uint64 - PIDs uint64 -} diff --git a/libpod/stats_unsupported.go b/libpod/stats_unsupported.go index ec19a89a1..6d21ae8f2 100644 --- a/libpod/stats_unsupported.go +++ b/libpod/stats_unsupported.go @@ -5,6 +5,6 @@ package libpod import "github.com/containers/libpod/libpod/define" // GetContainerStats gets the running stats for a given container -func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) { +func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) { return nil, define.ErrOSNotSupported } diff --git a/libpod/util.go b/libpod/util.go index 6457dac1c..bdfd153ed 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -9,12 +9,10 @@ import ( "os/exec" "path/filepath" "sort" - "strconv" "strings" "time" "github.com/containers/common/pkg/config" - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/utils" "github.com/fsnotify/fsnotify" @@ -36,24 +34,6 @@ func FuncTimer(funcName string) { fmt.Printf("%s executed in %d ms\n", funcName, elapsed) } -// 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 -} - // MountExists returns true if dest exists in the list of mounts func MountExists(specMounts []spec.Mount, dest string) bool { for _, m := range specMounts { diff --git a/libpod/util_test.go b/libpod/util_test.go index 227686c2b..4e18a7e4e 100644 --- a/libpod/util_test.go +++ b/libpod/util_test.go @@ -3,6 +3,7 @@ package libpod import ( "testing" + "github.com/containers/libpod/utils" "github.com/stretchr/testify/assert" ) @@ -10,7 +11,7 @@ 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) + result, err := utils.RemoveScientificNotationFromFloat(x) assert.NoError(t, err) assert.Equal(t, result, results[i]) } diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go index 53ad0a632..62ccd2b93 100644 --- a/pkg/api/handlers/compat/containers_stats.go +++ b/pkg/api/handlers/compat/containers_stats.go @@ -50,7 +50,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { return } - stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{}) + stats, err := ctnr.GetContainerStats(&define.ContainerStats{}) if err != nil { utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name)) return diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 622e8eb5b..071eff2fc 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -367,3 +367,14 @@ type ContainerCpOptions struct { // ContainerCpReport describes the output from a cp operation type ContainerCpReport struct { } + +// ContainerStatsOptions describes input options for getting +// stats on containers +type ContainerStatsOptions struct { + All bool + Format string + Latest bool + NoReset bool + NoStream bool + StatChan chan []*define.ContainerStats +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index eebf4c033..9f2abac65 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -35,6 +35,7 @@ type ContainerEngine interface { ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error) ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) + ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) ContainerUnmount(ctx context.Context, nameOrIds []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index f4996583a..bb7f0118d 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -8,8 +8,7 @@ import ( "strconv" "strings" "sync" - - lpfilters "github.com/containers/libpod/libpod/filters" + "time" "github.com/containers/buildah" "github.com/containers/common/pkg/config" @@ -17,8 +16,10 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" + lpfilters "github.com/containers/libpod/libpod/filters" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/logs" + "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/checkpoint" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra/abi/terminal" @@ -998,3 +999,76 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) { _ = ic.Libpod.Shutdown(false) }) } + +func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error { + containerFunc := ic.Libpod.GetRunningContainers + switch { + case len(namesOrIds) > 0: + containerFunc = func() ([]*libpod.Container, error) { return ic.Libpod.GetContainersByList(namesOrIds) } + case options.Latest: + containerFunc = func() ([]*libpod.Container, error) { + lastCtr, err := ic.Libpod.GetLatestContainer() + if err != nil { + return nil, err + } + return []*libpod.Container{lastCtr}, nil + } + case options.All: + containerFunc = ic.Libpod.GetAllContainers + } + + ctrs, err := containerFunc() + if err != nil { + return errors.Wrapf(err, "unable to get list of containers") + } + containerStats := map[string]*define.ContainerStats{} + for _, ctr := range ctrs { + initialStats, err := ctr.GetContainerStats(&define.ContainerStats{}) + if err != nil { + // when doing "all", don't worry about containers that are not running + cause := errors.Cause(err) + if options.All && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) { + continue + } + if cause == cgroups.ErrCgroupV1Rootless { + err = cause + } + return err + } + containerStats[ctr.ID()] = initialStats + } + for { + reportStats := []*define.ContainerStats{} + for _, ctr := range ctrs { + id := ctr.ID() + if _, ok := containerStats[ctr.ID()]; !ok { + initialStats, err := ctr.GetContainerStats(&define.ContainerStats{}) + if errors.Cause(err) == define.ErrCtrRemoved || errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrStateInvalid { + // skip dealing with a container that is gone + continue + } + if err != nil { + return err + } + containerStats[id] = initialStats + } + stats, err := ctr.GetContainerStats(containerStats[id]) + if err != nil && errors.Cause(err) != define.ErrNoSuchCtr { + return err + } + // replace the previous measurement with the current one + containerStats[id] = stats + reportStats = append(reportStats, stats) + } + ctrs, err = containerFunc() + if err != nil { + return err + } + options.StatChan <- reportStats + if options.NoStream { + break + } + time.Sleep(time.Second) + } + return nil +} diff --git a/pkg/domain/infra/abi/pods_stats.go b/pkg/domain/infra/abi/pods_stats.go index a41c01da0..c6befcf95 100644 --- a/pkg/domain/infra/abi/pods_stats.go +++ b/pkg/domain/infra/abi/pods_stats.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/utils" "github.com/docker/go-units" "github.com/pkg/errors" ) @@ -68,7 +69,7 @@ func combineHumanValues(a, b uint64) string { } func floatToPercentString(f float64) string { - strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f) + strippedFloat, err := utils.RemoveScientificNotationFromFloat(f) if err != nil || strippedFloat == 0 { // If things go bazinga, return a safe value return "--" diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 32f9c4e36..227b660f7 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -387,3 +387,7 @@ func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, // Shutdown Libpod engine func (ic *ContainerEngine) Shutdown(_ context.Context) { } + +func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error { + return errors.New("not implemented") +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 8fba07c18..258cb8652 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -331,7 +331,7 @@ func (i *VarlinkAPI) GetContainerStats(call iopodman.VarlinkCall, name string) e if err != nil { return call.ReplyContainerNotFound(name, err.Error()) } - containerStats, err := ctr.GetContainerStats(&libpod.ContainerStats{}) + containerStats, err := ctr.GetContainerStats(&define.ContainerStats{}) if err != nil { if errors.Cause(err) == define.ErrCtrStateInvalid { return call.ReplyNoContainerRunning() diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index 5a9360447..aeb3cdcb8 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -8,12 +8,12 @@ import ( "strconv" "syscall" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-connections/nat" "github.com/pkg/errors" - - "github.com/containers/libpod/libpod" - iopodman "github.com/containers/libpod/pkg/varlink" ) // CreatePod ... @@ -263,7 +263,7 @@ func (i *VarlinkAPI) GetPodStats(call iopodman.VarlinkCall, name string) error { if err != nil { return call.ReplyPodNotFound(name, err.Error()) } - prevStats := make(map[string]*libpod.ContainerStats) + prevStats := make(map[string]*define.ContainerStats) podStats, err := pod.GetPodStats(prevStats) if err != nil { return call.ReplyErrorOccurred(err.Error()) diff --git a/pkg/varlinkapi/remote_client.go b/pkg/varlinkapi/remote_client.go index a16d11dec..88e410de6 100644 --- a/pkg/varlinkapi/remote_client.go +++ b/pkg/varlinkapi/remote_client.go @@ -3,14 +3,14 @@ package varlinkapi import ( - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" iopodman "github.com/containers/libpod/pkg/varlink" ) // ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod // container stats -func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats { - cstats := libpod.ContainerStats{ +func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) define.ContainerStats { + cstats := define.ContainerStats{ ContainerID: stats.Id, Name: stats.Name, CPU: stats.Cpu, diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index 32f7cc520..762417a17 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -21,7 +21,6 @@ var _ = Describe("Podman stats", func() { ) BeforeEach(func() { - Skip(v2fail) cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() Expect(err).To(BeNil()) diff --git a/utils/utils.go b/utils/utils.go index cf58ca3fb..27ce1821d 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -6,6 +6,7 @@ import ( "io" "os" "os/exec" + "strconv" "strings" "github.com/containers/storage/pkg/archive" @@ -125,3 +126,21 @@ func Tar(source string) (io.ReadCloser, error) { logrus.Debugf("creating tarball of %s", source) return archive.Tar(source, archive.Uncompressed) } + +// 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 +} -- cgit v1.2.3-54-g00ecf