ProcessManager::finish()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 3
cp 0
crap 2
1
<?php
2
/**
3
 * @author    jan huang <[email protected]>
4
 * @copyright 2017
5
 *
6
 * @see      https://www.github.com/janhuang
7
 * @see      https://fastdlabs.com
8
 */
9
10
namespace FastD\Process;
11
12
use FastD\Swoole\Process;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Helper\Table;
15
use Symfony\Component\Console\Input\InputArgument;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Output\OutputInterface;
19
20
/**
21
 * Class Process.
22
 */
23
class ProcessManager extends Command
24
{
25
    /**
26
     * @var string
27
     */
28
    protected $pidPath;
29
30
    /**
31
     * php bin/console process {name} {args} {options}.
32
     */
33
    protected function configure()
34
    {
35
        $this->setName('process');
36
        $this->addArgument('process', InputArgument::OPTIONAL, 'process name');
37
        $this->addArgument('action', InputArgument::OPTIONAL, 'process action, <comment>status|start|stop</comment>', 'status');
38
        $this->addOption('pid', '-p', InputOption::VALUE_OPTIONAL, 'set process pid path.');
39
        $this->addOption('name', null, InputOption::VALUE_OPTIONAL, 'set process name.', null);
40
        $this->addOption('daemon', '-d', InputOption::VALUE_NONE, 'set process daemonize.');
41
        $this->setDescription('Create new processor.');
42
    }
43
44
    /**
45
     * @param InputInterface  $input
46
     * @param OutputInterface $output
47
     *
48
     * @return int
49
     */
50
    protected function execute(InputInterface $input, OutputInterface $output)
51
    {
52
        $path = $this->targetDirectory($input);
53
54
        $processName = $input->getArgument('process');
55
56
        if ($input->hasParameterOption(['--list', '-l']) || empty($processName)) {
57
            return $this->showProcesses($input, $output);
58
        }
59
60
        $processes = config()->get('processes', []);
61
62
        if (!isset($processes[$processName])) {
63
            throw new \RuntimeException(sprintf('Process %s cannot found', $processName));
64
        }
65
66
        $config = $processes[$processName];
67
        if (!class_exists($config['process'])) {
68
            throw new \RuntimeException(sprintf('Process class "%s" is not found.', $processName));
69
        }
70
71
        $name = $input->getOption('name');
72
        if (empty($name)) {
73
            $name = $processName;
74
        }
75
        $process = $config['process'];
76
        $options = isset($config['options']) ? (array) $config['options'] : [];
77
        $process = new $process($name);
78
        $process->configure($options);
79
80
        if (!($process instanceof Process)) {
81
            throw new \RuntimeException('Process must be instance of \FastD\Swoole\Process');
82
        }
83
        if ($input->hasParameterOption(['--daemon', '-d'])) {
84
            $process->daemon();
85
        }
86
87
        $pidFile = $path.'/'.$processName.'.pid';
88
89
        switch ($input->getArgument('action')) {
90
            case 'start':
91
                $pid = $process->start();
92
                file_put_contents($pidFile, $pid);
93
                $output->writeln(sprintf('process <info>%s</info> pid: <info>%s</info>', $process->getName(), $pid));
94
                $output->writeln(sprintf('pid: <info>%s</info>', $pidFile));
95
96
                $process->wait(function ($ret) use ($name) {
97
                    return $this->finish($name, $ret['pid'], $ret['code'], $ret['signal']);
98
                });
99
100
                break;
101
            case 'stop':
102
                $pid = (int) file_get_contents($pidFile);
103
                if ($process->kill($pid, SIGTERM)) {
104
                    $output->writeln(sprintf('process %s pid %s is killed', $process->getName(), $pid));
105
                }
106
107
                break;
108
            case 'status':
109
            default:
110
                $table = new Table($output);
111
                $info = $this->getProcessInfo($name);
112
                $table->setColumnWidth(0, 15);
0 ignored issues
show
Bug introduced by
The method setColumnWidth() does not seem to exist on object<Symfony\Component\Console\Helper\Table>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
113
                $rows = [
114
                    ['name', sprintf('<info>%s</info>', $process->getName())],
115
                    ['class', sprintf('<info>%s</info>', get_class($process))],
116
                    ['status', sprintf('<info>%s</info>', $info[2])],
117
                    ['pid', sprintf('<info>%s</info>', $info[1])],
118
                ];
119
                foreach ($options as $key => $value) {
120
                    $rows[] = ['options.'.$key, sprintf('<info>%s</info>', $value)];
121
                }
122
                $table->setRows($rows);
123
                $table->setStyle('compact');
124
                $table->render();
125
        }
126
127
        return 0;
128
    }
129
130
    /**
131
     * @param InputInterface $input
132
     *
133
     * @return string
134
     */
135
    protected function targetDirectory(InputInterface $input)
136
    {
137
        $pid = $input->getParameterOption(['--path', '-p']);
138
139
        if (empty($pid)) {
140
            $path = app()->getPath().'/runtime/process';
141
        } else {
142
            $path = dirname($pid);
143
        }
144
        if (!file_exists($path)) {
145
            mkdir($path, 0755, true);
146
        }
147
148
        return $this->pidPath = $path;
149
    }
150
151
    /**
152
     * @param InputInterface  $input
153
     * @param OutputInterface $output
154
     *
155
     * @return int
156
     */
157
    protected function showProcesses(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
158
    {
159
        $table = new Table($output);
160
        $table->setHeaders(['Process', 'Pid', 'Status', 'Start At', 'Runtime']);
161
        $rows = [];
162
        foreach (config()->get('processes', []) as $name => $processor) {
163
            $rows[] = $this->getProcessInfo($name);
164
        }
165
        $table->setRows($rows);
166
        $table->render();
167
168
        return 0;
169
    }
170
171
    /**
172
     * @param $name
173
     *
174
     * @return array
175
     */
176
    protected function getProcessInfo($name)
177
    {
178
        $pidFile = $this->pidPath.'/'.$name.'.pid';
179
        $pid = file_exists($pidFile) ? (int) file_get_contents($pidFile) : '';
180
        $isRunning = false;
181
        if (is_numeric($pid)) {
182
            $isRunning = process_kill($pid, 0);
183
        }
184
185
        return [
186
            $name,
187
            $isRunning ? $pid : '',
188
            $isRunning ? 'running' : 'stopped',
189
            $isRunning ? date('Y-m-d H:i:s', filemtime($pidFile)) : '',
190
            $isRunning ? time() - filemtime($pidFile) : '',
191
        ];
192
    }
193
194
    /**
195
     * @param $name
196
     * @param $pid
197
     * @param int $code
198
     * @param int $signal
199
     */
200
    protected function finish($name, $pid, $code = 0, $signal = 0)
201
    {
202
        output()->writeln(sprintf('process: %s. pid: %s exit. code: %s. signal: %s', $name, $pid, $code, $signal));
203
    }
204
}
205