diff --git a/go.mod b/go.mod index eeca566..444d5b6 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 271dfd5..2f1f6fe 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/linux/sysinfo.go b/linux/sysinfo.go new file mode 100644 index 0000000..5be1005 --- /dev/null +++ b/linux/sysinfo.go @@ -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 +} diff --git a/linux/sysinfo_test.go b/linux/sysinfo_test.go new file mode 100644 index 0000000..15a04fe --- /dev/null +++ b/linux/sysinfo_test.go @@ -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)) +} diff --git a/linux/uname.go b/linux/uname.go index 9c06e89..ded49de 100644 --- a/linux/uname.go +++ b/linux/uname.go @@ -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 {