xdebug в руках администратора

в 11:55, , рубрики: php, xdebug, администрирование, Песочница, системное администрирование, метки: , ,

В статье будет рассказано о возможном применении xdebug системными администраторами web-серверов. Может показаться, что администратор не должен заниматься диагностикой и отладкой кода, поскольку это – работа программиста. Это так. Но, как, в случае возникновения проблемы, убедить программиста в том, что его код неоптимален (если это действительно так) и нуждается в переработке, если программист все время заявлет: «У нас все хорошо – чините сервер»? Представим, что это программист, с которым нежелательно спорить. Например, наш очень дорогой и возмущенный клиент.

При администрировании web-серверов инженеры часто сталкиваются с проблемами, которые простые пользователи сайта называют «медленно работает» или «тормозит». Безусловно, это очень важная проблема, которая может иметь серьезные финансовые последствия для владельца сайта. Именно с такой формулировкой «медленно работает» обычно обращаются пользователи и/или владельцы сайта к администратору web-сервера, и этой формулировки достаточно для того, чтобы приступить к диагностике всего и вся.

Интересно, что причины таких проблем могут скрываться практически где угодно. И этих причин может быть несколько, а сами проблемы могут быть трудновоспроизводимыми. То, что в конечном счете пользователь классифицирует, как «тормоза», на нижнем уровне может оказаться:

  • Задержками в сетевом канале доступа до сайта
  • Аппаратными проблемами на сервере
  • Недостаточным количеством ресурсов сервера
  • Неоптимально настроенной операционной системой
  • Неоптимально настроенным программным обеспечением
  • Проблемами с доступом к сторонним ресурсам, доступ к которым реализован синхронно
  • Неоптимально написанным кодом сайта

Задачей администратора является определение и, по возможности, устранение этих проблем. Если они, конечно, находятся в его зоне ответственности. А последний и, возможно, предпоследний пункты в его зону ответственности не входят. Но нужно еще показать, что проблема лежит именно в этих пунктах.

Именно о последнем случае мы и поговорим в нашей статье. Практика показывает, что этот случай составляет примерно 90% всех проблем, связанных с медленной работой сайта. Неоптимальный код, плохо написанные SQL-запросы, неправильное использование блокировок – все это может приводить к замедлению работы сайта. И если до некоторого момента мощное железо могло переварить плохой код, то с наступлением некоторой точки «x», оно просто перестает справляться с нагрузкой. Наращивать мощности можно и дальше, но, во-первых, они когда-нибудь закончатся, а во-вторых – это лишние затраты на оборудование. Поэтому в первую очередь решение проблемы нужно начинать с анализа кода сайта.

Стоит оговориться, что есть случаи, когда приобретение нового железа получается экономически более выгодным, чем разбор и правка большого количества кода.

Основная задача администратора, как ни странно – администрирование сервера. Он не является разработчиком и не имеет морального права трогать код, даже если тот плохо работает. Поэтому, для проведения максимально подробной диагностики этого кода и выявления узких мест, приводящих к медленной работе сайта, необходимо использовать инструменты, не требующие правки кода.

Да, совсем забыл, речь идет о разработке на PHP! Именно его используют и любят большинство web-программистов. Хотя, на счет «любят» вопрос, скорее спорный.

Xdebug

Xdebug является расширением PHP, написанным одним из разработчиков PHP, и предназначенным для сбора и анализа отладочной информации в php-коде. Важно отметить, что это open-source проект.

Мы будем рассматривать xdebug через призму системного администратора, затрагивая только тот функционал, который не требует правки кода сайта, а также не замедляет работу сервера.

Очевидно, что любое профилирование или трассировка вносит дополнительные задержки во время выполнения кода. Поэтому, проведение диагностики не должно сказываться на работоспособности сайта.

Установка xdebug

При использовании Centos/RHEL/Fedora самым простым способом установить xdebug будет являться установка из репозитория EPEL:

$ yum install php-pecl-xdebug

Установка с помощью pecl:

$ pecl install xdebug

Установить xdebug можно также из исходного кода, предварительно скачав его по адресу http://xdebug.org/download.php :

$ tar xvzf xdebug-2.2.1.tgz
$ cd xdebug-2.2.1
$ phpize
$ ./configure --enable-xdebug
$ make && make install

Настройка xdebug

Базовая настройка будет заключаться в простом подключении только что установленного расширения в файл php.ini. В данном файле необходимо проверить наличие строки:

zend_extension = /path/to/xdebug.so
xdebug.default_enable = 0
xdebug.overload_var_dump = 0 

Если строка не была добавлена в php.ini или в один из подключаемых файлов, например /etc/php.d/xdebug.ini, то необходимо сделать это вручную. После чего перезапустить веб-сервер.

С этого момента разработчики сайта получили в свое распоряжение новые функции xdebug, которыми теоретически могут пользоваться. Двумя первыми директивами мы специально отключаем функцию расширенного отображения стека вызовов функций при возникновении ошибок и переопределение стандартной функции var_dump(). Несмотря на то, что это является замечательным функционалом, помогающим разработке, это изменяет (пусть и немного) поведение кода сайта. На это пойти мы не можем.

Приступаем к анализу: Трейс вызовов функций

Самый главный и полезный метод для диагностики сайта и выявления проблемных мест – это трейс вызовов функций. При заходе на любую, выбранную нами страницу сайта, будет запущен сбор статистики по работе функций, а именно:

  • Время начала и конца выполнения кода
  • Порядок выполнения функций
  • Время выполнения каждой функции
  • Потребление памяти каждой функцией

Эта бесценная информация поможет достоверно определить «тормозной» участок или участки кода. Кроме этого, появится возможность определить количество памяти, потребляемой той или иной функцией.
Перед началом трассировки определим некоторые переменные, которые помогут нам контролировать сбор информации и собрать больше необходимых данных в процессе выполнения кода.

xdebug.collect_params обозначает насколько подробно нужно собирать информацию об аргументах функций. 0 – минимум, информация не собирается. 1 – информация собирается о количестве и типе аргументов. 3 – информация о значении аргументов. 4 – полная информация: тип, имя, значение аргумента в момент передачи. Чем больше информации мы хотим получить – тем дольше будет выполняться трассировка.
xdebug.show_mem_delta определяет отразить или не отразить разницу в потреблении памяти в конечном отчете при вызове каждой функции.
xdebug.trace_enable_trigger включает или отключает возможность запуска трассировки по требованию.
xdebug.auto_trace включает или отключает автоматический запуск трассировки при каждом обращении к страницам сайта.
xdebug.collect_assignments включает или не включает в отчет информацию о присвоении значений переменным.
xdebug.collect_includes включает или не включает в отчет информацию о подключенных файлах.
xdebug.collect_return включает или не включает в отчет информацию о значениях, возвращенными функциями.
xdebug.trace_output_dir указывает директорию, в которую будут скидываться отчеты.
xdebug.trace_output_name формирует имя файла для отчета.

Остальные переменные связаны с визуальным отображением отчета, и нам не интересны. Стандартная форма выглядит вполне приемлемой для проведения анализа.

Суть метода будет заключаться в запуске трассировки только в тот момент, когда администратор захочет этого. Включать трассировку на постоянной основе неприемлемо, поскольку это ведет к повышенному потреблению ресурсов во время выполнения кода, а также создает много ненужных отчетов. Чтобы указать серверу, что администратор желает запустить трассировку функций в момент выполнения запроса, ему необходимо передать параметр XDEBUG_TRACE в GET или POST запросе, либо установить cookie с таким именем. Последний способ выглядит наиболее предпочтительным, поскольку POST-запрос послать не всегда возможно, и практически никогда не нужно. А при использовании GET могут быть проблемы, связанные с тем, что адресная строка часто обрабатывается на сервере с помощью mod_rewrite или алиасов еще ДО вызова PHP-кода. Поэтому наша переменная может просто не дойти до адресата.

Собирать будем максимум информации о переданных переменных, подключенных файлах и разнице в потребленной памяти между вызовами функций. Складывать отчеты будет в /var/tmp. Остальные настройки будут по-умолчанию. В итоге допишем в php.ini следующие строки:

xdebug.trace_enable_trigger=1
xdebug.auto_trace=0
xdebug.collect_params=4
xdebug.show_mem_delta=1
xdebug.trace_output_dir=/var/tmp

И перезагружаем веб-сервер. Для анализа возьмем следующий код:

<?php
require "config.inc";
require "class/db.php";

for ($i = 1; $i < 6; $i++) {
        show_num($i);
}

$v = array();
alloc_array(1024);

$db = DB::Get("mysql", HOST, USER, PASS, NAME);
$db->connect();

echo "finished";

function show_num($i) {
        ($i % 2) ? show_odd($i) : show_even($i);
}

function show_odd($i) {
        echo "odd: $i<br>";
}

function show_even($i) {
        echo "even: $i<br>";
        sleep(1);
}

function alloc_array($size) {
        global $v;

        for ($i = 0; $i < $size; $i++) {
                $v[] = $i;
        }
}
?>

Не будем сейчас вдаваться в его подробности, хотя нетрудно догадаться, что код не представляет ценности для использования в случаях, отличных от тестовых.Представим, что пользователь попытался открыть в браузере страницу, за которую отвечает данный код. Страница загружалась более десяти секунд, что есть очень долго! Наша цель понять почему.Поскольку трассировка кода уже была настроена соответствующим образом, нам остается только указать серверу включить ее для нашего будущего запроса и сделать этот самый запрос. Как указывалось ранее, для этого необходимо установить переменную XDEBUG_TRACE в GET или POST запросе, либо передать ее с помощью cookie. Мы выбрали последний вариант – передачу с помощью cookie.

Обычно переменные cookie для конкретного домена устанавливаются сервером в браузер клиента с помощью передачи HTTP-заголовка Set-Cookie либо устанавливаются уже на стороне клиента с помощью сценария JavaScript.

Получить от сервера нужный нам заголовок довольно затруднительно: к коду сайта мы не прикасаемся, а внесение изменений в настройки сервера требует его перезагрузки, что для периодических (не разовых) операций неприемлемо. Самый правильный вариант – это установить cookie для нашего домена на своей (клиентской) стороне. Мы не можем заставить сервер выдать нам нужный сценарий javascript для установки cookie. Все по тем же причинам, что и в случае заголовка Set-Cookie. Поэтому будем устанавливать cookie в браузере самостоятельно. В Google Chrome это можно сделать, вбив прямо в адресную строку сценарий:

javascript:document.cookie=«XDEBUG_TRACE=1»

Контекст выполнения этого сценария должен соответствовать нашему сайту. То есть нужно сначала открыть страницу сайта, а потом вводить команду.
Установив переменную, делаем запрос к странице и ждем окончания запроса. Далее, на сервере ищем в директории /var/tmp соответствующий trace-файл. Взглянем на результаты:

TRACE START [2012-09-25 11:19:54]
    0.0005     645152  +645152   -> {main}() /var/www/test.php:0
    0.0007     649296    +4144     -> require(/var/www/config.inc) /var/www/test.php:4
    0.0007     649504     +208       -> define('HOST', '10.1.1.1') /var/www/config.inc:3
    0.0008     649536      +32       -> define('NAME', 'db') /var/www/config.inc:4
    0.0008     649568      +32       -> define('USER', 'u0') /var/www/config.inc:5
    0.0008     649600      +32       -> define('PASS', 'ps') /var/www/config.inc:6
    0.0012     695728   +46128     -> require(/var/www/class/db.php) /var/www/test.php:5
    0.0013     694736     -992     -> show_num($i = 1) /var/www/test.php:8
    0.0013     694736       +0       -> show_odd($i = 1) /var/www/test.php:21
    0.0013     694864     +128     -> show_num($i = 2) /var/www/test.php:8
    0.0013     694864       +0       -> show_even($i = 2) /var/www/test.php:21
    0.0014     694960      +96         -> sleep(1) /var/www/test.php:30
    1.0033     694864      -96     -> show_num($i = 3) /var/www/test.php:8
    1.0034     694864       +0       -> show_odd($i = 3) /var/www/test.php:21
    1.0034     694864       +0     -> show_num($i = 4) /var/www/test.php:8
    1.0034     694864       +0       -> show_even($i = 4) /var/www/test.php:21
    1.0035     694960      +96         -> sleep(1) /var/www/test.php:30
    2.0047     694864      -96     -> show_num($i = 5) /var/www/test.php:8
    2.0048     694864       +0       -> show_odd($i = 5) /var/www/test.php:21
    2.0048     695224     +360     -> alloc_array($size = 1024) /var/www/test.php:13
    2.0057     843024  +147800     -> DB::Get($type = 'mysql', $host = '10.1.1.1', $user = 'u0', $pass = 'ps', $db = 'db') /var/www/test.php:15
    2.0057     843664     +640       -> absDB->__construct($host = '10.1.1.1', $user = 'u0', $pass = 'ps', $db = 'db') /var/www/class/db.php:10
    2.0058     843664       +0         -> DB->__construct($host = '10.1.1.1', $user = 'u0', $pass = 'ps', $db = 'db') /var/www/class/db.php:36
    2.0058     844000     +336           -> DB->build() /var/www/class/db.php:19
    2.0058     844016      +16     -> absDB->connect() /var/www/test.php:16
    2.0058     844368     +352       -> mysqli_connect('10.1.1.1', 'u0', 'ps', 'db') /var/www/class/db.php:47
   11.0164       8432
TRACE END   [2012-09-25 11:20:05]

В первой и последней строках отчета отображено время начала и конца запроса соответственно. Из этой информации следует, что код выполнялся 11 секунд. В отчете также отображен порядок вызовов всех функций в коде с учетом их вложенности. Первая колонка обозначает общее время выполнения кода в секундах на момент вызова функции, вторая колонка показывает потребление оперативной памяти в байтах также на момент вызова функции. Третья колонка – это разница в потреблении памяти, вызванное ПРЕДЫДУЩЕЙ функцией. Оставшиеся колонки показывают название функции, имя файла и номер строки, в которой она была вызвана.

Попробуем понять, что вызывает медленную работу кода. Прежде всего в глаза бросается долгая работа функции mysqli_connect(), которая выполняется почти 9 секунд. Очевидно, что существуют проблемы с доступом к удаленному серверу. Обратите внимание, что эта функция вызывается через несколько слоев абстракции и классов. Для многих фреймворков – это обычное дело. Помимо этого, задержки происходя в функции sleep(), которая вызывается в пользовательской функции show_even().

Что касается потребления памяти, то мы видим резкий скачок более 140КБ после вызова пользовательской функции alloc_array(), а также большое выделение памяти в самом начале выполнения кода.

Мы видим всю нужную информацию о переданных аргументах всех функций, благодаря чему разработчик сможет сопоставить данные отчеты с исходным кодом.

Профилирование кода

Стоит упомянуть еще одну возможность, которую предоставляет xdebug для поиска узких мест в коде, и которую администратор сервера может использовать без внесения изменений в код сайта. Это профилирование. Профилирование запускается аналогично методу запуска трассировки функций за исключением названия cookie переменной. Вместо XDEBUG_TRACE используется XDEBUG_PROFILE. В результате профилирования мы получаем файл с данными, которые могу быть распознаны утилитой callgrind_annotate из состава vallgrind, а также может быть отображены в графическом виде с помощью утилиты KCacheGrind под KDE или WinCacheGrind под Windows. KCacheGrind среди них – самый богатый по функционалу.

Настроить профилирование по запросу нужно директивами аналогично директивам трассировки функций:

xdebug.profiler_enable=0
xdebug.profiler_enable_trigger=1
xdebug.profiler_output_dir=/var/tmp

Автор: am83

Источник

Поделиться

* - обязательные к заполнению поля