pixelfederation/swoole-bundle

View on GitHub
src/Bridge/Symfony/Bundle/Command/ServerStatusCommand.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\Bundle\Command;

use Assert\Assertion;
use K911\Swoole\Client\Exception\ClientConnectionErrorException;
use K911\Swoole\Coroutine\CoroutinePool;
use K911\Swoole\Server\Api\ApiServerClientFactory;
use K911\Swoole\Server\Config\Socket;
use K911\Swoole\Server\Config\Sockets;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

final class ServerStatusCommand extends Command
{
    public function __construct(
        private Sockets $sockets,
        private ApiServerClientFactory $apiServerClientFactory,
        private ParameterBagInterface $parameterBag
    ) {
        parent::__construct();
    }

    /**
     * {@inheritdoc}
     */
    protected function configure(): void
    {
        $this->setDescription('Get current status of the Swoole HTTP Server by querying running API Server.')
            ->addOption('api-host', null, InputOption::VALUE_REQUIRED, 'API Server listens on this host.', $this->parameterBag->get('swoole.http_server.api.host'))
            ->addOption('api-port', null, InputOption::VALUE_REQUIRED, 'API Server listens on this port.', $this->parameterBag->get('swoole.http_server.api.port'))
        ;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \Assert\AssertionFailedException
     * @throws \Throwable
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $exitCode = 0;
        $io = new SymfonyStyle($input, $output);

        $this->prepareClientConfiguration($input);

        $coroutinePool = CoroutinePool::fromCoroutines(function () use ($io): void {
            $status = $this->apiServerClientFactory->newClient()
                ->status()
            ;
            $io->success('Fetched status');
            $this->showStatus($io, $status);
        }, function () use ($io): void {
            $metrics = $this->apiServerClientFactory->newClient()
                ->metrics()
            ;
            $io->success('Fetched metrics');
            $this->showMetrics($io, $metrics);
        });

        try {
            $coroutinePool->run();
        } catch (ClientConnectionErrorException) {
            $io->error('An error occurred while connecting to the API Server. Please verify configuration.');
            $exitCode = 1;
        }

        return $exitCode;
    }

    /**
     * @throws \Assert\AssertionFailedException
     */
    protected function prepareClientConfiguration(InputInterface $input): void
    {
        /** @var string $host */
        $host = $input->getOption('api-host');

        /** @var string $port */
        $port = $input->getOption('api-port');

        Assertion::numeric($port, 'Port must be a number.');
        Assertion::string($host, 'Host must be a string.');

        $this->sockets->changeApiSocket(new Socket($host, (int) $port));
    }

    private function showStatus(SymfonyStyle $io, array $status): void
    {
        $server = $status['server'];
        $processes = $server['processes'];

        $rows = [
            ['Host', $server['host']],
            ['Port', $server['port']],
            ['Running mode', $server['runningMode']],
            ['Master PID', $processes['master']['pid']],
            ['Manager PID', $processes['manager']['pid']],
            [sprintf('Worker[%d] PID', $processes['worker']['id']), $processes['worker']['pid']],
        ];

        foreach ($server['listeners'] as $id => ['host' => $host, 'port' => $port]) {
            $rows[] = [sprintf('Listener[%d] Host', $id), $host];
            $rows[] = [sprintf('Listener[%d] Port', $id), $port];
        }

        $io->table([
            'Configuration', 'Value',
        ], $rows);
    }

    private function showMetrics(SymfonyStyle $io, array $metrics): void
    {
        $date = \DateTimeImmutable::createFromFormat(\DATE_ATOM, $metrics['date']);
        Assertion::isInstanceOf($date, \DateTimeImmutable::class);
        $server = $metrics['server'];
        $runningSeconds = $date->getTimestamp() - $server['start_time'];

        $idleWorkers = $server['workers_idle'];
        $workers = $server['workers_total'];
        $activeWorkers = $workers - $idleWorkers;

        $io->table([
            'Metric', 'Quantity', 'Unit',
        ], [
            ['Requests', $server['request_count'], '1'],
            ['Up time', $runningSeconds, 'Seconds'],
            ['Active connections', $server['connections_active'], '1'],
            ['Accepted connections', $server['connections_accepted'], '1'],
            ['Closed connections', $server['connections_closed'], '1'],
            ['All workers', $workers, '1'],
            ['Active workers', $activeWorkers, '1'],
            ['Idle workers', $idleWorkers, '1'],
            ['Running coroutines', $server['coroutine_num'], '1'],
            ['Tasks in queue', $server['tasking_num'], '1'],
        ]);
    }
}