diff options
author | Valentin Rothberg <rothberg@redhat.com> | 2020-09-23 13:32:58 +0200 |
---|---|---|
committer | Valentin Rothberg <rothberg@redhat.com> | 2020-09-23 15:39:25 +0200 |
commit | 376ba349bfadc47a938084ccb3c2d112c92f09ca (patch) | |
tree | fa8acd364392a28b0f0cddc361b83806b4da4458 | |
parent | 5cedd830f7275e8dc3382502908b846bfa57a3b8 (diff) | |
download | podman-376ba349bfadc47a938084ccb3c2d112c92f09ca.tar.gz podman-376ba349bfadc47a938084ccb3c2d112c92f09ca.tar.bz2 podman-376ba349bfadc47a938084ccb3c2d112c92f09ca.zip |
stats refactor
Refactor the entities' stats API to simplify using it and reduce the
risk of running into concurrency issues at the call sites. Further
simplify the stats code by de-spaghetti-ing the logic and reducing
duplicate code.
`ContainerStats` now returns a data channel and an error. If the error
is nil, callers can read from the channel.
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
-rw-r--r-- | cmd/podman/containers/stats.go | 38 | ||||
-rw-r--r-- | pkg/domain/entities/containers.go | 7 | ||||
-rw-r--r-- | pkg/domain/entities/engine_container.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 109 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 4 |
5 files changed, 87 insertions, 73 deletions
diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index ddb5f32ef..1a4adb376 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "strings" - "sync" "text/tabwriter" "text/template" @@ -107,32 +106,31 @@ func stats(cmd *cobra.Command, args []string) error { return errors.New("stats is not supported in rootless mode without cgroups v2") } } - statsOptions.StatChan = make(chan []*define.ContainerStats, 1) - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - for reports := range statsOptions.StatChan { - if err := outputStats(reports); err != nil { - logrus.Error(err) - } - } - wg.Done() - }() - err := registry.ContainerEngine().ContainerStats(registry.Context(), args, statsOptions) - wg.Wait() - return err + statsChan, err := registry.ContainerEngine().ContainerStats(registry.Context(), args, statsOptions) + if err != nil { + return err + } + for report := range statsChan { + if report.Error != nil { + return report.Error + } + if err := outputStats(report.Stats); err != nil { + logrus.Error(err) + } + } + return nil } -func outputStats(reports []*define.ContainerStats) error { +func outputStats(reports []define.ContainerStats) error { if len(statsOptions.Format) < 1 && !statsOptions.NoReset { tm.Clear() tm.MoveCursor(1, 1) tm.Flush() } - stats := make([]*containerStats, 0, len(reports)) + stats := make([]containerStats, 0, len(reports)) for _, r := range reports { - stats = append(stats, &containerStats{r}) + stats = append(stats, containerStats{r}) } if statsOptions.Format == "json" { return outputJSON(stats) @@ -163,7 +161,7 @@ func outputStats(reports []*define.ContainerStats) error { } type containerStats struct { - *define.ContainerStats + define.ContainerStats } func (s *containerStats) ID() string { @@ -213,7 +211,7 @@ func combineHumanValues(a, b uint64) string { return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) } -func outputJSON(stats []*containerStats) error { +func outputJSON(stats []containerStats) error { type jstat struct { Id string `json:"id"` //nolint Name string `json:"name"` diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 16997cdd1..b6f86785f 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -416,5 +416,10 @@ type ContainerStatsOptions struct { Latest bool NoReset bool NoStream bool - StatChan chan []*define.ContainerStats +} + +// ContainerStatsReport is used for streaming container stats. +type ContainerStatsReport struct { + Error error + Stats []define.ContainerStats } diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 6c85c9267..f105dc333 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -38,7 +38,7 @@ type ContainerEngine interface { ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error) ContainerRunlabel(ctx context.Context, label string, image string, args []string, opts ContainerRunlabelOptions) error ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) - ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error + ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) (chan ContainerStatsReport, 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 21618f555..d55bc5c17 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1142,12 +1142,11 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) { }) } -func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error { - defer close(options.StatChan) +func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) { + statsChan = make(chan entities.ContainerStatsReport, 1) + 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() @@ -1156,62 +1155,74 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri } return []*libpod.Container{lastCtr}, nil } + case len(namesOrIds) > 0: + containerFunc = func() ([]*libpod.Container, error) { return ic.Libpod.GetContainersByList(namesOrIds) } 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 + go func() { + defer close(statsChan) + var ( + err error + containers []*libpod.Container + containerStats map[string]*define.ContainerStats + ) + containerStats = make(map[string]*define.ContainerStats) + + stream: // label to flatten the scope + select { + case <-ctx.Done(): + // client cancelled + logrus.Debugf("Container stats stopped: context cancelled") + return + default: + // just fall through and do work } - 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 + + // Anonymous func to easily use the return values for streaming. + computeStats := func() ([]define.ContainerStats, error) { + containers, err = containerFunc() + if err != nil { + return nil, errors.Wrapf(err, "unable to get list of containers") + } + + reportStats := []define.ContainerStats{} + for _, ctr := range containers { + prev, ok := containerStats[ctr.ID()] + if !ok { + prev = &define.ContainerStats{} } + + stats, err := ctr.GetContainerStats(prev) if err != nil { - return err + cause := errors.Cause(err) + if options.All && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) { + continue + } + if cause == cgroups.ErrCgroupV1Rootless { + err = cause + } + return nil, err } - containerStats[id] = initialStats - } - stats, err := ctr.GetContainerStats(containerStats[id]) - if err != nil && errors.Cause(err) != define.ErrNoSuchCtr { - return err + + containerStats[ctr.ID()] = stats + reportStats = append(reportStats, *stats) } - // replace the previous measurement with the current one - containerStats[id] = stats - reportStats = append(reportStats, stats) + return reportStats, nil } - ctrs, err = containerFunc() - if err != nil { - return err - } - options.StatChan <- reportStats + + report := entities.ContainerStatsReport{} + report.Stats, report.Error = computeStats() + statsChan <- report + if options.NoStream { - break + return } + time.Sleep(time.Second) - } - return nil + goto stream + }() + + return statsChan, nil } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index d0f90d900..60136faae 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -682,6 +682,6 @@ func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, func (ic *ContainerEngine) Shutdown(_ context.Context) { } -func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error { - return errors.New("not implemented") +func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) { + return nil, errors.New("not implemented") } |