Skip to content

Commit

Permalink
fixed process status in windows (signalfx#2063)
Browse files Browse the repository at this point in the history
  • Loading branch information
manang-splunk authored Jan 13, 2022
1 parent a088a68 commit fde9622
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 4 deletions.
56 changes: 54 additions & 2 deletions pkg/monitors/processlist/processlist_windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build windows
// +build windows

package processlist
Expand All @@ -12,6 +13,9 @@ import (
)

const (
// represents the thread is in waiting state
// ref: https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-thread
threadWaitingState = 5
processQueryLimitedInformation = 0x00001000
)

Expand All @@ -33,6 +37,13 @@ type Win32Process struct {
VirtualSize uint64
}

// Win32Thread is a WMI struct used for WMI calls
// https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-thread
type Win32Thread struct {
ThreadState uint32
ProcessHandle string
}

// PerfProcProcess is a performance process struct used for wmi calls
// https://msdn.microsoft.com/en-us/library/aa394323(v=vs.85).aspx
type PerfProcProcess struct {
Expand All @@ -53,6 +64,12 @@ var getAllProcesses = func() (ps []Win32Process, err error) {
return ps, err
}

// getAllThreads retrieves all the threads. It is set as a package variable so we can mock it during testing
var getAllThreads = func() (threads []Win32Thread, err error) {
err = wmi.Query("select ThreadState, ProcessHandle from Win32_Thread", &threads)
return threads, err
}

// getUsername - retrieves a username from an open process handle it is set as a package variable so we can mock it during testing
var getUsername = func(id uint32) (username string, err error) {
// open the process handle and collect any information that requires it
Expand Down Expand Up @@ -104,6 +121,13 @@ func ProcessList(conf *Config, cache *osCache) ([]*TopProcess, error) {
return nil, err
}

// Get all threads
threads, err := getAllThreads()
if err != nil {
return nil, err
}

processMap := mapThreadsToProcess(threads)
// iterate over each process and build an entry for the process list
for _, p := range ps {
username, err := getUsername(p.ProcessID)
Expand All @@ -126,7 +150,7 @@ func ProcessList(conf *Config, cache *osCache) ([]*TopProcess, error) {
if command == "" {
command = p.Name
}

status := statusMapping(processMap[fmt.Sprint(p.ProcessID)])
//example process "3":["root",20,"0",0,0,0,"S",0.0,0.0,"01:28.31","[ksoftirqd/0]"]
procs = append(procs, &TopProcess{
ProcessID: int(p.ProcessID),
Expand All @@ -137,11 +161,39 @@ func ProcessList(conf *Config, cache *osCache) ([]*TopProcess, error) {
VirtualMemoryBytes: p.VirtualSize,
WorkingSetSizeBytes: p.WorkingSetSize,
SharedMemBytes: 0,
Status: *p.Status,
Status: status,
MemPercent: memPercent,
TotalCPUTime: totalTime,
Command: command,
})
}
return procs, nil
}

// Mapping each thread's state to its respective process.
// for example, threadList = []Win32Thread{{ProcessHandle: "1", ThreadState: 3},
// {ProcessHandle: "2", ThreadState: 3},{ProcessHandle: "1", ThreadState: 5},{ProcessHandle: "1", ThreadState: 5},}
// it returns map[string][]uint32{"1": []uint32{3, 5, 5}, "2": []uint32{3},},
func mapThreadsToProcess(threadList []Win32Thread) map[string][]uint32 {
var processes = make(map[string][]uint32)
for _, thread := range threadList {
processes[thread.ProcessHandle] = append(processes[thread.ProcessHandle], thread.ThreadState)
}
return processes
}

// Returns the process status depending upon all thread's state.
// if all the threads of a process are in waiting state then it returns "S"(sleeping)
// else it returns "R"(running)
func statusMapping(threadStates []uint32) string {
if len(threadStates) == 0 {
return ""
}

for _, state := range threadStates {
if state != threadWaitingState {
return "R"
}
}
return "S"
}
114 changes: 112 additions & 2 deletions pkg/monitors/processlist/processlist_windows_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build windows
// +build windows

package processlist
Expand All @@ -18,6 +19,7 @@ func TestMonitor_Configure(t *testing.T) {
name string
m *Monitor
processes []Win32Process
threads []Win32Thread
cpuPercent map[uint32]uint64
usernames map[uint32]string
want *event.Event
Expand Down Expand Up @@ -56,6 +58,16 @@ func TestMonitor_Configure(t *testing.T) {
VirtualSize: 1900,
},
},
threads: []Win32Thread{
{
ThreadState: 3,
ProcessHandle: "1",
},
{
ThreadState: 5,
ProcessHandle: "0",
},
},
usernames: map[uint32]string{
0: "tedMosby",
1: "barneyStinson",
Expand All @@ -65,7 +77,7 @@ func TestMonitor_Configure(t *testing.T) {
Category: event.AGENT,
Dimensions: map[string]string{},
Properties: map[string]interface{}{
"message": "{\"t\":\"eJyqVjJQsopWKklN8c0vTqpU0rHQUSrNy87LL89T0jHUMdQx0FFS0jHQMzCAEEoGBlYGIJaSjpKzVUyMR2pOTn54flFOil5qRapSrI6SIci8pMSivNTK4JLMvOL8PAoMNYKYWgsIAAD//+q6LfA=\",\"v\":\"0.0.30\"}",
"message": "{\"t\":\"eJyqVjJQsopWKklN8c0vTqpU0rHQUSrNy87LL89T0jHUMdQx0FEKVtIx0DMwgBBKBgZWBiCWko6Ss1VMjEdqTk5+eH5RTopeakWqUqyOkiHIwKTEorzUyuCSzLzi/DyspgYRZ6oRxNhaQAAAAP//UTMulQ==\",\"v\":\"0.0.30\"}",
},
},
},
Expand All @@ -88,6 +100,12 @@ func TestMonitor_Configure(t *testing.T) {
VirtualSize: 1900,
},
},
threads: []Win32Thread{
{
ThreadState: 5,
ProcessHandle: "0",
},
},
usernames: map[uint32]string{
0: "ted\"bud\"Mosby",
},
Expand All @@ -96,14 +114,15 @@ func TestMonitor_Configure(t *testing.T) {
Category: event.AGENT,
Dimensions: map[string]string{},
Properties: map[string]interface{}{
"message": "{\"t\":\"eJyqVjJQsopWKklNUU8qTVH3zS9OqlTSsdBRKs3Lzssvz1PSMdQx1DHQUVLSMdAzMIAQSgYGVgYglpKOkrNVTIxHak5Ofnh+UU6KkXphaT7IML3UilSl2FpAAAAA///fVRrM\",\"v\":\"0.0.30\"}",
"message": "{\"t\":\"eJyqVjJQsopWKklNUU8qTVH3zS9OqlTSsdBRKs3Lzssvz1PSMdQx1DHQUQpW0jHQMzCAEEoGBlYGIJaSjpKzVUyMR2pOTn54flFOipF6YWk+yDS91IpUpdhaQAAAAP///QsbHw==\",\"v\":\"0.0.30\"}",
},
},
},
}
for i := range tests {
origGetAllProcesses := getAllProcesses
origGetUsername := getUsername
origGetAllThreads := getAllThreads

tt := tests[i]

Expand All @@ -118,6 +137,9 @@ func TestMonitor_Configure(t *testing.T) {
}
return username, nil
}
getAllThreads = func() ([]Win32Thread, error) {
return tt.threads, nil
}
if err := tt.m.Configure(&Config{config.MonitorConfig{IntervalSeconds: 10}}); (err != nil) != tt.wantErr {
t.Errorf("Monitor.Configure() error = %v, wantErr %v", err, tt.wantErr)
}
Expand All @@ -141,5 +163,93 @@ func TestMonitor_Configure(t *testing.T) {
})
getAllProcesses = origGetAllProcesses
getUsername = origGetUsername
getAllThreads = origGetAllThreads
}
}

func TestMapThreadsToProcess(t *testing.T) {
type args struct {
threadList []Win32Thread
}
tests := []struct {
name string
args args
want map[string][]uint32
}{
{
name: "check correct mapping",
args: args{
threadList: []Win32Thread{
{ProcessHandle: "2", ThreadState: 3},
},
},
want: map[string][]uint32{
"2": {3},
},
},
{
name: "check correct mapping 2",
args: args{
threadList: []Win32Thread{
{ProcessHandle: "1", ThreadState: 3},
{ProcessHandle: "2", ThreadState: 3},
{ProcessHandle: "1", ThreadState: 5},
{ProcessHandle: "1", ThreadState: 5},
},
},
want: map[string][]uint32{
"1": {3, 5, 5},
"2": {3},
},
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
if got := mapThreadsToProcess(tt.args.threadList); !reflect.DeepEqual(got, tt.want) {
t.Errorf("mapThreadsToProcess() = %v, want %v", got, tt.want)
}
})
}
}

func TestStatusMapping(t *testing.T) {
type args struct {
threadStates []uint32
}
tests := []struct {
name string
args args
want string
}{
{
name: "running process",
args: args{
threadStates: []uint32{5, 5, 5, 5, 2, 5},
},
want: "R",
},
{
name: "waiting process",
args: args{
threadStates: []uint32{5, 5, 5, 5, 5, 5},
},
want: "S",
},
{
name: "empty list",
args: args{
threadStates: []uint32{},
},
want: "",
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
if got := statusMapping(tt.args.threadStates); got != tt.want {
t.Errorf("statusMapping() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit fde9622

Please sign in to comment.