icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php
Matthias Jentsch 8f9e970360 Ignore negative values in reaction time
Negative values for reaction time make no sense in the reaction time chart. When the notification was acknowledged before it startet we assume a reaction time of 0.

fixes #8198
2015-02-04 09:55:39 +01:00

662 lines
18 KiB
PHP

<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | http://www.gnu.org/licenses/gpl-2.0.txt */
use Icinga\Chart\GridChart;
use Icinga\Chart\Unit\LinearUnit;
use Icinga\Chart\Unit\StaticAxis;
use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Web\Widget\SelectBox;
use Icinga\Web\Url;
class Monitoring_AlertsummaryController extends Controller
{
/**
* @var string
*/
protected $url;
/**
* @var array
*/
private $notificationData;
/**
* @var array
*/
private $problemData;
/**
* Init data set
*/
public function init()
{
$tabs = $this->getTabs();
if (in_array($this->_request->getActionName(), array('alertsummary'))) {
$tabs->extend(new OutputFormat())->extend(new DashboardAction());
}
$this->url = Url::fromRequest();
$this->notificationData = $this->createNotificationData();
$this->problemData = $this->createProblemData();
}
/**
* @param string $action
* @param bool $title
*/
protected function addTitleTab($action, $title = false)
{
$title = $title ?: ucfirst($action);
$this->getTabs()->add(
$action,
array(
'title' => $title,
'url' => $this->url
)
)->activate($action);
$this->view->title = $title;
}
/**
* Creat full report
*/
public function indexAction()
{
$this->addTitleTab('alertsummary', $this->translate('Alert Summary'));
$this->view->intervalBox = $this->createIntervalBox();
$this->view->recentAlerts = $this->createRecentAlerts();
$this->view->interval = $this->getInterval();
$this->view->defectChart = $this->createDefectImage();
$this->view->healingChart = $this->createHealingChart();
$this->view->perf = $this->createNotificationPerfdata();
$this->view->trend = $this->createTrendInformation();
$this->setAutorefreshInterval(15);
$query = $this->backend->select()->from(
'notification',
array(
'host',
'host_display_name',
'service',
'service_display_name',
'notification_output',
'notification_contact',
'notification_start_time',
'notification_state'
)
);
$this->view->notifications = $query->paginate();
}
/**
* Create data for charts
*
* @return array
*/
private function createNotificationData()
{
$interval = $this->getInterval();
$query = $this->backend->select()->from(
'notification',
array(
'host',
'service',
'notification_output',
'notification_contact',
'notification_start_time',
'notification_state'
)
);
$query->setFilter(
new Icinga\Data\Filter\FilterExpression(
'n.start_time',
'>=',
$this->getBeginDate($interval)->format('Y-m-d H:i:s')
)
);
$query->order('notification_start_time', 'asc');
$records = $query->getQuery()->fetchAll();
$data = array();
$period = $this->createPeriod($interval);
foreach ($period as $entry) {
$id = $this->getPeriodFormat($interval, $entry->getTimestamp());
$data[$id] = array($id, 0);
}
foreach ($records as $item) {
$id = $this->getPeriodFormat($interval, $item->notification_start_time);
if (empty($data[$id])) {
$data[$id] = array($id, 0);
}
$data[$id][1]++;
}
return $data;
}
/**
* Trend information for notifications
*
* @return stdClass
*/
private function createTrendInformation()
{
$date = new DateTime();
$beginDate = $date->sub(new DateInterval('P3D'));
$query = $this->backend->select()->from(
'notification',
array(
'host',
'service',
'notification_output',
'notification_contact',
'notification_start_time',
'notification_state'
)
);
$query->setFilter(
new Icinga\Data\Filter\FilterExpression(
'n.start_time',
'>=',
$beginDate->format('Y-m-d H:i:s')
)
);
$query->order('notification_start_time', 'asc');
$records = $query->getQuery()->fetchAll();
$slots = array();
$period = new DatePeriod($beginDate, new DateInterval('P1D'), 2, DatePeriod::EXCLUDE_START_DATE);
foreach ($period as $entry) {
$slots[$entry->format('Y-m-d')] = 0;
}
foreach ($records as $item) {
$id = strftime('%Y-%m-%d', $item->notification_start_time);
if (isset($slots[$id])) {
$slots[$id]++;
}
}
$yesterday = array_shift($slots);
$today = array_shift($slots);
$out = new stdClass();
if ($yesterday === $today) {
$out->trend = $this->translate('unchanged');
} elseif ($yesterday > $today) {
$out->trend = $this->translate('down');
} else {
$out->trend = $this->translate('up');
}
if ($yesterday <= 0) {
$out->percent = 100;
} elseif ($yesterday === $today) {
$out->percent = 0;
} else {
$out->percent = sprintf(
'%.2f',
100 - ((100/($yesterday > $today ? $yesterday : $today)) * ($yesterday > $today ? $today : $yesterday))
);
}
return $out;
}
/**
* Perfdata for notifications
*
* @return stdClass
*/
private function createNotificationPerfdata()
{
$interval = $this->getInterval();
$query = $this->backend->select()->from(
'notification',
array(
'host',
'service',
'notification_output',
'notification_contact',
'notification_start_time',
'notification_state'
)
);
$query->setFilter(
new Icinga\Data\Filter\FilterExpression(
'n.start_time',
'>=',
$this->getBeginDate($interval)->format('Y-m-d H:i:s')
)
);
$query->order('notification_start_time', 'desc');
$records = $query->getQuery()->fetchAll();
$slots = array();
foreach ($records as $item) {
$id = strftime('%Y-%m-%d %H:%I:00', $item->notification_start_time);
if (empty($slots[$id])) {
$slots[$id] = 0;
}
$slots[$id]++;
}
$out = new stdClass();
if (! empty($slots)) {
$out->avg = sprintf('%.2f', array_sum($slots) / count($slots));
} else {
$out->avg = '0.0';
}
$out->last = array_shift($slots);
return $out;
}
/**
* Problems for notifications
*
* @return array
*/
private function createProblemData()
{
$interval = $this->getInterval();
$query = $this->backend->select()->from(
'EventHistory',
array(
'host_name',
'service_description',
'object_type',
'timestamp',
'state',
'attempt',
'max_attempts',
'output',
'type',
'host',
'service',
'service_host_name'
)
);
$query->addFilter(
new Icinga\Data\Filter\FilterExpression(
'timestamp',
'>=',
$this->getBeginDate($interval)->getTimestamp()
)
);
$query->addFilter(
new Icinga\Data\Filter\FilterExpression(
'state',
'>',
0
)
);
$defects = array();
$records = $query->getQuery()->fetchAll();
$period = $this->createPeriod($interval);
foreach ($period as $entry) {
$id = $this->getPeriodFormat($interval, $entry->getTimestamp());
$defects[$id] = array($id, 0);
}
foreach ($records as $item) {
$id = $this->getPeriodFormat($interval, $item->timestamp);
if (empty($defects[$id])) {
$defects[$id] = array($id, 0);
}
$defects[$id][1]++;
}
return $defects;
}
/**
* Healing svg image
*
* @return GridChart
*/
public function createHealingChart()
{
$gridChart = new GridChart();
$gridChart->alignTopLeft();
$gridChart->setAxisLabel($this->createPeriodDescription(), mt('monitoring', 'Notifications'))
->setXAxis(new StaticAxis())
->setYAxis(new LinearUnit(10))
->setAxisMin(null, 0);
$interval = $this->getInterval();
$query = $this->backend->select()->from(
'notification',
array(
'host',
'service',
'notification_object_id',
'notification_output',
'notification_contact',
'notification_start_time',
'notification_state',
'acknowledgement_entry_time'
)
);
$query->setFilter(
new Icinga\Data\Filter\FilterExpression(
'n.start_time',
'>=',
$this->getBeginDate($interval)->format('Y-m-d H:i:s')
)
);
$query->order('notification_start_time', 'asc');
$records = $query->getQuery()->fetchAll();
$interval = $this->getInterval();
$period = $this->createPeriod($interval);
$dAvg = array();
$dMax = array();
$notifications = array();
$rData = array();
foreach ($period as $entry) {
$id = $this->getPeriodFormat($interval, $entry->getTimestamp());
$dMax[$id] = array($id, 0);
$dAvg[$id] = array($id, 0, 0);
$notifications[$id] = array($id, 0);
}
foreach ($records as $item) {
$id = $this->getPeriodFormat($interval, $item->notification_start_time);
if ($item->notification_state == '0' && isset($rData[$item->notification_object_id])) {
$rData[$item->notification_object_id]['recover'] =
$item->notification_start_time - $rData[$item->notification_object_id]['entry'];
} elseif ($item->notification_state !== '0') {
$recover = 0;
if ($item->acknowledgement_entry_time) {
$recover = $item->acknowledgement_entry_time - $item->notification_start_time;
/*
* Acknowledgements may happen before the actual notification starts, since notifications
* can be configured to start a certain time after the problem. In that case we assume
* a reaction time of 0s.
*/
if ($recover < 0) {
$recover = 0;
}
}
$rData[$item->notification_object_id] = array(
'id' => $id,
'entry' => $item->notification_start_time,
'recover' => $recover
);
}
}
foreach ($rData as $item) {
$notifications[$item['id']][1]++;
if ($item['recover'] > $dMax[$item['id']][1]) {
$dMax[$item['id']][1] = (int) $item['recover'];
}
$dAvg[$item['id']][1] += (int) $item['recover'];
$dAvg[$item['id']][2]++;
}
foreach ($dAvg as &$item) {
if ($item[2] > 0) {
$item[1] = ($item[1]/$item[2])/60/60;
}
}
foreach ($dMax as &$item) {
$item[1] = $item[1]/60/60;
}
$gridChart->drawBars(
array(
'label' => $this->translate('Notifications'),
'color' => '#07C0D9',
'data' => $notifications,
'showPoints' => true
)
);
$gridChart->drawLines(
array(
'label' => $this->translate('Avg (min)'),
'color' => '#ffaa44',
'data' => $dAvg,
'showPoints' => true
)
);
$gridChart->drawLines(
array(
'label' => $this->translate('Max (min)'),
'color' => '#ff5566',
'data' => $dMax,
'showPoints' => true
)
);
return $gridChart;
}
/**
* Notifications and defects
*
* @return GridChart
*/
public function createDefectImage()
{
$gridChart = new GridChart();
$gridChart->alignTopLeft();
$gridChart->setAxisLabel($this->createPeriodDescription(), mt('monitoring', 'Notifications'))
->setXAxis(new StaticAxis())
->setYAxis(new LinearUnit(10))
->setAxisMin(null, 0);
$gridChart->drawBars(
array(
'label' => $this->translate('Notifications'),
'color' => '#07C0D9',
'data' => $this->notificationData,
'showPoints' => true
)
);
$gridChart->drawLines(
array(
'label' => $this->translate('Defects'),
'color' => '#ff5566',
'data' => $this->problemData,
'showPoints' => true
)
);
return $gridChart;
}
/**
* Top recent alerts
*
* @return mixed
*/
private function createRecentAlerts()
{
$query = $this->backend->select()->from(
'notification',
array(
'host',
'host_display_name',
'service',
'service_display_name',
'notification_output',
'notification_contact',
'notification_start_time',
'notification_state'
)
);
$query->order('notification_start_time', 'desc');
return $query->paginate(5);
}
/**
* Interval selector box
*
* @return SelectBox
*/
private function createIntervalBox()
{
$box = new SelectBox(
'intervalBox',
array(
'1d' => mt('monitoring', 'One day'),
'1w' => mt('monitoring', 'One week'),
'1m' => mt('monitoring', 'One month'),
'1y' => mt('monitoring', 'One year')
),
mt('monitoring', 'Report interval'),
'interval'
);
$box->applyRequest($this->getRequest());
return $box;
}
/**
* Return reasonable date time format for an interval
*
* @param string $interval
* @param string $timestamp
*
* @return string
*/
private function getPeriodFormat($interval, $timestamp)
{
$format = '';
if ($interval === '1d') {
$format = '%H:00';
} elseif ($interval === '1w') {
$format = '%Y-%m-%d';
} elseif ($interval === '1m') {
$format = '%Y-%m-%d';
} elseif ($interval === '1y') {
$format = '%Y-%m';
}
return strftime($format, $timestamp);
}
/**
* Create a reasonable period based in interval strings
*
* @param $interval
* @return DatePeriod
*/
private function createPeriod($interval)
{
if ($interval === '1d') {
return new DatePeriod($this->getBeginDate($interval), new DateInterval('PT1H'), 24);
} elseif ($interval === '1w') {
return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1D'), 7);
} elseif ($interval === '1m') {
return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1D'), 31);
} elseif ($interval === '1y') {
return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1M'), 12);
}
}
/**
* Return start timestamps based on interval strings
*
* @param $interval
* @return DateTime|null
*/
private function getBeginDate($interval)
{
$new = new DateTime();
if ($interval === '1d') {
return $new->sub(new DateInterval('P1D'));
} elseif ($interval === '1w') {
return $new->sub(new DateInterval('P1W'));
} elseif ($interval === '1m') {
return $new->sub(new DateInterval('P1M'));
} elseif ($interval === '1y') {
return $new->sub(new DateInterval('P1Y'));
}
return null;
}
/**
* Getter for interval
*
* @return string
*
* @throws Zend_Controller_Action_Exception
*/
private function getInterval()
{
$interval = $this->getParam('interval', '1d');
if (false === in_array($interval, array('1d', '1w', '1m', '1y'))) {
throw new Zend_Controller_Action_Exception($this->translate('Value for interval not valid'));
}
return $interval;
}
/**
* Create a human-readable description of the current interval size
*
* @return string The description of the current interval size
*/
private function createPeriodDescription()
{
$int = $this->getInterval();
switch ($int) {
case '1d':
return t('Hour');
break;
case '1w';
return t('Day');
break;
case '1m':
return t('Day');
break;
case '1y':
return t('Month');
break;
}
}
}