Template: assemble charts from metrics from Graphite Web

refs #54
This commit is contained in:
Alexander A. Klimov 2017-09-28 16:16:28 +02:00
parent f3dd5c615c
commit 1c7dd2f46a
2 changed files with 399 additions and 0 deletions

View file

@ -0,0 +1,304 @@
<?php
namespace Icinga\Module\Graphite\Graphing;
use DateTimeZone;
use Icinga\Module\Graphite\GraphiteWebClientInterface;
use Icinga\Module\Graphite\Util\MacroTemplate;
use Icinga\Web\Response;
use Icinga\Web\Url;
use Icinga\Web\UrlParams;
class Chart
{
/**
* Used to render the chart
*
* @var GraphiteWebClientInterface
*/
protected $graphiteWebClient;
/**
* This chart's base
*
* @var Template
*/
protected $template;
/**
* Target metrics by curve name
*
* @var string[]
*/
protected $metrics;
/**
* The chart's begin
*
* @var string
*/
protected $from = '-4hours';
/**
* The chart's end
*
* @var string
*/
protected $until;
/**
* The chart's width
*
* @var int
*/
protected $width = 300;
/**
* The chart's height
*
* @var int
*/
protected $height = 200;
/**
* Whether to show the chart's legend
*
* @var bool
*/
protected $showLegend = true;
/**
* The chart's time zone
*
* @var DateTimeZone
*/
protected $timeZone;
/**
* Constructor
*
* @param GraphiteWebClientInterface $graphiteWebClient Used to render the chart
* @param Template $template This chart's base
* @param string[] $metrics Target metrics by curve name
*/
public function __construct(GraphiteWebClientInterface $graphiteWebClient, Template $template, array $metrics)
{
$this->graphiteWebClient = $graphiteWebClient;
$this->template = $template;
$this->metrics = $metrics;
}
/**
* Let Graphite Web render this chart and serve the result immediately to the user agent (via the given response)
*
* Does not return.
*
* @param Response $response
*/
public function serveImage(Response $response)
{
$params = (new UrlParams())->addValues([
'from' => $this->from,
'width' => $this->width,
'height' => $this->height,
'hideLegend' => (string) ! $this->showLegend,
'tz' => $this->timeZone->getName(),
'_salt' => time() . '.000',
'hideGrid' => 'true',
'vTitle' => 'Percent',
'lineMode' => 'connected',
'xFormat' => '%a %H:%M',
'drawNullAsZero' => 'false',
'graphType' => 'line',
'_ext' => 'whatever.svg'
]);
if ($this->until !== null) {
$params->set('until', $this->until);
}
$variables = $this->getMetricVariables();
foreach ($this->template->getUrlParams() as $key => $value) {
$params->set($key, $value->resolve($variables));
}
$image = $this->graphiteWebClient->request('/render', $params, 'POST', [
'Accept-language' => 'en',
'Content-type' => 'application/x-www-form-urlencoded'
]);
$response
->setHeader('Content-Type', 'image/png', true)
->setHeader('Content-Disposition', 'inline; filename="graph.png"', true)
->setBody($image)
->sendResponse();
exit;
}
/**
* Extract the values of the template's metrics filters' variables from the target metrics
*
* @return string[]
*/
public function getMetricVariables()
{
/** @var MacroTemplate[][] $curves */
$curves = $this->template->getCurves();
$variables = [];
foreach ($this->metrics as $curveName => $metric) {
$vars = $curves[$curveName][0]->reverseResolve($metric);
if ($vars !== false) {
$variables = array_merge($variables, $vars);
}
}
return $variables;
}
/**
* Get begin
*
* @return string
*/
public function getFrom()
{
return $this->from;
}
/**
* Set begin
*
* @param string $from
*
* @return $this
*/
public function setFrom($from)
{
$this->from = $from;
return $this;
}
/**
* Get end
*
* @return string
*/
public function getUntil()
{
return $this->until;
}
/**
* Set end
*
* @param string $until
*
* @return $this
*/
public function setUntil($until)
{
$this->until = $until;
return $this;
}
/**
* Get width
*
* @return int
*/
public function getWidth()
{
return $this->width;
}
/**
* Set width
*
* @param int $width
*
* @return $this
*/
public function setWidth($width)
{
$this->width = $width;
return $this;
}
/**
* Get height
*
* @return int
*/
public function getHeight()
{
return $this->height;
}
/**
* Set height
*
* @param int $height
*
* @return $this
*/
public function setHeight($height)
{
$this->height = $height;
return $this;
}
/**
* Get whether to show the chart's legend
*
* @return bool
*/
public function getShowLegend()
{
return $this->showLegend;
}
/**
* Set whether to show the chart's legend
*
* @param bool $showLegend
*
* @return $this
*/
public function setShowLegend($showLegend)
{
$this->showLegend = $showLegend;
return $this;
}
/**
* Get time zone
*
* @return DateTimeZone
*/
public function getTimeZone()
{
return $this->timeZone;
}
/**
* Set time zone
*
* @param DateTimeZone $timeZone
*
* @return $this
*/
public function setTimeZone(DateTimeZone $timeZone)
{
$this->timeZone = $timeZone;
return $this;
}
}

View file

@ -31,6 +31,101 @@ class Template
{
}
/**
* Get all charts based on this template and applicable to the metrics
* from the given data source restricted by the given filter
*
* @param MetricsDataSource $dataSource
* @param string[] $filter
*
* @return Chart[]
*/
public function getCharts(MetricsDataSource $dataSource, array $filter)
{
$metrics = [];
foreach ($this->curves as $curveName => $curve) {
$query = $dataSource->select()->from($curve[0]);
foreach ($filter as $key => $value) {
$query->where($key, $value);
}
foreach ($query->fetchColumn() as $metric) {
$vars = $curve[0]->reverseResolve($metric);
if ($vars !== false) {
$metrics[$curveName][$metric] = $vars;
}
}
}
if (empty($metrics)) {
return [];
}
$intersectingVariables = [];
foreach ($metrics as $curveName1 => $_) {
foreach ($metrics as $curveName2 => $_) {
if ($curveName1 !== $curveName2 && ! isset($intersectingVariables[$curveName2][$curveName1])) {
$vars = array_intersect(
$this->curves[$curveName1][0]->getMacros(),
$this->curves[$curveName2][0]->getMacros()
);
if (! empty($vars)) {
$intersectingVariables[$curveName1][$curveName2] = $vars;
}
}
}
}
$iterState = [];
foreach ($metrics as $curveName => $metric) {
$iterState[$curveName] = [0, array_keys($metric)];
}
$metricsCombinations = [];
$currentMetrics = [];
do {
foreach ($metrics as $curveName => $metric) {
$currentMetrics[$curveName] = $iterState[$curveName][1][ $iterState[$curveName][0] ];
}
$acceptCombination = true;
foreach ($intersectingVariables as $curveName1 => $intersectingWith) {
foreach ($intersectingWith as $curveName2 => $vars) {
foreach ($vars as $key) {
if ($metrics[$curveName1][ $currentMetrics[$curveName1] ][$key]
!== $metrics[$curveName2][ $currentMetrics[$curveName2] ][$key]) {
$acceptCombination = false;
break 3;
}
}
}
}
if ($acceptCombination) {
$metricsCombinations[] = $currentMetrics;
}
$overflow = true;
foreach ($iterState as $curveName => & $iterSubState) {
if (isset($iterSubState[1][ ++$iterSubState[0] ])) {
$overflow = false;
break;
} else {
$iterSubState[0] = 0;
}
}
unset($iterSubState);
} while (! $overflow);
$charts = [];
foreach ($metricsCombinations as $metricsCombination) {
$charts[] = new Chart($dataSource->getClient(), $this, $metricsCombination);
}
return $charts;
}
/**
* Get curves to show in a chart by name with Graphite Web metric filters and Graphite functions
*