12
3

More than 3 years have passed since last update.

Laravel 7 コマンドの実行結果に実行時間、最大使用メモリ、処理件数を出力する

Last updated at Posted at 2020-06-16

どのコマンドがいつ実行されて、どのくらい実行時間がかかったのか。
実行時の処理件数や最大使用メモリを知りたかったので、試しにやってみました。

環境

  • PHP: 7.4.x
  • Laravel: 7.x

作成するファイルについて

  • app/Console/Command.php
  • app/Console/CommandResult.php
  • app/Console/CommandTimer.php
  • app/Console/Commands/HelloWorldCommand.php

上記の4つのファイルを作成します。

app/Console/Command.php

Illuminate\Console\Command クラスを継承して App\Console\Command クラスを作成します。

やってることはコマンド実行前にコマンドの計測を開始して、コマンド実行後に計測結果を出力するようにしてます。

app/Console/Command.php
<?php declare(strict_types=1);

namespace App\Console;

use Exception;
use Illuminate\Console\Command as BaseCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

abstract class Command extends BaseCommand
{
    protected CommandTimer $timer;
    protected int $statusCode = 0;

    public function __construct()
    {
        parent::__construct();
        $this->timer = new CommandTimer();
    }

    /**
     * Execute the console command.
     *
     * @param  InputInterface  $input
     * @param  OutputInterface  $output
     * @return mixed
     * @throws
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->beforeHandle();

        try {
            $result = $this->laravel->call([$this, 'handle']);

            if ($result instanceof CommandResult) {
                $addMessage = $result->getMessage();
                $this->statusCode = $result->getStatusCode();
            }

            $this->afterHandle($addMessage ?? '');
        } catch (Exception $e) {
            $this->errorHandle();
            throw $e;
        }

        return $this->statusCode;
    }

    /**
     * コマンド実行前の処理
     * @throws Exception
     */
    protected function beforeHandle(): void
    {
        $this->timer->start();
        $message = sprintf('[Start] Execute command %s is started', $this->name);
        $this->comment($message);
        info($message);
    }

    /**
     * コマンド実行後の処理
     * @param string $addMessage
     * @throws Exception
     */
    protected function afterHandle(string $addMessage): void
    {
        $this->timer->stop();
        $message = sprintf('[Successful] Execute command %s is finished, %s seconds, max memory: %s MB.', $this->name, $this->timer->getTotalSeconds(), $this->getMaxMemory());
        $this->comment($message . ' ' . $addMessage);
        info($message . ' ' . $addMessage);
    }

    /**
     * コマンド実行エラーの処理
     * @throws Exception
     */
    protected function errorHandle(): void
    {
        $this->timer->stop();
        $message = sprintf('[Failed] Execute command %s is finished, %s seconds, max memory: %s MB.', $this->name, $this->timer->getTotalSeconds(), $this->getMaxMemory());
        $this->error($message);
        logger()->critical($message);
    }

    /**
     * コマンド実行時に割り当てられたメモリの最大値をMB単位で返す
     * @return float
     */
    private function getMaxMemory(): float
    {
        return memory_get_peak_usage(true) / (1024 * 1024);
    }
}

app/Console/CommandTimer.php

コマンドの実行時間を計測するクラスを作成します。

app/Console/CommandTimer.php
<?php declare(strict_types=1);

namespace App\Console;

use Carbon\Carbon;
use DateTime;
use Exception;

/**
 * コマンドの実行時間を計測
 */
class CommandTimer
{
    private const DATE_FORMAT = 'Y-m-d H:i:s.u';
    private Carbon $startedTime;
    private Carbon $stoppedTime;

    /**
     * @return DateTime
     * @throws Exception
     */
    public function start(): DateTime
    {
        return $this->startedTime = new Carbon();
    }

    /**
     * @return DateTime
     * @throws Exception
     */
    public function stop(): DateTime
    {
        return $this->stoppedTime = new Carbon();
    }

    /**
     * @return float
     * @throws Exception
     */
    public function getTotalSeconds(): float
    {
        if (empty($this->startedTime)) {
            throw new Exception('The timer has not started.');
        }

        if (empty($this->stoppedTime)) {
            throw new Exception('The timer has not stopped');
        }

        return $this->startedTime->floatDiffInSeconds($this->stoppedTime);
    }

    /**
     * @return string
     */
    public function getStartedTime(): string
    {
        return $this->startedTime->format(self::DATE_FORMAT);
    }

    /**
     * @return string
     */
    public function getStoppedTime(): string
    {
        return $this->stoppedTime->format(self::DATE_FORMAT);
    }
}

app/Console/CommandResult.php

app/Console/CommandResult.php
<?php declare(strict_types=1);

namespace App\Console;

/**
 * コマンドの実行結果
 */
class CommandResult
{
    private int $statusCode = 0;
    private int $successCount = 0;
    private int $failedCount = 0;

    /**
     * @param int $statusCode
     */
    public function setStatusCode(int $statusCode): void
    {
        $this->statusCode = $statusCode;
    }

    /**
     * @return int
     */
    public function getStatusCode(): int
    {
        return $this->statusCode;
    }

    public function addSuccess(): void
    {
        $this->successCount++;
    }

    public function addFail(): void
    {
        $this->failedCount++;
    }

    /**
     * @param int $count
     */
    public function setSuccess(int $count): void
    {
        $this->successCount = $count;
    }

    /**
     * @param int $count
     */
    public function setFail(int $count): void
    {
        $this->failedCount = $count;
    }

    /**
     * @return int
     */
    public function getSuccessCount(): int
    {
        return $this->successCount;
    }

    /**
     * @return int
     */
    public function getFailedCount(): int
    {
        return $this->failedCount;
    }

    /**
     * @return int
     */
    public function getTotalCount(): int
    {
        return $this->successCount + $this->failedCount;
    }

    /**
     * @return string
     */
    public function getMessage(): string
    {
        return sprintf('Total: %d Success: %d Failed: %d', $this->getTotalCount(), $this->getSuccessCount(), $this->getFailedCount());
    }
}

試しにコマンドを作成する

app/Console/Commands/HelloWorldCommand.php

app/Console/Commands/HelloWorldCommand.php
<?php declare(strict_types=1);

namespace App\Console\Commands;

use App\Console\Command;
use App\Console\CommandResult;

class HelloWorldCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'hello:world';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Hello World を出力するコマンド';

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $commandResult = new CommandResult();

        $this->comment('Hello World');
        $commandResult->addSuccess();

        return $commandResult;
    }
}

コマンドを実行

$ php artisan hello:world
[Start] Execute command hello:world is started
Hello World
[Successful] Execute command hello:world is finished, 0.05698 seconds, max memory: 20 MB. Total: 1 Success: 1 Failed: 0

このようにコマンドの実行結果が出力されます。

  • 実行時間: 0.05698 秒
  • 最大使用メモリ: 20 MB
  • 実行件数: 1件
  • 成功件数: 1件
  • 失敗件数: 0件

ログファイルも確認してみます。

storage/logs/laravel.log
[2020-06-16 09:30:00] local.INFO: [Start] Execute command hello:world is started  
[2020-06-16 09:30:00] local.INFO: [Successful] Execute command hello:world is finished, 0.05698 seconds, max memory: 20 MB. Total: 1 Success: 1 Failed: 0  

ログにも同様の内容が出力されます。

追記: スタブの差し替え

stubs/console.stub ファイルを差し替えておくと、
php artisan make:command した時に変更したテンプレートを使用できます。

stubs/console.stub
<?php declare(strict_types=1);

namespace {{ namespace }};

use App\Console\Command;

class {{ class }} extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = '{{ command }}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        //
    }
}
12
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
3