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 }