Сегодня разберу какие изменения я внёс в модуль отладки от open4dev. Изменения простые и будут вполне понятны начинающим разработчикам. Чем меня не устраивал модуль отладки, я описывал в одном из постов.
Ключевые слова MySQL
Подсветка синтаксиса в модуле уже была. Вот она.
1 |
define('DEBUG_SQL_KEYWORDS', 'select|delete|update|count|as|from|and|order by|or|where|inner join|left join|right join|join|group by|having|on|asc|desc|limit'); |
Всё самое нужное уже есть. Можно сюда было загнать сразу все ключевые слова SQL, но время деньги, а ещё нужно пост писать. Так что я ограничился только тем, что сразу бросилось в глаза при анализе главной страницы Опенкарта с включённым модулем.
Находим 45 строку и меняем её на такую.
1 |
define('DEBUG_SQL_KEYWORDS', 'select|delete|update|count|as|from|and|order by|or|where|distinct|replace|into|date_sub|now|interval|hour|sum|lcase|set|inner join|left join|right join|join|group by|having|on|asc|desc|limit'); |
Я добавил несколько ключевых слов и встроенных функций. Результат на картинке ниже.
Добавление строки вызова
Как я уже рассказывал, делается через замечательный back_trace(). Для начала нужно добавить в класс QueryDebug поле для хранения строки. Находим такой участок кода.
1 2 3 4 5 6 |
class QueryDebug { private $queries = array(); public function add($sql, $duration = 'not_defined') { $this->queries[] = array('text' => $sql, 'duration' => $duration); } |
И превращаем его.
1 2 3 4 5 6 |
class QueryDebug { private $queries = array(); public function add($sql, $duration = 'not_defined', $backtrace = array()) { $this->queries[] = array('text' => $sql, 'duration' => $duration, 'backtrace' => $backtrace); } |
Этим мы научили объект QueryDebug сохранять массив $backtrace в массиве отчётах о запросах $queies[]. Теперь нужно поправить вызов метода add(). Его можно найти дальше в модуле.
1 2 3 4 5 6 7 8 9 10 11 12 |
<!-- Wrap the query operation to count the time took and add it to our class (< 2.2.0.0)--> <operation> <ignoreif>$this->adaptor->query</ignoreif> <search><![CDATA[return $this->db->query($sql);]]></search> <add position="replace"><![CDATA[ $time_start = microtime(true); $result = $this->db->query($sql); $time_end = microtime(true); $this->queryDebug->add($sql, ($time_end - $time_start)); return $result; ]]></add> </operation> |
Этот участок модуля для ocmod отвечает за замену строки возврата результата запроса к БД в опенкарте младше 2.2.0.0, на наш, который перехватывает запрос и сохраняет его в лог, попутно считая время выполнения. Добавим в лог информацию о стеке вызовов вызвав debug_backtrace() внутри этого метода.
1 2 3 4 5 6 7 8 9 10 11 12 |
<!-- Wrap the query operation to count the time took and add it to our class (< 2.2.0.0)--> <operation> <ignoreif>$this->adaptor->query</ignoreif> <search><![CDATA[return $this->db->query($sql);]]></search> <add position="replace"><![CDATA[ $time_start = microtime(true); $result = $this->db->query($sql); $time_end = microtime(true); $this->queryDebug->add($sql, ($time_end - $time_start),debug_backtrace()); return $result; ]]></add> </operation> |
В опенкарте старше 2.2.0.0 вызов метода query() немного изменился и модуль это учитывает. Отдельная ветка изменений для свежего opencart 2.3.
Было:
1 2 3 4 5 6 7 8 9 10 11 12 |
<!-- Wrap the query operation to count the time took and add it to our class (>= 2.2.0.0)--> <operation> <ignoreif>$this->db->query</ignoreif> <search><![CDATA[return $this->adaptor->query($sql, $params);]]></search> <add position="replace"><![CDATA[ $time_start = microtime(true); $result = $this->adaptor->query($sql, $params); $time_end = microtime(true); $this->queryDebug->add($sql, ($time_end - $time_start)); return $result; ]]></add> </operation> |
Стало после изменений в нашем коде.
1 2 3 4 5 6 7 8 9 10 11 12 |
<!-- Wrap the query operation to count the time took and add it to our class (>= 2.2.0.0)--> <operation> <ignoreif>$this->db->query</ignoreif> <search><![CDATA[return $this->adaptor->query($sql, $params);]]></search> <add position="replace"><![CDATA[ $time_start = microtime(true); $result = $this->adaptor->query($sql, $params); $time_end = microtime(true); $this->queryDebug->add($sql, ($time_end - $time_start),debug_backtrace()); return $result; ]]></add> </operation> |
На этом изменения движка закончены. Пора приступить к выводу полезной информации.
Сортировка и вывод запросов SQL
Определимся, что нужно для вывода. Для начала неплохо, что бы файл вызова с указанием строки находился под SQL запросом. Потом нужна, конечно же, кнопка для группировки по файлам и неплохо бы сохранить аутентичные стили.
Стили самое простое — с них и начнём. Находим строку с определением стиля для вывода запросов.
1 |
$out .= ".query { white-space: normal; background-color: #efefef; margin-left: 90px; padding: 4px; border: 1px solid #bbbbbb; } " ."\n"; |
и просто добавляем к определнию стиля свой, пусть будет называться .line
1 |
$out .= ".query,.line { white-space: normal; background-color: #efefef; margin-left: 90px; padding: 4px; border: 1px solid #bbbbbb; } " ."\n"; |
Со стилями разобрались, давайте добавим кнопку. Находим строку с объявлениями кнопок.
1 |
$out .= '<div class="total">Queries: ' . count($this->queries) . ', Total time: ' . number_format($total_time * 1000, 3) . ' ms [<a id="query-sort-time" href="#">By time</a>] [<a id="query-sort-id" href="#">By id</a>]</div>'; |
И добавляем новую кнопку [By line]
1 |
$out .= '<div class="total">Queries: ' . count($this->queries) . ', Total time: ' . number_format($total_time * 1000, 3) . ' ms [<a id="query-sort-time" href="#">By time</a>] [<a id="query-sort-id" href="#">By id</a>] [<a id="query-sort-line" href="#">By line</a>]</div>'; |
Ну вот. Кнопка на месте. Теперь нужно вывести саму строку с указанием файла и номера строки. Не забываем про css класс .line.
Находим
1 |
$out .= '<div class="qrow"><div id= "c' .$count. '" class="count">' . $count . ' <strong>(' . number_format($q['duration'] * 1000, 3) . ')</strong> ' . '</div> <div id="q' .$count. '" class="query">' . $sql . '</div></div>'; |
Меняем на
1 |
$out .= '<div class="qrow"><div id= "c' .$count. '" class="count">' . $count . ' <strong>(' . number_format($q['duration'] * 1000, 3) . ')</strong> ' . '</div> <div id="q' .$count. '" class="query">' . $sql . '</div><div id="l' .$count. '" class="line">'.$q['backtrace'][0]['file'].':'.$q['backtrace'][0]['line'].'</div></div>'; |
Из кода видно как собирается строка с указанием файла и линии вызова. Теперь нужно добавить обработчик на нашу кнопку и отсортировать результаты. Всё очень просто.
Для начала находим хвости JS кода.
1 2 3 4 |
$out .= ' $(".queries.qrow").remove(); ' . "\n"; $out .= ' $(".queries").append(sorted); ' . "\n"; $out .= " return false; \n"; $out .= "}); \n"; |
А теперь перед ним вешаем наш обработчик.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$out .= '$("#query-sort-line").click(function(e) {' ."\n"; $out .= " e.stopPropagation(); \n"; $out .= " var sorted = $('.qrow').sort( function(a, b) { \n"; $out .= " var d = 0;\n"; $out .= " var t1 = $(a).find('.line').prop('innerHTML');\n"; $out .= " var file1 = t1.slice(0,t1.lastIndexOf(':'))\n"; $out .= " var file1Line = t1.slice(t1.lastIndexOf(':'))\n"; $out .= " var t1id = $(a).find('.count').prop('id').replace('c', '') || 0;\n"; $out .= " var t2 = $(b).find('.line').prop('innerHTML');\n"; $out .= " var file2 = t2.slice(0,t2.lastIndexOf(':'))\n"; $out .= " var file2Line = t2.slice(t2.lastIndexOf(':'));\n"; $out .= " var t2id = $(b).find('.count').prop('id').replace('c', '') || 0;\n"; $out .= " d = file1.localeCompare(file2);\n"; $out .= " if (d==0){\n"; $out .= " d = file1Line.localeCompare(file2Line);\n"; $out .= " if (d==0){\n"; $out .= " d = t1id - t2id;\n"; $out .= " };\n"; $out .= " };\n"; $out .= " return d;\n"; $out .= " });\n"; |
Выглядит страшно и ужасно, но работает надёжно. Сначала получаем нужные нам для последующий сортировки параметры: путь к файлу, номер строки, порядковый номер вызова. Сначала сравниваем имена файлов, если они совпали, значит сравниваем номер строки вызова. Если же и номер совпадает, то просто сортируем по ID. Это позволяет быстро оценить масштабы бедствия.
Вывод максимального объёма выделенной памяти
В добавок ко всему выше написанному можно в две строчки добавить вывод. Добавить к стилю .total, стиль .memory
1 |
$out .= ".total,.memory { font-weight: bold; margin: 0px 10px 10px 95px; border-bottom: 1px solid #bbbbbb;} " ."\n"; |
А перед выводом кнопок сделать вывод memory_get_peak_usage().
1 |
$out .= '<div class="memory">Total memory used: '.round(memory_get_peak_usage()/1024/1024,2).' MB</div>'; |
Репозиторий проекта на github — Opencart-debug-database-queries