From b91e030fcf817a22ab30a39ee2e0b25d724802d0 Mon Sep 17 00:00:00 2001 From: Jiangtian Li Date: Fri, 10 Nov 2017 14:50:44 -0800 Subject: [PATCH] This PR fixes issue #55031 where kubelet.exe crashes on Windows Server Core. The root cause is that kubelet.exe depends on package lxn/win pdh and kernel32 wrapper for node metrics. However, opengl32.dll is not available in Server Core and lxn/win requires the presence of all win32 DLLs. This PR uses a slim win32 package JeffAshton/win_pdh since most win32 APIs needed are PDH API. Also this PR makes own implementation of GetPhysicallyInstalledSystemMemory until golang Windows syscall has it or lxn/win fixes opengl32 issue. Also this PR modifies the way to get Windows version. --- pkg/kubelet/winstats/BUILD | 2 +- pkg/kubelet/winstats/perfcounter_nodestats.go | 30 ++++++++++--- pkg/kubelet/winstats/perfcounters.go | 44 +++++++++---------- pkg/kubelet/winstats/winstats.go | 9 ---- pkg/kubelet/winstats/winstats_test.go | 7 --- 5 files changed, 48 insertions(+), 44 deletions(-) diff --git a/pkg/kubelet/winstats/BUILD b/pkg/kubelet/winstats/BUILD index 4cc59997c97..a4bdfa09c1b 100644 --- a/pkg/kubelet/winstats/BUILD +++ b/pkg/kubelet/winstats/BUILD @@ -34,8 +34,8 @@ go_library( "//vendor/github.com/google/cadvisor/info/v2:go_default_library", ] + select({ "@io_bazel_rules_go//go/platform:windows_amd64": [ + "//vendor/github.com/JeffAshton/win_pdh:go_default_library", "//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": [], diff --git a/pkg/kubelet/winstats/perfcounter_nodestats.go b/pkg/kubelet/winstats/perfcounter_nodestats.go index 9d0a3e92a22..415d02459de 100644 --- a/pkg/kubelet/winstats/perfcounter_nodestats.go +++ b/pkg/kubelet/winstats/perfcounter_nodestats.go @@ -25,14 +25,20 @@ import ( "runtime" "strings" "sync" + "syscall" "time" + "unsafe" "github.com/golang/glog" cadvisorapi "github.com/google/cadvisor/info/v1" - "github.com/lxn/win" "k8s.io/apimachinery/pkg/util/wait" ) +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetPhysicallyInstalledSystemMemory = modkernel32.NewProc("GetPhysicallyInstalledSystemMemory") +) + // NewPerfCounterClient creates a client using perf counters func NewPerfCounterClient() (Client, error) { return newClient(&perfCounterNodeStatsClient{}) @@ -51,13 +57,18 @@ func (p *perfCounterNodeStatsClient) startMonitoring() error { return err } - version, err := exec.Command("cmd", "/C", "ver").Output() + iv, err := exec.Command("powershell", "-command", "(Get-CimInstance Win32_OperatingSystem).Caption").Output() if err != nil { return err } + osImageVersion := strings.TrimSpace(string(iv)) + + kv, err := exec.Command("powershell", "-command", "[System.Environment]::OSVersion.Version.ToString()").Output() + if err != nil { + return err + } + kernelVersion := strings.TrimSpace(string(kv)) - osImageVersion := strings.TrimSpace(string(version)) - kernelVersion := extractVersionNumber(osImageVersion) p.nodeInfo = nodeInfo{ kernelVersion: kernelVersion, osImageVersion: osImageVersion, @@ -158,9 +169,18 @@ func (p *perfCounterNodeStatsClient) convertCPUValue(cpuValue uint64) uint64 { func getPhysicallyInstalledSystemMemoryBytes() (uint64, error) { var physicalMemoryKiloBytes uint64 - if ok := win.GetPhysicallyInstalledSystemMemory(&physicalMemoryKiloBytes); !ok { + if ok := getPhysicallyInstalledSystemMemory(&physicalMemoryKiloBytes); !ok { return 0, errors.New("unable to read physical memory") } return physicalMemoryKiloBytes * 1024, nil // convert kilobytes to bytes } + +func getPhysicallyInstalledSystemMemory(totalMemoryInKilobytes *uint64) bool { + ret, _, _ := syscall.Syscall(procGetPhysicallyInstalledSystemMemory.Addr(), 1, + uintptr(unsafe.Pointer(totalMemoryInKilobytes)), + 0, + 0) + + return ret != 0 +} diff --git a/pkg/kubelet/winstats/perfcounters.go b/pkg/kubelet/winstats/perfcounters.go index dd77103bd0a..e5ed450b78e 100644 --- a/pkg/kubelet/winstats/perfcounters.go +++ b/pkg/kubelet/winstats/perfcounters.go @@ -24,7 +24,7 @@ import ( "time" "unsafe" - "github.com/lxn/win" + "github.com/JeffAshton/win_pdh" ) const ( @@ -37,31 +37,31 @@ const ( ) type perfCounter struct { - queryHandle win.PDH_HQUERY - counterHandle win.PDH_HCOUNTER + queryHandle win_pdh.PDH_HQUERY + counterHandle win_pdh.PDH_HCOUNTER } func newPerfCounter(counter string) (*perfCounter, error) { - var queryHandle win.PDH_HQUERY - var counterHandle win.PDH_HCOUNTER + var queryHandle win_pdh.PDH_HQUERY + var counterHandle win_pdh.PDH_HCOUNTER - ret := win.PdhOpenQuery(0, 0, &queryHandle) - if ret != win.ERROR_SUCCESS { + ret := win_pdh.PdhOpenQuery(0, 0, &queryHandle) + if ret != win_pdh.ERROR_SUCCESS { return nil, errors.New("unable to open query through DLL call") } - ret = win.PdhValidatePath(counter) - if ret != win.ERROR_SUCCESS { + ret = win_pdh.PdhValidatePath(counter) + if ret != win_pdh.ERROR_SUCCESS { return nil, fmt.Errorf("unable to valid path to counter. Error code is %x", ret) } - ret = win.PdhAddEnglishCounter(queryHandle, counter, 0, &counterHandle) - if ret != win.ERROR_SUCCESS { + ret = win_pdh.PdhAddEnglishCounter(queryHandle, counter, 0, &counterHandle) + if ret != win_pdh.ERROR_SUCCESS { return nil, fmt.Errorf("unable to add process counter. Error code is %x", ret) } - ret = win.PdhCollectQueryData(queryHandle) - if ret != win.ERROR_SUCCESS { + ret = win_pdh.PdhCollectQueryData(queryHandle) + if ret != win_pdh.ERROR_SUCCESS { return nil, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } @@ -72,24 +72,24 @@ func newPerfCounter(counter string) (*perfCounter, error) { } func (p *perfCounter) getData() (uint64, error) { - ret := win.PdhCollectQueryData(p.queryHandle) - if ret != win.ERROR_SUCCESS { + ret := win_pdh.PdhCollectQueryData(p.queryHandle) + if ret != win_pdh.ERROR_SUCCESS { return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } var bufSize, bufCount uint32 - var size = uint32(unsafe.Sizeof(win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) - var emptyBuf [1]win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. + var size = uint32(unsafe.Sizeof(win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) + var emptyBuf [1]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. var data uint64 - ret = win.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0]) - if ret != win.PDH_MORE_DATA { + ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0]) + if ret != win_pdh.PDH_MORE_DATA { return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } - filledBuf := make([]win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) - ret = win.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0]) - if ret != win.ERROR_SUCCESS { + filledBuf := make([]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) + ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0]) + if ret != win_pdh.ERROR_SUCCESS { return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } diff --git a/pkg/kubelet/winstats/winstats.go b/pkg/kubelet/winstats/winstats.go index 123870b8968..b02bab4f6bd 100644 --- a/pkg/kubelet/winstats/winstats.go +++ b/pkg/kubelet/winstats/winstats.go @@ -18,7 +18,6 @@ limitations under the License. package winstats import ( - "regexp" "time" cadvisorapi "github.com/google/cadvisor/info/v1" @@ -135,11 +134,3 @@ func (c *statsClient) createRootContainerInfo() (*cadvisorapiv2.ContainerInfo, e return &rootInfo, nil } - -// extractVersionNumber gets the version number from the full version string on Windows -// e.g. extracts "10.0.14393" from "Microsoft Windows [Version 10.0.14393]" -func extractVersionNumber(fullVersion string) string { - re := regexp.MustCompile("[^0-9.]") - version := re.ReplaceAllString(fullVersion, "") - return version -} diff --git a/pkg/kubelet/winstats/winstats_test.go b/pkg/kubelet/winstats/winstats_test.go index c1710750708..42df2bb2d80 100644 --- a/pkg/kubelet/winstats/winstats_test.go +++ b/pkg/kubelet/winstats/winstats_test.go @@ -116,13 +116,6 @@ func TestWinVersionInfo(t *testing.T) { KernelVersion: "v42"}) } -func TestExtractVersionNumber(t *testing.T) { - fullVersion := "Microsoft Windows [Version 10.0.14393]" - versionNumber := extractVersionNumber(fullVersion) - expected := "10.0.14393" - assert.Equal(t, expected, versionNumber) -} - func getClient(t *testing.T) Client { f := fakeWinNodeStatsClient{} c, err := newClient(f)