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