mirror of
https://github.com/Icinga/icingaweb2-module-graphite.git
synced 2026-06-09 00:32:31 -04:00
Graphite module prototype: initial commit
This commit is contained in:
commit
f491471622
12 changed files with 654 additions and 0 deletions
85
application/controllers/ShowController.php
Normal file
85
application/controllers/ShowController.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Module\Graphite\GraphiteWeb;
|
||||
use Icinga\Module\Graphite\GraphTemplate;
|
||||
use Icinga\Web\Controller;
|
||||
use Icinga\Web\Widget;
|
||||
use \DirectoryIterator;
|
||||
|
||||
class Graphite_ShowController extends Controller
|
||||
{
|
||||
protected $baseUrl;
|
||||
|
||||
protected $graphiteWeb;
|
||||
|
||||
protected $templates;
|
||||
|
||||
protected $filters;
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->baseUrl = $this->Config()->get('global', 'web_url');
|
||||
$graphite = $this->graphiteWeb = new GraphiteWeb($this->baseUrl);
|
||||
}
|
||||
|
||||
public function hostAction()
|
||||
{
|
||||
$hostname = $this->view->hostname = $this->params->get('host');
|
||||
if (! $hostname) {
|
||||
throw new NotFoundError('Host is required');
|
||||
}
|
||||
$this->tabs()->activate('host');
|
||||
$hosts = $this->Config()->get('global', 'host_pattern');
|
||||
$imgs = array();
|
||||
|
||||
foreach ($this->loadTemplates() as $type => $template) {
|
||||
|
||||
$imgs[$type] = $this->graphiteWeb
|
||||
->select()
|
||||
->from(
|
||||
array('host' => $hosts),
|
||||
$template->getFilterString()
|
||||
)
|
||||
->where('hostname', $hostname)
|
||||
->getImages($template);
|
||||
|
||||
foreach ($imgs[$type] as $img) {
|
||||
$img->setStart($this->params->get('start', '-1hours'))
|
||||
->setWidth($this->params->get('width', '300'))
|
||||
->setHeight($this->params->get('height', '200'))
|
||||
->showLegend(! $this->params->get('hideLegend', false));
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->images = $imgs;
|
||||
}
|
||||
|
||||
protected function loadTemplates()
|
||||
{
|
||||
$dir = $this->Module()->getConfigDir() . '/templates';
|
||||
$templates = array();
|
||||
|
||||
foreach (new DirectoryIterator($dir) as $file) {
|
||||
if ($file->isDot()) continue;
|
||||
$filename = $file->getFilename();
|
||||
if (substr($filename, -5) === '.conf') {
|
||||
$name = substr($filename, 0, -5);
|
||||
$templates[$name] = GraphTemplate::load(
|
||||
file_get_contents($file->getPathname())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ksort($templates);
|
||||
return $templates;
|
||||
}
|
||||
|
||||
protected function tabs()
|
||||
{
|
||||
return $this->view->tabs = Widget::create('tabs')->add('host', array(
|
||||
'label' => 'Graphite - Single Host',
|
||||
'url' => $this->getRequest()->getUrl()
|
||||
));
|
||||
}
|
||||
}
|
||||
17
application/views/scripts/show/host.phtml
Normal file
17
application/views/scripts/show/host.phtml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<div class="controls">
|
||||
<?= $this->tabs ?>
|
||||
<h1><?= $this->hostname ?></h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<?php foreach ($this->images as $type => $imgs): ?>
|
||||
<?php if (count($imgs) > 0): ?>
|
||||
|
||||
<div class="images"><h3><?= $this->escape(ucfirst($type)) ?></h3>
|
||||
<?php foreach ($imgs as $img): ?>
|
||||
<img class="svg" src="<?= $img->getUrl() ?>" alt="<?= $img->getTitle() ?>" title="<?= $img->getTitle() ?>" />
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
12
application/views/scripts/test/apache.phtml
Normal file
12
application/views/scripts/test/apache.phtml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div class="controls">
|
||||
<?= $this->tabs ?>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<?php foreach ($this->images as $base => $img): ?>
|
||||
<div style="width: 260px; float: left; margin-right: 5px;">
|
||||
<h3><?= $this->escape($base) ?></h3>
|
||||
<img src="<?= $img ?>" />
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
28
application/views/scripts/test/cpu.phtml
Normal file
28
application/views/scripts/test/cpu.phtml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
$maxCnt = 0;
|
||||
foreach ($this->images as $base => $cpus) {
|
||||
$maxCnt = max($maxCnt, count($cpus));
|
||||
}
|
||||
?>
|
||||
<div class="controls">
|
||||
<?= $this->tabs ?>
|
||||
<h1>CPUs</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<th style="width: 15em;"> </th>
|
||||
<th>CPUs</th>
|
||||
</tr>
|
||||
<?php foreach ($this->images as $base => $cpus): ?>
|
||||
<tr>
|
||||
<th style="vertical-align: top; text-align: right; padding-right: 2em;"><?= $this->escape($base) ?></th>
|
||||
<td>
|
||||
<?php foreach ($cpus as $num => $img): ?>
|
||||
<div style="width: 53px; float: left;"><img src="<?= $img ?>" /><!--<br />CPU <?= $num ?>--></div>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
</div>
|
||||
81
library/Graphite/GraphDatasource.php
Normal file
81
library/Graphite/GraphDatasource.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Graphite;
|
||||
|
||||
use Icinga\Web\Url;
|
||||
|
||||
class GraphDatasource
|
||||
{
|
||||
protected $path;
|
||||
|
||||
protected $color;
|
||||
|
||||
protected $alias;
|
||||
|
||||
protected $scale;
|
||||
|
||||
protected $scaleToSeconds;
|
||||
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
public function setColor($color)
|
||||
{
|
||||
$this->color = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setScale($scale)
|
||||
{
|
||||
$this->scale = $scale;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setScaleToSeconds($seconds)
|
||||
{
|
||||
$this->scaleToSeconds = $seconds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAlias($alias)
|
||||
{
|
||||
$this->alias = $alias;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if ($this->alias !== null) {
|
||||
return $this->alias;
|
||||
}
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
protected function func()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$function = array_shift($args);
|
||||
return sprintf($function . '(%s)', implode(',', $args));
|
||||
}
|
||||
|
||||
public function addToUrl(Url $url, $metric)
|
||||
{
|
||||
$target = $metric . '.' . $this->path;
|
||||
if ($this->color !== null) {
|
||||
$target = $this->func('color', $target, "'" . $this->color . "'");
|
||||
}
|
||||
if ($this->scaleToSeconds !== null) {
|
||||
$target = $this->func('scaleToSeconds', $target, $this->scaleToSeconds);
|
||||
}
|
||||
if ($this->scale !== null) {
|
||||
$target = $this->func('scale', $target, $this->scale);
|
||||
}
|
||||
if ($this->alias !== null) {
|
||||
$target = $this->func('alias', $target, "'" . $this->alias . "'");
|
||||
}
|
||||
|
||||
return $url->getParams()->add('target', $target);
|
||||
}
|
||||
}
|
||||
120
library/Graphite/GraphTemplate.php
Normal file
120
library/Graphite/GraphTemplate.php
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Graphite;
|
||||
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Module\Graphite\GraphDatasource;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
* TODO: allow same metric multiple times
|
||||
* wording: metric vs datasource
|
||||
* allow a template to generate multiple (different) graphs (e.g. traffic & errors)
|
||||
*
|
||||
*/
|
||||
class GraphTemplate
|
||||
{
|
||||
protected $datasources = array();
|
||||
|
||||
protected $attributes = array();
|
||||
|
||||
protected $filterString;
|
||||
|
||||
/**
|
||||
* Use one of the static constructors instead
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a template from a given string
|
||||
*/
|
||||
public static function load($string)
|
||||
{
|
||||
$tmpl = new static;
|
||||
$tmpl->parse($string);
|
||||
return $tmpl;
|
||||
}
|
||||
|
||||
public function getFilterString()
|
||||
{
|
||||
return $this->filterString;
|
||||
}
|
||||
|
||||
protected function parse($string)
|
||||
{
|
||||
$lines = preg_split('/\n/', $string);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line, ' ');
|
||||
if ($line === '') continue;
|
||||
if ($line[0] === '#') continue;
|
||||
if (preg_match('/^(\w+)\s*=\s*(.+)$/', $line, $m)) {
|
||||
if ($m[1] === 'filter') {
|
||||
$this->filterString = $m[2];
|
||||
} else {
|
||||
$this->attributes[$m[1]] = $m[2];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! preg_match('/^([^:\s]+)\s*:\s*(.+)$/', $line, $m)) {
|
||||
throw new ConfigurationError('Got invalid template line: %s', $line);
|
||||
}
|
||||
|
||||
$ds = new GraphDatasource($m[1]);
|
||||
$params = preg_split('/\s*,\s*/', $m[2]);
|
||||
$props = array();
|
||||
|
||||
foreach ($params as $p) {
|
||||
list($k, $v) = preg_split('/\s*=\s*/', $p, 2);
|
||||
$func = 'set' . ucfirst($k);
|
||||
$ds->$func($v);
|
||||
}
|
||||
|
||||
$this->datasources[$m[1]] = $ds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the given vars into the given string
|
||||
*/
|
||||
protected function fillVars($string, $vars)
|
||||
{
|
||||
$regexes = array();
|
||||
$values = array();
|
||||
|
||||
foreach ($vars as $k => $v) {
|
||||
$regexes[] = '/' . preg_quote('$' . $k, '/') . '/';
|
||||
$values[] = $v;
|
||||
}
|
||||
|
||||
return preg_replace(
|
||||
$regexes,
|
||||
$values,
|
||||
$string
|
||||
);
|
||||
}
|
||||
|
||||
public function getTitle($vars)
|
||||
{
|
||||
return $this->fillVars($this->attributes['title'], $vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the given URL and add all configured data sources based on the
|
||||
* given metric string
|
||||
*/
|
||||
public function extendUrl(Url $url, $metric, $vars)
|
||||
{
|
||||
$params = $url->getParams();
|
||||
foreach ($this->attributes as $k => $v) {
|
||||
$params->add($k, $this->fillVars($v, $vars));
|
||||
}
|
||||
foreach ($this->datasources as $ds) {
|
||||
$ds->addToUrl($url, $metric);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
122
library/Graphite/GraphiteChart.php
Normal file
122
library/Graphite/GraphiteChart.php
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Graphite;
|
||||
|
||||
use Icinga\Web\Url;
|
||||
|
||||
class GraphiteChart
|
||||
{
|
||||
protected $web;
|
||||
|
||||
protected $metric;
|
||||
|
||||
protected $from = '-4hours';
|
||||
|
||||
protected $showLegend = true;
|
||||
|
||||
protected $height = 200;
|
||||
|
||||
protected $width = 300;
|
||||
|
||||
public function __construct(GraphiteWeb $web, GraphTemplate $template, $metric, $vars)
|
||||
{
|
||||
$this->web = $web;
|
||||
$this->template = $template;
|
||||
$this->metric = $metric;
|
||||
$this->vars = $vars;
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->template->getTitle($this->vars);
|
||||
}
|
||||
|
||||
public function setStart($start)
|
||||
{
|
||||
$this->from = $start;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setWidth($width)
|
||||
{
|
||||
$this->width = $width;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHeight($height)
|
||||
{
|
||||
$this->height = $height;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showLegend($show = true)
|
||||
{
|
||||
$this->showLegend = (bool) $show;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMetrics($metrics = array())
|
||||
{
|
||||
}
|
||||
|
||||
public function setFrom($from)
|
||||
{
|
||||
$this->from = $from;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFrom()
|
||||
{
|
||||
return $this->from;
|
||||
}
|
||||
|
||||
protected function getParams()
|
||||
{
|
||||
return array(
|
||||
'height' => $this->height,
|
||||
'width' => $this->width,
|
||||
'_salt' => time() . '.000',
|
||||
'from' => $this->from,
|
||||
'graphOnly' => (string) ! $this->showLegend,
|
||||
'hideLegend' => (string) ! $this->showLegend,
|
||||
'hideGrid' => 'true',
|
||||
'vTitle' => 'Percent',
|
||||
'lineMode' => 'connected', // staircase, slope
|
||||
'xFormat' => '%a %H:%M',
|
||||
'drawNullAsZero' => 'false',
|
||||
'graphType' => 'line', // pie
|
||||
'tz' => 'Europe/Berlin',
|
||||
// 'hideAxes' => 'true',
|
||||
// 'hideYAxis' => 'true',
|
||||
// 'format' => 'svg',
|
||||
// 'pieMode' => 'average',
|
||||
);
|
||||
}
|
||||
|
||||
public function getUrl()
|
||||
{
|
||||
$urlPattern = '/^' . preg_quote(Url::fromPath('/'), '/') . '/';
|
||||
$url = Url::fromPath('/render', $this->getParams());
|
||||
$this->template->extendUrl($url, $this->metric, $this->vars);
|
||||
$url->getParams()->add('_ext', 'whatever.svg');
|
||||
$url = preg_replace($urlPattern, $this->web->getBaseUrl() . '/', $url);
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function fetchImage()
|
||||
{
|
||||
$options = array(
|
||||
'http'=>array(
|
||||
'method'=>"POST",
|
||||
'header'=>
|
||||
"Accept-language: en\r\n".
|
||||
"Content-type: application/x-www-form-urlencoded\r\n",
|
||||
'content'=> $data
|
||||
)
|
||||
);
|
||||
|
||||
$context = stream_context_create($options);
|
||||
header('Content-Type: image/png');
|
||||
return file_get_contents($this->getUrl(), false, $context);
|
||||
}
|
||||
}
|
||||
117
library/Graphite/GraphiteQuery.php
Normal file
117
library/Graphite/GraphiteQuery.php
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Graphite;
|
||||
|
||||
use Icinga\Module\Graphite\GraphiteWeb;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
|
||||
class GraphiteQuery
|
||||
{
|
||||
protected $web;
|
||||
|
||||
protected $search;
|
||||
|
||||
protected $searchPattern;
|
||||
|
||||
public function __construct(GraphiteWeb $web)
|
||||
{
|
||||
$this->web = $web;
|
||||
}
|
||||
|
||||
public function from($base, $pattern = null)
|
||||
{
|
||||
if (is_array($base)) {
|
||||
$key = key($base);
|
||||
if ($pattern === null) {
|
||||
$this->search = current($base);
|
||||
} else {
|
||||
$this->search = $this->replace($pattern, $key, current($base));
|
||||
}
|
||||
} else {
|
||||
// TODO: well... patterns might also work for non-aliases $base's
|
||||
$this->search = $base;
|
||||
}
|
||||
|
||||
$this->searchPattern = $this->search;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSearchPattern()
|
||||
{
|
||||
return $this->searchPattern;
|
||||
}
|
||||
|
||||
protected function replace($string, $key, $replacement)
|
||||
{
|
||||
return preg_replace(
|
||||
'/\$' . preg_quote($key) . '(\.|$)/',
|
||||
$replacement . '\1',
|
||||
$string
|
||||
);
|
||||
}
|
||||
|
||||
public function where($column, $search)
|
||||
{
|
||||
$this->search = $this->replace($this->search, $column, $search);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all variables ($some_thing) with an asterisk
|
||||
*
|
||||
* TODO: I'd opt for \w instead of [^\.]
|
||||
*/
|
||||
protected function replaceRemainingVariables($string)
|
||||
{
|
||||
return preg_replace('/\$[^\.]+(\.|$)/', '*\1', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a filter string allowing us to filter metrics
|
||||
*/
|
||||
protected function toFilterString()
|
||||
{
|
||||
return $this->replaceRemainingVariables($this->search);
|
||||
}
|
||||
|
||||
protected function extractVars($string, $pattern)
|
||||
{
|
||||
$regexVar = '/\$(\w+)/';
|
||||
$vars = array();
|
||||
|
||||
if (preg_match_all($regexVar, $pattern, $m)) {
|
||||
$varnames = $m[1];
|
||||
|
||||
$parts = preg_split($regexVar, $pattern);
|
||||
foreach ($parts as $key => $val) {
|
||||
$parts[$key] = preg_quote($val, '/');
|
||||
}
|
||||
|
||||
$regex = '/' . implode('([^\.]+?)', $parts) . '/';
|
||||
if (preg_match($regex, $string, $m)) {
|
||||
array_shift($m);
|
||||
$vars = array_combine($varnames, $m);
|
||||
}
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
public function getImages(GraphTemplate $template)
|
||||
{
|
||||
$charts = array();
|
||||
|
||||
foreach ($this->listMetrics() as $metric) {
|
||||
$vars = $this->extractVars($metric, $this->getSearchPattern());
|
||||
$charts[] = new GraphiteChart($this->web, $template, $metric, $vars);
|
||||
}
|
||||
|
||||
return $charts;
|
||||
}
|
||||
|
||||
public function listMetrics()
|
||||
{
|
||||
return $this->web->listMetrics($this->toFilterString());
|
||||
}
|
||||
}
|
||||
37
library/Graphite/GraphiteWeb.php
Normal file
37
library/Graphite/GraphiteWeb.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Graphite;
|
||||
|
||||
use Icinga\Module\Graphite\GraphTemplate;
|
||||
use Icinga\Module\Graphite\GraphiteQuery;
|
||||
|
||||
class GraphiteWeb
|
||||
{
|
||||
protected $baseUrl;
|
||||
|
||||
public function __construct($baseUrl)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
}
|
||||
|
||||
public function select()
|
||||
{
|
||||
return new GraphiteQuery($this);
|
||||
}
|
||||
|
||||
public function getBaseUrl()
|
||||
{
|
||||
return $this->baseUrl;
|
||||
}
|
||||
|
||||
public function listMetrics($filter)
|
||||
{
|
||||
$res = json_decode(
|
||||
file_get_contents(
|
||||
$this->baseUrl . '/metrics/expand?query=' . $filter
|
||||
)
|
||||
);
|
||||
natsort($res->results);
|
||||
return array_values($res->results);
|
||||
}
|
||||
}
|
||||
17
library/Graphite/HostActions.php
Normal file
17
library/Graphite/HostActions.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Graphite;
|
||||
|
||||
use Icinga\Module\Monitoring\Web\Hook\HostActionsHook;
|
||||
use Icinga\Module\Monitoring\Object\Host;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
class HostActions extends HostActionsHook
|
||||
{
|
||||
public function getActionsForHost(Host $host)
|
||||
{
|
||||
return array(
|
||||
'Graphite' => Url::fromPath('graphite/show/host', array('host' => $host->host_name))
|
||||
);
|
||||
}
|
||||
}
|
||||
14
public/css/module.less
Normal file
14
public/css/module.less
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
div.images {
|
||||
|
||||
display: inline-block;
|
||||
|
||||
h3 {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
img.svg {
|
||||
float: left;
|
||||
border: none;
|
||||
}
|
||||
|
||||
}
|
||||
4
run.php
Normal file
4
run.php
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
$this->registerHook('Monitoring\\HostActions', '\\Icinga\\Module\\Graphite\\HostActions');
|
||||
|
||||
Loading…
Reference in a new issue