From 878eb66a04f3b45941c6d1bb6577051ee3bf7971 Mon Sep 17 00:00:00 2001 From: elfrucool Date: Tue, 4 Oct 2022 18:15:20 -0500 Subject: [PATCH] [issue-1357] remove procstats package Types and functions from procstats package are moved to internal/common package --- internal/common/common_windows.go | 212 ++++++++++++++++++++++++++++++++++++ load/load_windows.go | 3 +- procstats/procstats_windows.go | 223 -------------------------------------- 3 files changed, 213 insertions(+), 225 deletions(-) delete mode 100644 procstats/procstats_windows.go diff --git a/internal/common/common_windows.go b/internal/common/common_windows.go index 295b70b..c98e59c 100644 --- a/internal/common/common_windows.go +++ b/internal/common/common_windows.go @@ -299,3 +299,215 @@ func NtQuerySystemInformation( uintptr(unsafe.Pointer(ReturnLength))) return NtStatus(r0) } + +// represents a win32 thread status +// see for possible values +type THREAD_STATE uint32 + +const ( + StateInitialized THREAD_STATE = iota + StateReady + StateRunning + StateStandby + StateTerminated + StateWait + StateTransition + StateUnknown +) + +func (s THREAD_STATE) String() string { + switch s { + case StateInitialized: + return "StateInitialized" + case StateReady: + return "StateReady" + case StateRunning: + return "StateRunning" + case StateStandby: + return "StateStandby" + case StateTerminated: + return "StateTerminated" + case StateWait: + return "StateWait" + case StateTransition: + return "StateTransition" + case StateUnknown: + return "StateUnknown" + default: + return "" + } +} + +// "BUSY" thread means a thread that is either running, transitioning to run or on a state that demans resources +// see https://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html +func (s THREAD_STATE) Busy() bool { + switch s { + case StateReady, StateRunning, StateStandby, StateTransition: + return true + default: + return false + } +} + +// SYSTEM_THREAD_INFORMATION contains thread information as it is returned by NtQuerySystemInformation() API call +// look for its structure & documentation at: +// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation +type SYSTEM_THREAD_INFORMATION struct { + Reserved1 [3]int64 + Reserved2 uint32 + StartAddress uintptr + UniqueProcess windows.Handle + UniqueThread windows.Handle + Priority int32 + BasePriority int32 + Reserved3 uint32 + ThreadState THREAD_STATE + WaitReason uint32 +} + +// SYSTEM_PROCESS_INFORMATION is a convenience struct to have first thread address at hand +// for this technique to access to heterogeneous data, see: +// https://justen.codes/breaking-all-the-rules-using-go-to-call-windows-api-2cbfd8c79724 +type SYSTEM_PROCESS_INFORMATION struct { + windows.SYSTEM_PROCESS_INFORMATION + ThreadsTable [1]SYSTEM_THREAD_INFORMATION +} + +// Stats are the stats this package offers +type Stats struct { + ProcessCount uint32 + ThreadCount uint32 + ThreadsByStatus map[THREAD_STATE]uint32 + Load uint32 // number of threads that contribute to system load, see https://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html +} + +func EmptyStats() *Stats { + return &Stats{ + ThreadsByStatus: make(map[THREAD_STATE]uint32), + } +} + +// AddProc increments process count and returns itself. +func (s *Stats) AddProc() *Stats { + s.ProcessCount += 1 + return s +} + +// AddThread increments thread count, also updates ThreadsByStatus based on the status, +// finally if the state represents a busy thread, it increments the load. +// returns the current stats structure pointer. +func (s *Stats) AddThread(state THREAD_STATE) *Stats { + s.ThreadCount += 1 + s.ThreadsByStatus[state] += 1 + + if state.Busy() { + s.Load += 1 + } + + return s +} + +// SystemProcessInformationWalk is a helper structure to walk through the raw bytes +// that NtQuerySystemInformation produces and get correct structures +type SystemProcessInformationWalk struct { + SizeInBytes uint32 // buffer size + Offset uint32 // current offset + Buffer []byte // buffer with the data +} + +// Process returns the process under current offset +func (w *SystemProcessInformationWalk) Process() *SYSTEM_PROCESS_INFORMATION { + return (*SYSTEM_PROCESS_INFORMATION)(unsafe.Pointer(&w.Buffer[w.Offset])) +} + +// Next moves offset to the next process structure +// it returns true if there are still more PENDING processess to iterate +// it returns false if there are no more PENDING processess to iterate +// calling Next() when there are no more processes, has no effect +func (w *SystemProcessInformationWalk) Next() bool { + proc := w.Process() + + if proc.NextEntryOffset == 0 || proc.NextEntryOffset+w.Offset > w.SizeInBytes { + return false // reached the end + } + + w.Offset += proc.NextEntryOffset + + return true +} + +// Stats calculate stats for all processes and their threads +func (w *SystemProcessInformationWalk) Stats() *Stats { + stats := EmptyStats() + + for { + proc := w.Process() + + stats.AddProc() + + WalkThreads(proc, func(t SYSTEM_THREAD_INFORMATION) { + stats.AddThread(t.ThreadState) + }) + + if ok := w.Next(); !ok { + break + } + } + + return stats +} + +// WalkThreads() iterates over all threads of current process and applies given function +func WalkThreads(proc *SYSTEM_PROCESS_INFORMATION, fn func(t SYSTEM_THREAD_INFORMATION)) { + for i := 0; i < int(proc.NumberOfThreads); i++ { + thread := *(*SYSTEM_THREAD_INFORMATION)(unsafe.Pointer( + uintptr(unsafe.Pointer(&proc.ThreadsTable[0])) + + uintptr(i)*unsafe.Sizeof(proc.ThreadsTable[0]), + )) + + fn(thread) + } +} + +// GetSystemProcessInformation retrieves information of all procecess and threads +// see: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation +// look for SystemProcessInformation and related structures SYSTEM_PROCESS_INFORMATION and SYSTEM_THREAD_INFORMATION +// the returned structure has methods to walk through the structure +func GetSystemProcessInformation() (*SystemProcessInformationWalk, error) { + var ( + oneKb uint32 = 1024 + allocKb uint32 = 1 + buffer []byte + usedBytes uint32 + ) + + // iterating instead of calling common.CallWithExpandingBuffer hangs forever + for { + var allocBytes uint32 = allocKb * oneKb + buffer = make([]byte, allocBytes) + + st := NtQuerySystemInformation( + windows.SystemProcessInformation, + &buffer[0], + allocBytes, + &usedBytes, + ) + + if st == NtStatus(windows.STATUS_INFO_LENGTH_MISMATCH) { + allocKb *= 2 + continue + } + + if st.IsError() { + return nil, st.Error() + } + + break + } + + return &SystemProcessInformationWalk{ + SizeInBytes: usedBytes, + Offset: 0, + Buffer: buffer, + }, nil +} diff --git a/load/load_windows.go b/load/load_windows.go index 2d7c9db..e1dec29 100644 --- a/load/load_windows.go +++ b/load/load_windows.go @@ -11,7 +11,6 @@ import ( "time" "github.com/shirou/gopsutil/v3/internal/common" - "github.com/shirou/gopsutil/v3/procstats" ) var ( @@ -39,7 +38,7 @@ func loadAvgGoroutine() { tick := time.NewTicker(samplingFrequency).C for { // calling this because common.ProcessorQueueLengthCounter() returns zero values all time - w, err := procstats.GetSystemProcessInformation() + w, err := common.GetSystemProcessInformation() if err != nil { log.Printf("gopsutil: unexpected GetSystemProcessInformation error, please file an issue on github: %v", err) } else { diff --git a/procstats/procstats_windows.go b/procstats/procstats_windows.go deleted file mode 100644 index 505ec6b..0000000 --- a/procstats/procstats_windows.go +++ /dev/null @@ -1,223 +0,0 @@ -//go:build windows -// +build windows - -package procstats - -import ( - "unsafe" - - "github.com/shirou/gopsutil/v3/internal/common" - "golang.org/x/sys/windows" -) - -// represents a win32 thread status -// see for possible values -type THREAD_STATE uint32 - -const ( - StateInitialized THREAD_STATE = iota - StateReady - StateRunning - StateStandby - StateTerminated - StateWait - StateTransition - StateUnknown -) - -func (s THREAD_STATE) String() string { - switch s { - case StateInitialized: - return "StateInitialized" - case StateReady: - return "StateReady" - case StateRunning: - return "StateRunning" - case StateStandby: - return "StateStandby" - case StateTerminated: - return "StateTerminated" - case StateWait: - return "StateWait" - case StateTransition: - return "StateTransition" - case StateUnknown: - return "StateUnknown" - default: - return "" - } -} - -// "BUSY" thread means a thread that is either running, transitioning to run or on a state that demans resources -// see https://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html -func (s THREAD_STATE) Busy() bool { - switch s { - case StateReady, StateRunning, StateStandby, StateTransition: - return true - default: - return false - } -} - -// SYSTEM_THREAD_INFORMATION contains thread information as it is returned by NtQuerySystemInformation() API call -// look for its structure & documentation at: -// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation -type SYSTEM_THREAD_INFORMATION struct { - Reserved1 [3]int64 - Reserved2 uint32 - StartAddress uintptr - UniqueProcess windows.Handle - UniqueThread windows.Handle - Priority int32 - BasePriority int32 - Reserved3 uint32 - ThreadState THREAD_STATE - WaitReason uint32 -} - -// SYSTEM_PROCESS_INFORMATION is a convenience struct to have first thread address at hand -// for this technique to access to heterogeneous data, see: -// https://justen.codes/breaking-all-the-rules-using-go-to-call-windows-api-2cbfd8c79724 -type SYSTEM_PROCESS_INFORMATION struct { - windows.SYSTEM_PROCESS_INFORMATION - ThreadsTable [1]SYSTEM_THREAD_INFORMATION -} - -// Stats are the stats this package offers -type Stats struct { - ProcessCount uint32 - ThreadCount uint32 - ThreadsByStatus map[THREAD_STATE]uint32 - Load uint32 // number of threads that contribute to system load, see https://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html -} - -func EmptyStats() *Stats { - return &Stats{ - ThreadsByStatus: make(map[THREAD_STATE]uint32), - } -} - -// AddProc increments process count and returns itself. -func (s *Stats) AddProc() *Stats { - s.ProcessCount += 1 - return s -} - -// AddThread increments thread count, also updates ThreadsByStatus based on the status, -// finally if the state represents a busy thread, it increments the load. -// returns the current stats structure pointer. -func (s *Stats) AddThread(state THREAD_STATE) *Stats { - s.ThreadCount += 1 - s.ThreadsByStatus[state] += 1 - - if state.Busy() { - s.Load += 1 - } - - return s -} - -// SystemProcessInformationWalk is a helper structure to walk through the raw bytes -// that NtQuerySystemInformation produces and get correct structures -type SystemProcessInformationWalk struct { - SizeInBytes uint32 // buffer size - Offset uint32 // current offset - Buffer []byte // buffer with the data -} - -// Process returns the process under current offset -func (w *SystemProcessInformationWalk) Process() *SYSTEM_PROCESS_INFORMATION { - return (*SYSTEM_PROCESS_INFORMATION)(unsafe.Pointer(&w.Buffer[w.Offset])) -} - -// Next moves offset to the next process structure -// it returns true if there are still more PENDING processess to iterate -// it returns false if there are no more PENDING processess to iterate -// calling Next() when there are no more processes, has no effect -func (w *SystemProcessInformationWalk) Next() bool { - proc := w.Process() - - if proc.NextEntryOffset == 0 || proc.NextEntryOffset+w.Offset > w.SizeInBytes { - return false // reached the end - } - - w.Offset += proc.NextEntryOffset - - return true -} - -// Stats calculate stats for all processes and their threads -func (w *SystemProcessInformationWalk) Stats() *Stats { - stats := EmptyStats() - - for { - proc := w.Process() - - stats.AddProc() - - WalkThreads(proc, func(t SYSTEM_THREAD_INFORMATION) { - stats.AddThread(t.ThreadState) - }) - - if ok := w.Next(); !ok { - break - } - } - - return stats -} - -// WalkThreads() iterates over all threads of current process and applies given function -func WalkThreads(proc *SYSTEM_PROCESS_INFORMATION, fn func(t SYSTEM_THREAD_INFORMATION)) { - for i := 0; i < int(proc.NumberOfThreads); i++ { - thread := *(*SYSTEM_THREAD_INFORMATION)(unsafe.Pointer( - uintptr(unsafe.Pointer(&proc.ThreadsTable[0])) + - uintptr(i)*unsafe.Sizeof(proc.ThreadsTable[0]), - )) - - fn(thread) - } -} - -// GetSystemProcessInformation retrieves information of all procecess and threads -// see: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation -// look for SystemProcessInformation and related structures SYSTEM_PROCESS_INFORMATION and SYSTEM_THREAD_INFORMATION -// the returned structure has methods to walk through the structure -func GetSystemProcessInformation() (*SystemProcessInformationWalk, error) { - var ( - oneKb uint32 = 1024 - allocKb uint32 = 1 - buffer []byte - usedBytes uint32 - ) - - // iterating instead of calling common.CallWithExpandingBuffer hangs forever - for { - var allocBytes uint32 = allocKb * oneKb - buffer = make([]byte, allocBytes) - - st := common.NtQuerySystemInformation( - windows.SystemProcessInformation, - &buffer[0], - allocBytes, - &usedBytes, - ) - - if st == common.NtStatus(windows.STATUS_INFO_LENGTH_MISMATCH) { - allocKb *= 2 - continue - } - - if st.IsError() { - return nil, st.Error() - } - - break - } - - return &SystemProcessInformationWalk{ - SizeInBytes: usedBytes, - Offset: 0, - Buffer: buffer, - }, nil -}