Feat[linux]: sysinfo

This commit is contained in:
kayos@tcp.direct 2022-12-18 04:05:54 -08:00
parent c7eafdbc52
commit 2e58f576ff
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
5 changed files with 200 additions and 2 deletions

1
go.mod
View File

@ -3,6 +3,7 @@ module git.tcp.direct/kayos/common
go 1.19
require (
github.com/davecgh/go-spew v1.1.1
github.com/pkg/errors v0.9.1
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
inet.af/netaddr v0.0.0-20220811202034-502d2d690317

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

108
linux/sysinfo.go Normal file
View File

@ -0,0 +1,108 @@
//go:build linux
package linux
import (
"syscall"
"time"
)
/*
some interesting information on RAM, sysinfo, and /proc/meminfo
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
- https://github.com/mmalecki/procps/blob/master/proc/sysinfo.c
in the second link we see that even procps is parsing /proc/meminfo to get the RAM information
i'd really like to avoid this, and I may end up estimating the available memory myself...
the whole idea of this package is to focus on system calls and not on parsing files
for now I'll just add a note to the RAMInfo struct definition.
tldr; sysinfo is a bit incomplete due to it's lack of available memory calculation
*/
// from https://man7.org/linux/man-pages/man2/sysinfo.2.html
/*
struct sysinfo {
long uptime; // Seconds since boot
unsigned long loads[3]; // 1, 5, and 15 minute load averages
unsigned long totalram; // Total usable main memory size
unsigned long freeram; // Available memory size
unsigned long sharedram; // Amount of shared memory
unsigned long bufferram; // Memory used by buffers
unsigned long totalswap; // Total swap space size
unsigned long freeswap; // Swap space still available
unsigned short procs; // Number of current processes
unsigned long totalhigh; // Total high memory size
unsigned long freehigh; // Available high memory size
unsigned int mem_unit; // Memory unit size in bytes
char _f[20-2*sizeof(long)-sizeof(int)]; // Padding to 64 bytes
};
*/
type SystemInfo struct {
Uptime time.Duration
Loads [3]uint64
RAM RAMInfo
// Totalswap is the total amount of swap space available.
Totalswap uint64
// Freeswap is the amount of swap space still available.
Freeswap uint64
// Procs is the number of current processes.
Procs uint16
}
// RAMInfo is a struct that contains information about the running system's memory.
// Please see important notes in the struct definition.
type RAMInfo struct {
Total int64
// Free is the amount of memory that is not being used.
// This does not take into account memory that is being used for caching.
// For more information, see the comments at the top of this file.
Free int64
Used int64
Shared int64
Buffers int64
Cached int64
SwapTotal int64
SwapFree int64
// unit is the memory unit size multiple in bytes.
// It is used to calculate the above values when the struct is initialized.q
unit uint32
}
// Sysinfo returns a SystemInfo struct containing information about the running system.
// For memory, please see important notes in the RAMInfo struct definition and the top of this file.
func Sysinfo() (systemInfo *SystemInfo, err error) {
sysinf := syscall.Sysinfo_t{}
err = syscall.Sysinfo(&sysinf)
unit := uint64(sysinf.Unit)
return &SystemInfo{
Uptime: time.Duration(sysinf.Uptime) * time.Second,
Loads: [3]uint64{sysinf.Loads[0], sysinf.Loads[1], sysinf.Loads[2]},
RAM: RAMInfo{
Total: int64(sysinf.Totalram * unit),
Free: int64(sysinf.Freeram * unit),
Used: int64((sysinf.Totalram - sysinf.Freeram) * unit),
Shared: int64(sysinf.Sharedram * unit),
Buffers: int64(sysinf.Bufferram * unit),
Cached: int64((sysinf.Totalram - sysinf.Freeram - sysinf.Bufferram) * unit),
SwapTotal: int64(sysinf.Totalswap * unit),
SwapFree: int64(sysinf.Freeswap * unit),
unit: sysinf.Unit,
},
Totalswap: sysinf.Totalswap,
Freeswap: sysinf.Freeswap,
Procs: sysinf.Procs,
}, nil
}
func Uptime() (utime time.Duration, err error) {
var sysinf *SystemInfo
sysinf, err = Sysinfo()
return sysinf.Uptime, nil
}

87
linux/sysinfo_test.go Normal file
View File

@ -0,0 +1,87 @@
//go:build linux
package linux
import (
"io"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
)
func TestUptime(t *testing.T) {
f, err := os.Open("/proc/uptime")
if err != nil {
t.Fatalf("failed to open /proc/uptime with error: %v", err)
}
buf, err := io.ReadAll(f)
// calling Uptime() here to try and get the same time as the file
if err != nil {
t.Fatalf("failed to read /proc/uptime with error: %v", err)
}
t.Logf("read %d bytes from /proc/uptime: %s", len(buf), buf)
controlSeconds, err := strconv.ParseInt(string(buf[:strings.IndexByte(string(buf), '.')]), 10, 64)
if err != nil {
t.Fatalf("failed to parse /proc/uptime with error: %e", err)
}
if controlSeconds < 1 {
t.Fatalf("failed to parse /proc/uptime")
}
uptimeCtrl := time.Duration(controlSeconds) * time.Second
t.Logf("control uptime: %v", uptimeCtrl)
// ---
uptime, err := Uptime()
if err != nil {
t.Fatalf("failed to get uptime with error: %e", err)
}
if uptime < 1 {
t.Fatalf("failed to get uptime (zero value)")
}
t.Logf("uptime: %v", uptime)
matching := uptime == uptimeCtrl
// if somehow the uptime is less than the control, which was called first
// then this has failed terribly.
// If it's greater, then it's possible we are within an acceptable tolerance.
if !matching && uptime < uptimeCtrl {
t.Fatalf("uptime does not match control uptime (uptime < uptimeCtrl)!!")
}
if !matching {
t.Logf("no match, allowing for a 1 second tolerance...")
matching = uptime == uptimeCtrl+time.Second
if matching {
t.Logf("success! (uptime == uptimeCtrl+time.Second)")
}
}
if !matching {
t.Fatalf("uptime does not match control uptime: %v != %v", uptime, uptimeCtrl)
}
}
func TestSysinfo(t *testing.T) {
si, err := Sysinfo()
if err != nil {
t.Fatalf("failed to get sysinfo with error: %e", err)
}
if si == nil {
t.Fatalf("failed to get sysinfo (nil)")
}
t.Logf("sysinfo: %s", spew.Sdump(si))
}

View File

@ -49,7 +49,7 @@ const (
// GetUname uses system calls to retrieve the same values as the uname linux command
func GetUname(unameFlags string) (un string, err error) {
ub := &syscall.Utsname{}
_ = syscall.Uname(ub)
err = syscall.Uname(ub)
var targets []*[65]int8
for _, n := range unameFlags {
var flag = [1]string{string(n)}
@ -76,7 +76,7 @@ func GetUname(unameFlags string) (un string, err error) {
return "", errors.New("no valid uname targets in string")
}
var uns []string
var uns = make([]string, len(targets))
for _, target := range targets {
var sub []string
for _, r := range target {