Skip to content

Commit da2c31b

Browse files
author
zhangyue
committed
feature: add container stats api client side
Signed-off-by: zhangyue <[email protected]>
1 parent daeca08 commit da2c31b

4 files changed

Lines changed: 88 additions & 6 deletions

File tree

client/container_stats.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/url"
8+
)
9+
10+
// ContainerStats return the stats related a container in an io.ReadCloser.
11+
func (client *APIClient) ContainerStats(ctx context.Context, name string, stream bool) (io.ReadCloser, error) {
12+
query := url.Values{}
13+
if stream {
14+
query.Set("stream", "1")
15+
}
16+
resp, err := client.get(ctx, fmt.Sprintf("/containers/%s/stats", name), query, nil)
17+
if err != nil {
18+
return nil, err
19+
}
20+
return resp.Body, nil
21+
}

client/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type ContainerAPIClient interface {
4545
ContainerCheckpointList(ctx context.Context, name string, options types.CheckpointListOptions) ([]string, error)
4646
ContainerCheckpointDelete(ctx context.Context, name string, options types.CheckpointDeleteOptions) error
4747
ContainerCommit(ctx context.Context, name string, options types.ContainerCommitOptions) (*types.ContainerCommitResp, error)
48+
ContainerStats(ctx context.Context, name string, stream bool) (io.ReadCloser, error)
4849
}
4950

5051
// ImageAPIClient defines methods of Image client.

daemon/mgr/container_stats.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ func (mgr *ContainerManager) StreamStats(ctx context.Context, name string, confi
2323
}
2424

2525
outStream := config.OutStream
26-
if (!c.IsRunning() || c.IsRestarting()) && !config.Stream {
26+
if (!c.IsRunningOrPaused() || c.IsRestarting()) && !config.Stream {
2727
return json.NewEncoder(outStream).Encode(&types.ContainerStats{
2828
Name: c.Name,
2929
ID: c.ID,
3030
})
3131
}
3232

33-
if c.IsRunning() && !config.Stream {
33+
if c.IsRunningOrPaused() && !config.Stream {
3434
metrics, stats, err := mgr.Stats(ctx, name)
3535
if err != nil {
3636
return err
3737
}
38-
containerStat := toContainerStats(metrics.Timestamp, stats)
38+
containerStat := toContainerStats(metrics.Timestamp, c, stats)
3939
return json.NewEncoder(outStream).Encode(containerStat)
4040
}
4141

@@ -63,7 +63,7 @@ func (mgr *ContainerManager) StreamStats(ctx context.Context, name string, confi
6363
// metrics may be nil if the container is not running,
6464
// so just ignore it and try get the metrics next time.
6565
if metrics != nil {
66-
containerStat := toContainerStats(metrics.Timestamp, stats)
66+
containerStat := toContainerStats(metrics.Timestamp, c, stats)
6767

6868
if err := enc.Encode(containerStat); err != nil {
6969
return err
@@ -87,7 +87,7 @@ func (mgr *ContainerManager) Stats(ctx context.Context, name string) (*container
8787
c.Unlock()
8888

8989
// only get metrics when the container is running
90-
if !(c.IsRunning() || c.IsPaused()) {
90+
if !c.IsRunningOrPaused() {
9191
return nil, nil, nil
9292
}
9393

@@ -104,9 +104,11 @@ func (mgr *ContainerManager) Stats(ctx context.Context, name string) (*container
104104
return metric, v.(*cgroups.Metrics), nil
105105
}
106106

107-
func toContainerStats(time time.Time, metric *cgroups.Metrics) *types.ContainerStats {
107+
func toContainerStats(time time.Time, container *Container, metric *cgroups.Metrics) *types.ContainerStats {
108108
return &types.ContainerStats{
109109
Read: strfmt.DateTime(time),
110+
ID: container.ID,
111+
Name: container.Name,
110112
PidsStats: &types.PidsStats{
111113
Current: metric.Pids.Current,
112114
},

test/api_container_stats_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/alibaba/pouch/apis/types"
8+
"github.com/alibaba/pouch/test/command"
9+
"github.com/alibaba/pouch/test/environment"
10+
"github.com/alibaba/pouch/test/request"
11+
12+
"github.com/go-check/check"
13+
"github.com/gotestyourself/gotestyourself/icmd"
14+
)
15+
16+
// APIContainerListSuite is the test suite for container list API.
17+
type APIContainerStatsSuite struct{}
18+
19+
func init() {
20+
check.Suite(&APIContainerStatsSuite{})
21+
}
22+
23+
// SetUpTest does common setup in the beginning of each test.
24+
func (suite *APIContainerStatsSuite) SetUpTest(c *check.C) {
25+
SkipIfFalse(c, environment.IsLinux)
26+
PullImage(c, busyboxImage125)
27+
}
28+
29+
// TestNoSuchContainer tests a container that doesn't exits return error.
30+
func (suite *APIContainerStatsSuite) TestNoSuchContainer(c *check.C) {
31+
resp, err := request.Get("/containers/nosuchcontainer/stats")
32+
c.Assert(err, check.IsNil)
33+
CheckRespStatus(c, resp, http.StatusNotFound)
34+
}
35+
36+
// TestNoStream tests stats api without stream
37+
func (suite *APIContainerStatsSuite) TestNoStream(c *check.C) {
38+
name := "test_no_stream"
39+
command.PouchRun("run", "-d", "--name", name, busyboxImage, "sh", "-c", "while true; do sleep 1; done").Assert(c, icmd.Success)
40+
defer DelContainerForceMultyTime(c, name)
41+
42+
resp, err := request.Get(fmt.Sprintf("/containers/%s/stats", name))
43+
c.Assert(err, check.IsNil)
44+
CheckRespStatus(c, resp, http.StatusOK)
45+
46+
out := types.ContainerStats{}
47+
err = request.DecodeBody(&out, resp.Body)
48+
c.Assert(err, check.IsNil)
49+
50+
c.Assert(out.Name, check.Equals, name)
51+
c.Assert(out.ID, check.NotNil)
52+
c.Assert(out.Read, check.NotNil)
53+
c.Assert(out.CPUStats, check.NotNil)
54+
c.Assert(out.MemoryStats, check.NotNil)
55+
c.Assert(out.BlkioStats, check.NotNil)
56+
c.Assert(out.PidsStats, check.NotNil)
57+
c.Assert(out.PrecpuStats, check.NotNil)
58+
}

0 commit comments

Comments
 (0)