summaryrefslogtreecommitdiffstats
path: root/library/Idoreports
diff options
context:
space:
mode:
Diffstat (limited to 'library/Idoreports')
-rw-r--r--library/Idoreports/HostSlaReport.php104
-rw-r--r--library/Idoreports/IdoReport.php257
-rw-r--r--library/Idoreports/ServiceSlaReport.php104
3 files changed, 465 insertions, 0 deletions
diff --git a/library/Idoreports/HostSlaReport.php b/library/Idoreports/HostSlaReport.php
new file mode 100644
index 0000000..70cbc1b
--- /dev/null
+++ b/library/Idoreports/HostSlaReport.php
@@ -0,0 +1,104 @@
+<?php
+
+// Icinga IDO Reports | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\Idoreports;
+
+use Icinga\Module\Reporting\ReportData;
+use Icinga\Module\Reporting\ReportRow;
+use Icinga\Module\Reporting\Timerange;
+use ipl\Html\Form;
+
+class HostSlaReport extends IdoReport
+{
+ public function getName()
+ {
+ return 'Host SLA';
+ }
+
+ public function initConfigForm(Form $form)
+ {
+ $form->addElement('text', 'filter', [
+ 'label' => 'Filter'
+ ]);
+
+ $form->addElement('select', 'breakdown', [
+ 'label' => 'Breakdown',
+ 'options' => [
+ 'none' => 'None',
+ 'day' => 'Day',
+ 'week' => 'Week',
+ 'month' => 'Month'
+ ]
+ ]);
+
+ $form->addElement('number', 'threshold', [
+ 'label' => 'Threshold',
+ 'placeholder' => '99.5',
+ 'step' => '0.01',
+ 'min' => '1',
+ 'max' => '100'
+ ]);
+ }
+
+ protected function fetchSla(Timerange $timerange, array $config = null)
+ {
+ $rd = new ReportData();
+
+ if (isset($config['breakdown']) && $config['breakdown'] !== 'none') {
+ switch ($config['breakdown']) {
+ case 'day':
+ $interval = new \DateInterval('P1D');
+ $format = 'Y-m-d';
+ $boundary = false;
+ break;
+ case 'week':
+ $interval = new \DateInterval('P1W');
+ $format = 'Y-\WW';
+ $boundary = 'monday next week midnight';
+ break;
+ case 'month':
+ $interval = new \DateInterval('P1M');
+ $format = 'Y-m';
+ $boundary = 'first day of next month midnight';
+ break;
+ }
+
+ $rd
+ ->setDimensions(['Hostname', ucfirst($config['breakdown'])])
+ ->setValues(['SLA in %']);
+
+ $rows = [];
+
+ foreach ($this->yieldTimerange($timerange, $interval, $boundary) as list($start, $end)) {
+ foreach ($this->fetchHostSla(new Timerange($start, $end), $config) as $row) {
+ if ($row->sla === null) {
+ continue;
+ }
+
+ $rows[] = (new ReportRow())
+ ->setDimensions([$row->host_display_name, $start->format($format)])
+ ->setValues([(float) $row->sla]);
+ }
+ }
+
+ $rd->setRows($rows);
+ } else {
+ $rd
+ ->setDimensions(['Hostname'])
+ ->setValues(['SLA in %']);
+
+ $rows = [];
+
+ foreach ($this->fetchHostSla($timerange, $config) as $row) {
+ $rows[] = (new ReportRow())
+ ->setDimensions([$row->host_display_name])
+ ->setValues([(float) $row->sla]);
+ }
+
+ $rd->setRows($rows);
+ }
+
+ return $rd;
+ }
+}
diff --git a/library/Idoreports/IdoReport.php b/library/Idoreports/IdoReport.php
new file mode 100644
index 0000000..36e2d17
--- /dev/null
+++ b/library/Idoreports/IdoReport.php
@@ -0,0 +1,257 @@
+<?php
+
+// Icinga IDO Reports | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\Idoreports;
+
+use Icinga\Application\Icinga;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filterable;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\QueryException;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Reporting\Hook\ReportHook;
+use Icinga\Module\Reporting\ReportData;
+use Icinga\Module\Reporting\Timerange;
+use ipl\Html\Html;
+
+/**
+ * @TODO(el): Respect restrictions from monitoring module
+ */
+abstract class IdoReport extends ReportHook
+{
+ public function getData(Timerange $timerange, array $config = null)
+ {
+ return $this->fetchSla($timerange, $config);
+ }
+
+ public function getHtml(Timerange $timerange, array $config = null)
+ {
+ $data = $this->fetchSla($timerange, $config);
+
+ if (! count($data)) {
+ return Html::tag('p', 'No data found.');
+ }
+
+ $threshold = isset($config['threshold']) ? (float) $config['threshold'] : 99.5;
+
+ $tableHeaderCells = [];
+
+ foreach ($data->getDimensions() as $dimension) {
+ $tableHeaderCells[] = Html::tag('th', null, $dimension);
+ }
+
+ foreach ($data->getValues() as $value) {
+ $tableHeaderCells[] = Html::tag('th', null, $value);
+ }
+
+ $tableRows = [];
+
+ foreach ($data->getRows() as $row) {
+ $cells = [];
+
+ foreach ($row->getDimensions() as $dimension) {
+ $cells[] = Html::tag('td', null, $dimension);
+ }
+
+ // We only have one metric
+ $sla = $row->getValues()[0];
+
+ if ($sla < $threshold) {
+ $slaClass = 'nok';
+ } else {
+ $slaClass = 'ok';
+ }
+
+ $cells[] = Html::tag('td', ['class' => "sla-column $slaClass"], \round($sla, 2));
+
+ $tableRows[] = Html::tag('tr', null, $cells);
+ }
+
+ // We only have one average
+ $average = $data->getAverages()[0];
+
+ if ($average < $threshold) {
+ $slaClass = 'nok';
+ } else {
+ $slaClass = 'ok';
+ }
+
+ $tableRows[] = Html::tag('tr', null, [
+ Html::tag('td', ['colspan' => count($data->getDimensions())], 'Total'),
+ Html::tag('td', ['class' => "sla-column $slaClass"], \round($average, 2))
+ ]);
+
+ $table = Html::tag(
+ 'table',
+ ['class' => 'common-table sla-table'],
+ [
+ Html::tag(
+ 'thead',
+ null,
+ Html::tag(
+ 'tr',
+ null,
+ $tableHeaderCells
+ )
+ ),
+ Html::tag('tbody', null, $tableRows)
+ ]
+ );
+
+ return $table;
+ }
+
+ /**
+ * @param Timerange $timerange
+ * @param array $config
+ *
+ * @return ReportData
+ */
+ abstract protected function fetchSla(Timerange $timerange, array $config = null);
+
+ protected function applyFilterAndRestrictions($filter, Filterable $filterable)
+ {
+ $filters = Filter::matchAll();
+ $filters->setAllowedFilterColumns(array(
+ 'host_name',
+ 'hostgroup_name',
+ 'instance_name',
+ 'service_description',
+ 'servicegroup_name',
+ function ($c) {
+ return \preg_match('/^_(?:host|service)_/i', $c);
+ }
+ ));
+
+ try {
+ if ($filter !== '*') {
+ $filters->addFilter(Filter::fromQueryString($filter));
+ }
+
+ foreach ($this->yieldMonitoringRestrictions() as $filter) {
+ $filters->addFilter($filter);
+ }
+ } catch (QueryException $e) {
+ throw new ConfigurationError(
+ 'Cannot apply filter. You can only use the following columns: %s',
+ implode(', ', array(
+ 'instance_name',
+ 'host_name',
+ 'hostgroup_name',
+ 'service_description',
+ 'servicegroup_name',
+ '_(host|service)_<customvar-name>'
+ )),
+ $e
+ );
+ }
+
+ $filterable->applyFilter($filters);
+ }
+
+ protected function getBackend()
+ {
+ MonitoringBackend::clearInstances();
+
+ return MonitoringBackend::instance();
+ }
+
+ protected function getRestrictions($name)
+ {
+ $app = Icinga::app();
+ if (! $app->isCli()) {
+ $result = $app->getRequest()->getUser()->getRestrictions($name);
+ } else {
+ $result = [];
+ }
+
+ return $result;
+ }
+
+ protected function fetchHostSla(Timerange $timerange, array $config)
+ {
+ $sla = $this->getBackend()->select()->from('hoststatus', ['host_display_name'])->order('host_display_name');
+
+ $this->applyFilterAndRestrictions($config['filter'] ?: '*', $sla);
+
+ /** @var \Zend_Db_Select $select */
+ $select = $sla->getQuery()->getSelectQuery();
+
+ $columns = $sla->getQuery()->getColumns();
+ $columns['sla'] = new \Zend_Db_Expr(\sprintf(
+ "idoreports_get_sla_ok_percent(%s, '%s', '%s', NULL)",
+ 'ho.object_id',
+ $timerange->getStart()->format('Y-m-d H:i:s'),
+ $timerange->getEnd()->format('Y-m-d H:i:s')
+ ));
+
+ $select->columns($columns);
+
+ return $this->getBackend()->getResource()->getDbAdapter()->query($select);
+ }
+
+ protected function fetchServiceSla(Timerange $timerange, array $config)
+ {
+ $sla = $this
+ ->getBackend()
+ ->select()
+ ->from('servicestatus', ['host_display_name', 'service_display_name'])
+ ->order('host_display_name');
+
+ $this->applyFilterAndRestrictions($config['filter'] ?: '*', $sla);
+
+ /** @var \Zend_Db_Select $select */
+ $select = $sla->getQuery()->getSelectQuery();
+
+ $columns = $sla->getQuery()->getColumns();
+ $columns['sla'] = new \Zend_Db_Expr(\sprintf(
+ "idoreports_get_sla_ok_percent(%s, '%s', '%s', NULL)",
+ 'so.object_id',
+ $timerange->getStart()->format('Y-m-d H:i:s'),
+ $timerange->getEnd()->format('Y-m-d H:i:s')
+ ));
+
+ $select->columns($columns);
+
+ return $this->getBackend()->getResource()->getDbAdapter()->query($select);
+ }
+
+ protected function yieldMonitoringRestrictions()
+ {
+ foreach ($this->getRestrictions('monitoring/filter/objects') as $restriction) {
+ if ($restriction !== '*') {
+ yield Filter::fromQueryString($restriction);
+ }
+ }
+ }
+
+ protected function yieldTimerange(Timerange $timerange, \DateInterval $interval, $boundary)
+ {
+ $start = clone $timerange->getStart();
+ $end = clone $timerange->getEnd();
+ $oneSecond = new \DateInterval('PT1S');
+
+ if ($boundary !== false) {
+ $intermediate = (clone $start)->modify($boundary);
+ if ($intermediate < $end) {
+ yield [clone $start, $intermediate->sub($oneSecond)];
+
+ $start->modify($boundary);
+ }
+ }
+
+ $period = new \DatePeriod($start, $interval, $end, \DatePeriod::EXCLUDE_START_DATE);
+
+ foreach ($period as $date) {
+ /** @var \DateTime $date */
+
+ yield [$start, (clone $date)->sub($oneSecond)];
+
+ $start = $date;
+ }
+
+ // @TODO(el): Should we add if ($start < $end) here to protect us from invalid ranges?
+ yield [$start, $end];
+ }
+}
diff --git a/library/Idoreports/ServiceSlaReport.php b/library/Idoreports/ServiceSlaReport.php
new file mode 100644
index 0000000..7127736
--- /dev/null
+++ b/library/Idoreports/ServiceSlaReport.php
@@ -0,0 +1,104 @@
+<?php
+
+// Icinga IDO Reports | (c) 2018 Icinga GmbH | GPLv2
+
+namespace Icinga\Module\Idoreports;
+
+use Icinga\Module\Reporting\ReportData;
+use Icinga\Module\Reporting\ReportRow;
+use Icinga\Module\Reporting\Timerange;
+use ipl\Html\Form;
+
+class ServiceSlaReport extends IdoReport
+{
+ public function getName()
+ {
+ return 'Service SLA';
+ }
+
+ public function initConfigForm(Form $form)
+ {
+ $form->addElement('text', 'filter', [
+ 'label' => 'Filter'
+ ]);
+
+ $form->addElement('select', 'breakdown', [
+ 'label' => 'Breakdown',
+ 'options' => [
+ 'none' => 'None',
+ 'day' => 'Day',
+ 'week' => 'Week',
+ 'month' => 'Month'
+ ]
+ ]);
+
+ $form->addElement('number', 'threshold', [
+ 'label' => 'Threshold',
+ 'placeholder' => '99.5',
+ 'step' => '0.01',
+ 'min' => '1',
+ 'max' => '100'
+ ]);
+ }
+
+ protected function fetchSla(Timerange $timerange, array $config = null)
+ {
+ $rd = new ReportData();
+
+ if (isset($config['breakdown']) && $config['breakdown'] !== 'none') {
+ switch ($config['breakdown']) {
+ case 'day':
+ $interval = new \DateInterval('P1D');
+ $format = 'Y-m-d';
+ $boundary = false;
+ break;
+ case 'week':
+ $interval = new \DateInterval('P1W');
+ $format = 'Y-\WW';
+ $boundary = 'monday next week midnight';
+ break;
+ case 'month':
+ $interval = new \DateInterval('P1M');
+ $format = 'Y-m';
+ $boundary = 'first day of next month midnight';
+ break;
+ }
+
+ $rd
+ ->setDimensions(['Hostname', 'Service Name', ucfirst($config['breakdown'])])
+ ->setValues(['SLA in %']);
+
+ $rows = [];
+
+ foreach ($this->yieldTimerange($timerange, $interval, $boundary) as list($start, $end)) {
+ foreach ($this->fetchServiceSla(new Timerange($start, $end), $config) as $row) {
+ if ($row->sla === null) {
+ continue;
+ }
+
+ $rows[] = (new ReportRow())
+ ->setDimensions([$row->host_display_name, $row->service_display_name, $start->format($format)])
+ ->setValues([(float) $row->sla]);
+ }
+ }
+
+ $rd->setRows($rows);
+ } else {
+ $rd
+ ->setDimensions(['Hostname', 'Service Name'])
+ ->setValues(['SLA in %']);
+
+ $rows = [];
+
+ foreach ($this->fetchServiceSla($timerange, $config) as $row) {
+ $rows[] = (new ReportRow())
+ ->setDimensions([$row->host_display_name, $row->service_display_name])
+ ->setValues([(float) $row->sla]);
+ }
+
+ $rd->setRows($rows);
+ }
+
+ return $rd;
+ }
+}