Magento2でPHPのパフォーマンスチューニング

## はじめに
Magento2(その他PHPのフレームワーク)でエクステンション等の開発をしていると、「なんだか動きが遅いな」と感じることがあると思います。
遅いけどどこで遅くなってしまっているのかわからない。
そんな時に、一連の処理を視覚化し分析することができる「XdebugのProfiler」という機能があります。
今回は試しにDen+Magento2の環境でXdebugのProfilerを使用し、分析を行ってみました。
- PHPStormで分析する手法
- [jokkedk/webgrind](https://github.com/jokkedk/webgrind "jokkedk/webgrind")というツールを用いて分析する手法

上記の2通りで試しました。
補足として、Magentoのコア機能として存在するProfiler機能も紹介します。

## 環境
Den : 1.0.0-beta.14
Magento2 : 2.4.6-p1
Xdebug : 3.2.1
PHPStorm : 2023.1.3
jokkedk/webgrind : 1.9.2

## 前提
Den+Magento2のインストールが完了していることとします。（Den+Magento2の環境構築方法は[こちら](https://qiita.com/nao_v/items/25658eefb784ecd232a1 "WSL2+DenでMagento2の環境構築")で紹介してます。）

## 下準備
### Xdebugの設定
まず、Xdebugの設定を変更します。
> ※Cliだけで使いたい場合は、`php -d xdebug.mode=profile bin/magento`と、php cliのdオプションでiniファイルの設定を上書きできるので、以下の設定はスキップできます。

Denの場合、Xdebugが入っているphpコンテナは別に用意されている(php-debug)ため、こちらで作業します。

まず、Denの環境を立ち上げたディレクトリ下に.wardenディレクトリを作成しておきます。

元となるXdebugの設定ファイルをphp-debugコンテナから.wardenディレクトリ下にコピーします。
```
den env cp php-debug:/usr/local/etc/php/conf.d/15-xdebug.ini ./warden
```
コピーしてきた15-xdebug.iniに以下のように設定します。
```./warden/15-debug.ini
# xdebug.mode = debug; //元の設定
xdebug.mode = profile;
```
warden-env.ymlを作成します。
```.warden/warden-env.yml
services:
  php-debug:
    volumes:
      - ./.warden/15-xdebug.ini:/usr/local/etc/php/conf.d/15-xdebug.ini
```
### Cliコマンドの作成
CliコマンドでProfilerの分析(PHPStorm)
試しに下記のようなCliコマンドを作成し、分析に充ててみます。(思い切って8GBの文字列を生成してみます。)
MagentoにおけるCliコマンドの作成方法は[Adobeの公式ドキュメント](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/cli-cmds/cli-howto.html "How to add CLI commands")に載っています。
```
<?php
namespace Nao\Xdebug\Console\Command;

use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class TestProfiler
 */
class TestProfiler extends Command
{
    /**
     * @inheritDoc
     */
    protected function configure(): void
    {
        $this->setName('nao:xdebug:test-profiler');
        $this->setDescription('Test the Xdebug profiler.');

        parent::configure();
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $memoryToUseInMB = 8192;

        $largeData = $this->generateLargeDataArray($memoryToUseInMB);

        return Cli::RETURN_SUCCESS;
    }

    /**
     * @param $sizeInMB
     * @return string
     */
    private function generateLargeDataArray($sizeInMB): string
    {
        $sizeInBytes = $sizeInMB * 1024 * 1024;
        return str_repeat('A', $sizeInBytes);
    }
}
```

### Cliコマンド実行・出力ファイルの処理
den debugでphp-debugコンテナの中に入り、Cliコマンドをたたきます。（上記のコードの場合は`bin/magento nao:xdebug:test-profiler`）
`xdebug.output_dir`に設定された保存ディレクトリに、`xdebug.trace_output_name`で指定したフォーマットでファイルが出力されます。
出力先のディレクトリは、dockerのvolumeを設定するなり、docker cpするなりしてホスト側にコピーしておきます。

## Profilerの分析
準備が整ったので、さっそく分析を行っていきます。
### PHPStormによる分析
まず、PHPStormで分析してみましょう。
トップナビの`Tools > Analyze Xdebug Profiler Snapshot...`から、先ほどホスト側にコピーした出力ファイルを選択します。
しばらく読み込まれるのを待つと、下図のようなタブが開きます。
赤枠の部分を見てみると、`Nao\Xdebug\Console\Command\TestProfiler::execute`でMemoryが約8GB使用されていることが分かります。
また、`str_repeart`関数の実行に約3秒かかっていることが分かります。
![xdebug-profiler-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287468/9cd9985a-781b-0034-b14f-d1a3e458936d.png)
また、下にあるCallersタブで、対象の関数が呼び出される際のコールツリーを見ることができます。
`str_repeart`関数が、`Nao\Xdebug\Console\Command\TestProfiler::execute`内の`Nao\Xdebug\Console\Command\TestProfiler::generateLargeDataArray`で呼び出されていることが分かります。
![xdebug-profiler-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287468/64b5b22a-376e-0f8f-0845-667417de6db6.png)

### jokkedk/webgrindを用いた分析
ここまでで十分な分析結果を得ることができましたが、もう一つ便利なツールがあります。
それが[jokkedk/webgrind](https://github.com/jokkedk/webgrind "jokkedk/webgrind")です。
docker imageがありますので、それを使用します。
```
docker run --rm -v ./xdebug:/tmp -p 83:80 jokkedk/webgrind:latest
```
`-v ./xdebug:/tmp` : ./xdebugの部分は、XdebugのProfilerで出力されたファイル（ホスト側にコピーしたもの）が格納されているディレクトリを指定します。
`-p 83:80` : 83の部分は、ご自身の環境で使われていないポート番号を指定します。

[http://127.0.0.1:83](http://127.0.0.1:83 "http://127.0.0.1:83")にアクセスし、右上のドロップダウンメニューからファイルを選び、「update」ボタンをクリックします。
![xdebug-profiler-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287468/01c3cec5-f466-f6c0-7446-c6ef5defeeee.png)
Total Self Costとは、対象の関数の、全体に対する処理時間の割合です。`str_repeat`の処理時間が全体の80.75%占めているということになります。
また、関数名をクリックすると呼び出し元の表示もできます。

右上の`Show Call Graph`をクリックすると、処理の流れを視覚化したグラフを見ることができます。
![xdebug_profiler_test-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287468/9fda6db7-a0cd-e5d0-fe41-1c80c74f0e60.png)
全体の80.75%を占める`str_repeat`が呼び出されるまでの処理の一連の流れを見ることができました。

## 補足
実はMagentoのコア機能としてもProfiler機能が存在します。
下記のコマンドでProfiler機能を有効化できます。
```
bin/magento dev:profiler:enable [type]
```
[type]の部分には`html`と`csvfile`が指定できますが、特に`html`を指定するとページにアクセスした際にページの最下部に分析結果がレンダリングされるため、かなり使いやすいオプションです。
![xdebug-profiler-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287468/04d331bd-c1f4-d7b2-7721-3d35fa3ae11b.png)
詳しくは[公式ドキュメント](https://devdocs.magento.com/guides/v2.3/config-guide/bootstrap/mage-profiler.html "Enable profiling (MAGE_PROFILER)")を参照してください。

## おわりに
今回は検証のために8GBの文字列を作成するというかなり大げさなことをしたため、とてもあからさまな分析結果となりました笑
しかし、パフォーマンスチューニングをする際にはかなり便利な機能ですので、是非使いこなしていきたいですね！