topology_windows.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. // Use and distribution licensed under the Apache license version 2.
  2. //
  3. // See the COPYING file in the root project directory for full text.
  4. //
  5. package topology
  6. import (
  7. "encoding/binary"
  8. "fmt"
  9. "syscall"
  10. "unsafe"
  11. )
  12. const (
  13. rcFailure = 0
  14. sizeofLogicalProcessorInfo = 32
  15. errInsufficientBuffer syscall.Errno = 122
  16. relationProcessorCore = 0
  17. relationNUMANode = 1
  18. relationCache = 2
  19. relationProcessorPackage = 3
  20. relationGroup = 4
  21. )
  22. func (i *Info) load() error {
  23. nodes, err := topologyNodes()
  24. if err != nil {
  25. return err
  26. }
  27. i.Nodes = nodes
  28. if len(nodes) == 1 {
  29. i.Architecture = ARCHITECTURE_SMP
  30. } else {
  31. i.Architecture = ARCHITECTURE_NUMA
  32. }
  33. return nil
  34. }
  35. func topologyNodes() ([]*Node, error) {
  36. nodes := make([]*Node, 0)
  37. lpis, err := getWin32LogicalProcessorInfos()
  38. if err != nil {
  39. return nil, err
  40. }
  41. for _, lpi := range lpis {
  42. switch lpi.relationship {
  43. case relationNUMANode:
  44. nodes = append(nodes, &Node{
  45. ID: lpi.numaNodeID(),
  46. })
  47. case relationProcessorCore:
  48. // TODO(jaypipes): associated LP to processor core
  49. case relationProcessorPackage:
  50. // ignore
  51. case relationCache:
  52. // TODO(jaypipes) handle cache layers
  53. default:
  54. return nil, fmt.Errorf("Unknown LOGICAL_PROCESSOR_RELATIONSHIP value: %d", lpi.relationship)
  55. }
  56. }
  57. return nodes, nil
  58. }
  59. // This is the CACHE_DESCRIPTOR struct in the Win32 API
  60. type cacheDescriptor struct {
  61. level uint8
  62. associativity uint8
  63. lineSize uint16
  64. size uint32
  65. cacheType uint32
  66. }
  67. // This is the SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct in the Win32 API
  68. type logicalProcessorInfo struct {
  69. processorMask uint64
  70. relationship uint64
  71. // The following dummyunion member is a representation of this part of
  72. // the SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct:
  73. //
  74. // union {
  75. // struct {
  76. // BYTE Flags;
  77. // } ProcessorCore;
  78. // struct {
  79. // DWORD NodeNumber;
  80. // } NumaNode;
  81. // CACHE_DESCRIPTOR Cache;
  82. // ULONGLONG Reserved[2];
  83. // } DUMMYUNIONNAME;
  84. dummyunion [16]byte
  85. }
  86. // numaNodeID returns the NUMA node's identifier from the logical processor
  87. // information struct by grabbing the integer representation of the struct's
  88. // NumaNode unioned data element
  89. func (lpi *logicalProcessorInfo) numaNodeID() int {
  90. if lpi.relationship != relationNUMANode {
  91. return -1
  92. }
  93. return int(binary.LittleEndian.Uint16(lpi.dummyunion[0:]))
  94. }
  95. // ref: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformation
  96. func getWin32LogicalProcessorInfos() (
  97. []*logicalProcessorInfo,
  98. error,
  99. ) {
  100. lpis := make([]*logicalProcessorInfo, 0)
  101. win32api := syscall.NewLazyDLL("kernel32.dll")
  102. glpi := win32api.NewProc("GetLogicalProcessorInformation")
  103. // The way the GetLogicalProcessorInformation (GLPI) Win32 API call
  104. // works is wonky, but consistent with the Win32 API calling structure.
  105. // Basically, you need to first call the GLPI API with a NUL pointerr
  106. // and a pointer to an integer. That first call to the API should
  107. // return ERROR_INSUFFICIENT_BUFFER, which is the indication that the
  108. // supplied buffer pointer is NUL and needs to have memory allocated to
  109. // it of an amount equal to the value of the integer pointer argument.
  110. // Once the buffer is allocated this amount of space, the GLPI API call
  111. // is again called. This time, the return value should be 0 and the
  112. // buffer will have been set to an array of
  113. // SYSTEM_LOGICAL_PROCESSOR_INFORMATION structs.
  114. toAllocate := uint32(0)
  115. // first, figure out how much we need
  116. rc, _, win32err := glpi.Call(uintptr(0), uintptr(unsafe.Pointer(&toAllocate)))
  117. if rc == rcFailure {
  118. if win32err != errInsufficientBuffer {
  119. return nil, fmt.Errorf("GetLogicalProcessorInformation Win32 API initial call failed to return ERROR_INSUFFICIENT_BUFFER")
  120. }
  121. } else {
  122. // This shouldn't happen because buffer hasn't yet been allocated...
  123. return nil, fmt.Errorf("GetLogicalProcessorInformation Win32 API initial call returned success instead of failure with ERROR_INSUFFICIENT_BUFFER")
  124. }
  125. // OK, now we actually allocate a raw buffer to fill with some number
  126. // of SYSTEM_LOGICAL_PROCESSOR_INFORMATION structs
  127. b := make([]byte, toAllocate)
  128. rc, _, win32err = glpi.Call(uintptr(unsafe.Pointer(&b[0])), uintptr(unsafe.Pointer(&toAllocate)))
  129. if rc == rcFailure {
  130. return nil, fmt.Errorf("GetLogicalProcessorInformation Win32 API call failed to set supplied buffer. Win32 system error: %s", win32err)
  131. }
  132. for x := uint32(0); x < toAllocate; x += sizeofLogicalProcessorInfo {
  133. lpiraw := b[x : x+sizeofLogicalProcessorInfo]
  134. lpi := &logicalProcessorInfo{
  135. processorMask: binary.LittleEndian.Uint64(lpiraw[0:]),
  136. relationship: binary.LittleEndian.Uint64(lpiraw[8:]),
  137. }
  138. copy(lpi.dummyunion[0:16], lpiraw[16:32])
  139. lpis = append(lpis, lpi)
  140. }
  141. return lpis, nil
  142. }