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

Add stats options to not prime the stats
This commit is contained in:
Sebastiaan van Stijn 2020-04-16 20:57:06 +02:00 committed by GitHub
commit 54d88a7cd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 51 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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