-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
windows: fix inefficient gathering of task processes
- Loading branch information
Showing
8 changed files
with
185 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:bug | ||
windows: Fixed a regression where scanning task processes was inefficient | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
//go:build windows | ||
|
||
package procstats | ||
|
||
import ( | ||
"github.com/hashicorp/go-set/v2" | ||
"github.com/mitchellh/go-ps" | ||
) | ||
|
||
func gather(procs map[int]ps.Process, family set.Collection[int], root int, candidate ps.Process) bool { | ||
if candidate == nil { | ||
return false | ||
} | ||
pid := candidate.Pid() | ||
if pid == 0 || pid == 1 { | ||
return false | ||
} | ||
if pid == root { | ||
return true | ||
} | ||
parent := procs[candidate.PPid()] | ||
result := gather(procs, family, root, parent) | ||
if result { | ||
family.Insert(pid) | ||
} | ||
return result | ||
} | ||
|
||
func mapping(all []ps.Process) map[int]ps.Process { | ||
result := make(map[int]ps.Process) | ||
for _, process := range all { | ||
result[process.Pid()] = process | ||
} | ||
return result | ||
} | ||
|
||
func list(executorPID int, processes func() ([]ps.Process, error)) set.Collection[ProcessID] { | ||
family := set.From([]int{executorPID}) | ||
|
||
all, err := processes() | ||
if err != nil { | ||
return set.New[ProcessID](0) | ||
} | ||
|
||
m := mapping(all) | ||
for _, candidate := range all { | ||
gather(m, family, executorPID, candidate) | ||
} | ||
|
||
return family | ||
} | ||
|
||
// List will scan the process table and return a set of the process family | ||
// tree starting with executorPID as the root. | ||
// | ||
// The implementation here specifically avoids using more than one system | ||
// call. Unlike on Linux where we just read a cgroup, on Windows we must build | ||
// the tree manually. We do so knowing only the child->parent relationships. | ||
// | ||
// So this turns into a fun leet code problem, where we invert the tree using | ||
// only a bucket of edges pointing in the wrong direction. Basically we just | ||
// iterate every process, recursively follow its parent, and determine whether | ||
// executorPID is an ancestor. | ||
// | ||
// See https://github.com/hashicorp/nomad/issues/20042 as an example of what | ||
// happens when you use syscalls to work your way from the root down to its | ||
// descendants. | ||
func List(executorPID int) set.Collection[ProcessID] { | ||
return list(executorPID, ps.Processes) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
//go:build windows | ||
|
||
package procstats | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/mitchellh/go-ps" | ||
"github.com/shoenig/test/must" | ||
) | ||
|
||
type mockProcess struct { | ||
pid int | ||
ppid int | ||
} | ||
|
||
func (p *mockProcess) Pid() int { | ||
return p.pid | ||
} | ||
|
||
func (p *mockProcess) PPid() int { | ||
return p.ppid | ||
} | ||
|
||
func (p *mockProcess) Executable() string { | ||
return "" | ||
} | ||
|
||
func mockProc(pid, ppid int) *mockProcess { | ||
return &mockProcess{pid: pid, ppid: ppid} | ||
} | ||
|
||
var ( | ||
executorOnly = []ps.Process{ | ||
mockProc(1, 1), | ||
mockProc(42, 1), | ||
} | ||
|
||
simpleLine = []ps.Process{ | ||
mockProc(1, 1), | ||
mockProc(50, 42), | ||
mockProc(42, 1), | ||
mockProc(51, 50), | ||
mockProc(101, 100), | ||
mockProc(60, 51), | ||
mockProc(100, 1), | ||
} | ||
|
||
bigTree = []ps.Process{ | ||
mockProc(1, 1), | ||
mockProc(25, 50), | ||
mockProc(100, 1), | ||
mockProc(75, 50), | ||
mockProc(10, 25), | ||
mockProc(80, 75), | ||
mockProc(81, 75), | ||
mockProc(51, 50), | ||
mockProc(42, 1), | ||
mockProc(101, 100), | ||
mockProc(52, 51), | ||
mockProc(50, 42), | ||
} | ||
) | ||
|
||
func Test_list(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
procs []ps.Process | ||
exp []ProcessID | ||
}{ | ||
{ | ||
name: "executor only", | ||
procs: executorOnly, | ||
exp: []ProcessID{42}, | ||
}, | ||
{ | ||
name: "simple line", | ||
procs: simpleLine, | ||
exp: []ProcessID{42, 50, 51, 60}, | ||
}, | ||
{ | ||
name: "big tree", | ||
procs: bigTree, | ||
exp: []ProcessID{42, 50, 25, 75, 10, 80, 81, 51, 52}, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
const executorPID = 42 | ||
t.Run(tc.name, func(t *testing.T) { | ||
lister := func() ([]ps.Process, error) { | ||
return tc.procs, nil | ||
} | ||
result := list(executorPID, lister) | ||
must.SliceContainsAll(t, tc.exp, result.Slice(), | ||
must.Sprintf("exp: %v; got: %v", tc.exp, result), | ||
) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters