diff --git a/internal/common/common_windows.go b/internal/common/common_windows.go index 0064b9e..0a7aff9 100644 --- a/internal/common/common_windows.go +++ b/internal/common/common_windows.go @@ -142,44 +142,49 @@ func GetCounterValue(counter windows.Handle) (float64, error) { 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 +type Win32PerformanceCounter struct { + PostName string + CounterName string + Query windows.Handle + Counter windows.Handle } -// 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() +func NewWin32PerformanceCounter(postName, counterName string) (*Win32PerformanceCounter, error) { + query, err := CreateQuery() if err != nil { return nil, err } - - counter, err := CreateCounter(g.query, "processor_queue_length", `\System\Processor Queue Length`) - if err != nil { + var counter = Win32PerformanceCounter{ + Query: query, + PostName: postName, + CounterName: counterName, + } + r, _, err := PdhAddCounter.Call( + uintptr(counter.Query), + uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(counter.CounterName))), + 0, + uintptr(unsafe.Pointer(&counter.Counter)), + ) + if r != 0 { return nil, err } - g.counter = counter - return g, nil + return &counter, nil } -// Generate XXX -// adapted from https://github.com/mackerelio/mackerel-agent/ -func (g *ProcessorQueueLengthGenerator) Generate() (float64, error) { - r, _, err := PdhCollectQueryData.Call(uintptr(g.query)) +func (w *Win32PerformanceCounter) GetValue() (float64, error) { + r, _, err := PdhCollectQueryData.Call(uintptr(w.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, fmt.Errorf("%w: this counter has not data", err) } return 0.0, err } - return GetCounterValue(g.counter.Counter) + return GetCounterValue(w.Counter) +} + +func ProcessorQueueLengthCounter() (*Win32PerformanceCounter, error) { + return NewWin32PerformanceCounter("processor_queue_length", `\System\Processor Queue Length`) } // WMIQueryWithContext - wraps wmi.Query with a timed-out context to avoid hanging diff --git a/load/load_test.go b/load/load_test.go index ffa7887..72f1fc9 100644 --- a/load/load_test.go +++ b/load/load_test.go @@ -7,7 +7,7 @@ import ( "github.com/shirou/gopsutil/internal/common" ) -func skipIfNotImplementedErr(t *testing.T, err error) { +func skipIfNotImplementedErr(t testing.TB, err error) { if err == common.ErrNotImplementedError { t.Skip("not implemented") } @@ -27,23 +27,6 @@ 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, @@ -84,3 +67,28 @@ func TestMiscStatString(t *testing.T) { } t.Log(e) } + +func BenchmarkLoad(b *testing.B) { + + loadAvg := func(t testing.TB) { + v, err := Avg() + skipIfNotImplementedErr(t, err) + if err != nil { + t.Errorf("error %v", err) + } + empty := &AvgStat{} + if v == empty { + t.Errorf("error load: %v", v) + } + } + + b.Run("FirstCall", func(b *testing.B) { + loadAvg(b) + }) + + b.Run("SubsequentCalls", func(b *testing.B) { + for i := 0; i < b.N; i++ { + loadAvg(b) + } + }) +} diff --git a/load/load_windows.go b/load/load_windows.go index fc70ece..7ab6200 100644 --- a/load/load_windows.go +++ b/load/load_windows.go @@ -4,9 +4,11 @@ package load import ( "context" + "log" "sync" "time" + "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/internal/common" ) @@ -31,30 +33,51 @@ func loadAvgGoroutine() { ) var ( - interval = 5 * time.Second + sleepInterval time.Duration = 5 * time.Second + currentLoad float64 ) - generator, err := common.NewProcessorQueueLengthGenerator() - if err != nil { - loadAvgMutex.Lock() - loadErr = err + counter, err := common.ProcessorQueueLengthCounter() + loadErr = err + if err != nil || counter == nil { loadAvgMutex.Unlock() + log.Println("unexpected processor queue length counter error, please file an issue on github") return } + for { - time.Sleep(interval) - if generator == nil { - return + currentLoad, loadErr = counter.GetValue() + if loadErr != nil { + goto SKIP } - 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) + // comment following block if you want load to be 0 as long as process queue is zero + { + if currentLoad == 0.0 { + percent, err := cpu.Percent(0, false) + if err == nil { + currentLoad = percent[0] / 100 + // load averages are also given some amount of the currentLoad + // maybe they shouldnt? + if loadAvg1M == 0 { + loadAvg1M = currentLoad + } + if loadAvg5M == 0 { + loadAvg5M = currentLoad / 2 + } + if loadAvg15M == 0 { + loadAvg15M = currentLoad / 3 + } + } + } } + loadAvg1M = loadAvg1M*loadAvgFactor1F + currentLoad*(1.0-loadAvgFactor1F) + loadAvg5M = loadAvg5M*loadAvgFactor5F + currentLoad*(1.0-loadAvgFactor5F) + loadAvg15M = loadAvg15M*loadAvgFactor15F + currentLoad*(1.0-loadAvgFactor15F) + + SKIP: loadAvgMutex.Unlock() + time.Sleep(sleepInterval) + loadAvgMutex.Lock() } } @@ -63,7 +86,10 @@ func Avg() (*AvgStat, error) { } func AvgWithContext(ctx context.Context) (*AvgStat, error) { - loadAvgGoroutineOnce.Do(func() { go loadAvgGoroutine() }) + loadAvgGoroutineOnce.Do(func() { + loadAvgMutex.Lock() + go loadAvgGoroutine() + }) loadAvgMutex.RLock() defer loadAvgMutex.RUnlock() ret := AvgStat{