Skip to content

Commit 697c231

Browse files
authored
Merge pull request kubernetes#70121 from feiskyer/win-net-stats3
Add network stats for Windows containers
2 parents d547163 + 9cf38de commit 697c231

File tree

9 files changed

+237
-11
lines changed

9 files changed

+237
-11
lines changed

pkg/kubelet/stats/BUILD

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ go_library(
55
srcs = [
66
"cadvisor_stats_provider.go",
77
"cri_stats_provider.go",
8+
"cri_stats_provider_unsupported.go",
9+
"cri_stats_provider_windows.go",
810
"helper.go",
911
"log_metrics_provider.go",
1012
"stats_provider.go",
@@ -33,7 +35,12 @@ go_library(
3335
"//vendor/github.com/google/cadvisor/info/v1:go_default_library",
3436
"//vendor/github.com/google/cadvisor/info/v2:go_default_library",
3537
"//vendor/k8s.io/klog:go_default_library",
36-
],
38+
] + select({
39+
"@io_bazel_rules_go//go/platform:windows": [
40+
"//vendor/github.com/Microsoft/hcsshim:go_default_library",
41+
],
42+
"//conditions:default": [],
43+
}),
3744
)
3845

3946
filegroup(

pkg/kubelet/stats/cri_stats_provider.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@ import (
2525
"time"
2626

2727
cadvisorfs "github.com/google/cadvisor/fs"
28-
"k8s.io/klog"
29-
3028
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
3129
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3230
"k8s.io/apimachinery/pkg/types"
31+
"k8s.io/klog"
3332
internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri"
3433
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
3534
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
@@ -124,6 +123,12 @@ func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
124123
}
125124
caInfos := getCRICadvisorStats(allInfos)
126125

126+
// get network stats for containers.
127+
containerNetworkStats, err := p.listContainerNetworkStats()
128+
if err != nil {
129+
return nil, fmt.Errorf("failed to list container network stats: %v", err)
130+
}
131+
127132
for _, stats := range resp {
128133
containerID := stats.Attributes.Id
129134
container, found := containerMap[containerID]
@@ -147,7 +152,7 @@ func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
147152

148153
// Fill available stats for full set of required pod stats
149154
cs := p.makeContainerStats(stats, container, &rootFsInfo, fsIDtoInfo, podSandbox.GetMetadata().GetUid())
150-
p.addPodNetworkStats(ps, podSandboxID, caInfos, cs)
155+
p.addPodNetworkStats(ps, podSandboxID, caInfos, cs, containerNetworkStats[podSandboxID])
151156
p.addPodCPUMemoryStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)
152157

153158
// If cadvisor stats is available for the container, use it to populate
@@ -353,11 +358,21 @@ func (p *criStatsProvider) addPodNetworkStats(
353358
podSandboxID string,
354359
caInfos map[string]cadvisorapiv2.ContainerInfo,
355360
cs *statsapi.ContainerStats,
361+
netStats *statsapi.NetworkStats,
356362
) {
357363
caPodSandbox, found := caInfos[podSandboxID]
358364
// try get network stats from cadvisor first.
359365
if found {
360-
ps.Network = cadvisorInfoToNetworkStats(ps.PodRef.Name, &caPodSandbox)
366+
networkStats := cadvisorInfoToNetworkStats(ps.PodRef.Name, &caPodSandbox)
367+
if networkStats != nil {
368+
ps.Network = networkStats
369+
return
370+
}
371+
}
372+
373+
// Not found from cadvisor, get from netStats.
374+
if netStats != nil {
375+
ps.Network = netStats
361376
return
362377
}
363378

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// +build !windows
2+
3+
/*
4+
Copyright 2018 The Kubernetes Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package stats
20+
21+
import (
22+
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
23+
)
24+
25+
// listContainerNetworkStats returns the network stats of all the running containers.
26+
func (p *criStatsProvider) listContainerNetworkStats() (map[string]*statsapi.NetworkStats, error) {
27+
// Always return nil for unsupported platforms.
28+
return nil, nil
29+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// +build windows
2+
3+
/*
4+
Copyright 2018 The Kubernetes Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package stats
20+
21+
import (
22+
"time"
23+
24+
"github.com/Microsoft/hcsshim"
25+
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/klog"
28+
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
29+
)
30+
31+
// listContainerNetworkStats returns the network stats of all the running containers.
32+
func (p *criStatsProvider) listContainerNetworkStats() (map[string]*statsapi.NetworkStats, error) {
33+
containers, err := hcsshim.GetContainers(hcsshim.ComputeSystemQuery{
34+
Types: []string{"Container"},
35+
})
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
stats := make(map[string]*statsapi.NetworkStats)
41+
for _, c := range containers {
42+
container, err := hcsshim.OpenContainer(c.ID)
43+
if err != nil {
44+
klog.Warningf("Failed to open container %q with error '%v', continue to get stats for other containers", c.ID, err)
45+
continue
46+
}
47+
48+
cstats, err := container.Statistics()
49+
if err != nil {
50+
klog.Warningf("Failed to get statistics for container %q with error '%v', continue to get stats for other containers", c.ID, err)
51+
continue
52+
}
53+
54+
if len(cstats.Network) > 0 {
55+
stats[c.ID] = hcsStatsToNetworkStats(cstats.Timestamp, cstats.Network)
56+
}
57+
}
58+
59+
return stats, nil
60+
}
61+
62+
// hcsStatsToNetworkStats converts hcsshim.Statistics.Network to statsapi.NetworkStats
63+
func hcsStatsToNetworkStats(timestamp time.Time, hcsStats []hcsshim.NetworkStats) *statsapi.NetworkStats {
64+
result := &statsapi.NetworkStats{
65+
Time: metav1.NewTime(timestamp),
66+
Interfaces: make([]statsapi.InterfaceStats, 0),
67+
}
68+
69+
for _, stat := range hcsStats {
70+
iStat := hcsStatsToInterfaceStats(stat)
71+
if iStat != nil {
72+
result.Interfaces = append(result.Interfaces, *iStat)
73+
}
74+
}
75+
76+
// TODO(feiskyer): add support of multiple interfaces for getting default interface.
77+
if len(result.Interfaces) > 0 {
78+
result.InterfaceStats = result.Interfaces[0]
79+
}
80+
81+
return result
82+
}
83+
84+
// hcsStatsToInterfaceStats converts hcsshim.NetworkStats to statsapi.InterfaceStats.
85+
func hcsStatsToInterfaceStats(stat hcsshim.NetworkStats) *statsapi.InterfaceStats {
86+
return &statsapi.InterfaceStats{
87+
Name: stat.EndpointId,
88+
RxBytes: &stat.BytesReceived,
89+
TxBytes: &stat.BytesSent,
90+
}
91+
}

pkg/kubelet/stats/helper.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,10 @@ import (
2020
"fmt"
2121
"time"
2222

23-
"k8s.io/klog"
24-
2523
cadvisorapiv1 "github.com/google/cadvisor/info/v1"
2624
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
2725
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/klog"
2827
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
2928
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
3029
)
@@ -158,6 +157,10 @@ func cadvisorInfoToNetworkStats(name string, info *cadvisorapiv2.ContainerInfo)
158157
return nil
159158
}
160159

160+
if cstat.Network == nil {
161+
return nil
162+
}
163+
161164
iStats := statsapi.NetworkStats{
162165
Time: metav1.NewTime(cstat.Timestamp),
163166
}

pkg/kubelet/winstats/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go_library(
44
name = "go_default_library",
55
srcs = [
66
"doc.go",
7+
"network_stats.go",
78
"perfcounter_nodestats.go",
89
"perfcounters.go",
910
"version.go",
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// +build windows
2+
3+
/*
4+
Copyright 2019 The Kubernetes Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package winstats
20+
21+
import (
22+
"encoding/json"
23+
"os/exec"
24+
25+
cadvisorapi "github.com/google/cadvisor/info/v1"
26+
)
27+
28+
// netAdapterStat represents network statistics for an adapter.
29+
type netAdapterStat struct {
30+
Name string `json:"Name,omitempty"`
31+
ReceivedBytes uint64 `json:"ReceivedBytes,omitempty"`
32+
ReceivedErrors uint64 `json:"ReceivedPacketErrors,omitempty"`
33+
SentBytes uint64 `json:"SentBytes,omitempty"`
34+
SentErrors uint64 `json:"OutboundPacketErrors,omitempty"`
35+
}
36+
37+
// toCadvisorInterfaceStats converts netAdapterStat to cadvisorapi.InterfaceStats.
38+
func (s *netAdapterStat) toCadvisorInterfaceStats() cadvisorapi.InterfaceStats {
39+
return cadvisorapi.InterfaceStats{
40+
Name: s.Name,
41+
RxBytes: s.ReceivedBytes,
42+
RxErrors: s.ReceivedErrors,
43+
TxBytes: s.SentBytes,
44+
TxErrors: s.SentErrors,
45+
}
46+
}
47+
48+
// getNetAdapterStats gets a list of network adapter statistics.
49+
func getNetAdapterStats() ([]cadvisorapi.InterfaceStats, error) {
50+
rawOutput, err := exec.Command("powershell", "/c", " Get-NetAdapterStatistics | ConvertTo-Json").CombinedOutput()
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
var stats []*netAdapterStat
56+
err = json.Unmarshal(rawOutput, &stats)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
result := make([]cadvisorapi.InterfaceStats, len(stats))
62+
for i := range stats {
63+
result[i] = stats[i].toCadvisorInterfaceStats()
64+
}
65+
66+
return result, nil
67+
}

pkg/kubelet/winstats/winstats.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,16 @@ func (c *StatsClient) WinVersionInfo() (*cadvisorapi.VersionInfo, error) {
109109

110110
func (c *StatsClient) createRootContainerInfo() (*cadvisorapiv2.ContainerInfo, error) {
111111
nodeMetrics, err := c.client.getNodeMetrics()
112+
if err != nil {
113+
return nil, err
114+
}
112115

116+
netAdapterStats, err := getNetAdapterStats()
113117
if err != nil {
114118
return nil, err
115119
}
116-
var stats []*cadvisorapiv2.ContainerStats
117120

121+
var stats []*cadvisorapiv2.ContainerStats
118122
stats = append(stats, &cadvisorapiv2.ContainerStats{
119123
Timestamp: nodeMetrics.timeStamp,
120124
Cpu: &cadvisorapi.CpuStats{
@@ -126,6 +130,9 @@ func (c *StatsClient) createRootContainerInfo() (*cadvisorapiv2.ContainerInfo, e
126130
WorkingSet: nodeMetrics.memoryPrivWorkingSetBytes,
127131
Usage: nodeMetrics.memoryCommittedBytes,
128132
},
133+
Network: &cadvisorapiv2.NetworkStats{
134+
Interfaces: netAdapterStats,
135+
},
129136
})
130137

131138
nodeInfo := c.client.getNodeInfo()
@@ -134,6 +141,7 @@ func (c *StatsClient) createRootContainerInfo() (*cadvisorapiv2.ContainerInfo, e
134141
CreationTime: nodeInfo.startTime,
135142
HasCpu: true,
136143
HasMemory: true,
144+
HasNetwork: true,
137145
Memory: cadvisorapiv2.MemorySpec{
138146
Limit: nodeInfo.memoryPhysicalCapacityBytes,
139147
},

pkg/kubelet/winstats/winstats_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,21 @@ func TestWinContainerInfos(t *testing.T) {
8686
infos := make(map[string]cadvisorapiv2.ContainerInfo)
8787
infos["/"] = cadvisorapiv2.ContainerInfo{
8888
Spec: cadvisorapiv2.ContainerSpec{
89-
HasCpu: true,
90-
HasMemory: true,
89+
HasCpu: true,
90+
HasMemory: true,
91+
HasNetwork: true,
9192
Memory: cadvisorapiv2.MemorySpec{
9293
Limit: 1.6e+10,
9394
},
9495
},
9596
Stats: stats,
9697
}
9798

98-
assert.Equal(t, actualRootInfos, infos)
99+
assert.Equal(t, len(actualRootInfos), len(infos))
100+
assert.Equal(t, actualRootInfos["/"].Spec, infos["/"].Spec)
101+
assert.Equal(t, len(actualRootInfos["/"].Stats), len(infos["/"].Stats))
102+
assert.Equal(t, actualRootInfos["/"].Stats[0].Cpu, infos["/"].Stats[0].Cpu)
103+
assert.Equal(t, actualRootInfos["/"].Stats[0].Memory, infos["/"].Stats[0].Memory)
99104
}
100105

101106
func TestWinMachineInfo(t *testing.T) {

0 commit comments

Comments
 (0)