fd6b17fe140dcb0e6184d195dc4707d836c3e4d6
[friendica.git/.git] / src / Core / Process.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Core;
23
24 use Friendica\App;
25 use Friendica\Core\Config\IConfig;
26 use Psr\Log\LoggerInterface;
27
28 /**
29  * Methods for interacting with the current process or create new process
30  *
31  * @todo 2019.12 Next release, this class holds all process relevant methods based on the big Worker class
32  *       - Starting new processes (including checks)
33  *       - Enabling multi-node processing (e.g. for docker service)
34  *         - Using an process-id per node
35  *         - Using memory locks for multi-node locking (redis, memcached, ..)
36  */
37 class Process
38 {
39         /**
40          * @var LoggerInterface
41          */
42         private $logger;
43
44         /**
45          * @var App\Mode
46          */
47         private $mode;
48
49         /**
50          * @var IConfig
51          */
52         private $config;
53
54         /**
55          * @var string
56          */
57         private $basePath;
58
59         public function __construct(LoggerInterface $logger, App\Mode $mode, IConfig $config, string $basepath)
60         {
61                 $this->logger   = $logger;
62                 $this->mode     = $mode;
63                 $this->config   = $config;
64                 $this->basePath = $basepath;
65         }
66
67         /**
68          * Checks if the maximum number of database processes is reached
69          *
70          * @return bool Is the limit reached?
71          */
72         public function isMaxProcessesReached()
73         {
74                 // Deactivated, needs more investigating if this check really makes sense
75                 return false;
76
77                 /*
78                  * Commented out to suppress static analyzer issues
79                  *
80                 if ($this->mode->isBackend()) {
81                         $process = 'backend';
82                         $max_processes = $this->config->get('system', 'max_processes_backend');
83                         if (intval($max_processes) == 0) {
84                                 $max_processes = 5;
85                         }
86                 } else {
87                         $process = 'frontend';
88                         $max_processes = $this->config->get('system', 'max_processes_frontend');
89                         if (intval($max_processes) == 0) {
90                                 $max_processes = 20;
91                         }
92                 }
93
94                 $processlist = DBA::processlist();
95                 if ($processlist['list'] != '') {
96                         $this->logger->debug('Processcheck: Processes: ' . $processlist['amount'] . ' - Processlist: ' . $processlist['list']);
97
98                         if ($processlist['amount'] > $max_processes) {
99                                 $this->logger->debug('Processcheck: Maximum number of processes for ' . $process . ' tasks (' . $max_processes . ') reached.');
100                                 return true;
101                         }
102                 }
103                 return false;
104                  */
105         }
106
107         /**
108          * Checks if the minimal memory is reached
109          *
110          * @return bool Is the memory limit reached?
111          */
112         public function isMinMemoryReached()
113         {
114                 $min_memory = $this->config->get('system', 'min_memory', 0);
115                 if ($min_memory == 0) {
116                         return false;
117                 }
118
119                 if (!is_readable('/proc/meminfo')) {
120                         return false;
121                 }
122
123                 $memdata = explode("\n", file_get_contents('/proc/meminfo'));
124
125                 $meminfo = [];
126                 foreach ($memdata as $line) {
127                         $data = explode(':', $line);
128                         if (count($data) != 2) {
129                                 continue;
130                         }
131                         list($key, $val) = $data;
132                         $meminfo[$key] = (int)trim(str_replace('kB', '', $val));
133                         $meminfo[$key] = (int)($meminfo[$key] / 1024);
134                 }
135
136                 if (!isset($meminfo['MemFree'])) {
137                         return false;
138                 }
139
140                 $free = $meminfo['MemFree'];
141
142                 $reached = ($free < $min_memory);
143
144                 if ($reached) {
145                         $this->logger->debug('Minimal memory reached.', ['free' => $free, 'memtotal' => $meminfo['MemTotal'], 'limit' => $min_memory]);
146                 }
147
148                 return $reached;
149         }
150
151         /**
152          * Checks if the maximum load is reached
153          *
154          * @return bool Is the load reached?
155          */
156         public function isMaxLoadReached()
157         {
158                 if ($this->mode->isBackend()) {
159                         $process    = 'backend';
160                         $maxsysload = intval($this->config->get('system', 'maxloadavg'));
161                         if ($maxsysload < 1) {
162                                 $maxsysload = 50;
163                         }
164                 } else {
165                         $process    = 'frontend';
166                         $maxsysload = intval($this->config->get('system', 'maxloadavg_frontend'));
167                         if ($maxsysload < 1) {
168                                 $maxsysload = 50;
169                         }
170                 }
171
172                 $load = System::currentLoad();
173                 if ($load) {
174                         if (intval($load) > $maxsysload) {
175                                 $this->logger->info('system load for process too high.', ['load' => $load, 'process' => $process, 'maxsysload' => $maxsysload]);
176                                 return true;
177                         }
178                 }
179                 return false;
180         }
181
182         /**
183          * Executes a child process with 'proc_open'
184          *
185          * @param string $command The command to execute
186          * @param array  $args    Arguments to pass to the command ( [ 'key' => value, 'key2' => value2, ... ]
187          */
188         public function run($command, $args)
189         {
190                 if (!function_exists('proc_open')) {
191                         return;
192                 }
193
194                 $cmdline = $this->config->get('config', 'php_path', 'php') . ' ' . escapeshellarg($command);
195
196                 foreach ($args as $key => $value) {
197                         if (!is_null($value) && is_bool($value) && !$value) {
198                                 continue;
199                         }
200
201                         $cmdline .= ' --' . $key;
202                         if (!is_null($value) && !is_bool($value)) {
203                                 $cmdline .= ' ' . $value;
204                         }
205                 }
206
207                 if ($this->isMinMemoryReached()) {
208                         return;
209                 }
210
211                 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
212                         $resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->basePath);
213                 } else {
214                         $resource = proc_open($cmdline . ' &', [], $foo, $this->basePath);
215                 }
216                 if (!is_resource($resource)) {
217                         $this->logger->debug('We got no resource for command.', ['cmd' => $cmdline]);
218                         return;
219                 }
220                 proc_close($resource);
221         }
222 }