mirror of
https://github.com/Icinga/icingaweb2-module-graphite.git
synced 2026-02-03 20:40:06 -05:00
692 lines
20 KiB
PHP
692 lines
20 KiB
PHP
<?php
|
|
|
|
namespace Icinga\Module\Graphite\Web\Widget;
|
|
|
|
use Icinga\Application\Config;
|
|
use Icinga\Application\Icinga;
|
|
use Icinga\Authentication\Auth;
|
|
use Icinga\Exception\ConfigurationError;
|
|
use Icinga\Module\Graphite\Graphing\Chart;
|
|
use Icinga\Module\Graphite\Graphing\GraphingTrait;
|
|
use Icinga\Module\Graphite\Graphing\Template;
|
|
use Icinga\Module\Graphite\Util\InternalProcessTracker as IPT;
|
|
use Icinga\Module\Graphite\Util\TimeRangePickerTools;
|
|
use Icinga\Module\Graphite\Web\Widget\Graphs\Host as HostGraphs;
|
|
use Icinga\Module\Graphite\Web\Widget\Graphs\Service as ServiceGraphs;
|
|
use Icinga\Module\Monitoring\Object\Host;
|
|
use Icinga\Module\Monitoring\Object\MonitoredObject;
|
|
use Icinga\Module\Monitoring\Object\Service;
|
|
use Icinga\Web\Request;
|
|
use Icinga\Web\Url;
|
|
use Icinga\Web\View;
|
|
use Icinga\Web\Widget\AbstractWidget;
|
|
use ipl\Orm\Model;
|
|
use Icinga\Module\Icingadb\Model\Host as IcingadbHost;
|
|
use Icinga\Module\Graphite\Web\Widget\Graphs\Icingadb\IcingadbHost as IcingadbHostGraphs;
|
|
use Icinga\Module\Graphite\Web\Widget\Graphs\Icingadb\IcingadbService as IcingadbServiceGraphs;
|
|
|
|
abstract class Graphs extends AbstractWidget
|
|
{
|
|
use GraphingTrait;
|
|
|
|
/**
|
|
* The Icinga custom variable with the "real" check command (if any) of objects we display graphs for
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $obscuredCheckCommandCustomVar;
|
|
|
|
/**
|
|
* The type of the object to render the graphs for
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $objectType;
|
|
|
|
/**
|
|
* The object to render the graphs for
|
|
*
|
|
* @var MonitoredObject|Model
|
|
*/
|
|
protected $object;
|
|
|
|
/**
|
|
* Graph image width
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $width = '350';
|
|
|
|
/**
|
|
* Graph image height
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $height = '200';
|
|
|
|
/**
|
|
* Graph range start
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $start;
|
|
|
|
/**
|
|
* Graph range end
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $end;
|
|
|
|
/**
|
|
* Whether to render as compact as possible
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $compact = false;
|
|
|
|
/**
|
|
* The check command of the monitored object we display graphs for
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $checkCommand;
|
|
|
|
/**
|
|
* The "real" check command (if any) of the monitored object we display graphs for
|
|
*
|
|
* E.g. the command executed remotely via check_by_ssh
|
|
*
|
|
* @var string|null
|
|
*/
|
|
protected $obscuredCheckCommand;
|
|
|
|
/**
|
|
* Additional CSS classes for the <div/>s around the images
|
|
*
|
|
* @var string[]
|
|
*/
|
|
protected $classes = [];
|
|
|
|
/**
|
|
* Whether to serve a transparent dummy image first and let the JS code load the actual graph
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $preloadDummy = false;
|
|
|
|
/**
|
|
* Whether to render the graphs inline
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $renderInline;
|
|
|
|
/**
|
|
* Whether to explicitly display that no graphs were found
|
|
*
|
|
* @var bool|null
|
|
*/
|
|
protected $showNoGraphsFound;
|
|
|
|
/**
|
|
* Factory, based on the given monitoring object
|
|
*
|
|
* @param MonitoredObject $object
|
|
*
|
|
* @return ?static
|
|
*/
|
|
public static function forMonitoredObject(MonitoredObject $object)
|
|
{
|
|
switch ($object->getType()) {
|
|
case 'host':
|
|
/** @var Host $object */
|
|
return new HostGraphs($object);
|
|
|
|
case 'service':
|
|
/** @var Service $object */
|
|
return new ServiceGraphs($object);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Factory, based on the given icingadb object
|
|
*
|
|
* @param Model $object
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function forIcingadbObject(Model $object)
|
|
{
|
|
if ($object instanceof IcingadbHost) {
|
|
return new IcingadbHostGraphs($object);
|
|
}
|
|
|
|
return new IcingadbServiceGraphs($object);
|
|
}
|
|
|
|
/**
|
|
* Get the Icinga custom variable with the "real" check command (if any) of monitored objects we display graphs for
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function getObscuredCheckCommandCustomVar()
|
|
{
|
|
if (static::$obscuredCheckCommandCustomVar === null) {
|
|
static::$obscuredCheckCommandCustomVar = Config::module('graphite')
|
|
->get('icinga', 'customvar_obscured_check_command', 'check_command');
|
|
}
|
|
|
|
return static::$obscuredCheckCommandCustomVar;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param MonitoredObject|Model $object The object to render the graphs for
|
|
*/
|
|
public function __construct($object)
|
|
{
|
|
$this->object = $object;
|
|
$this->renderInline = Url::fromRequest()->getParam('format') === 'pdf';
|
|
|
|
if ($object instanceof Model) {
|
|
$this->checkCommand = $object->checkcommand_name;
|
|
$this->obscuredCheckCommand = $object->vars[Graphs::getObscuredCheckCommandCustomVar()] ?? null;
|
|
} else {
|
|
$this->checkCommand = $object->{"{$this->objectType}_check_command"};
|
|
$this->obscuredCheckCommand = $object->{
|
|
"_{$this->objectType}_" . Graphs::getObscuredCheckCommandCustomVar()
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process the given request using this widget
|
|
*
|
|
* @param ?Request $request The request to be processed
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function handleRequest(?Request $request = null)
|
|
{
|
|
if ($request === null) {
|
|
$request = Icinga::app()->getRequest();
|
|
}
|
|
|
|
$params = $request->getUrl()->getParams();
|
|
list($this->start, $this->end) = $this->getRangeFromTimeRangePicker($request);
|
|
$this->width = $params->shift('width', $this->width);
|
|
$this->height = $params->shift('height', $this->height);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Render the graphs list
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getGraphsList()
|
|
{
|
|
$result = []; // kind of string builder
|
|
$imageBaseUrl = $this->getImageBaseUrl();
|
|
$allTemplates = $this->getAllTemplates();
|
|
$actualCheckCommand = $this->obscuredCheckCommand === null ? $this->checkCommand : $this->obscuredCheckCommand;
|
|
$concreteTemplates = $allTemplates->getTemplates($actualCheckCommand);
|
|
|
|
$excludedMetrics = [];
|
|
|
|
foreach ($concreteTemplates as $concreteTemplate) {
|
|
foreach ($concreteTemplate->getCurves() as $curve) {
|
|
$excludedMetrics[] = $curve[0];
|
|
}
|
|
}
|
|
|
|
IPT::recordf("Icinga check command: %s", $this->checkCommand);
|
|
IPT::recordf("Obscured check command: %s", $this->obscuredCheckCommand);
|
|
|
|
foreach (
|
|
[
|
|
['template', $concreteTemplates, []],
|
|
['default_template', $allTemplates->getDefaultTemplates(), $excludedMetrics],
|
|
] as $templateSet
|
|
) {
|
|
list($urlParam, $templates, $excludeMetrics) = $templateSet;
|
|
|
|
if ($urlParam === 'template') {
|
|
IPT::recordf('Applying templates for check command %s', $actualCheckCommand);
|
|
} else {
|
|
IPT::recordf('Applying default templates, excluding previously used metrics');
|
|
}
|
|
|
|
IPT::indent();
|
|
|
|
foreach ($templates as $templateName => $template) {
|
|
if ($this->designedForObjectType($template)) {
|
|
IPT::recordf('Applying template %s', $templateName);
|
|
IPT::indent();
|
|
|
|
$charts = $template->getCharts(
|
|
static::getMetricsDataSource(),
|
|
$this->object,
|
|
[],
|
|
$excludeMetrics
|
|
);
|
|
|
|
if (! empty($charts)) {
|
|
$currentGraphs = [];
|
|
|
|
foreach ($charts as $chart) {
|
|
/** @var Chart $chart */
|
|
|
|
$metricVariables = $chart->getMetricVariables();
|
|
$bestIntersect = -1;
|
|
$bestPos = count($result);
|
|
|
|
foreach ($result as $graphPos => & $graph) {
|
|
$currentIntersect = count(array_intersect_assoc($graph[1], $metricVariables));
|
|
|
|
if ($currentIntersect >= $bestIntersect) {
|
|
$bestIntersect = $currentIntersect;
|
|
$bestPos = $graphPos + 1;
|
|
}
|
|
}
|
|
unset($graph);
|
|
|
|
$urlParams = $template->getUrlParams();
|
|
if (array_key_exists("height", $urlParams)) {
|
|
$actheight = $urlParams["height"]->resolve(['height']);
|
|
if ($actheight < $this->height) {
|
|
$actheight = $this->height;
|
|
}
|
|
} else {
|
|
$actheight = $this->height;
|
|
}
|
|
$actwidth = $this->width;
|
|
$explicitWidth = false;
|
|
if (array_key_exists("width", $urlParams)) {
|
|
$actwidth = $urlParams["width"]->resolve(['width']);
|
|
$explicitWidth = $actwidth;
|
|
}
|
|
|
|
if ($this->renderInline) {
|
|
$chart->setFrom($this->start)
|
|
->setUntil($this->end)
|
|
->setWidth((int) $actwidth)
|
|
->setHeight((int) $actheight)
|
|
->setBackgroundColor('white')
|
|
->setForegroundColor('black')
|
|
->setMajorGridLineColor('grey')
|
|
->setMinorGridLineColor('white')
|
|
->setShowLegend(! $this->compact);
|
|
|
|
$img = new InlineGraphImage($chart);
|
|
} else {
|
|
$imageUrl = $this->filterImageUrl($imageBaseUrl->with($metricVariables))
|
|
->setParam($urlParam, $templateName)
|
|
->setParam('start', $this->start)
|
|
->setParam('end', $this->end)
|
|
->setParam('width', $actwidth)
|
|
->setParam('height', $actheight);
|
|
|
|
if (! $this->compact) {
|
|
$imageUrl->setParam('legend', '1');
|
|
}
|
|
|
|
if ($this->preloadDummy) {
|
|
$src = 'data:image/png;base64,' // 1x1 dummy
|
|
. 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRS'
|
|
. 'TlMAQObYZgAAAApJREFUeAFjZAAAAAQAAhq+CAMAAAAASUVORK5CYII=';
|
|
} else {
|
|
$src = $imageUrl;
|
|
}
|
|
|
|
$img = '<img id="graphiteImg-' . md5((string) $imageUrl) . '"'
|
|
. " src=\"$src\" data-actualimageurl=\"$imageUrl\" class=\"detach graphiteImg\""
|
|
. " alt=\"\" width=\"$actwidth\" height=\"$actheight\"";
|
|
if ($explicitWidth !== false) {
|
|
$img .= " data-width=\"$explicitWidth\"";
|
|
}
|
|
|
|
$img .= '>';
|
|
}
|
|
|
|
$currentGraphs[] = [$img, $metricVariables, $bestPos];
|
|
}
|
|
|
|
foreach (array_reverse($currentGraphs) as $graph) {
|
|
list($img, $metricVariables, $bestPos) = $graph;
|
|
array_splice($result, $bestPos, 0, [[$img, $metricVariables]]);
|
|
}
|
|
}
|
|
|
|
IPT::unindent();
|
|
} else {
|
|
IPT::recordf('Not applying template %s', $templateName);
|
|
}
|
|
}
|
|
|
|
IPT::unindent();
|
|
}
|
|
|
|
if (! empty($result)) {
|
|
foreach ($result as & $graph) {
|
|
$graph = $graph[0];
|
|
}
|
|
unset($graph);
|
|
|
|
$currentUrl = Icinga::app()->getRequest()->getUrl();
|
|
$limit = (int) $currentUrl->getParam('graphs_limit', 2);
|
|
$total = count($result);
|
|
|
|
if ($limit < 1) {
|
|
$limit = -1;
|
|
}
|
|
|
|
if ($limit !== -1 && $total > $limit) {
|
|
$result = array_slice($result, 0, $limit);
|
|
|
|
if (! $this->compact) {
|
|
/** @var View $view */
|
|
$view = $this->view();
|
|
|
|
$url = $this->getGraphsListBaseUrl();
|
|
TimeRangePickerTools::copyAllRangeParameters($url->getParams(), $currentUrl->getParams());
|
|
|
|
$result[] = "<p class='load-more'>{$view->qlink(
|
|
sprintf($view->translate('Load all %d graphs'), $total),
|
|
$url->setParam('graphs_limit', '-1'),
|
|
null,
|
|
['class' => 'action-link']
|
|
)}</p>";
|
|
}
|
|
}
|
|
|
|
$classes = $this->classes;
|
|
$classes[] = 'images';
|
|
|
|
array_unshift($result, '<div class="' . implode(' ', $classes) . '">');
|
|
$result[] = '</div>';
|
|
}
|
|
|
|
if ($this->renderInline) {
|
|
foreach ($result as $html) {
|
|
if ($html instanceof InlineGraphImage) {
|
|
// Errors should occur now or not at all
|
|
$html->render();
|
|
}
|
|
}
|
|
}
|
|
|
|
return implode($result);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
IPT::clear();
|
|
|
|
try {
|
|
$result = $this->getGraphsList();
|
|
} catch (ConfigurationError $e) {
|
|
$view = $this->view();
|
|
|
|
return "<p>{$view->escape($e->getMessage())}</p>"
|
|
. '<p>' . vsprintf(
|
|
$view->escape($view->translate('Please %scorrect%s the configuration of the Graphite module.')),
|
|
Auth::getInstance()->hasPermission('config/modules')
|
|
? explode(
|
|
'$LINK_TEXT$',
|
|
$view->qlink('$LINK_TEXT$', 'graphite/config/backend', null, ['class' => 'action-link'])
|
|
)
|
|
: ['', '']
|
|
) . '</p>';
|
|
}
|
|
|
|
$view = $this->view();
|
|
|
|
if ($result === '' && $this->getShowNoGraphsFound()) {
|
|
$result = "<p>{$view->escape($view->translate('No graphs found'))}</p>";
|
|
}
|
|
|
|
if (IPT::enabled()) {
|
|
$result .= "<h3>{$view->escape($view->translate('Graphs assembling process record'))}</h3>"
|
|
. "<pre>{$view->escape(IPT::dump())}</pre>";
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get time range parameters for Graphite from the URL
|
|
*
|
|
* @param Request $request The request to be used
|
|
*
|
|
* @return string[]
|
|
*/
|
|
protected function getRangeFromTimeRangePicker(Request $request)
|
|
{
|
|
$params = $request->getUrl()->getParams();
|
|
$relative = $params->get(TimeRangePickerTools::getRelativeRangeParameter());
|
|
if ($relative !== null) {
|
|
return ["-$relative", null];
|
|
}
|
|
|
|
$absolute = TimeRangePickerTools::getAbsoluteRangeParameters();
|
|
$start = $params->get($absolute['start']);
|
|
return [
|
|
$start === null ? -TimeRangePickerTools::getDefaultRelativeTimeRange() : $start,
|
|
$params->get($absolute['end'])
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Return a identifier specifying the monitored object we display graphs for
|
|
*
|
|
* @return string
|
|
*/
|
|
abstract protected function getMonitoredObjectIdentifier();
|
|
|
|
/**
|
|
* Get the base URL to a graph specifying just the monitored object kind
|
|
*
|
|
* @return Url
|
|
*/
|
|
abstract protected function getImageBaseUrl();
|
|
|
|
/**
|
|
* Get the base URL to the monitored object's graphs list
|
|
*
|
|
* @return Url
|
|
*/
|
|
abstract protected function getGraphsListBaseUrl();
|
|
|
|
/**
|
|
* Extend the {@link getImageBaseUrl()}'s result's parameters with the concrete monitored object
|
|
*
|
|
* @param Url $url The URL to extend
|
|
*
|
|
* @return Url The given URL
|
|
*/
|
|
abstract protected function filterImageUrl(Url $url);
|
|
|
|
/**
|
|
* Return whether the given template is designed for the type of the object we display graphs for
|
|
*
|
|
* @param Template $template
|
|
*
|
|
* @return bool
|
|
*/
|
|
abstract protected function designedForObjectType(Template $template);
|
|
|
|
/**
|
|
* Get {@link compact}
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function getCompact()
|
|
{
|
|
return $this->compact;
|
|
}
|
|
|
|
/**
|
|
* Set {@link compact}
|
|
*
|
|
* @param bool $compact
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setCompact($compact = true)
|
|
{
|
|
$this->compact = $compact;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the graph image width
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getWidth()
|
|
{
|
|
return $this->width;
|
|
}
|
|
|
|
/**
|
|
* Set the graph image width
|
|
*
|
|
* @param string $width
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setWidth($width)
|
|
{
|
|
$this->width = $width;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the graph image height
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getHeight()
|
|
{
|
|
return $this->height;
|
|
}
|
|
|
|
/**
|
|
* Set the graph image height
|
|
*
|
|
* @param string $height
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setHeight($height)
|
|
{
|
|
$this->height = $height;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get additional CSS classes for the <div/>s around the images
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public function getClasses()
|
|
{
|
|
return $this->classes;
|
|
}
|
|
|
|
/**
|
|
* Set additional CSS classes for the <div/>s around the images
|
|
*
|
|
* @param string[] $classes
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setClasses($classes)
|
|
{
|
|
$this->classes = $classes;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get whether to serve a transparent dummy image first and let the JS code load the actual graph
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function getPreloadDummy()
|
|
{
|
|
return $this->preloadDummy;
|
|
}
|
|
|
|
/**
|
|
* Set whether to serve a transparent dummy image first and let the JS code load the actual graph
|
|
*
|
|
* @param bool $preloadDummy
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setPreloadDummy($preloadDummy = true)
|
|
{
|
|
$this->preloadDummy = $preloadDummy;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get whether to render the graphs inline
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function getRenderInline()
|
|
{
|
|
return $this->renderInline;
|
|
}
|
|
|
|
/**
|
|
* Set whether to render the graphs inline
|
|
*
|
|
* @param bool $renderInline
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setRenderInline($renderInline = true)
|
|
{
|
|
$this->renderInline = $renderInline;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get whether to explicitly display that no graphs were found
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function getShowNoGraphsFound()
|
|
{
|
|
if ($this->showNoGraphsFound === null) {
|
|
$this->showNoGraphsFound = ! Config::module('graphite')->get('ui', 'disable_no_graphs_found');
|
|
}
|
|
|
|
return $this->showNoGraphsFound;
|
|
}
|
|
|
|
/**
|
|
* Set whether to explicitly display that no graphs were found
|
|
*
|
|
* @param bool $showNoGraphsFound
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setShowNoGraphsFound($showNoGraphsFound = true)
|
|
{
|
|
$this->showNoGraphsFound = $showNoGraphsFound;
|
|
|
|
return $this;
|
|
}
|
|
}
|