github.com/jaypipes/ghw@v0.21.1/pkg/memory/memory_cache_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 memory
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/jaypipes/ghw/pkg/context"
    18  	"github.com/jaypipes/ghw/pkg/linuxpath"
    19  	"github.com/jaypipes/ghw/pkg/unitutil"
    20  )
    21  
    22  func CachesForNode(ctx *context.Context, nodeID int) ([]*Cache, error) {
    23  	// The /sys/devices/node/nodeX directory contains a subdirectory called
    24  	// 'cpuX' for each logical processor assigned to the node. Each of those
    25  	// subdirectories containers a 'cache' subdirectory which contains a number
    26  	// of subdirectories beginning with 'index' and ending in the cache's
    27  	// internal 0-based identifier. Those subdirectories contain a number of
    28  	// files, including 'shared_cpu_list', 'size', and 'type' which we use to
    29  	// determine cache characteristics.
    30  	paths := linuxpath.New(ctx)
    31  	path := filepath.Join(
    32  		paths.SysDevicesSystemNode,
    33  		fmt.Sprintf("node%d", nodeID),
    34  	)
    35  	caches := make(map[string]*Cache)
    36  
    37  	files, err := os.ReadDir(path)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	for _, file := range files {
    42  		filename := file.Name()
    43  		if !strings.HasPrefix(filename, "cpu") {
    44  			continue
    45  		}
    46  		if filename == "cpumap" || filename == "cpulist" {
    47  			// There are two files in the node directory that start with 'cpu'
    48  			// but are not subdirectories ('cpulist' and 'cpumap'). Ignore
    49  			// these files.
    50  			continue
    51  		}
    52  		// Grab the logical processor ID by cutting the integer from the
    53  		// /sys/devices/system/node/nodeX/cpuX filename
    54  		cpuPath := filepath.Join(path, filename)
    55  		lpID, _ := strconv.Atoi(filename[3:])
    56  
    57  		// Inspect the caches for each logical processor. There will be a
    58  		// /sys/devices/system/node/nodeX/cpuX/cache directory containing a
    59  		// number of directories beginning with the prefix "index" followed by
    60  		// a number. The number indicates the level of the cache, which
    61  		// indicates the "distance" from the processor. Each of these
    62  		// directories contains information about the size of that level of
    63  		// cache and the processors mapped to it.
    64  		cachePath := filepath.Join(cpuPath, "cache")
    65  		if _, err = os.Stat(cachePath); errors.Is(err, os.ErrNotExist) {
    66  			continue
    67  		}
    68  		cacheDirFiles, err := os.ReadDir(cachePath)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  		for _, cacheDirFile := range cacheDirFiles {
    73  			cacheDirFileName := cacheDirFile.Name()
    74  			if !strings.HasPrefix(cacheDirFileName, "index") {
    75  				continue
    76  			}
    77  			cacheIndex, _ := strconv.Atoi(cacheDirFileName[5:])
    78  
    79  			// The cache information is repeated for each node, so here, we
    80  			// just ensure that we only have a one Cache object for each
    81  			// unique combination of level, type and processor map
    82  			level := memoryCacheLevel(ctx, paths, nodeID, lpID, cacheIndex)
    83  			cacheType := memoryCacheType(ctx, paths, nodeID, lpID, cacheIndex)
    84  			sharedCpuMap := memoryCacheSharedCPUMap(ctx, paths, nodeID, lpID, cacheIndex)
    85  			cacheKey := fmt.Sprintf("%d-%d-%s", level, cacheType, sharedCpuMap)
    86  
    87  			cache, exists := caches[cacheKey]
    88  			if !exists {
    89  				size := memoryCacheSize(ctx, paths, nodeID, lpID, level)
    90  				cache = &Cache{
    91  					Level:             uint8(level),
    92  					Type:              cacheType,
    93  					SizeBytes:         uint64(size) * uint64(unitutil.KB),
    94  					LogicalProcessors: make([]uint32, 0),
    95  				}
    96  				caches[cacheKey] = cache
    97  			}
    98  			cache.LogicalProcessors = append(
    99  				cache.LogicalProcessors,
   100  				uint32(lpID),
   101  			)
   102  		}
   103  	}
   104  
   105  	cacheVals := make([]*Cache, len(caches))
   106  	x := 0
   107  	for _, c := range caches {
   108  		// ensure the cache's processor set is sorted by logical process ID
   109  		sort.Sort(SortByLogicalProcessorId(c.LogicalProcessors))
   110  		cacheVals[x] = c
   111  		x++
   112  	}
   113  
   114  	return cacheVals, nil
   115  }
   116  
   117  func memoryCacheLevel(ctx *context.Context, paths *linuxpath.Paths, nodeID int, lpID int, cacheIndex int) int {
   118  	levelPath := filepath.Join(
   119  		paths.NodeCPUCacheIndex(nodeID, lpID, cacheIndex),
   120  		"level",
   121  	)
   122  	levelContents, err := os.ReadFile(levelPath)
   123  	if err != nil {
   124  		ctx.Warn("%s", err)
   125  		return -1
   126  	}
   127  	// levelContents is now a []byte with the last byte being a newline
   128  	// character. Trim that off and convert the contents to an integer.
   129  	level, err := strconv.Atoi(string(levelContents[:len(levelContents)-1]))
   130  	if err != nil {
   131  		ctx.Warn("Unable to parse int from %s", levelContents)
   132  		return -1
   133  	}
   134  	return level
   135  }
   136  
   137  func memoryCacheSize(ctx *context.Context, paths *linuxpath.Paths, nodeID int, lpID int, cacheIndex int) int {
   138  	sizePath := filepath.Join(
   139  		paths.NodeCPUCacheIndex(nodeID, lpID, cacheIndex),
   140  		"size",
   141  	)
   142  	sizeContents, err := os.ReadFile(sizePath)
   143  	if err != nil {
   144  		ctx.Warn("%s", err)
   145  		return -1
   146  	}
   147  	// size comes as XK\n, so we trim off the K and the newline.
   148  	size, err := strconv.Atoi(string(sizeContents[:len(sizeContents)-2]))
   149  	if err != nil {
   150  		ctx.Warn("Unable to parse int from %s", sizeContents)
   151  		return -1
   152  	}
   153  	return size
   154  }
   155  
   156  func memoryCacheType(ctx *context.Context, paths *linuxpath.Paths, nodeID int, lpID int, cacheIndex int) CacheType {
   157  	typePath := filepath.Join(
   158  		paths.NodeCPUCacheIndex(nodeID, lpID, cacheIndex),
   159  		"type",
   160  	)
   161  	cacheTypeContents, err := os.ReadFile(typePath)
   162  	if err != nil {
   163  		ctx.Warn("%s", err)
   164  		return CacheTypeUnified
   165  	}
   166  	switch string(cacheTypeContents[:len(cacheTypeContents)-1]) {
   167  	case "Data":
   168  		return CacheTypeData
   169  	case "Instruction":
   170  		return CacheTypeInstruction
   171  	default:
   172  		return CacheTypeUnified
   173  	}
   174  }
   175  
   176  func memoryCacheSharedCPUMap(ctx *context.Context, paths *linuxpath.Paths, nodeID int, lpID int, cacheIndex int) string {
   177  	scpuPath := filepath.Join(
   178  		paths.NodeCPUCacheIndex(nodeID, lpID, cacheIndex),
   179  		"shared_cpu_map",
   180  	)
   181  	sharedCpuMap, err := os.ReadFile(scpuPath)
   182  	if err != nil {
   183  		ctx.Warn("%s", err)
   184  		return ""
   185  	}
   186  	return string(sharedCpuMap[:len(sharedCpuMap)-1])
   187  }