`docker stats --no-stream` always print zero values.
```
$ docker stats --no-stream
CONTAINER CPU % MEM USAGE / LIMIT MEM %
NET I/O BLOCK I/O
7f4ef234ca8c 0.00% 0 B / 0 B 0.00%
0 B / 0 B 0 B / 0 B
f05bd18819aa 0.00% 0 B / 0 B 0.00%
0 B / 0 B 0 B / 0 B
```
This commit will let docker client wait until it gets correct stat
data before print it on screen.
Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"io" |
| 6 | 6 |
"strings" |
| 7 |
+ "sync" |
|
| 7 | 8 |
"text/tabwriter" |
| 8 | 9 |
"time" |
| 9 | 10 |
|
| ... | ... |
@@ -59,6 +60,9 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
| 59 | 59 |
}) |
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 |
+ // waitFirst is a WaitGroup to wait first stat data's reach for each container |
|
| 63 |
+ waitFirst := &sync.WaitGroup{}
|
|
| 64 |
+ |
|
| 62 | 65 |
cStats := stats{}
|
| 63 | 66 |
// getContainerList simulates creation event for all previously existing |
| 64 | 67 |
// containers (only used when calling `docker stats` without arguments). |
| ... | ... |
@@ -72,8 +76,10 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
| 72 | 72 |
} |
| 73 | 73 |
for _, container := range cs {
|
| 74 | 74 |
s := &containerStats{Name: container.ID[:12]}
|
| 75 |
- cStats.add(s) |
|
| 76 |
- go s.Collect(cli.client, !*noStream) |
|
| 75 |
+ if cStats.add(s) {
|
|
| 76 |
+ waitFirst.Add(1) |
|
| 77 |
+ go s.Collect(cli.client, !*noStream, waitFirst) |
|
| 78 |
+ } |
|
| 77 | 79 |
} |
| 78 | 80 |
} |
| 79 | 81 |
|
| ... | ... |
@@ -87,15 +93,19 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
| 87 | 87 |
eh.Handle("create", func(e events.Message) {
|
| 88 | 88 |
if *all {
|
| 89 | 89 |
s := &containerStats{Name: e.ID[:12]}
|
| 90 |
- cStats.add(s) |
|
| 91 |
- go s.Collect(cli.client, !*noStream) |
|
| 90 |
+ if cStats.add(s) {
|
|
| 91 |
+ waitFirst.Add(1) |
|
| 92 |
+ go s.Collect(cli.client, !*noStream, waitFirst) |
|
| 93 |
+ } |
|
| 92 | 94 |
} |
| 93 | 95 |
}) |
| 94 | 96 |
|
| 95 | 97 |
eh.Handle("start", func(e events.Message) {
|
| 96 | 98 |
s := &containerStats{Name: e.ID[:12]}
|
| 97 |
- cStats.add(s) |
|
| 98 |
- go s.Collect(cli.client, !*noStream) |
|
| 99 |
+ if cStats.add(s) {
|
|
| 100 |
+ waitFirst.Add(1) |
|
| 101 |
+ go s.Collect(cli.client, !*noStream, waitFirst) |
|
| 102 |
+ } |
|
| 99 | 103 |
}) |
| 100 | 104 |
|
| 101 | 105 |
eh.Handle("die", func(e events.Message) {
|
| ... | ... |
@@ -112,14 +122,16 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
| 112 | 112 |
|
| 113 | 113 |
// Start a short-lived goroutine to retrieve the initial list of |
| 114 | 114 |
// containers. |
| 115 |
- go getContainerList() |
|
| 115 |
+ getContainerList() |
|
| 116 | 116 |
} else {
|
| 117 | 117 |
// Artificially send creation events for the containers we were asked to |
| 118 | 118 |
// monitor (same code path than we use when monitoring all containers). |
| 119 | 119 |
for _, name := range names {
|
| 120 | 120 |
s := &containerStats{Name: name}
|
| 121 |
- cStats.add(s) |
|
| 122 |
- go s.Collect(cli.client, !*noStream) |
|
| 121 |
+ if cStats.add(s) {
|
|
| 122 |
+ waitFirst.Add(1) |
|
| 123 |
+ go s.Collect(cli.client, !*noStream, waitFirst) |
|
| 124 |
+ } |
|
| 123 | 125 |
} |
| 124 | 126 |
|
| 125 | 127 |
// We don't expect any asynchronous errors: closeChan can be closed. |
| ... | ... |
@@ -143,6 +155,9 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
| 143 | 143 |
} |
| 144 | 144 |
} |
| 145 | 145 |
|
| 146 |
+ // before print to screen, make sure each container get at least one valid stat data |
|
| 147 |
+ waitFirst.Wait() |
|
| 148 |
+ |
|
| 146 | 149 |
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
| 147 | 150 |
printHeader := func() {
|
| 148 | 151 |
if !*noStream {
|
| ... | ... |
@@ -33,12 +33,14 @@ type stats struct {
|
| 33 | 33 |
cs []*containerStats |
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 |
-func (s *stats) add(cs *containerStats) {
|
|
| 36 |
+func (s *stats) add(cs *containerStats) bool {
|
|
| 37 | 37 |
s.mu.Lock() |
| 38 |
+ defer s.mu.Unlock() |
|
| 38 | 39 |
if _, exists := s.isKnownContainer(cs.Name); !exists {
|
| 39 | 40 |
s.cs = append(s.cs, cs) |
| 41 |
+ return true |
|
| 40 | 42 |
} |
| 41 |
- s.mu.Unlock() |
|
| 43 |
+ return false |
|
| 42 | 44 |
} |
| 43 | 45 |
|
| 44 | 46 |
func (s *stats) remove(id string) {
|
| ... | ... |
@@ -58,7 +60,22 @@ func (s *stats) isKnownContainer(cid string) (int, bool) {
|
| 58 | 58 |
return -1, false |
| 59 | 59 |
} |
| 60 | 60 |
|
| 61 |
-func (s *containerStats) Collect(cli client.APIClient, streamStats bool) {
|
|
| 61 |
+func (s *containerStats) Collect(cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) {
|
|
| 62 |
+ var ( |
|
| 63 |
+ getFirst bool |
|
| 64 |
+ previousCPU uint64 |
|
| 65 |
+ previousSystem uint64 |
|
| 66 |
+ u = make(chan error, 1) |
|
| 67 |
+ ) |
|
| 68 |
+ |
|
| 69 |
+ defer func() {
|
|
| 70 |
+ // if error happens and we get nothing of stats, release wait group whatever |
|
| 71 |
+ if !getFirst {
|
|
| 72 |
+ getFirst = true |
|
| 73 |
+ waitFirst.Done() |
|
| 74 |
+ } |
|
| 75 |
+ }() |
|
| 76 |
+ |
|
| 62 | 77 |
responseBody, err := cli.ContainerStats(context.Background(), s.Name, streamStats) |
| 63 | 78 |
if err != nil {
|
| 64 | 79 |
s.mu.Lock() |
| ... | ... |
@@ -68,12 +85,7 @@ func (s *containerStats) Collect(cli client.APIClient, streamStats bool) {
|
| 68 | 68 |
} |
| 69 | 69 |
defer responseBody.Close() |
| 70 | 70 |
|
| 71 |
- var ( |
|
| 72 |
- previousCPU uint64 |
|
| 73 |
- previousSystem uint64 |
|
| 74 |
- dec = json.NewDecoder(responseBody) |
|
| 75 |
- u = make(chan error, 1) |
|
| 76 |
- ) |
|
| 71 |
+ dec := json.NewDecoder(responseBody) |
|
| 77 | 72 |
go func() {
|
| 78 | 73 |
for {
|
| 79 | 74 |
var v *types.StatsJSON |
| ... | ... |
@@ -125,6 +137,11 @@ func (s *containerStats) Collect(cli client.APIClient, streamStats bool) {
|
| 125 | 125 |
s.BlockRead = 0 |
| 126 | 126 |
s.BlockWrite = 0 |
| 127 | 127 |
s.mu.Unlock() |
| 128 |
+ // if this is the first stat you get, release WaitGroup |
|
| 129 |
+ if !getFirst {
|
|
| 130 |
+ getFirst = true |
|
| 131 |
+ waitFirst.Done() |
|
| 132 |
+ } |
|
| 128 | 133 |
case err := <-u: |
| 129 | 134 |
if err != nil {
|
| 130 | 135 |
s.mu.Lock() |
| ... | ... |
@@ -132,6 +149,11 @@ func (s *containerStats) Collect(cli client.APIClient, streamStats bool) {
|
| 132 | 132 |
s.mu.Unlock() |
| 133 | 133 |
return |
| 134 | 134 |
} |
| 135 |
+ // if this is the first stat you get, release WaitGroup |
|
| 136 |
+ if !getFirst {
|
|
| 137 |
+ getFirst = true |
|
| 138 |
+ waitFirst.Done() |
|
| 139 |
+ } |
|
| 135 | 140 |
} |
| 136 | 141 |
if !streamStats {
|
| 137 | 142 |
return |
| ... | ... |
@@ -75,6 +75,18 @@ func (s *DockerSuite) TestStatsAllRunningNoStream(c *check.C) {
|
| 75 | 75 |
if strings.Contains(out, id3) {
|
| 76 | 76 |
c.Fatalf("Did not expect %s in stats, got %s", id3, out)
|
| 77 | 77 |
} |
| 78 |
+ |
|
| 79 |
+ // check output contains real data, but not all zeros |
|
| 80 |
+ reg, _ := regexp.Compile("[1-9]+")
|
|
| 81 |
+ // split output with "\n", outLines[1] is id2's output |
|
| 82 |
+ // outLines[2] is id1's output |
|
| 83 |
+ outLines := strings.Split(out, "\n") |
|
| 84 |
+ // check stat result of id2 contains real data |
|
| 85 |
+ realData := reg.Find([]byte(outLines[1][12:])) |
|
| 86 |
+ c.Assert(realData, checker.NotNil, check.Commentf("stat result are empty: %s", out))
|
|
| 87 |
+ // check stat result of id1 contains real data |
|
| 88 |
+ realData = reg.Find([]byte(outLines[2][12:])) |
|
| 89 |
+ c.Assert(realData, checker.NotNil, check.Commentf("stat result are empty: %s", out))
|
|
| 78 | 90 |
} |
| 79 | 91 |
|
| 80 | 92 |
func (s *DockerSuite) TestStatsAllNoStream(c *check.C) {
|
| ... | ... |
@@ -93,6 +105,17 @@ func (s *DockerSuite) TestStatsAllNoStream(c *check.C) {
|
| 93 | 93 |
if !strings.Contains(out, id1) || !strings.Contains(out, id2) {
|
| 94 | 94 |
c.Fatalf("Expected stats output to contain both %s and %s, got %s", id1, id2, out)
|
| 95 | 95 |
} |
| 96 |
+ |
|
| 97 |
+ // check output contains real data, but not all zeros |
|
| 98 |
+ reg, _ := regexp.Compile("[1-9]+")
|
|
| 99 |
+ // split output with "\n", outLines[1] is id2's output |
|
| 100 |
+ outLines := strings.Split(out, "\n") |
|
| 101 |
+ // check stat result of id2 contains real data |
|
| 102 |
+ realData := reg.Find([]byte(outLines[1][12:])) |
|
| 103 |
+ c.Assert(realData, checker.NotNil, check.Commentf("stat result of %s is empty: %s", id2, out))
|
|
| 104 |
+ // check stat result of id1 contains all zero |
|
| 105 |
+ realData = reg.Find([]byte(outLines[2][12:])) |
|
| 106 |
+ c.Assert(realData, checker.IsNil, check.Commentf("stat result of %s should be empty : %s", id1, out))
|
|
| 96 | 107 |
} |
| 97 | 108 |
|
| 98 | 109 |
func (s *DockerSuite) TestStatsAllNewContainersAdded(c *check.C) {
|