197 lines
4.9 KiB
Go
197 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"log"
|
|
"math"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/alitto/pond"
|
|
"github.com/sevlyar/go-daemon"
|
|
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() {
|
|
// Самое время поставить траппер сигналов.
|
|
signal.Notify(sigChan,
|
|
syscall.SIGINT,
|
|
syscall.SIGTERM,
|
|
syscall.SIGQUIT)
|
|
|
|
go sigHandler()
|
|
|
|
var (
|
|
cnf Config
|
|
err error
|
|
)
|
|
|
|
cnf, err = readConf()
|
|
|
|
if err != nil {
|
|
log.Fatalf("unable to parse config: %s", err)
|
|
}
|
|
|
|
if cnf.Daemon {
|
|
cntxt := &daemon.Context{
|
|
WorkDir: "/",
|
|
Args: []string{"reniced"},
|
|
}
|
|
|
|
if cnf.Pidfile != "" {
|
|
cntxt.PidFileName = cnf.Pidfile
|
|
}
|
|
|
|
if d, err := cntxt.Reborn(); err != nil {
|
|
log.Fatal("Unable to run: ", err)
|
|
|
|
return
|
|
} else if d != nil {
|
|
return
|
|
}
|
|
|
|
defer cntxt.Release() //nolint: errcheck
|
|
}
|
|
|
|
// То, ради чего всё затевалось.
|
|
reniced(cnf)
|
|
}
|
|
|
|
// reniced основная логика.
|
|
func reniced(cnf Config) {
|
|
// Создаём пул со статическим количеством воркеров и длиной очереди неблокируемых задач, равной NbCapacity.
|
|
pool = pond.New(cnf.MaxWorkers, cnf.NbCapacity, pond.Strategy(pond.Lazy()))
|
|
|
|
for {
|
|
processList, err := proc.Processes()
|
|
|
|
if err != nil {
|
|
log.Fatalf("Unable to get process list: %s", err)
|
|
}
|
|
|
|
// Не самый эффективный, зато работающий способ - простой перебор массивов.
|
|
for _, p := range processList {
|
|
ok := 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 := 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 %d already set to %d", p.Pid, niceLevel)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Посылаем процессу сигналы, если таковые есть в конфиге.
|
|
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()),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
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))
|
|
}
|
|
|
|
// Мы сюда никогда не попадём.
|
|
// pool.StopAndWait()
|
|
} //nolint:wsl
|
|
|
|
// sigHandler хэндлер сигналов. Работает на выходе приложения. Держит INT, TERM, QUIT.
|
|
func sigHandler() {
|
|
for {
|
|
var s = <-sigChan
|
|
switch s {
|
|
case syscall.SIGINT:
|
|
log.Print("Got SIGINT, quitting")
|
|
case syscall.SIGTERM:
|
|
log.Print("Got SIGTERM, quitting")
|
|
case syscall.SIGQUIT:
|
|
log.Print("Got SIGQUIT, quitting")
|
|
|
|
// Заходим на новую итерацию, если у нас "неинтересный" сигнал
|
|
default:
|
|
continue
|
|
}
|
|
|
|
// Закрываем пул воркеров и освобождаем ресурсы.
|
|
pool.StopAndWait()
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|