Skip to content

Commit

Permalink
add fields definition for DockerStat API of Docker host uses cgroup2 (#…
Browse files Browse the repository at this point in the history
…922)

* chore: add fields definition for DockerStat API of Docker host uses cgroup2

Docker Engine 20.10.0+ uses cgroup2 instead of cgroup by default when available (cf. moby/moby#40846 ).
Using cgroup2 varies the output of DockerStats API.
In particular, there are many new fields on Stats.MemoryStats.Stats.
Therefore this commit adds the fields for new fields on cgroup2 to `Stats` struct.

* chore: removed extra commas
  • Loading branch information
xruins committed Jun 2, 2022
1 parent fbff592 commit 991fa23
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 1 deletion.
24 changes: 24 additions & 0 deletions container_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ type Stats struct {
TotalPgpgin uint64 `json:"total_pgpgin,omitempty" yaml:"total_pgpgin,omitempty" toml:"total_pgpgin,omitempty"`
HierarchicalMemswLimit uint64 `json:"hierarchical_memsw_limit,omitempty" yaml:"hierarchical_memsw_limit,omitempty" toml:"hierarchical_memsw_limit,omitempty"`
Swap uint64 `json:"swap,omitempty" yaml:"swap,omitempty" toml:"swap,omitempty"`
Anon uint64 `json:"anon,omitempty" yaml:"anon,omitempty" toml:"anon,omitempty"`
AnonThp uint64 `json:"anon_thp,omitempty" yaml:"anon_thp,omitempty" toml:"anon_thp,omitempty"`
File uint64 `json:"file,omitempty" yaml:"file,omitempty" toml:"file,omitempty"`
FileDirty uint64 `json:"file_dirty,omitempty" yaml:"file_dirty,omitempty" toml:"file_dirty,omitempty"`
FileMapped uint64 `json:"file_mapped,omitempty" yaml:"file_mapped,omitempty" toml:"file_mapped,omitempty"`
FileWriteback uint64 `json:"file_writeback,omitempty" yaml:"file_writeback,omitempty" toml:"file_writeback,omitempty"`
KernelStack uint64 `json:"kernel_stack,omitempty" yaml:"kernel_stack,omitempty" toml:"kernel_stack,omitempty"`
Pgactivate uint64 `json:"pgactivate,omitempty" yaml:"pgactivate,omitempty" toml:"pgactivate,omitempty"`
Pgdeactivate uint64 `json:"pgdeactivate,omitempty" yaml:"pgdeactivate,omitempty" toml:"pgdeactivate,omitempty"`
Pglazyfree uint64 `json:"pglazyfree,omitempty" yaml:"pglazyfree,omitempty" toml:"pglazyfree,omitempty"`
Pglazyfreed uint64 `json:"pglazyfreed,omitempty" yaml:"pglazyfreed,omitempty" toml:"pglazyfreed,omitempty"`
Pgrefill uint64 `json:"pgrefill,omitempty" yaml:"pgrefill,omitempty" toml:"pgrefill,omitempty"`
Pgscan uint64 `json:"pgscan,omitempty" yaml:"pgscan,omitempty" toml:"pgscan,omitempty"`
Pgsteal uint64 `json:"pgsteal,omitempty" yaml:"pgsteal,omitempty" toml:"pgsteal,omitempty"`
Shmem uint64 `json:"shmem,omitempty" yaml:"shmem,omitempty" toml:"shmem,omitempty"`
Slab uint64 `json:"slab,omitempty" yaml:"slab,omitempty" toml:"slab,omitempty"`
SlabReclaimable uint64 `json:"slab_reclaimable,omitempty" yaml:"slab_reclaimable,omitempty" toml:"slab_reclaimable,omitempty"`
SlabUnreclaimable uint64 `json:"slab_unreclaimable,omitempty" yaml:"slab_unreclaimable,omitempty" toml:"slab_unreclaimable,omitempty"`
Sock uint64 `json:"sock,omitempty" yaml:"sock,omitempty" toml:"sock,omitempty"`
ThpCollapseAlloc uint64 `json:"thp_collapse_alloc,omitempty" yaml:"thp_collapse_alloc,omitempty" toml:"thp_collapse_alloc,omitempty"`
ThpFaultAlloc uint64 `json:"thp_fault_alloc,omitempty" yaml:"thp_fault_alloc,omitempty" toml:"thp_fault_alloc,omitempty"`
WorkingsetActivate uint64 `json:"workingset_activate,omitempty" yaml:"workingset_activate,omitempty" toml:"workingset_activate,omitempty"`
WorkingsetNodereclaim uint64 `json:"workingset_nodereclaim,omitempty" yaml:"workingset_nodereclaim,omitempty" toml:"workingset_nodereclaim,omitempty"`
WorkingsetRefault uint64 `json:"workingset_refault,omitempty" yaml:"workingset_refault,omitempty" toml:"workingset_refault,omitempty"`
} `json:"stats,omitempty" yaml:"stats,omitempty" toml:"stats,omitempty"`
MaxUsage uint64 `json:"max_usage,omitempty" yaml:"max_usage,omitempty" toml:"max_usage,omitempty"`
Usage uint64 `json:"usage,omitempty" yaml:"usage,omitempty" toml:"usage,omitempty"`
Expand Down
287 changes: 286 additions & 1 deletion container_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (
"testing"
)

func TestStats(t *testing.T) {
// The test for Docker Stat API of the host uses cgroup
func TestStatsV1(t *testing.T) {
t.Parallel()
jsonStats1 := `{
"read" : "2015-01-08T22:57:31.547920715Z",
Expand Down Expand Up @@ -321,6 +322,290 @@ func TestStats(t *testing.T) {
}
}

// The test for Docker Stat API of the host uses cgroup2
func TestStatsV2(t *testing.T) {
t.Parallel()
jsonStats1 := `{
"read": "2022-05-18T14:37:18.175989615Z",
"preread": "2022-05-18T14:37:17.171501052Z",
"pids_stats": {
"current": 29,
"limit": 76948
},
"blkio_stats": {
"io_service_bytes_recursive": [
{
"major": 259,
"minor": 0,
"op": "read",
"value": 11390976
},
{
"major": 259,
"minor": 0,
"op": "write",
"value": 0
}
],
"io_serviced_recursive": null,
"io_queue_recursive": null,
"io_service_time_recursive": null,
"io_wait_time_recursive": null,
"io_merged_recursive": null,
"io_time_recursive": null,
"sectors_recursive": null
},
"num_procs": 0,
"storage_stats": {},
"cpu_stats": {
"cpu_usage": {
"total_usage": 185266562000,
"usage_in_kernelmode": 37912635000,
"usage_in_usermode": 147353926000
},
"system_cpu_usage": 26707255190000000,
"online_cpus": 24,
"throttling_data": {
"periods": 0,
"throttled_periods": 0,
"throttled_time": 0
}
},
"precpu_stats": {
"cpu_usage": {
"total_usage": 185266562000,
"usage_in_kernelmode": 37912635000,
"usage_in_usermode": 147353926000
},
"system_cpu_usage": 26707231080000000,
"online_cpus": 24,
"throttling_data": {
"periods": 0,
"throttled_periods": 0,
"throttled_time": 0
}
},
"memory_stats": {
"usage": 28557312,
"stats": {
"active_anon": 4096,
"active_file": 7446528,
"anon": 16572416,
"anon_thp": 0,
"file": 10829824,
"file_dirty": 0,
"file_mapped": 9740288,
"file_writeback": 0,
"inactive_anon": 8069120,
"inactive_file": 11882496,
"kernel_stack": 475136,
"pgactivate": 241,
"pgdeactivate": 253,
"pgfault": 7714,
"pglazyfree": 3042,
"pglazyfreed": 967,
"pgmajfault": 155,
"pgrefill": 301,
"pgscan": 1802,
"pgsteal": 1100,
"shmem": 0,
"slab": 488920,
"slab_reclaimable": 159664,
"slab_unreclaimable": 329256,
"sock": 0,
"thp_collapse_alloc": 0,
"thp_fault_alloc": 0,
"unevictable": 0,
"workingset_activate": 0,
"workingset_nodereclaim": 0,
"workingset_refault": 0
},
"limit": 67353382912
},
"networks": {
"eth0": {
"rx_bytes": 96802652,
"rx_packets": 623704,
"rx_errors": 0,
"rx_dropped": 0,
"tx_bytes": 16597749,
"tx_packets": 91982,
"tx_errors": 0,
"tx_dropped": 0
}
}
}`
// 1 second later, shmem is 100
jsonStats2 := `{
"read": "2022-05-18T14:37:18.175989615Z",
"preread": "2022-05-18T14:37:17.171501052Z",
"pids_stats": {
"current": 29,
"limit": 76948
},
"blkio_stats": {
"io_service_bytes_recursive": [
{
"major": 259,
"minor": 0,
"op": "read",
"value": 11390976
},
{
"major": 259,
"minor": 0,
"op": "write",
"value": 0
}
],
"io_serviced_recursive": null,
"io_queue_recursive": null,
"io_service_time_recursive": null,
"io_wait_time_recursive": null,
"io_merged_recursive": null,
"io_time_recursive": null,
"sectors_recursive": null
},
"num_procs": 0,
"storage_stats": {},
"cpu_stats": {
"cpu_usage": {
"total_usage": 185266562000,
"usage_in_kernelmode": 37912635000,
"usage_in_usermode": 147353926000
},
"system_cpu_usage": 26707255190000000,
"online_cpus": 24,
"throttling_data": {
"periods": 0,
"throttled_periods": 0,
"throttled_time": 0
}
},
"precpu_stats": {
"cpu_usage": {
"total_usage": 185266562000,
"usage_in_kernelmode": 37912635000,
"usage_in_usermode": 147353926000
},
"system_cpu_usage": 26707231080000000,
"online_cpus": 24,
"throttling_data": {
"periods": 0,
"throttled_periods": 0,
"throttled_time": 0
}
},
"memory_stats": {
"usage": 28557312,
"stats": {
"active_anon": 4096,
"active_file": 7446528,
"anon": 16572416,
"anon_thp": 0,
"file": 10829824,
"file_dirty": 0,
"file_mapped": 9740288,
"file_writeback": 0,
"inactive_anon": 8069120,
"inactive_file": 11882496,
"kernel_stack": 475136,
"pgactivate": 241,
"pgdeactivate": 253,
"pgfault": 7714,
"pglazyfree": 3042,
"pglazyfreed": 967,
"pgmajfault": 155,
"pgrefill": 301,
"pgscan": 1802,
"pgsteal": 1100,
"shmem": 100,
"slab": 488920,
"slab_reclaimable": 159664,
"slab_unreclaimable": 329256,
"sock": 0,
"thp_collapse_alloc": 0,
"thp_fault_alloc": 0,
"unevictable": 0,
"workingset_activate": 0,
"workingset_nodereclaim": 0,
"workingset_refault": 0
},
"limit": 67353382912
},
"networks": {
"eth0": {
"rx_bytes": 96802652,
"rx_packets": 623704,
"rx_errors": 0,
"rx_dropped": 0,
"tx_bytes": 16597749,
"tx_packets": 91982,
"tx_errors": 0,
"tx_dropped": 0
}
}
}`
var expected1 Stats
var expected2 Stats
err := json.Unmarshal([]byte(jsonStats1), &expected1)
if err != nil {
t.Fatal(err)
}
err = json.Unmarshal([]byte(jsonStats2), &expected2)
if err != nil {
t.Fatal(err)
}
id := "4fa6e0f0"

var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(jsonStats1))
w.Write([]byte(jsonStats2))
req = *r
}))
defer server.Close()
client, _ := NewClient(server.URL)
client.SkipServerVersionCheck = true
errC := make(chan error, 1)
statsC := make(chan *Stats)
done := make(chan bool)
defer close(done)
go func() {
errC <- client.Stats(StatsOptions{ID: id, Stats: statsC, Stream: true, Done: done})
close(errC)
}()
var resultStats []*Stats
for {
stats, ok := <-statsC
if !ok {
break
}
resultStats = append(resultStats, stats)
}
err = <-errC
if err != nil {
t.Fatal(err)
}
if len(resultStats) != 2 {
t.Fatalf("Stats: Expected 2 results. Got %d.", len(resultStats))
}
if !reflect.DeepEqual(resultStats[0], &expected1) {
t.Errorf("Stats: Expected:\n%+v\nGot:\n%+v", expected1, resultStats[0])
}
if !reflect.DeepEqual(resultStats[1], &expected2) {
t.Errorf("Stats: Expected:\n%+v\nGot:\n%+v", expected2, resultStats[1])
}
if req.Method != http.MethodGet {
t.Errorf("Stats: wrong HTTP method. Want GET. Got %s.", req.Method)
}
u, _ := url.Parse(client.getURL("/containers/" + id + "/stats"))
if req.URL.Path != u.Path {
t.Errorf("Stats: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path)
}
}

func TestStatsContainerNotFound(t *testing.T) {
t.Parallel()
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
Expand Down

0 comments on commit 991fa23

Please sign in to comment.