Browse code

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

Add stats options to not prime the stats

Sebastiaan van Stijn authored on 2020/04/17 03:57:06
Showing 8 changed files
... ...
@@ -105,9 +105,14 @@ func (s *containerRouter) getContainersStats(ctx context.Context, w http.Respons
105 105
 	if !stream {
106 106
 		w.Header().Set("Content-Type", "application/json")
107 107
 	}
108
+	var oneShot bool
109
+	if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.41") {
110
+		oneShot = httputils.BoolValueOrDefault(r, "one-shot", false)
111
+	}
108 112
 
109 113
 	config := &backend.ContainerStatsConfig{
110 114
 		Stream:    stream,
115
+		OneShot:   oneShot,
111 116
 		OutStream: w,
112 117
 		Version:   httputils.VersionFromContext(ctx),
113 118
 	}
... ...
@@ -5691,6 +5691,11 @@ paths:
5691 5691
           description: "Stream the output. If false, the stats will be output once and then it will disconnect."
5692 5692
           type: "boolean"
5693 5693
           default: true
5694
+        - name: "one-shot"
5695
+          in: "query"
5696
+          description: "Only get a single stat instead of waiting for 2 cycles. Must be used with stream=false"
5697
+          type: "boolean"
5698
+          default: false
5694 5699
       tags: ["Container"]
5695 5700
   /containers/{id}/resize:
5696 5701
     post:
... ...
@@ -73,6 +73,7 @@ type LogSelector struct {
73 73
 // behavior of a backend.ContainerStats() call.
74 74
 type ContainerStatsConfig struct {
75 75
 	Stream    bool
76
+	OneShot   bool
76 77
 	OutStream io.Writer
77 78
 	Version   string
78 79
 }
... ...
@@ -24,3 +24,19 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
24 24
 	osType := getDockerOS(resp.header.Get("Server"))
25 25
 	return types.ContainerStats{Body: resp.body, OSType: osType}, err
26 26
 }
27
+
28
+// ContainerStatsOneShot gets a single stat entry from a container.
29
+// It differs from `ContainerStats` in that the API should not wait to prime the stats
30
+func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (types.ContainerStats, error) {
31
+	query := url.Values{}
32
+	query.Set("stream", "0")
33
+	query.Set("one-shot", "1")
34
+
35
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
36
+	if err != nil {
37
+		return types.ContainerStats{}, err
38
+	}
39
+
40
+	osType := getDockerOS(resp.header.Get("Server"))
41
+	return types.ContainerStats{Body: resp.body, OSType: osType}, err
42
+}
... ...
@@ -67,6 +67,7 @@ type ContainerAPIClient interface {
67 67
 	ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error
68 68
 	ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
69 69
 	ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error)
70
+	ContainerStatsOneShot(ctx context.Context, container string) (types.ContainerStats, error)
70 71
 	ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
71 72
 	ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
72 73
 	ContainerTop(ctx context.Context, container string, arguments []string) (containertypes.ContainerTopOKBody, error)
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"github.com/docker/docker/api/types/versions"
13 13
 	"github.com/docker/docker/api/types/versions/v1p20"
14 14
 	"github.com/docker/docker/container"
15
+	"github.com/docker/docker/errdefs"
15 16
 	"github.com/docker/docker/pkg/ioutils"
16 17
 )
17 18
 
... ...
@@ -30,6 +31,10 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
30 30
 		return err
31 31
 	}
32 32
 
33
+	if config.Stream && config.OneShot {
34
+		return errdefs.InvalidParameter(errors.New("cannot have stream=true and one-shot=true"))
35
+	}
36
+
33 37
 	// If the container is either not running or restarting and requires no stream, return an empty stats.
34 38
 	if (!ctr.IsRunning() || ctr.IsRestarting()) && !config.Stream {
35 39
 		return json.NewEncoder(config.OutStream).Encode(&types.StatsJSON{
... ...
@@ -64,7 +69,7 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
64 64
 	updates := daemon.subscribeToContainerStats(ctr)
65 65
 	defer daemon.unsubscribeToContainerStats(ctr, updates)
66 66
 
67
-	noStreamFirstFrame := true
67
+	noStreamFirstFrame := !config.OneShot
68 68
 	for {
69 69
 		select {
70 70
 		case v, ok := <-updates:
... ...
@@ -61,6 +61,8 @@ keywords: "API, Docker, rcli, REST, documentation"
61 61
   service.
62 62
 * `GET /tasks/{id}` now includes `JobIteration` on the task if spawned from a
63 63
   job-mode service.
64
+* `GET /containers/{id}/stats` now accepts a query param (`one-shot`) which, when used with `stream=false` fetches a
65
+  single set of stats instead of waiting for two collection cycles to have 2 CPU stats over a 1 second period.
64 66
 
65 67
 ## v1.40 API changes
66 68
 
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"io"
7
+	"reflect"
7 8
 	"testing"
8 9
 	"time"
9 10
 
... ...
@@ -34,10 +35,23 @@ func TestStats(t *testing.T) {
34 34
 	assert.NilError(t, err)
35 35
 	defer resp.Body.Close()
36 36
 
37
-	var v *types.Stats
37
+	var v types.Stats
38 38
 	err = json.NewDecoder(resp.Body).Decode(&v)
39 39
 	assert.NilError(t, err)
40 40
 	assert.Check(t, is.Equal(int64(v.MemoryStats.Limit), info.MemTotal))
41
+	assert.Check(t, !reflect.DeepEqual(v.PreCPUStats, types.CPUStats{}))
42
+	err = json.NewDecoder(resp.Body).Decode(&v)
43
+	assert.Assert(t, is.ErrorContains(err, ""), io.EOF)
44
+
45
+	resp, err = client.ContainerStatsOneShot(ctx, cID)
46
+	assert.NilError(t, err)
47
+	defer resp.Body.Close()
48
+
49
+	v = types.Stats{}
50
+	err = json.NewDecoder(resp.Body).Decode(&v)
51
+	assert.NilError(t, err)
52
+	assert.Check(t, is.Equal(int64(v.MemoryStats.Limit), info.MemTotal))
53
+	assert.Check(t, is.DeepEqual(v.PreCPUStats, types.CPUStats{}))
41 54
 	err = json.NewDecoder(resp.Body).Decode(&v)
42 55
 	assert.Assert(t, is.ErrorContains(err, ""), io.EOF)
43 56
 }