Статьи

Углубленное прохождение приложений Supercharging с Blackfire

Никто не ненавидит надежные и масштабируемые приложения, особенно когда база данных быстро растет, и миллионы запросов должны обслуживаться ежедневно. Профилирование — это форма анализа программы для измерения времени и ресурсов, потребляемых программой. С помощью профилирования мы можем определить узкие места производительности в коде и что-то с ними сделать. Существует множество инструментов для профилирования, каждый из которых использует свой подход.

Наддувная разработка

Существует два основных типа методов профилирования: отбор проб и приборостроение .

В методе выборки профилировщик берет выборки из стека вызовов или памяти через определенные промежутки времени и обновляет свою статистику. Этот метод оказывает меньшее влияние на производительность, потому что он не изменяет код вообще. Однако выборка имеет свои издержки, которые можно настроить, увеличив частоту дискретизации.

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

Blackfire.io — это новое поколение веб-профилировщиков, которое использует автоматический инструментальный подход, но не влияет на производительность нашего приложения. Он был разработан Sensio Labs , командой разработчиков Symfony Framework .

Особенностью Blackfire является то, что он помогает нам постоянно тестировать производительность нашего приложения, не добавляя ни одной строки кода.

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

Blackfire прост в установке, поскольку поддерживается многими поставщиками облачных серверов и виртуальными машинами, включая Homestead. В этом уроке мы узнаем, как мы можем использовать Blackfire для создания более быстрых приложений. Как обычно, мы будем использовать Homestead Improved для настройки среды разработки.

Начиная

После того, как виртуальная машина загружена, и нам удалось войти в систему с помощью vagrant ssh , мы можем начать использовать Blackfire!

Но подождите, сначала нам нужно создать учетную запись Blackfire здесь . Если у нас его уже есть, мы можем продолжить, поместив наши учетные данные homestead.yaml файл homestead.yaml , который находится в корневом каталоге нашего блока Vagrant.

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

Учетные данные делятся на две категории: Client Credentials и Server Credentials .

Мои учетные данные

Нам нужно раскомментировать настройки Blackfire в нашем файле Homestead.yaml и поставить учетные данные на место:

homestead.yml

 blackfire: - id: "Server Id here" token: "Server token here" client-id: "Client Id here" client-token: "Client token here" 

Строительные блоки Blackfire

Blackfire состоит из пяти основных компонентов:

  • Probe — это расширение PHP, которое инструментирует приложение и собирает информацию о производительности (в настоящее время работает на Linux и macOS )
  • Агент — это серверный демон, который собирает и перенаправляет информацию профиля в Blackfire .
  • Companion — это расширение Google Chrome, используемое для запуска профилировщика из браузера; это может быть установлено с этого URL .
  • Клиент — это эквивалент командной строки Companion, который мы используем для профилирования API, веб-сервисов, веб-страниц и т. Д.
  • Веб-интерфейс сравнивает и визуализирует информацию профиля в графических диаграммах и в табличных форматах.

Датчик, Агент, Клиент предварительно установлены, если мы используем коробку Homestead Improved Vagrant.

Некоторые условия, чтобы знать, прежде чем мы начнем

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

  • Эксклюзивное время: количество времени, затрачиваемое на выполнение функции / метода без учета времени, потраченного на внешние вызовы.

  • Включенное время: общее время, затраченное на выполнение функции, включая все внешние вызовы.

  • Горячие пути: Горячие пути — это части нашего приложения, которые были наиболее активными во время профиля. Это могут быть части, которые занимают больше памяти или занимают больше процессорного времени.

Профилирование скрипта

В этом разделе давайте профилируем простой PHP-скрипт, чтобы увидеть, насколько он быстрый или медленный. Чтобы получить реалистичные результаты профиля, мы напишем небольшой скрипт PHP, который содержит взаимодействия с базой данных и вызовы функций. Сценарий вставляет 1000 строк случайных пользовательских данных в таблицу базы данных.

Генерация фиктивных данных

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

 composer require fzanintto / faker 

Затем мы создаем скрипт поставщика данных, который заполняет файл JSON фиктивными данными. Мы будем использовать этот файл JSON в нашем основном скрипте PHP.

Почему мы не делаем это в нашем основном скрипте? Если мы используем Faker в нашем основном скрипте, результат профиля также будет включать все операции библиотеки Faker . Это усложнит анализ нашего профиля, в то время как для этого урока нам понадобится нечто более скромное.

Назовем файл UserProviderJSON.php :

 <?php require_once ( 'vendor/autoload.php' ) ; $num = isset ( $_GET [ 'num' ] ) ? $_GET [ 'num' ] : 1000 ; $data = [ ] ; $faker = Faker\ Factory : : create ( ) ; if ( ! file_exists ( 'data' ) ) { mkdir ( 'data' ) ; } for ( $i = 0 ; $i < $num ; $i ++ ) { $data [ ] = [ 'name' = > $faker - > name , 'email' = > $faker - > email , 'city' = > $faker - > city , ] ; } file_put_contents ( 'data/users.json' , json_encode ( $data ) ) ; echo 'JSON file generated.' ; 

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

Затем мы можем запустить скрипт поставщика данных с помощью php UserProviderJSON.php .

В результате файл с именем users.json будет создан в каталоге data корневого каталога нашего проекта. Этот файл должен содержать 1000 записей случайной пользовательской информации в формате JSON.

Настройка базы данных MySQL

Если до сих пор все прошло хорошо, мы можем создать базу данных MySQL для хранения данных.

Затем мы запускаем клиент командной строки MySQL:

 mysql -h localhost -u homestead -psecret 

Теперь мы создаем базу данных с именем blackfire_tutorial :

 CREATE DATABASE blackfire_tutorial ; USE blackfire_tutorial ; 

И стол:

 CREATE TABLE IF NOT EXISTS `sample_users` ( ` id ` int ( 11 ) NOT NULL , `name` varchar ( 255 ) DEFAULT NULL , `email` varchar ( 255 ) DEFAULT NULL , `city` varchar ( 255 ) DEFAULT NULL ) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; 

Написание основного сценария

Давайте benchmark-before.php это benchmark-before.php :

 <?php $db = new PDO ( 'mysql:host=localhost;dbname=blackfire_tutorial;charset=utf8' , 'homestead' , 'secret' ) ; function SaveCustomers ( $db ) {  // Providing data $users = userProvider ( ) ; for ( $i = 0 ; $i < count ( $users ) ; $i ++ ) { foreach ( $users [ $i ] as $key = > $value ) { $ $key = addslashes ( $value ) ; } $db - > exec ( "INSERT INTO sample_users (name, email, city) VALUES ('$name', '$email', '$city')" ) ; } } function userProvider ( ) { return json_decode ( file_get_contents ( 'data/users.json' ) , true ) ; } // Storing data saveCustomers ( $db ) ; echo 'Users imported successfully.' ; 

В предыдущем коде мы создаем функцию с именем saveCustomers() , которая принимает объект PDO в качестве аргумента. saveCustomers() вызывает userProvider() для загрузки содержимого data/users.json в массив.

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

Чтобы запустить скрипт, мы получаем к нему доступ через относительный URL-адрес /benchmark-before.php . Если все в порядке, таблица MySQL будет заполнена тысячей строк случайной пользовательской информации.

Чтобы проверить, была ли операция успешной:

 mysql - h localhost - u homestead - psecret use blackfire_tutorial ; select count ( id ) as rows from sample_users ; 

Ожидаемый результат должен быть следующим:

 +- -- -- -- + | rows | +- -- -- -- + | 1000 | +- -- -- -- + 1 row in set ( 0.01 sec ) 

Запуск первого профиля

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

Чтобы профилировать страницу, на странице benchmark-before.php мы нажимаем значок benchmark-before.php на панели инструментов браузера. Затем в поле « Compare With выберите « Create New Reference и нажмите « Profile .

Запуск первого профиля

Примечание. Для использования Companion Chrome Extension сначала необходимо убедиться, что мы вошли в Blackfire.io .

Если все настроено правильно, мы должны увидеть панель инструментов Blackfire в верхней части страницы.

Чтобы увидеть подробности профиля, мы нажимаем на View profile в правой части панели инструментов Blackfire.

Анализ информации профиля

После нажатия на кнопку « View profile мы будем перенаправлены на веб-интерфейс, где можно найти все подробности об этом профиле:

веб интерфейс

Веб-интерфейс состоит из нескольких частей:

Панель инструментов

Панель инструментов содержит сводную информацию о профиле, включая время стены, время ввода-вывода, использование памяти, время процессора и т. Д. Для нашего первого профиля панель инструментов содержит следующую информацию:

blackfire_toolbar

 | Wall Time | I / O | CPU Time | SQL Queries | | -- -- -- -- -- - | -- -- -- -- | -- -- -- -- -- | -- -- -- -- -- -- -- -- -- | | 578 ms | 541 ms | 36.8 ms | 556 s / 1000 rq | 

Диаграмма графа вызовов

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

Диаграмма графа вызовов

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

Цветные границы показывают горячие пути приложения (части нашего приложения, которые были наиболее активны во время профиля), в то время как цветные фоны показывают наиболее интенсивные узлы. Во многих случаях горячие пути не подразумевают узкое место в производительности, поскольку некоторые подпрограммы обычно делают тяжелую работу в нашем приложении. Тем не менее, это хорошее место, чтобы начать при попытке найти узкие места.

Быстрый взгляд на график позволяет определить наиболее активные узлы. В нашем случае saveCustomers() использовало 99,27% (включая внешние вызовы) от общего времени.

PDO :: exec (вызываемый saveCustomers() ) является наиболее интенсивным узлом в графе, так как мы вызывали этот метод тысячу раз из saveCustomers() . Это заняло 92,56% от общего времени, проведенного, согласно графику!

Список функций

Список функций

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

В качестве примера, мы нажимаем на узел saveCustomers() . Согласно скриншоту выше, мы видим, что эта функция была вызвана один раз (1 вызывающая сторона ) и имеет четыре вызываемых (внешние вызовы функций / методов).

Есть также несколько горизонтальных гистограмм, показывающих время включения / исключения для этого узла в каждом измерении (время стены, время ввода-вывода, память, время ЦП и т. Д.). Более темный оттенок показывает эксклюзивное время, в то время как более светлый оттенок показывает инклюзивное время. Если мы наведем указатель мыши на столбцы, мы увидим время / процент для каждого из этих измерений.

Мы также можем видеть абонентов этой функции, нажав на кнопку под меткой 1 Callers (1 звонки) .

Если прокрутить немного вниз, мы также увидим вызываемого абонента . Над меткой 4 абонентов , рядом друг с другом есть несколько кнопок разной ширины. Нажав на каждую из этих кнопок, мы увидим информацию о производительности для каждого внешнего вызова из saveCustomer() . Например, userProvider() является одним из абонентов saveCustomers() .

вызываемые методы

Метрики черного огня

Blackfire предоставляет некоторые метрики из коробки, что позволяет нам оценивать производительность нашего приложения и с других точек зрения, таких как запросы SQL, соединения PDO, размер ответа HTTP, количество скомпилированных и выполненных файлов, и это лишь некоторые из них.

метрика

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

Оптимизация существующего кода

Хорошо, теперь давайте сделаем несколько микрооптимизаций для существующего скрипта, чтобы немного улучшить производительность. В существующем коде мы делаем один запрос к базе данных на каждую запись, что означает 1000 взаимодействий с базой данных во время каждого запроса. Что если бы мы могли сократить это число только до одного запроса? Рассмотрим следующее утверждение:

 INSERT INTO persons (name, email, city) VALUES (?, ?, ?),(?, ?, ?),(?, ?, ?),(?, ?, ?)...(?, ?, ?) 

В приведенном выше утверждении все значения объединяются в группы скобок (разделенных запятыми). Это утверждение, очевидно, будет использовать больше памяти, но будет намного быстрее, чем существующее. Давайте посмотрим на это в действии:

 <?php $db = new PDO ( 'mysql:host=localhost;dbname=blackfire_tutorial;charset=utf8' , 'homestead' , 'secret' ) ; function SaveCustomers ( $db ) {  // Providing data $users = userProvider ( ) ; $params = [ ] ; $num = count ( $users ) ; $placeholders = rtrim ( str_repeat ( '(?, ?, ?), ' , $num ) , ', ' ) ; for ( $i = 0 ; $i < $num ; $i ++ ) { $params [ ] = $users [ $i ] [ 'name' ] ; $params [ ] = $users [ $i ] [ 'email' ] ; $params [ ] = $users [ $i ] [ 'city' ] ; } $q = $db - > prepare ( 'INSERT INTO sample_users (name, email, city) VALUES ' . $placeholders ) ; $q - > execute ( $params ) ; unset ( $params ) ; unset ( $placeholders ) ; } function userProvider ( ) { return json_decode ( file_get_contents ( 'data/users.json' ) , true ) ; } //Saving data saveCustomers ( $db ) ; echo 'Users imported successfully.' ; 

Мы вызываем этот файл benchmark-after.php и запускаем его в браузере. Теперь мы снова запускаем профилировщик. На этот раз в поле « Сравнить с выбором» мы выбираем наш справочный профиль.

Выбор профиля

Когда профилирование закончено, мы нажимаем на View Comparisons чтобы перейти к веб-интерфейсу.

Панель инструментов второго профиля

Как мы видим, веб-интерфейс немного изменился, поскольку мы сравниваем два разных профиля. Бегло взглянув на панель инструментов, мы увидим, что время стены было значительно оптимизировано на 98%578 мс до 14 мс ), а количество запросов к базе данных уменьшено до одного! Использование памяти увеличилось на 97% (2,42 МБ).

Если производительность была увеличена по сравнению с эталонным профилем, соответствующие значения на панели инструментов должны быть окрашены в зеленый цвет. Красный означает, что производительность была снижена (в нашем случае использование памяти):

 | Wall Time | I / O | CPU Time | Memory | SQL Queries | | -- -- -- -- -- - | -- -- -- -- | -- -- -- -- -- | -- -- -- -- | -- -- -- -- -- -- -- -- -- | | -98 % | -98 % | -89 % | + 117 % | -99 % / -999 rq | 

Информация панели инструментов во втором прогоне профиля отображает процентные различия (между текущим профилем и эталоном). Чтобы просмотреть информацию о текущем профиле, переключите Delta переключатель в правой части панели инструментов:

Дельта-переключатель

Глядя на график, мы также видим, что производительность улучшилась. Узлы на этой диаграмме окрашены в синий цвет. Отрицательное значение в каждом узле означает количество времени, которое мы сохранили.

Синяя диаграмма

В нашем случае больше всего пострадали PDO :: exec (время сократилось на 555 мс). Нажав на узел, мы можем увидеть его детали в левой панели. Информация о производительности этого профиля и эталонного профиля отображаются рядом .

Нажав на вкладку метрики, мы можем увидеть улучшения с других точек зрения. Например, в нашем последнем запуске профиля количество PDO Queries было уменьшено до одного.

Метрики запросов

Ну, это был не урок в настройке производительности, но достаточно хорош, чтобы поцарапать поверхность Blackfire.

Использование интерфейса командной строки

Наряду с Companion, Blackfire предоставляет изящную утилиту командной строки под названием blackfire позволяющую нам профилировать любой скрипт PHP, включая веб-страницы, веб-сервисы, вызовы API или сценарии командной строки прямо из терминала.

Профилирование HTTP-запросов

Для профилирования веб-страницы из командной строки мы используем curl за которой следует URL страницы:

 blackfire curl http : / / 192.168 . 10 . 10 / benchmark - before . php 

В результате Blackfire выводит некоторую информацию о производительности вместе с URL-адресом веб-интерфейса:

 Profile URL : https : / / blackfire . io / profiles / b8fceed1 - 06be - 4a0f - b28f - 7841457e0837 / graph Total time : 628 ms CPU time : 74 ms I / O : 554 ms Memory : 1.23 MB Network : n / a SQL : 570 ms 1000 rq 

Чтобы получить более точные результаты, мы можем взять несколько сэмплов одного и того же запроса, передав опцию --sample , а затем количество --sample сэмплов. По умолчанию Blackfire берет 10 образцов, поэтому не удивляйтесь, если ваша таблица базы данных будет содержать 11 000 строк после первого запуска профиля.

 blackfire -- sample 15 curl http : / / 192.168 . 10 . 10 / benchmark - before . php 

Мы также можем создать новый ссылочный профиль так же, как мы использовали Companion:

 blackfire -- new - reference curl http : / / 192.168 . 10 . 10 / benchmark - before . php 

Или сравните его с ранее созданным ссылочным профилем. Для этого мы передаем --reference последующим идентификатором ссылочного профиля:

 blackfire -- reference = 7 curl http : / / 192.168 . 10 . 10 / benchmark - after . php 

Идентификатор ссылочного профиля доступен в веб-интерфейсе или как часть вывода профиля при использовании параметра --new-reference .

Скрипты профилирования CLI

Используя утилиту blackfire , мы также можем профилировать любой скрипт командной строки. Это возможно с помощью run :

 blackfire run php benchmark - before . php 

Все параметры, используемые с curl также можно использовать с run .

Тесты производительности

Еще одной замечательной особенностью Blackfire является постоянное тестирование производительности. Как упоминалось ранее, Blackfire предоставляет различные метрики из коробки, которые мы можем использовать для написания тестов производительности. Эта функция доступна только для премиум-пользователей, но она также доступна в виде двухнедельной пробной версии. Утверждения могут быть во временных измерениях или других измерениях, таких как количество запросов к базе данных, использование памяти или размер ответа.

Все тесты должны быть в .blackfire.yml в корневом каталоге нашего проекта.

Тест Blackfire похож на следующий код:

 tests: "Pages should be fast enough": path: "/benchmark-before.php" # run the assertions for all HTTP requests assertions: - "main.wall_time < 100ms" # wall clock time is less than 100ms 

Как видим, все тесты должны находиться под основным ключом tests .

Тест состоит из следующих компонентов:

  • Имя (в приведенном выше примере: страницы должны быть достаточно быстрыми )
  • Регулярное выражение (путь), которому должен соответствовать весь HTTP-запрос, для выполнения теста.
  • Набор утверждений, которые состоят из метрик и значений утверждений .

Каждый раз, когда профилировщик запускается для проекта, содержащего файл .blackfire.yml , Blackfire автоматически запускает все тесты и отображает результат в веб-интерфейсе (вкладка « Утверждение » на левой панели).

Проверка утверждения

В приведенном выше примере тест выполняется для benchmark-before.php . main.wall_time — это показатель main.wall_time для общего времени, необходимого для выполнения сценария. В приведенном выше утверждении мы проверяем, меньше ли оно 100ms :

Вот еще один пример с дополнительными утверждениями из документации Blackfire :

 tests: "Homepage should not hit the DB": path: "/" # only apply the assertions for the homepage assertions: - "metrics.sql.queries.count == 0" # no SQL statements executed - "main.peak_memory < 10mb" # memory does not exceed 10mb - "metrics.output.network_out < 100kb" # the response size is less than 100kb 

Вышеуказанный тест запускается для домашней страницы ( / ). В утверждениях мы удостоверяемся, что на домашней странице не делается никаких запросов к базе данных, использование памяти не превышает 10 MB а размер ответа — менее 100 KB .

Чтобы узнать больше об утверждениях, обратитесь к справке об утверждениях .

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

Примечание: для проверки тестов мы можем использовать Валидатор Blackfire .

Завершение

Blackfire.io — это мощный веб-профилировщик, который обрабатывает приложения, не добавляя ни одной строки кода. Он состоит из пяти основных компонентов: Probe, Agent, The Companion, инструмент CLI и веб-интерфейс. Зонд и Агент несут ответственность за ввод кода и пересылку результатов профиля на сервер Blackfire.

Мы можем профилировать приложение, используя Companion или blackfire командной строки blackfire .

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