├── LICENSE ├── README.md ├── doc.go ├── example_test.go ├── go.mod ├── memory_bsd.go ├── memory_darwin.go ├── memory_linux.go ├── memory_test.go ├── memory_windows.go ├── memsysctl.go └── stub.go /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Jeremy Jay 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memory 2 | 3 | Package `memory` provides two methods reporting total physical system memory 4 | accessible to the kernel, and free memory available to the running application. 5 | 6 | This package has no external dependency besides the standard library and default operating system tools. 7 | 8 | Documentation: 9 | [![GoDoc](https://godoc.org/github.com/pbnjay/memory?status.svg)](https://godoc.org/github.com/pbnjay/memory) 10 | 11 | This is useful for dynamic code to minimize thrashing and other contention, similar to the stdlib `runtime.NumCPU` 12 | See some history of the proposal at https://github.com/golang/go/issues/21816 13 | 14 | 15 | ## Example 16 | 17 | ```go 18 | fmt.Printf("Total system memory: %d\n", memory.TotalMemory()) 19 | fmt.Printf("Free memory: %d\n", memory.FreeMemory()) 20 | ``` 21 | 22 | 23 | ## Testing 24 | 25 | Tested/working on: 26 | - macOS 10.12.6 (16G29), 10.15.7 (19H2) 27 | - Windows 10 1511 (10586.1045) 28 | - Linux RHEL (3.10.0-327.3.1.el7.x86_64) 29 | - Raspberry Pi 3 (ARMv8) on Raspbian, ODROID-C1+ (ARMv7) on Ubuntu, C.H.I.P 30 | (ARMv7). 31 | - Amazon Linux 2 aarch64 (m6a.large, 4.14.203-156.332.amzn2.aarch64) 32 | 33 | Tested on virtual machines: 34 | - Windows 7 SP1 386 35 | - Debian stretch 386 36 | - NetBSD 7.1 amd64 + 386 37 | - OpenBSD 6.1 amd64 + 386 38 | - FreeBSD 11.1 amd64 + 386 39 | - DragonFly BSD 4.8.1 amd64 40 | 41 | If you have access to untested systems please test and report success/bugs. 42 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package memory provides a single method reporting total system memory 2 | // accessible to the kernel. 3 | package memory 4 | 5 | // TotalMemory returns the total accessible system memory in bytes. 6 | // 7 | // The total accessible memory is installed physical memory size minus reserved 8 | // areas for the kernel and hardware, if such reservations are reported by 9 | // the operating system. 10 | // 11 | // If accessible memory size could not be determined, then 0 is returned. 12 | func TotalMemory() uint64 { 13 | return sysTotalMemory() 14 | } 15 | 16 | // FreeMemory returns the total free system memory in bytes. 17 | // 18 | // The total free memory is installed physical memory size minus reserved 19 | // areas for other applications running on the same system. 20 | // 21 | // If free memory size could not be determined, then 0 is returned. 22 | func FreeMemory() uint64 { 23 | return sysFreeMemory() 24 | } 25 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package memory_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pbnjay/memory" 7 | ) 8 | 9 | func ExampleTotalMemory() { 10 | fmt.Printf("Total system memory: %d\n", memory.TotalMemory()) 11 | } 12 | func ExampleFreeMemory() { 13 | fmt.Printf("Free system memory: %d\n", memory.FreeMemory()) 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pbnjay/memory 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /memory_bsd.go: -------------------------------------------------------------------------------- 1 | // +build freebsd openbsd dragonfly netbsd 2 | 3 | package memory 4 | 5 | func sysTotalMemory() uint64 { 6 | s, err := sysctlUint64("hw.physmem") 7 | if err != nil { 8 | return 0 9 | } 10 | return s 11 | } 12 | 13 | func sysFreeMemory() uint64 { 14 | s, err := sysctlUint64("hw.usermem") 15 | if err != nil { 16 | return 0 17 | } 18 | return s 19 | } 20 | -------------------------------------------------------------------------------- /memory_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package memory 4 | 5 | import ( 6 | "os/exec" 7 | "regexp" 8 | "strconv" 9 | ) 10 | 11 | func sysTotalMemory() uint64 { 12 | s, err := sysctlUint64("hw.memsize") 13 | if err != nil { 14 | return 0 15 | } 16 | return s 17 | } 18 | 19 | func sysFreeMemory() uint64 { 20 | cmd := exec.Command("vm_stat") 21 | outBytes, err := cmd.Output() 22 | if err != nil { 23 | return 0 24 | } 25 | 26 | rePageSize := regexp.MustCompile("page size of ([0-9]*) bytes") 27 | reFreePages := regexp.MustCompile("Pages free: *([0-9]*)\\.") 28 | 29 | // default: page size of 4096 bytes 30 | matches := rePageSize.FindSubmatchIndex(outBytes) 31 | pageSize := uint64(4096) 32 | if len(matches) == 4 { 33 | pageSize, err = strconv.ParseUint(string(outBytes[matches[2]:matches[3]]), 10, 64) 34 | if err != nil { 35 | return 0 36 | } 37 | } 38 | 39 | // ex: Pages free: 1126961. 40 | matches = reFreePages.FindSubmatchIndex(outBytes) 41 | freePages := uint64(0) 42 | if len(matches) == 4 { 43 | freePages, err = strconv.ParseUint(string(outBytes[matches[2]:matches[3]]), 10, 64) 44 | if err != nil { 45 | return 0 46 | } 47 | } 48 | return freePages * pageSize 49 | } 50 | -------------------------------------------------------------------------------- /memory_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package memory 4 | 5 | import "syscall" 6 | 7 | func sysTotalMemory() uint64 { 8 | in := &syscall.Sysinfo_t{} 9 | err := syscall.Sysinfo(in) 10 | if err != nil { 11 | return 0 12 | } 13 | // If this is a 32-bit system, then these fields are 14 | // uint32 instead of uint64. 15 | // So we always convert to uint64 to match signature. 16 | return uint64(in.Totalram) * uint64(in.Unit) 17 | } 18 | 19 | func sysFreeMemory() uint64 { 20 | in := &syscall.Sysinfo_t{} 21 | err := syscall.Sysinfo(in) 22 | if err != nil { 23 | return 0 24 | } 25 | // If this is a 32-bit system, then these fields are 26 | // uint32 instead of uint64. 27 | // So we always convert to uint64 to match signature. 28 | return uint64(in.Freeram) * uint64(in.Unit) 29 | } 30 | -------------------------------------------------------------------------------- /memory_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import "testing" 4 | 5 | func TestNonZero(t *testing.T) { 6 | if TotalMemory() == 0 { 7 | t.Fatal("TotalMemory returned 0") 8 | } 9 | if FreeMemory() == 0 { 10 | t.Fatal("FreeMemory returned 0") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /memory_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package memory 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | // omitting a few fields for brevity... 11 | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366589(v=vs.85).aspx 12 | type memStatusEx struct { 13 | dwLength uint32 14 | dwMemoryLoad uint32 15 | ullTotalPhys uint64 16 | ullAvailPhys uint64 17 | unused [5]uint64 18 | } 19 | 20 | func sysTotalMemory() uint64 { 21 | kernel32, err := syscall.LoadDLL("kernel32.dll") 22 | if err != nil { 23 | return 0 24 | } 25 | // GetPhysicallyInstalledSystemMemory is simpler, but broken on 26 | // older versions of windows (and uses this under the hood anyway). 27 | globalMemoryStatusEx, err := kernel32.FindProc("GlobalMemoryStatusEx") 28 | if err != nil { 29 | return 0 30 | } 31 | msx := &memStatusEx{ 32 | dwLength: 64, 33 | } 34 | r, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx))) 35 | if r == 0 { 36 | return 0 37 | } 38 | return msx.ullTotalPhys 39 | } 40 | 41 | func sysFreeMemory() uint64 { 42 | kernel32, err := syscall.LoadDLL("kernel32.dll") 43 | if err != nil { 44 | return 0 45 | } 46 | // GetPhysicallyInstalledSystemMemory is simpler, but broken on 47 | // older versions of windows (and uses this under the hood anyway). 48 | globalMemoryStatusEx, err := kernel32.FindProc("GlobalMemoryStatusEx") 49 | if err != nil { 50 | return 0 51 | } 52 | msx := &memStatusEx{ 53 | dwLength: 64, 54 | } 55 | r, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx))) 56 | if r == 0 { 57 | return 0 58 | } 59 | return msx.ullAvailPhys 60 | } 61 | -------------------------------------------------------------------------------- /memsysctl.go: -------------------------------------------------------------------------------- 1 | // +build darwin freebsd openbsd dragonfly netbsd 2 | 3 | package memory 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | func sysctlUint64(name string) (uint64, error) { 11 | s, err := syscall.Sysctl(name) 12 | if err != nil { 13 | return 0, err 14 | } 15 | // hack because the string conversion above drops a \0 16 | b := []byte(s) 17 | if len(b) < 8 { 18 | b = append(b, 0) 19 | } 20 | return *(*uint64)(unsafe.Pointer(&b[0])), nil 21 | } 22 | -------------------------------------------------------------------------------- /stub.go: -------------------------------------------------------------------------------- 1 | // +build !linux,!darwin,!windows,!freebsd,!dragonfly,!netbsd,!openbsd 2 | 3 | package memory 4 | 5 | func sysTotalMemory() uint64 { 6 | return 0 7 | } 8 | func sysFreeMemory() uint64 { 9 | return 0 10 | } 11 | --------------------------------------------------------------------------------