1
2
3 package mem
4
5 import (
6 "context"
7 "fmt"
8 "os/exec"
9 "strconv"
10 "strings"
11 )
12
13 const swapCommand = "swapctl"
14
15
16 const (
17 nameCol = 0
18 totalKiBCol = 1
19 usedKiBCol = 2
20 )
21
22 func SwapDevices() ([]*SwapDevice, error) {
23 return SwapDevicesWithContext(context.Background())
24 }
25
26 func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
27 swapCommandPath, err := exec.LookPath(swapCommand)
28 if err != nil {
29 return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err)
30 }
31 output, err := invoke.CommandWithContext(ctx, swapCommandPath, "-lk")
32 if err != nil {
33 return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err)
34 }
35
36 return parseSwapctlOutput(string(output))
37 }
38
39 func parseSwapctlOutput(output string) ([]*SwapDevice, error) {
40 lines := strings.Split(output, "\n")
41 if len(lines) == 0 {
42 return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapCommand, output)
43 }
44
45
46 header := lines[0]
47 header = strings.ToLower(header)
48 header = strings.ReplaceAll(header, ":", "")
49 headerFields := strings.Fields(header)
50 if len(headerFields) < usedKiBCol {
51 return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapCommand, header)
52 }
53 if headerFields[nameCol] != "device" {
54 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[nameCol], "device")
55 }
56 if headerFields[totalKiBCol] != "1kb-blocks" && headerFields[totalKiBCol] != "1k-blocks" {
57 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[totalKiBCol], "1kb-blocks")
58 }
59 if headerFields[usedKiBCol] != "used" {
60 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[usedKiBCol], "used")
61 }
62
63 var swapDevices []*SwapDevice
64 for _, line := range lines[1:] {
65 if line == "" {
66 continue
67 }
68 fields := strings.Fields(line)
69 if len(fields) < usedKiBCol {
70 return nil, fmt.Errorf("couldn't parse %q: too few fields", swapCommand)
71 }
72
73 totalKiB, err := strconv.ParseUint(fields[totalKiBCol], 10, 64)
74 if err != nil {
75 return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapCommand, err)
76 }
77
78 usedKiB, err := strconv.ParseUint(fields[usedKiBCol], 10, 64)
79 if err != nil {
80 return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapCommand, err)
81 }
82
83 swapDevices = append(swapDevices, &SwapDevice{
84 Name: fields[nameCol],
85 UsedBytes: usedKiB * 1024,
86 FreeBytes: (totalKiB - usedKiB) * 1024,
87 })
88 }
89
90 return swapDevices, nil
91 }
92
View as plain text