Amazon Linux 2のPHP 7.4環境に、xhprofを導入する

# What's？

タイトルのとおり、xhprofをPHP 7.4に導入したいなと思ったのですが。

xhprofに関する情報は、もうメンテナンスさあれてないよ、forkがあるよ、代替があるよ等いろいろあってよくわからなくなり、最終的にはPECLでインストールするようにしました。

よく見かけた情報は、こちらについてのものですね。

https://github.com/tideways/php-xhprof-extension

# xhprof

xhprofは、PHPのプロファイラーです。

https://www.php.net/manual/ja/book.xhprof.php

PECLではこちら。

https://pecl.php.net/package/xhprof

プロファイリングの機能と、Web UIが含まれており、プロファイル結果を確認することができます。

最新バージョンは2.3.5で、現在メンテナンスされているリポジトリはこちらになります。

https://github.com/longxinH/xhprof

今回は、こちらを使っていきましょう。

# 環境

今回は、Amazon Linux 2を使います。

```shell
$ cat /etc/os-release
NAME="Amazon Linux"
VERSION="2"
ID="amzn"
ID_LIKE="centos rhel fedora"
VERSION_ID="2"
PRETTY_NAME="Amazon Linux 2"
ANSI_COLOR="0;33"
CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2"
HOME_URL="https://amazonlinux.com/"


$ uname -srvmpio
Linux 4.14.252-195.483.amzn2.x86_64 #1 SMP Mon Nov 1 20:58:46 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
```

PHP 7.4は、`amazon-linux-extras`でインストールしているものとします。

```shell
$ sudo amazon-linux-extras install php7.4
```

バージョン。

```shell
$ php --version
PHP 7.4.21 (cli) (built: Jul  7 2021 17:35:08) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
```

Apacheは、こちらとします。

```shell
$ httpd -V
Server version: Apache/2.4.51 ()
Server built:   Oct  8 2021 22:03:47
Server's Module Magic Number: 20120211:118
Server loaded:  APR 1.7.0, APR-UTIL 1.6.1
Compiled using: APR 1.7.0, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_PROC_PTHREAD_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/etc/httpd"
 -D SUEXEC_BIN="/usr/sbin/suexec"
 -D DEFAULT_PIDLOG="/run/httpd/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"
```

# xhprofをインストールする

xhprofの要件に関するページはこちらですが、特に前提条件はなさそうです。

https://www.php.net/manual/ja/xhprof.requirements.php

UIに関しては以下にforkがあると書かれていますが、

https://github.com/preinheimer/xhprof

現在のリポジトリにも含まれています…。

https://github.com/longxinH/xhprof

今回は、`longxinH/xhprof`に含まれている方を使うことにしました。

## PECLとPHP開発パッケージ、Graphvizのインストール

まずは、必要なパッケージをインストールします。PECLが必要なのですが、Amazon Linux 2ではxhprofをコンパイルインストールすることになるようなので`php-devel`もインストールします。  
※Remiでは、xhprof自体を`yum`でインストールできるみたいです

```shell
$ sudo yum install php-pear php-devel
```

バージョン。

```shell
$ pear version
PEAR Version: 1.10.12
PHP Version: 7.4.21
Zend Engine Version: 3.4.0
Running on: Linux localhost 4.14.252-195.483.amzn2.x86_64 #1 SMP Mon Nov 1 20:58:46 UTC 2021 x86_64


$ php-config --version
7.4.21
```

また、UIで表示する際に可視化ができるようになるのでGraphvizも入れておきます。

```shell
$ sudo yum install graphviz
```
バージョン。

```shell
$ dot -V
dot - graphviz version 2.30.1 (20180828.1746)
```

## xhprofのインストール

続いて、xhprofのインストール。

```shell
$ sudo pecl install xhprof
```

コンパイルが終了し、「`php_ini`に`extension`を追記しなさい」と言われます。

```shell
Build process completed successfully
Installing '/usr/lib64/php/modules/xhprof.so'
install ok: channel://pecl.php.net/xhprof-2.3.5
configuration option "php_ini" is not set to php.ini location
You should add "extension=xhprof.so" to php.ini
```

今回、xhprofの2.3.5がインストールされたみたいです。

## xhprofをextensionに追加する

xhprofをPHPに組み込みます。

今回の環境では`/etc/php.d`ディレクトリ配下に設定ファイルを置くと良さそうなので`/etc/php.d/90-xhprof.ini`というファイルを作成することにします。

内容は、こんな感じで。


```ini:/etc/php.d/90-xhprof.ini
extension=xhprof.so
xhprof.output_dir=/tmp
```

`xhprof.output_dir`は、xhprofのプロファイリング結果を出力するディレクトリです。

今回は`/tmp`としました。
※今回の環境では注意点があるので、最後に補足します

xhprofを反映するため、PHP-FPMを起動している場合は再起動します（mod_phpの場合はApache）。

```shell
$ sudo systemctl restart php-fpm
```

こんなファイルを作成して

```php:/var/www/html/phpinfo.php
<?php
phpinfo();
```

ブラウザでアクセスしてみて確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309669/22410cfe-46a2-e9df-f6b9-a4b31eb2f690.png)


xhprofを認識していますね。

## XhprofのUIをインストールする

PECLでインストールするパッケージそのものは、`/tmp/pear/download`に`.tar.gz`が置かれているのでこちらを使うことにします。
※`/tmp`配下なのでそのうち消えると思いますが、その場合はリポジトリから取得で…

```shell
$ ll /tmp/pear/download
合計 828
-rw-r--r-- 1 root root    648 12月  7 04:00 channel.xml
-rw-r--r-- 1 root root 842658 12月  7 04:00 xhprof-2.3.5.tgz
```

この中に、`xhprof_html`および`xhprof_lib`というディレクトリが含まれていますが

```shell
$ tar tvf /tmp/pear/download/xhprof-2.3.5.tgz | grep -E 'xhprof_(html|lib)'
-rw-r--r-- longxinhui/staff  1610 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/css/xhprof.css
-rw-r--r-- longxinhui/staff 25313 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/docs/index.html
-rw-r--r-- longxinhui/staff 110076 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/docs/sample-callgraph-image.jpg
-rw-r--r-- longxinhui/staff 231801 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/docs/sample-diff-report-flat-view.jpg
-rw-r--r-- longxinhui/staff 184704 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/docs/sample-diff-report-parent-child-view.jpg
-rw-r--r-- longxinhui/staff 214289 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/docs/sample-flat-view.jpg
-rw-r--r-- longxinhui/staff 104056 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/docs/sample-parent-child-view.jpg
-rw-r--r-- longxinhui/staff   1435 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/jquery/indicator.gif
-rw-r--r-- longxinhui/staff 100171 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/jquery/jquery-1.2.6.js
-rw-r--r-- longxinhui/staff   1102 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/jquery/jquery.autocomplete.css
-rw-r--r-- longxinhui/staff  19824 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/jquery/jquery.autocomplete.js
-rw-r--r-- longxinhui/staff    533 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/jquery/jquery.tooltip.css
-rw-r--r-- longxinhui/staff   8086 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/jquery/jquery.tooltip.js
-rw-r--r-- longxinhui/staff   5713 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/js/xhprof_report.js
-rw-r--r-- longxinhui/staff   3172 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/callgraph.php
-rw-r--r-- longxinhui/staff   2814 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/index.php
-rw-r--r-- longxinhui/staff   1085 2021-09-05 05:31 xhprof-2.3.5/xhprof_html/typeahead.php
-rw-r--r-- longxinhui/staff   2518 2021-09-05 05:31 xhprof-2.3.5/xhprof_lib/display/typeahead_common.php
-rw-r--r-- longxinhui/staff  44826 2021-09-05 05:31 xhprof-2.3.5/xhprof_lib/display/xhprof.php
-rw-r--r-- longxinhui/staff  17105 2021-09-05 05:31 xhprof-2.3.5/xhprof_lib/utils/callgraph_utils.php
-rw-r--r-- longxinhui/staff  26748 2021-09-05 05:31 xhprof-2.3.5/xhprof_lib/utils/xhprof_lib.php
-rw-r--r-- longxinhui/staff   4856 2021-09-05 05:31 xhprof-2.3.5/xhprof_lib/utils/xhprof_runs.php
```

これを取り出して、DocumentRoot（デフォルトでは`/var/www/html`）に配置します。

```shell
$ cd /var/www/html
$ sudo tar zxvf /tmp/pear/download/xhprof-2.3.5.tgz xhprof-2.3.5/xhprof_html xhprof-2.3.5/xhprof_lib
$ sudo mv xhprof-2.3.5/xhprof* ./.
$ sudo rmdir xhprof-2.3.5
```

この配置だと、UIにアクセスする時は`http://[ホスト名]/xhprof_html`というURLになります。  
※`xhprof_lib`は直接参照しません

# xhprofを使う

では、xhprofを使ってみましょう。

使用する関数は、こちらにリファレンスがありますが

https://www.php.net/manual/ja/ref.xhprof.php

サンプルを見るのがよい気がします。

https://github.com/longxinH/xhprof/blob/v2.3.5/examples/sample.php

プロファイル対象は、先ほどの`phpinfo`のコードを変更しましょう。

```php:/var/www/html/phpinfo.php
<?php

require_once '/var/www/html/xhprof_lib/utils/xhprof_lib.php';
require_once '/var/www/html/xhprof_lib/utils/xhprof_runs.php';

xhprof_enable();  // プロファイル開始


// プロファイル対象のコードを実行
phpinfo();


$xhprof_data = xhprof_disable();  // プロファイル終了

// 結果保存
$xhprof_runs = new XHProfRuns_Default();
$xhprof_runs->save_run($xhprof_data, "app");
```

この状態で、ブラウザから`phpinfo.php`にアクセスすると、プロファイル結果が`xhprof.output_dir`で指定したディレクトリに保存されます。

インストールしたUIのページ（`http://[ホスト名]/xhprof_html`）にアクセスすると、保存されたプロファイル結果を確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309669/37d992f8-bee9-3771-3c3f-307837468695.png)


この数は、保存した分だけ増えていきます。

ファイルのリンクを選択すると、詳細が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309669/e0748187-55cf-338a-d4f7-36060f0ae5e1.png)

処理のツリーもたどれますし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309669/9c0fd128-b36f-62e1-4360-f485e61d8bf0.png)

Graphvizをインストールしているとコールグラフも見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309669/01263763-e25e-3250-9194-b1f2e713bd08.png)

使いかたは、こんな感じですね。

というわけで、準備ができたのでLet's Profiling!!

# 補足： systemd環境下で `/tmp` を選んだ場合の注意

今回の環境は、systemdでPHP-FPMを管理しています。この環境下で`xhprof.output_dir`に`/tmp`配下の値を指定した場合、systemdが管理している`tmp`ディレクトリ配下に割り当てられます。
※`/tmp`と指定した時点で対象になります

こんな感じのディレクトリになります。

```shell
/tmp/systemd-private-xxxxx-php-fpm.service-xxxxx/tmp
```

PHP-FPMのsystemdの設定である、`PrivateTmp`が効きます。

```shell:/etc/systemd/system/multi-user.target.wants/php-fpm.service 
...

[Unit]
...

[Service]
...

PrivateTmp=true
```

注意点としてはディレクトリのパスもそうですが、PHP-FPMを再起動するとディレクトリの中身がクリアされることですね。