github.com/jaypipes/ghw@v0.21.1/pkg/topology/topology_linux.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  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/jaypipes/ghw/pkg/context"
    16  	"github.com/jaypipes/ghw/pkg/cpu"
    17  	"github.com/jaypipes/ghw/pkg/linuxpath"
    18  	"github.com/jaypipes/ghw/pkg/memory"
    19  )
    20  
    21  func (i *Info) load() error {
    22  	i.Nodes = topologyNodes(i.ctx)
    23  	if len(i.Nodes) == 1 {
    24  		i.Architecture = ArchitectureSMP
    25  	} else {
    26  		i.Architecture = ArchitectureNUMA
    27  	}
    28  	return nil
    29  }
    30  
    31  func topologyNodes(ctx *context.Context) []*Node {
    32  	paths := linuxpath.New(ctx)
    33  	nodes := make([]*Node, 0)
    34  
    35  	files, err := os.ReadDir(paths.SysDevicesSystemNode)
    36  	if err != nil {
    37  		ctx.Warn("failed to determine nodes: %s\n", err)
    38  		return nodes
    39  	}
    40  	for _, file := range files {
    41  		filename := file.Name()
    42  		if !strings.HasPrefix(filename, "node") {
    43  			continue
    44  		}
    45  		node := &Node{}
    46  		nodeID, err := strconv.Atoi(filename[4:])
    47  		if err != nil {
    48  			ctx.Warn("failed to determine node ID: %s\n", err)
    49  			return nodes
    50  		}
    51  		node.ID = nodeID
    52  		cores, err := cpu.CoresForNode(ctx, nodeID)
    53  		if err != nil {
    54  			ctx.Warn("failed to determine cores for node: %s\n", err)
    55  			return nodes
    56  		}
    57  		node.Cores = cores
    58  		caches, err := memory.CachesForNode(ctx, nodeID)
    59  		if err != nil {
    60  			ctx.Warn("failed to determine caches for node: %s\n", err)
    61  			return nodes
    62  		}
    63  		node.Caches = caches
    64  
    65  		distances, err := distancesForNode(ctx, nodeID)
    66  		if err != nil {
    67  			ctx.Warn("failed to determine node distances for node: %s\n", err)
    68  			return nodes
    69  		}
    70  		node.Distances = distances
    71  
    72  		area, err := memory.AreaForNode(ctx, nodeID)
    73  		if err != nil {
    74  			ctx.Warn("failed to determine memory area for node: %s\n", err)
    75  			return nodes
    76  		}
    77  		node.Memory = area
    78  
    79  		nodes = append(nodes, node)
    80  	}
    81  	return nodes
    82  }
    83  
    84  func distancesForNode(ctx *context.Context, nodeID int) ([]int, error) {
    85  	paths := linuxpath.New(ctx)
    86  	path := filepath.Join(
    87  		paths.SysDevicesSystemNode,
    88  		fmt.Sprintf("node%d", nodeID),
    89  		"distance",
    90  	)
    91  
    92  	data, err := os.ReadFile(path)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	items := strings.Fields(strings.TrimSpace(string(data)))
    98  	dists := make([]int, len(items)) // TODO: can a NUMA cell be offlined?
    99  	for idx, item := range items {
   100  		dist, err := strconv.Atoi(item)
   101  		if err != nil {
   102  			return dists, err
   103  		}
   104  		dists[idx] = dist
   105  	}
   106  	return dists, nil
   107  }