Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add one minute load avg for monitor middleware #1530

Merged
merged 1 commit into from Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 59 additions & 0 deletions internal/gopsutil/common/common_windows.go
@@ -1,9 +1,11 @@
//go:build windows
// +build windows

package common

import (
"context"
"fmt"
"path/filepath"
"strings"
"syscall"
Expand Down Expand Up @@ -70,6 +72,7 @@ var (
ProcNtWow64ReadVirtualMemory64 = ModNt.NewProc("NtWow64ReadVirtualMemory64")

PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery")
PdhAddEnglishCounterW = ModPdh.NewProc("PdhAddEnglishCounterW")
PdhAddCounter = ModPdh.NewProc("PdhAddCounterW")
PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData")
PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue")
Expand Down Expand Up @@ -130,6 +133,62 @@ 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
}

type Win32PerformanceCounter struct {
PostName string
CounterName string
Query windows.Handle
Counter windows.Handle
}

func NewWin32PerformanceCounter(postName, counterName string) (*Win32PerformanceCounter, error) {
query, err := CreateQuery()
if err != nil {
return nil, err
}
var counter = Win32PerformanceCounter{
Query: query,
PostName: postName,
CounterName: counterName,
}
r, _, err := PdhAddEnglishCounterW.Call(
uintptr(counter.Query),
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(counter.CounterName))),
0,
uintptr(unsafe.Pointer(&counter.Counter)),
)
if r != 0 {
return nil, err
}
return &counter, nil
}

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 counter has not data", err)
}
return 0.0, err
}

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
func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, connectServerArgs ...interface{}) error {
if _, ok := ctx.Deadline(); !ok {
Expand Down
31 changes: 31 additions & 0 deletions internal/gopsutil/load/load.go
@@ -0,0 +1,31 @@
package load

import (
"encoding/json"
)

//var invoke common.Invoker = common.Invoke{}

type AvgStat struct {
Load1 float64 `json:"load1"`
Load5 float64 `json:"load5"`
Load15 float64 `json:"load15"`
}

func (l AvgStat) String() string {
s, _ := json.Marshal(l)
return string(s)
}

type MiscStat struct {
ProcsTotal int64 `json:"procsTotal"`
ProcsCreated int64 `json:"procsCreated"`
ProcsRunning int64 `json:"procsRunning"`
ProcsBlocked int64 `json:"procsBlocked"`
Ctxt int64 `json:"ctxt"`
}

func (m MiscStat) String() string {
s, _ := json.Marshal(m)
return string(s)
}
80 changes: 80 additions & 0 deletions internal/gopsutil/load/load_bsd.go
@@ -0,0 +1,80 @@
// +build freebsd openbsd

package load

import (
"context"
"os/exec"
"strings"
"unsafe"

"golang.org/x/sys/unix"
)

func Avg() (*AvgStat, error) {
return AvgWithContext(context.Background())
}

func AvgWithContext(ctx context.Context) (*AvgStat, error) {
// This SysctlRaw method borrowed from
// https://github.com/prometheus/node_exporter/blob/master/collector/loadavg_freebsd.go
type loadavg struct {
load [3]uint32
scale int
}
b, err := unix.SysctlRaw("vm.loadavg")
if err != nil {
return nil, err
}
load := *(*loadavg)(unsafe.Pointer((&b[0])))
scale := float64(load.scale)
ret := &AvgStat{
Load1: float64(load.load[0]) / scale,
Load5: float64(load.load[1]) / scale,
Load15: float64(load.load[2]) / scale,
}

return ret, nil
}

type forkstat struct {
forks int
vforks int
__tforks int
}

// Misc returns miscellaneous host-wide statistics.
// darwin use ps command to get process running/blocked count.
// Almost same as Darwin implementation, but state is different.
func Misc() (*MiscStat, error) {
return MiscWithContext(context.Background())
}

func MiscWithContext(ctx context.Context) (*MiscStat, error) {
bin, err := exec.LookPath("ps")
if err != nil {
return nil, err
}
out, err := invoke.CommandWithContext(ctx, bin, "axo", "state")
if err != nil {
return nil, err
}
lines := strings.Split(string(out), "\n")

ret := MiscStat{}
for _, l := range lines {
if strings.Contains(l, "R") {
ret.ProcsRunning++
} else if strings.Contains(l, "D") {
ret.ProcsBlocked++
}
}

f, err := getForkStat()
if err != nil {
return nil, err
}
ret.ProcsCreated = f.forks

return &ret, nil
}
71 changes: 71 additions & 0 deletions internal/gopsutil/load/load_darwin.go
@@ -0,0 +1,71 @@
// +build darwin

package load

import (
"context"
"os/exec"
"strings"
"unsafe"

"golang.org/x/sys/unix"
)

func Avg() (*AvgStat, error) {
return AvgWithContext(context.Background())
}

func AvgWithContext(ctx context.Context) (*AvgStat, error) {
// This SysctlRaw method borrowed from
// https://github.com/prometheus/node_exporter/blob/master/collector/loadavg_freebsd.go
// this implementation is common with BSDs
type loadavg struct {
load [3]uint32
scale int
}
b, err := unix.SysctlRaw("vm.loadavg")
if err != nil {
return nil, err
}
load := *(*loadavg)(unsafe.Pointer((&b[0])))
scale := float64(load.scale)
ret := &AvgStat{
Load1: float64(load.load[0]) / scale,
Load5: float64(load.load[1]) / scale,
Load15: float64(load.load[2]) / scale,
}

return ret, nil
}

// Misc returnes miscellaneous host-wide statistics.
// darwin use ps command to get process running/blocked count.
// Almost same as FreeBSD implementation, but state is different.
// U means 'Uninterruptible Sleep'.
func Misc() (*MiscStat, error) {
return MiscWithContext(context.Background())
}

func MiscWithContext(ctx context.Context) (*MiscStat, error) {
bin, err := exec.LookPath("ps")
if err != nil {
return nil, err
}
out, err := invoke.CommandWithContext(ctx, bin, "axo", "state")
if err != nil {
return nil, err
}
lines := strings.Split(string(out), "\n")

ret := MiscStat{}
for _, l := range lines {
if strings.Contains(l, "R") {
ret.ProcsRunning++
} else if strings.Contains(l, "U") {
// uninterruptible sleep == blocked
ret.ProcsBlocked++
}
}

return &ret, nil
}
25 changes: 25 additions & 0 deletions internal/gopsutil/load/load_fallback.go
@@ -0,0 +1,25 @@
// +build !darwin,!linux,!freebsd,!openbsd,!windows,!solaris

package load

import (
"context"

"github.com/shirou/gopsutil/internal/common"
)

func Avg() (*AvgStat, error) {
return AvgWithContext(context.Background())
}

func AvgWithContext(ctx context.Context) (*AvgStat, error) {
return nil, common.ErrNotImplementedError
}

func Misc() (*MiscStat, error) {
return MiscWithContext(context.Background())
}

func MiscWithContext(ctx context.Context) (*MiscStat, error) {
return nil, common.ErrNotImplementedError
}
7 changes: 7 additions & 0 deletions internal/gopsutil/load/load_freebsd.go
@@ -0,0 +1,7 @@
// +build freebsd

package load

func getForkStat() (forkstat, error) {
return forkstat{}, nil
}