From 23148ea823fc56bdd79baba7a9a96560d37ae5a0 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Sun, 19 Feb 2023 16:24:52 +0100 Subject: [PATCH] fix(windows): use GlobalMemoryStatusEx for memory resolves #3272 resolves #3514 --- src/mock/environment.go | 5 +++ src/platform/shell.go | 61 +++++++++++++++++++++++++++++++++ src/platform/shell_unix.go | 22 ++++++++++++ src/platform/win32_windows.go | 33 ++++++++++++++++++ src/segments/sysinfo.go | 63 ++++------------------------------- src/segments/sysinfo_test.go | 62 ++++++++++++++++++++++++++-------- 6 files changed, 175 insertions(+), 71 deletions(-) diff --git a/src/mock/environment.go b/src/mock/environment.go index fa26c7da..f98f57ab 100644 --- a/src/mock/environment.go +++ b/src/mock/environment.go @@ -273,3 +273,8 @@ func (env *MockedEnvironment) CursorPosition() (int, int) { args := env.Called() return args.Int(0), args.Int(1) } + +func (env *MockedEnvironment) SystemInfo() (*platform.SystemInfo, error) { + args := env.Called() + return args.Get(0).(*platform.SystemInfo), args.Error(1) +} diff --git a/src/platform/shell.go b/src/platform/shell.go index add8053b..962fbdbd 100644 --- a/src/platform/shell.go +++ b/src/platform/shell.go @@ -24,6 +24,9 @@ import ( "github.com/jandedobbeleer/oh-my-posh/src/platform/cmd" "github.com/jandedobbeleer/oh-my-posh/src/regex" + cpu "github.com/shirou/gopsutil/v3/cpu" + disk "github.com/shirou/gopsutil/v3/disk" + load "github.com/shirou/gopsutil/v3/load" process "github.com/shirou/gopsutil/v3/process" ) @@ -133,6 +136,30 @@ type Connection struct { SSID string // Wi-Fi only } +type Memory struct { + PhysicalTotalMemory uint64 + PhysicalAvailableMemory uint64 + PhysicalFreeMemory uint64 + PhysicalPercentUsed float64 + SwapTotalMemory uint64 + SwapFreeMemory uint64 + SwapPercentUsed float64 +} + +type SystemInfo struct { + // mem + Memory + // cpu + Times float64 + CPU []cpu.InfoStat + // load + Load1 float64 + Load5 float64 + Load15 float64 + // disk + Disks map[string]disk.IOCountersStat +} + type SegmentsCache map[string]interface{} func (s *SegmentsCache) Contains(key string) bool { @@ -218,6 +245,7 @@ type Environment interface { LoadTemplateCache() SetPromptCount() CursorPosition() (row, col int) + SystemInfo() (*SystemInfo, error) Debug(message string) Error(err error) Trace(start time.Time, args ...string) @@ -846,6 +874,39 @@ func (env *Shell) CursorPosition() (row, col int) { return } +func (env *Shell) SystemInfo() (*SystemInfo, error) { + s := &SystemInfo{} + + mem, err := env.Memory() + if err != nil { + return nil, err + } + s.Memory = *mem + + loadStat, err := load.Avg() + if err == nil { + s.Load1 = loadStat.Load1 + s.Load5 = loadStat.Load5 + s.Load15 = loadStat.Load15 + } + + processorTimes, err := cpu.Percent(0, false) + if err == nil && len(processorTimes) > 0 { + s.Times = processorTimes[0] + } + + processors, err := cpu.Info() + if err == nil { + s.CPU = processors + } + + diskIO, err := disk.IOCounters() + if err == nil { + s.Disks = diskIO + } + return s, nil +} + func IsPathSeparator(env Environment, c uint8) bool { if c == '/' { return true diff --git a/src/platform/shell_unix.go b/src/platform/shell_unix.go index 99abaef5..74aa64f5 100644 --- a/src/platform/shell_unix.go +++ b/src/platform/shell_unix.go @@ -9,6 +9,7 @@ import ( "time" "github.com/shirou/gopsutil/v3/host" + mem "github.com/shirou/gopsutil/v3/mem" terminal "github.com/wayneashleyberry/terminal-dimensions" "golang.org/x/sys/unix" ) @@ -139,3 +140,24 @@ func (env *Shell) Connection(connectionType ConnectionType) (*Connection, error) } return nil, &NotImplemented{} } + +func (env *Shell) Memory() (*Memory, error) { + m := &Memory{} + memStat, err := mem.VirtualMemory() + if err != nil { + env.Error(err) + return nil, err + } + m.PhysicalTotalMemory = memStat.Total + m.PhysicalAvailableMemory = memStat.Available + m.PhysicalFreeMemory = memStat.Free + m.PhysicalPercentUsed = memStat.UsedPercent + swapStat, err := mem.SwapMemory() + if err != nil { + env.Error(err) + } + m.SwapTotalMemory = swapStat.Total + m.SwapFreeMemory = swapStat.Free + m.SwapPercentUsed = swapStat.UsedPercent + return m, nil +} diff --git a/src/platform/win32_windows.go b/src/platform/win32_windows.go index c382662d..e9905067 100644 --- a/src/platform/win32_windows.go +++ b/src/platform/win32_windows.go @@ -317,3 +317,36 @@ func (env *Shell) isWriteable(folder string) bool { env.Debug("no write access") return false } + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + globalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx") +) + +type memoryStatusEx struct { + Length uint32 + MemoryLoad uint32 + TotalPhys uint64 + AvailPhys uint64 + TotalPageFile uint64 + AvailPageFile uint64 + TotalVirtual uint64 + AvailVirtual uint64 + AvailExtendedVirtual uint64 +} + +func (env *Shell) Memory() (*Memory, error) { + var memStat memoryStatusEx + memStat.Length = uint32(unsafe.Sizeof(memStat)) + r0, _, err := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memStat))) + if r0 == 0 { + env.Error(err) + return nil, err + } + return &Memory{ + PhysicalTotalMemory: memStat.TotalPhys, + PhysicalFreeMemory: memStat.AvailPhys, + PhysicalAvailableMemory: memStat.AvailPhys, + PhysicalPercentUsed: float64(memStat.MemoryLoad), + }, nil +} diff --git a/src/segments/sysinfo.go b/src/segments/sysinfo.go index 31d5cb11..b9c6eec4 100644 --- a/src/segments/sysinfo.go +++ b/src/segments/sysinfo.go @@ -3,11 +3,6 @@ package segments import ( "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/properties" - - cpu "github.com/shirou/gopsutil/v3/cpu" - disk "github.com/shirou/gopsutil/v3/disk" - load "github.com/shirou/gopsutil/v3/load" - mem "github.com/shirou/gopsutil/v3/mem" ) type SystemInfo struct { @@ -15,23 +10,8 @@ type SystemInfo struct { env platform.Environment Precision int - // mem - PhysicalTotalMemory uint64 - PhysicalAvailableMemory uint64 - PhysicalFreeMemory uint64 - PhysicalPercentUsed float64 - SwapTotalMemory uint64 - SwapFreeMemory uint64 - SwapPercentUsed float64 - // cpu - Times float64 - CPU []cpu.InfoStat - // load - Load1 float64 - Load5 float64 - Load15 float64 - // disk - Disks map[string]disk.IOCountersStat + + platform.SystemInfo } const ( @@ -54,40 +34,9 @@ func (s *SystemInfo) Init(props properties.Properties, env platform.Environment) s.props = props s.env = env s.Precision = s.props.GetInt(Precision, 2) - // mem - memStat, err := mem.VirtualMemory() - if err == nil { - s.PhysicalTotalMemory = memStat.Total - s.PhysicalAvailableMemory = memStat.Available - s.PhysicalFreeMemory = memStat.Free - s.PhysicalPercentUsed = memStat.UsedPercent - } - swapStat, err := mem.SwapMemory() - if err == nil { - s.SwapTotalMemory = swapStat.Total - s.SwapFreeMemory = swapStat.Free - s.SwapPercentUsed = swapStat.UsedPercent - } - // load - loadStat, err := load.Avg() - if err == nil { - s.Load1 = loadStat.Load1 - s.Load5 = loadStat.Load5 - s.Load15 = loadStat.Load15 - } - // times - processorTimes, err := cpu.Percent(0, false) - if err == nil && len(processorTimes) > 0 { - s.Times = processorTimes[0] - } - // cpu - processors, err := cpu.Info() - if err == nil { - s.CPU = processors - } - // disk - diskIO, err := disk.IOCounters() - if err == nil { - s.Disks = diskIO + sysInfo, err := env.SystemInfo() + if err != nil { + return } + s.SystemInfo = *sysInfo } diff --git a/src/segments/sysinfo_test.go b/src/segments/sysinfo_test.go index dc97d03a..7803e64b 100644 --- a/src/segments/sysinfo_test.go +++ b/src/segments/sysinfo_test.go @@ -1,9 +1,11 @@ package segments import ( + "errors" "testing" "github.com/jandedobbeleer/oh-my-posh/src/mock" + "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/shirou/gopsutil/v3/cpu" @@ -15,53 +17,85 @@ func TestSysInfo(t *testing.T) { Case string ExpectedString string ExpectDisabled bool - SysInfo SystemInfo + SysInfo platform.SystemInfo Precision int Template string + Error error }{ + { + Case: "Error", + ExpectDisabled: true, + Error: errors.New("error"), + }, { Case: "physical mem", ExpectedString: "50", - SysInfo: SystemInfo{PhysicalPercentUsed: 50}, - Template: "{{ round .PhysicalPercentUsed .Precision }}", + SysInfo: platform.SystemInfo{ + Memory: platform.Memory{ + PhysicalPercentUsed: 50, + }, + }, + Template: "{{ round .PhysicalPercentUsed .Precision }}", }, { Case: "physical mem 2 digits", ExpectedString: "60.51", - SysInfo: SystemInfo{Precision: 2, PhysicalPercentUsed: 60.51}, - Template: "{{ round .PhysicalPercentUsed .Precision }}", + SysInfo: platform.SystemInfo{ + Memory: platform.Memory{ + PhysicalPercentUsed: 60.51, + }, + }, + Precision: 2, + Template: "{{ round .PhysicalPercentUsed .Precision }}", }, { Case: "physical meme rounded", ExpectedString: "61", - SysInfo: SystemInfo{Precision: 0, PhysicalPercentUsed: 61}, - Template: "{{ round .PhysicalPercentUsed .Precision }}", + SysInfo: platform.SystemInfo{ + Memory: platform.Memory{ + PhysicalPercentUsed: 61, + }, + }, + Template: "{{ round .PhysicalPercentUsed .Precision }}", }, { Case: "load", ExpectedString: "0.22 0.12 0", + Precision: 2, Template: "{{ round .Load1 .Precision }} {{round .Load5 .Precision }} {{round .Load15 .Precision }}", - SysInfo: SystemInfo{Precision: 2, Load1: 0.22, Load5: 0.12, Load15: 0}}, - {Case: "not enabled", ExpectDisabled: true, SysInfo: SystemInfo{PhysicalPercentUsed: 0, SwapPercentUsed: 0}}, + SysInfo: platform.SystemInfo{Load1: 0.22, Load5: 0.12, Load15: 0}, + }, + { + Case: "not enabled", + ExpectDisabled: true, + SysInfo: platform.SystemInfo{ + Memory: platform.Memory{ + PhysicalPercentUsed: 0, + SwapPercentUsed: 0, + }, + }, + }, { Case: "2 physical cpus", ExpectedString: "1200 1200", Template: "{{range $cpu := .CPU}}{{round $cpu.Mhz 2 }} {{end}}", - SysInfo: SystemInfo{CPU: []cpu.InfoStat{{Mhz: 1200}, {Mhz: 1200}}}, + SysInfo: platform.SystemInfo{CPU: []cpu.InfoStat{{Mhz: 1200}, {Mhz: 1200}}}, }, } for _, tc := range cases { env := new(mock.MockedEnvironment) - tc.SysInfo.env = env - tc.SysInfo.props = properties.Map{ + env.On("SystemInfo").Return(&tc.SysInfo, tc.Error) + sysInfo := &SystemInfo{} + props := properties.Map{ Precision: tc.Precision, } - enabled := tc.SysInfo.Enabled() + sysInfo.Init(props, env) + enabled := sysInfo.Enabled() if tc.ExpectDisabled { assert.Equal(t, false, enabled, tc.Case) } else { - assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, tc.SysInfo), tc.Case) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, sysInfo), tc.Case) } } }