Implemented commandline params, updated README.md, added cpanfile, fixed couple of bugs
- Use Proc::Find instead of ps utility - Use builtin kill instead of commandline utility - Use setpriority instead of renice utility - Implemented config parameter loopdelay - Implemented config parameter cmddelay - Implemented commandline parameter --config - Added --help parameter - Implemented commandline parameter --pidfile - Added cpanfile and bootstrap.sh to bootstrap this daemon - Optimized algo - do not send signals to already "signaled" processes, do not ionice already ioniced pids
This commit is contained in:
55
README.md
55
README.md
@@ -10,33 +10,59 @@
|
||||
|
||||
## Как это установить и заставить работать?
|
||||
|
||||
Для установки нам понадобятся пакеты с перлом и перловым модулем JSON::XS из текущего дистрибутива линукса, например, их
|
||||
можно уставить командами:
|
||||
### Установка reniced
|
||||
|
||||
Для вуишфт-образных дистрибутивов
|
||||
Для установки и работы нам понадобятся модули, которые скорее всего с дистрибутивом не идут. Чтобы их установить нам
|
||||
понадобятся собственно perl и его пакетный менеджер cpanm.
|
||||
|
||||
Для debian-образных дистрибутивов эти пакеты можно установить командами:
|
||||
|
||||
```bash
|
||||
apt-get install perl perl-json-xs
|
||||
dnf install perl perl-App-cpanminus
|
||||
```
|
||||
|
||||
Для наследников redhat-а
|
||||
Для rhel-образых - командами:
|
||||
|
||||
```bash
|
||||
dnf install perl perl-json-xs
|
||||
apt install perl cpanminus
|
||||
```
|
||||
|
||||
Для всех остальных нужен будет perl, cpanm и переменная окружения PERL5LIB. За подробностями - добро пожаловать в гугл.
|
||||
И далее приложение надо будет "забутстрапить". С этим есть небольшая тонкость - если у вас определена переменная
|
||||
PERL5LIBS, то при запуске модули будут браться из системы и каталогов, обозначенных в ней, если не определена, то только
|
||||
из системы. Соответственно, предполагается, что у нас запуск команды reniced будет в окружении, в котором есть всё
|
||||
необходимое (в cpanfile есть список всех нужных для работы библиотек), либо в системе, где все нужные либы уже
|
||||
установлены штатным пакетным менеджером.
|
||||
|
||||
Как заставить это работать?
|
||||
Бутстрапится приложение командой
|
||||
|
||||
Вначале надо скопировать конфиг из **config_example.json** в **~/.reniced.json** и далее подправить все необходимые
|
||||
значения в нём.
|
||||
```
|
||||
bash bootstrap.sh
|
||||
```
|
||||
|
||||
При этом все либы складываются в ~/perl5. У рута это будет /root/perl5, у обычного пользователя - ${HOME}/perl5. (при
|
||||
этом $HOME обычно это /home/имя_пользователя). В случае MacOS пути будут иные: /var/root/perl5 для рута и ${HOME}/perl5
|
||||
(при этом $HOME обычно это /Users/имя_пользователя). Собственно, тот каталог с либами далее можно переместить куда надо.
|
||||
|
||||
### Работа с reniced
|
||||
|
||||
Вначале надо скопировать конфиг из **example_config.json** в **~/.reniced.json**, если запуск планируется от
|
||||
пользователя (что не рекомендуется см. Caveats ниже), либо скопировать конфиг из **example_config.json** в
|
||||
**/etc/reniced.json**, если запуск будет от суперпользователя (рута, uid=0) и далее подправить все необходимые значения
|
||||
в нём.
|
||||
|
||||
После настройки списка процессов и приоритетов их работы, можно запускать сервис как unix daemon или как обычный
|
||||
процесс. Второй вариант нужен для запуска сервиса через systemd.
|
||||
|
||||
Если reniced планируется запускать от рута (uid=0), то конфиг будет находиться не в **~/.reniced.json**, а в
|
||||
**/etc/reniced.json**.
|
||||
#### Аргументы командной строки
|
||||
|
||||
**--help** - краткая справка по запуску команды
|
||||
|
||||
**--pidfile** - указать положение pidfile, в который будет записываться process id (для рута по-умолчанию
|
||||
/run/reniced.pid, для обычного пользователя по-умолчанию /tmp/reniced-UID.pid)
|
||||
|
||||
**--config** - указать положение файла с конфигом (для рута - /etc/reniced.json, для обычного
|
||||
пользователя - ~/.reniced.json)
|
||||
|
||||
|
||||
## Caveats
|
||||
|
||||
@@ -50,7 +76,8 @@ dnf install perl perl-json-xs
|
||||
sudo reniced
|
||||
```
|
||||
|
||||
в каталоге с reniced. Кроме того, под MacOS конфиг всегда ищется в ~/.reniced.json
|
||||
в каталоге с reniced. Кроме того, под MacOS конфиг всегда ищется в ~/.reniced.json, а при таком запуске нужно, чтобы
|
||||
была выставлена переменная окружения PERL5LIB, например в ~/.zprofile.
|
||||
|
||||
В линуксах такой проблемы пока нет (но возможно будет, когда придут "атомарные" обДНОвления).
|
||||
|
||||
@@ -68,5 +95,5 @@ sudo reniced
|
||||
При запуске reniced от обычного пользователя, niceness процесса может идти только в плюс. То есть, процесс с nice=11 не
|
||||
получится сделать с nice=9, например (без перезапуска процесса, конечно). Зато от суперпользователя такое работает.
|
||||
|
||||
## Special thans
|
||||
## Special thanks
|
||||
Авторам VK Teams. Без их мессенджера, с его "недостатками", не было бы этого костыля.
|
||||
|
||||
11
bootstrap.sh
Normal file
11
bootstrap.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Защита от дурака
|
||||
if [[ -z $HOME ]]; then
|
||||
echo "HOME variable is not defined, please set it up and re-run bootstrap script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cpanm --installdeps --local-lib "$HOME/perl5"
|
||||
|
||||
# vim: set ft=sh noet ai ts=4 sw=4 sts=4:
|
||||
10
cpanfile
Normal file
10
cpanfile
Normal file
@@ -0,0 +1,10 @@
|
||||
## no critic
|
||||
# Почему-то perlcritic пытается критиковать и cpanfile тоже, хотя он к перловым программам и либам не относится.
|
||||
# Проставим версии зависимостей в то, что по идее должно работать с текущим перлом. Типа "как у взрослых".
|
||||
requires Encode, '>=3.19';
|
||||
requires Getopt::Long, '>=2.54';
|
||||
requires JSON::XS, '>=4.03';
|
||||
# Built-in perl distribution
|
||||
requires POSIX, '0';
|
||||
requires Proc::Find, '>=0.051';
|
||||
requires Time::HiRes, '>=1.9764';
|
||||
@@ -2,7 +2,17 @@
|
||||
"parameters": {
|
||||
# 0 - запустить как обычный прооцесс (для systemd, например)
|
||||
# > 0 - запустить как unix daemon
|
||||
"foreground" : 0
|
||||
"foreground" : 1,
|
||||
|
||||
# куда складываем process id нашего демоночка, пустая строка - не складываем никуда (pid-файл не нужен)
|
||||
"pidfile": "/run/reniced.pid",
|
||||
|
||||
# Задержка между итерациями скрипта. По-умолчанию, 2 секунды. Измеряется в секундах.
|
||||
# Не может быть менее 2-х секунд.
|
||||
"loopdelay": "10",
|
||||
|
||||
# задержка между применением команд по renice-у. Измеряется в миллисекундах, по-умолчанию 0
|
||||
"cmddelay": "1000"
|
||||
},
|
||||
|
||||
"kill" : {
|
||||
@@ -98,7 +108,7 @@
|
||||
"com.apple.siri-distributed\\-evaluation",
|
||||
"com.apple.CloudPhotosConfiguration"
|
||||
],
|
||||
"12": ["bash"],
|
||||
|
||||
# IO hogs and other "bad" services
|
||||
"19":[
|
||||
"coreduetd",
|
||||
|
||||
182
reniced
182
reniced
@@ -1,38 +1,57 @@
|
||||
#!/usr/bin/env perl
|
||||
# Renice some processes, to make system more respositive
|
||||
|
||||
# TODO: Add -c parameter to pointout where config actually is
|
||||
# TODO: Add perlcritic rules :)
|
||||
# TODO: Запользовать модуль Proc::Find для получения списка pid-ов искомых процессов и их текущих парметров.
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use open qw (:std :utf8);
|
||||
use Encode qw (encode);
|
||||
use English qw (-no_match_vars);
|
||||
|
||||
use Getopt::Long qw (GetOptions);
|
||||
use JSON::XS ();
|
||||
use POSIX qw (setsid);
|
||||
use POSIX qw (setsid :sys_resource_h);
|
||||
use Proc::Find qw (find_proc);
|
||||
use Time::HiRes qw (usleep);
|
||||
|
||||
local $SIG{INT} = \&__signal_handler;
|
||||
local $SIG{TERM} = \&__signal_handler;
|
||||
|
||||
my $list;
|
||||
my $F;
|
||||
|
||||
my $uid = $<;
|
||||
|
||||
# We need it empty on first iteration
|
||||
my %ionice_pids = ();
|
||||
my %ionice_pids_new = ();
|
||||
|
||||
my ($pidfile, $configfile, $help, $loopdelay);
|
||||
|
||||
GetOptions (
|
||||
'pidfile=s' => \$pidfile, # Пусть будет глобальной переменной, чтобы обратиться к ней в хэндлерах сигналов
|
||||
'config=s' => \$configfile,
|
||||
'help' => \$help,
|
||||
);
|
||||
|
||||
if (defined $help) {
|
||||
help ();
|
||||
exit 0;
|
||||
}
|
||||
|
||||
my $config = LoadConf();
|
||||
my $pidfile = '/run/reniced.pid'; # Пусть будет глобальной переменной, чтобы обратиться к ней в хэндлерах сигналов
|
||||
|
||||
if ($uid > 0) {
|
||||
unless (defined $pidfile) {
|
||||
$pidfile = '/run/reniced.pid';
|
||||
|
||||
if ($uid > 0) {
|
||||
$pidfile = sprintf '/tmp/reniced-%s.pid', $uid;
|
||||
}
|
||||
|
||||
if ((defined $config->{pidfile})) {
|
||||
$pidfile = $config->{parameters}->{pidfile};
|
||||
}
|
||||
}
|
||||
|
||||
if ((defined $config->{patameters}->{pidfile}) && ($config->{patameters}->{pidfile} ne '')) {
|
||||
$pidfile = $config->{patameters}->{pidfile};
|
||||
}
|
||||
|
||||
unless ($config->{foreground}) {
|
||||
if (! $config->{parameters}->{foreground}) {
|
||||
daemonize ();
|
||||
my $pidfilehandler;
|
||||
|
||||
@@ -44,36 +63,20 @@ unless ($config->{foreground}) {
|
||||
}
|
||||
}
|
||||
|
||||
while (sleep 2) {
|
||||
open $F, qq{ps xau |} or do { warn "Can't 'run ps xau': $!" ; next; };
|
||||
binmode $F, ':utf8';
|
||||
if ((defined $config->{parameters}->{loopdelay}) && ($config->{parameters}->{loopdelay} > 2)) {
|
||||
$loopdelay = $config->{parameters}->{loopdelay};
|
||||
} else {
|
||||
$loopdelay = 2;
|
||||
}
|
||||
|
||||
my $cmd;
|
||||
while (sleep $loopdelay) {
|
||||
# Конечно, хотелось бы не искать по регулярке, ибо это относительно затратно, а просто собрать список... но что поделать
|
||||
my $cmd = find_proc (
|
||||
name => qr /./,
|
||||
detail => 1,
|
||||
);
|
||||
|
||||
while (my $str = readline $F) {
|
||||
chomp $str;
|
||||
my ($user, $pid, $cpu, $mem, $vsz, $rss, $tt, $stat, $started, $time, $command) = split /\s+/, $str, 11;
|
||||
if ($uid > 0) { # i'm not root!
|
||||
my $u = getpwuid ($<);
|
||||
|
||||
if (defined $pid && defined $command && defined $user) {
|
||||
if (($u eq $user) && ($pid =~ /^\d+$/)) {
|
||||
push @{$cmd->{$command}}, $pid;
|
||||
}
|
||||
}
|
||||
} else { # fuck, yeah! i'm root!
|
||||
# since we interested in changing niceness of processes, we need to make map command->[pids]
|
||||
if (defined $pid && defined $command) {
|
||||
if ($pid =~ /^\d+$/) {
|
||||
push @{$cmd->{$command}}, $pid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close $F;
|
||||
|
||||
if (defined $cmd) {
|
||||
if ((defined $cmd) && ($#{$cmd} > 0)) {
|
||||
# bulk chanage niceness :)
|
||||
my $prio = $config->{prio};
|
||||
my $kill = $config->{kill};
|
||||
@@ -81,49 +84,93 @@ while (sleep 2) {
|
||||
foreach my $kill_signal (keys %{$kill}) {
|
||||
my @pids;
|
||||
|
||||
foreach my $command (keys %{$cmd}) {
|
||||
foreach my $command (@{$cmd}) {
|
||||
foreach my $command_pattern (@{$kill->{$kill_signal}}) {
|
||||
if ($command =~ /$command_pattern/) {
|
||||
push @pids, @{$cmd->{$command}};
|
||||
if ($command->{cmdline}->[0] =~ /$command_pattern/) {
|
||||
push @pids, $command->{pid};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($#pids >= 0) {
|
||||
my $killcmd = sprintf 'kill -%s %s', $kill_signal, join (' ', @pids);
|
||||
`$killcmd`;
|
||||
# This op is a bit racy, so eval() it
|
||||
eval { kill $kill_signal, join (' ', @pids) };
|
||||
|
||||
# Use our cmd_delay here, but convert it from milliseconds to microseconds: 1k ms = 1 sec, 1M us = 1 sec
|
||||
# Not sure if we can handle properly delays < 10 ms, but noone really cares about such little delays.
|
||||
if (defined $config->{parameters}->{cmddelay}) {
|
||||
my $sleeptime = 1000 * $config->{parameters}->{cmddelay};
|
||||
usleep ($sleeptime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $priority (keys %{$prio}) {
|
||||
my @pids;
|
||||
|
||||
foreach my $command (keys %{$cmd}) {
|
||||
foreach my $command (@{$cmd}) {
|
||||
foreach my $command_pattern (@{$prio->{$priority}}) {
|
||||
if ($command =~ /$command_pattern/u) {
|
||||
push @pids, @{$cmd->{$command}};
|
||||
|
||||
if ($command->{cmdline}->[0] =~ /$command_pattern/u) {
|
||||
push @pids, $command->{pid};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($#pids >= 0) {
|
||||
my $renice;
|
||||
|
||||
# If we're not superuser we cannot set prio to negative value, for sure, so skip it
|
||||
if (($uid > 0) && ($priority < 0)) {
|
||||
$renice = sprintf "renice 0 -p %s", join (' ', @pids);
|
||||
# skip it
|
||||
} else {
|
||||
$renice = sprintf "renice %s -p %s", $priority, join (' ', @pids);
|
||||
foreach (@pids) {
|
||||
eval { setpriority PRIO_PROCESS, $_, $priority; };
|
||||
|
||||
if (defined $config->{parameters}->{cmddelay}) {
|
||||
my $sleeptime = 1000 * $config->{parameters}->{cmddelay};
|
||||
usleep ($sleeptime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`$renice`;
|
||||
|
||||
# Менять политику io процессам можно только руту :)
|
||||
if ($uid == 0) {
|
||||
if ($priority > 2) {
|
||||
if ($OSNAME eq 'darwin') {
|
||||
my $taskpol = sprintf 'taskpolicy -b -p %s', join (' '. @pids);
|
||||
my $setflag = 0;
|
||||
my $taskpol = 'taskpolicy -b -p';
|
||||
|
||||
foreach my $pid (@pids) {
|
||||
if (defined ($ionice_pids{$pid})) {
|
||||
$ionice_pids_new{$pid} = 1;
|
||||
} else {
|
||||
# set ionice for pid
|
||||
$setflag = 1;
|
||||
$ionice_pids_new{$pid} = 1;
|
||||
$taskpol .= " $pid";
|
||||
}
|
||||
}
|
||||
|
||||
if ($setflag) {
|
||||
`$taskpol`;
|
||||
}
|
||||
} elsif ($OSNAME eq 'linux') {
|
||||
foreach my $pid (@pids) {
|
||||
if (defined ($ionice_pids{$pid})) {
|
||||
# do nothing
|
||||
$ionice_pids_new{$pid} = 1;
|
||||
} else {
|
||||
# set ionice for pid
|
||||
`ionice -c 3 -p $pid`;
|
||||
$ionice_pids_new{$pid} = 1;
|
||||
}
|
||||
|
||||
%ionice_pids = %ionice_pids_new;
|
||||
}
|
||||
}
|
||||
|
||||
if (defined $config->{parameters}->{cmddelay}) {
|
||||
my $sleeptime = 1000 * $config->{parameters}->{cmddelay};
|
||||
usleep ($sleeptime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,8 +192,28 @@ if (-f $pidfile) {
|
||||
|
||||
exit 0;
|
||||
|
||||
sub help {
|
||||
my $msg = "Usage: reniced [--config /etc/reniced.json] [--pidfile /run/reniced.pid] [--help]\n";
|
||||
$msg .= "\n";
|
||||
$msg .= " --config overrides default value for config file, which is /etc/reniced.json for root and ~/.reniced.json for user\n";
|
||||
$msg .= " --pidfile overrides default value for pid-file file, which is /run/reniced.pid for root and /tmp/reniced-uid.pid for user\n";
|
||||
$msg .= " --help displays this help\n\n";
|
||||
|
||||
if (defined $msg) {
|
||||
print $msg;
|
||||
exit 0;
|
||||
} else {
|
||||
warn $msg;
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
|
||||
sub LoadConf {
|
||||
my $file = '/etc/reniced.json';
|
||||
|
||||
if ((defined $configfile) && ($configfile ne '')) {
|
||||
$file = $configfile;
|
||||
} else {
|
||||
my $uid = $<;
|
||||
|
||||
# On recent MacOS we have no direct ability to run reniced as system service, so we have to run it as user, probably
|
||||
@@ -157,8 +224,9 @@ sub LoadConf {
|
||||
|
||||
$file = sprintf '%s/.reniced.json', $ENV{HOME};
|
||||
}
|
||||
}
|
||||
|
||||
open my $FILEHANDLE, '<', $file || die "[FATA] No conf at $file: $OS_ERROR\n";
|
||||
open my $FILEHANDLE, '<', $file || die "No config file at $file: $OS_ERROR\n";
|
||||
|
||||
# Use binmode in order to get amount of read bytes (not chars!) from read()
|
||||
binmode $FILEHANDLE;
|
||||
|
||||
Reference in New Issue
Block a user