Профилирование с xhprof на PHP7.x

Профилирование с xhprof на PHP7.x

omentes

Несколько дней назад я репостил ссылку на прекрасную книгу по оптимизацию кода, где практически в самом начале есть призыв

Самая сложная часть оптимизации — это не процесс оптимизации кода, а непосредственно принятие решения: делать ее или нет. Для этого существует всего несколько четких правил, все остальное — советы и рекомендации.


Иллюстрация с http://optimization.guide/intro.html

Там же приведена известная цитата Кнута:

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


Поэтому я хочу поговорить об одном из способов замеров работы кода на 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();

Пример вывода модуля в 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  


Report Page