diff --git a/pkg/kubelet/server/stats/BUILD b/pkg/kubelet/server/stats/BUILD index 985ec49f49..c3623fd98d 100644 --- a/pkg/kubelet/server/stats/BUILD +++ b/pkg/kubelet/server/stats/BUILD @@ -8,6 +8,8 @@ go_library( "handler.go", "resource_analyzer.go", "summary.go", + "summary_sys_containers.go", + "summary_sys_containers_windows.go", "volume_stat_calculator.go", ], importpath = "k8s.io/kubernetes/pkg/kubelet/server/stats", @@ -32,20 +34,65 @@ go_test( name = "go_default_test", srcs = [ "summary_test.go", + "summary_windows_test.go", "volume_stat_calculator_test.go", ], embed = [":go_default_library"], deps = [ "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", - "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/server/stats/testing:go_default_library", "//pkg/volume:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", - ], + ] + select({ + "@io_bazel_rules_go//go/platform:android": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:dragonfly": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:freebsd": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:nacl": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:netbsd": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:openbsd": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:plan9": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:solaris": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "@io_bazel_rules_go//go/platform:windows": [ + "//pkg/kubelet/cm:go_default_library", + "//vendor/github.com/google/gofuzz:go_default_library", + ], + "//conditions:default": [], + }), ) filegroup( diff --git a/pkg/kubelet/server/stats/summary.go b/pkg/kubelet/server/stats/summary.go index 0fff691f12..900a8ad97d 100644 --- a/pkg/kubelet/server/stats/summary.go +++ b/pkg/kubelet/server/stats/summary.go @@ -19,8 +19,6 @@ package stats import ( "fmt" - "github.com/golang/glog" - statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" ) @@ -73,41 +71,16 @@ func (sp *summaryProviderImpl) Get(updateStats bool) (*statsapi.Summary, error) } nodeStats := statsapi.NodeStats{ - NodeName: node.Name, - CPU: rootStats.CPU, - Memory: rootStats.Memory, - Network: networkStats, - StartTime: rootStats.StartTime, - Fs: rootFsStats, - Runtime: &statsapi.RuntimeStats{ImageFs: imageFsStats}, - Rlimit: rlimit, + NodeName: node.Name, + CPU: rootStats.CPU, + Memory: rootStats.Memory, + Network: networkStats, + StartTime: rootStats.StartTime, + Fs: rootFsStats, + Runtime: &statsapi.RuntimeStats{ImageFs: imageFsStats}, + Rlimit: rlimit, + SystemContainers: sp.GetSystemContainersStats(nodeConfig, podStats, updateStats), } - - systemContainers := map[string]struct { - name string - forceStatsUpdate bool - }{ - statsapi.SystemContainerKubelet: {nodeConfig.KubeletCgroupsName, false}, - statsapi.SystemContainerRuntime: {nodeConfig.RuntimeCgroupsName, false}, - statsapi.SystemContainerMisc: {nodeConfig.SystemCgroupsName, false}, - statsapi.SystemContainerPods: {sp.provider.GetPodCgroupRoot(), updateStats}, - } - for sys, cont := range systemContainers { - // skip if cgroup name is undefined (not all system containers are required) - if cont.name == "" { - continue - } - s, _, err := sp.provider.GetCgroupStats(cont.name, cont.forceStatsUpdate) - if err != nil { - glog.Errorf("Failed to get system container stats for %q: %v", cont.name, err) - continue - } - // System containers don't have a filesystem associated with them. - s.Logs, s.Rootfs = nil, nil - s.Name = sys - nodeStats.SystemContainers = append(nodeStats.SystemContainers, *s) - } - summary := statsapi.Summary{ Node: nodeStats, Pods: podStats, diff --git a/pkg/kubelet/server/stats/summary_sys_containers.go b/pkg/kubelet/server/stats/summary_sys_containers.go new file mode 100644 index 0000000000..7c4205c93b --- /dev/null +++ b/pkg/kubelet/server/stats/summary_sys_containers.go @@ -0,0 +1,55 @@ +// +build !windows + +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package stats + +import ( + "github.com/golang/glog" + + statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" + "k8s.io/kubernetes/pkg/kubelet/cm" +) + +func (sp *summaryProviderImpl) GetSystemContainersStats(nodeConfig cm.NodeConfig, podStats []statsapi.PodStats, updateStats bool) (stats []statsapi.ContainerStats) { + systemContainers := map[string]struct { + name string + forceStatsUpdate bool + }{ + statsapi.SystemContainerKubelet: {nodeConfig.KubeletCgroupsName, false}, + statsapi.SystemContainerRuntime: {nodeConfig.RuntimeCgroupsName, false}, + statsapi.SystemContainerMisc: {nodeConfig.SystemCgroupsName, false}, + statsapi.SystemContainerPods: {sp.provider.GetPodCgroupRoot(), updateStats}, + } + for sys, cont := range systemContainers { + // skip if cgroup name is undefined (not all system containers are required) + if cont.name == "" { + continue + } + s, _, err := sp.provider.GetCgroupStats(cont.name, cont.forceStatsUpdate) + if err != nil { + glog.Errorf("Failed to get system container stats for %q: %v", cont.name, err) + continue + } + // System containers don't have a filesystem associated with them. + s.Logs, s.Rootfs = nil, nil + s.Name = sys + stats = append(stats, *s) + } + + return stats +} diff --git a/pkg/kubelet/server/stats/summary_sys_containers_windows.go b/pkg/kubelet/server/stats/summary_sys_containers_windows.go new file mode 100644 index 0000000000..b5e3affb7b --- /dev/null +++ b/pkg/kubelet/server/stats/summary_sys_containers_windows.go @@ -0,0 +1,92 @@ +// +build windows + +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package stats + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" + "k8s.io/kubernetes/pkg/kubelet/cm" +) + +func (sp *summaryProviderImpl) GetSystemContainersStats(nodeConfig cm.NodeConfig, podStats []statsapi.PodStats, updateStats bool) (stats []statsapi.ContainerStats) { + stats = append(stats, sp.getSystemPodsStats(nodeConfig, podStats, updateStats)) + return stats +} + +func (sp *summaryProviderImpl) getSystemPodsStats(nodeConfig cm.NodeConfig, podStats []statsapi.PodStats, updateStats bool) statsapi.ContainerStats { + now := metav1.NewTime(time.Now()) + podsSummary := statsapi.ContainerStats{ + StartTime: now, + CPU: &statsapi.CPUStats{}, + Memory: &statsapi.MemoryStats{}, + Name: statsapi.SystemContainerPods, + } + + // Sum up all pod's stats. + var usageCoreNanoSeconds uint64 + var usageNanoCores uint64 + var availableBytes uint64 + var usageBytes uint64 + var workingSetBytes uint64 + for _, pod := range podStats { + if pod.CPU != nil { + podsSummary.CPU.Time = now + if pod.CPU.UsageCoreNanoSeconds != nil { + usageCoreNanoSeconds = usageCoreNanoSeconds + *pod.CPU.UsageCoreNanoSeconds + } + if pod.CPU.UsageNanoCores != nil { + usageNanoCores = usageNanoCores + *pod.CPU.UsageNanoCores + } + } + + if pod.Memory != nil { + podsSummary.Memory.Time = now + if pod.Memory.AvailableBytes != nil { + availableBytes = availableBytes + *pod.Memory.AvailableBytes + } + if pod.Memory.UsageBytes != nil { + usageBytes = usageBytes + *pod.Memory.UsageBytes + } + if pod.Memory.WorkingSetBytes != nil { + workingSetBytes = workingSetBytes + *pod.Memory.WorkingSetBytes + } + } + } + + // Set results only if they are not zero. + if usageCoreNanoSeconds != 0 { + podsSummary.CPU.UsageCoreNanoSeconds = &usageCoreNanoSeconds + } + if usageNanoCores != 0 { + podsSummary.CPU.UsageNanoCores = &usageNanoCores + } + if availableBytes != 0 { + podsSummary.Memory.AvailableBytes = &availableBytes + } + if usageBytes != 0 { + podsSummary.Memory.UsageBytes = &usageBytes + } + if workingSetBytes != 0 { + podsSummary.Memory.WorkingSetBytes = &workingSetBytes + } + + return podsSummary +} diff --git a/pkg/kubelet/server/stats/summary_test.go b/pkg/kubelet/server/stats/summary_test.go index 3b260cf799..7807e2cf41 100644 --- a/pkg/kubelet/server/stats/summary_test.go +++ b/pkg/kubelet/server/stats/summary_test.go @@ -1,3 +1,5 @@ +// +build !windows + /* Copyright 2016 The Kubernetes Authors. diff --git a/pkg/kubelet/server/stats/summary_windows_test.go b/pkg/kubelet/server/stats/summary_windows_test.go new file mode 100644 index 0000000000..35846cf479 --- /dev/null +++ b/pkg/kubelet/server/stats/summary_windows_test.go @@ -0,0 +1,112 @@ +// +build windows + +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package stats + +import ( + "testing" + "time" + + fuzz "github.com/google/gofuzz" + "github.com/stretchr/testify/assert" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" + "k8s.io/kubernetes/pkg/kubelet/cm" + statstest "k8s.io/kubernetes/pkg/kubelet/server/stats/testing" +) + +func TestSummaryProvider(t *testing.T) { + var ( + podStats = []statsapi.PodStats{*getPodStats()} + imageFsStats = getFsStats() + rootFsStats = getFsStats() + node = &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test-node"}} + nodeConfig = cm.NodeConfig{} + cgroupRoot = "/kubepods" + cgroupStatsMap = map[string]struct { + cs *statsapi.ContainerStats + ns *statsapi.NetworkStats + }{ + "/": {cs: getContainerStats()}, + "/pods": {cs: getContainerStats()}, + } + ) + + assert := assert.New(t) + + mockStatsProvider := new(statstest.StatsProvider) + mockStatsProvider. + On("GetNode").Return(node, nil). + On("GetNodeConfig").Return(nodeConfig). + On("GetPodCgroupRoot").Return(cgroupRoot). + On("ListPodStats").Return(podStats, nil). + On("ImageFsStats").Return(imageFsStats, nil). + On("RootFsStats").Return(rootFsStats, nil). + On("RlimitStats").Return(nil, nil). + On("GetCgroupStats", "/", true).Return(cgroupStatsMap["/"].cs, cgroupStatsMap["/"].ns, nil) + + provider := NewSummaryProvider(mockStatsProvider) + summary, err := provider.Get(true) + assert.NoError(err) + + assert.Equal(summary.Node.NodeName, "test-node") + assert.Equal(summary.Node.StartTime, cgroupStatsMap["/"].cs.StartTime) + assert.Equal(summary.Node.CPU, cgroupStatsMap["/"].cs.CPU) + assert.Equal(summary.Node.Memory, cgroupStatsMap["/"].cs.Memory) + assert.Equal(summary.Node.Network, cgroupStatsMap["/"].ns) + assert.Equal(summary.Node.Fs, rootFsStats) + assert.Equal(summary.Node.Runtime, &statsapi.RuntimeStats{ImageFs: imageFsStats}) + + assert.Equal(len(summary.Node.SystemContainers), 1) + assert.Equal(summary.Node.SystemContainers[0].Name, "pods") + assert.Equal(summary.Node.SystemContainers[0].CPU.UsageCoreNanoSeconds, podStats[0].CPU.UsageCoreNanoSeconds) + assert.Equal(summary.Node.SystemContainers[0].CPU.UsageNanoCores, podStats[0].CPU.UsageNanoCores) + assert.Equal(summary.Node.SystemContainers[0].Memory.WorkingSetBytes, podStats[0].Memory.WorkingSetBytes) + assert.Equal(summary.Node.SystemContainers[0].Memory.UsageBytes, podStats[0].Memory.UsageBytes) + assert.Equal(summary.Node.SystemContainers[0].Memory.AvailableBytes, podStats[0].Memory.AvailableBytes) + assert.Equal(summary.Pods, podStats) +} + +func getFsStats() *statsapi.FsStats { + f := fuzz.New().NilChance(0) + v := &statsapi.FsStats{} + f.Fuzz(v) + return v +} + +func getContainerStats() *statsapi.ContainerStats { + f := fuzz.New().NilChance(0) + v := &statsapi.ContainerStats{} + f.Fuzz(v) + return v +} + +func getPodStats() *statsapi.PodStats { + containerStats := getContainerStats() + podStats := statsapi.PodStats{ + PodRef: statsapi.PodReference{Name: "test-pod", Namespace: "test-namespace", UID: "UID_test-pod"}, + StartTime: metav1.NewTime(time.Now()), + Containers: []statsapi.ContainerStats{*containerStats}, + CPU: containerStats.CPU, + Memory: containerStats.Memory, + } + + return &podStats +}