mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00
Merge pull request #11003 from pascomnet/f_stats
stats: add a interval parameter to cli and api stats streaming
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
tm "github.com/buger/goterm"
|
tm "github.com/buger/goterm"
|
||||||
|
"github.com/containers/common/pkg/completion"
|
||||||
"github.com/containers/common/pkg/report"
|
"github.com/containers/common/pkg/report"
|
||||||
"github.com/containers/podman/v3/cmd/podman/common"
|
"github.com/containers/podman/v3/cmd/podman/common"
|
||||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||||
@ -16,7 +17,6 @@ import (
|
|||||||
"github.com/containers/podman/v3/utils"
|
"github.com/containers/podman/v3/utils"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,6 +55,7 @@ type statsOptionsCLI struct {
|
|||||||
Latest bool
|
Latest bool
|
||||||
NoReset bool
|
NoReset bool
|
||||||
NoStream bool
|
NoStream bool
|
||||||
|
Interval int
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -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.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")
|
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() {
|
func init() {
|
||||||
@ -124,6 +128,7 @@ func stats(cmd *cobra.Command, args []string) error {
|
|||||||
opts := entities.ContainerStatsOptions{
|
opts := entities.ContainerStatsOptions{
|
||||||
Latest: statsOptions.Latest,
|
Latest: statsOptions.Latest,
|
||||||
Stream: !statsOptions.NoStream,
|
Stream: !statsOptions.NoStream,
|
||||||
|
Interval: statsOptions.Interval,
|
||||||
}
|
}
|
||||||
statsChan, err := registry.ContainerEngine().ContainerStats(registry.Context(), args, opts)
|
statsChan, err := registry.ContainerEngine().ContainerStats(registry.Context(), args, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -134,7 +139,7 @@ func stats(cmd *cobra.Command, args []string) error {
|
|||||||
return report.Error
|
return report.Error
|
||||||
}
|
}
|
||||||
if err := outputStats(report.Stats); err != nil {
|
if err := outputStats(report.Stats); err != nil {
|
||||||
logrus.Error(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -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
|
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*
|
#### **--format**=*template*
|
||||||
|
|
||||||
Pretty-print container statistics to JSON or using a Go template
|
Pretty-print container statistics to JSON or using a Go template
|
||||||
|
@ -3,7 +3,6 @@ package libpod
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containers/podman/v3/libpod"
|
"github.com/containers/podman/v3/libpod"
|
||||||
"github.com/containers/podman/v3/pkg/api/handlers/utils"
|
"github.com/containers/podman/v3/pkg/api/handlers/utils"
|
||||||
@ -16,8 +15,6 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultStatsPeriod = 5 * time.Second
|
|
||||||
|
|
||||||
func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
||||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||||
@ -35,8 +32,10 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
|||||||
query := struct {
|
query := struct {
|
||||||
Containers []string `schema:"containers"`
|
Containers []string `schema:"containers"`
|
||||||
Stream bool `schema:"stream"`
|
Stream bool `schema:"stream"`
|
||||||
|
Interval int `schema:"interval"`
|
||||||
}{
|
}{
|
||||||
Stream: true,
|
Stream: true,
|
||||||
|
Interval: 5,
|
||||||
}
|
}
|
||||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
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()))
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
|
||||||
@ -49,6 +48,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
statsOptions := entities.ContainerStatsOptions{
|
statsOptions := entities.ContainerStatsOptions{
|
||||||
Stream: query.Stream,
|
Stream: query.Stream,
|
||||||
|
Interval: query.Interval,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats will stop if the connection is closed.
|
// Stats will stop if the connection is closed.
|
||||||
|
@ -1108,6 +1108,11 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
|
|||||||
// type: boolean
|
// type: boolean
|
||||||
// default: true
|
// default: true
|
||||||
// description: Stream the output
|
// description: Stream the output
|
||||||
|
// - in: query
|
||||||
|
// name: interval
|
||||||
|
// type: integer
|
||||||
|
// default: 5
|
||||||
|
// description: Time in seconds between stats reports
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// responses:
|
// responses:
|
||||||
|
@ -223,6 +223,9 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if !response.IsSuccess() {
|
||||||
|
return nil, response.Process(nil)
|
||||||
|
}
|
||||||
|
|
||||||
statsChan := make(chan entities.ContainerStatsReport)
|
statsChan := make(chan entities.ContainerStatsReport)
|
||||||
|
|
||||||
|
@ -167,6 +167,7 @@ type StartOptions struct {
|
|||||||
// StatsOptions are optional options for getting stats on containers
|
// StatsOptions are optional options for getting stats on containers
|
||||||
type StatsOptions struct {
|
type StatsOptions struct {
|
||||||
Stream *bool
|
Stream *bool
|
||||||
|
Interval *int
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../generator/generator.go TopOptions
|
//go:generate go run ../generator/generator.go TopOptions
|
||||||
|
@ -35,3 +35,19 @@ func (o *StatsOptions) GetStream() bool {
|
|||||||
}
|
}
|
||||||
return *o.Stream
|
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
|
||||||
|
}
|
||||||
|
@ -440,6 +440,8 @@ type ContainerStatsOptions struct {
|
|||||||
Latest bool
|
Latest bool
|
||||||
// Stream stats.
|
// Stream stats.
|
||||||
Stream bool
|
Stream bool
|
||||||
|
// Interval in seconds
|
||||||
|
Interval int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerStatsReport is used for streaming container stats.
|
// ContainerStatsReport is used for streaming container stats.
|
||||||
|
@ -1283,6 +1283,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) {
|
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)
|
statsChan = make(chan entities.ContainerStatsReport, 1)
|
||||||
|
|
||||||
containerFunc := ic.Libpod.GetRunningContainers
|
containerFunc := ic.Libpod.GetRunningContainers
|
||||||
@ -1363,7 +1366,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second * time.Duration(options.Interval))
|
||||||
goto stream
|
goto stream
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -873,7 +873,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
|
|||||||
if options.Latest {
|
if options.Latest {
|
||||||
return nil, errors.New("latest is not supported for the remote client")
|
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
|
// ShouldRestart reports back whether the container will restart
|
||||||
|
@ -37,19 +37,19 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
processTestResult(f)
|
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 := podmanTest.Podman([]string{"pod", "stats", "--no-stream"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session).Should(Exit(0))
|
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 := podmanTest.Podman([]string{"pod", "stats", "foobar"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session).Should(Exit(125))
|
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)
|
_, ec, podid := podmanTest.CreatePod(nil)
|
||||||
Expect(ec).To(Equal(0))
|
Expect(ec).To(Equal(0))
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats).Should(Exit(0))
|
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)
|
_, ec, podid := podmanTest.CreatePod(nil)
|
||||||
Expect(ec).To(Equal(0))
|
Expect(ec).To(Equal(0))
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats).Should(Exit(0))
|
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"}})
|
_, ec, podid := podmanTest.CreatePod(map[string][]string{"--name": {"test"}})
|
||||||
Expect(ec).To(Equal(0))
|
Expect(ec).To(Equal(0))
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats).Should(Exit(0))
|
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)
|
_, ec, podid := podmanTest.CreatePod(nil)
|
||||||
Expect(ec).To(Equal(0))
|
Expect(ec).To(Equal(0))
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats).Should(Exit(0))
|
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)
|
_, ec, podid := podmanTest.CreatePod(nil)
|
||||||
Expect(ec).To(Equal(0))
|
Expect(ec).To(Equal(0))
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats).Should(Exit(0))
|
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)
|
_, ec, podid := podmanTest.CreatePod(nil)
|
||||||
Expect(ec).To(Equal(0))
|
Expect(ec).To(Equal(0))
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats).Should(Exit(0))
|
Expect(stats).Should(Exit(0))
|
||||||
Expect(stats.IsJSONOutputValid()).To(BeTrue())
|
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)
|
_, ec, podid := podmanTest.CreatePod(nil)
|
||||||
Expect(ec).To(Equal(0))
|
Expect(ec).To(Equal(0))
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats).To(Exit(0))
|
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)
|
_, ec, podid := podmanTest.CreatePod(nil)
|
||||||
Expect(ec).To(Equal(0))
|
Expect(ec).To(Equal(0))
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats).To(ExitWithError())
|
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")
|
SkipIfRootless("--net=host not supported for rootless pods at present")
|
||||||
podName := "testPod"
|
podName := "testPod"
|
||||||
podCreate := podmanTest.Podman([]string{"pod", "create", "--net=host", "--name", podName})
|
podCreate := podmanTest.Podman([]string{"pod", "create", "--net=host", "--name", podName})
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -84,15 +82,49 @@ var _ = Describe("Podman stats", func() {
|
|||||||
Expect(session).Should(Exit(0))
|
Expect(session).Should(Exit(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman stats only output CPU data", func() {
|
It("podman stats with GO template", func() {
|
||||||
session := podmanTest.RunTopContainer("")
|
session := podmanTest.RunTopContainer("")
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session).Should(Exit(0))
|
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()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.LineInOutputContains("UpTime")).To(BeTrue())
|
|
||||||
Expect(session.LineInOutputContains("AVGCPU")).To(BeTrue())
|
|
||||||
Expect(session).Should(Exit(0))
|
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() {
|
It("podman stats with json output", func() {
|
||||||
|
Reference in New Issue
Block a user