github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/mem/mem_linux.go (about) 1 //go:build linux 2 3 package mem 4 5 import ( 6 "bufio" 7 "context" 8 "encoding/json" 9 "fmt" 10 "github.com/isyscore/isc-gobase/system/common" 11 "golang.org/x/sys/unix" 12 "io" 13 "math" 14 "os" 15 "strconv" 16 "strings" 17 ) 18 19 type VirtualMemoryExStat struct { 20 ActiveFile uint64 `json:"activefile"` 21 InactiveFile uint64 `json:"inactivefile"` 22 ActiveAnon uint64 `json:"activeanon"` 23 InactiveAnon uint64 `json:"inactiveanon"` 24 Unevictable uint64 `json:"unevictable"` 25 } 26 27 func (v VirtualMemoryExStat) String() string { 28 s, _ := json.Marshal(v) 29 return string(s) 30 } 31 32 func VirtualMemory() (*VirtualMemoryStat, error) { 33 return VirtualMemoryWithContext(context.Background()) 34 } 35 36 func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { 37 vm, _, err := fillFromMeminfoWithContext(ctx) 38 if err != nil { 39 return nil, err 40 } 41 return vm, nil 42 } 43 44 func VirtualMemoryEx() (*VirtualMemoryExStat, error) { 45 return VirtualMemoryExWithContext(context.Background()) 46 } 47 48 func VirtualMemoryExWithContext(ctx context.Context) (*VirtualMemoryExStat, error) { 49 _, vmEx, err := fillFromMeminfoWithContext(ctx) 50 if err != nil { 51 return nil, err 52 } 53 return vmEx, nil 54 } 55 56 func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *VirtualMemoryExStat, error) { 57 filename := common.HostProc("meminfo") 58 lines, _ := common.ReadLines(filename) 59 60 // flag if MemAvailable is in /proc/meminfo (kernel 3.14+) 61 memavail := false 62 activeFile := false // "Active(file)" not available: 2.6.28 / Dec 2008 63 inactiveFile := false // "Inactive(file)" not available: 2.6.28 / Dec 2008 64 sReclaimable := false // "SReclaimable:" not available: 2.6.19 / Nov 2006 65 66 ret := &VirtualMemoryStat{} 67 retEx := &VirtualMemoryExStat{} 68 69 for _, line := range lines { 70 fields := strings.Split(line, ":") 71 if len(fields) != 2 { 72 continue 73 } 74 key := strings.TrimSpace(fields[0]) 75 value := strings.TrimSpace(fields[1]) 76 value = strings.Replace(value, " kB", "", -1) 77 78 switch key { 79 case "MemTotal": 80 t, err := strconv.ParseUint(value, 10, 64) 81 if err != nil { 82 return ret, retEx, err 83 } 84 ret.Total = t * 1024 85 case "MemFree": 86 t, err := strconv.ParseUint(value, 10, 64) 87 if err != nil { 88 return ret, retEx, err 89 } 90 ret.Free = t * 1024 91 case "MemAvailable": 92 t, err := strconv.ParseUint(value, 10, 64) 93 if err != nil { 94 return ret, retEx, err 95 } 96 memavail = true 97 ret.Available = t * 1024 98 case "Buffers": 99 t, err := strconv.ParseUint(value, 10, 64) 100 if err != nil { 101 return ret, retEx, err 102 } 103 ret.Buffers = t * 1024 104 case "Cached": 105 t, err := strconv.ParseUint(value, 10, 64) 106 if err != nil { 107 return ret, retEx, err 108 } 109 ret.Cached = t * 1024 110 case "Active": 111 t, err := strconv.ParseUint(value, 10, 64) 112 if err != nil { 113 return ret, retEx, err 114 } 115 ret.Active = t * 1024 116 case "Inactive": 117 t, err := strconv.ParseUint(value, 10, 64) 118 if err != nil { 119 return ret, retEx, err 120 } 121 ret.Inactive = t * 1024 122 case "Active(anon)": 123 t, err := strconv.ParseUint(value, 10, 64) 124 if err != nil { 125 return ret, retEx, err 126 } 127 retEx.ActiveAnon = t * 1024 128 case "Inactive(anon)": 129 t, err := strconv.ParseUint(value, 10, 64) 130 if err != nil { 131 return ret, retEx, err 132 } 133 retEx.InactiveAnon = t * 1024 134 case "Active(file)": 135 t, err := strconv.ParseUint(value, 10, 64) 136 if err != nil { 137 return ret, retEx, err 138 } 139 activeFile = true 140 retEx.ActiveFile = t * 1024 141 case "Inactive(file)": 142 t, err := strconv.ParseUint(value, 10, 64) 143 if err != nil { 144 return ret, retEx, err 145 } 146 inactiveFile = true 147 retEx.InactiveFile = t * 1024 148 case "Unevictable": 149 t, err := strconv.ParseUint(value, 10, 64) 150 if err != nil { 151 return ret, retEx, err 152 } 153 retEx.Unevictable = t * 1024 154 case "Writeback": 155 t, err := strconv.ParseUint(value, 10, 64) 156 if err != nil { 157 return ret, retEx, err 158 } 159 ret.Writeback = t * 1024 160 case "WritebackTmp": 161 t, err := strconv.ParseUint(value, 10, 64) 162 if err != nil { 163 return ret, retEx, err 164 } 165 ret.WritebackTmp = t * 1024 166 case "Dirty": 167 t, err := strconv.ParseUint(value, 10, 64) 168 if err != nil { 169 return ret, retEx, err 170 } 171 ret.Dirty = t * 1024 172 case "Shmem": 173 t, err := strconv.ParseUint(value, 10, 64) 174 if err != nil { 175 return ret, retEx, err 176 } 177 ret.Shared = t * 1024 178 case "Slab": 179 t, err := strconv.ParseUint(value, 10, 64) 180 if err != nil { 181 return ret, retEx, err 182 } 183 ret.Slab = t * 1024 184 case "SReclaimable": 185 t, err := strconv.ParseUint(value, 10, 64) 186 if err != nil { 187 return ret, retEx, err 188 } 189 sReclaimable = true 190 ret.SReclaimable = t * 1024 191 case "SUnreclaim": 192 t, err := strconv.ParseUint(value, 10, 64) 193 if err != nil { 194 return ret, retEx, err 195 } 196 ret.SUnreclaim = t * 1024 197 case "PageTables": 198 t, err := strconv.ParseUint(value, 10, 64) 199 if err != nil { 200 return ret, retEx, err 201 } 202 ret.PageTables = t * 1024 203 case "SwapCached": 204 t, err := strconv.ParseUint(value, 10, 64) 205 if err != nil { 206 return ret, retEx, err 207 } 208 ret.SwapCached = t * 1024 209 case "CommitLimit": 210 t, err := strconv.ParseUint(value, 10, 64) 211 if err != nil { 212 return ret, retEx, err 213 } 214 ret.CommitLimit = t * 1024 215 case "Committed_AS": 216 t, err := strconv.ParseUint(value, 10, 64) 217 if err != nil { 218 return ret, retEx, err 219 } 220 ret.CommittedAS = t * 1024 221 case "HighTotal": 222 t, err := strconv.ParseUint(value, 10, 64) 223 if err != nil { 224 return ret, retEx, err 225 } 226 ret.HighTotal = t * 1024 227 case "HighFree": 228 t, err := strconv.ParseUint(value, 10, 64) 229 if err != nil { 230 return ret, retEx, err 231 } 232 ret.HighFree = t * 1024 233 case "LowTotal": 234 t, err := strconv.ParseUint(value, 10, 64) 235 if err != nil { 236 return ret, retEx, err 237 } 238 ret.LowTotal = t * 1024 239 case "LowFree": 240 t, err := strconv.ParseUint(value, 10, 64) 241 if err != nil { 242 return ret, retEx, err 243 } 244 ret.LowFree = t * 1024 245 case "SwapTotal": 246 t, err := strconv.ParseUint(value, 10, 64) 247 if err != nil { 248 return ret, retEx, err 249 } 250 ret.SwapTotal = t * 1024 251 case "SwapFree": 252 t, err := strconv.ParseUint(value, 10, 64) 253 if err != nil { 254 return ret, retEx, err 255 } 256 ret.SwapFree = t * 1024 257 case "Mapped": 258 t, err := strconv.ParseUint(value, 10, 64) 259 if err != nil { 260 return ret, retEx, err 261 } 262 ret.Mapped = t * 1024 263 case "VmallocTotal": 264 t, err := strconv.ParseUint(value, 10, 64) 265 if err != nil { 266 return ret, retEx, err 267 } 268 ret.VMallocTotal = t * 1024 269 case "VmallocUsed": 270 t, err := strconv.ParseUint(value, 10, 64) 271 if err != nil { 272 return ret, retEx, err 273 } 274 ret.VMallocUsed = t * 1024 275 case "VmallocChunk": 276 t, err := strconv.ParseUint(value, 10, 64) 277 if err != nil { 278 return ret, retEx, err 279 } 280 ret.VMallocChunk = t * 1024 281 case "HugePages_Total": 282 t, err := strconv.ParseUint(value, 10, 64) 283 if err != nil { 284 return ret, retEx, err 285 } 286 ret.HugePagesTotal = t 287 case "HugePages_Free": 288 t, err := strconv.ParseUint(value, 10, 64) 289 if err != nil { 290 return ret, retEx, err 291 } 292 ret.HugePagesFree = t 293 case "Hugepagesize": 294 t, err := strconv.ParseUint(value, 10, 64) 295 if err != nil { 296 return ret, retEx, err 297 } 298 ret.HugePageSize = t * 1024 299 } 300 } 301 302 ret.Cached += ret.SReclaimable 303 304 if !memavail { 305 if activeFile && inactiveFile && sReclaimable { 306 ret.Available = calcuateAvailVmem(ret, retEx) 307 } else { 308 ret.Available = ret.Cached + ret.Free 309 } 310 } 311 312 ret.Used = ret.Total - ret.Free - ret.Buffers - ret.Cached 313 ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0 314 315 return ret, retEx, nil 316 } 317 318 func SwapMemory() (*SwapMemoryStat, error) { 319 return SwapMemoryWithContext(context.Background()) 320 } 321 322 func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { 323 sysinfo := &unix.Sysinfo_t{} 324 325 if err := unix.Sysinfo(sysinfo); err != nil { 326 return nil, err 327 } 328 ret := &SwapMemoryStat{ 329 Total: uint64(sysinfo.Totalswap) * uint64(sysinfo.Unit), 330 Free: uint64(sysinfo.Freeswap) * uint64(sysinfo.Unit), 331 } 332 ret.Used = ret.Total - ret.Free 333 //check Infinity 334 if ret.Total != 0 { 335 ret.UsedPercent = float64(ret.Total-ret.Free) / float64(ret.Total) * 100.0 336 } else { 337 ret.UsedPercent = 0 338 } 339 filename := common.HostProc("vmstat") 340 lines, _ := common.ReadLines(filename) 341 for _, l := range lines { 342 fields := strings.Fields(l) 343 if len(fields) < 2 { 344 continue 345 } 346 switch fields[0] { 347 case "pswpin": 348 value, err := strconv.ParseUint(fields[1], 10, 64) 349 if err != nil { 350 continue 351 } 352 ret.Sin = value * 4 * 1024 353 case "pswpout": 354 value, err := strconv.ParseUint(fields[1], 10, 64) 355 if err != nil { 356 continue 357 } 358 ret.Sout = value * 4 * 1024 359 case "pgpgin": 360 value, err := strconv.ParseUint(fields[1], 10, 64) 361 if err != nil { 362 continue 363 } 364 ret.PgIn = value * 4 * 1024 365 case "pgpgout": 366 value, err := strconv.ParseUint(fields[1], 10, 64) 367 if err != nil { 368 continue 369 } 370 ret.PgOut = value * 4 * 1024 371 case "pgfault": 372 value, err := strconv.ParseUint(fields[1], 10, 64) 373 if err != nil { 374 continue 375 } 376 ret.PgFault = value * 4 * 1024 377 case "pgmajfault": 378 value, err := strconv.ParseUint(fields[1], 10, 64) 379 if err != nil { 380 continue 381 } 382 ret.PgMajFault = value * 4 * 1024 383 } 384 } 385 return ret, nil 386 } 387 388 // calcuateAvailVmem is a fallback under kernel 3.14 where /proc/meminfo does not provide 389 // "MemAvailable:" column. It reimplements an algorithm from the link below 390 // https://github.com/giampaolo/psutil/pull/890 391 func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint64 { 392 var watermarkLow uint64 393 394 fn := common.HostProc("zoneinfo") 395 lines, err := common.ReadLines(fn) 396 397 if err != nil { 398 return ret.Free + ret.Cached // fallback under kernel 2.6.13 399 } 400 401 pagesize := uint64(os.Getpagesize()) 402 watermarkLow = 0 403 404 for _, line := range lines { 405 fields := strings.Fields(line) 406 407 if strings.HasPrefix(fields[0], "low") { 408 lowValue, err := strconv.ParseUint(fields[1], 10, 64) 409 410 if err != nil { 411 lowValue = 0 412 } 413 watermarkLow += lowValue 414 } 415 } 416 417 watermarkLow *= pagesize 418 419 availMemory := ret.Free - watermarkLow 420 pageCache := retEx.ActiveFile + retEx.InactiveFile 421 pageCache -= uint64(math.Min(float64(pageCache/2), float64(watermarkLow))) 422 availMemory += pageCache 423 availMemory += ret.SReclaimable - uint64(math.Min(float64(ret.SReclaimable/2.0), float64(watermarkLow))) 424 425 if availMemory < 0 { 426 availMemory = 0 427 } 428 429 return availMemory 430 } 431 432 const swapsFilename = "swaps" 433 434 // swaps file column indexes 435 const ( 436 nameCol = 0 437 // typeCol = 1 438 totalCol = 2 439 usedCol = 3 440 // priorityCol = 4 441 ) 442 443 func SwapDevices() ([]*SwapDevice, error) { 444 return SwapDevicesWithContext(context.Background()) 445 } 446 447 func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { 448 swapsFilePath := common.HostProc(swapsFilename) 449 f, err := os.Open(swapsFilePath) 450 if err != nil { 451 return nil, err 452 } 453 defer f.Close() 454 455 return parseSwapsFile(f) 456 } 457 458 func parseSwapsFile(r io.Reader) ([]*SwapDevice, error) { 459 swapsFilePath := common.HostProc(swapsFilename) 460 scanner := bufio.NewScanner(r) 461 if !scanner.Scan() { 462 if err := scanner.Err(); err != nil { 463 return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err) 464 } 465 return nil, fmt.Errorf("unexpected end-of-file in %q", swapsFilePath) 466 467 } 468 469 // Check header headerFields are as expected 470 headerFields := strings.Fields(scanner.Text()) 471 if len(headerFields) < usedCol { 472 return nil, fmt.Errorf("couldn't parse %q: too few fields in header", swapsFilePath) 473 } 474 if headerFields[nameCol] != "Filename" { 475 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[nameCol], "Filename") 476 } 477 if headerFields[totalCol] != "Size" { 478 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[totalCol], "Size") 479 } 480 if headerFields[usedCol] != "Used" { 481 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[usedCol], "Used") 482 } 483 484 var swapDevices []*SwapDevice 485 for scanner.Scan() { 486 fields := strings.Fields(scanner.Text()) 487 if len(fields) < usedCol { 488 return nil, fmt.Errorf("couldn't parse %q: too few fields", swapsFilePath) 489 } 490 491 totalKiB, err := strconv.ParseUint(fields[totalCol], 10, 64) 492 if err != nil { 493 return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapsFilePath, err) 494 } 495 496 usedKiB, err := strconv.ParseUint(fields[usedCol], 10, 64) 497 if err != nil { 498 return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapsFilePath, err) 499 } 500 501 swapDevices = append(swapDevices, &SwapDevice{ 502 Name: fields[nameCol], 503 UsedBytes: usedKiB * 1024, 504 FreeBytes: (totalKiB - usedKiB) * 1024, 505 }) 506 } 507 508 if err := scanner.Err(); err != nil { 509 return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err) 510 } 511 512 return swapDevices, nil 513 }