mirror of
https://github.com/shirou/gopsutil.git
synced 2025-04-26 13:48:59 +08:00
Add mem.SwapDevices() method.
This commit is contained in:
parent
f86a042980
commit
84a665b712
13
mem/mem.go
13
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)
|
||||
}
|
||||
|
87
mem/mem_bsd.go
Normal file
87
mem/mem_bsd.go
Normal file
@ -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
|
||||
}
|
63
mem/mem_bsd_test.go
Normal file
63
mem/mem_bsd_test.go
Normal file
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
// +build darwin
|
||||
// +build cgo
|
||||
// +build darwin,cgo
|
||||
|
||||
package mem
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
// +build darwin
|
||||
// +build !cgo
|
||||
// +build darwin,!cgo
|
||||
|
||||
package mem
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
45
mem/mem_solaris_test.go
Normal file
45
mem/mem_solaris_test.go
Normal file
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user