Skip to content

Commit

Permalink
Merge pull request kubernetes#50396 from bobbypage/stats
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue (batch tested with PRs 52168, 48939, 51889, 52051, 50396). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>..

Add Windows Server Containers Stats and Metrics to Kubelet

**What this PR does / why we need it**:

This PR implements stats for Windows Server Containers. This adds the ability to monitor Windows Server containers via the existing stats/summary endpoint inside the kubelet. Windows metrics can now be ingested into heapster and monitored using existing tools (like Grafana). 

Previously, the /stats/summary api would consistently crash the kubelet on Windows server containers. This PR implements a new package "winstats" which reads windows server metrics from a combination of windows specific perf counters as well as docker stats. The "winstats" package exports functions that return CAdvisor data structures, which the existing summary api can read. 


**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes kubernetes#49398

This PR addresses my plan to implement windows server container stats kubernetes#49398 .


**Release note**:

```release-note
Add monitoring of Windows Server containers metrics in the kubelet via the stats/summary endpoint.
```
  • Loading branch information
Kubernetes Submit Queue committed Sep 23, 2017
2 parents 48bb3e6 + a854ddb commit 441f674
Show file tree
Hide file tree
Showing 50 changed files with 11,149 additions and 4 deletions.
4 changes: 4 additions & 0 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions Godeps/LICENSES

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/kubelet/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ filegroup(
"//pkg/kubelet/types:all-srcs",
"//pkg/kubelet/util:all-srcs",
"//pkg/kubelet/volumemanager:all-srcs",
"//pkg/kubelet/winstats:all-srcs",
],
tags = ["automanaged"],
)
3 changes: 3 additions & 0 deletions pkg/kubelet/cadvisor/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ go_library(
"//vendor/github.com/google/cadvisor/utils/sysfs:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
],
"@io_bazel_rules_go//go/platform:windows_amd64": [
"//pkg/kubelet/winstats:go_default_library",
],
"//conditions:default": [],
}),
)
Expand Down
11 changes: 7 additions & 4 deletions pkg/kubelet/cadvisor/cadvisor_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@ import (
"github.com/google/cadvisor/events"
cadvisorapi "github.com/google/cadvisor/info/v1"
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
"k8s.io/kubernetes/pkg/kubelet/winstats"
)

type cadvisorClient struct {
winStatsClient winstats.Client
}

var _ Interface = new(cadvisorClient)

// New creates a cAdvisor and exports its API on the specified port if port > 0.
func New(address string, port uint, imageFsInfoProvider ImageFsInfoProvider, rootPath string) (Interface, error) {
return &cadvisorClient{}, nil
client, err := winstats.NewPerfCounterClient()
return &cadvisorClient{winStatsClient: client}, err
}

func (cu *cadvisorClient) Start() error {
Expand All @@ -47,19 +50,19 @@ func (cu *cadvisorClient) ContainerInfo(name string, req *cadvisorapi.ContainerI
}

func (cu *cadvisorClient) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) {
return make(map[string]cadvisorapiv2.ContainerInfo), nil
return cu.winStatsClient.WinContainerInfos()
}

func (cu *cadvisorClient) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
return nil, nil
}

func (cu *cadvisorClient) MachineInfo() (*cadvisorapi.MachineInfo, error) {
return &cadvisorapi.MachineInfo{}, nil
return cu.winStatsClient.WinMachineInfo()
}

func (cu *cadvisorClient) VersionInfo() (*cadvisorapi.VersionInfo, error) {
return &cadvisorapi.VersionInfo{}, nil
return cu.winStatsClient.WinVersionInfo()
}

func (cu *cadvisorClient) ImagesFsInfo() (cadvisorapiv2.FsInfo, error) {
Expand Down
53 changes: 53 additions & 0 deletions pkg/kubelet/winstats/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package(default_visibility = ["//visibility:public"])

licenses(["notice"])

filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)

filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
srcs = [
"winstats.go",
] + select({
"@io_bazel_rules_go//go/platform:windows_amd64": [
"perfcounter_nodestats.go",
"perfcounters.go",
],
"//conditions:default": [],
}),
deps = [
"//vendor/github.com/google/cadvisor/info/v1:go_default_library",
"//vendor/github.com/google/cadvisor/info/v2:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:windows_amd64": [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/lxn/win:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
],
"//conditions:default": [],
}),
)

go_test(
name = "go_default_test",
srcs = ["winstats_test.go"],
library = ":go_default_library",
deps = [
"//vendor/github.com/google/cadvisor/info/v1:go_default_library",
"//vendor/github.com/google/cadvisor/info/v2:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
166 changes: 166 additions & 0 deletions pkg/kubelet/winstats/perfcounter_nodestats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// +build windows

/*
Copyright 2017 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 winstats

import (
"errors"
"os"
"os/exec"
"runtime"
"strings"
"sync"
"time"

"github.com/golang/glog"
cadvisorapi "github.com/google/cadvisor/info/v1"
"github.com/lxn/win"
"k8s.io/apimachinery/pkg/util/wait"
)

// NewPerfCounterClient creates a client using perf counters
func NewPerfCounterClient() (Client, error) {
return newClient(&perfCounterNodeStatsClient{})
}

// perfCounterNodeStatsClient is a client that provides Windows Stats via PerfCounters
type perfCounterNodeStatsClient struct {
nodeMetrics
mu sync.RWMutex // mu protects nodeMetrics
nodeInfo
}

func (p *perfCounterNodeStatsClient) startMonitoring() error {
memory, err := getPhysicallyInstalledSystemMemoryBytes()
if err != nil {
return err
}

version, err := exec.Command("cmd", "/C", "ver").Output()
if err != nil {
return err
}

osImageVersion := strings.TrimSpace(string(version))
kernelVersion := extractVersionNumber(osImageVersion)
p.nodeInfo = nodeInfo{
kernelVersion: kernelVersion,
osImageVersion: osImageVersion,
memoryPhysicalCapacityBytes: memory,
startTime: time.Now(),
}

cpuCounter, err := newPerfCounter(cpuQuery)
if err != nil {
return err
}

memWorkingSetCounter, err := newPerfCounter(memoryPrivWorkingSetQuery)
if err != nil {
return err
}

memCommittedBytesCounter, err := newPerfCounter(memoryCommittedBytesQuery)
if err != nil {
return err
}

go wait.Forever(func() {
p.collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter)
}, perfCounterUpdatePeriod)

return nil
}

func (p *perfCounterNodeStatsClient) getMachineInfo() (*cadvisorapi.MachineInfo, error) {
hostname, err := os.Hostname()
if err != nil {
return nil, err
}

return &cadvisorapi.MachineInfo{
NumCores: runtime.NumCPU(),
MemoryCapacity: p.nodeInfo.memoryPhysicalCapacityBytes,
MachineID: hostname,
}, nil
}

func (p *perfCounterNodeStatsClient) getVersionInfo() (*cadvisorapi.VersionInfo, error) {
return &cadvisorapi.VersionInfo{
KernelVersion: p.nodeInfo.kernelVersion,
ContainerOsVersion: p.nodeInfo.osImageVersion,
}, nil
}

func (p *perfCounterNodeStatsClient) getNodeMetrics() (nodeMetrics, error) {
p.mu.RLock()
defer p.mu.RUnlock()
return p.nodeMetrics, nil
}

func (p *perfCounterNodeStatsClient) getNodeInfo() nodeInfo {
return p.nodeInfo
}

func (p *perfCounterNodeStatsClient) collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter *perfCounter) {
cpuValue, err := cpuCounter.getData()
if err != nil {
glog.Errorf("Unable to get cpu perf counter data; err: %v", err)
return
}

memWorkingSetValue, err := memWorkingSetCounter.getData()
if err != nil {
glog.Errorf("Unable to get memWorkingSet perf counter data; err: %v", err)
return
}

memCommittedBytesValue, err := memCommittedBytesCounter.getData()
if err != nil {
glog.Errorf("Unable to get memCommittedBytes perf counter data; err: %v", err)
return
}

p.mu.Lock()
defer p.mu.Unlock()
p.nodeMetrics = nodeMetrics{
cpuUsageCoreNanoSeconds: p.convertCPUValue(cpuValue),
memoryPrivWorkingSetBytes: memWorkingSetValue,
memoryCommittedBytes: memCommittedBytesValue,
timeStamp: time.Now(),
}
}

func (p *perfCounterNodeStatsClient) convertCPUValue(cpuValue uint64) uint64 {
cpuCores := runtime.NumCPU()
// This converts perf counter data which is cpu percentage for all cores into nanoseconds.
// The formula is (cpuPercentage / 100.0) * #cores * 1e+9 (nano seconds). More info here:
// https://github.com/kubernetes/heapster/issues/650
newValue := p.nodeMetrics.cpuUsageCoreNanoSeconds + uint64((float64(cpuValue)/100.0)*float64(cpuCores)*1e9)
return newValue
}

func getPhysicallyInstalledSystemMemoryBytes() (uint64, error) {
var physicalMemoryKiloBytes uint64

if ok := win.GetPhysicallyInstalledSystemMemory(&physicalMemoryKiloBytes); !ok {
return 0, errors.New("unable to read physical memory")
}

return physicalMemoryKiloBytes * 1024, nil // convert kilobytes to bytes
}
Loading

0 comments on commit 441f674

Please sign in to comment.