При разработке модулей неизбежно возникают вопросы: почему страница генерируется медленно? Какой SQL-запрос тормозит? Правильно ли работает кеш? Для ответа на эти вопросы платформа Melbis оснащена встроенным отладчиком, который выводит детальную статистику прямо на странице — без сторонних инструментов.
Отладчик активируется через URL: к адресу любой страницы нужно
добавить GET-параметр debug_on_KEY, где KEY —
секретный код, заданный в параметре MELBIS_DEBUG_CODE в
конфигурации (или через «Проектирование → Инсталляция»,
поле «Пароль отладчика»).
Например, если код — pass:
https://site.com/?debug_on_pass
https://site.com/?topic_id=5&debug_on_pass
Состояние отладчика сохраняется в PHP-сессии — после активации он остаётся включённым при любых переходах между страницами сайта. Это позволяет изучать работу разных страниц, не добавляя параметр к каждому URL вручную.
Для отключения — перейти на любую страницу с параметром
debug_off:
https://site.com/?debug_off
Машинный вид — debug_ai_KEY. Тот же
секретный код, но вместо панели страница получает только отчёт — тем же
JSON, который панель даёт по кнопке «Download Reports», в теге
<script type="application/json" id="melbis-report-json">.
Панель со стилями, таблицами и таймлайном при этом не собирается: это
около четырёх пятых её объёма, и читающей программе она не нужна. Режим
действует ровно на один запрос и в сессии не
сохраняется, поэтому включать и выключать его не нужно. Размеры файловых
кешей в этом виде берутся только если их уже посчитала сессия: их
подсчёт обходит каталоги кеша, а разового запроса это не стоит.
Метод
MELBIS()->DebugState()позволяет в коде модуля проверить, включён ли отладчик, и вывести контрольные данные только в режиме отладки без влияния на продакшн:if ( MELBIS()->DebugState() ) { error_log('Значение переменной: ' . print_r($data, true)); }
В самом верху — краткая сводка текущего запроса:
| Параметр | Описание |
|---|---|
| Cache | Включено ли кеширование (On / Off) |
| Compile | Общее время генерации страницы в секундах |
| SQL cnt/time | Количество SQL-запросов / суммарное время их выполнения |
| Load | Нагрузка на сервер: 1 мин / 5 мин / 15 мин |
| Mem peak/lim | Пиковое потребление памяти / лимит PHP |
| Files | Количество подключённых PHP-файлов |
| Cache Static | Три числа: объём записей статического кеша проекта / вся занятая память APCu / размер сегмента APCu |
| Cache Base | Занято / свободно в каталоге базового кеша. Если каталог смонтирован в память (как это делает штатный инсталлятор), второе число — остаток отведённого объёма |
| Cache Trick | Занято / свободно на диске |
Три числа у Cache Static нужны потому, что память APCu общая: между «сколько занял ваш проект» и «сколько всего свободно в сегменте» может лежать что-то ещё. Если второе число близко к третьему, APCu начнёт вытеснять записи, и промахиваться мимо кеша начнут даже те запросы, которые вели себя образцово.
Под сводкой — горизонтальная полоса, которая показывает, как распределилось время генерации страницы между всеми модулями. Каждый модуль закрашен несколькими цветовыми полосами в зависимости от способа, которым был обработан:
| Цвет | Тип выполнения | Описание |
|---|---|---|
| Розовый | Run Native | Модуль выполнился полностью (кеш не использовался) |
| Жёлтый | Paste Lazy | Модуль загружен асинхронно (Lazy Loading) |
| Голубой | Run Smart | Модуль выполнился досрочно по команде Smart-кеша |
| Оранжевый | Cache Trick | Отдан Trick-кеш вместо выполнения |
| Светло-зелёный | Cache Base Pause | Кеш актуален, но ещё в периоде паузы |
| Зелёный | Cache Base Valid | Кеш актуален, взят с диска |
| Серый | Core | Накладные расходы ядра и фреймворка |
Наведя курсор на сегмент, можно увидеть имя модуля, время и процент от общего времени страницы.
Детальная статистика по каждому модулю страницы. Колонки:
| Колонка | Описание |
|---|---|
| Module | Имя модуля и его настройки кеша (Cache On/Off, Pause, Trick, Smart) |
| Paste Lazy | Сколько раз модуль был подставлен как Lazy-заглушка |
| Run Native | Сколько раз модуль выполнился полностью |
| Run Smart | Сколько раз модуль был запущен Smart-кешем досрочно |
| Cache Trick | Сколько раз был отдан Trick-кеш |
| Cache Base Pause | Сколько раз кеш был актуален, но находился в паузе |
| Cache Base Valid | Сколько раз кеш был взят с диска как валидный |
| Total Call | Суммарное количество вызовов модуля на странице |
| Total Time | Суммарное время выполнения модуля |
| SQL in/total | SQL-запросов в одном вызове / всего за все вызовы |
| SQL time | Суммарное время SQL-запросов |
| SQL avg/max | Среднее и максимальное время одного SQL-запроса |
| Content of query for max time | Текст самого медленного SQL-запроса с параметрами |
Ячейки, где произошло реальное выполнение или взятие кеша, подсвечиваются соответствующим цветом — так мгновенно видно, каким путём обработан каждый модуль.
В строке Summary внизу — итоговые суммы по всем модулям страницы.
Раздел подробного анализа кеша для каждого модуля (отображается при включённом кешировании). Для каждого модуля показана строка с заголовком: имя, количество отслеживаемых таблиц и количество вызовов на странице.
Для каждого вызова модуля отображается:
Last update — таблица, которая изменялась последней
среди всех зависимых таблиц, с указанием времени изменения. Показывает,
что именно потенциально могло сбросить кеш. Правее — текущее время
сервера (right now), чтобы видеть актуальную разницу.
Cache state — состояние кеша: включён ли, таймаут, временны́е метки последних папок кеша.
Cache load — каким способом получен результат для этого конкретного вызова:
Run Native — модуль выполнился, кеш создаётся.Run Smart — модуль выполнился досрочно по инициативе
Smart-кеша.Cache Base Valid — кеш актуален, взят с диска.
Показывается время создания кеша и таблица, определившая его
актуальность.Cache Base Pause — кеш актуален по паузе. Видны время
создания кеша, время до конца паузы (ETA) и дедлайн.Cache Trick — отдан Trick-кеш. Указывается причина:
превышение нагрузки (High Server load) или времени
компиляции (Long Page compile), фактическое и допустимое
значения, возраст Trick-кеша.Run info — тайминг конкретного вызова: время начала
(Start), конца (End), длительность
(Run) и доля в общем времени страницы (Part).
Ниже — входные параметры, с которыми был вызван модуль.
Накопительная статистика работы кеша за всё время наблюдения (хранится в APCu). Показывается если у хотя бы одного модуля включён Smart-кеш. Именно эти данные Smart-кеш использует для принятия решений о досрочном обновлении.
Для каждого модуля — строка с его настройками кеша и семь колонок
статистики в формате Cnt/Sum/Avg (количество / суммарно /
среднее):
| Колонка | Описание |
|---|---|
| Paste Lazy | Статистика Lazy-вызовов |
| Run Native | Статистика реальных выполнений |
| Run Smart | Статистика досрочных Smart-запусков |
| Cache Base Valid | Статистика выдачи актуального кеша |
| Cache Base Pause | Статистика выдачи кеша в состоянии паузы |
| Cache Trick | Статистика использования Trick-кеша |
| Total Run + Cache | Суммарная статистика всех вызовов (Cnt/Sum/Avg/Min/Max) |
Под цифрами для каждой колонки отображается мини-гистограмма (полоска пропорциональная максимуму по колонке), что позволяет быстро увидеть аномалии и дисбаланс без вглядывания в числа.
Кнопка «Сброс Smart-данных модуля» в IDE очищает накопленную статистику конкретного модуля.
Разбор кеша отдельных SQL-запросов (SqlSelectStatic,
SqlSelectStaticFlat). Записи группируются по тексту
запроса: одна строка — один SQL, независимо от того, с какими
параметрами его вызывали. Сортировка по занимаемому объёму, самые
тяжёлые сверху.
| Колонка | Описание |
|---|---|
| Query | Код группы — короткий хеш текста запроса. Под кодом стоит вердикт (см. ниже) |
| Size | Сколько памяти занимает группа |
| Share, % | Доля от всего статического кеша проекта |
| Variants | Сколько различных наборов параметров встретилось |
| Stale, % | Доля мёртвых записей: таблица-источник уже изменилась, попасть в них невозможно, память они занимают до истечения срока хранения |
| Hit/Var | Попаданий на вариант: среднее и медиана |
| Saved | Оценка сэкономленного процессорного времени: замер запроса, умноженный на число попаданий |
| Unused, % | Доля памяти, занятой записями с одним попаданием или без единого |
| Refresh reason | Таблицы, которые уже сбросили записи этой группы: имя, число убитых записей и сколько времени прошло с изменения. Пока ничего не протухло — пусто |
| Unit:line | Модуль и строка, где запись была создана. SYSTEM
означает вызов из корневого скрипта, до старта модулей |
| Cached query | Текст запроса |
Это главная пара чисел таблицы, и смотреть надо именно на расхождение между ними.
24 : 18 — числа рядом, попадания размазаны по вариантам
равномерно, кеш работает как задумано.
5 : 1 — среднее нарисовано горсткой «горячих» вариантов,
а типичная запись отработала ровно один раз. Именно так выглядит
ситуация, когда запрос закешировали с чересчур разнообразными
параметрами: суммарные попадания набегают за счёт количества, а
большинство записей просто занимает память. Одно среднее этого не
покажет — оно будет выглядеть прилично.
Сколько при этом тратится памяти, отвечает колонка Unused, %.
| Вердикт | Когда | Что делать |
|---|---|---|
| Waste | ни одного попадания | записи создаются и никогда не читаются — кеш здесь не нужен |
| Bloat | доля памяти больше 25% и Unused больше 50% | запрос кешируется со слишком разнообразными параметрами; сузить набор или отказаться от кеша |
| Good | сэкономлено больше секунды | окупается, даже если попаданий немного |
| Churn | протухло больше половины записей | источник меняется чаще, чем живёт кеш; уменьшить срок хранения |
| Good | больше пяти попаданий на вариант | окупается частотой |
| Poor | меньше двух попаданий на вариант | почти не переиспользуется |
| OK | во всех остальных случаях |
Проверки идут в этом порядке, и Good по сэкономленному
времени намеренно стоит выше частотных: тяжёлый запрос,
вызванный десять раз, разгружает сервер сильнее, чем лёгкий, вызванный
тысячу раз, и получить за это Poor он не должен.
Отчёт показывается только при включённом кешировании (
MELBIS_CACHE): при выключенномSqlSelectStaticработает как обычныйSqlSelect, новых записей не появляется и показывать нечего.
Под строкой сводки есть кнопка Download Reports. Она скачивает JSON-файл со всеми исходными данными панели — не картинку и не разметку таблиц, а те самые числа, из которых таблицы построены.
Что внутри:
Файл ничего не пишет на сервер: данные встроены в саму страницу отладчика, браузер собирает файл на стороне клиента. Значений, лежащих в кеше, в выгрузке нет — только метаданные, поэтому размер остаётся в пределах десятков килобайт.
Основной сценарий, ради которого выгрузка и сделана: файл можно отдать языковой модели и попросить разобрать. По скриншоту так не поработаешь — модель видит только то, что попало в кадр, и не может ни отсортировать, ни сопоставить разделы между собой.
Что осмысленно спрашивать:
Последний пункт — самый практичный. Скачайте файл до оптимизации и после, отдайте оба: разница по конкретным числам видна куда лучше, чем по ощущениям от скорости страницы.
В выгрузку попадают тексты SQL-запросов, имена модулей и структура таблиц вашего проекта. Это не пароли и не данные покупателей, но и не то, что стоит выкладывать публично, — относитесь к файлу как к внутренней технической информации.
RunTimeДля точечного измерения времени выполнения произвольного участка кода:
// Засечь начало
$start = MELBIS()->RunTime();
// ... код, который нужно измерить ...
// Получить прошедшее время
$elapsed = MELBIS()->RunTime($start);
if ( MELBIS()->DebugState() )
{
error_log("Расчёт скидок: {$elapsed}s");
}RunTime() без аргумента возвращает текущую временну́ю
метку. RunTime($start) — разницу между текущим временем и
$start.
При разработке AJAX-модулей и веб-модулей обычная отладка через вывод в браузер не работает — результат уходит в JavaScript и там теряется. В таких случаях удобно сохранить промежуточные данные в файл:
// Сохранить массив $data в файл dump.htm рядом с шаблонами модуля
MELBIS()->TplDumpVar('dump', $data);Файл создаётся в каталоге шаблонов текущего модуля. После отладки не забудьте удалить вызов.
Найти медленный модуль. Посмотреть строку сводки
(Compile) и временну́ю шкалу — розовые
(Run Native) сегменты с наибольшей шириной указывают на
самые долгие выполнения. В таблице «Page Modules» найти строку с
максимальным Total Time и изучить
Content of query for max time.
Проверить что кеш работает правильно. В таблице
«Page Modules» у большинства модулей должны быть ненулевые значения в
колонках Cache Base Valid или
Cache Base Pause. В «Modules details» для каждого вызова
должно стоять Cache Base Valid или
Cache Base Pause, а не Run Native.
Понять, почему кеш сбрасывается слишком часто. В
«Modules details» смотреть колонку Last update — там видно,
какая таблица изменялась последней и в какое время. Если кеш
сбрасывается по таблице, которая не должна влиять — возможно, она лишняя
в списке зависимостей модуля.
Оценить эффективность Smart-кеша. В «Cache Smart
Monitor» соотношение Run Smart к Run Native
показывает, насколько Smart-кеш успевает обновлять данные в фоне, не
допуская «холодного» запуска при живых посетителях.
Проверить срабатывание Trick-кеша. В таблице «Page
Modules» колонка Cache Trick должна быть ненулевой только
при реально высокой нагрузке. Если Trick срабатывает постоянно при
нормальной нагрузке — нужно пересмотреть пороги в настройках модуля.
Найти запрос, который зря занимает память. В таблице
«Static Cache» отсортированы по объёму — смотреть сверху. Тревожное
сочетание: большая Share, %, высокий Unused, %
и расхождение среднего с медианой в Hit/Var. Это значит,
что запрос закеширован со слишком разнообразными параметрами: попаданий
вроде много, но приходятся они на горстку вариантов, а остальные записи
просто лежат. Движок помечает такую строку вердиктом
Bloat.
Понять, стоит ли вообще кешировать запрос. Смотреть
Saved — оценку сэкономленного процессорного времени.
Дешёвый запрос с тысячей попаданий может экономить меньше, чем тяжёлый с
десятком; в первом случае кеш занимает память почти без пользы.
Сравнить состояние до и после правки. Скачать отчёт кнопкой Download Reports перед оптимизацией и после неё, затем отдать оба файла языковой модели с просьбой показать разницу. По числам это видно точнее, чем по субъективному ощущению скорости.