]> git.mxchange.org Git - friendica.git/blob - src/Core/Process.php
Added logging for executing child processes
[friendica.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 Friendica\Model;
27 use Psr\Log\LoggerInterface;
28
29 /**
30  * Methods for interacting with the current process or create new process
31  *
32  * @todo 2019.12 Next release, this class holds all process relevant methods based on the big Worker class
33  *       - Starting new processes (including checks)
34  *       - Enabling multi-node processing (e.g. for docker service)
35  *         - Using an process-id per node
36  *         - Using memory locks for multi-node locking (redis, memcached, ..)
37  */
38 class Process
39 {
40         /**
41          * @var LoggerInterface
42          */
43         private $logger;
44
45         /**
46          * @var App\Mode
47          */
48         private $mode;
49
50         /**
51          * @var IConfig
52          */
53         private $config;
54
55         /**
56          * @var string
57          */
58         private $basePath;
59
60         /** @var Model\Process */
61         private $processModel;
62
63         /**
64          * The Process ID of this process
65          *
66          * @var int
67          */
68         private $pid;
69
70         public function __construct(LoggerInterface $logger, App\Mode $mode, IConfig $config, Model\Process $processModel, string $basepath, int $pid)
71         {
72                 $this->logger = $logger;
73                 $this->mode = $mode;
74                 $this->config = $config;
75                 $this->basePath = $basepath;
76                 $this->processModel = $processModel;
77                 $this->pid = $pid;
78         }
79
80         /**
81          * Log active processes into the "process" table
82          */
83         public function start()
84         {
85                 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
86
87                 $command = basename($trace[0]['file']);
88
89                 $this->processModel->deleteInactive();
90                 $this->processModel->insert($command, $this->pid);
91         }
92
93         /**
94          * Remove the active process from the "process" table
95          *
96          * @return bool
97          * @throws \Exception
98          */
99         public function end()
100         {
101                 return $this->processModel->deleteByPid($this->pid);
102         }
103
104         /**
105          * Checks if the maximum number of database processes is reached
106          *
107          * @return bool Is the limit reached?
108          */
109         public function isMaxProcessesReached()
110         {
111                 // Deactivated, needs more investigating if this check really makes sense
112                 return false;
113
114                 /*
115                  * Commented out to suppress static analyzer issues
116                  *
117                 if ($this->mode->isBackend()) {
118                         $process = 'backend';
119                         $max_processes = $this->config->get('system', 'max_processes_backend');
120                         if (intval($max_processes) == 0) {
121                                 $max_processes = 5;
122                         }
123                 } else {
124                         $process = 'frontend';
125                         $max_processes = $this->config->get('system', 'max_processes_frontend');
126                         if (intval($max_processes) == 0) {
127                                 $max_processes = 20;
128                         }
129                 }
130
131                 $processlist = DBA::processlist();
132                 if ($processlist['list'] != '') {
133                         $this->logger->debug('Processcheck: Processes: ' . $processlist['amount'] . ' - Processlist: ' . $processlist['list']);
134
135                         if ($processlist['amount'] > $max_processes) {
136                                 $this->logger->debug('Processcheck: Maximum number of processes for ' . $process . ' tasks (' . $max_processes . ') reached.');
137                                 return true;
138                         }
139                 }
140                 return false;
141                  */
142         }
143
144         /**
145          * Checks if the minimal memory is reached
146          *
147          * @return bool Is the memory limit reached?
148          */
149         public function isMinMemoryReached()
150         {
151                 $min_memory = $this->config->get('system', 'min_memory', 0);
152                 if ($min_memory == 0) {
153                         return false;
154                 }
155
156                 if (!is_readable('/proc/meminfo')) {
157                         return false;
158                 }
159
160                 $memdata = explode("\n", file_get_contents('/proc/meminfo'));
161
162                 $meminfo = [];
163                 foreach ($memdata as $line) {
164                         $data = explode(':', $line);
165                         if (count($data) != 2) {
166                                 continue;
167                         }
168                         list($key, $val) = $data;
169                         $meminfo[$key] = (int)trim(str_replace('kB', '', $val));
170                         $meminfo[$key] = (int)($meminfo[$key] / 1024);
171                 }
172
173                 if (!isset($meminfo['MemFree'])) {
174                         return false;
175                 }
176
177                 $free = $meminfo['MemFree'];
178
179                 $reached = ($free < $min_memory);
180
181                 if ($reached) {
182                         $this->logger->debug('Minimal memory reached.', ['free' => $free, 'memtotal' => $meminfo['MemTotal'], 'limit' => $min_memory]);
183                 }
184
185                 return $reached;
186         }
187
188         /**
189          * Checks if the maximum load is reached
190          *
191          * @return bool Is the load reached?
192          */
193         public function isMaxLoadReached()
194         {
195                 if ($this->mode->isBackend()) {
196                         $process    = 'backend';
197                         $maxsysload = intval($this->config->get('system', 'maxloadavg'));
198                         if ($maxsysload < 1) {
199                                 $maxsysload = 50;
200                         }
201                 } else {
202                         $process    = 'frontend';
203                         $maxsysload = intval($this->config->get('system', 'maxloadavg_frontend'));
204                         if ($maxsysload < 1) {
205                                 $maxsysload = 50;
206                         }
207                 }
208
209                 $load = System::currentLoad();
210                 if ($load) {
211                         if (intval($load) > $maxsysload) {
212                                 $this->logger->info('system load for process too high.', ['load' => $load, 'process' => $process, 'maxsysload' => $maxsysload]);
213                                 return true;
214                         }
215                 }
216                 return false;
217         }
218
219         /**
220          * Executes a child process with 'proc_open'
221          *
222          * @param string $command The command to execute
223          * @param array  $args    Arguments to pass to the command ( [ 'key' => value, 'key2' => value2, ... ]
224          */
225         public function run($command, $args)
226         {
227                 if (!function_exists('proc_open')) {
228                         $this->logger->notice('"proc_open" not available - quitting');
229                         return;
230                 }
231
232                 $cmdline = $this->config->get('config', 'php_path', 'php') . ' ' . escapeshellarg($command);
233
234                 foreach ($args as $key => $value) {
235                         if (!is_null($value) && is_bool($value) && !$value) {
236                                 continue;
237                         }
238
239                         $cmdline .= ' --' . $key;
240                         if (!is_null($value) && !is_bool($value)) {
241                                 $cmdline .= ' ' . $value;
242                         }
243                 }
244
245                 if ($this->isMinMemoryReached()) {
246                         $this->logger->notice('Memory limit reached - quitting');
247                         return;
248                 }
249
250                 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
251                         $resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->basePath);
252                 } else {
253                         $resource = proc_open($cmdline . ' &', [], $foo, $this->basePath);
254                 }
255                 if (!is_resource($resource)) {
256                         $this->logger->notice('We got no resource for command.', ['command' => $cmdline]);
257                         return;
258                 }
259                 proc_close($resource);
260
261                 $this->logger->info('Executed "proc_open"', ['command' => $cmdline, 'callstack' => System::callstack(10)]);
262         }
263 }