diff --git a/README.md b/README.md index 5c86a7ca2..b87ca681d 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ make clean windows-binaries make test # Run with containerd log monitoring enabled in Command Prompt. (Assumes containerd is installed.) -%CD%\bin\windows_amd64\node-problem-detector.exe --logtostderr --enable-k8s-exporter=false --config.system-log-monitor=%CD%\config\windows-containerd-monitor-filelog.json +%CD%\output\windows_amd64\node-problem-detector.exe --logtostderr --enable-k8s-exporter=false --config.system-log-monitor=%CD%\config\windows-containerd-monitor-filelog.json --config.system-stats-monitor=config\windows-system-stats-monitor.json # Configure NPD to run as a Windows Service sc.exe create NodeProblemDetector binpath= "%CD%\node-problem-detector.exe [FLAGS]" start= demand diff --git a/config/windows-system-stats-monitor.json b/config/windows-system-stats-monitor.json new file mode 100644 index 000000000..854860768 --- /dev/null +++ b/config/windows-system-stats-monitor.json @@ -0,0 +1,94 @@ +{ + "cpu": { + "metricsConfigs": { + "cpu/load_15m": { + "displayName": "cpu/load_15m" + }, + "cpu/load_1m": { + "displayName": "cpu/load_1m" + }, + "cpu/load_5m": { + "displayName": "cpu/load_5m" + }, + "cpu/runnable_task_count": { + "displayName": "cpu/runnable_task_count" + }, + "cpu/usage_time": { + "displayName": "cpu/usage_time" + }, + "system/cpu_stat": { + "displayName": "system/cpu_stat" + }, + "system/interrupts_total": { + "displayName": "system/interrupts_total" + }, + "system/processes_total": { + "displayName": "system/processes_total" + }, + "system/procs_blocked": { + "displayName": "system/procs_blocked" + }, + "system/procs_running": { + "displayName": "system/procs_running" + } + } + }, + "disk": { + "includeAllAttachedBlk": false, + "includeRootBlk": false, + "lsblkTimeout": "60s", + "metricsConfigs": { + "disk/avg_queue_len": { + "displayName": "disk/avg_queue_len" + }, + "disk/bytes_used": { + "displayName": "disk/bytes_used" + }, + "disk/io_time": { + "displayName": "disk/io_time" + }, + "disk/merged_operation_count": { + "displayName": "disk/merged_operation_count" + }, + "disk/operation_bytes_count": { + "displayName": "disk/operation_bytes_count" + }, + "disk/operation_count": { + "displayName": "disk/operation_count" + }, + "disk/operation_time": { + "displayName": "disk/operation_time" + }, + "disk/weighted_io": { + "displayName": "disk/weighted_io" + } + } + }, + "host": { + "metricsConfigs": { + "host/uptime": { + "displayName": "host/uptime" + } + } + }, + "invokeInterval": "60s", + "memory": { + "metricsConfigs": { + "memory/anonymous_used": { + "displayName": "memory/anonymous_used" + }, + "memory/bytes_used": { + "displayName": "memory/bytes_used" + }, + "memory/dirty_used": { + "displayName": "memory/dirty_used" + }, + "memory/page_cache_used": { + "displayName": "memory/page_cache_used" + }, + "memory/unevictable_used": { + "displayName": "memory/unevictable_used" + } + } + } +} diff --git a/pkg/systemstatsmonitor/README.md b/pkg/systemstatsmonitor/README.md index f098b7aea..5f6cfe24a 100644 --- a/pkg/systemstatsmonitor/README.md +++ b/pkg/systemstatsmonitor/README.md @@ -116,3 +116,14 @@ Below metrics are collected from `net` component: * `net/tx_compressed`: Cumulative count of compressed packets transmitted by the device driver. All of the above have `interface_name` label for the net interface. + +## Windows Support + +NPD has preliminary support for system stats monitor. The following modules are supported: + +* CPU - Idle, System, and User metrics. +* Memory - Used and available. +* Disk - Space used and free. +* Uptime - within kernel version and product name. + +All the data is currently retried from the `github.com/shirou/gopsutil` library. Any data parsed directly from `/proc` from Linux is not supported on Windows. There will be later integration to use WMI (Windows Management Instrumentation) to gather node metrics. diff --git a/pkg/systemstatsmonitor/cpu_collector.go b/pkg/systemstatsmonitor/cpu_collector.go index 78ed3edee..8419756a6 100644 --- a/pkg/systemstatsmonitor/cpu_collector.go +++ b/pkg/systemstatsmonitor/cpu_collector.go @@ -17,12 +17,8 @@ limitations under the License. package systemstatsmonitor import ( - "fmt" - "github.com/golang/glog" - "github.com/prometheus/procfs" "github.com/shirou/gopsutil/cpu" - "github.com/shirou/gopsutil/load" ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types" "k8s.io/node-problem-detector/pkg/util/metrics" @@ -174,24 +170,6 @@ func NewCPUCollectorOrDie(cpuConfig *ssmtypes.CPUStatsConfig) *cpuCollector { return &cc } -func (cc *cpuCollector) recordLoad() { - if cc.mRunnableTaskCount == nil { - return - } - - loadAvg, err := load.Avg() - if err != nil { - glog.Errorf("Failed to retrieve average CPU load: %v", err) - return - } - - cc.mRunnableTaskCount.Record(map[string]string{}, loadAvg.Load1) - - cc.mCpuLoad1m.Record(map[string]string{}, loadAvg.Load1) - cc.mCpuLoad5m.Record(map[string]string{}, loadAvg.Load5) - cc.mCpuLoad15m.Record(map[string]string{}, loadAvg.Load15) -} - func (cc *cpuCollector) recordUsage() { if cc.mUsageTime == nil { return @@ -236,46 +214,6 @@ func (cc *cpuCollector) recordUsage() { cc.lastUsageTime["guest_nice"] = clockTick * timersStat.GuestNice } -func (cc *cpuCollector) recordSystemStats() { - fs, err := procfs.NewFS("/proc") - stats, err := fs.Stat() - if err != nil { - glog.Errorf("Failed to retrieve cpu/process stats: %v", err) - return - } - - cc.mSystemProcessesTotal.Record(map[string]string{}, int64(stats.ProcessCreated)) - cc.mSystemProcsRunning.Record(map[string]string{}, int64(stats.ProcessesRunning)) - cc.mSystemProcsBlocked.Record(map[string]string{}, int64(stats.ProcessesBlocked)) - cc.mSystemInterruptsTotal.Record(map[string]string{}, int64(stats.IRQTotal)) - - for i, c := range stats.CPU { - tags := map[string]string{} - tags[cpuLabel] = fmt.Sprintf("cpu%d", i) - - tags[stageLabel] = "user" - cc.mSystemCPUStat.Record(tags, c.User) - tags[stageLabel] = "nice" - cc.mSystemCPUStat.Record(tags, c.Nice) - tags[stageLabel] = "system" - cc.mSystemCPUStat.Record(tags, c.System) - tags[stageLabel] = "idle" - cc.mSystemCPUStat.Record(tags, c.Idle) - tags[stageLabel] = "iowait" - cc.mSystemCPUStat.Record(tags, c.Iowait) - tags[stageLabel] = "iRQ" - cc.mSystemCPUStat.Record(tags, c.IRQ) - tags[stageLabel] = "softIRQ" - cc.mSystemCPUStat.Record(tags, c.SoftIRQ) - tags[stageLabel] = "steal" - cc.mSystemCPUStat.Record(tags, c.Steal) - tags[stageLabel] = "guest" - cc.mSystemCPUStat.Record(tags, c.Guest) - tags[stageLabel] = "guestNice" - cc.mSystemCPUStat.Record(tags, c.GuestNice) - } -} - func (cc *cpuCollector) collect() { if cc == nil { return diff --git a/pkg/systemstatsmonitor/cpu_collector_linux.go b/pkg/systemstatsmonitor/cpu_collector_linux.go new file mode 100644 index 000000000..9e3460bd1 --- /dev/null +++ b/pkg/systemstatsmonitor/cpu_collector_linux.go @@ -0,0 +1,83 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. + +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 systemstatsmonitor + +import ( + "fmt" + + "github.com/golang/glog" + "github.com/prometheus/procfs" + "github.com/shirou/gopsutil/load" +) + +func (cc *cpuCollector) recordLoad() { + if cc.mRunnableTaskCount == nil { + return + } + + loadAvg, err := load.Avg() + if err != nil { + glog.Errorf("Failed to retrieve average CPU load: %v", err) + return + } + + cc.mRunnableTaskCount.Record(map[string]string{}, loadAvg.Load1) + + cc.mCpuLoad1m.Record(map[string]string{}, loadAvg.Load1) + cc.mCpuLoad5m.Record(map[string]string{}, loadAvg.Load5) + cc.mCpuLoad15m.Record(map[string]string{}, loadAvg.Load15) +} + +func (cc *cpuCollector) recordSystemStats() { + fs, err := procfs.NewFS("/proc") + stats, err := fs.Stat() + if err != nil { + glog.Errorf("Failed to retrieve cpu/process stats: %v", err) + return + } + + cc.mSystemProcessesTotal.Record(map[string]string{}, int64(stats.ProcessCreated)) + cc.mSystemProcsRunning.Record(map[string]string{}, int64(stats.ProcessesRunning)) + cc.mSystemProcsBlocked.Record(map[string]string{}, int64(stats.ProcessesBlocked)) + cc.mSystemInterruptsTotal.Record(map[string]string{}, int64(stats.IRQTotal)) + + for i, c := range stats.CPU { + tags := map[string]string{} + tags[cpuLabel] = fmt.Sprintf("cpu%d", i) + + tags[stageLabel] = "user" + cc.mSystemCPUStat.Record(tags, c.User) + tags[stageLabel] = "nice" + cc.mSystemCPUStat.Record(tags, c.Nice) + tags[stageLabel] = "system" + cc.mSystemCPUStat.Record(tags, c.System) + tags[stageLabel] = "idle" + cc.mSystemCPUStat.Record(tags, c.Idle) + tags[stageLabel] = "iowait" + cc.mSystemCPUStat.Record(tags, c.Iowait) + tags[stageLabel] = "iRQ" + cc.mSystemCPUStat.Record(tags, c.IRQ) + tags[stageLabel] = "softIRQ" + cc.mSystemCPUStat.Record(tags, c.SoftIRQ) + tags[stageLabel] = "steal" + cc.mSystemCPUStat.Record(tags, c.Steal) + tags[stageLabel] = "guest" + cc.mSystemCPUStat.Record(tags, c.Guest) + tags[stageLabel] = "guestNice" + cc.mSystemCPUStat.Record(tags, c.GuestNice) + } +} diff --git a/pkg/systemstatsmonitor/cpu_collector_test.go b/pkg/systemstatsmonitor/cpu_collector_test.go new file mode 100644 index 000000000..faf3fb674 --- /dev/null +++ b/pkg/systemstatsmonitor/cpu_collector_test.go @@ -0,0 +1,72 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +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 systemstatsmonitor + +import ( + "encoding/json" + "testing" + + ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types" +) + +const ( + fakeCPUConfig = ` +{ + "metricsConfigs": { + "cpu/load_15m": { + "displayName": "cpu/load_15m" + }, + "cpu/load_1m": { + "displayName": "cpu/load_1m" + }, + "cpu/load_5m": { + "displayName": "cpu/load_5m" + }, + "cpu/runnable_task_count": { + "displayName": "cpu/runnable_task_count" + }, + "cpu/usage_time": { + "displayName": "cpu/usage_time" + }, + "system/cpu_stat": { + "displayName": "system/cpu_stat" + }, + "system/interrupts_total": { + "displayName": "system/interrupts_total" + }, + "system/processes_total": { + "displayName": "system/processes_total" + }, + "system/procs_blocked": { + "displayName": "system/procs_blocked" + }, + "system/procs_running": { + "displayName": "system/procs_running" + } + } +} +` +) + +func TestCpuCollector(t *testing.T) { + cfg := &ssmtypes.CPUStatsConfig{} + if err := json.Unmarshal([]byte(fakeCPUConfig), cfg); err != nil { + t.Fatalf("cannot load cpu config: %s", err) + } + mc := NewCPUCollectorOrDie(cfg) + mc.collect() +} diff --git a/pkg/systemstatsmonitor/cpu_collector_windows.go b/pkg/systemstatsmonitor/cpu_collector_windows.go new file mode 100644 index 000000000..c7d1c8350 --- /dev/null +++ b/pkg/systemstatsmonitor/cpu_collector_windows.go @@ -0,0 +1,25 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. + +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 systemstatsmonitor + +func (cc *cpuCollector) recordLoad() { + // not supported +} + +func (cc *cpuCollector) recordSystemStats() { + // not supported +} diff --git a/pkg/systemstatsmonitor/disk_collector_test.go b/pkg/systemstatsmonitor/disk_collector_test.go new file mode 100644 index 000000000..41cdc2cc5 --- /dev/null +++ b/pkg/systemstatsmonitor/disk_collector_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +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 systemstatsmonitor + +import ( + "testing" + + ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types" +) + +func TestDiskCollector(t *testing.T) { + dc := NewDiskCollectorOrDie(&ssmtypes.DiskStatsConfig{}) + dc.collect() +} diff --git a/pkg/systemstatsmonitor/host_collector_test.go b/pkg/systemstatsmonitor/host_collector_test.go new file mode 100644 index 000000000..b96ad702a --- /dev/null +++ b/pkg/systemstatsmonitor/host_collector_test.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +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 systemstatsmonitor + +import ( + "testing" + + ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types" +) + +func TestHostCollector(t *testing.T) { + hc := NewHostCollectorOrDie(&ssmtypes.HostStatsConfig{}) + hc.collect() + val, ok := hc.tags["os_version"] + if !ok { + t.Errorf("tags[os_version] should exist.") + } else if val == "" { + t.Errorf("tags[os_version] should not be empty") + } + + val, ok = hc.tags["kernel_version"] + if !ok { + t.Errorf("tags[kernel_version] should exist.") + } else if val == "" { + t.Errorf("tags[kernel_version] should not be empty") + } +} diff --git a/pkg/systemstatsmonitor/memory_collector.go b/pkg/systemstatsmonitor/memory_collector.go index dafd15513..a572d0b40 100644 --- a/pkg/systemstatsmonitor/memory_collector.go +++ b/pkg/systemstatsmonitor/memory_collector.go @@ -18,7 +18,6 @@ package systemstatsmonitor import ( "github.com/golang/glog" - "github.com/prometheus/procfs" ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types" "k8s.io/node-problem-detector/pkg/util/metrics" @@ -96,48 +95,3 @@ func NewMemoryCollectorOrDie(memoryConfig *ssmtypes.MemoryStatsConfig) *memoryCo return &mc } - -func (mc *memoryCollector) collect() { - if mc == nil { - return - } - - proc, err := procfs.NewDefaultFS() - if err != nil { - glog.Errorf("Failed to find /proc mount point: %v", err) - return - } - meminfo, err := proc.Meminfo() - if err != nil { - glog.Errorf("Failed to retrieve memory stats: %v", err) - return - } - - if mc.mBytesUsed != nil { - memUsed := meminfo.MemTotal - meminfo.MemFree - meminfo.Buffers - meminfo.Cached - meminfo.Slab - mc.mBytesUsed.Record(map[string]string{stateLabel: "free"}, int64(meminfo.MemFree)*1024) - mc.mBytesUsed.Record(map[string]string{stateLabel: "used"}, int64(memUsed)*1024) - mc.mBytesUsed.Record(map[string]string{stateLabel: "buffered"}, int64(meminfo.Buffers)*1024) - mc.mBytesUsed.Record(map[string]string{stateLabel: "cached"}, int64(meminfo.Cached)*1024) - mc.mBytesUsed.Record(map[string]string{stateLabel: "slab"}, int64(meminfo.Slab)*1024) - } - - if mc.mDirtyUsed != nil { - mc.mDirtyUsed.Record(map[string]string{stateLabel: "dirty"}, int64(meminfo.Dirty)*1024) - mc.mDirtyUsed.Record(map[string]string{stateLabel: "writeback"}, int64(meminfo.Writeback)*1024) - } - - if mc.mAnonymousUsed != nil { - mc.mAnonymousUsed.Record(map[string]string{stateLabel: "active"}, int64(meminfo.ActiveAnon)*1024) - mc.mAnonymousUsed.Record(map[string]string{stateLabel: "inactive"}, int64(meminfo.InactiveAnon)*1024) - } - - if mc.mPageCacheUsed != nil { - mc.mPageCacheUsed.Record(map[string]string{stateLabel: "active"}, int64(meminfo.ActiveFile)*1024) - mc.mPageCacheUsed.Record(map[string]string{stateLabel: "inactive"}, int64(meminfo.InactiveFile)*1024) - } - - if mc.mUnevictableUsed != nil { - mc.mUnevictableUsed.Record(map[string]string{}, int64(meminfo.Unevictable)*1024) - } -} diff --git a/pkg/systemstatsmonitor/memory_collector_linux.go b/pkg/systemstatsmonitor/memory_collector_linux.go new file mode 100644 index 000000000..96ee8f39e --- /dev/null +++ b/pkg/systemstatsmonitor/memory_collector_linux.go @@ -0,0 +1,67 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. + +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 systemstatsmonitor + +import ( + "github.com/golang/glog" + "github.com/prometheus/procfs" +) + +func (mc *memoryCollector) collect() { + if mc == nil { + return + } + + proc, err := procfs.NewDefaultFS() + if err != nil { + glog.Errorf("Failed to find /proc mount point: %v", err) + return + } + meminfo, err := proc.Meminfo() + if err != nil { + glog.Errorf("Failed to retrieve memory stats: %v", err) + return + } + + if mc.mBytesUsed != nil { + memUsed := meminfo.MemTotal - meminfo.MemFree - meminfo.Buffers - meminfo.Cached - meminfo.Slab + mc.mBytesUsed.Record(map[string]string{stateLabel: "free"}, int64(meminfo.MemFree)*1024) + mc.mBytesUsed.Record(map[string]string{stateLabel: "used"}, int64(memUsed)*1024) + mc.mBytesUsed.Record(map[string]string{stateLabel: "buffered"}, int64(meminfo.Buffers)*1024) + mc.mBytesUsed.Record(map[string]string{stateLabel: "cached"}, int64(meminfo.Cached)*1024) + mc.mBytesUsed.Record(map[string]string{stateLabel: "slab"}, int64(meminfo.Slab)*1024) + } + + if mc.mDirtyUsed != nil { + mc.mDirtyUsed.Record(map[string]string{stateLabel: "dirty"}, int64(meminfo.Dirty)*1024) + mc.mDirtyUsed.Record(map[string]string{stateLabel: "writeback"}, int64(meminfo.Writeback)*1024) + } + + if mc.mAnonymousUsed != nil { + mc.mAnonymousUsed.Record(map[string]string{stateLabel: "active"}, int64(meminfo.ActiveAnon)*1024) + mc.mAnonymousUsed.Record(map[string]string{stateLabel: "inactive"}, int64(meminfo.InactiveAnon)*1024) + } + + if mc.mPageCacheUsed != nil { + mc.mPageCacheUsed.Record(map[string]string{stateLabel: "active"}, int64(meminfo.ActiveFile)*1024) + mc.mPageCacheUsed.Record(map[string]string{stateLabel: "inactive"}, int64(meminfo.InactiveFile)*1024) + } + + if mc.mUnevictableUsed != nil { + mc.mUnevictableUsed.Record(map[string]string{}, int64(meminfo.Unevictable)*1024) + } +} diff --git a/pkg/systemstatsmonitor/memory_collector_test.go b/pkg/systemstatsmonitor/memory_collector_test.go new file mode 100644 index 000000000..944b52b16 --- /dev/null +++ b/pkg/systemstatsmonitor/memory_collector_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +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 systemstatsmonitor + +import ( + "testing" + + ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types" +) + +func TestMemoryCollector(t *testing.T) { + mc := NewMemoryCollectorOrDie(&ssmtypes.MemoryStatsConfig{}) + mc.collect() +} diff --git a/pkg/systemstatsmonitor/memory_collector_windows.go b/pkg/systemstatsmonitor/memory_collector_windows.go new file mode 100644 index 000000000..eab5ea173 --- /dev/null +++ b/pkg/systemstatsmonitor/memory_collector_windows.go @@ -0,0 +1,40 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. + +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 systemstatsmonitor + +import ( + "github.com/golang/glog" + + "github.com/shirou/gopsutil/mem" +) + +func (mc *memoryCollector) collect() { + if mc == nil { + return + } + + meminfo, err := mem.VirtualMemory() + if err != nil { + glog.Errorf("cannot get windows memory metrics from GlobalMemoryStatusEx: %v", err) + return + } + + if mc.mBytesUsed != nil { + mc.mBytesUsed.Record(map[string]string{stateLabel: "free"}, int64(meminfo.Available)*1024) + mc.mBytesUsed.Record(map[string]string{stateLabel: "used"}, int64(meminfo.Used)*1024) + } +} diff --git a/pkg/util/exec_test.go b/pkg/util/exec_test.go index 9d350cb4a..f4578b845 100644 --- a/pkg/util/exec_test.go +++ b/pkg/util/exec_test.go @@ -28,8 +28,8 @@ func TestExec(t *testing.T) { if runtime.GOOS == "windows" { cmds = [][]string{ {"powershell.exe"}, - {"cmd.exe", "/C", "echo", "Hello"}, - {"cmd.exe", "/K", "echo", "Wait", "forever"}, + {"cmd.exe", "/C", "set", "/p", "$="}, + {"cmd.exe", "/K", "set", "/p", "$="}, {"testdata/hello-world.cmd"}, {"testdata/hello-world.bat"}, {"testdata/hello-world.ps1"}, diff --git a/pkg/util/helpers.go b/pkg/util/helpers.go index dfadd4327..22528d6e7 100644 --- a/pkg/util/helpers.go +++ b/pkg/util/helpers.go @@ -19,13 +19,9 @@ import ( "fmt" "time" - "github.com/cobaugh/osrelease" - "k8s.io/node-problem-detector/pkg/types" ) -var osReleasePath = "/etc/os-release" - // GenerateConditionChangeEvent generates an event for condition change. func GenerateConditionChangeEvent(t string, status types.ConditionStatus, reason string, timestamp time.Time) types.Event { return types.Event{ @@ -65,38 +61,3 @@ func GetStartTime(now time.Time, uptimeDuration time.Duration, lookbackStr strin return startTime, nil } - -// GetOSVersion retrieves the version of the current operating system. -// For example: "cos 77-12293.0.0", "ubuntu 16.04.6 LTS (Xenial Xerus)". -func GetOSVersion() (string, error) { - osReleaseMap, err := osrelease.ReadFile(osReleasePath) - if err != nil { - return "", err - } - switch osReleaseMap["ID"] { - case "cos": - return getCOSVersion(osReleaseMap), nil - case "debian": - return getDebianVersion(osReleaseMap), nil - case "ubuntu": - return getDebianVersion(osReleaseMap), nil - case "centos": - return getDebianVersion(osReleaseMap), nil - case "rhel": - return getDebianVersion(osReleaseMap), nil - default: - return "", fmt.Errorf("Unsupported ID in /etc/os-release: %q", osReleaseMap["ID"]) - } -} - -func getCOSVersion(osReleaseMap map[string]string) string { - // /etc/os-release syntax for COS is defined here: - // https://chromium.git.corp.google.com/chromiumos/docs/+/8edec95a297edfd8f1290f0f03a8aa35795b516b/os_config.md - return fmt.Sprintf("%s %s-%s", osReleaseMap["ID"], osReleaseMap["VERSION"], osReleaseMap["BUILD_ID"]) -} - -func getDebianVersion(osReleaseMap map[string]string) string { - // /etc/os-release syntax for Debian is defined here: - // https://manpages.debian.org/testing/systemd/os-release.5.en.html - return fmt.Sprintf("%s %s", osReleaseMap["ID"], osReleaseMap["VERSION"]) -} diff --git a/pkg/util/helpers_linux.go b/pkg/util/helpers_linux.go index 2c7a93eed..c6c93fd23 100644 --- a/pkg/util/helpers_linux.go +++ b/pkg/util/helpers_linux.go @@ -17,10 +17,15 @@ package util import ( "fmt" + "github.com/cobaugh/osrelease" "syscall" "time" ) +const ( + osReleasePath = "/etc/os-release" +) + // GetUptimeDuration returns the time elapsed since last boot. func GetUptimeDuration() (time.Duration, error) { var info syscall.Sysinfo_t @@ -29,3 +34,42 @@ func GetUptimeDuration() (time.Duration, error) { } return time.Duration(info.Uptime) * time.Second, nil } + +// GetOSVersion retrieves the version of the current operating system. +// For example: "cos 77-12293.0.0", "ubuntu 16.04.6 LTS (Xenial Xerus)". +func GetOSVersion() (string, error) { + return getOSVersion(osReleasePath) +} + +func getOSVersion(osReleasePath string) (string, error) { + osReleaseMap, err := osrelease.ReadFile(osReleasePath) + if err != nil { + return "", err + } + switch osReleaseMap["ID"] { + case "cos": + return getCOSVersion(osReleaseMap), nil + case "debian": + return getDebianVersion(osReleaseMap), nil + case "ubuntu": + return getDebianVersion(osReleaseMap), nil + case "centos": + return getDebianVersion(osReleaseMap), nil + case "rhel": + return getDebianVersion(osReleaseMap), nil + default: + return "", fmt.Errorf("Unsupported ID in /etc/os-release: %q", osReleaseMap["ID"]) + } +} + +func getCOSVersion(osReleaseMap map[string]string) string { + // /etc/os-release syntax for COS is defined here: + // https://chromium.git.corp.google.com/chromiumos/docs/+/8edec95a297edfd8f1290f0f03a8aa35795b516b/os_config.md + return fmt.Sprintf("%s %s-%s", osReleaseMap["ID"], osReleaseMap["VERSION"], osReleaseMap["BUILD_ID"]) +} + +func getDebianVersion(osReleaseMap map[string]string) string { + // /etc/os-release syntax for Debian is defined here: + // https://manpages.debian.org/testing/systemd/os-release.5.en.html + return fmt.Sprintf("%s %s", osReleaseMap["ID"], osReleaseMap["VERSION"]) +} diff --git a/pkg/util/helpers_linux_test.go b/pkg/util/helpers_linux_test.go new file mode 100644 index 000000000..1f5944def --- /dev/null +++ b/pkg/util/helpers_linux_test.go @@ -0,0 +1,92 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +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 util + +import ( + "testing" +) + +func TestGetOSVersionLinux(t *testing.T) { + testCases := []struct { + name string + fakeOSReleasePath string + expectedOSVersion string + expectErr bool + }{ + { + name: "COS", + fakeOSReleasePath: "testdata/os-release-cos", + expectedOSVersion: "cos 77-12293.0.0", + expectErr: false, + }, + { + name: "Debian", + fakeOSReleasePath: "testdata/os-release-debian", + expectedOSVersion: "debian 9 (stretch)", + expectErr: false, + }, + { + name: "Ubuntu", + fakeOSReleasePath: "testdata/os-release-ubuntu", + expectedOSVersion: "ubuntu 16.04.6 LTS (Xenial Xerus)", + expectErr: false, + }, + { + name: "centos", + fakeOSReleasePath: "testdata/os-release-centos", + expectedOSVersion: "centos 7 (Core)", + expectErr: false, + }, + { + name: "rhel", + fakeOSReleasePath: "testdata/os-release-rhel", + expectedOSVersion: "rhel 7.7 (Maipo)", + expectErr: false, + }, + { + name: "Unknown", + fakeOSReleasePath: "testdata/os-release-unknown", + expectedOSVersion: "", + expectErr: true, + }, + { + name: "Empty", + fakeOSReleasePath: "testdata/os-release-empty", + expectedOSVersion: "", + expectErr: true, + }, + } + + for _, tt := range testCases { + tc := tt + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + osVersion, err := getOSVersion(tc.fakeOSReleasePath) + + if tc.expectErr && err == nil { + t.Errorf("Expect to get error, but got no returned error.") + } + if !tc.expectErr && err != nil { + t.Errorf("Expect to get no error, but got returned error: %v", err) + } + if !tc.expectErr && osVersion != tc.expectedOSVersion { + t.Errorf("Wanted: %+v. \nGot: %+v", tc.expectedOSVersion, osVersion) + } + }) + } +} diff --git a/pkg/util/helpers_test.go b/pkg/util/helpers_test.go index e3642d332..2ad20d69d 100644 --- a/pkg/util/helpers_test.go +++ b/pkg/util/helpers_test.go @@ -137,75 +137,11 @@ func TestGetStartTime(t *testing.T) { } func TestGetOSVersion(t *testing.T) { - testCases := []struct { - name string - fakeOSReleasePath string - expectedOSVersion string - expectErr bool - }{ - { - name: "COS", - fakeOSReleasePath: "testdata/os-release-cos", - expectedOSVersion: "cos 77-12293.0.0", - expectErr: false, - }, - { - name: "Debian", - fakeOSReleasePath: "testdata/os-release-debian", - expectedOSVersion: "debian 9 (stretch)", - expectErr: false, - }, - { - name: "Ubuntu", - fakeOSReleasePath: "testdata/os-release-ubuntu", - expectedOSVersion: "ubuntu 16.04.6 LTS (Xenial Xerus)", - expectErr: false, - }, - { - name: "centos", - fakeOSReleasePath: "testdata/os-release-centos", - expectedOSVersion: "centos 7 (Core)", - expectErr: false, - }, - { - name: "rhel", - fakeOSReleasePath: "testdata/os-release-rhel", - expectedOSVersion: "rhel 7.7 (Maipo)", - expectErr: false, - }, - { - name: "Unknown", - fakeOSReleasePath: "testdata/os-release-unknown", - expectedOSVersion: "", - expectErr: true, - }, - { - name: "Empty", - fakeOSReleasePath: "testdata/os-release-empty", - expectedOSVersion: "", - expectErr: true, - }, + ver, err := GetOSVersion() + if err != nil { + t.Errorf("cannot get os version, %s", err) } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - originalOSReleasePath := osReleasePath - defer func() { - osReleasePath = originalOSReleasePath - }() - - osReleasePath = test.fakeOSReleasePath - osVersion, err := GetOSVersion() - - if test.expectErr && err == nil { - t.Errorf("Expect to get error, but got no returned error.") - } - if !test.expectErr && err != nil { - t.Errorf("Expect to get no error, but got returned error: %v", err) - } - if !test.expectErr && osVersion != test.expectedOSVersion { - t.Errorf("Wanted: %+v. \nGot: %+v", test.expectedOSVersion, osVersion) - } - }) + if ver == "" { + t.Errorf("GetOSVersion() should not be empty string") } } diff --git a/pkg/util/helpers_windows.go b/pkg/util/helpers_windows.go index 294f10b94..6207a37f0 100644 --- a/pkg/util/helpers_windows.go +++ b/pkg/util/helpers_windows.go @@ -16,9 +16,12 @@ limitations under the License. package util import ( + "fmt" "time" "github.com/shirou/gopsutil/host" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" ) // GetUptimeDuration returns the time elapsed since last boot. @@ -29,3 +32,27 @@ func GetUptimeDuration() (time.Duration, error) { } return time.Duration(ut), nil } + +// GetOSVersion retrieves the version of the current operating system. +// For example: "windows 10.0.17763.1697 (Windows Server 2016 Datacenter)". +func GetOSVersion() (string, error) { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return "", err + } + defer k.Close() + + productName, _, err := k.GetStringValue("ProductName") + if err != nil { + productName = "windows" + } + + ubr, _, err := k.GetIntegerValue("UBR") + if err != nil { + ubr = 0 + } + + major, minor, build := windows.RtlGetNtVersionNumbers() + + return fmt.Sprintf("windows %d.%d.%d.%d (%s)", major, minor, build, ubr, productName), nil +}