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