Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -572,7 +572,16 @@ func (s *Server) getContainersStats(version version.Version, w http.ResponseWrit |
| 572 | 572 |
return fmt.Errorf("Missing parameter")
|
| 573 | 573 |
} |
| 574 | 574 |
|
| 575 |
- return s.daemon.ContainerStats(vars["name"], boolValueOrDefault(r, "stream", true), ioutils.NewWriteFlusher(w)) |
|
| 575 |
+ stream := boolValueOrDefault(r, "stream", true) |
|
| 576 |
+ var out io.Writer |
|
| 577 |
+ if !stream {
|
|
| 578 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 579 |
+ out = w |
|
| 580 |
+ } else {
|
|
| 581 |
+ out = ioutils.NewWriteFlusher(w) |
|
| 582 |
+ } |
|
| 583 |
+ |
|
| 584 |
+ return s.daemon.ContainerStats(vars["name"], stream, out) |
|
| 576 | 585 |
} |
| 577 | 586 |
|
| 578 | 587 |
func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| ... | ... |
@@ -2,9 +2,10 @@ package daemon |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 |
+ "io" |
|
| 6 |
+ |
|
| 5 | 7 |
"github.com/docker/docker/api/types" |
| 6 | 8 |
"github.com/docker/docker/daemon/execdriver" |
| 7 |
- "io" |
|
| 8 | 9 |
) |
| 9 | 10 |
|
| 10 | 11 |
func (daemon *Daemon) ContainerStats(name string, stream bool, out io.Writer) error {
|
| ... | ... |
@@ -12,31 +13,39 @@ func (daemon *Daemon) ContainerStats(name string, stream bool, out io.Writer) er |
| 12 | 12 |
if err != nil {
|
| 13 | 13 |
return err |
| 14 | 14 |
} |
| 15 |
- var pre_cpu_stats types.CpuStats |
|
| 16 |
- for first_v := range updates {
|
|
| 17 |
- first_update := first_v.(*execdriver.ResourceStats) |
|
| 18 |
- first_stats := convertToAPITypes(first_update.Stats) |
|
| 19 |
- pre_cpu_stats = first_stats.CpuStats |
|
| 20 |
- pre_cpu_stats.SystemUsage = first_update.SystemUsage |
|
| 21 |
- break |
|
| 22 |
- } |
|
| 23 |
- enc := json.NewEncoder(out) |
|
| 24 |
- for v := range updates {
|
|
| 15 |
+ |
|
| 16 |
+ var preCpuStats types.CpuStats |
|
| 17 |
+ getStat := func(v interface{}) *types.Stats {
|
|
| 25 | 18 |
update := v.(*execdriver.ResourceStats) |
| 26 |
- ss := convertToAPITypes(update.Stats) |
|
| 27 |
- ss.PreCpuStats = pre_cpu_stats |
|
| 19 |
+ ss := convertStatsToAPITypes(update.Stats) |
|
| 20 |
+ ss.PreCpuStats = preCpuStats |
|
| 28 | 21 |
ss.MemoryStats.Limit = uint64(update.MemoryLimit) |
| 29 | 22 |
ss.Read = update.Read |
| 30 | 23 |
ss.CpuStats.SystemUsage = update.SystemUsage |
| 31 |
- pre_cpu_stats = ss.CpuStats |
|
| 32 |
- if err := enc.Encode(ss); err != nil {
|
|
| 24 |
+ preCpuStats = ss.CpuStats |
|
| 25 |
+ return ss |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ enc := json.NewEncoder(out) |
|
| 29 |
+ |
|
| 30 |
+ if !stream {
|
|
| 31 |
+ // prime the cpu stats so they aren't 0 in the final output |
|
| 32 |
+ s := getStat(<-updates) |
|
| 33 |
+ |
|
| 34 |
+ // now pull stats again with the cpu stats primed |
|
| 35 |
+ s = getStat(<-updates) |
|
| 36 |
+ err := enc.Encode(s) |
|
| 37 |
+ daemon.UnsubscribeToContainerStats(name, updates) |
|
| 38 |
+ return err |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ for v := range updates {
|
|
| 42 |
+ s := getStat(v) |
|
| 43 |
+ if err := enc.Encode(s); err != nil {
|
|
| 33 | 44 |
// TODO: handle the specific broken pipe |
| 34 | 45 |
daemon.UnsubscribeToContainerStats(name, updates) |
| 35 | 46 |
return err |
| 36 | 47 |
} |
| 37 |
- if !stream {
|
|
| 38 |
- break |
|
| 39 |
- } |
|
| 40 | 48 |
} |
| 41 | 49 |
return nil |
| 42 | 50 |
} |
| ... | ... |
@@ -6,9 +6,9 @@ import ( |
| 6 | 6 |
"github.com/docker/libcontainer/cgroups" |
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 |
-// convertToAPITypes converts the libcontainer.Stats to the api specific |
|
| 9 |
+// convertStatsToAPITypes converts the libcontainer.Stats to the api specific |
|
| 10 | 10 |
// structs. This is done to preserve API compatibility and versioning. |
| 11 |
-func convertToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
|
| 11 |
+func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
|
| 12 | 12 |
s := &types.Stats{}
|
| 13 | 13 |
if ls.Interfaces != nil {
|
| 14 | 14 |
s.Network = types.Network{}
|
| ... | ... |
@@ -5,9 +5,9 @@ import ( |
| 5 | 5 |
"github.com/docker/libcontainer" |
| 6 | 6 |
) |
| 7 | 7 |
|
| 8 |
-// convertToAPITypes converts the libcontainer.Stats to the api specific |
|
| 8 |
+// convertStatsToAPITypes converts the libcontainer.Stats to the api specific |
|
| 9 | 9 |
// structs. This is done to preserve API compatibility and versioning. |
| 10 |
-func convertToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
|
| 10 |
+func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
|
| 11 | 11 |
// TODO Windows. Refactor accordingly to fill in stats. |
| 12 | 12 |
s := &types.Stats{}
|
| 13 | 13 |
return s |
| ... | ... |
@@ -10,14 +10,16 @@ import ( |
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 | 12 |
func (s *DockerSuite) TestCliStatsNoStreamGetCpu(c *check.C) {
|
| 13 |
- out, _ := dockerCmd(c, "run", "-d", "--cpu-quota=2000", "busybox", "/bin/sh", "-c", "while true;do echo 'Hello';done") |
|
| 13 |
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true;do echo 'Hello'; usleep 100000; done") |
|
| 14 | 14 |
|
| 15 | 15 |
id := strings.TrimSpace(out) |
| 16 | 16 |
err := waitRun(id) |
| 17 | 17 |
c.Assert(err, check.IsNil) |
| 18 | 18 |
|
| 19 |
- _, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
|
|
| 19 |
+ resp, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
|
|
| 20 | 20 |
c.Assert(err, check.IsNil) |
| 21 |
+ c.Assert(resp.ContentLength > 0, check.Equals, true, check.Commentf("should not use chunked encoding"))
|
|
| 22 |
+ c.Assert(resp.Header.Get("Content-Type"), check.Equals, "application/json")
|
|
| 21 | 23 |
|
| 22 | 24 |
var v *types.Stats |
| 23 | 25 |
err = json.NewDecoder(body).Decode(&v) |