Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add log and fs stats for Windows containers #62266

Merged
merged 4 commits into from
Jun 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions pkg/kubelet/cadvisor/cadvisor_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
)

type cadvisorClient struct {
rootPath string
winStatsClient winstats.Client
}

Expand All @@ -34,7 +35,10 @@ 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, usingLegacyStats bool) (Interface, error) {
client, err := winstats.NewPerfCounterClient()
return &cadvisorClient{winStatsClient: client}, err
return &cadvisorClient{
rootPath: rootPath,
winStatsClient: client,
}, err
}

func (cu *cadvisorClient) Start() error {
Expand Down Expand Up @@ -70,7 +74,7 @@ func (cu *cadvisorClient) ImagesFsInfo() (cadvisorapiv2.FsInfo, error) {
}

func (cu *cadvisorClient) RootFsInfo() (cadvisorapiv2.FsInfo, error) {
return cadvisorapiv2.FsInfo{}, nil
return cu.GetDirFsInfo(cu.rootPath)
}

func (cu *cadvisorClient) WatchEvents(request *events.Request) (*events.EventChannel, error) {
Expand Down
6 changes: 6 additions & 0 deletions pkg/kubelet/dockershim/docker_stats_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ func (ds *dockerService) ListContainerStats(ctx context.Context, r *runtimeapi.L
}

func (ds *dockerService) getContainerStats(containerID string) (*runtimeapi.ContainerStats, error) {
info, err := ds.client.Info()
if err != nil {
return nil, err
}

statsJSON, err := ds.client.GetContainerStats(containerID)
if err != nil {
return nil, err
Expand Down Expand Up @@ -101,6 +106,7 @@ func (ds *dockerService) getContainerStats(containerID string) (*runtimeapi.Cont
},
WritableLayer: &runtimeapi.FilesystemUsage{
Timestamp: timestamp,
FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: info.DockerRootDir},
UsedBytes: &runtimeapi.UInt64Value{Value: uint64(*containerJSON.SizeRw)},
},
}
Expand Down
56 changes: 49 additions & 7 deletions pkg/kubelet/stats/cri_stats_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,14 @@ func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
ps, found := sandboxIDToPodStats[podSandboxID]
if !found {
ps = buildPodStats(podSandbox)
// Fill stats from cadvisor is available for full set of required pod stats
p.addCadvisorPodNetworkStats(ps, podSandboxID, caInfos)
p.addCadvisorPodCPUMemoryStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos)
sandboxIDToPodStats[podSandboxID] = ps
}

// Fill available stats for full set of required pod stats
cs := p.makeContainerStats(stats, container, &rootFsInfo, fsIDtoInfo, podSandbox.GetMetadata().GetUid())
p.addPodNetworkStats(ps, podSandboxID, caInfos, cs)
p.addPodCPUMemoryStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)

// If cadvisor stats is available for the container, use it to populate
// container stats
caStats, caFound := caInfos[containerID]
Expand Down Expand Up @@ -263,29 +265,69 @@ func (p *criStatsProvider) makePodStorageStats(s *statsapi.PodStats, rootFsInfo
return s
}

func (p *criStatsProvider) addCadvisorPodNetworkStats(
func (p *criStatsProvider) addPodNetworkStats(
ps *statsapi.PodStats,
podSandboxID string,
caInfos map[string]cadvisorapiv2.ContainerInfo,
cs *statsapi.ContainerStats,
) {
caPodSandbox, found := caInfos[podSandboxID]
// try get network stats from cadvisor first.
if found {
ps.Network = cadvisorInfoToNetworkStats(ps.PodRef.Name, &caPodSandbox)
} else {
glog.V(4).Infof("Unable to find cadvisor stats for sandbox %q", podSandboxID)
return
}

// TODO: sum Pod network stats from container stats.
glog.V(4).Infof("Unable to find cadvisor stats for sandbox %q", podSandboxID)
}

func (p *criStatsProvider) addCadvisorPodCPUMemoryStats(
func (p *criStatsProvider) addPodCPUMemoryStats(
ps *statsapi.PodStats,
podUID types.UID,
allInfos map[string]cadvisorapiv2.ContainerInfo,
cs *statsapi.ContainerStats,
) {
// try get cpu and memory stats from cadvisor first.
podCgroupInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos)
if podCgroupInfo != nil {
cpu, memory := cadvisorInfoToCPUandMemoryStats(podCgroupInfo)
ps.CPU = cpu
ps.Memory = memory
return
}

// Sum Pod cpu and memory stats from containers stats.
if cs.CPU != nil {
if ps.CPU == nil {
ps.CPU = &statsapi.CPUStats{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference between statsapi.cpustats and statsapi.containerstats?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

statsapi.cpustats is part of statsapi.containerstats and statsapi.podstats.

Because windows doesn't support cadvisor, we can't get pod level stats, so here sum all containers' stats belonging to the same Pod.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. makes sense. thanks

}

ps.CPU.Time = cs.StartTime
usageCoreNanoSeconds := getUint64Value(cs.CPU.UsageCoreNanoSeconds) + getUint64Value(ps.CPU.UsageCoreNanoSeconds)
usageNanoCores := getUint64Value(cs.CPU.UsageNanoCores) + getUint64Value(ps.CPU.UsageNanoCores)
ps.CPU.UsageCoreNanoSeconds = &usageCoreNanoSeconds
ps.CPU.UsageNanoCores = &usageNanoCores
}

if cs.Memory != nil {
if ps.Memory == nil {
ps.Memory = &statsapi.MemoryStats{}
}

ps.Memory.Time = cs.Memory.Time
availableBytes := getUint64Value(cs.Memory.AvailableBytes) + getUint64Value(ps.Memory.AvailableBytes)
usageBytes := getUint64Value(cs.Memory.UsageBytes) + getUint64Value(ps.Memory.UsageBytes)
workingSetBytes := getUint64Value(cs.Memory.WorkingSetBytes) + getUint64Value(ps.Memory.WorkingSetBytes)
rSSBytes := getUint64Value(cs.Memory.RSSBytes) + getUint64Value(ps.Memory.RSSBytes)
pageFaults := getUint64Value(cs.Memory.PageFaults) + getUint64Value(ps.Memory.PageFaults)
majorPageFaults := getUint64Value(cs.Memory.MajorPageFaults) + getUint64Value(ps.Memory.MajorPageFaults)
ps.Memory.AvailableBytes = &availableBytes
ps.Memory.UsageBytes = &usageBytes
ps.Memory.WorkingSetBytes = &workingSetBytes
ps.Memory.RSSBytes = &rSSBytes
ps.Memory.PageFaults = &pageFaults
ps.Memory.MajorPageFaults = &majorPageFaults
}
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/kubelet/stats/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,11 @@ func buildRootfsStats(cstat *cadvisorapiv2.ContainerStats, imageFs *cadvisorapiv
Inodes: imageFs.Inodes,
}
}

func getUint64Value(value *uint64) uint64 {
if value == nil {
return 0
}

return *value
}
10 changes: 5 additions & 5 deletions pkg/volume/metrics_du.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
var _ MetricsProvider = &metricsDu{}

// metricsDu represents a MetricsProvider that calculates the used and
// available Volume space by executing the "du" command and gathering
// available Volume space by calling fs.DiskUsage() and gathering
// filesystem info for the Volume path.
type metricsDu struct {
// the directory path the volume is mounted to.
Expand All @@ -46,7 +46,7 @@ func (md *metricsDu) GetMetrics() (*Metrics, error) {
return metrics, NewNoPathDefinedError()
}

err := md.runDu(metrics)
err := md.runDiskUsage(metrics)
if err != nil {
return metrics, err
}
Expand All @@ -64,9 +64,9 @@ func (md *metricsDu) GetMetrics() (*Metrics, error) {
return metrics, nil
}

// runDu executes the "du" command and writes the results to metrics.Used
func (md *metricsDu) runDu(metrics *Metrics) error {
used, err := fs.Du(md.path)
// runDiskUsage gets disk usage of md.path and writes the results to metrics.Used
func (md *metricsDu) runDiskUsage(metrics *Metrics) error {
used, err := fs.DiskUsage(md.path)
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/volume/util/fs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ go_library(
"fs_unsupported.go",
],
"@io_bazel_rules_go//go/platform:windows": [
"fs_unsupported.go",
"fs_windows.go",
],
"//conditions:default": [],
}),
Expand Down Expand Up @@ -74,6 +74,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
],
"@io_bazel_rules_go//go/platform:windows": [
"//vendor/golang.org/x/sys/windows:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
],
"//conditions:default": [],
Expand Down
3 changes: 2 additions & 1 deletion pkg/volume/util/fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ func FsInfo(path string) (int64, int64, int64, int64, int64, int64, error) {
return available, capacity, usage, inodes, inodesFree, inodesUsed, nil
}

func Du(path string) (*resource.Quantity, error) {
// DiskUsage gets disk usage of specified path.
func DiskUsage(path string) (*resource.Quantity, error) {
// Uses the same niceness level as cadvisor.fs does when running du
// Uses -B 1 to always scale to a blocksize of 1 byte
out, err := exec.Command("nice", "-n", "19", "du", "-s", "-B", "1", path).CombinedOutput()
Expand Down
5 changes: 3 additions & 2 deletions pkg/volume/util/fs/fs_unsupported.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build !linux,!darwin
// +build !linux,!darwin,!windows

/*
Copyright 2014 The Kubernetes Authors.
Expand Down Expand Up @@ -29,7 +29,8 @@ func FsInfo(path string) (int64, int64, int64, int64, int64, int64, error) {
return 0, 0, 0, 0, 0, 0, fmt.Errorf("FsInfo not supported for this build.")
}

func Du(path string) (*resource.Quantity, error) {
// DiskUsage gets disk usage of specified path.
func DiskUsage(path string) (*resource.Quantity, error) {
return nil, fmt.Errorf("Du not supported for this build.")
}

Expand Down
77 changes: 77 additions & 0 deletions pkg/volume/util/fs/fs_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// +build windows

/*
Copyright 2014 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 fs

import (
"fmt"
"syscall"
"unsafe"

"golang.org/x/sys/windows"

"k8s.io/apimachinery/pkg/api/resource"
)

var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
procGetDiskFreeSpaceEx = modkernel32.NewProc("GetDiskFreeSpaceExW")
)

// FSInfo returns (available bytes, byte capacity, byte usage, total inodes, inodes free, inode usage, error)
// for the filesystem that path resides upon.
func FsInfo(path string) (int64, int64, int64, int64, int64, int64, error) {
var freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes int64
var err error

ret, _, err := syscall.Syscall6(
procGetDiskFreeSpaceEx.Addr(),
4,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
uintptr(unsafe.Pointer(&freeBytesAvailable)),
uintptr(unsafe.Pointer(&totalNumberOfBytes)),
uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)),
0,
0,
)
if ret == 0 {
return 0, 0, 0, 0, 0, 0, err
}

return freeBytesAvailable, totalNumberOfBytes, totalNumberOfBytes - freeBytesAvailable, 0, 0, 0, nil
}

// DiskUsage gets disk usage of specified path.
func DiskUsage(path string) (*resource.Quantity, error) {
_, _, usage, _, _, _, err := FsInfo(path)
if err != nil {
return nil, err
}

used, err := resource.ParseQuantity(fmt.Sprintf("%d", usage))
if err != nil {
return nil, fmt.Errorf("failed to parse fs usage %d due to %v", usage, err)
}
used.Format = resource.BinarySI
return &used, nil
}

// Always return zero since inodes is not supported on Windows.
func Find(path string) (int64, error) {
return 0, nil
}