fix(windows): use GlobalMemoryStatusEx for memory

resolves #3272
resolves #3514
This commit is contained in:
Jan De Dobbeleer 2023-02-19 16:24:52 +01:00 committed by Jan De Dobbeleer
parent 9d6f2d938b
commit 23148ea823
6 changed files with 175 additions and 71 deletions

View file

@ -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)
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}
}