new endpoint: /libpod/containers/stats

Add a new endpoint for container stats allowing for batch operations on
more than one container.  The new endpoint deprecates the
single-container endpoint which will eventually be removed with the next
major release.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg 2020-09-23 13:33:17 +02:00
parent 376ba349bf
commit 762b787fbf
6 changed files with 157 additions and 5 deletions

View File

@ -0,0 +1,73 @@
package libpod
import (
"encoding/json"
"net/http"
"time"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"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"`
}{
Stream: true,
}
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()))
return
}
// Reduce code duplication and use the local/abi implementation of
// container stats.
containerEngine := abi.ContainerEngine{Libpod: runtime}
statsOptions := entities.ContainerStatsOptions{
All: len(query.Containers) == 0, // no containers -> query all of them
NoStream: !query.Stream,
}
// Stats will stop if the connection is closed.
statsChan, err := containerEngine.ContainerStats(r.Context(), query.Containers, statsOptions)
if err != nil {
utils.InternalServerError(w, err)
return
}
// Write header and content type.
w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "application/json")
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
// Setup JSON encoder for streaming.
coder := json.NewEncoder(w)
coder.SetEscapeHTML(true)
for stats := range statsChan {
if err := coder.Encode(stats); err != nil {
// Note: even when streaming, the stats goroutine will
// be notified (and stop) as the connection will be
// closed.
logrus.Errorf("Unable to encode stats: %v", err)
return
}
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
}
}

View File

@ -1013,7 +1013,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// tags:
// - containers
// summary: Get stats for a container
// description: This returns a live stream of a containers resource usage statistics.
// description: DEPRECATED. This endpoint will be removed with the next major release. Please use /libpod/containers/stats instead.
// parameters:
// - in: path
// name: name
@ -1035,6 +1035,35 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/stats"), s.APIHandler(compat.StatsContainer)).Methods(http.MethodGet)
// swagger:operation GET /libpod/containers/stats libpod libpodStatsContainers
// ---
// tags:
// - containers
// summary: Get stats for one or more containers
// description: Return a live stream of resource usage statistics of one or more container. If no container is specified, the statistics of all containers are returned.
// parameters:
// - in: query
// name: containers
// description: names or IDs of containers
// type: array
// items:
// type: string
// - in: query
// name: stream
// type: boolean
// default: true
// description: Stream the output
// produces:
// - application/json
// responses:
// 200:
// description: no error
// 404:
// $ref: "#/responses/NoSuchContainer"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/stats"), s.APIHandler(libpod.StatsContainer)).Methods(http.MethodGet)
// swagger:operation GET /libpod/containers/{name}/top libpod libpodTopContainer
// ---
// tags:

View File

@ -197,7 +197,56 @@ func Start(ctx context.Context, nameOrID string, detachKeys *string) error {
return response.Process(nil)
}
func Stats() {}
func Stats(ctx context.Context, containers []string, stream *bool) (chan entities.ContainerStatsReport, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params := url.Values{}
if stream != nil {
params.Set("stream", strconv.FormatBool(*stream))
}
for _, c := range containers {
params.Add("containers", c)
}
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/stats", params, nil)
if err != nil {
return nil, err
}
statsChan := make(chan entities.ContainerStatsReport)
go func() {
defer close(statsChan)
dec := json.NewDecoder(response.Body)
doStream := true
if stream != nil {
doStream = *stream
}
streamLabel: // label to flatten the scope
select {
case <-response.Request.Context().Done():
return // lost connection - maybe the server quit
default:
// fall through and do some work
}
var report entities.ContainerStatsReport
if err := dec.Decode(&report); err != nil {
report = entities.ContainerStatsReport{Error: err}
}
statsChan <- report
if report.Error != nil || !doStream {
return
}
goto streamLabel
}()
return statsChan, nil
}
// Top gathers statistics about the running processes in a container. The nameOrID can be a container name
// or a partial/full ID. The descriptors allow for specifying which data to collect from the process.

View File

@ -1216,7 +1216,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
report.Stats, report.Error = computeStats()
statsChan <- report
if options.NoStream {
if report.Error != nil || options.NoStream {
return
}

View File

@ -683,5 +683,6 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) {
}
func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) {
return nil, errors.New("not implemented")
stream := !options.NoStream
return containers.Stats(ic.ClientCxt, namesOrIds, &stream)
}

View File

@ -1,4 +1,4 @@
// +build !remote
// +build
package integration