perfcounter_nodestats.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // +build windows
  2. /*
  3. Copyright 2017 The Kubernetes Authors.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package winstats
  15. import (
  16. "errors"
  17. "os"
  18. "runtime"
  19. "sync"
  20. "time"
  21. "unsafe"
  22. cadvisorapi "github.com/google/cadvisor/info/v1"
  23. "golang.org/x/sys/windows"
  24. "k8s.io/apimachinery/pkg/util/wait"
  25. "k8s.io/klog"
  26. )
  27. // MemoryStatusEx is the same as Windows structure MEMORYSTATUSEX
  28. // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366770(v=vs.85).aspx
  29. type MemoryStatusEx struct {
  30. Length uint32
  31. MemoryLoad uint32
  32. TotalPhys uint64
  33. AvailPhys uint64
  34. TotalPageFile uint64
  35. AvailPageFile uint64
  36. TotalVirtual uint64
  37. AvailVirtual uint64
  38. AvailExtendedVirtual uint64
  39. }
  40. var (
  41. modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
  42. procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
  43. )
  44. // NewPerfCounterClient creates a client using perf counters
  45. func NewPerfCounterClient() (Client, error) {
  46. // Initialize the cache
  47. initCache := cpuUsageCoreNanoSecondsCache{0, 0}
  48. return newClient(&perfCounterNodeStatsClient{
  49. cpuUsageCoreNanoSecondsCache: initCache,
  50. })
  51. }
  52. // perfCounterNodeStatsClient is a client that provides Windows Stats via PerfCounters
  53. type perfCounterNodeStatsClient struct {
  54. nodeMetrics
  55. mu sync.RWMutex // mu protects nodeMetrics
  56. nodeInfo
  57. // cpuUsageCoreNanoSecondsCache caches the cpu usage for nodes.
  58. cpuUsageCoreNanoSecondsCache
  59. }
  60. func (p *perfCounterNodeStatsClient) startMonitoring() error {
  61. memory, err := getPhysicallyInstalledSystemMemoryBytes()
  62. if err != nil {
  63. return err
  64. }
  65. kernelVersion, err := getKernelVersion()
  66. if err != nil {
  67. return err
  68. }
  69. osImageVersion, err := getOSImageVersion()
  70. if err != nil {
  71. return err
  72. }
  73. p.nodeInfo = nodeInfo{
  74. kernelVersion: kernelVersion,
  75. osImageVersion: osImageVersion,
  76. memoryPhysicalCapacityBytes: memory,
  77. startTime: time.Now(),
  78. }
  79. cpuCounter, err := newPerfCounter(cpuQuery)
  80. if err != nil {
  81. return err
  82. }
  83. memWorkingSetCounter, err := newPerfCounter(memoryPrivWorkingSetQuery)
  84. if err != nil {
  85. return err
  86. }
  87. memCommittedBytesCounter, err := newPerfCounter(memoryCommittedBytesQuery)
  88. if err != nil {
  89. return err
  90. }
  91. networkAdapterCounter, err := newNetworkCounters()
  92. if err != nil {
  93. return err
  94. }
  95. go wait.Forever(func() {
  96. p.collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter, networkAdapterCounter)
  97. }, perfCounterUpdatePeriod)
  98. // Cache the CPU usage every defaultCachePeriod
  99. go wait.Forever(func() {
  100. newValue := p.nodeMetrics.cpuUsageCoreNanoSeconds
  101. p.mu.Lock()
  102. defer p.mu.Unlock()
  103. p.cpuUsageCoreNanoSecondsCache = cpuUsageCoreNanoSecondsCache{
  104. previousValue: p.cpuUsageCoreNanoSecondsCache.latestValue,
  105. latestValue: newValue,
  106. }
  107. }, defaultCachePeriod)
  108. return nil
  109. }
  110. func (p *perfCounterNodeStatsClient) getMachineInfo() (*cadvisorapi.MachineInfo, error) {
  111. hostname, err := os.Hostname()
  112. if err != nil {
  113. return nil, err
  114. }
  115. return &cadvisorapi.MachineInfo{
  116. NumCores: runtime.NumCPU(),
  117. MemoryCapacity: p.nodeInfo.memoryPhysicalCapacityBytes,
  118. MachineID: hostname,
  119. }, nil
  120. }
  121. func (p *perfCounterNodeStatsClient) getVersionInfo() (*cadvisorapi.VersionInfo, error) {
  122. return &cadvisorapi.VersionInfo{
  123. KernelVersion: p.nodeInfo.kernelVersion,
  124. ContainerOsVersion: p.nodeInfo.osImageVersion,
  125. }, nil
  126. }
  127. func (p *perfCounterNodeStatsClient) getNodeMetrics() (nodeMetrics, error) {
  128. p.mu.RLock()
  129. defer p.mu.RUnlock()
  130. return p.nodeMetrics, nil
  131. }
  132. func (p *perfCounterNodeStatsClient) getNodeInfo() nodeInfo {
  133. return p.nodeInfo
  134. }
  135. func (p *perfCounterNodeStatsClient) collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter *perfCounter, networkAdapterCounter *networkCounter) {
  136. cpuValue, err := cpuCounter.getData()
  137. cpuCores := runtime.NumCPU()
  138. if err != nil {
  139. klog.Errorf("Unable to get cpu perf counter data; err: %v", err)
  140. return
  141. }
  142. memWorkingSetValue, err := memWorkingSetCounter.getData()
  143. if err != nil {
  144. klog.Errorf("Unable to get memWorkingSet perf counter data; err: %v", err)
  145. return
  146. }
  147. memCommittedBytesValue, err := memCommittedBytesCounter.getData()
  148. if err != nil {
  149. klog.Errorf("Unable to get memCommittedBytes perf counter data; err: %v", err)
  150. return
  151. }
  152. networkAdapterStats, err := networkAdapterCounter.getData()
  153. if err != nil {
  154. klog.Errorf("Unable to get network adapter perf counter data; err: %v", err)
  155. return
  156. }
  157. p.mu.Lock()
  158. defer p.mu.Unlock()
  159. p.nodeMetrics = nodeMetrics{
  160. cpuUsageCoreNanoSeconds: p.convertCPUValue(cpuCores, cpuValue),
  161. cpuUsageNanoCores: p.getCPUUsageNanoCores(),
  162. memoryPrivWorkingSetBytes: memWorkingSetValue,
  163. memoryCommittedBytes: memCommittedBytesValue,
  164. interfaceStats: networkAdapterStats,
  165. timeStamp: time.Now(),
  166. }
  167. }
  168. func (p *perfCounterNodeStatsClient) convertCPUValue(cpuCores int, cpuValue uint64) uint64 {
  169. // This converts perf counter data which is cpu percentage for all cores into nanoseconds.
  170. // The formula is (cpuPercentage / 100.0) * #cores * 1e+9 (nano seconds). More info here:
  171. // https://github.com/kubernetes/heapster/issues/650
  172. newValue := p.nodeMetrics.cpuUsageCoreNanoSeconds + uint64((float64(cpuValue)/100.0)*float64(cpuCores)*1e9)
  173. return newValue
  174. }
  175. func (p *perfCounterNodeStatsClient) getCPUUsageNanoCores() uint64 {
  176. cachePeriodSeconds := uint64(defaultCachePeriod / time.Second)
  177. cpuUsageNanoCores := (p.cpuUsageCoreNanoSecondsCache.latestValue - p.cpuUsageCoreNanoSecondsCache.previousValue) / cachePeriodSeconds
  178. return cpuUsageNanoCores
  179. }
  180. func getPhysicallyInstalledSystemMemoryBytes() (uint64, error) {
  181. // We use GlobalMemoryStatusEx instead of GetPhysicallyInstalledSystemMemory
  182. // on Windows node for the following reasons:
  183. // 1. GetPhysicallyInstalledSystemMemory retrieves the amount of physically
  184. // installed RAM from the computer's SMBIOS firmware tables.
  185. // https://msdn.microsoft.com/en-us/library/windows/desktop/cc300158(v=vs.85).aspx
  186. // On some VM, it is unable to read data from SMBIOS and fails with ERROR_INVALID_DATA.
  187. // 2. On Linux node, total physical memory is read from MemTotal in /proc/meminfo.
  188. // GlobalMemoryStatusEx returns the amount of physical memory that is available
  189. // for the operating system to use. The amount returned by GlobalMemoryStatusEx
  190. // is closer in parity with Linux
  191. // https://www.kernel.org/doc/Documentation/filesystems/proc.txt
  192. var statex MemoryStatusEx
  193. statex.Length = uint32(unsafe.Sizeof(statex))
  194. ret, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&statex)))
  195. if ret == 0 {
  196. return 0, errors.New("unable to read physical memory")
  197. }
  198. return statex.TotalPhys, nil
  199. }