This commit is contained in:
Nicolas Debrigode 2024-01-30 17:01:56 +01:00
parent ad457e9a30
commit 72a24ec46c
9 changed files with 326 additions and 115 deletions

View File

@ -20,6 +20,19 @@ config:
## Size of graphs on top N AS page ##
top_graph_width: 600
top_graph_height: 220
linksusage:
top: 3
color:
- A6CEE3
- 1F78B4
- B2DF8A
- 33A02C
- FB9A99
- E31A1C
- FDBF6F
- FF7F00
- CAB2D6
- 6A3D9A
##
# Custom time intervals for top N AS */

View File

@ -148,4 +148,30 @@ class ConfigApplication
return self::getAsStatsConfig()['graph'];
}
public static function getLinksUsageColor(): array
{
if (false === \array_key_exists('linksusage', self::getAsStatsConfig())) {
throw new ConfigErrorException('Unable to found config.linksusage variable');
}
if (false === \array_key_exists('color', self::getAsStatsConfig()['linksusage'])) {
throw new ConfigErrorException('Unable to found config.linksusage.color variable');
}
return self::getAsStatsConfig()['linksusage']['color'];
}
public static function getLinksUsageTop(): int
{
if (false === \array_key_exists('linksusage', self::getAsStatsConfig())) {
throw new ConfigErrorException('Unable to found config.linksusage variable');
}
if (false === \array_key_exists('top', self::getAsStatsConfig()['linksusage'])) {
throw new ConfigErrorException('Unable to found config.linksusage.top variable');
}
return self::getAsStatsConfig()['linksusage']['top'];
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Controller;
use App\Repository\KnowlinksRepository;
use App\Util\Annotation\Menu;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
@ -12,7 +13,7 @@ use Symfony\Component\Routing\Annotation\Route;
path: '/links/usage',
)]
#[Menu('links_usage')]
class LinkUsageController extends BaseController
class LinksUsageController extends BaseController
{
protected array $data = [];
@ -25,6 +26,7 @@ class LinkUsageController extends BaseController
{
return $this->render('pages/link_usage/index.html.twig', [
'base_data' => $this->base_data,
'knownlinks' => KnowlinksRepository::get(),
]);
}

View File

@ -4,7 +4,13 @@ declare(strict_types=1);
namespace App\Controller;
use App\Repository\RRDRepository;
use App\Application\ConfigApplication;
use App\Exception\ConfigErrorException;
use App\Exception\DbErrorException;
use App\Repository\GetAsDataRepository;
use App\Repository\RRDAsnRepository;
use App\Repository\RRDLinksUsageRepository;
use Doctrine\DBAL\Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@ -24,7 +30,41 @@ class RenderController extends AbstractController
int $as,
Request $request,
): Response {
$cmd = new RRDRepository($as, $request->query->all());
$cmd = new RRDAsnRepository($as, $request->query->all());
$response = new Response();
$response->headers->set('Content-type', 'image/png');
$response->sendHeaders();
$response->setContent(
\sprintf(
'%s',
passthru($cmd->generateCmd())
)
);
return $response;
}
/**
* @throws ConfigErrorException
* @throws DbErrorException
* @throws Exception
*/
#[Route(
path: '/links/usage/graph/{link}',
name: 'render.links.usage.graph',
methods: ['GET'],
)]
public function renderGraphLinksUsage(
string $link,
Request $request,
GetAsDataRepository $asDataRepository,
): Response {
$cmd = new RRDLinksUsageRepository(
$link,
$request->query->all(),
$asDataRepository::get(ConfigApplication::getLinksUsageTop(), '', [$link => true])
);
$response = new Response();
$response->headers->set('Content-type', 'image/png');

View File

@ -5,68 +5,31 @@ declare(strict_types=1);
namespace App\Repository;
use App\Application\ConfigApplication;
use App\Util\RRDGraph;
class RRDRepository
class RRDAsnRepository
{
private int $as;
private array $request;
private array $knowlinks;
private string $rrdfile;
private string $v6;
private RRDGraph $rrdGraph;
public function __construct(int $as, array $req)
{
$this->as = $as;
$this->request = $req;
$this->rrdGraph = new RRDGraph($this->request);
$this->knowlinks = $this->selectedLinks();
$this->rrdfile = $this->getRRDFileForAS();
$this->v6 = $this->addV6Graph();
}
private function getRRDFileForAS(): string
{
return \sprintf(
'%s/%s/%s.rrd',
ConfigApplication::getAsStatsConfigGraph()['rrdpath'],
\sprintf('%02x', $this->as % 256),
$this->as
);
}
private function getGraphSize(): array
{
$width = ConfigApplication::getAsStatsConfigGraph()['default_graph_width'];
$height = ConfigApplication::getAsStatsConfigGraph()['default_graph_height'];
if (isset($this->request['width'])) {
$width = (int) $this->request['width'];
}
if (isset($this->request['height'])) {
$height = (int) $this->request['height'];
}
return [
'width' => $width,
'height' => $height,
];
}
private function addV6Graph(): string
{
if ($this->request['v'] === '6') {
return 'v6_';
}
return '';
$this->rrdfile = $this->rrdGraph->getRRDFileForAS($as);
$this->v6 = $this->rrdGraph->addV6Graph();
}
private function selectedLinks(): array
{
$knownlinks = KnowlinksRepository::get();
if (isset($this->request['selected_links']) && $this->request['selected_links'] !== '') {
if (isset($this->request['selected_links']) && '' !== $this->request['selected_links']) {
$reverse = [];
foreach ($knownlinks as $link) {
@ -99,43 +62,6 @@ class RRDRepository
return $knownlinks;
}
private function verticalLabel(): string
{
if (ConfigApplication::getAsStatsConfigGraph()['vertical_label']) {
if (ConfigApplication::getAsStatsConfigGraph()['outispositive']) {
return '--vertical-label \'<- IN | OUT ->\' ';
}
return '--vertical-label \'<- OUT | IN ->\' ';
}
return '';
}
private function addLegend(): string
{
if (isset($this->request['legend']) && $this->request['legend'] === '0') {
return '--no-legend ';
}
return '';
}
private function addStartEnd(): string
{
$cmd = '';
if (isset($this->request['start']) && is_numeric($this->request['start'])) {
$cmd .= \sprintf('--start %s ', $this->request['start']);
}
if (isset($this->request['end']) && is_numeric($this->request['end'])) {
$cmd .= \sprintf('--end %s ', $this->request['end']);
}
return $cmd;
}
private function addData(): string
{
$cmd = '';
@ -183,7 +109,7 @@ class RRDRepository
return $cmd;
}
public function generateStackAreaInbound(): string
private function generateStackAreaInbound(): string
{
$cmd = '';
$i = 0;
@ -194,7 +120,7 @@ class RRDRepository
$col = $link['color'];
}
$descr = \str_replace(':', '\:', $link['descr']); # Escaping colons in description
$descr = \str_replace(':', '\:', $link['descr']); // Escaping colons in description
$cmd .= \sprintf('AREA:%s_%sin_bits#%s:"%s"', $link['tag'], $this->v6, $col, $descr);
if ($i > 0) {
@ -202,13 +128,13 @@ class RRDRepository
}
$cmd .= ' ';
$i++;
++$i;
}
return $cmd;
}
public function generateStackAreaOutbound(): string
private function generateStackAreaOutbound(): string
{
$cmd = '';
$i = 0;
@ -226,13 +152,13 @@ class RRDRepository
}
$cmd .= ' ';
$i++;
++$i;
}
return $cmd;
}
public function add95th(): string
private function add95th(): string
{
$cmd = '';
if (ConfigApplication::getAsStatsConfigGraph()['show95th']) {
@ -245,20 +171,9 @@ class RRDRepository
return $cmd;
}
public function addTitle(): string
{
$cmd = '';
if (ConfigApplication::getAsStatsConfigGraph()['showtitledetail'] && isset($this->request['title']) && $this->request['title'] !== '') {
$cmd .= \sprintf('--title %s ', \escapeshellarg(\sprintf('%s', $this->request['title'])));
} elseif (isset($this->request['v']) && is_numeric($this->request['v'])) {
$cmd .= \sprintf('--title IPv%s ', $this->request['v']);
}
return $cmd;
}
public function generateCmd(): string
{
$graphSize = $this->getGraphSize();
$graphSize = $this->rrdGraph->getGraphSize();
return \sprintf(
'%s graph - --slope-mode --alt-autoscale --upper-limit 0 --lower-limit 0 --imgformat=PNG \
@ -268,10 +183,10 @@ class RRDRepository
ConfigApplication::getAsStatsConfigGraph()['rrdtool'],
$graphSize['height'],
$graphSize['width'],
$this->verticalLabel(),
$this->addTitle(),
$this->addLegend(),
$this->addStartEnd(),
$this->rrdGraph->verticalLabel(),
$this->rrdGraph->addTitle(),
$this->rrdGraph->addLegend(),
$this->rrdGraph->addStartEnd(),
$this->addData(),
$this->generateStackAreaInbound(),
$this->generateStackAreaOutbound(),

View File

@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Application\ConfigApplication;
use App\Util\RRDGraph;
class RRDLinksUsageRepository
{
private string $link;
private array $request;
private array $topas;
private array $colors;
// private array $knowlinks;
// private string $rrdfile;
private string $v6;
private RRDGraph $rrdGraph;
public function __construct(string $link, array $req, array $topas)
{
$this->link = $link;
$this->request = $req;
$this->topas = $topas;
//dump($topas);
$this->rrdGraph = new RRDGraph($this->request);
// $this->knowlinks = $this->selectedLinks();
// $this->rrdfile = $this->getRRDFileForAS();
$this->v6 = $this->rrdGraph->addV6Graph();
$this->colors = ConfigApplication::getLinksUsageColor();
}
private function addData(): string
{
$cmd = '';
foreach ($this->topas['asinfo'] as $as => $value) {
$rrdfile = $this->rrdGraph->getRRDFileForAS($as);
$cmd .= \sprintf('DEF:as%1$s_%2$sin="%3$s":%4$s_%2$sin:AVERAGE ', $as, $this->v6, $rrdfile, $this->link);
$cmd .= \sprintf('DEF:as%1$s_%2$sout="%3$s":%4$s_%2$sout:AVERAGE ', $as, $this->v6, $rrdfile, $this->link);
}
foreach ($this->topas['asinfo'] as $as => $value) {
if (ConfigApplication::getAsStatsConfigGraph()['outispositive']) {
$cmd .= \sprintf('CDEF:as%1$s_%2$sin_bits=as%1$s_%2$sin,-8,* ', $as, $this->v6);
$cmd .= \sprintf('CDEF:as%1$s_%2$sout_bits=as%1$s_%2$sout,8,* ', $as, $this->v6);
} else {
$cmd .= \sprintf('CDEF:as%1$s_%2$sin_bits=as%1$s_%2$sin,8,* ', $as, $this->v6);
$cmd .= \sprintf('CDEF:as%1$s_%2$sout_bits=as%1$s_%2$sout,-8,* ', $as, $this->v6);
}
}
return $cmd;
}
private function generateStackAreaInbound(): string
{
$cmd = '';
$i = 0;
foreach ($this->topas['asinfo'] as $as => $value) {
$descr = \str_replace(':', '\:', $value['info']['description']); // Escaping colons in description
$cmd .= \sprintf('AREA:as%1$s_%2$sin_bits#%3$s:"AS%1$s (%4$s)\\n"', $as, $this->v6, $this->colors[$i], $descr);
if ($i > 0) {
$cmd .= ':STACK';
}
$cmd .= ' ';
++$i;
}
return $cmd;
}
private function generateStackAreaOutbound(): string
{
$cmd = '';
$i = 0;
foreach ($this->topas['asinfo'] as $as => $value) {
$cmd .= \sprintf('AREA:as%s_%sout_bits#%s:', $as, $this->v6, $this->colors[$i]);
if ($i > 0) {
$cmd .= ':STACK';
}
$cmd .= ' ';
++$i;
}
return $cmd;
}
public function generateCmd(): string
{
$graphSize = $this->rrdGraph->getGraphSize();
return \sprintf(
'%s graph - --slope-mode --alt-autoscale --upper-limit 0 --lower-limit 0 --imgformat=PNG \
--base=1000 --height=%s --width=%s --full-size-mode \
--color BACK#ffffff00 --color SHADEA#ffffff00 --color SHADEB#ffffff00 \
%s %s %s %s %s %s %s HRULE:0#00000080',
ConfigApplication::getAsStatsConfigGraph()['rrdtool'],
$graphSize['height'],
$graphSize['width'],
$this->rrdGraph->verticalLabel(),
$this->rrdGraph->addTitle(),
$this->rrdGraph->addLegend(),
$this->rrdGraph->addStartEnd(),
$this->addData(),
$this->generateStackAreaInbound(),
$this->generateStackAreaOutbound(),
);
}
}

View File

@ -21,6 +21,7 @@ class GenGraphExtension extends AbstractExtension
{
return [
new TwigFunction('gen_graph', [$this, 'genGraph'], ['is_safe' => ['html']]),
//new TwigFunction('gen_graph_linkusage', [$this, 'genGraphLinkUsage'], ['is_safe' => ['html']]),
];
}

105
src/Util/RRDGraph.php Normal file
View File

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace App\Util;
use App\Application\ConfigApplication;
final class RRDGraph
{
private array $request;
public function __construct(array $req)
{
$this->request = $req;
}
public function getGraphSize(): array
{
$width = ConfigApplication::getAsStatsConfigGraph()['default_graph_width'];
$height = ConfigApplication::getAsStatsConfigGraph()['default_graph_height'];
if (isset($this->request['width'])) {
$width = (int) $this->request['width'];
}
if (isset($this->request['height'])) {
$height = (int) $this->request['height'];
}
return [
'width' => $width,
'height' => $height,
];
}
public function addV6Graph(): string
{
if ('6' === $this->request['v']) {
return 'v6_';
}
return '';
}
public function addLegend(): string
{
if (isset($this->request['legend']) && '0' === $this->request['legend']) {
return '--no-legend ';
}
return '';
}
public function verticalLabel(): string
{
if (ConfigApplication::getAsStatsConfigGraph()['vertical_label']) {
if (ConfigApplication::getAsStatsConfigGraph()['outispositive']) {
return '--vertical-label \'<- IN | OUT ->\' ';
}
return '--vertical-label \'<- OUT | IN ->\' ';
}
return '';
}
public function addStartEnd(): string
{
$cmd = '';
if (isset($this->request['start']) && is_numeric($this->request['start'])) {
$cmd .= \sprintf('--start %s ', $this->request['start']);
}
if (isset($this->request['end']) && is_numeric($this->request['end'])) {
$cmd .= \sprintf('--end %s ', $this->request['end']);
}
return $cmd;
}
public function addTitle(): string
{
$cmd = '';
if (ConfigApplication::getAsStatsConfigGraph()['showtitledetail'] && isset($this->request['title']) && '' !== $this->request['title']) {
$cmd .= \sprintf('--title %s ', \escapeshellarg(\sprintf('%s', $this->request['title'])));
} elseif (isset($this->request['v']) && is_numeric($this->request['v'])) {
$cmd .= \sprintf('--title IPv%s ', $this->request['v']);
}
return $cmd;
}
public function getRRDFileForAS(int $as): string
{
return \sprintf(
'%s/%s/%s.rrd',
ConfigApplication::getAsStatsConfigGraph()['rrdpath'],
\sprintf('%02x', $as % 256),
$as
);
}
}

View File

@ -11,15 +11,7 @@
<div class="navbar-nav flex-row order-md-last">
<div class="my-1 flex-grow-1 flex-md-grow-0 order-first">
<!--<form method="get" autocomplete="off" novalidate="">
<div class="input-icon">
<span class="input-icon-addon">
{{ icon('filter') }}
</span>
<input type="number" name='top' min=1 class="form-control" value="{{ base_data.request.top|default('') }}" placeholder="Top AS">
</div>
</form>-->
{% if form.top is defined %}
{% if form.top is defined %}
{{ form_start(form.top) }}
<div class="input-icon">
<span class="input-icon-addon">