diff --git a/internal/common/common_windows.go b/internal/common/common_windows.go index 9bc05de..0064b9e 100644 --- a/internal/common/common_windows.go +++ b/internal/common/common_windows.go @@ -4,6 +4,7 @@ package common import ( "context" + "fmt" "path/filepath" "strings" "syscall" @@ -69,13 +70,13 @@ var ( ProcNtWow64QueryInformationProcess64 = ModNt.NewProc("NtWow64QueryInformationProcess64") ProcNtWow64ReadVirtualMemory64 = ModNt.NewProc("NtWow64ReadVirtualMemory64") - PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery") - PdhAddCounter = ModPdh.NewProc("PdhAddCounterW") - PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData") - PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue") - PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery") + PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery") + PdhAddCounter = ModPdh.NewProc("PdhAddCounterW") + PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData") + PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue") + PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery") - procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW") + procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW") ) type FILETIME struct { @@ -93,7 +94,7 @@ func BytePtrToString(p *uint8) string { return string(a[:i]) } -// CounterInfo +// CounterInfo XXX // copied from https://github.com/mackerelio/mackerel-agent/ type CounterInfo struct { PostName string @@ -130,6 +131,57 @@ func CreateCounter(query windows.Handle, pname, cname string) (*CounterInfo, err }, nil } +// GetCounterValue get counter value from handle +// adapted from https://github.com/mackerelio/mackerel-agent/ +func GetCounterValue(counter windows.Handle) (float64, error) { + var value PDH_FMT_COUNTERVALUE_DOUBLE + r, _, err := PdhGetFormattedCounterValue.Call(uintptr(counter), PDH_FMT_DOUBLE, uintptr(0), uintptr(unsafe.Pointer(&value))) + if r != 0 && r != PDH_INVALID_DATA { + return 0.0, err + } + return value.DoubleValue, nil +} + +// ProcessorQueueLengthGenerator is struct of windows api +// adapted from https://github.com/mackerelio/mackerel-agent/ +type ProcessorQueueLengthGenerator struct { + query windows.Handle + counter *CounterInfo +} + +// NewProcessorQueueLengthGenerator is set up windows api +// adapted from https://github.com/mackerelio/mackerel-agent/ +func NewProcessorQueueLengthGenerator() (*ProcessorQueueLengthGenerator, error) { + g := &ProcessorQueueLengthGenerator{0, nil} + + var err error + g.query, err = CreateQuery() + if err != nil { + return nil, err + } + + counter, err := CreateCounter(g.query, "processor_queue_length", `\System\Processor Queue Length`) + if err != nil { + return nil, err + } + g.counter = counter + return g, nil +} + +// Generate XXX +// adapted from https://github.com/mackerelio/mackerel-agent/ +func (g *ProcessorQueueLengthGenerator) Generate() (float64, error) { + r, _, err := PdhCollectQueryData.Call(uintptr(g.query)) + if r != 0 && err != nil { + if r == PDH_NO_DATA { + return 0.0, fmt.Errorf("%w: this metric has not data", err) + } + return 0.0, err + } + + return GetCounterValue(g.counter.Counter) +} + // WMIQueryWithContext - wraps wmi.Query with a timed-out context to avoid hanging func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, connectServerArgs ...interface{}) error { if _, ok := ctx.Deadline(); !ok { diff --git a/load/load_test.go b/load/load_test.go index 9c8735d..ffa7887 100644 --- a/load/load_test.go +++ b/load/load_test.go @@ -27,6 +27,23 @@ func TestLoad(t *testing.T) { t.Log(v) } +// Commented out to not to slow down CI +// Do conduct heavy cpu load on he computer to observe change +// func TestLoadWithInterval(t *testing.T) { +// interval := 5 +// iteration := 110 / interval + +// for i := 0; i < iteration; i++ { +// v, err := Avg() +// skipIfNotImplementedErr(t, err) +// if err != nil { +// t.Errorf("error %v", err) +// } +// t.Log(v) +// time.Sleep(time.Duration(interval) * time.Second) +// } +// } + func TestLoadAvgStat_String(t *testing.T) { v := AvgStat{ Load1: 10.1, diff --git a/load/load_windows.go b/load/load_windows.go index 42968b3..fc70ece 100644 --- a/load/load_windows.go +++ b/load/load_windows.go @@ -4,18 +4,75 @@ package load import ( "context" + "sync" + "time" "github.com/shirou/gopsutil/internal/common" ) +var ( + loadErr error + loadAvg1M float64 = 0.0 + loadAvg5M float64 = 0.0 + loadAvg15M float64 = 0.0 + loadAvgMutex sync.RWMutex + loadAvgGoroutineOnce sync.Once +) + +// loadAvgGoroutine updates avg data by fetching current load by interval +// TODO register callback rather than this +// see https://psutil.readthedocs.io/en/latest/#psutil.getloadavg +// code https://github.com/giampaolo/psutil/blob/master/psutil/arch/windows/wmi.c +func loadAvgGoroutine() { + const ( + loadAvgFactor1F = 0.9200444146293232478931553241 + loadAvgFactor5F = 0.9834714538216174894737477501 + loadAvgFactor15F = 0.9944598480048967508795473394 + ) + + var ( + interval = 5 * time.Second + ) + + generator, err := common.NewProcessorQueueLengthGenerator() + if err != nil { + loadAvgMutex.Lock() + loadErr = err + loadAvgMutex.Unlock() + return + } + for { + time.Sleep(interval) + if generator == nil { + return + } + currentLoad, err := generator.Generate() + loadAvgMutex.Lock() + loadErr = err + if err == nil { + loadAvg1M = loadAvg1M*loadAvgFactor1F + currentLoad*(1.0-loadAvgFactor1F) + loadAvg5M = loadAvg5M*loadAvgFactor5F + currentLoad*(1.0-loadAvgFactor5F) + loadAvg15M = loadAvg15M*loadAvgFactor15F + currentLoad*(1.0-loadAvgFactor15F) + } + loadAvgMutex.Unlock() + } +} + func Avg() (*AvgStat, error) { return AvgWithContext(context.Background()) } func AvgWithContext(ctx context.Context) (*AvgStat, error) { - ret := AvgStat{} + loadAvgGoroutineOnce.Do(func() { go loadAvgGoroutine() }) + loadAvgMutex.RLock() + defer loadAvgMutex.RUnlock() + ret := AvgStat{ + Load1: loadAvg1M, + Load5: loadAvg5M, + Load15: loadAvg15M, + } - return &ret, common.ErrNotImplementedError + return &ret, loadErr } func Misc() (*MiscStat, error) {