diff --git a/pkg/kubelet/eviction/helpers_test.go b/pkg/kubelet/eviction/helpers_test.go index 0ed5a1b673f..ab960a4a546 100644 --- a/pkg/kubelet/eviction/helpers_test.go +++ b/pkg/kubelet/eviction/helpers_test.go @@ -1011,6 +1011,10 @@ func (f *fakeSummaryProvider) Get(updateStats bool) (*statsapi.Summary, error) { return f.result, nil } +func (f *fakeSummaryProvider) GetCPUAndMemoryStats() (*statsapi.Summary, error) { + return f.result, nil +} + // newPodStats returns a pod stat where each container is using the specified working set // each pod must have a Name, UID, Namespace func newPodStats(pod *v1.Pod, containerWorkingSetBytes int64) statsapi.PodStats { diff --git a/pkg/kubelet/server/stats/handler.go b/pkg/kubelet/server/stats/handler.go index f069cd898cb..6e665344503 100644 --- a/pkg/kubelet/server/stats/handler.go +++ b/pkg/kubelet/server/stats/handler.go @@ -192,10 +192,23 @@ func (h *handler) handleStats(request *restful.Request, response *restful.Respon } // Handles stats summary requests to /stats/summary +// If "only_cpu_and_memory" GET param is true then only cpu and memory is returned in response. func (h *handler) handleSummary(request *restful.Request, response *restful.Response) { - // external calls to the summary API use cached stats - forceStatsUpdate := false - summary, err := h.summaryProvider.Get(forceStatsUpdate) + onlyCPUAndMemory := false + request.Request.ParseForm() + if onlyCluAndMemoryParam, found := request.Request.Form["only_cpu_and_memory"]; found && + len(onlyCluAndMemoryParam) == 1 && onlyCluAndMemoryParam[0] == "true" { + onlyCPUAndMemory = true + } + var summary *statsapi.Summary + var err error + if onlyCPUAndMemory { + summary, err = h.summaryProvider.GetCPUAndMemoryStats() + } else { + // external calls to the summary API use cached stats + forceStatsUpdate := false + summary, err = h.summaryProvider.Get(forceStatsUpdate) + } if err != nil { handleError(response, "/stats/summary", err) } else { diff --git a/pkg/kubelet/server/stats/summary.go b/pkg/kubelet/server/stats/summary.go index 900a8ad97d7..2897aff50f5 100644 --- a/pkg/kubelet/server/stats/summary.go +++ b/pkg/kubelet/server/stats/summary.go @@ -26,6 +26,8 @@ type SummaryProvider interface { // Get provides a new Summary with the stats from Kubelet, // and will update some stats if updateStats is true Get(updateStats bool) (*statsapi.Summary, error) + // GetCPUAndMemoryStats provides a new Summary with the CPU and memory stats from Kubelet, + GetCPUAndMemoryStats() (*statsapi.Summary, error) } // summaryProviderImpl implements the SummaryProvider interface. @@ -87,3 +89,32 @@ func (sp *summaryProviderImpl) Get(updateStats bool) (*statsapi.Summary, error) } return &summary, nil } + +func (sp *summaryProviderImpl) GetCPUAndMemoryStats() (*statsapi.Summary, error) { + summary, err := sp.Get(false) + if err != nil { + return nil, err + } + summary.Node.Network = nil + summary.Node.Fs = nil + summary.Node.Runtime = nil + summary.Node.Rlimit = nil + for i := 0; i < len(summary.Node.SystemContainers); i++ { + summary.Node.SystemContainers[i].Accelerators = nil + summary.Node.SystemContainers[i].Rootfs = nil + summary.Node.SystemContainers[i].Logs = nil + summary.Node.SystemContainers[i].UserDefinedMetrics = nil + } + for i := 0; i < len(summary.Pods); i++ { + summary.Pods[i].Network = nil + summary.Pods[i].VolumeStats = nil + summary.Pods[i].EphemeralStorage = nil + for j := 0; j < len(summary.Pods[i].Containers); j++ { + summary.Pods[i].Containers[j].Accelerators = nil + summary.Pods[i].Containers[j].Rootfs = nil + summary.Pods[i].Containers[j].Logs = nil + summary.Pods[i].Containers[j].UserDefinedMetrics = nil + } + } + return summary, nil +} diff --git a/pkg/kubelet/server/stats/summary_test.go b/pkg/kubelet/server/stats/summary_test.go index 7807e2cf412..b6d7dbbab74 100644 --- a/pkg/kubelet/server/stats/summary_test.go +++ b/pkg/kubelet/server/stats/summary_test.go @@ -32,39 +32,39 @@ import ( statstest "k8s.io/kubernetes/pkg/kubelet/server/stats/testing" ) -func TestSummaryProvider(t *testing.T) { - var ( - podStats = []statsapi.PodStats{ - { - PodRef: statsapi.PodReference{Name: "test-pod", Namespace: "test-namespace", UID: "UID_test-pod"}, - StartTime: metav1.NewTime(time.Now()), - Containers: []statsapi.ContainerStats{*getContainerStats()}, - Network: getNetworkStats(), - VolumeStats: []statsapi.VolumeStats{*getVolumeStats()}, - }, - } - imageFsStats = getFsStats() - rootFsStats = getFsStats() - node = &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test-node"}} - nodeConfig = cm.NodeConfig{ - RuntimeCgroupsName: "/runtime", - SystemCgroupsName: "/misc", - KubeletCgroupsName: "/kubelet", - } - cgroupRoot = "/kubepods" - cgroupStatsMap = map[string]struct { - cs *statsapi.ContainerStats - ns *statsapi.NetworkStats - }{ - "/": {cs: getContainerStats(), ns: getNetworkStats()}, - "/runtime": {cs: getContainerStats(), ns: getNetworkStats()}, - "/misc": {cs: getContainerStats(), ns: getNetworkStats()}, - "/kubelet": {cs: getContainerStats(), ns: getNetworkStats()}, - "/pods": {cs: getContainerStats(), ns: getNetworkStats()}, - } - rlimitStats = getRlimitStats() - ) +var ( + podStats = []statsapi.PodStats{ + { + PodRef: statsapi.PodReference{Name: "test-pod", Namespace: "test-namespace", UID: "UID_test-pod"}, + StartTime: metav1.NewTime(time.Now()), + Containers: []statsapi.ContainerStats{*getContainerStats()}, + Network: getNetworkStats(), + VolumeStats: []statsapi.VolumeStats{*getVolumeStats()}, + }, + } + imageFsStats = getFsStats() + rootFsStats = getFsStats() + node = &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test-node"}} + nodeConfig = cm.NodeConfig{ + RuntimeCgroupsName: "/runtime", + SystemCgroupsName: "/misc", + KubeletCgroupsName: "/kubelet", + } + cgroupRoot = "/kubepods" + cgroupStatsMap = map[string]struct { + cs *statsapi.ContainerStats + ns *statsapi.NetworkStats + }{ + "/": {cs: getContainerStats(), ns: getNetworkStats()}, + "/runtime": {cs: getContainerStats(), ns: getNetworkStats()}, + "/misc": {cs: getContainerStats(), ns: getNetworkStats()}, + "/kubelet": {cs: getContainerStats(), ns: getNetworkStats()}, + "/pods": {cs: getContainerStats(), ns: getNetworkStats()}, + } + rlimitStats = getRlimitStats() +) +func TestSummaryProviderGetStats(t *testing.T) { assert := assert.New(t) mockStatsProvider := new(statstest.StatsProvider) @@ -130,6 +130,64 @@ func TestSummaryProvider(t *testing.T) { assert.Equal(summary.Pods, podStats) } +func TestSummaryProviderGetCPUAndMemoryStats(t *testing.T) { + 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(rlimitStats, nil). + On("GetCgroupStats", "/", false).Return(cgroupStatsMap["/"].cs, cgroupStatsMap["/"].ns, nil). + On("GetCgroupStats", "/runtime", false).Return(cgroupStatsMap["/runtime"].cs, cgroupStatsMap["/runtime"].ns, nil). + On("GetCgroupStats", "/misc", false).Return(cgroupStatsMap["/misc"].cs, cgroupStatsMap["/misc"].ns, nil). + On("GetCgroupStats", "/kubelet", false).Return(cgroupStatsMap["/kubelet"].cs, cgroupStatsMap["/kubelet"].ns, nil). + On("GetCgroupStats", "/kubepods", false).Return(cgroupStatsMap["/pods"].cs, cgroupStatsMap["/pods"].ns, nil) + + provider := NewSummaryProvider(mockStatsProvider) + summary, err := provider.GetCPUAndMemoryStats() + 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.Nil(summary.Node.Network) + assert.Nil(summary.Node.Fs) + assert.Nil(summary.Node.Runtime) + + assert.Equal(len(summary.Node.SystemContainers), 4) + assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{ + Name: "kubelet", + StartTime: cgroupStatsMap["/kubelet"].cs.StartTime, + CPU: cgroupStatsMap["/kubelet"].cs.CPU, + Memory: cgroupStatsMap["/kubelet"].cs.Memory, + }) + assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{ + Name: "misc", + StartTime: cgroupStatsMap["/misc"].cs.StartTime, + CPU: cgroupStatsMap["/misc"].cs.CPU, + Memory: cgroupStatsMap["/misc"].cs.Memory, + }) + assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{ + Name: "runtime", + StartTime: cgroupStatsMap["/runtime"].cs.StartTime, + CPU: cgroupStatsMap["/runtime"].cs.CPU, + Memory: cgroupStatsMap["/runtime"].cs.Memory, + }) + assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{ + Name: "pods", + StartTime: cgroupStatsMap["/pods"].cs.StartTime, + CPU: cgroupStatsMap["/pods"].cs.CPU, + Memory: cgroupStatsMap["/pods"].cs.Memory, + }) + assert.Equal(summary.Pods, podStats) +} + func getFsStats() *statsapi.FsStats { f := fuzz.New().NilChance(0) v := &statsapi.FsStats{} diff --git a/pkg/kubelet/stats/stats_provider_test.go b/pkg/kubelet/stats/stats_provider_test.go index e2abed50991..bbb149075bf 100644 --- a/pkg/kubelet/stats/stats_provider_test.go +++ b/pkg/kubelet/stats/stats_provider_test.go @@ -634,8 +634,9 @@ type fakeResourceAnalyzer struct { podVolumeStats serverstats.PodVolumeStats } -func (o *fakeResourceAnalyzer) Start() {} -func (o *fakeResourceAnalyzer) Get(bool) (*statsapi.Summary, error) { return nil, nil } +func (o *fakeResourceAnalyzer) Start() {} +func (o *fakeResourceAnalyzer) Get(bool) (*statsapi.Summary, error) { return nil, nil } +func (o *fakeResourceAnalyzer) GetCPUAndMemoryStats() (*statsapi.Summary, error) { return nil, nil } func (o *fakeResourceAnalyzer) GetPodVolumeStats(uid types.UID) (serverstats.PodVolumeStats, bool) { return o.podVolumeStats, true }