Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stats: add a interval parameter to cli and api stats streaming #11003

Merged
merged 2 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions cmd/podman/containers/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

tm "github.com/buger/goterm"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v3/cmd/podman/common"
"github.com/containers/podman/v3/cmd/podman/registry"
Expand All @@ -16,7 +17,6 @@ import (
"github.com/containers/podman/v3/utils"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -55,6 +55,7 @@ type statsOptionsCLI struct {
Latest bool
NoReset bool
NoStream bool
Interval int
}

var (
Expand All @@ -72,6 +73,9 @@ func statFlags(cmd *cobra.Command) {

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")
intervalFlagName := "interval"
flags.IntVarP(&statsOptions.Interval, intervalFlagName, "i", 5, "Time in seconds between stats reports")
_ = cmd.RegisterFlagCompletionFunc(intervalFlagName, completion.AutocompleteNone)
}

func init() {
Expand Down Expand Up @@ -122,8 +126,9 @@ func stats(cmd *cobra.Command, args []string) error {
// Convert to the entities options. We should not leak CLI-only
// options into the backend and separate concerns.
opts := entities.ContainerStatsOptions{
Latest: statsOptions.Latest,
Stream: !statsOptions.NoStream,
Latest: statsOptions.Latest,
Stream: !statsOptions.NoStream,
Interval: statsOptions.Interval,
}
statsChan, err := registry.ContainerEngine().ContainerStats(registry.Context(), args, opts)
if err != nil {
Expand All @@ -134,7 +139,7 @@ func stats(cmd *cobra.Command, args []string) error {
return report.Error
}
if err := outputStats(report.Stats); err != nil {
logrus.Error(err)
return err
}
}
return nil
Expand Down
4 changes: 4 additions & 0 deletions docs/source/markdown/podman-stats.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Do not clear the terminal/screen in between reporting intervals

Disable streaming stats and only pull the first result, default setting is false

#### **--interval**=*seconds*, **-i**=*seconds*

Time in seconds between stats reports, defaults to 5 seconds.

#### **--format**=*template*

Pretty-print container statistics to JSON or using a Go template
Expand Down
10 changes: 5 additions & 5 deletions pkg/api/handlers/libpod/containers_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package libpod
import (
"encoding/json"
"net/http"
"time"

"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/pkg/api/handlers/utils"
Expand All @@ -14,17 +13,17 @@ import (
"github.com/sirupsen/logrus"
)

const DefaultStatsPeriod = 5 * time.Second

func StatsContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)

query := struct {
Containers []string `schema:"containers"`
Stream bool `schema:"stream"`
Interval int `schema:"interval"`
}{
Stream: true,
Stream: true,
Interval: 5,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
Expand All @@ -36,7 +35,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
containerEngine := abi.ContainerEngine{Libpod: runtime}

statsOptions := entities.ContainerStatsOptions{
Stream: query.Stream,
Stream: query.Stream,
Interval: query.Interval,
}

// Stats will stop if the connection is closed.
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/server/register_containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,11 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// type: boolean
// default: true
// description: Stream the output
// - in: query
// name: interval
// type: integer
// default: 5
// description: Time in seconds between stats reports
// produces:
// - application/json
// responses:
Expand Down
3 changes: 3 additions & 0 deletions pkg/bindings/containers/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha
if err != nil {
return nil, err
}
if !response.IsSuccess() {
return nil, response.Process(nil)
}

statsChan := make(chan entities.ContainerStatsReport)

Expand Down
3 changes: 2 additions & 1 deletion pkg/bindings/containers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ type StartOptions struct {
//go:generate go run ../generator/generator.go StatsOptions
// StatsOptions are optional options for getting stats on containers
type StatsOptions struct {
Stream *bool
Stream *bool
Interval *int
}

//go:generate go run ../generator/generator.go TopOptions
Expand Down
16 changes: 16 additions & 0 deletions pkg/bindings/containers/types_stats_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,19 @@ func (o *StatsOptions) GetStream() bool {
}
return *o.Stream
}

// WithInterval
func (o *StatsOptions) WithInterval(value int) *StatsOptions {
v := &value
o.Interval = v
return o
}

// GetInterval
func (o *StatsOptions) GetInterval() int {
var interval int
if o.Interval == nil {
return interval
}
return *o.Interval
}
2 changes: 2 additions & 0 deletions pkg/domain/entities/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@ type ContainerStatsOptions struct {
Latest bool
// Stream stats.
Stream bool
// Interval in seconds
Interval int
}

// ContainerStatsReport is used for streaming container stats.
Expand Down
5 changes: 4 additions & 1 deletion pkg/domain/infra/abi/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,9 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) {
}

func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) {
if options.Interval < 1 {
return nil, errors.New("Invalid interval, must be a positive number greater zero")
}
statsChan = make(chan entities.ContainerStatsReport, 1)

containerFunc := ic.Libpod.GetRunningContainers
Expand Down Expand Up @@ -1361,7 +1364,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
return
}

time.Sleep(time.Second)
time.Sleep(time.Second * time.Duration(options.Interval))
goto stream
}()

Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/infra/tunnel/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
if options.Latest {
return nil, errors.New("latest is not supported for the remote client")
}
return containers.Stats(ic.ClientCtx, namesOrIds, new(containers.StatsOptions).WithStream(options.Stream))
return containers.Stats(ic.ClientCtx, namesOrIds, new(containers.StatsOptions).WithStream(options.Stream).WithInterval(options.Interval))
}

// ShouldRestart reports back whether the container will restart
Expand Down
22 changes: 11 additions & 11 deletions test/e2e/pod_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ var _ = Describe("Podman pod stats", func() {
processTestResult(f)

})
It("podman stats should run with no pods", func() {
It("podman pod stats should run with no pods", func() {
session := podmanTest.Podman([]string{"pod", "stats", "--no-stream"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
})

It("podman stats with a bogus pod", func() {
It("podman pod stats with a bogus pod", func() {
session := podmanTest.Podman([]string{"pod", "stats", "foobar"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125))
})

It("podman stats on a specific running pod", func() {
It("podman pod stats on a specific running pod", func() {
_, ec, podid := podmanTest.CreatePod(nil)
Expect(ec).To(Equal(0))

Expand All @@ -66,7 +66,7 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats).Should(Exit(0))
})

It("podman stats on a specific running pod with shortID", func() {
It("podman pod stats on a specific running pod with shortID", func() {
_, ec, podid := podmanTest.CreatePod(nil)
Expect(ec).To(Equal(0))

Expand All @@ -83,7 +83,7 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats).Should(Exit(0))
})

It("podman stats on a specific running pod with name", func() {
It("podman pod stats on a specific running pod with name", func() {
_, ec, podid := podmanTest.CreatePod(map[string][]string{"--name": {"test"}})
Expect(ec).To(Equal(0))

Expand All @@ -100,7 +100,7 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats).Should(Exit(0))
})

It("podman stats on running pods", func() {
It("podman pod stats on running pods", func() {
_, ec, podid := podmanTest.CreatePod(nil)
Expect(ec).To(Equal(0))

Expand All @@ -117,7 +117,7 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats).Should(Exit(0))
})

It("podman stats on all pods", func() {
It("podman pod stats on all pods", func() {
_, ec, podid := podmanTest.CreatePod(nil)
Expect(ec).To(Equal(0))

Expand All @@ -134,7 +134,7 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats).Should(Exit(0))
})

It("podman stats with json output", func() {
It("podman pod stats with json output", func() {
_, ec, podid := podmanTest.CreatePod(nil)
Expect(ec).To(Equal(0))

Expand All @@ -151,7 +151,7 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats).Should(Exit(0))
Expect(stats.IsJSONOutputValid()).To(BeTrue())
})
It("podman stats with GO template", func() {
It("podman pod stats with GO template", func() {
_, ec, podid := podmanTest.CreatePod(nil)
Expect(ec).To(Equal(0))

Expand All @@ -163,7 +163,7 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats).To(Exit(0))
})

It("podman stats with invalid GO template", func() {
It("podman pod stats with invalid GO template", func() {
_, ec, podid := podmanTest.CreatePod(nil)
Expect(ec).To(Equal(0))

Expand All @@ -175,7 +175,7 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats).To(ExitWithError())
})

It("podman stats on net=host post", func() {
It("podman pod stats on net=host post", func() {
SkipIfRootless("--net=host not supported for rootless pods at present")
podName := "testPod"
podCreate := podmanTest.Podman([]string{"pod", "create", "--net=host", "--name", podName})
Expand Down
44 changes: 38 additions & 6 deletions test/e2e/stats_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build

package integration

import (
Expand Down Expand Up @@ -84,15 +82,49 @@ var _ = Describe("Podman stats", func() {
Expect(session).Should(Exit(0))
})

It("podman stats only output CPU data", func() {
It("podman stats with GO template", func() {
session := podmanTest.RunTopContainer("")
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{"stats", "--all", "--no-stream", "--format", "\"{{.ID}} {{.UpTime}} {{.AVGCPU}}\""})
stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--format", "table {{.ID}} {{.AVGCPU}} {{.MemUsage}} {{.CPU}} {{.NetIO}} {{.BlockIO}} {{.PIDS}}"})
stats.WaitWithDefaultTimeout()
Expect(stats).To(Exit(0))
})

It("podman stats with invalid GO template", func() {
session := podmanTest.RunTopContainer("")
session.WaitWithDefaultTimeout()
Expect(session.LineInOutputContains("UpTime")).To(BeTrue())
Expect(session.LineInOutputContains("AVGCPU")).To(BeTrue())
Expect(session).Should(Exit(0))
stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.ID}} {{.NoSuchField}} \""})
stats.WaitWithDefaultTimeout()
Expect(stats).To(ExitWithError())
})

It("podman stats with negative interval", func() {
session := podmanTest.RunTopContainer("")
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--interval=-1"})
stats.WaitWithDefaultTimeout()
Expect(stats).To(ExitWithError())
})

It("podman stats with zero interval", func() {
session := podmanTest.RunTopContainer("")
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--interval=0"})
stats.WaitWithDefaultTimeout()
Expect(stats).To(ExitWithError())
})

It("podman stats with interval", func() {
session := podmanTest.RunTopContainer("")
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--interval=5"})
stats.WaitWithDefaultTimeout()
Expect(stats).Should(Exit(0))
})

It("podman stats with json output", func() {
Expand Down