Ver Fonte

Fix Windows `docker stats` showing Linux headers

This fix is an attempt to fix issue raised in #28005 where
`docker stats` on Windows shows Linux headers if there is
no containers in stats.

The reason for the issue is that, in case there is no container,
a header is faked in:
https://github.com/docker/docker/blob/v1.13.0/cli/command/formatter/formatter.go#L74-L78
which does not know OS type information (as OS was stored with container stat entries)

This fix tries to fix the issue by moving OS type information
to stats context (instead of individual container stats entry).

Additional unit tests have been added.

This fix fixes #28005.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
Yong Tang há 8 anos atrás
pai
commit
0a3a3ba2b6

+ 1 - 1
cli/command/container/stats.go

@@ -213,7 +213,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
 			ccstats = append(ccstats, c.GetStatistics())
 			ccstats = append(ccstats, c.GetStatistics())
 		}
 		}
 		cStats.mu.Unlock()
 		cStats.mu.Unlock()
-		if err = formatter.ContainerStatsWrite(statsCtx, ccstats); err != nil {
+		if err = formatter.ContainerStatsWrite(statsCtx, ccstats, daemonOSType); err != nil {
 			break
 			break
 		}
 		}
 		if len(cStats.cs) == 0 && !showAll {
 		if len(cStats.cs) == 0 && !showAll {

+ 11 - 11
cli/command/formatter/stats.go

@@ -37,7 +37,6 @@ type StatsEntry struct {
 	BlockWrite       float64
 	BlockWrite       float64
 	PidsCurrent      uint64 // Not used on Windows
 	PidsCurrent      uint64 // Not used on Windows
 	IsInvalid        bool
 	IsInvalid        bool
-	OSType           string
 }
 }
 
 
 // ContainerStats represents an entity to store containers statistics synchronously
 // ContainerStats represents an entity to store containers statistics synchronously
@@ -88,7 +87,6 @@ func (cs *ContainerStats) SetStatistics(s StatsEntry) {
 	cs.mutex.Lock()
 	cs.mutex.Lock()
 	defer cs.mutex.Unlock()
 	defer cs.mutex.Unlock()
 	s.Container = cs.Container
 	s.Container = cs.Container
-	s.OSType = cs.OSType
 	cs.StatsEntry = s
 	cs.StatsEntry = s
 }
 }
 
 
@@ -113,16 +111,17 @@ func NewStatsFormat(source, osType string) Format {
 // NewContainerStats returns a new ContainerStats entity and sets in it the given name
 // NewContainerStats returns a new ContainerStats entity and sets in it the given name
 func NewContainerStats(container, osType string) *ContainerStats {
 func NewContainerStats(container, osType string) *ContainerStats {
 	return &ContainerStats{
 	return &ContainerStats{
-		StatsEntry: StatsEntry{Container: container, OSType: osType},
+		StatsEntry: StatsEntry{Container: container},
 	}
 	}
 }
 }
 
 
 // ContainerStatsWrite renders the context for a list of containers statistics
 // ContainerStatsWrite renders the context for a list of containers statistics
-func ContainerStatsWrite(ctx Context, containerStats []StatsEntry) error {
+func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string) error {
 	render := func(format func(subContext subContext) error) error {
 	render := func(format func(subContext subContext) error) error {
 		for _, cstats := range containerStats {
 		for _, cstats := range containerStats {
 			containerStatsCtx := &containerStatsContext{
 			containerStatsCtx := &containerStatsContext{
-				s: cstats,
+				s:  cstats,
+				os: osType,
 			}
 			}
 			if err := format(containerStatsCtx); err != nil {
 			if err := format(containerStatsCtx); err != nil {
 				return err
 				return err
@@ -130,12 +129,13 @@ func ContainerStatsWrite(ctx Context, containerStats []StatsEntry) error {
 		}
 		}
 		return nil
 		return nil
 	}
 	}
-	return ctx.Write(&containerStatsContext{}, render)
+	return ctx.Write(&containerStatsContext{os: osType}, render)
 }
 }
 
 
 type containerStatsContext struct {
 type containerStatsContext struct {
 	HeaderContext
 	HeaderContext
-	s StatsEntry
+	s  StatsEntry
+	os string
 }
 }
 
 
 func (c *containerStatsContext) MarshalJSON() ([]byte, error) {
 func (c *containerStatsContext) MarshalJSON() ([]byte, error) {
@@ -168,14 +168,14 @@ func (c *containerStatsContext) CPUPerc() string {
 
 
 func (c *containerStatsContext) MemUsage() string {
 func (c *containerStatsContext) MemUsage() string {
 	header := memUseHeader
 	header := memUseHeader
-	if c.s.OSType == winOSType {
+	if c.os == winOSType {
 		header = winMemUseHeader
 		header = winMemUseHeader
 	}
 	}
 	c.AddHeader(header)
 	c.AddHeader(header)
 	if c.s.IsInvalid {
 	if c.s.IsInvalid {
 		return fmt.Sprintf("-- / --")
 		return fmt.Sprintf("-- / --")
 	}
 	}
-	if c.s.OSType == winOSType {
+	if c.os == winOSType {
 		return fmt.Sprintf("%s", units.BytesSize(c.s.Memory))
 		return fmt.Sprintf("%s", units.BytesSize(c.s.Memory))
 	}
 	}
 	return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
 	return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
@@ -184,7 +184,7 @@ func (c *containerStatsContext) MemUsage() string {
 func (c *containerStatsContext) MemPerc() string {
 func (c *containerStatsContext) MemPerc() string {
 	header := memPercHeader
 	header := memPercHeader
 	c.AddHeader(header)
 	c.AddHeader(header)
-	if c.s.IsInvalid || c.s.OSType == winOSType {
+	if c.s.IsInvalid || c.os == winOSType {
 		return fmt.Sprintf("--")
 		return fmt.Sprintf("--")
 	}
 	}
 	return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
 	return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
@@ -208,7 +208,7 @@ func (c *containerStatsContext) BlockIO() string {
 
 
 func (c *containerStatsContext) PIDs() string {
 func (c *containerStatsContext) PIDs() string {
 	c.AddHeader(pidsHeader)
 	c.AddHeader(pidsHeader)
-	if c.s.IsInvalid || c.s.OSType == winOSType {
+	if c.s.IsInvalid || c.os == winOSType {
 		return fmt.Sprintf("--")
 		return fmt.Sprintf("--")
 	}
 	}
 	return fmt.Sprintf("%d", c.s.PidsCurrent)
 	return fmt.Sprintf("%d", c.s.PidsCurrent)

+ 59 - 24
cli/command/formatter/stats_test.go

@@ -14,30 +14,31 @@ func TestContainerStatsContext(t *testing.T) {
 	var ctx containerStatsContext
 	var ctx containerStatsContext
 	tt := []struct {
 	tt := []struct {
 		stats     StatsEntry
 		stats     StatsEntry
+		osType    string
 		expValue  string
 		expValue  string
 		expHeader string
 		expHeader string
 		call      func() string
 		call      func() string
 	}{
 	}{
-		{StatsEntry{Container: containerID}, containerID, containerHeader, ctx.Container},
-		{StatsEntry{CPUPercentage: 5.5}, "5.50%", cpuPercHeader, ctx.CPUPerc},
-		{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "--", cpuPercHeader, ctx.CPUPerc},
-		{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "0.31 B / 12.3 B", netIOHeader, ctx.NetIO},
-		{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "--", netIOHeader, ctx.NetIO},
-		{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "0.1 B / 2.3 B", blockIOHeader, ctx.BlockIO},
-		{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "--", blockIOHeader, ctx.BlockIO},
-		{StatsEntry{MemoryPercentage: 10.2}, "10.20%", memPercHeader, ctx.MemPerc},
-		{StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "--", memPercHeader, ctx.MemPerc},
-		{StatsEntry{MemoryPercentage: 10.2, OSType: "windows"}, "--", memPercHeader, ctx.MemPerc},
-		{StatsEntry{Memory: 24, MemoryLimit: 30}, "24 B / 30 B", memUseHeader, ctx.MemUsage},
-		{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "-- / --", memUseHeader, ctx.MemUsage},
-		{StatsEntry{Memory: 24, MemoryLimit: 30, OSType: "windows"}, "24 B", winMemUseHeader, ctx.MemUsage},
-		{StatsEntry{PidsCurrent: 10}, "10", pidsHeader, ctx.PIDs},
-		{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "--", pidsHeader, ctx.PIDs},
-		{StatsEntry{PidsCurrent: 10, OSType: "windows"}, "--", pidsHeader, ctx.PIDs},
+		{StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container},
+		{StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc},
+		{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc},
+		{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31 B / 12.3 B", netIOHeader, ctx.NetIO},
+		{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
+		{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1 B / 2.3 B", blockIOHeader, ctx.BlockIO},
+		{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "", "--", blockIOHeader, ctx.BlockIO},
+		{StatsEntry{MemoryPercentage: 10.2}, "", "10.20%", memPercHeader, ctx.MemPerc},
+		{StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "", "--", memPercHeader, ctx.MemPerc},
+		{StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc},
+		{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24 B / 30 B", memUseHeader, ctx.MemUsage},
+		{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
+		{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24 B", winMemUseHeader, ctx.MemUsage},
+		{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
+		{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
+		{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
 	}
 	}
 
 
 	for _, te := range tt {
 	for _, te := range tt {
-		ctx = containerStatsContext{s: te.stats}
+		ctx = containerStatsContext{s: te.stats, os: te.osType}
 		if v := te.call(); v != te.expValue {
 		if v := te.call(); v != te.expValue {
 			t.Fatalf("Expected %q, got %q", te.expValue, v)
 			t.Fatalf("Expected %q, got %q", te.expValue, v)
 		}
 		}
@@ -93,7 +94,6 @@ container2  --
 				BlockWrite:       20,
 				BlockWrite:       20,
 				PidsCurrent:      2,
 				PidsCurrent:      2,
 				IsInvalid:        false,
 				IsInvalid:        false,
-				OSType:           "linux",
 			},
 			},
 			{
 			{
 				Container:        "container2",
 				Container:        "container2",
@@ -107,12 +107,11 @@ container2  --
 				BlockWrite:       30,
 				BlockWrite:       30,
 				PidsCurrent:      3,
 				PidsCurrent:      3,
 				IsInvalid:        true,
 				IsInvalid:        true,
-				OSType:           "linux",
 			},
 			},
 		}
 		}
 		var out bytes.Buffer
 		var out bytes.Buffer
 		te.context.Output = &out
 		te.context.Output = &out
-		err := ContainerStatsWrite(te.context, stats)
+		err := ContainerStatsWrite(te.context, stats, "linux")
 		if err != nil {
 		if err != nil {
 			assert.Error(t, err, te.expected)
 			assert.Error(t, err, te.expected)
 		} else {
 		} else {
@@ -161,7 +160,6 @@ container2  --  --
 				BlockWrite:       20,
 				BlockWrite:       20,
 				PidsCurrent:      2,
 				PidsCurrent:      2,
 				IsInvalid:        false,
 				IsInvalid:        false,
-				OSType:           "windows",
 			},
 			},
 			{
 			{
 				Container:        "container2",
 				Container:        "container2",
@@ -175,12 +173,11 @@ container2  --  --
 				BlockWrite:       30,
 				BlockWrite:       30,
 				PidsCurrent:      3,
 				PidsCurrent:      3,
 				IsInvalid:        true,
 				IsInvalid:        true,
-				OSType:           "windows",
 			},
 			},
 		}
 		}
 		var out bytes.Buffer
 		var out bytes.Buffer
 		te.context.Output = &out
 		te.context.Output = &out
-		err := ContainerStatsWrite(te.context, stats)
+		err := ContainerStatsWrite(te.context, stats, "windows")
 		if err != nil {
 		if err != nil {
 			assert.Error(t, err, te.expected)
 			assert.Error(t, err, te.expected)
 		} else {
 		} else {
@@ -220,9 +217,47 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
 	}
 	}
 
 
 	for _, context := range contexts {
 	for _, context := range contexts {
-		ContainerStatsWrite(context.context, []StatsEntry{})
+		ContainerStatsWrite(context.context, []StatsEntry{}, "linux")
 		assert.Equal(t, context.expected, out.String())
 		assert.Equal(t, context.expected, out.String())
 		// Clean buffer
 		// Clean buffer
 		out.Reset()
 		out.Reset()
 	}
 	}
 }
 }
+
+func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
+	var out bytes.Buffer
+
+	contexts := []struct {
+		context  Context
+		expected string
+	}{
+		{
+			Context{
+				Format: "{{.Container}}",
+				Output: &out,
+			},
+			"",
+		},
+		{
+			Context{
+				Format: "table {{.Container}}\t{{.MemUsage}}",
+				Output: &out,
+			},
+			"CONTAINER           PRIV WORKING SET\n",
+		},
+		{
+			Context{
+				Format: "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}",
+				Output: &out,
+			},
+			"CONTAINER           CPU %               PRIV WORKING SET\n",
+		},
+	}
+
+	for _, context := range contexts {
+		ContainerStatsWrite(context.context, []StatsEntry{}, "windows")
+		assert.Equal(t, out.String(), context.expected)
+		// Clean buffer
+		out.Reset()
+	}
+}