1
2
3 package mem
4
5 import (
6 "context"
7 "fmt"
8 "os/exec"
9 "regexp"
10 "strconv"
11 "strings"
12
13 "github.com/shirou/gopsutil/internal/common"
14 )
15
16
17
18 func VirtualMemory() (*VirtualMemoryStat, error) {
19 return VirtualMemoryWithContext(context.Background())
20 }
21
22 func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
23 result := &VirtualMemoryStat{}
24
25 zoneName, err := zoneName()
26 if err != nil {
27 return nil, err
28 }
29
30 if zoneName == "global" {
31 cap, err := globalZoneMemoryCapacity()
32 if err != nil {
33 return nil, err
34 }
35 result.Total = cap
36 } else {
37 cap, err := nonGlobalZoneMemoryCapacity()
38 if err != nil {
39 return nil, err
40 }
41 result.Total = cap
42 }
43
44 return result, nil
45 }
46
47 func SwapMemory() (*SwapMemoryStat, error) {
48 return SwapMemoryWithContext(context.Background())
49 }
50
51 func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
52 return nil, common.ErrNotImplementedError
53 }
54
55 func zoneName() (string, error) {
56 zonename, err := exec.LookPath("zonename")
57 if err != nil {
58 return "", err
59 }
60
61 ctx := context.Background()
62 out, err := invoke.CommandWithContext(ctx, zonename)
63 if err != nil {
64 return "", err
65 }
66
67 return strings.TrimSpace(string(out)), nil
68 }
69
70 var globalZoneMemoryCapacityMatch = regexp.MustCompile(`[Mm]emory size: (\d+) Megabytes`)
71
72 func globalZoneMemoryCapacity() (uint64, error) {
73 prtconf, err := exec.LookPath("prtconf")
74 if err != nil {
75 return 0, err
76 }
77
78 ctx := context.Background()
79 out, err := invoke.CommandWithContext(ctx, prtconf)
80 if err != nil {
81 return 0, err
82 }
83
84 match := globalZoneMemoryCapacityMatch.FindAllStringSubmatch(string(out), -1)
85 if len(match) != 1 {
86 return 0, fmt.Errorf("memory size not contained in output of %q", prtconf)
87 }
88
89 totalMB, err := strconv.ParseUint(match[0][1], 10, 64)
90 if err != nil {
91 return 0, err
92 }
93
94 return totalMB * 1024 * 1024, nil
95 }
96
97 var kstatMatch = regexp.MustCompile(`(\S+)\s+(\S*)`)
98
99 func nonGlobalZoneMemoryCapacity() (uint64, error) {
100 kstat, err := exec.LookPath("kstat")
101 if err != nil {
102 return 0, err
103 }
104
105 ctx := context.Background()
106 out, err := invoke.CommandWithContext(ctx, kstat, "-p", "-c", "zone_memory_cap", "memory_cap:*:*:physcap")
107 if err != nil {
108 return 0, err
109 }
110
111 kstats := kstatMatch.FindAllStringSubmatch(string(out), -1)
112 if len(kstats) != 1 {
113 return 0, fmt.Errorf("expected 1 kstat, found %d", len(kstats))
114 }
115
116 memSizeBytes, err := strconv.ParseUint(kstats[0][2], 10, 64)
117 if err != nil {
118 return 0, err
119 }
120
121 return memSizeBytes, nil
122 }
123
124 const swapCommand = "swap"
125
126
127 const blockSize = 512
128
129
130 const (
131 nameCol = 0
132
133
134 totalBlocksCol = 3
135 freeBlocksCol = 4
136 )
137
138 func SwapDevices() ([]*SwapDevice, error) {
139 return SwapDevicesWithContext(context.Background())
140 }
141
142 func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
143 swapCommandPath, err := exec.LookPath(swapCommand)
144 if err != nil {
145 return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err)
146 }
147 output, err := invoke.CommandWithContext(ctx, swapCommandPath, "-l")
148 if err != nil {
149 return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err)
150 }
151
152 return parseSwapsCommandOutput(string(output))
153 }
154
155 func parseSwapsCommandOutput(output string) ([]*SwapDevice, error) {
156 lines := strings.Split(output, "\n")
157 if len(lines) == 0 {
158 return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapCommand, output)
159 }
160
161
162 headerFields := strings.Fields(lines[0])
163 if len(headerFields) < freeBlocksCol {
164 return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapCommand, lines[0])
165 }
166 if headerFields[nameCol] != "swapfile" {
167 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[nameCol], "swapfile")
168 }
169 if headerFields[totalBlocksCol] != "blocks" {
170 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[totalBlocksCol], "blocks")
171 }
172 if headerFields[freeBlocksCol] != "free" {
173 return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[freeBlocksCol], "free")
174 }
175
176 var swapDevices []*SwapDevice
177 for _, line := range lines[1:] {
178 if line == "" {
179 continue
180 }
181 fields := strings.Fields(line)
182 if len(fields) < freeBlocksCol {
183 return nil, fmt.Errorf("couldn't parse %q: too few fields", swapCommand)
184 }
185
186 totalBlocks, err := strconv.ParseUint(fields[totalBlocksCol], 10, 64)
187 if err != nil {
188 return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapCommand, err)
189 }
190
191 freeBlocks, err := strconv.ParseUint(fields[freeBlocksCol], 10, 64)
192 if err != nil {
193 return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapCommand, err)
194 }
195
196 swapDevices = append(swapDevices, &SwapDevice{
197 Name: fields[nameCol],
198 UsedBytes: (totalBlocks - freeBlocks) * blockSize,
199 FreeBytes: freeBlocks * blockSize,
200 })
201 }
202
203 return swapDevices, nil
204 }
205
View as plain text