mirror of https://github.com/k3s-io/k3s
201 lines
5.8 KiB
Go
201 lines
5.8 KiB
Go
|
// +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"
|
||
|
"runtime"
|
||
|
"sync"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
|
||
|
cadvisorapi "github.com/google/cadvisor/info/v1"
|
||
|
"golang.org/x/sys/windows"
|
||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||
|
"k8s.io/klog"
|
||
|
)
|
||
|
|
||
|
// MemoryStatusEx is the same as Windows structure MEMORYSTATUSEX
|
||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366770(v=vs.85).aspx
|
||
|
type MemoryStatusEx struct {
|
||
|
Length uint32
|
||
|
MemoryLoad uint32
|
||
|
TotalPhys uint64
|
||
|
AvailPhys uint64
|
||
|
TotalPageFile uint64
|
||
|
AvailPageFile uint64
|
||
|
TotalVirtual uint64
|
||
|
AvailVirtual uint64
|
||
|
AvailExtendedVirtual uint64
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||
|
procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
|
||
|
)
|
||
|
|
||
|
// 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
|
||
|
}
|
||
|
|
||
|
kernelVersion, err := getKernelVersion()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
osImageVersion, err := getOSImageVersion()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
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 {
|
||
|
klog.Errorf("Unable to get cpu perf counter data; err: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
memWorkingSetValue, err := memWorkingSetCounter.getData()
|
||
|
if err != nil {
|
||
|
klog.Errorf("Unable to get memWorkingSet perf counter data; err: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
memCommittedBytesValue, err := memCommittedBytesCounter.getData()
|
||
|
if err != nil {
|
||
|
klog.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) {
|
||
|
// We use GlobalMemoryStatusEx instead of GetPhysicallyInstalledSystemMemory
|
||
|
// on Windows node for the following reasons:
|
||
|
// 1. GetPhysicallyInstalledSystemMemory retrieves the amount of physically
|
||
|
// installed RAM from the computer's SMBIOS firmware tables.
|
||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/cc300158(v=vs.85).aspx
|
||
|
// On some VM, it is unable to read data from SMBIOS and fails with ERROR_INVALID_DATA.
|
||
|
// 2. On Linux node, total physical memory is read from MemTotal in /proc/meminfo.
|
||
|
// GlobalMemoryStatusEx returns the amount of physical memory that is available
|
||
|
// for the operating system to use. The amount returned by GlobalMemoryStatusEx
|
||
|
// is closer in parity with Linux
|
||
|
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||
|
var statex MemoryStatusEx
|
||
|
statex.Length = uint32(unsafe.Sizeof(statex))
|
||
|
ret, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&statex)))
|
||
|
|
||
|
if ret == 0 {
|
||
|
return 0, errors.New("unable to read physical memory")
|
||
|
}
|
||
|
|
||
|
return statex.TotalPhys, nil
|
||
|
}
|