Skip to content

Commit

Permalink
Merge pull request #1138 from shirou/feature/v3_add_swapdevice
Browse files Browse the repository at this point in the history
[mem] Add mem.SwapDevices() to v3
  • Loading branch information
shirou committed Sep 30, 2021
2 parents 8d7a3ab + 7be7e78 commit 8d02cf8
Show file tree
Hide file tree
Showing 14 changed files with 343 additions and 11 deletions.
2 changes: 1 addition & 1 deletion process/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ func Test_Process_CreateTime(t *testing.T) {
}

gotElapsed := time.Since(time.Unix(int64(c/1000), 0))
maxElapsed := time.Duration(5 * time.Second)
maxElapsed := time.Duration(20 * time.Second)

if gotElapsed >= maxElapsed {
t.Errorf("this process has not been running for %v", gotElapsed)
Expand Down
11 changes: 11 additions & 0 deletions v3/mem/mem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
9 changes: 9 additions & 0 deletions v3/mem/mem_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"unsafe"

"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/unix"
)

Expand Down Expand Up @@ -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
}
3 changes: 1 addition & 2 deletions v3/mem/mem_darwin_cgo.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// +build darwin
// +build cgo
// +build darwin,cgo

package mem

Expand Down
3 changes: 1 addition & 2 deletions v3/mem/mem_darwin_nocgo.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// +build darwin
// +build !cgo
// +build darwin,!cgo

package mem

Expand Down
8 changes: 8 additions & 0 deletions v3/mem/mem_fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
86 changes: 86 additions & 0 deletions v3/mem/mem_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
package mem

import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"math"
"os"
"strconv"
Expand Down Expand Up @@ -426,3 +429,86 @@ func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint6

return availMemory
}

const swapsFilename = "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) {
swapsFilePath := common.HostProc(swapsFilename)
f, err := os.Open(swapsFilePath)
if err != nil {
return nil, err
}
defer f.Close()

return parseSwapsFile(f)
}

func parseSwapsFile(r io.Reader) ([]*SwapDevice, error) {
swapsFilePath := common.HostProc(swapsFilename)
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
}
43 changes: 43 additions & 0 deletions v3/mem/mem_linux_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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)
}
4 changes: 2 additions & 2 deletions v3/mem/mem_openbsd_386.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions v3/mem/mem_openbsd_arm64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 84 additions & 0 deletions v3/mem/mem_solaris.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// +build solaris

package mem

import (
Expand Down Expand Up @@ -119,3 +121,85 @@ 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) {
swapsCommandPath, err := exec.LookPath(swapsCommand)
if err != nil {
return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err)
}
output, err := invoke.CommandWithContext(swapsCommandPath, "-l")
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
}
26 changes: 26 additions & 0 deletions v3/mem/mem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

0 comments on commit 8d02cf8

Please sign in to comment.