Initial commit

This commit is contained in:
eleksir
2023-04-23 17:25:01 +03:00
commit ec666ac558
4 changed files with 447 additions and 0 deletions

20
LICENSE.txt Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2022 Sergei Fedosoff <eleksir@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

66
README.md Normal file
View File

@@ -0,0 +1,66 @@
# Reniced
Сервис автоматического изменения приоритета исполнения процессов. Работает по принципу периодиеского вылавливания списка
запущенных процессов и изменения приоритета "проклятым" и (если получится) "благословлённым" процессам.
## Зачем это нужно?
В некоторых случаях необходимо, чтобы система обладала достаточной интерактивностью, чтобы, например, нормально работала
видеосвязь в таких мессенджерах, как VK Teams.
## Как это установить и заставить работать?
Для установки нам понадобятся пакеты с перлом и перловым модулем JSON::XS из текущего дистрибутива линукса, например, их
можно уставить командами:
```bash
apt-get install perl perl-json-xs
```
```bash
dnf install perl perl-json-xs
```
Как заставить это работать?
Вначале надо скопировать конфиг из **config_example.json** в **~/.reniced.json** и далее подправить все необходимые
значения в нём.
После настройки списка процессов и приоритетов их работы, можно запускать сервис как unix daemon или как обычный
процесс. Второй вариант нужен для запуска сервиса через systemd.
Если reniced планируется запускать от рута (uid=0), то конфиг будет находиться не в **~/.reniced.json**, а в
**/etc/reniced.json**.
## Caveats
### Для MacOS
В свежих выпусках Mac OS нет возможности запускать сервисы от пользователя с uid=0, при запуске системы. Во всяком
случае без танцев с бубнами и отключения "защиты" системных файлов от модификации этого не сделать. Поэтому придётся при
запуске системы ручками писать
```bash
sudo reniced
```
в каталоге с reniced. Кроме того под MacOS конфиг всегда ищется в ~/.reniced.json
В линуксах такой проблемы пока нет (но возможно будет, когда придут "атомарные" обДНОвления).
### Для Linux
В линуксах есть такая фича, как namespaces. Так вот, без особых приседаний и раскачиваний, нет возможности собрать
список процессов не из текущего namespace-а. Так что скорее всего в некоторых контейнерных системах изоляции окружений
возможны варианты, при которых у изолированных таким образом процессов не будет меняться приоритет исполнения.
### Общего характера
По понятным причинам, нет возможности изменять niceness до отрицательных величин, если процесс reniced запущен от
обычного пользователя (uid > 0).
При запуске reniced от обычного пользователя, niceness процесса может идти только в плюс. То есть процесс с nice=11, не
получится сделать с nice=9, например (без перезапуска процесса, конечно). Зато от суперпользователя такое работает.
## Special thans
Авторам VK Teams. Без их мессенджера, с его "недостатками", не было бы этого костыля.

137
example_config.json Normal file
View File

@@ -0,0 +1,137 @@
{
"parameters": {
# 0 - запустить как обычный прооцесс (для systemd, например)
# > 0 - запустить как unix daemon
"foreground" : 0
},
"kill" : {
"STOP": [
# Это такая бэкапилка для MacOS, но на удалёнке она больше жрёт проц и греет макбук, чем что-то там реально
# бэкапит.
"commvault"
]
},
# Это на самом деле nice level, safe values 0-19, для обычного пользователя и -19-19 для суперпользователя (uid=0)
# Значения в "списке процессов" задаются регулярными выражениями
"prio" : {
# High prio: Audio, Streaming, Video, Windows management
"-11": [
"coreaudiod",
"WindowServer",
"com.apple.audio.SandboxHelper",
"com.apple.audio.DriverHelper",
"audioclocksyncd",
"SystemUIServer"
],
# Medium prio
"-5": [
"VTDecoderXPCService ",
"VTEncoderXPCService",
"VK Teams ",
"mDNSResponder$",
"mDNSResponderHelper$",
"AudioComponentRegistrar",
"CVMServer"
],
# Above normal - keyboard event remapper and trapper
"-3": [
"Karabiner\\-Elements",
"Hammerspoon",
"MTLCompilerService",
"com.apple.appkit.xpc.openAndSavePanelService"
],
# Background processes, loggers, misc less valuable services
"3": [
"\\/logd$",
"\\/syslogd",
"syspolicyd",
"\\/cron$",
"softwareupdated",
"periodic\\-wrapper",
"iconservicesagent",
"com.apple.quicklook.ThumbnailsAgent",
"appstored"
],
# IO active and system services
"5": [
"corebrightnessd",
"airportd",
"containermanagerd",
"searchpartyd",
"\\/usr\\/bin\\/log",
"corespeechd",
"peopled",
"AssetCacheLocatorService",
"avatarsd",
"contactsdonationagent",
"storedownloadd",
"Dock\\.app",
"Dock$",
"Finder$",
"DockHelper$"
],
# Browsers and such
"9": [
"\\/Google Chrome",
"\\/Google Chrome Helper",
"EmojiFunction",
"Microsoft Edge Helper",
"Opera Helper",
"UserNotificationCenter"
],
# Even more less important services
"11": [
"siriactionsd",
"CoreSpotlightService",
"spotlightknowledged",
"Spotlight",
"suggestd",
"studentd",
"com.apple.siri-distributed\\-evaluation",
"com.apple.CloudPhotosConfiguration"
],
"12": ["bash"],
# IO hogs and other "bad" services
"19":[
"coreduetd",
"endpointsecurityd",
"analyticsd",
"backupd",
"com.apple.CodeSigningHelper",
"distnoted",
"backupd\\-helper",
"JamfDaemon",
"osqueryd",
"commvault",
"diagnosticd",
"JamfProCommService",
"XprotectService",
"XProtectPluginService",
"media\\-indexer",
"com.apple.Safari.SafeBrowsing.Service",
"SafariNotificationAgent",
"AMPArtworkAgent",
"AMPLibraryAgent",
"IDSBlastDoorService",
"photoanalysisd",
"photolibraryd",
"Crash Reports",
"\\/mds$",
"\\/mds_stores$",
"\\/mdsync",
"mdbulkimport",
"mdworker_shared",
"osqueryd",
"opendirectoryd",
"mediaanalysisd"
]
}
}

224
reniced Normal file
View File

@@ -0,0 +1,224 @@
#!/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 JSON::XS ();
use POSIX qw (setsid);
local $SIG{INT} = \&__signal_handler;
local $SIG{TERM} = \&__signal_handler;
my $list;
my $F;
my $uid = $<;
my $config = LoadConf();
my $pidfile = '/run/reniced.pid'; # Пусть будет глобальной переменной, чтобы обратиться к ней в хэндлерах сигналов
if ($uid > 0) {
$pidfile = sprintf '/tmp/reniced-%s.pid', $uid;
}
if ((defined $config->{patameters}->{pidfile}) && ($config->{patameters}->{pidfile} ne '')) {
$pidfile = $config->{patameters}->{pidfile};
}
unless ($config->{foreground}) {
daemonize ();
my $pidfilehandler;
# Поскольку мы логи никуда не пишем, то обработать ошибку, если она возникнет, вербозно не можем.
if (open ($pidfilehandler, '>', $pidfile)) {
if (print {$pidfilehandler} $PID) {
close $pidfilehandler;
}
}
}
while (sleep 2) {
open $F, qq{ps xau |} or do { warn "Can't 'run ps xau': $!" ; next; };
binmode $F, ':utf8';
my $cmd;
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) {
# bulk chanage niceness :)
my $prio = $config->{prio};
my $kill = $config->{kill};
foreach my $kill_signal (keys %{$kill}) {
my @pids;
foreach my $command (keys %{$cmd}) {
foreach my $command_pattern (@{$kill->{$kill_signal}}) {
if ($command =~ /$command_pattern/) {
push @pids, @{$cmd->{$command}};
}
}
}
if ($#pids >= 0) {
my $killcmd = sprintf 'kill -%s %s', $kill_signal, join (' ', @pids);
`$killcmd`;
}
}
foreach my $priority (keys %{$prio}) {
my @pids;
foreach my $command (keys %{$cmd}) {
foreach my $command_pattern (@{$prio->{$priority}}) {
if ($command =~ /$command_pattern/u) {
push @pids, @{$cmd->{$command}};
}
}
}
if ($#pids >= 0) {
my $renice;
if (($uid > 0) && ($priority < 0)) {
$renice = sprintf "renice 0 -p %s", join (' ', @pids);
} else {
$renice = sprintf "renice %s -p %s", $priority, join (' ', @pids);
}
`$renice`;
if ($priority > 2) {
if ($OSNAME eq 'darwin') {
my $taskpol = sprintf 'taskpolicy -b -p %s', join (' '. @pids);
`$taskpol`;
} elsif ($OSNAME eq 'linux') {
foreach my $pid (@pids) {
`ionice -c 3 -p $pid`;
}
}
}
}
$#pids = -1; undef @pids;
sleep 1;
}
undef $prio;
undef $cmd;
}
}
# This code unreachable, but for formal correctness it should be here :)
if (-f $pidfile) {
unlink $pidfile;
}
exit 0;
sub LoadConf {
my $file = '/etc/reniced.json';
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
# via sudo, so we have to check OS here and use $HOME/.reniced.json for MacOS.
if (($OSNAME eq 'darwin') || ($uid != 0)) {
# Assume that script is not running under completely sanitized environment
die "No HOME variable found.\n" unless (defined $ENV{HOME});
$file = sprintf '%s/.reniced.json', $ENV{HOME};
}
open my $FILEHANDLE, '<', $file || die "[FATA] No conf at $file: $OS_ERROR\n";
# Use binmode in order to get amount of read bytes (not chars!) from read()
binmode $FILEHANDLE;
my $len = (stat $file) [7];
my $json;
my $readlen = read $FILEHANDLE, $json, $len;
$json = encode 'UTF-8', $json;
unless (defined $readlen) {
close $FILEHANDLE;
die "Unable to read $file: $OS_ERROR\n";
}
if ($readlen != $len) {
close $FILEHANDLE;
die "File $file is $len bytes on disk, but we read only $readlen bytes\n";
}
close $FILEHANDLE;
my $j = JSON::XS->new->utf8->relaxed;
my $config = eval { $j->decode ($json); };
unless (defined $config) {
die "File $file does not contain a valid json data: $EVAL_ERROR\n";
}
return $config;
}
sub daemonize {
__fork ();
setsid ();
open STDIN, '<', '/dev/null';
open STDOUT, '>', '/dev/null';
open STDERR, '>', '/dev/null';
__fork ();
umask 0;
return;
}
sub __fork {
my $pid = fork;
unless (defined $pid) {
die ("Can't fork: $!");
}
if ($pid != 0) {
exit;
}
return;
}
sub __signal_handler {
# remove pidfile if it exist
if (-f $pidfile) {
unlink $pidfile;
}
exit 0;
}