Skip to content

Commit

Permalink
adding free memory interface for #7
Browse files Browse the repository at this point in the history
untested on BSDs and windows, working on darwin and linux
  • Loading branch information
pbnjay committed Jul 28, 2021
1 parent f6148d1 commit 7b4eea6
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 4 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# memory

Package `memory` provides a single method reporting total physical system memory
accessible to the kernel. It does not account for memory used by other processes.
Package `memory` provides two methods reporting total physical system memory
accessible to the kernel, and free memory available to the running application.

This package has no external dependency beside the standard library.
This package has no external dependency besides the standard library and default operating system tools.

Documentation:
[![GoDoc](https://godoc.org/github.com/pbnjay/memory?status.svg)](https://godoc.org/github.com/pbnjay/memory)
Expand All @@ -16,6 +16,7 @@ See some history of the proposal at https://github.com/golang/go/issues/21816

```go
fmt.Printf("Total system memory: %d\n", memory.TotalMemory())
fmt.Printf("Free memory: %d\n", memory.FreeMemory())
```


Expand Down
10 changes: 10 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ package memory
func TotalMemory() uint64 {
return sysTotalMemory()
}

// FreeMemory returns the total free system memory in bytes.
//
// The total free memory is installed physical memory size minus reserved
// areas for other applications running on the same system.
//
// If free memory size could not be determined, then 0 is returned.
func FreeMemory() uint64 {
return sysFreeMemory()
}
3 changes: 3 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ import (
func ExampleTotalMemory() {
fmt.Printf("Total system memory: %d\n", memory.TotalMemory())
}
func ExampleFreeMemory() {
fmt.Printf("Free system memory: %d\n", memory.FreeMemory())
}
8 changes: 8 additions & 0 deletions memory_bsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ func sysTotalMemory() uint64 {
}
return s
}

func sysFreeMemory() uint64 {
s, err := sysctlUint64("hw.usermem")
if err != nil {
return 0
}
return s
}
38 changes: 38 additions & 0 deletions memory_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,48 @@

package memory

import (
"os/exec"
"regexp"
"strconv"
)

func sysTotalMemory() uint64 {
s, err := sysctlUint64("hw.memsize")
if err != nil {
return 0
}
return s
}

func sysFreeMemory() uint64 {
cmd := exec.Command("vm_stat")
outBytes, err := cmd.Output()
if err != nil {
return 0
}

rePageSize := regexp.MustCompile("page size of ([0-9]*) bytes")
reFreePages := regexp.MustCompile("Pages free: *([0-9]*)\\.")

// default: page size of 4096 bytes
matches := rePageSize.FindSubmatchIndex(outBytes)
pageSize := uint64(4096)
if len(matches) == 4 {
pageSize, err = strconv.ParseUint(string(outBytes[matches[2]:matches[3]]), 10, 64)
if err != nil {
return 0
}
}

// ex: Pages free: 1126961.
matches = reFreePages.FindSubmatchIndex(outBytes)
freePages := uint64(0)
if len(matches) == 4 {
freePages, err = strconv.ParseUint(string(outBytes[matches[2]:matches[3]]), 10, 64)
if err != nil {
return 0
}
}
return freePages * pageSize
}
12 changes: 12 additions & 0 deletions memory_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ func sysTotalMemory() uint64 {
// So we always convert to uint64 to match signature.
return uint64(in.Totalram) * uint64(in.Unit)
}

func sysFreeMemory() uint64 {
in := &syscall.Sysinfo_t{}
err := syscall.Sysinfo(in)
if err != nil {
return 0
}
// If this is a 32-bit system, then these fields are
// uint32 instead of uint64.
// So we always convert to uint64 to match signature.
return uint64(in.Freeram) * uint64(in.Unit)
}
3 changes: 3 additions & 0 deletions memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ func TestNonZero(t *testing.T) {
if TotalMemory() == 0 {
t.Fatal("TotalMemory returned 0")
}
if FreeMemory() == 0 {
t.Fatal("FreeMemory returned 0")
}
}
24 changes: 23 additions & 1 deletion memory_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ type memStatusEx struct {
dwLength uint32
dwMemoryLoad uint32
ullTotalPhys uint64
unused [6]uint64
ullAvailPhys uint64
unused [5]uint64
}

func sysTotalMemory() uint64 {
Expand All @@ -36,3 +37,24 @@ func sysTotalMemory() uint64 {
}
return msx.ullTotalPhys
}

func sysFreeMemory() uint64 {
kernel32, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
return 0
}
// GetPhysicallyInstalledSystemMemory is simpler, but broken on
// older versions of windows (and uses this under the hood anyway).
globalMemoryStatusEx, err := kernel32.FindProc("GlobalMemoryStatusEx")
if err != nil {
return 0
}
msx := &memStatusEx{
dwLength: 64,
}
r, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx)))
if r == 0 {
return 0
}
return msx.ullAvailPhys
}
3 changes: 3 additions & 0 deletions stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ package memory
func sysTotalMemory() uint64 {
return 0
}
func sysFreeMemory() uint64 {
return 0
}

0 comments on commit 7b4eea6

Please sign in to comment.