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  }