[Мессенджеры, Open source, Системное администрирование, PHP, Программирование] Введение в метрики для PHP разработчика
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет. Я php разработчик и в свободное время пишу телеграм ботов. Зачастую они требуют дополнительного мониторинга работоспособности, который я реализую через связку Prometheus + Grafana. Когда я решил писать статью про метрики, я сначала планировал досконально описать пошагово как настроить окружение, как разворачивать, как строить графики. Но прикинув объем материала я решил пойти по пути наименьшего сопротивления. Сделать простое приложение, засунуть его в докер, и попутно обвешать его всем необходимым. Что бы любой желающий мог самостоятельно поднять его у себя на домашней машине и посмотреть, как это работает. В итоге за вечер написал подобие магазина в виде телеграм бота. Цель статьи познакомить читателя с принципами работы с метриками, а не написать какое-то достойное приложение.ОкружениеВам потребуется:
- linux/macos
- docker, docker-compose
- make
- api token для телеграм бота
Создаем себе нового бота в телеграм, сделать это можно написав @BotFather Затем скачиваем тестовый проект
git clone https://github.com/omentes/sample-metrics.git
cd sample-metrics
make up
После того, как соберется проект, вам нужно создать файл .env и выполнить установку php пакетов
cp .env.dist .env
make install
В этот момент нужно внести свой токен в .env файл, в переменную TG_API. Токен берете в телеграме, там где регистрировали нового бота.После этого переходим по адресу localhost:3000 и видим форму входа в Grafana. Логинимся admin/admin Там сразу должен подтянуться дашборд Metrics. Скорее всего сразу будут красные пометки, что источник базы данных не работает. Это связано с тем, что в запросах использовался group by, а в настройках по умолчанию включен sql mode ONLY_FULL_GROUP_BY. Его довольно просто отключить, достаточно зайти в phpMyAdmin, открыть SQL и выполнить запрос
SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
Если по-прежнему Grafana не ожила, значит все еще висят процессы со включенным sql mode, поэтому заходим в phpMyAdmin > Status > Processes и убиваем процессы нажатием на кнопку killПосле этого в Grafana нажимаем на красный треугольник возле нерабочей панельки и заходим во вкладку Query, чтобы еще раз нажать Refresh. Данные должны быть доступны, и вы увидите следующее:
Пример корректно полученных данных от сервера MySQLОтлично, теперь у вас все работает. На одном из графиков уже должны быть данные от воркера, который вытягивает данные с сервера Telegram.
Теперь можете написать своему боту в личку, начав с команд /startПриложение
Ответ бота на команду /startВ проекте лежит дамп для базы данных, который сразу загрузил 10 товаров. 1, 2, 5 - тянется с базы данных. 3 это пагинация, 4 и 6 не требуют объяснения. Корзина тут хранится в Redis, и это можно считать оверхедом, но Redis все равно необходим для сбора метрик. Было проще всего положить в кеш. И главное быстро, я это приложение написал за вечер специально для этой статьи.Сценарий следующий: выбираем игрушки, и нажимаем оформить заказ. Так как цель статьи научить работать с метриками, я в этом сценарии выделил несколько метрик, которые могут быть интересны "бизнесу".
- Сколько у нас пользователей (всего, или за последний час/неделю)
- Сколько у нас реальных пользователей, которые не удалили чат с ботом
- Как много люди выбирают товар (ака просмотры)
- Какие товары заказывают чаще
- Сколько заказов, где есть 2, 3, 5 товаров и тд
Помимо этого можно еще выделить метрику "работоспособности", а работает ли воркер вообще. И на эту метрику настроить оповещения, если воркер остановился и бот перестал работать. Метрики: источники, запросыЯ не буду рассказывать о настройке источников, я специально в докере все прокинул, чтобы заработало из коробки, включая dashboard. В любом случае вы можете самостоятельно зайти в настройки Grafana и посмотреть, там все тривиально.
Сколько у нас пользователей (всего, или за последний час/неделю)
Для телеграм бота я использовал библиотеку php-telegram-bot/core, в которой уже с коробки есть интеграция с MySQL. В базу сохраняется все необходимая информация по взаимодействию с юзером. Следовательно, для построения этой метрики нам надо взять как источник MySQL и забирать данные с таблички user
SELECT
created_at AS "time",
count(id) as "NEW\:"
FROM user
WHERE
$__timeFilter(created_at)
ORDER BY created_at
Панель UsersТак выглядит запрос для получения новых юзеров за промежуток времени. Grafana требуется временной отрезок, по которому будет фильтровать данные. Даже если вам это не нужно, в любом случае одну из колонок нужно назвать time. Поэтому во второй метрике, где все юзеры за все время, все равно есть эта колонка, хоть она по факту и не используется
SELECT
created_at AS "time",
count(id) AS "All Time\:"
FROM user
Сколько у нас реальных пользователей, которые не удалили чат с ботом
Для того, чтобы это понять, боту нужно попробовать что-то отправить пользователю. В данном случае я сделал механизм "оповещений о новой версии".
Панель доставленных сообщений пользователям после создания "версии"Если в табличке version появится новая запись с used=0, то при следующем старте воркера бот отправит всем активным чатам сообщение, и запишет информацию об этом в табличку version_notification. Таким образом можно узнать точное количество активных чатов. Запрос для таблички Delivered Version Notifications:
SELECT
version_notification.created_at AS "time",
version.version as version,
COALESCE(COUNT(version_notification.chat_id), 0) as users
FROM version_notification
inner join version ON version.id = version_notification.version_id
group by version.version
order by version.version desc
Как много люди выбирают товар (ака просмотры)
Этот пункт решается созданием отдельной метрики usage. Она сохраняется в момент обращения к коду пользователем. increase(sample_metrics_bot_usage[1m]) - берется метрика sample_metrics_bot_usage за одну минуту и считается на сколько она была увеличена.
Использование бота видно на графике - кто-то выбирает себе слоника
Какие товары заказывают чаще
Данную метрику нужно сохранять в момент оформления заказа. Тут есть важный момент, метрика будет одна, но у нее будут параметры, в моем случае productId. Обратите внимание, что Prometheus очень сильно приболеет, если вы будете сохранять в него плохо агрегируемые данные. У меня простой пример, где может быть всего 10 вариантов. Т.е. не стоит на проде сохранять такой параметр, если у сотни тысяч товаров. Агрегируйте до категорий, либо найдите другой способ.
Метрика по заказанным товарамКак видно на графике, я добавил 2 метрики. Первая sample_metrics_bot_item{} добавила на график все варианты с productId, а вторая sum(sample_metrics_bot_item{})это просто сумма количества всех товаров. Если навести мышкой на график, вы увидите расшифровку по количеству
Популярность товаров в заказахВ данном случае самый популярный товар это productId=2, productId=4, productId=6
Сколько заказов, где есть 2, 3, 5 товаров и тд
Данную метрику мы тоже будем сохранять в момент создания заказа, посчитав количество товаров и отправив одну метрику с дополнительным параметром. График по метрике sample_metrics_bot_cart{}выглядит так
График по количеству товаров в заказеНа графике видно, что было три заказа. Два заказа было с двумя товарами, и один с 7ю.
По мимо этого можно еще выделить метрику "работоспособности"
Для метрики воркера increase(sample_metrics_bot_worker[1m]) все идентично метрики usage, с одним нюансом - надо бы добавить оповещения. Я настроил оповещения в телеграм в настройках Grafana, там все довольно просто. Сам график выглядит так:
Настройка оповещенийВыбираем раздел Alert после открытия Edit панели. Даем название, выбираем период проверки, я поставил проверить 5 секунд в течение 30 секунд. То есть по факту если пропадут данные, на 5 секунд, то в течение 30 секунд Grafana даст шанс восстановится. Затем выбирают считать сумму показателей и сравнивать сейчас с тем, что было 10 секунд назад, а потом само правило - меньше 1. Так же выбрал два состояния ниже, там есть выбор как поступать если данные не 0, а просто null, ну или таймаут. Метрики: пишем кодДля сбора и публикации метрик я использовал пакет promphp/prometheus_client_php, который поддерживает php 8. Рассмотрим класс, который я написал для сохранения метрик
<?php
declare(strict_types=1);
namespace SampleMetrics\Core;
use SampleMetrics\Common\Config;
use SampleMetrics\Common\Singleton;
use Prometheus\CollectorRegistry;
use Prometheus\Exception\MetricsRegistrationException;
use Prometheus\Storage\Redis;
class Metrics extends Singleton
{
private const METRIC_USAGE_PREFIX = 'usage';
private const METRIC_ITEM_PREFIX = 'item';
private const METRIC_CART_PREFIX = 'cart';
/**
* @var CollectorRegistry
*/
private CollectorRegistry $registry;
public function init(Config $config): self
{
Redis::setDefaultOptions(
[
'host' => $config->getKey('redis.host'),
'port' => intval($config->getKey('redis.port')),
'database' => intval($config->getKey('redis.database')),
'password' => null,
'timeout' => 0.1, // in seconds
'read_timeout' => '10', // in seconds
'persistent_connections' => false
]
);
$this->registry = CollectorRegistry::getDefault();
return $this;
}
/**
* @return CollectorRegistry
*/
public function getRegistry(): CollectorRegistry
{
return $this->registry;
}
/**
* @param string $metricName
* @param array $labels
*
* @throws MetricsRegistrationException
*/
public function increaseMetric(string $metricName, array $labels = []): void
{
$counter = $this->registry->getOrRegisterCounter('sample_metrics_bot', $metricName, 'it increases', []);
$counter->incBy(1, $labels);
}
/**
* @param string $metricName
* @param array $labels
*
* @throws MetricsRegistrationException
*/
public function increaseMetricItem(string $metricName, array $labels = []): void
{
$counter = $this->registry->getOrRegisterCounter(
'sample_metrics_bot',
$metricName,
'it increases',
[
'productId'
]
);
$counter->incBy(1, $labels);
}
/**
* @param string $metricName
* @param array $labels
*
* @throws MetricsRegistrationException
*/
public function increaseMetricCart(string $metricName, array $labels = []): void
{
$counter = $this->registry->getOrRegisterCounter(
'sample_metrics_bot',
$metricName,
'it increases',
[
'quantity'
]
);
$counter->incBy(1, $labels);
}
/**
* @throws MetricsRegistrationException
*/
public function increaseUsage(): void
{
$this->increaseMetric(self::METRIC_USAGE_PREFIX);
}
/**
* @param array $labels
*
* @throws MetricsRegistrationException
*/
public function increaseItemMetric(array $labels = []): void
{
$this->increaseMetricItem(self::METRIC_ITEM_PREFIX, $labels);
}
/**
* @param array $labels
*
* @throws MetricsRegistrationException
*/
public function increaseCartMetric(array $labels = []): void
{
$this->increaseMetricCart(self::METRIC_CART_PREFIX, $labels);
}
Все метрики имеют общий префикс sample_metrics_bot, с которого начинается название каждой метрики. Обратите внимание на вызов методоа $this->registry->getOrRegisterCounter()и $counter->incBy(1, $labels)в методах increaseMetricCart()и increaseMetricItem Помните выше шла речь о дополнительных параметрах для метрик, чтобы передавать productIdи quantity Вот как раз в этом месте в вызове метода getOrRegisterCounter()объявляется, что у метрики есть один дополнительный параметр, и его значение передается в метод incBy()Если вы в процессе теста сохранили много ошибочных метрик, то их можно удалить с Redis при помощи консоли
make redis
KEY '*'
del k
Где k это название метрики, которые вы увидите после команды KEYТеперь вызовем все нужные метрики в нужных местах.
- в двух местах (ака контролеры) вызовем $metric->increaseUsage();
- в цикле воркера будем вызывать метрику воркера $metric->increaseMetric('worker')
- при оформлении заказа переберем productId и тоже сохраним
$items = $cache->getCarts($chat_id);
foreach ($items as $item) {
$metric->increaseItemMetric(['productId' => $item]);
}
$metric->increaseCartMetric(['quantity' => count($items)]);
Все метрики попали в Redis, а теперь нужно отправить их в Prometheus. Есть два популярных способов доставки, первый это пушить в специальный сервис, а второй это публиковать в виде текста, куда будет ходить скраппер Prometheus. В моем случае настроен второй вариант. Я поднял отдельно контейнер для веб, где по урлу /metrics доступны метрики.
Публикация метрикРаботает это следующим образом. В nginx я добавил конфиг
location / {
try_files $uri $uri/ /index.php?uri=$uri$is_args$args;
}
Чтобы затем в приложении получить урл странички
<?php
require __DIR__ . '/vendor/autoload.php';
use Prometheus\RenderTextFormat;
use SampleMetrics\Core\App;
use SampleMetrics\Core\Metrics;
if (isset($_REQUEST['uri']) && $_REQUEST['uri'] == '/metrics') {
$app = App::getInstance()->init();
$config = $app->getConfig();
$metrics = Metrics::getInstance()->init($config);
$renderer = new RenderTextFormat();
$result = $renderer->render($metrics->getRegistry()->getMetricFamilySamples());
header('Content-type: ' . RenderTextFormat::MIME_TYPE);
echo $result;
} else {
echo json_encode(
["silence" => "gold"]
);
}
Все довольно тривиально и по факту работает с документации самого пакета.ИтогиЯ надеюсь смог помочь разобраться в работе с метриками, и в любом случае жду ваших комментариев. Так же заранее большое спасибо за вычитку и исправления ошибок.P.S. Сам телеграм бот я развернул на своем сервере, и его можно посмотреть тут
===========
Источник:
habr.com
===========
Похожие новости:
- [Информационная безопасность, PHP] Бэкдор в 1С-Битрикс: под угрозой сотни сайтов
- [Open source, Python, IT-инфраструктура, Хранение данных, Хранилища данных] HDB++ TANGO Archiving System (перевод)
- [Программирование, Java, Kotlin, Gradle, Микросервисы] Шаблон Kotlin микросервисов
- [Ненормальное программирование, Ruby, Программирование, Go] Запускаем скрипты Ruby из Go Lang
- [Программирование, Удалённая работа] Дружим WSL и VSCode через Tailscale и упрощаем работу в сети (перевод)
- [Программирование, Учебный процесс в IT, Карьера в IT-индустрии, Конференции] Бесплатные онлайн-мероприятия по разработке (1 марта — 7 марта 2021)
- [Программирование] Чем синьор отличается от джуна?
- [Open source, Разработка под Linux, Софт] Red Hat предложит бесплатные подписки на RHEL некоторым организациям открытого ПО
- [Системное администрирование, Python] Как ленивый работящему помог. Ещё один скрипт для релиза подкаста
- [Мессенджеры, IT-инфраструктура, Финансы в IT, IT-компании] VTimes: проблемы, планы и финансовые показатели Telegram, о которых узнали потенциальные покупатели облигаций
Теги для поиска: #_messendzhery (Мессенджеры), #_open_source, #_sistemnoe_administrirovanie (Системное администрирование), #_php, #_programmirovanie (Программирование), #_php, #_grafana, #_prometheus, #_monitoring, #_telegram, #_telegrambot, #_messendzhery (
Мессенджеры
), #_open_source, #_sistemnoe_administrirovanie (
Системное администрирование
), #_php, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 06:59
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет. Я php разработчик и в свободное время пишу телеграм ботов. Зачастую они требуют дополнительного мониторинга работоспособности, который я реализую через связку Prometheus + Grafana. Когда я решил писать статью про метрики, я сначала планировал досконально описать пошагово как настроить окружение, как разворачивать, как строить графики. Но прикинув объем материала я решил пойти по пути наименьшего сопротивления. Сделать простое приложение, засунуть его в докер, и попутно обвешать его всем необходимым. Что бы любой желающий мог самостоятельно поднять его у себя на домашней машине и посмотреть, как это работает. В итоге за вечер написал подобие магазина в виде телеграм бота. Цель статьи познакомить читателя с принципами работы с метриками, а не написать какое-то достойное приложение.ОкружениеВам потребуется:
git clone https://github.com/omentes/sample-metrics.git
cd sample-metrics make up cp .env.dist .env
make install SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
Пример корректно полученных данных от сервера MySQLОтлично, теперь у вас все работает. На одном из графиков уже должны быть данные от воркера, который вытягивает данные с сервера Telegram. Теперь можете написать своему боту в личку, начав с команд /startПриложение Ответ бота на команду /startВ проекте лежит дамп для базы данных, который сразу загрузил 10 товаров. 1, 2, 5 - тянется с базы данных. 3 это пагинация, 4 и 6 не требуют объяснения. Корзина тут хранится в Redis, и это можно считать оверхедом, но Redis все равно необходим для сбора метрик. Было проще всего положить в кеш. И главное быстро, я это приложение написал за вечер специально для этой статьи.Сценарий следующий: выбираем игрушки, и нажимаем оформить заказ. Так как цель статьи научить работать с метриками, я в этом сценарии выделил несколько метрик, которые могут быть интересны "бизнесу".
Сколько у нас пользователей (всего, или за последний час/неделю)
SELECT
created_at AS "time", count(id) as "NEW\:" FROM user WHERE $__timeFilter(created_at) ORDER BY created_at Панель UsersТак выглядит запрос для получения новых юзеров за промежуток времени. Grafana требуется временной отрезок, по которому будет фильтровать данные. Даже если вам это не нужно, в любом случае одну из колонок нужно назвать time. Поэтому во второй метрике, где все юзеры за все время, все равно есть эта колонка, хоть она по факту и не используется SELECT
created_at AS "time", count(id) AS "All Time\:" FROM user Сколько у нас реальных пользователей, которые не удалили чат с ботом
Панель доставленных сообщений пользователям после создания "версии"Если в табличке version появится новая запись с used=0, то при следующем старте воркера бот отправит всем активным чатам сообщение, и запишет информацию об этом в табличку version_notification. Таким образом можно узнать точное количество активных чатов. Запрос для таблички Delivered Version Notifications: SELECT
version_notification.created_at AS "time", version.version as version, COALESCE(COUNT(version_notification.chat_id), 0) as users FROM version_notification inner join version ON version.id = version_notification.version_id group by version.version order by version.version desc Как много люди выбирают товар (ака просмотры)
Использование бота видно на графике - кто-то выбирает себе слоника Какие товары заказывают чаще
Метрика по заказанным товарамКак видно на графике, я добавил 2 метрики. Первая sample_metrics_bot_item{} добавила на график все варианты с productId, а вторая sum(sample_metrics_bot_item{})это просто сумма количества всех товаров. Если навести мышкой на график, вы увидите расшифровку по количеству Популярность товаров в заказахВ данном случае самый популярный товар это productId=2, productId=4, productId=6 Сколько заказов, где есть 2, 3, 5 товаров и тд
График по количеству товаров в заказеНа графике видно, что было три заказа. Два заказа было с двумя товарами, и один с 7ю. По мимо этого можно еще выделить метрику "работоспособности"
Настройка оповещенийВыбираем раздел Alert после открытия Edit панели. Даем название, выбираем период проверки, я поставил проверить 5 секунд в течение 30 секунд. То есть по факту если пропадут данные, на 5 секунд, то в течение 30 секунд Grafana даст шанс восстановится. Затем выбирают считать сумму показателей и сравнивать сейчас с тем, что было 10 секунд назад, а потом само правило - меньше 1. Так же выбрал два состояния ниже, там есть выбор как поступать если данные не 0, а просто null, ну или таймаут. Метрики: пишем кодДля сбора и публикации метрик я использовал пакет promphp/prometheus_client_php, который поддерживает php 8. Рассмотрим класс, который я написал для сохранения метрик <?php
declare(strict_types=1); namespace SampleMetrics\Core; use SampleMetrics\Common\Config; use SampleMetrics\Common\Singleton; use Prometheus\CollectorRegistry; use Prometheus\Exception\MetricsRegistrationException; use Prometheus\Storage\Redis; class Metrics extends Singleton { private const METRIC_USAGE_PREFIX = 'usage'; private const METRIC_ITEM_PREFIX = 'item'; private const METRIC_CART_PREFIX = 'cart'; /** * @var CollectorRegistry */ private CollectorRegistry $registry; public function init(Config $config): self { Redis::setDefaultOptions( [ 'host' => $config->getKey('redis.host'), 'port' => intval($config->getKey('redis.port')), 'database' => intval($config->getKey('redis.database')), 'password' => null, 'timeout' => 0.1, // in seconds 'read_timeout' => '10', // in seconds 'persistent_connections' => false ] ); $this->registry = CollectorRegistry::getDefault(); return $this; } /** * @return CollectorRegistry */ public function getRegistry(): CollectorRegistry { return $this->registry; } /** * @param string $metricName * @param array $labels * * @throws MetricsRegistrationException */ public function increaseMetric(string $metricName, array $labels = []): void { $counter = $this->registry->getOrRegisterCounter('sample_metrics_bot', $metricName, 'it increases', []); $counter->incBy(1, $labels); } /** * @param string $metricName * @param array $labels * * @throws MetricsRegistrationException */ public function increaseMetricItem(string $metricName, array $labels = []): void { $counter = $this->registry->getOrRegisterCounter( 'sample_metrics_bot', $metricName, 'it increases', [ 'productId' ] ); $counter->incBy(1, $labels); } /** * @param string $metricName * @param array $labels * * @throws MetricsRegistrationException */ public function increaseMetricCart(string $metricName, array $labels = []): void { $counter = $this->registry->getOrRegisterCounter( 'sample_metrics_bot', $metricName, 'it increases', [ 'quantity' ] ); $counter->incBy(1, $labels); } /** * @throws MetricsRegistrationException */ public function increaseUsage(): void { $this->increaseMetric(self::METRIC_USAGE_PREFIX); } /** * @param array $labels * * @throws MetricsRegistrationException */ public function increaseItemMetric(array $labels = []): void { $this->increaseMetricItem(self::METRIC_ITEM_PREFIX, $labels); } /** * @param array $labels * * @throws MetricsRegistrationException */ public function increaseCartMetric(array $labels = []): void { $this->increaseMetricCart(self::METRIC_CART_PREFIX, $labels); } make redis
KEY '*' del k
Публикация метрикРаботает это следующим образом. В nginx я добавил конфиг location / {
try_files $uri $uri/ /index.php?uri=$uri$is_args$args; } <?php
require __DIR__ . '/vendor/autoload.php'; use Prometheus\RenderTextFormat; use SampleMetrics\Core\App; use SampleMetrics\Core\Metrics; if (isset($_REQUEST['uri']) && $_REQUEST['uri'] == '/metrics') { $app = App::getInstance()->init(); $config = $app->getConfig(); $metrics = Metrics::getInstance()->init($config); $renderer = new RenderTextFormat(); $result = $renderer->render($metrics->getRegistry()->getMetricFamilySamples()); header('Content-type: ' . RenderTextFormat::MIME_TYPE); echo $result; } else { echo json_encode( ["silence" => "gold"] ); } =========== Источник: habr.com =========== Похожие новости:
Мессенджеры ), #_open_source, #_sistemnoe_administrirovanie ( Системное администрирование ), #_php, #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 06:59
Часовой пояс: UTC + 5