stats: add a interval parameter to cli and api stream mode

podman stats polled by default in a 1 sec period.
This can put quite some load on a machine if you run many containers.

The default value is now 5 seconds.
You can change this interval with a new, optional, --interval, -i cli flag.
The api request got also a interval query parameter for the same purpose.

Additionally a unused const was removed.
Api and cli will fail the request if a 0 or negative value is passed in.

Signed-off-by: Thomas Weber <towe75@googlemail.com>
This commit is contained in:
Thomas Weber
2021-07-21 07:42:39 +02:00
parent 389c9b8dca
commit cdbbd79155
9 changed files with 47 additions and 10 deletions

View File

@ -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"
@ -55,6 +56,7 @@ type statsOptionsCLI struct {
Latest bool Latest bool
NoReset bool NoReset bool
NoStream bool NoStream bool
Interval int
} }
var ( var (
@ -72,6 +74,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() {
@ -122,8 +127,9 @@ func stats(cmd *cobra.Command, args []string) error {
// Convert to the entities options. We should not leak CLI-only // Convert to the entities options. We should not leak CLI-only
// options into the backend and separate concerns. // options into the backend and separate concerns.
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 {

View File

@ -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

View File

@ -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"
@ -14,8 +13,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)
@ -23,8 +20,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()))
@ -36,7 +35,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
containerEngine := abi.ContainerEngine{Libpod: runtime} containerEngine := abi.ContainerEngine{Libpod: runtime}
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.

View File

@ -1106,6 +1106,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:

View File

@ -165,7 +165,8 @@ type StartOptions struct {
//go:generate go run ../generator/generator.go StatsOptions //go:generate go run ../generator/generator.go StatsOptions
// 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

View File

@ -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
}

View File

@ -435,6 +435,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.

View File

@ -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) { 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
@ -1361,7 +1364,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
}() }()

View File

@ -871,7 +871,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