Sfoglia il codice sorgente

Merge pull request #40478 from cpuguy83/dont-prime-the-stats

Add stats options to not prime the stats
Sebastiaan van Stijn 5 anni fa
parent
commit
54d88a7cd3

+ 5 - 0
api/server/router/container/container_routes.go

@@ -105,9 +105,14 @@ func (s *containerRouter) getContainersStats(ctx context.Context, w http.Respons
 	if !stream {
 		w.Header().Set("Content-Type", "application/json")
 	}
+	var oneShot bool
+	if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.41") {
+		oneShot = httputils.BoolValueOrDefault(r, "one-shot", false)
+	}
 
 	config := &backend.ContainerStatsConfig{
 		Stream:    stream,
+		OneShot:   oneShot,
 		OutStream: w,
 		Version:   httputils.VersionFromContext(ctx),
 	}

+ 5 - 0
api/swagger.yaml

@@ -5691,6 +5691,11 @@ paths:
           description: "Stream the output. If false, the stats will be output once and then it will disconnect."
           type: "boolean"
           default: true
+        - name: "one-shot"
+          in: "query"
+          description: "Only get a single stat instead of waiting for 2 cycles. Must be used with stream=false"
+          type: "boolean"
+          default: false
       tags: ["Container"]
   /containers/{id}/resize:
     post:

+ 1 - 0
api/types/backend/backend.go

@@ -73,6 +73,7 @@ type LogSelector struct {
 // behavior of a backend.ContainerStats() call.
 type ContainerStatsConfig struct {
 	Stream    bool
+	OneShot   bool
 	OutStream io.Writer
 	Version   string
 }

+ 16 - 0
client/container_stats.go

@@ -24,3 +24,19 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
 	osType := getDockerOS(resp.header.Get("Server"))
 	return types.ContainerStats{Body: resp.body, OSType: osType}, err
 }
+
+// ContainerStatsOneShot gets a single stat entry from a container.
+// It differs from `ContainerStats` in that the API should not wait to prime the stats
+func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (types.ContainerStats, error) {
+	query := url.Values{}
+	query.Set("stream", "0")
+	query.Set("one-shot", "1")
+
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
+	if err != nil {
+		return types.ContainerStats{}, err
+	}
+
+	osType := getDockerOS(resp.header.Get("Server"))
+	return types.ContainerStats{Body: resp.body, OSType: osType}, err
+}

+ 1 - 0
client/interface.go

@@ -67,6 +67,7 @@ type ContainerAPIClient interface {
 	ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error
 	ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
 	ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error)
+	ContainerStatsOneShot(ctx context.Context, container string) (types.ContainerStats, error)
 	ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
 	ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
 	ContainerTop(ctx context.Context, container string, arguments []string) (containertypes.ContainerTopOKBody, error)

+ 6 - 1
daemon/stats.go

@@ -12,6 +12,7 @@ import (
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions/v1p20"
 	"github.com/docker/docker/container"
+	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/pkg/ioutils"
 )
 
@@ -30,6 +31,10 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
 		return err
 	}
 
+	if config.Stream && config.OneShot {
+		return errdefs.InvalidParameter(errors.New("cannot have stream=true and one-shot=true"))
+	}
+
 	// If the container is either not running or restarting and requires no stream, return an empty stats.
 	if (!ctr.IsRunning() || ctr.IsRestarting()) && !config.Stream {
 		return json.NewEncoder(config.OutStream).Encode(&types.StatsJSON{
@@ -64,7 +69,7 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
 	updates := daemon.subscribeToContainerStats(ctr)
 	defer daemon.unsubscribeToContainerStats(ctr, updates)
 
-	noStreamFirstFrame := true
+	noStreamFirstFrame := !config.OneShot
 	for {
 		select {
 		case v, ok := <-updates:

+ 2 - 0
docs/api/version-history.md

@@ -61,6 +61,8 @@ keywords: "API, Docker, rcli, REST, documentation"
   service.
 * `GET /tasks/{id}` now includes `JobIteration` on the task if spawned from a
   job-mode service.
+* `GET /containers/{id}/stats` now accepts a query param (`one-shot`) which, when used with `stream=false` fetches a
+  single set of stats instead of waiting for two collection cycles to have 2 CPU stats over a 1 second period.
 
 ## v1.40 API changes
 

+ 15 - 1
integration/container/stats_test.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"io"
+	"reflect"
 	"testing"
 	"time"
 
@@ -34,10 +35,23 @@ func TestStats(t *testing.T) {
 	assert.NilError(t, err)
 	defer resp.Body.Close()
 
-	var v *types.Stats
+	var v types.Stats
 	err = json.NewDecoder(resp.Body).Decode(&v)
 	assert.NilError(t, err)
 	assert.Check(t, is.Equal(int64(v.MemoryStats.Limit), info.MemTotal))
+	assert.Check(t, !reflect.DeepEqual(v.PreCPUStats, types.CPUStats{}))
+	err = json.NewDecoder(resp.Body).Decode(&v)
+	assert.Assert(t, is.ErrorContains(err, ""), io.EOF)
+
+	resp, err = client.ContainerStatsOneShot(ctx, cID)
+	assert.NilError(t, err)
+	defer resp.Body.Close()
+
+	v = types.Stats{}
+	err = json.NewDecoder(resp.Body).Decode(&v)
+	assert.NilError(t, err)
+	assert.Check(t, is.Equal(int64(v.MemoryStats.Limit), info.MemTotal))
+	assert.Check(t, is.DeepEqual(v.PreCPUStats, types.CPUStats{}))
 	err = json.NewDecoder(resp.Body).Decode(&v)
 	assert.Assert(t, is.ErrorContains(err, ""), io.EOF)
 }