Bu örneğimizde symfony istek cevap sürelerini kaydedip grafiklendireceğiz. Grafik için Grafana kullanacağız. Symfony uygulamamız bir sunucuda, InfluxDB ve Grafana diğer bir sunucuda barındırılıyor.


Sistem


Application server: 192.168.99.10

InfluxDB server: 192.168.99.20
InfluxDB port: 8086
InfluxDB version: 1.5.1
Grafana version: v5.0.4

InfluxDB veritabanımız response_stats, tablomuz (measurement) ise points olarak adlandırılıyor.


Bağlantı testi


Symfony sunucusunun InfluxDB sunucusuna bağlanıp bağlanamadığını kontrol edelim.


$ nc -zv 192.168.99.20 8086
Connection to 192.168.99.20 8086 port [tcp/*] succeeded!

Ön koşullar


Her iki sunucudaki zamanın birbirlerine eşit olmalarına dikkat edin. Eğer aralarında fark olursa ya grafikleri göremezsiniz ya da gördüğünüz grafikler aldatıcı olabilir. Zaman ayarı için NTP kurulumu yapmanız gerekir. Kurulumdan sonra $ timedatectl komutu ile kontrol edebilirsiniz. InfluxDB'ye izinsiz erişimi kesmek için /etc/influxdb/influxdb.conf konfigürasyon dosyasındaki auth-enabled satırını aktif hale getirin. Bu işlemi yapmadan önce veritabanında bir kullanıcı yaratın.


Kurulum


InfluxDB paketini kurmak için $ composer require influxdb/influxdb-php komutunu kullanın. Daha fazla bilgi için InfluxDB client library for PHP sayfasını okuyun.


Uygulama


Testimizi basit tutmak için sadece aşağıdaki adresleri kullanacağız.



parameters.yml


parameters:
...
influx_host: 192.168.99.20
influx_port: 8086
influx_user: inanzzz
influx_pass: 123123

config.yml


imports:
...
- { resource: '@AppBundle/Resources/config/' }

services.yml


services:
_defaults:
autowire: true
public: false

AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository,Tests}'

AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']

AbstractController


Sadece test amaçlı olarak istek cevap sürelerini 1 ila 5 saniye arasında geciktiriyorum. Gerçek hayat örneklerinde saniye yerine microsaniye kullanmaya özen gösterin.


declare(strict_types=1);

namespace AppBundle\Controller;

use AppBundle\Exception\InfluxException;
use AppBundle\Service\InfluxService;

abstract class AbstractController
{
private $influxService;

public function __construct(
InfluxService $influxService
) {
$this->influxService = $influxService;
}

protected function record(string $controller, string $action): void
{
$start = time();
sleep(rand(1, 5));
$end = time();

try {
$this->influxService->write($end-$start, ['controller' => $controller, 'action' => $action]);
} catch (InfluxException $exception) {
// Do something about if if you wish or just ignore
}
}
}

UserController


declare(strict_types=1);

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;

/**
* @Route("/users")
*/
class UserController extends AbstractController
{
/**
* @Method({"GET"})
* @Route("/list")
*/
public function listAction(): Response
{
$this->record('users', 'list');

return new Response('User listed.');
}

/**
* @Method({"GET"})
* @Route("/create")
*/
public function createAction(): Response
{
$this->record('users', 'create');

return new Response('User created.');
}

/**
* @Method({"GET"})
* @Route("/delete")
*/
public function deleteAction(): Response
{
$this->record('users', 'delete');

return new Response('User deleted.');
}
}

AdminController


declare(strict_types=1);

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;

/**
* @Route("/admins")
*/
class AdminController extends AbstractController
{
/**
* @Method({"GET"})
* @Route("/create")
*/
public function createAction(): Response
{
$this->record('admins', 'create');

return new Response('Admin created.');
}

/**
* @Method({"GET"})
* @Route("/delete")
*/
public function deleteAction(): Response
{
$this->record('admins', 'delete');

return new Response('Admin deleted.');
}
}

InfluxController


declare(strict_types=1);

namespace AppBundle\Controller;

use AppBundle\Exception\InfluxException;
use AppBundle\Service\InfluxService;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;

/**
* @Route("/influx")
*/
class InfluxController
{
private $influxService;
private $templating;

public function __construct(
InfluxService $influxService,
EngineInterface $templating
) {
$this->influxService = $influxService;
$this->templating = $templating;
}

/**
* @Method({"GET"})
* @Route("")
*/
public function indexAction(): Response
{
try {
$performance = $this->influxService->result();
} catch (InfluxException $exception) {
$performance = $exception->getMessage();
}

return (new Response())->setContent(
$this->templating->render('AppBundle:influx:index.html.twig', ['performance' => $performance])
);
}
}

index.html.twig


{% extends '::base.html.twig' %}

{% block body %}
{{ dump(performance) }}
{% endblock %}

InfluxException


declare(strict_types=1);

namespace AppBundle\Exception;

use RuntimeException;

class InfluxException extends RuntimeException
{
}

InfluxService


declare(strict_types=1);

namespace AppBundle\Service;

use AppBundle\Exception\InfluxException;
use Exception;
use InfluxDB\Client;
use InfluxDB\Database;
use InfluxDB\Database\RetentionPolicy;
use InfluxDB\Point;

class InfluxService
{
const DATABASE = 'response_stats';
const MEASUREMENT = 'points';
const RETENTION_POLICY = 'autogen';
const DEFAULT_TAGS = ['application' => 'symfony'];

private $client;

public function __construct(Client $client)
{
$this->client = $client;
}

public function result(): iterable
{
try {
$database = $this->getDatabase();

$total = $database->query(sprintf(
'SELECT COUNT("value") FROM "%s"', self::MEASUREMENT
));

$records = $database->query(sprintf(
'SELECT * FROM "%s" ORDER BY "time" DESC LIMIT 10 OFFSET 0', self::MEASUREMENT
));

return [
'total' => $total->getPoints()[0]['count'],
'records' => $records->getPoints(),
];

} catch (Exception $e) {
throw new InfluxException($e->getMessage());
}
}

public function write(int $elapsed, iterable $tags): void
{
try {
$database = $this->getDatabase();

$points = array(
new Point(
self::MEASUREMENT, // Used in FROM clause
$elapsed, // Actual time series value
$tags, // Used in WHERE clause
self::DEFAULT_TAGS, // Used in SELECT clause
exec('date +%s%N') // Actual timestamp of the record
),
);

$database->writePoints($points, Database::PRECISION_NANOSECONDS);
} catch (Exception $e) {
throw new InfluxException($e->getMessage());
}
}

private function getDatabase(): Database
{
$database = $this->client->selectDB(self::DATABASE);

if (!$database->exists()) {
$database->create(new RetentionPolicy(self::DATABASE, self::RETENTION_POLICY, 1, true));
}

return $database;
}
}

Test


InfluxDB verileri


Aşağıdaki PHP dosyasını yaratıp terminalde çalıştırın.


$endpoints = [
'users/list',
'users/create',
'users/delete',
'admins/create',
'admins/delete',
];

do {
echo shell_exec('curl http://192.168.99.10:8082/app_dev.php/'.$endpoints[array_rand($endpoints)]);
} while (1 === 1);

Veritabanı


Veritabanına bağlanıp verilerimizi görelim.


$ influx -username inanzzz -password 123123
Connected to http://localhost:8086 version 1.5.1
InfluxDB shell version: 1.5.1
>
> SHOW DATABASES
name: databases
name
----
_internal
response_stats
>
> USE response_stats
Using database response_stats
>
> SHOW MEASUREMENTS
name: measurements
name
----
points
>
> SELECT * FROM points LIMIT 10
name: points
time action application controller value
---- ------ ----------- ---------- -----
1522569761392457485 delete symfony admins 5
1522569765801070159 delete symfony admins 4
1522569771337090535 list symfony users 5
1522569775671952409 list symfony users 4
1522569778064807532 delete symfony admins 2
1522569782668878979 list symfony users 4
1522569785041316946 list symfony users 2
1522569787419202650 delete symfony admins 2
1522569790641263793 create symfony admins 3
1522569791967151595 list symfony users 1
>

Grafikler


Grafana'nın nasıl kurulduğu ve konfigüre edildiğini göstermek yerine, size kendi eklediğim grafikleri göstereceğim. GUI http://192.168.99.20:3000 adresinden admin:admin ile ulaşılabilir.