From 84a665b712b8b5e82ab2a36eb9a704dd02996f3a Mon Sep 17 00:00:00 2001 From: Tom Barker Date: Wed, 18 Aug 2021 09:52:13 -0400 Subject: [PATCH 1/5] Add mem.SwapDevices() method. --- mem/mem.go | 13 +++++- mem/mem_bsd.go | 87 ++++++++++++++++++++++++++++++++++++++++ mem/mem_bsd_test.go | 63 +++++++++++++++++++++++++++++ mem/mem_darwin.go | 9 +++++ mem/mem_darwin_cgo.go | 3 +- mem/mem_darwin_nocgo.go | 3 +- mem/mem_fallback.go | 8 ++++ mem/mem_linux.go | 84 ++++++++++++++++++++++++++++++++++++++ mem/mem_linux_test.go | 43 ++++++++++++++++++++ mem/mem_openbsd_386.go | 4 +- mem/mem_openbsd_arm64.go | 4 +- mem/mem_solaris.go | 80 ++++++++++++++++++++++++++++++++++++ mem/mem_solaris_test.go | 45 +++++++++++++++++++++ mem/mem_test.go | 26 ++++++++++++ mem/mem_windows.go | 69 ++++++++++++++++++++++++++++++- 15 files changed, 531 insertions(+), 10 deletions(-) create mode 100644 mem/mem_bsd.go create mode 100644 mem/mem_bsd_test.go create mode 100644 mem/mem_solaris_test.go diff --git a/mem/mem.go b/mem/mem.go index dc2aacb59..c7e9880b7 100644 --- a/mem/mem.go +++ b/mem/mem.go @@ -91,7 +91,7 @@ type SwapMemoryStat struct { // Linux specific numbers // https://www.kernel.org/doc/Documentation/cgroup-v2.txt - PgMajFault uint64 `json:"pgmajfault"` + PgMajFault uint64 `json:"pgmajfault"` } func (m VirtualMemoryStat) String() string { @@ -103,3 +103,14 @@ func (m SwapMemoryStat) String() string { s, _ := json.Marshal(m) return string(s) } + +type SwapDevice struct { + Name string `json:"name"` + UsedBytes uint64 `json:"usedBytes"` + FreeBytes uint64 `json:"freeBytes"` +} + +func (m SwapDevice) String() string { + s, _ := json.Marshal(m) + return string(s) +} diff --git a/mem/mem_bsd.go b/mem/mem_bsd.go new file mode 100644 index 000000000..058f8fa0a --- /dev/null +++ b/mem/mem_bsd.go @@ -0,0 +1,87 @@ +// +build freebsd openbsd + +package mem + +import ( + "context" + "fmt" + "os/exec" + "strconv" + "strings" +) + +const swapCommand = "/sbin/swapctl" + +// swapctl column indexes +const ( + nameCol = 0 + totalKiBCol = 1 + usedKiBCol = 2 +) + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + output, err := exec.Command(swapCommand, "-lk").Output() + if err != nil { + return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err) + } + + return parseSwapctlOutput(string(output)) +} + +func parseSwapctlOutput(output string) ([]*SwapDevice, error) { + lines := strings.Split(output, "\n") + if len(lines) == 0 { + return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapCommand, output) + } + + // Check header headerFields are as expected. + header := lines[0] + header = strings.ToLower(header) + header = strings.ReplaceAll(header, ":", "") + headerFields := strings.Fields(header) + if len(headerFields) < usedKiBCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapCommand, header) + } + if headerFields[nameCol] != "device" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[nameCol], "device") + } + if headerFields[totalKiBCol] != "1kb-blocks" && headerFields[totalKiBCol] != "1k-blocks" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[totalKiBCol], "1kb-blocks") + } + if headerFields[usedKiBCol] != "used" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[usedKiBCol], "used") + } + + var swapDevices []*SwapDevice + for _, line := range lines[1:] { + if line == "" { + continue // the terminal line is typically empty + } + fields := strings.Fields(line) + if len(fields) < usedKiBCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields", swapCommand) + } + + totalKiB, err := strconv.ParseUint(fields[totalKiBCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapCommand, err) + } + + usedKiB, err := strconv.ParseUint(fields[usedKiBCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapCommand, err) + } + + swapDevices = append(swapDevices, &SwapDevice{ + Name: fields[nameCol], + UsedBytes: usedKiB * 1024, + FreeBytes: (totalKiB - usedKiB) * 1024, + }) + } + + return swapDevices, nil +} diff --git a/mem/mem_bsd_test.go b/mem/mem_bsd_test.go new file mode 100644 index 000000000..ad3838e7f --- /dev/null +++ b/mem/mem_bsd_test.go @@ -0,0 +1,63 @@ +// +build freebsd openbsd + +package mem + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const validFreeBSD = `Device: 1kB-blocks Used: +/dev/gpt/swapfs 1048576 1234 +/dev/md0 1048576 666 +` + +const validOpenBSD = `Device 1K-blocks Used Avail Capacity Priority +/dev/wd0b 655025 1234 653791 1% 0 +` + +const invalid = `Device: 512-blocks Used: +/dev/gpt/swapfs 1048576 1234 +/dev/md0 1048576 666 +` + +func TestParseSwapctlOutput_FreeBSD(t *testing.T) { + assert := assert.New(t) + stats, err := parseSwapctlOutput(validFreeBSD) + assert.NoError(err) + + assert.Equal(*stats[0], SwapDevice{ + Name: "/dev/gpt/swapfs", + UsedBytes: 1263616, + FreeBytes: 1072478208, + }) + + assert.Equal(*stats[1], SwapDevice{ + Name: "/dev/md0", + UsedBytes: 681984, + FreeBytes: 1073059840, + }) +} + +func TestParseSwapctlOutput_OpenBSD(t *testing.T) { + assert := assert.New(t) + stats, err := parseSwapctlOutput(validOpenBSD) + assert.NoError(err) + + assert.Equal(*stats[0], SwapDevice{ + Name: "/dev/wd0b", + UsedBytes: 1234 * 1024, + FreeBytes: 653791 * 1024, + }) +} + +func TestParseSwapctlOutput_Invalid(t *testing.T) { + _, err := parseSwapctlOutput(invalid) + assert.Error(t, err) +} + +func TestParseSwapctlOutput_Empty(t *testing.T) { + _, err := parseSwapctlOutput("") + assert.Error(t, err) +} diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index fac748151..600a7a322 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -8,6 +8,7 @@ import ( "fmt" "unsafe" + "github.com/shirou/gopsutil/internal/common" "golang.org/x/sys/unix" ) @@ -67,3 +68,11 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { return ret, nil } + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + return nil, common.ErrNotImplementedError +} diff --git a/mem/mem_darwin_cgo.go b/mem/mem_darwin_cgo.go index 389f8cdf9..ade3cecd4 100644 --- a/mem/mem_darwin_cgo.go +++ b/mem/mem_darwin_cgo.go @@ -1,5 +1,4 @@ -// +build darwin -// +build cgo +// +build darwin,cgo package mem diff --git a/mem/mem_darwin_nocgo.go b/mem/mem_darwin_nocgo.go index dd7c2e600..2e847cbe4 100644 --- a/mem/mem_darwin_nocgo.go +++ b/mem/mem_darwin_nocgo.go @@ -1,5 +1,4 @@ -// +build darwin -// +build !cgo +// +build darwin,!cgo package mem diff --git a/mem/mem_fallback.go b/mem/mem_fallback.go index 2a0fd45b3..02e87d7a9 100644 --- a/mem/mem_fallback.go +++ b/mem/mem_fallback.go @@ -23,3 +23,11 @@ func SwapMemory() (*SwapMemoryStat, error) { func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { return nil, common.ErrNotImplementedError } + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + return nil, common.ErrNotImplementedError +} diff --git a/mem/mem_linux.go b/mem/mem_linux.go index f9cb8f260..fe653b262 100644 --- a/mem/mem_linux.go +++ b/mem/mem_linux.go @@ -3,8 +3,11 @@ package mem import ( + "bufio" "context" "encoding/json" + "fmt" + "io" "math" "os" "strconv" @@ -426,3 +429,84 @@ func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint6 return availMemory } + +const swapsFilePath = "/proc/swaps" + +// swaps file column indexes +const ( + nameCol = 0 + // typeCol = 1 + totalCol = 2 + usedCol = 3 + // priorityCol = 4 +) + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + f, err := os.Open(swapsFilePath) + if err != nil { + return nil, err + } + defer f.Close() + + return parseSwapsFile(f) +} + +func parseSwapsFile(r io.Reader) ([]*SwapDevice, error) { + scanner := bufio.NewScanner(r) + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err) + } + return nil, fmt.Errorf("unexpected end-of-file in %q", swapsFilePath) + + } + + // Check header headerFields are as expected + headerFields := strings.Fields(scanner.Text()) + if len(headerFields) < usedCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields in header", swapsFilePath) + } + if headerFields[nameCol] != "Filename" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[nameCol], "Filename") + } + if headerFields[totalCol] != "Size" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[totalCol], "Size") + } + if headerFields[usedCol] != "Used" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[usedCol], "Used") + } + + var swapDevices []*SwapDevice + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < usedCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields", swapsFilePath) + } + + totalKiB, err := strconv.ParseUint(fields[totalCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapsFilePath, err) + } + + usedKiB, err := strconv.ParseUint(fields[usedCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapsFilePath, err) + } + + swapDevices = append(swapDevices, &SwapDevice{ + Name: fields[nameCol], + UsedBytes: usedKiB * 1024, + FreeBytes: (totalKiB - usedKiB) * 1024, + }) + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err) + } + + return swapDevices, nil +} diff --git a/mem/mem_linux_test.go b/mem/mem_linux_test.go index 0d1500a1d..d52011bc6 100644 --- a/mem/mem_linux_test.go +++ b/mem/mem_linux_test.go @@ -1,10 +1,15 @@ +// +build linux + package mem import ( "os" "path/filepath" "reflect" + "strings" "testing" + + "github.com/stretchr/testify/assert" ) func TestVirtualMemoryEx(t *testing.T) { @@ -115,3 +120,41 @@ func TestVirtualMemoryLinux(t *testing.T) { }) } } + +const validFile = `Filename Type Size Used Priority +/dev/dm-2 partition 67022844 490788 -2 +/swapfile file 2 1 -3 +` + +const invalidFile = `INVALID Type Size Used Priority +/dev/dm-2 partition 67022844 490788 -2 +/swapfile file 1048572 0 -3 +` + +func TestParseSwapsFile_ValidFile(t *testing.T) { + assert := assert.New(t) + stats, err := parseSwapsFile(strings.NewReader(validFile)) + assert.NoError(err) + + assert.Equal(*stats[0], SwapDevice{ + Name: "/dev/dm-2", + UsedBytes: 502566912, + FreeBytes: 68128825344, + }) + + assert.Equal(*stats[1], SwapDevice{ + Name: "/swapfile", + UsedBytes: 1024, + FreeBytes: 1024, + }) +} + +func TestParseSwapsFile_InvalidFile(t *testing.T) { + _, err := parseSwapsFile(strings.NewReader(invalidFile)) + assert.Error(t, err) +} + +func TestParseSwapsFile_EmptyFile(t *testing.T) { + _, err := parseSwapsFile(strings.NewReader("")) + assert.Error(t, err) +} diff --git a/mem/mem_openbsd_386.go b/mem/mem_openbsd_386.go index aacd4f61e..0fa65d9c7 100644 --- a/mem/mem_openbsd_386.go +++ b/mem/mem_openbsd_386.go @@ -1,5 +1,5 @@ -// +build openbsd -// +build 386 +// +build openbsd,386 + // Code generated by cmd/cgo -godefs; DO NOT EDIT. // cgo -godefs mem/types_openbsd.go diff --git a/mem/mem_openbsd_arm64.go b/mem/mem_openbsd_arm64.go index ad48fc30f..35f8517b6 100644 --- a/mem/mem_openbsd_arm64.go +++ b/mem/mem_openbsd_arm64.go @@ -1,5 +1,5 @@ -// +build openbsd -// +build arm64 +// +build openbsd,arm64 + // Code generated by cmd/cgo -godefs; DO NOT EDIT. // cgo -godefs mem/types_openbsd.go diff --git a/mem/mem_solaris.go b/mem/mem_solaris.go index 08512733c..2b161d13a 100644 --- a/mem/mem_solaris.go +++ b/mem/mem_solaris.go @@ -1,3 +1,5 @@ +// +build solaris + package mem import ( @@ -119,3 +121,81 @@ func nonGlobalZoneMemoryCapacity() (uint64, error) { return memSizeBytes, nil } + +const swapsCommand = "swap" + +// The blockSize as reported by `swap -l`. See https://docs.oracle.com/cd/E23824_01/html/821-1459/fsswap-52195.html +const blockSize = 512 + +// swapctl column indexes +const ( + nameCol = 0 + // devCol = 1 + // swaploCol = 2 + totalBlocksCol = 3 + freeBlocksCol = 4 +) + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + output, err := exec.Command(swapsCommand, "-l").Output() + if err != nil { + return nil, fmt.Errorf("could not execute %q: %w", swapsCommand, err) + } + + return parseSwapsCommandOutput(string(output)) +} + +func parseSwapsCommandOutput(output string) ([]*SwapDevice, error) { + lines := strings.Split(output, "\n") + if len(lines) == 0 { + return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapsCommand, output) + } + + // Check header headerFields are as expected. + headerFields := strings.Fields(lines[0]) + if len(headerFields) < freeBlocksCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapsCommand, lines[0]) + } + if headerFields[nameCol] != "swapfile" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsCommand, headerFields[nameCol], "swapfile") + } + if headerFields[totalBlocksCol] != "blocks" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsCommand, headerFields[totalBlocksCol], "blocks") + } + if headerFields[freeBlocksCol] != "free" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsCommand, headerFields[freeBlocksCol], "free") + } + + var swapDevices []*SwapDevice + for _, line := range lines[1:] { + if line == "" { + continue // the terminal line is typically empty + } + fields := strings.Fields(line) + if len(fields) < freeBlocksCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields", swapsCommand) + } + + totalBlocks, err := strconv.ParseUint(fields[totalBlocksCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapsCommand, err) + } + + freeBlocks, err := strconv.ParseUint(fields[freeBlocksCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapsCommand, err) + } + + swapDevices = append(swapDevices, &SwapDevice{ + Name: fields[nameCol], + UsedBytes: (totalBlocks - freeBlocks) * blockSize, + FreeBytes: freeBlocks * blockSize, + }) + } + + return swapDevices, nil +} diff --git a/mem/mem_solaris_test.go b/mem/mem_solaris_test.go new file mode 100644 index 000000000..907e49d46 --- /dev/null +++ b/mem/mem_solaris_test.go @@ -0,0 +1,45 @@ +// +build solaris + +package mem + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const validFile = `swapfile dev swaplo blocks free +/dev/zvol/dsk/rpool/swap 256,1 16 1058800 1058800 +/dev/dsk/c0t0d0s1 136,1 16 1638608 1600528` + +const invalidFile = `swapfile dev swaplo INVALID free +/dev/zvol/dsk/rpool/swap 256,1 16 1058800 1058800 +/dev/dsk/c0t0d0s1 136,1 16 1638608 1600528` + +func TestParseSwapsCommandOutput_Valid(t *testing.T) { + assert := assert.New(t) + stats, err := parseSwapsCommandOutput(validFile) + assert.NoError(err) + + assert.Equal(*stats[0], SwapDevice{ + Name: "/dev/zvol/dsk/rpool/swap", + UsedBytes: 0, + FreeBytes: 1058800 * 512, + }) + + assert.Equal(*stats[1], SwapDevice{ + Name: "/dev/dsk/c0t0d0s1", + UsedBytes: 38080 * 512, + FreeBytes: 1600528 * 512, + }) +} + +func TestParseSwapsCommandOutput_Invalid(t *testing.T) { + _, err := parseSwapsCommandOutput(invalidFile) + assert.Error(t, err) +} + +func TestParseSwapsCommandOutput_Empty(t *testing.T) { + _, err := parseSwapsCommandOutput("") + assert.Error(t, err) +} diff --git a/mem/mem_test.go b/mem/mem_test.go index 57fd65054..69f148b4e 100644 --- a/mem/mem_test.go +++ b/mem/mem_test.go @@ -110,3 +110,29 @@ func TestSwapMemoryStat_String(t *testing.T) { t.Errorf("SwapMemoryStat string is invalid: %v", v) } } + +func TestSwapDevices(t *testing.T) { + v, err := SwapDevices() + skipIfNotImplementedErr(t, err) + if err != nil { + t.Fatalf("error calling SwapDevices: %v", err) + } + + t.Logf("SwapDevices() -> %+v", v) + + if len(v) == 0 { + t.Fatalf("no swap devices found. [this is expected if the host has swap disabled]") + } + + for _, device := range v { + if device.Name == "" { + t.Fatalf("deviceName not set in %+v", device) + } + if device.FreeBytes == 0 { + t.Logf("[WARNING] free-bytes is zero in %+v. This might be expected", device) + } + if device.UsedBytes == 0 { + t.Logf("[WARNING] used-bytes is zero in %+v. This might be expected", device) + } + } +} diff --git a/mem/mem_windows.go b/mem/mem_windows.go index a925faa25..7c2f3bc68 100644 --- a/mem/mem_windows.go +++ b/mem/mem_windows.go @@ -4,6 +4,8 @@ package mem import ( "context" + "sync" + "syscall" "unsafe" "github.com/shirou/gopsutil/internal/common" @@ -11,8 +13,10 @@ import ( ) var ( - procGlobalMemoryStatusEx = common.Modkernel32.NewProc("GlobalMemoryStatusEx") + procEnumPageFilesW = common.ModPsapi.NewProc("EnumPageFilesW") + procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") procGetPerformanceInfo = common.ModPsapi.NewProc("GetPerformanceInfo") + procGlobalMemoryStatusEx = common.Modkernel32.NewProc("GlobalMemoryStatusEx") ) type memoryStatusEx struct { @@ -96,3 +100,66 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { return ret, nil } + +var ( + pageSize uint64 + pageSizeOnce sync.Once +) + +type systemInfo struct { + wProcessorArchitecture uint16 + wReserved uint16 + dwPageSize uint32 + lpMinimumApplicationAddress uintptr + lpMaximumApplicationAddress uintptr + dwActiveProcessorMask uintptr + dwNumberOfProcessors uint32 + dwProcessorType uint32 + dwAllocationGranularity uint32 + wProcessorLevel uint16 + wProcessorRevision uint16 +} + +// system type as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-enum_page_file_information +type enumPageFileInformation struct { + cb uint32 + reserved uint32 + totalSize uint64 + totalInUse uint64 + peakUsage uint64 +} + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + pageSizeOnce.Do(func() { + var sysInfo systemInfo + procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&sysInfo))) + pageSize = uint64(sysInfo.dwPageSize) + }) + + // the following system call invokes the supplied callback function once for each page file before returning + // see https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumpagefilesw + var swapDevices []*SwapDevice + result, _, _ := procEnumPageFilesW.Call(windows.NewCallback(pEnumPageFileCallbackW), uintptr(unsafe.Pointer(&swapDevices))) + if result == 0 { + return nil, windows.GetLastError() + } + + return swapDevices, nil +} + +// system callback as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/nc-psapi-penum_page_file_callbackw +func pEnumPageFileCallbackW(swapDevices *[]*SwapDevice, enumPageFileInfo *enumPageFileInformation, lpFilenamePtr *[syscall.MAX_LONG_PATH]uint16) *bool { + *swapDevices = append(*swapDevices, &SwapDevice{ + Name: syscall.UTF16ToString((*lpFilenamePtr)[:]), + UsedBytes: enumPageFileInfo.totalInUse * pageSize, + FreeBytes: (enumPageFileInfo.totalSize - enumPageFileInfo.totalInUse) * pageSize, + }) + + // return true to continue enumerating page files + ret := true + return &ret +} From 7c1aa06a5eb4682ab50883dd1cf0eb5ff0ab4cf7 Mon Sep 17 00:00:00 2001 From: Punya Biswal Date: Wed, 8 Sep 2021 16:15:26 -0400 Subject: [PATCH 2/5] Respond to review comments * use LookPath for better error messages * support procfs in containers --- mem/mem_bsd.go | 6 +++++- mem/mem_linux.go | 5 ++++- mem/mem_solaris.go | 6 +++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/mem/mem_bsd.go b/mem/mem_bsd.go index 058f8fa0a..98826b06a 100644 --- a/mem/mem_bsd.go +++ b/mem/mem_bsd.go @@ -24,7 +24,11 @@ func SwapDevices() ([]*SwapDevice, error) { } func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { - output, err := exec.Command(swapCommand, "-lk").Output() + swapCommandPath, err := exec.LookPath(swapCommand) + if err != nil { + return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err) + } + output, err := exec.Command(swapCommandPath, "-lk").Output() if err != nil { return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err) } diff --git a/mem/mem_linux.go b/mem/mem_linux.go index fe653b262..8c3445a2c 100644 --- a/mem/mem_linux.go +++ b/mem/mem_linux.go @@ -14,6 +14,7 @@ import ( "strings" "github.com/shirou/gopsutil/internal/common" + "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" ) @@ -430,7 +431,7 @@ func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint6 return availMemory } -const swapsFilePath = "/proc/swaps" +const swapsFilename = "swaps" // swaps file column indexes const ( @@ -446,6 +447,7 @@ func SwapDevices() ([]*SwapDevice, error) { } func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + swapsFilePath := common.HostProc(swapsFilename) f, err := os.Open(swapsFilePath) if err != nil { return nil, err @@ -456,6 +458,7 @@ func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { } func parseSwapsFile(r io.Reader) ([]*SwapDevice, error) { + swapsFilePath := common.HostProc(swapsFilename) scanner := bufio.NewScanner(r) if !scanner.Scan() { if err := scanner.Err(); err != nil { diff --git a/mem/mem_solaris.go b/mem/mem_solaris.go index 2b161d13a..353d4cf56 100644 --- a/mem/mem_solaris.go +++ b/mem/mem_solaris.go @@ -141,7 +141,11 @@ func SwapDevices() ([]*SwapDevice, error) { } func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { - output, err := exec.Command(swapsCommand, "-l").Output() + swapsCommandPath, err := exec.LookPath(swapsCommand) + if err != nil { + return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err) + } + output, err := exec.Command(swapsCommandPath, "-l").Output() if err != nil { return nil, fmt.Errorf("could not execute %q: %w", swapsCommand, err) } From 5169bfe02d1586e2f0d92118a8967c820cada63c Mon Sep 17 00:00:00 2001 From: Punya Biswal Date: Fri, 10 Sep 2021 21:36:00 -0400 Subject: [PATCH 3/5] Update mem/mem_bsd.go Co-authored-by: shirou --- mem/mem_bsd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mem/mem_bsd.go b/mem/mem_bsd.go index 98826b06a..a82fddb36 100644 --- a/mem/mem_bsd.go +++ b/mem/mem_bsd.go @@ -10,7 +10,7 @@ import ( "strings" ) -const swapCommand = "/sbin/swapctl" +const swapCommand = "swapctl" // swapctl column indexes const ( From 32b14a3723cad8502ea6a9ae81bb277515bc04e0 Mon Sep 17 00:00:00 2001 From: Punya Biswal Date: Tue, 28 Sep 2021 10:05:33 -0400 Subject: [PATCH 4/5] Use invoke.CommandWithContext --- mem/mem_bsd.go | 2 +- mem/mem_solaris.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mem/mem_bsd.go b/mem/mem_bsd.go index a82fddb36..17ca92086 100644 --- a/mem/mem_bsd.go +++ b/mem/mem_bsd.go @@ -28,7 +28,7 @@ func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { if err != nil { return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err) } - output, err := exec.Command(swapCommandPath, "-lk").Output() + output, err := invoke.CommandWithContext(swapCommandPath, "-lk") if err != nil { return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err) } diff --git a/mem/mem_solaris.go b/mem/mem_solaris.go index 353d4cf56..0c5831409 100644 --- a/mem/mem_solaris.go +++ b/mem/mem_solaris.go @@ -145,7 +145,7 @@ func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { if err != nil { return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err) } - output, err := exec.Command(swapsCommandPath, "-l").Output() + output, err := invoke.CommandWithContext(swapsCommandPath, "-l") if err != nil { return nil, fmt.Errorf("could not execute %q: %w", swapsCommand, err) } From 582bb14d8a2cf48f0daee2da3d446fa968fc806f Mon Sep 17 00:00:00 2001 From: shirou Date: Wed, 29 Sep 2021 22:55:44 +0900 Subject: [PATCH 5/5] [linux][mem] remove unnecessary import --- mem/mem_linux.go | 1 - 1 file changed, 1 deletion(-) diff --git a/mem/mem_linux.go b/mem/mem_linux.go index 8c3445a2c..cf3c64e9c 100644 --- a/mem/mem_linux.go +++ b/mem/mem_linux.go @@ -14,7 +14,6 @@ import ( "strings" "github.com/shirou/gopsutil/internal/common" - "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" )