commit ec666ac5583fcffd97284a16b3da540c7536a2c8 Author: eleksir Date: Sun Apr 23 17:25:01 2023 +0300 Initial commit diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..13255ce --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2022 Sergei Fedosoff + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6562b45 --- /dev/null +++ b/README.md @@ -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. Без их мессенджера, с его "недостатками", не было бы этого костыля. diff --git a/example_config.json b/example_config.json new file mode 100644 index 0000000..8998d38 --- /dev/null +++ b/example_config.json @@ -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" + ] + } +} \ No newline at end of file diff --git a/reniced b/reniced new file mode 100644 index 0000000..60cfc27 --- /dev/null +++ b/reniced @@ -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; +}