From 81288389c679756c95586077716a56bdf69fb1b8 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sun, 23 Mar 2025 11:34:05 +0300 Subject: [PATCH] Add periodic exec of one-shot commands --- .reniced.yaml.example | 25 ++++- Makefile | 2 +- cmd/reniced/main.go | 4 +- internal/lib/ionice_other.go | 2 +- internal/lib/reniced.go | 204 ++++++++++++++++++++++------------- internal/lib/types.go | 22 +++- internal/lib/util.go | 31 +++++- 7 files changed, 200 insertions(+), 90 deletions(-) diff --git a/.reniced.yaml.example b/.reniced.yaml.example index 275b72c..5f6aff6 100644 --- a/.reniced.yaml.example +++ b/.reniced.yaml.example @@ -4,8 +4,23 @@ daemon: true # PID file for daemon. pidfile: /var/run/reniced.pid -# Delay among iterations. -loop_delay: 5000 +# Delay between process list update task consecutive runs. +process_list_update_period: 5000 + +# Delay between kill consecutive tasks. +process_kill_period: 5000 + +# Delay between changing niceness consecutive tasks. +process_nice_period: 5000 + +# Delay between changing io niceness consecutive tasks (linux-only). +process_ionice_period: 5000 + +# Period of time between consecutive periodic_process list tasks hammering. +periodic_process_invocation_period: 60000 + +# Period of time between process exec() invocations. +periodic_process_invocation_delay: 500 # Whether to print debug messages to stdout. debug: true @@ -15,7 +30,11 @@ max_workers: 3 # Capacity of non-blocking tasks. All tasks that exceeding this capacity will block submission of new tasks until queue # has vacant place for new task. -nb_capacity: 30 +nb_capacity: 3000 + +# Time to time run these commands. +periodic_process: + - ps xa kill: - sig: stop diff --git a/Makefile b/Makefile index 1987452..b948008 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ #!/usr/bin/env gmake -f BINARY=reniced -BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath +BUILDOPTS=-ldflags="-s -w" -a -gcflags=all="-l -B" -trimpath -buildvcs=false all: clean build diff --git a/cmd/reniced/main.go b/cmd/reniced/main.go index 06a8cb1..f3a22a4 100644 --- a/cmd/reniced/main.go +++ b/cmd/reniced/main.go @@ -77,9 +77,7 @@ func main() { } if d, err := cntxt.Reborn(); err != nil { - log.Fatal("Unable to run: ", err) - - return + log.Fatalf("Unable to run: %s", err) //nolint: gocritic } else if d != nil { return } diff --git a/internal/lib/ionice_other.go b/internal/lib/ionice_other.go index 6e759f7..7035fe6 100644 --- a/internal/lib/ionice_other.go +++ b/internal/lib/ionice_other.go @@ -13,4 +13,4 @@ func (cnf Config) SetIOPriority(which int, who int, prio uint32) (err error) { return cnf.NotImplementedError } -func (cnf Config) IORenice(cnf Config, p *proc.Process, processName string) {} +func (cnf Config) IORenice(p *proc.Process, processName string) {} diff --git a/internal/lib/reniced.go b/internal/lib/reniced.go index 228fc00..ffba65d 100644 --- a/internal/lib/reniced.go +++ b/internal/lib/reniced.go @@ -3,6 +3,7 @@ package lib import ( "log" "math" + "os/exec" "runtime" "strings" "syscall" @@ -13,100 +14,149 @@ import ( // reniced основная логика. func (cnf Config) Reniced() { + var ( + processListUpdateTimer *time.Ticker + processKillTimer *time.Ticker + processReniceTimer *time.Ticker + processIONiceTimer *time.Ticker + processListTimer *time.Ticker + processList []*proc.Process + err error + ) + + // Прежде всего настроим таймеры + processListUpdateTimer = time.NewTicker(time.Duration(cnf.ProcessListUpdatePeriod) * time.Millisecond) + processReniceTimer = time.NewTicker(time.Duration(cnf.ProcessNicePeriod) * time.Millisecond) + processKillTimer = time.NewTicker(time.Duration(cnf.ProcessKillPeriod) * time.Millisecond) + processIONiceTimer = time.NewTicker(time.Duration(cnf.ProcessIONicePeriod) * time.Millisecond) + processListTimer = time.NewTicker(time.Duration(cnf.PeriodicProcessInvocationPeriod) * time.Millisecond) + for { - processList, err := proc.Processes() + select { + // Обновляем список процессов. + case <-processListUpdateTimer.C: + processList, err = proc.Processes() - if err != nil { - log.Fatalf("Unable to get process list: %s", err) - } - - // Не самый эффективный, зато работающий способ - простой перебор массивов. - for _, p := range processList { - ok := cnf.Pool.TrySubmit(func() { - if processName, err := p.Name(); err == nil { - // Для каждого процесса извлекаем его текущий priority. - if currentPrioLevel, err := syscall.Getpriority(syscall.PRIO_PROCESS, int(p.Pid)); err == nil { - if niceLevel := cnf.Renice[processName]; niceLevel != 0 { - prioLevel := niceLevel - currentNiceLevel := currentPrioLevel - - if runtime.GOOS == "linux" { - // Value of PR = 20 + (-20 to +19) is 0 to 39 - switch { - case niceLevel > 0: - prioLevel = 20 - niceLevel - case niceLevel < 0: - prioLevel = 20 + int(math.Abs(float64(niceLevel))) - default: - prioLevel = 20 - } - - currentNiceLevel = 20 - currentPrioLevel - } - - if currentPrioLevel != prioLevel { - // on Linux Setpriority actually operates niceness value. - _ = syscall.Setpriority(syscall.PRIO_PROCESS, int(p.Pid), niceLevel) + if err != nil { + log.Fatalf("Unable to get process list: %s", err) + } + // Проходим по списку процессов и посылаем сигналы процессам. + case <-processKillTimer.C: + for _, p := range processList { + ok := cnf.Pool.TrySubmit(func() { + if processName, err := p.Name(); err == nil { + // Посылаем процессу сигналы, если таковые есть в конфиге. + if killSignal := cnf.KillSignal[processName]; killSignal != 0 { + switch killSignal { //nolint:exhaustive + case syscall.SIGSTOP: + _ = p.SendSignal(killSignal) if cnf.Debug { log.Printf( - "Set niceness for %s(%d) to %d (was %d)", + "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()), + ) + } + } + } + } + }) + + if !ok { + log.Printf("Unable to add task to pool. Increase nb_capacity?") + } + } + // Проходим по списку процессов и меняем ionice процессам. + case <-processIONiceTimer.C: + for _, p := range processList { + ok := cnf.Pool.TrySubmit(func() { + if processName, err := p.Name(); err == nil { + cnf.IORenice(p, processName) + } + }) + + if !ok { + log.Printf("Unable to add task to pool. Increase nb_capacity?") + } + } + // Проходим по списку того, что надо запустить и запускаем. + case <-processListTimer.C: + for _, v := range cnf.PeriodicProcesses { + log.Printf("Invoking command: %s", v) + exec.Command(v) + time.Sleep(time.Duration(cnf.PeriodicProcessInvocationDelay) * time.Millisecond) + } + // Проходим по списку процессов и меняем ionice процессам. + case <-processReniceTimer.C: + // Не самый эффективный, зато работающий способ - простой перебор массивов. + for _, p := range processList { + ok := cnf.Pool.TrySubmit(func() { + if processName, err := p.Name(); err == nil { + // Для каждого процесса извлекаем его текущий priority. + if currentPrioLevel, err := syscall.Getpriority(syscall.PRIO_PROCESS, int(p.Pid)); err == nil { + if niceLevel := cnf.Renice[processName]; niceLevel != 0 { + prioLevel := niceLevel + currentNiceLevel := currentPrioLevel + + if runtime.GOOS == "linux" { + // Value of PR = 20 + (-20 to +19) is 0 to 39 + switch { + case niceLevel > 0: + prioLevel = 20 - niceLevel + case niceLevel < 0: + prioLevel = 20 + int(math.Abs(float64(niceLevel))) + default: + prioLevel = 20 + } + + currentNiceLevel = 20 - currentPrioLevel + } + + if currentPrioLevel != prioLevel { + // on Linux Setpriority actually operates niceness value. + _ = syscall.Setpriority(syscall.PRIO_PROCESS, int(p.Pid), niceLevel) + + if cnf.Debug { + log.Printf( + "Set niceness for %s(%d) to %d (was %d)", + processName, + p.Pid, + niceLevel, + currentNiceLevel, + ) + } + } else if cnf.Debug { + log.Printf( + "Niceness for %s(%d) already set to %d", processName, p.Pid, niceLevel, - currentNiceLevel, ) } - } else if cnf.Debug { - log.Printf( - "Niceness for %s(%d) already set to %d", - processName, - p.Pid, - niceLevel, - ) } } } + }) - cnf.IORenice(p, processName) - - // Посылаем процессу сигналы, если таковые есть в конфиге. - if killSignal := cnf.KillSignal[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()), - ) - } - } - } + if !ok { + log.Printf("Unable to add task to pool. Increase nb_capacity?") } - }) - - if !ok { - log.Printf("Unable to add task to pool. Increase nb_capacity?") } } - - time.Sleep(time.Millisecond * time.Duration(cnf.LoopDelay)) } // Мы сюда никогда не попадём. diff --git a/internal/lib/types.go b/internal/lib/types.go index 7c60476..d1138c1 100644 --- a/internal/lib/types.go +++ b/internal/lib/types.go @@ -29,8 +29,26 @@ type Config struct { // Where to put pid-file. Pidfile string `yaml:"pidfile,omitempty"` - // LoopDelay in milliseconds - whole set of action delay period. - LoopDelay int `yaml:"loop_delay,omitempty"` + // ProcessListUpdatePeriod - delay between process list update task consecutive runs. + ProcessListUpdatePeriod int `yaml:"process_list_update_period,omitempty"` + + // ProcessKillPeriod in milliseconds - delay between kill consecutive tasks. + ProcessKillPeriod int `yaml:"process_kill_period,omitempty"` + + // ProcessNicePeriod in milliseconds - delay between changing niceness consecutive tasks. + ProcessNicePeriod int `yaml:"process_nice_period,omitempty"` + + // ProcessNicePeriod in milliseconds - delay between changing io niceness consecutive tasks (linux-only). + ProcessIONicePeriod int `yaml:"process_ionice_period,omitempty"` + + // PeriodicProcessInvocationPeriod in milliseconds - delay between PeriodicProcesses invocations. + PeriodicProcessInvocationPeriod int `yaml:"periodic_process_invocation_period,omitempty"` + + // PriodicProcessInvocationDelay in milliseconds - delay between each process calling in PeriodicProcesses list. + PeriodicProcessInvocationDelay int `yaml:"periodic_process_invocation_delay,omitempty"` + + // PeriodicProcesses list of processes that should be executed priodically. + PeriodicProcesses []string `yaml:"periodic_process,omitempty"` // Whether to print debug info on stdout. Debug bool `yaml:"debug,omitempty"` diff --git a/internal/lib/util.go b/internal/lib/util.go index 6f676a7..efca72f 100644 --- a/internal/lib/util.go +++ b/internal/lib/util.go @@ -70,9 +70,34 @@ func (cnf *Config) readConfFile(path string) error { return err } - if cnf.LoopDelay == 0 { - cnf.LoopDelay = 2000 - log.Printf("loop_delay set to %d milliseconds", cnf.LoopDelay) + if cnf.ProcessListUpdatePeriod == 0 { + cnf.ProcessListUpdatePeriod = 5000 + log.Printf("process_list_update_priod set to %d milliseconds", cnf.ProcessListUpdatePeriod) + } + + if cnf.ProcessNicePeriod == 0 { + cnf.ProcessNicePeriod = 5000 + log.Printf("process_nice_priod set to %d milliseconds", cnf.ProcessNicePeriod) + } + + if cnf.ProcessIONicePeriod == 0 { + cnf.ProcessIONicePeriod = 5000 + log.Printf("process_ionice_period set to %d milliseconds", cnf.ProcessIONicePeriod) + } + + if cnf.ProcessKillPeriod == 0 { + cnf.ProcessKillPeriod = 5000 + log.Printf("process_kill_period set to %d milliseconds", cnf.ProcessIONicePeriod) + } + + if cnf.PeriodicProcessInvocationPeriod == 0 { + cnf.PeriodicProcessInvocationPeriod = 60000 + log.Printf("periodic_process_invocation_period set to %d milliseconds", cnf.PeriodicProcessInvocationPeriod) + } + + if cnf.PeriodicProcessInvocationDelay == 0 { + cnf.PeriodicProcessInvocationDelay = 500 + log.Printf("periodic_process_invocation_delay set to %d milliseconds", cnf.PeriodicProcessInvocationDelay) } if cnf.Debug {