mem_linux.go 13 KB

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