Skip to content

Commit

Permalink
Merge pull request #51 from thediveo/develop
Browse files Browse the repository at this point in the history
feat: affinity; refact: SortProcessByX
  • Loading branch information
thediveo authored Jul 17, 2024
2 parents b5734ee + 2c23aea commit 11758be
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 17 deletions.
10 changes: 5 additions & 5 deletions cmd/pidtree/treevisitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package main

import (
"reflect"
"sort"
"slices"

"github.com/thediveo/lxkns/cmd/internal/tool"
"github.com/thediveo/lxkns/model"
Expand Down Expand Up @@ -74,8 +74,8 @@ func (v *TreeVisitor) Get(node reflect.Value) (
clist := []interface{}{}
if proc, ok := node.Interface().(*model.Process); ok {
pidns := proc.Namespaces[model.PIDNS]
childprocesses := model.ProcessListByPID(proc.Children)
sort.Sort(childprocesses)
childprocesses := slices.Clone(proc.Children)
slices.SortFunc(childprocesses, model.SortProcessByPID)
childpidns := map[species.NamespaceID]bool{}
for _, childproc := range childprocesses {
if childproc.Namespaces[model.PIDNS] == pidns {
Expand Down Expand Up @@ -104,8 +104,8 @@ func (v *TreeVisitor) Get(node reflect.Value) (
} else {
// The child nodes of a PID namespace tree node will be the "leader"
// (or "topmost") processes inside the PID namespace.
leaders := model.ProcessListByPID(node.Interface().(model.Namespace).Leaders())
sort.Sort(leaders)
leaders := slices.Clone(node.Interface().(model.Namespace).Leaders())
slices.SortFunc(leaders, model.SortProcessByPID)
for _, proc := range leaders {
clist = append(clist, proc)
}
Expand Down
10 changes: 0 additions & 10 deletions model/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,16 +381,6 @@ func (t ProcessTable) ProcessesByPIDs(pid ...PIDType) []*Process {
return procs
}

// ProcessListByPID is a type alias for sorting slices of *[model.Process] by
// their PIDs in numerically ascending order.
type ProcessListByPID []*Process

func (l ProcessListByPID) Len() int { return len(l) }
func (l ProcessListByPID) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l ProcessListByPID) Less(i, j int) bool {
return l[i].PID < l[j].PID
}

// newTaskFromStatline parses a task (process) status line (as read from
// /proc/[PID]/task/[TID]/status) into a Task object.
func newTaskFromStatline(procstat string, proc *Process) (task *Task) {
Expand Down
83 changes: 83 additions & 0 deletions model/process_sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2024 Harald Albrecht.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package model

import (
"os"
"strconv"
"strings"
)

// SortProcessByPID sorts processes by increasing PID numbers (no interval
// arithmetics though).
func SortProcessByPID(a, b *Process) int {
return int(a.PID) - int(b.PID)
}

// SortProcessByAgeThenPIDDistance sorts processes first by their “age”
// (starttime) and then by their PIDs, taking PID number wrap-arounds into
// consideration.
//
// As PIDs are monotonously increasing, wrapping around at “N” (which defaults
// to 1<<22 on Linux 64 bit systems), we consider a PID “B” to be after PID “A”
// if the “positive” distance from “A” to “B” (in increasing PIDs, distance
// taken modulo N) is at most N/2.
//
// For a nice write-up see also [The ryg blog: Intervals in modular arithmetic].
//
// [The ryg blog: Intervals in modular arithmetic]: https://fgiesen.wordpress.com/2015/09/24/intervals-in-modular-arithmetic/
func SortProcessByAgeThenPIDDistance(a, b *Process) int {
switch {
case a.Starttime < b.Starttime:
return -1
case a.Starttime > b.Starttime:
return 1
}
pidA := uint64(a.PID)
pidB := uint64(b.PID)
switch dist := (pidB - pidA) & pidMaxMask; {
case dist == 0:
return 0
case dist <= pidMaxDist:
return -1
default:
return 1
}
}

var pidMaxMask uint64 // N-1
var pidMaxDist uint64 // N/2

// pidWrapping reads the PID interval “N” set for this system (which must be to
// the power of two) and then returns N-1 and N/2, falling back to the specified
// default N in case the system configuration cannot be read.
func pidWrapping(defaultMax uint64) (mask, maxdist uint64) {
mask = defaultMax - 1
maxdist = defaultMax >> 1
// https://www.man7.org/linux/man-pages/man5/proc_sys_kernel.5.html
pidmaxb, err := os.ReadFile("/proc/sys/kernel/pid_max")
if err != nil {
return
}
pidmax, err := strconv.ParseUint(strings.TrimSuffix(string(pidmaxb), "\n"), 10, 32)
if err != nil {
return
}
return pidmax - 1, pidmax >> 1
}

func init() {
pidMaxMask, pidMaxDist = pidWrapping((uint64(1) << 22))
}
61 changes: 61 additions & 0 deletions model/process_sort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2024 Harald Albrecht.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package model

import (
deco "github.com/onsi/ginkgo/v2/dsl/decorators"

. "github.com/onsi/ginkgo/v2/dsl/core"
. "github.com/onsi/ginkgo/v2/dsl/table"
. "github.com/onsi/gomega"
)

var _ = Describe("Mr Wehrli sorting processes", func() {

It("detects the system's PID wrap-around", func() {
mask, dist := pidWrapping(0)
Expect(mask).NotTo(BeZero())
Expect(dist).NotTo(BeZero())
Expect((dist << 1) - 1).To(Equal(mask))
})

Context("interval arithmetic", deco.Ordered, func() {

BeforeAll(func() {
pidMaxMask, pidMaxDist = 8-1, 8>>1
DeferCleanup(func() {
pidMaxMask, pidMaxDist = pidWrapping((uint64(1) << 22))
})
})

DescribeTable("sorting by age and PID distance",
func(ageA int, pidA int, ageB int, pidB int, expect int) {
delta := SortProcessByAgeThenPIDDistance(
&Process{PID: PIDType(pidA), ProTaskCommon: ProTaskCommon{Starttime: uint64(ageA)}},
&Process{PID: PIDType(pidB), ProTaskCommon: ProTaskCommon{Starttime: uint64(ageB)}})
Expect(delta).To(Equal(expect),
"(%d-%d)&%x=%x ?? %x", pidA, pidB, pidMaxMask, (pidB-pidA)&int(pidMaxMask), pidMaxDist)
},
Entry("a older than b", 100, 1, 200, 2, -1),
Entry("a younger than b", 200, 1, 100, 2, 1),
Entry("a same age as b, PID a before PID b, nowrap", 100, 4, 100, 5, -1),
Entry("a same age as b, PID b before PID a, nowrap", 100, 5, 100, 4, 1),
Entry("a same age as b, PID a before PID b, wrap", 100, 7, 100, 1, -1),
Entry("a same age as b, PID b before PID a, wrap", 100, 1, 100, 7, 1),
)

})

})
4 changes: 2 additions & 2 deletions model/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package model
import (
"os"
"runtime"
"sort"
"slices"
"strconv"
"time"

Expand Down Expand Up @@ -226,7 +226,7 @@ var _ = Describe("process lists", func() {
{p42, p1},
}
for _, pl := range pls {
sort.Sort(ProcessListByPID(pl))
slices.SortFunc(pl, SortProcessByPID)
Expect(pl[0].PID).To(Equal(PIDType(1)))
Expect(pl[1].PID).To(Equal(PIDType(42)))
}
Expand Down

0 comments on commit 11758be

Please sign in to comment.