Профилирование с xhprof на PHP7.x
omentes- Установка
- Пример кода без фреймворка
- Анализ логов и локальный просмотр логов xhprof с прода
- Пример кода для Laravel
Несколько дней назад я репостил ссылку на прекрасную книгу по оптимизацию кода, где практически в самом начале есть призыв
Самая сложная часть оптимизации — это не процесс оптимизации кода, а непосредственно принятие решения: делать ее или нет. Для этого существует всего несколько четких правил, все остальное — советы и рекомендации.

Там же приведена известная цитата Кнута:
Программисты тратят огромное количество времени, размышляя и беспокоясь о некритичных местах кода, и пытаются оптимизировать их, что исключительно негативно сказывается на последующей отладке и поддержке. Мы должны вообще забыть об оптимизации в, скажем, 97% случаев; более того, поспешная оптимизация является корнем всех зол. И напротив, мы должны уделить все внимание оставшимся
- 3%.Дональд Кнут, Структурное программирование с операторами go to

Поэтому я хочу поговорить об одном из способов замеров работы кода на PHP - профилировании с Xhprof
На данный момент xhprof не поддерживает php 7 версии, но существует множество форков, где эта проблема была устранена. Существуют и другие профилировщики, такие как BlackFire, но они в основном платные, или их можно использовать только локально. В данном же случае можно делать замеры прямо на продакшине.
Установка
git clone https://github.com/longxinH/xhprof cd xhprof/extension phpize ./configure --with-php-config=/usr/bin/php-config sudo make && sudo make install mkdir /tmp/xhprof
Обратите внимание, что /usr/bin/php-config это путь к вашему php-config, и полный путь писать не обязательно.
Теперь вам нужно модифировать ваш php.ini, который используется для php (php-cli/php-fpm - возможно у вас fpm, убедитесь что модифицировали нужный ini-файл)
[xhprof] extension=xhprof.so xhprof.output_dir="/tmp/xhprof"
В моем случае я делал установку внутри докер контейнера, поэтому путь к папке я указал такой. Вы можете указать любой удобный для вас путь.
Проверьте, что все работает, это можно сделать с помощью вывода функции phpinfo();

Если не сработало, и вы используете php-fpm, выполните перезагрузку, например:
/etc/init.d/php7.2-fpm restart
Так же для отчетов в изображение вам понадобится graphviz
apt-get install graphviz
Использование - пример для кода без фреймворка
<?php xhprof_enable(); /* * Your awesome code... */ $xhprof_data = xhprof_disable(); $XHPROF_ROOT = "/home/omentes/xhprof/"; // путь, куда мы установили xhprof include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php"; include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php"; $xhprof_runs = new XHProfRuns_Default(); $run_id = $xhprof_runs->save_run($xhprof_data, "my_prefix");
После открытия страницы в браузере, которая содержит этот код, в папке /tmp/xhprof появится лог профилировщика, который будет называться примерно так: hash.my_prefix.xhprof
Если у вас реализован роутер, вы можете сделать вставку этого кода прямо в index.php, где весь ваш код будет на месте комментария.
Анализ логов xhprof
В современных реалиях неудобно использовать олдскульный способ просмотра логов через уже готовую вьюху, которая идет в комплекте. Все находится в папке $XHPROF_ROOT . "/xhprof_html/"
Но зачастую сейчас код изолирован докер контейнером, или же мы вообще хотим тестировать один из микросервисов. Тогда нам стандартный рецепт просто не подходит. Я подготовил docker-compose.yml, который поможет вам анализировать все логи локально. Достать логи с прода, или же с докер контейнера, обычно не составляет труда.
Давайте установим докер контейнер, который поможет нам смотреть логи без использования прода
git clone https://github.com/omentes/xhprof-php-docker-viewer.git cd xhprof-php-docker-viewer docker-compose up -d
Теперь скачаем наши логи и положим в папку, которую "слушает" вьювер логов.
В моем случае я достаю логи из докер-контейнера, например:
docker exec -it name_container cat /tmp/xhprof/hash.my_prefix.xhprof > traces/xhprof/hash.my_prefix.xhprof
Открываем ссылку
localhost:8000/index.php?run=hash&source=my_prefix
Обратите внимание, что если вы работаете на MacOS, то вместо localhost у вас будет ip адрес докер машины, который можно получить в консоли (docker-machine ip machine-name)
Наша ссылка содержит префикс и айди запроса, из которых по сути составлено имя файла.

Что значат столбцы:
- Calls — количество и процентное соотношение вызовов функции;
- Incl. Wall Time — время выполнения функции с вложенными функциями;
- Excl. Wall Time — время выполнения функции без вложенных функций;
- Incl. CPU — процессорное время с вложенными функциями;
- Excl. CPU — процессорное время без вложенных функций;
- Incl. MemUse — потребление памяти с вложенными функциями;
- Excl. MemUse — потребление памяти без вложенных функций;
- Incl. PeakMemUse — максимальное потребление памяти с вложенными функциями;
- Excl. PeakMemUse — максимальное потребление памяти без вложенных функций.
Перейдя по ссылке [View Full Callgraph], то отобразится дерево вызовов с визуализацией наиболее медленного кода (для работы нужен установленый graphviz). Желтым помечены вызовы, которые ведут к самому медленному - красному блоку вызова

Профилирование xhprof - пример кода для Laravel
С Laravel дела обстоят чуть иначе, тут мы будем использовать Middleware
Установим пакет для xhprof
composer require lox/xhprof
Создаем класс App\Http\Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use XHProfRuns_Default;
/**
* Class XhprofMiddleware.
*
* @package App\Http\Middleware
*/
class XhprofMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
// We will only profile requests if the proper flag is set on the query
// of the request. You may further customize this to be disabled on
// production releases of your application.
if ($request->query->get('xhprof') !== 'true' || !env ('XHPROF_ENABLE', false)) {
return $next($request);
}
xhprof_enable();
$result = $next($request);
$xhprofData = xhprof_disable();
$xhprofRuns = new XHProfRuns_Default();
$runId = $xhprofRuns->save_run($xhprofData, 'xhprof_laravel');
// We will attach the XHProf run ID as part of the response header.
// This is a lot better than modifying the actual response body.
$result->headers->set('X-Xhprof-Run-Id', $runId);
return $result;
}
}
И модифицируем вызов Middleware в Kernel.php, дописав в массив protected $middleware еще один класс:
... \App\Http\Middleware\XhprofMiddleware::class, ...
Обратите внимание на проверку перед созданием логов. Я добавил два кейса в условие. Первый кейс это если мы руками добавили get параметр к ссылке, чтобы включить сохранение лога. Второй - если у меня есть задефайненая переменная в енве, и она true (если она false или ее нет - условие не отработает). Это позволит менеджментить сохранение логов на проде.
Надеюсь данный материал был вам полезен :)
Подписывайтесь на мой канал в телеграм: https://t.me/response418