7 Commits

Author SHA1 Message Date
eleksir
7474028b82 Remove validation of unused cmd_delay parameter 2024-05-06 23:25:34 +03:00
eleksir
120b7c995a Update config for golangci-lint 2024-05-06 23:23:15 +03:00
eleksir
301c22be02 Fix typo in README.md 2024-05-06 23:03:03 +03:00
eleksir
1d76fd9920 Remove cmd_delay config option as it seems to be useless 2024-05-06 23:00:32 +03:00
eleksir
d6f56dc569 Update README.md 2024-05-06 22:59:40 +03:00
eleksir
863ca34383 Implemented linux ionice funtionality 2024-04-29 20:42:23 +03:00
eleksir
d962e4fd46 Send signal to process after all other actions 2024-04-29 16:30:28 +03:00
11 changed files with 282 additions and 54 deletions

View File

@@ -1,10 +1,11 @@
run:
tests: false
skip-dirs:
- vendor
modules-download-mode: readonly
allow-parallel-runners: true
go: '1.21'
issues:
skip-dirs:
- vendor
output:
sort-results: true
linters:

View File

@@ -4,9 +4,6 @@ daemon: true
# PID file for daemon.
pidfile: /var/run/reniced.pid
# Delay between addition and execution of tasks to task pool, to lower cpu load peaks.
cmd_delay: 20
# Delay among iterations.
loop_delay: 5000
@@ -161,4 +158,12 @@ prio:
- siriknowledged
- triald
ioprio:
- name:
- htop
- top
- atop
class: 2
prio: 1
# vim: set ft=yaml et ai ts=2 sw=2 sts=2:

View File

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

View File

@@ -60,6 +60,37 @@ 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
**/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.
## Note #2 on ionice operation on Mac OS / Darwin
The only working official method for Apple OS is via taskpolicy utility that comes with Mac OS itself. (Unlimited kernel
API calls requires that calling binary being signed by Apple Inc itself) In context of this program such method is
inefficient and beacuse of that mostly likely io task policy for Mac OS will not be implemented.
## *BSD porting help needed :)
Indeed. If you have some time and will to add this functionality PRs are welcome!
## Special "thanks"
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) {}

75
main.go
View File

@@ -15,15 +15,6 @@ import (
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 основная функция программы.
func main() {
// Самое время поставить траппер сигналов.
@@ -86,32 +77,6 @@ func reniced(cnf Config) {
for _, p := range processList {
ok := pool.TrySubmit(func() {
if processName, err := p.Name(); err == nil {
if killSignal := kill[processName]; killSignal != 0 {
switch killSignal { //nolint:exhaustive
case syscall.SIGSTOP:
_ = p.SendSignal(killSignal)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(killSignal.String()),
)
}
default:
_ = p.SendSignal(killSignal)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(killSignal.String()),
)
}
}
}
// Для каждого процесса извлекаем его текущий priority.
if currentPrioLevel, err := syscall.Getpriority(syscall.PRIO_PROCESS, int(p.Pid)); err == nil {
if niceLevel := renice[processName]; niceLevel != 0 {
@@ -146,7 +111,43 @@ func reniced(cnf Config) {
)
}
} 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 {
switch killSignal { //nolint:exhaustive
case syscall.SIGSTOP:
_ = p.SendSignal(killSignal)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname %s(%d), SIG%s sent",
processName,
p.Pid,
strings.ToUpper(killSignal.String()),
)
}
default:
_ = p.SendSignal(killSignal)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname %s(%d), SIG%s sent",
processName,
p.Pid,
strings.ToUpper(killSignal.String()),
)
}
}
}
@@ -156,8 +157,6 @@ func reniced(cnf Config) {
if !ok {
log.Printf("Unable to add task to pool")
}
time.Sleep(time.Millisecond * time.Duration(cnf.CmdDelay))
}
time.Sleep(time.Millisecond * time.Duration(cnf.LoopDelay))

View File

@@ -10,9 +10,6 @@ type Config struct {
// LoopDelay in milliseconds - whole set of action delay period.
LoopDelay int `yaml:"loop_delay,omitempty"`
// CmdDelay in milliseconds - delay between each change application.
CmdDelay int `yaml:"cmd_delay,omitempty"`
// Whether to print debug info on stdout.
Debug bool `yaml:"debug,omitempty"`
@@ -29,6 +26,8 @@ type Config struct {
Kill Kill `yaml:"kill,omitempty"`
Prio Prio `yaml:"prio,omitempty"`
IOPrio IOPrio `yaml:"ioprio,omitempty"`
}
type Prio []struct {
@@ -41,4 +40,10 @@ type Kill []struct {
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: */

32
util.go
View File

@@ -69,11 +69,6 @@ func readConfFile(path string) (Config, error) {
return Config{}, err
}
if cfg.CmdDelay == 0 {
cfg.CmdDelay = 20
log.Printf("cmd_delay can not be 0, setting to %d milliseconds", cfg.CmdDelay)
}
if cfg.LoopDelay == 0 {
cfg.LoopDelay = 2000
log.Printf("loop_delay set to %d milliseconds", cfg.LoopDelay)
@@ -122,6 +117,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 {
if len(v.Name) != 0 {
switch v.Sig {