Мониторинг производительности приложения (Application Performance Monitoring или APM)

Начиная с версии 1.3, драйвер MongoDB содержит API для мониторинга производительности. Это API позволяет определить, как долго выполняется конкретная операция, путем настройки подписчиков. Каждый подписчик должен реализовывать один или более интерфейсов из пространства имен MongoDB\Driver\Monitoring. В данный момент доступен только один интерфейс - MongoDB\Driver\Monitoring\CommandSubscriber.

Интерфейс MongoDB\Driver\Monitoring\CommandSubscriber определяет три метода: commandStarted, commandSucceeded и commandFailed. Каждый из них принимает один параметр event класса, соответствующего нужному событию. К примеру, commandSucceeded принимает аргумент $event класса MongoDB\Driver\Monitoring\CommandSucceededEvent.

В данном руководстве вы реализуем подписчика, который создает список профилировок всех запросов и среднего времени их исполнения.

Класс подписчик Scaffolding

Мы начнес с шаблона для нашего подписчика:

<?php

class QueryTimeCollector implements \MongoDB\Driver\Monitoring\CommandSubscriber
{
    public function 
commandStarted( \MongoDB\Driver\Monitoring\CommandStartedEvent $event )
    {
    }

    public function 
commandSucceeded( \MongoDB\Driver\Monitoring\CommandSucceededEvent $event )
    {
    }

    public function 
commandFailed( \MongoDB\Driver\Monitoring\CommandFailedEvent $event )
    {
    }
}

?>

Регистрация подписчика

Как только объект подписчик создан, необходимо его зарегистрировать в драйвере в системе мониторинга. Регистрация производится методом MongoDB\Driver\Monitoring\addSubscriber().

<?php

\MongoDB\Driver\Monitoring\addSubscriber( new QueryTimeCollector() );

?>

Реализуем логику

Теперь займемся реализацией логики класа подписчика. Для сопоставления двух событий, относящихся к успешно выполненной команды (commandStarted and commandSucceeded), каждый объект события предоставляет поле requestId.

Для записи среднего времени выполнения запроса мы начнем с отслеживания команды find в событии commandStarted. Мы будем добавлять элемент в массив pendingCommands с индексом соответствующим requestId и значением, соответствующим запросу.

Когда мы получим соответствующее событие commandSucceeded с соответствующим requestId, мы добавим время выполнения (из durationMicros) к общему времени и увеличим счетчик операций.

Если мы получим событие commandFailed, мы просто удалим соответствующую запись из pendingCommands.

<?php

class QueryTimeCollector implements \MongoDB\Driver\Monitoring\CommandSubscriber
{
    private 
$pendingCommands = [];
    private 
$queryShapeStats = [];

    
/* Создает форму запроса из аргумента фильтра. В данный момент учитываются 
     * только поля верхнего уровня. */
    
private function createQueryShape( array $filter )
    {
        return 
json_encodearray_keys$filter ) );
    }

    public function 
commandStarted( \MongoDB\Driver\Monitoring\CommandStartedEvent $event )
    {
        if ( 
array_key_exists'find', (array) $event->getCommand() ) )
        {
            
$queryShape $this->createQueryShape( (array) $event->getCommand()->filter );
            
$this->pendingCommands[$event->getRequestId()] = $queryShape;
        }
    }

    public function 
commandSucceeded( \MongoDB\Driver\Monitoring\CommandSucceededEvent $event )
    {
        
$requestId $event->getRequestId();
        if ( 
array_key_exists$requestId$this->pendingCommands ) )
        {
            
$this->queryShapeStats[$this->pendingCommands[$requestId]]['count']++;
            
$this->queryShapeStats[$this->pendingCommands[$requestId]]['duration'] += $event->getDurationMicros();
            unset( 
$this->pendingCommands[$requestId] );
        }
    }

    public function 
commandFailed( \MongoDB\Driver\Monitoring\CommandFailedEvent $event )
    {
        if ( 
array_key_exists$event->getRequestId(), $this->pendingCommands ) )
        {
            unset( 
$this->pendingCommands[$event->getRequestId()] );
        }
    }

    public function 
__destruct()
    {
        foreach( 
$this->queryShapeStats as $shape => $stats )
        {
            echo 
"Shape: "$shape" ("$stats['count'], ")\n  ",
                
$stats['duration'] / $stats['count'], "µs\n\n";
        }
    }
}

$m = new \MongoDB\Driver\Manager'mongodb://localhost:27016' );

/* Добавляем подписчика */
\MongoDB\Driver\Monitoring\addSubscriber( new QueryTimeCollector() );

/* Запускаем пачку запросов */
$query = new \MongoDB\Driver\Query( [
    
'region_slug' => 'scotland-highlands''age' => [ '$gte' => 20 ]
] );
$cursor $m->executeQuery'dramio.whisky'$query );

$query = new \MongoDB\Driver\Query( [
    
'region_slug' => 'scotland-lowlands''age' => [ '$gte' => 15 ]
] );
$cursor $m->executeQuery'dramio.whisky'$query );

$query = new \MongoDB\Driver\Query( [ 'region_slug' => 'scotland-lowlands' ] );
$cursor $m->executeQuery'dramio.whisky'$query );

?>