mem_linux.go 13 KB

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