Implemented linux ionice funtionality

This commit is contained in:
eleksir
2024-04-29 20:42:23 +03:00
parent d962e4fd46
commit 863ca34383
10 changed files with 244 additions and 16 deletions

View File

@@ -161,4 +161,12 @@ prio:
- siriknowledged - siriknowledged
- triald - triald
ioprio:
- name:
- htop
- top
- atop
class: 2
prio: 1
# vim: set ft=yaml et ai ts=2 sw=2 sts=2: # vim: set ft=yaml et ai ts=2 sw=2 sts=2:

View File

@@ -1,15 +1,13 @@
#!/usr/bin/env gmake -f #!/usr/bin/env gmake -f
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath
FILELIST=main.go types.go util.go
BINARY=reniced BINARY=reniced
all: clean build all: clean build
build: build:
go build ${BUILDOPTS} -o ${BINARY} ${FILELIST} go build ${BUILDOPTS} -o ${BINARY}
clean: clean:
go clean go clean

View File

@@ -60,6 +60,27 @@ Unlike supplpied systemd unit, supplied System V Init script assumes reniced run
setting must be set to true). Script also expects to find pid-file at **/var/run/reniced.pid** and executable itself at setting must be set to true). Script also expects to find pid-file at **/var/run/reniced.pid** and executable itself at
**/usr/sbin/reniced**. **/usr/sbin/reniced**.
## Note on ionice operation
Currently implemented only linux io prio. On all other reniced-supported systems it is noop. Please note that io
priority can be set only by digits. Refer to *man ionice* for specific io classes and priorities inplemented there.
Here is table with numbers for defined class and priorities.
| class name | class number | list of possible priorities |
|:-----------|-------------:|----------------------------:|
| NONE |0 |0 |
| RealTime |1 |0, 1, 2, 3, 4, 5, 6, 7 |
| BestEffort |2 |0, 1, 2, 3, 4, 5, 6, 7 |
| Idle |3 |0 |
None is default io prio if process is not ioreniced. And it is the same value and behaviour as BestEffort with priority
of 4.
The lower priority value means higher priority and process will eat more io.
RealTime prio is not available for regular users but for root only.
## Special "thanks" ## Special "thanks"
To security assholes that disrupt workflows and make this world worse. To security assholes that disrupt workflows and make this world worse.

54
globals.go Normal file
View File

@@ -0,0 +1,54 @@
package main
import (
"fmt"
"os"
"syscall"
"github.com/alitto/pond"
)
// Пул воркеров.
var pool *pond.WorkerPool
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
var sigChan = make(chan os.Signal, 1)
var kill = make(map[string]syscall.Signal)
var renice = make(map[string]int)
var ioreniceClass = make(map[string]uint32)
var ioreniceClassdata = make(map[string]uint32)
var NotImplementedError = fmt.Errorf("SYSCALL Not Implemented on current platform") //nolint:revive,stylecheck
const IOPRIO_CLASS_NONE = uint32(0) //nolint:revive,stylecheck
const IOPRIO_CLASS_RT = uint32(1) //nolint:revive,stylecheck
const IOPRIO_CLASS_BE = uint32(2) //nolint:revive,stylecheck
const IOPRIO_CLASS_IDLE = uint32(3) //nolint:revive,stylecheck
const IOPRIO_WHO_PROCESS = 1 //nolint:revive,stylecheck
const IOPRIO_WHO_PGRP = 2 //nolint:revive,stylecheck
const IOPRIO_WHO_USER = 3 //nolint:revive,stylecheck
const IOPRIO_CLASS_SHIFT = uint32(13) //nolint:revive,stylecheck
const IOPRIO_PRIO_MASK = ((uint32(1) << IOPRIO_CLASS_SHIFT) - 1) //nolint:revive,stylecheck
var ClassToString = map[uint32]string{
IOPRIO_CLASS_NONE: "none",
IOPRIO_CLASS_RT: "realtime",
IOPRIO_CLASS_BE: "best-effort",
IOPRIO_CLASS_IDLE: "idle",
}
var StringToClass = map[string]uint32{
"none": IOPRIO_CLASS_NONE,
"0": IOPRIO_CLASS_NONE,
"realtime": IOPRIO_CLASS_RT,
"1": IOPRIO_CLASS_RT,
"best-effort": IOPRIO_CLASS_BE,
"2": IOPRIO_CLASS_BE,
"idle": IOPRIO_CLASS_IDLE,
"3": IOPRIO_CLASS_IDLE,
}
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */

17
ionice.go Normal file
View File

@@ -0,0 +1,17 @@
package main
func PrioToClass(prio uint32) uint32 {
return prio >> IOPRIO_CLASS_SHIFT
}
func PrioToClassdata(prio uint32) uint32 {
return prio & IOPRIO_PRIO_MASK
}
func ClassAndClassdataToPrio(class uint32, classdata uint32) uint32 {
return ((class) << IOPRIO_CLASS_SHIFT) | classdata
}
func PrioToClassAndClassdata(prio uint32) (uint32, uint32) {
return PrioToClass(prio), PrioToClassdata(prio)
}

80
ionice_linux.go Normal file
View File

@@ -0,0 +1,80 @@
//go:build linux
// +build linux
package main
import (
"fmt"
"log"
"syscall"
proc "github.com/shirou/gopsutil/v3/process"
)
// GetIOPriority достаёт приоритет вводв-вывода для укаанного процесса.
func GetIOPriority(which int, who int) (uint32, error) {
var (
err error
r0, _, e1 = syscall.Syscall(syscall.SYS_IOPRIO_GET, uintptr(which), uintptr(who), 0)
prio = uint32(r0)
)
if e1 != 0 {
err = fmt.Errorf(fmt.Sprintf("Received error number %v", e1))
}
return prio, err
}
// GetIOPriority ставит приоритет вводв-вывода для укаанного процесса.
func SetIOPriority(which int, who int, prio uint32) error {
var (
err error
_, _, e1 = syscall.Syscall(syscall.SYS_IOPRIO_SET, uintptr(which), uintptr(who), uintptr(prio))
)
if e1 != 0 {
err = fmt.Errorf(fmt.Sprintf("Received error number %v", e1))
}
return err
}
// IORenice выставляет процессу приоритет для операций ввода-вывода.
func IORenice(cnf Config, p *proc.Process, processName string) {
if currentIOPrioLevel, err := GetIOPriority(IOPRIO_WHO_PROCESS, int(p.Pid)); err == nil {
currentClass, currentClassdata := PrioToClassAndClassdata(currentIOPrioLevel)
class := ioreniceClass[processName]
classdata := ioreniceClassdata[processName]
if class == currentClass && classdata == currentClassdata {
if cnf.Debug && currentIOPrioLevel != 0 { // 0 - это дефолтный уровень io niceness.
log.Printf(
"IONiceness for %s(%d) already set to %d %d",
processName,
p.Pid,
currentClass,
currentClassdata,
)
}
} else {
iopriority := ClassAndClassdataToPrio(class, classdata)
_ = SetIOPriority(IOPRIO_WHO_PROCESS, int(p.Pid), iopriority)
if cnf.Debug {
log.Printf(
"Set ioniceness for %s(%d) to %d %d (was %d %d)",
processName,
p.Pid,
class,
classdata,
currentClass,
currentClassdata,
)
}
}
} else {
log.Printf("Unable to get ioprio for %s(%d)", processName, p.Pid)
}
}

16
ionice_other.go Normal file
View File

@@ -0,0 +1,16 @@
//go:build !linux
// +build !linux
package main
import proc "github.com/shirou/gopsutil/v3/process"
func GetIOPriority(which int, who int) (prio uint32, err error) {
return 0, NotImplementedError
}
func SetIOPriority(which int, who int, prio uint32) (err error) {
return NotImplementedError
}
func IORenice(cnf Config, p *proc.Process, processName string) {}

25
main.go
View File

@@ -15,15 +15,6 @@ import (
proc "github.com/shirou/gopsutil/v3/process" proc "github.com/shirou/gopsutil/v3/process"
) )
// Пул воркеров.
var pool *pond.WorkerPool
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
var sigChan = make(chan os.Signal, 1)
var kill = make(map[string]syscall.Signal)
var renice = make(map[string]int)
// main основная функция программы. // main основная функция программы.
func main() { func main() {
// Самое время поставить траппер сигналов. // Самое время поставить траппер сигналов.
@@ -86,7 +77,6 @@ func reniced(cnf Config) {
for _, p := range processList { for _, p := range processList {
ok := pool.TrySubmit(func() { ok := pool.TrySubmit(func() {
if processName, err := p.Name(); err == nil { if processName, err := p.Name(); err == nil {
// Для каждого процесса извлекаем его текущий priority. // Для каждого процесса извлекаем его текущий priority.
if currentPrioLevel, err := syscall.Getpriority(syscall.PRIO_PROCESS, int(p.Pid)); err == nil { if currentPrioLevel, err := syscall.Getpriority(syscall.PRIO_PROCESS, int(p.Pid)); err == nil {
if niceLevel := renice[processName]; niceLevel != 0 { if niceLevel := renice[processName]; niceLevel != 0 {
@@ -121,11 +111,18 @@ func reniced(cnf Config) {
) )
} }
} else if cnf.Debug { } else if cnf.Debug {
log.Printf("Niceness for %d already set to %d", p.Pid, niceLevel) log.Printf(
"Niceness for %s(%d) already set to %d",
processName,
p.Pid,
niceLevel,
)
} }
} }
} }
IORenice(cnf, p, processName)
// Посылаем процессу сигналы, если таковые есть в конфиге. // Посылаем процессу сигналы, если таковые есть в конфиге.
if killSignal := kill[processName]; killSignal != 0 { if killSignal := kill[processName]; killSignal != 0 {
switch killSignal { //nolint:exhaustive switch killSignal { //nolint:exhaustive
@@ -134,8 +131,9 @@ func reniced(cnf Config) {
if cnf.Debug { if cnf.Debug {
log.Printf( log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent", "Matching processName with killProcessname %s(%d), SIG%s sent",
processName, processName,
p.Pid,
strings.ToUpper(killSignal.String()), strings.ToUpper(killSignal.String()),
) )
} }
@@ -145,8 +143,9 @@ func reniced(cnf Config) {
if cnf.Debug { if cnf.Debug {
log.Printf( log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent", "Matching processName with killProcessname %s(%d), SIG%s sent",
processName, processName,
p.Pid,
strings.ToUpper(killSignal.String()), strings.ToUpper(killSignal.String()),
) )
} }

View File

@@ -29,6 +29,8 @@ type Config struct {
Kill Kill `yaml:"kill,omitempty"` Kill Kill `yaml:"kill,omitempty"`
Prio Prio `yaml:"prio,omitempty"` Prio Prio `yaml:"prio,omitempty"`
IOPrio IOPrio `yaml:"ioprio,omitempty"`
} }
type Prio []struct { type Prio []struct {
@@ -41,4 +43,10 @@ type Kill []struct {
Name []string `yaml:"name,omitempty"` Name []string `yaml:"name,omitempty"`
} }
type IOPrio []struct {
Class uint32 `yaml:"class,omitempty"`
Prio uint32 `yaml:"prio,omitempty"`
Name []string `yaml:"name,omitempty"`
}
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */

27
util.go
View File

@@ -122,6 +122,33 @@ func readConfFile(path string) (Config, error) {
} }
} }
for _, v := range cfg.IOPrio {
switch {
case len(v.Name) == 0:
log.Println("Skipping empty name entry in ioprio config block")
case v.Class > 3:
log.Printf("Skipping entry %s with class > 3 in ioprio config block", v.Name)
case v.Class == 0 && v.Prio != 0:
log.Printf("Skipping entry %s with class 0 and prio not 0 in ioprio config block", v.Name)
case v.Class == 3 && v.Prio != 0:
log.Printf("Skipping entry %s with class 3 and prio not 0 in ioprio config block", v.Name)
case v.Class == 1 && v.Prio > 7:
log.Printf("Skipping entry %s with class 1 and prio > 7 in ioprio config block", v.Name)
case v.Class == 2 && v.Prio > 7:
log.Printf("Skipping entry %s with class 2 and prio > 7 in ioprio config block", v.Name)
default:
for _, processName := range v.Name {
ioreniceClass[processName] = v.Class
ioreniceClassdata[processName] = v.Prio
if cfg.Debug {
log.Printf("Add %s to list of ionice class %d processes.", processName, v.Class)
log.Printf("Add %s to list of ionice prio %d processes.", processName, v.Prio)
}
}
}
}
for _, v := range cfg.Kill { for _, v := range cfg.Kill {
if len(v.Name) != 0 { if len(v.Name) != 0 {
switch v.Sig { switch v.Sig {