01/04/2018 - LINUX, SYMFONY
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.
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.
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!
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.
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.
Testimizi basit tutmak için sadece aşağıdaki adresleri kullanacağız.
parameters:
...
influx_host: 192.168.99.20
influx_port: 8086
influx_user: inanzzz
influx_pass: 123123
imports:
...
- { resource: '@AppBundle/Resources/config/' }
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']
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
}
}
}
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.');
}
}
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.');
}
}
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])
);
}
}
{% extends '::base.html.twig' %}
{% block body %}
{{ dump(performance) }}
{% endblock %}
declare(strict_types=1);
namespace AppBundle\Exception;
use RuntimeException;
class InfluxException extends RuntimeException
{
}
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;
}
}
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ı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
>
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.