summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/api/handlers/libpod/containers_stats.go73
-rw-r--r--pkg/api/server/register_containers.go31
-rw-r--r--pkg/bindings/containers/containers.go51
-rw-r--r--pkg/domain/infra/abi/containers.go2
-rw-r--r--pkg/domain/infra/tunnel/containers.go3
-rw-r--r--test/e2e/stats_test.go2
6 files changed, 157 insertions, 5 deletions
diff --git a/pkg/api/handlers/libpod/containers_stats.go b/pkg/api/handlers/libpod/containers_stats.go
new file mode 100644
index 000000000..3066d5ebc
--- /dev/null
+++ b/pkg/api/handlers/libpod/containers_stats.go
@@ -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()
+ }
+ }
+}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 0ad5d29ea..870c6a90c 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -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 container’s 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:
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
index 981912665..46e4df1d2 100644
--- a/pkg/bindings/containers/containers.go
+++ b/pkg/bindings/containers/containers.go
@@ -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.
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index d55bc5c17..c77d27cb2 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -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
}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 60136faae..41fb62ab6 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -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)
}
diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go
index ff6ddce7e..7ab435007 100644
--- a/test/e2e/stats_test.go
+++ b/test/e2e/stats_test.go
@@ -1,4 +1,4 @@
-// +build !remote
+// +build
package integration