diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:44:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:44:46 +0000 |
commit | b18bc644404e02b57635bfcc8258e85abb141146 (patch) | |
tree | 686512eacb2dba0055277ef7ec2f28695b3418ea /library/Icingadb/ProvidedHook/Reporting | |
parent | Initial commit. (diff) | |
download | icingadb-web-b18bc644404e02b57635bfcc8258e85abb141146.tar.xz icingadb-web-b18bc644404e02b57635bfcc8258e85abb141146.zip |
Adding upstream version 1.1.1.upstream/1.1.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Icingadb/ProvidedHook/Reporting')
5 files changed, 475 insertions, 0 deletions
diff --git a/library/Icingadb/ProvidedHook/Reporting/HostSlaReport.php b/library/Icingadb/ProvidedHook/Reporting/HostSlaReport.php new file mode 100644 index 0000000..d9c4f4f --- /dev/null +++ b/library/Icingadb/ProvidedHook/Reporting/HostSlaReport.php @@ -0,0 +1,68 @@ +<?php + +/* Icinga DB Web | (c) 2022 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\ProvidedHook\Reporting; + +use Icinga\Application\Icinga; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Module\Reporting\ReportData; +use Icinga\Module\Reporting\ReportRow; +use Icinga\Module\Reporting\Timerange; +use ipl\Sql\Expression; +use ipl\Stdlib\Filter\Rule; + +use function ipl\I18n\t; + +class HostSlaReport extends SlaReport +{ + public function getName() + { + $name = t('Host SLA'); + if (Icinga::app()->getModuleManager()->hasEnabled('idoreports')) { + $name .= ' (Icinga DB)'; + } + + return $name; + } + + protected function createReportData() + { + return (new ReportData()) + ->setDimensions([t('Hostname')]) + ->setValues([t('SLA in %')]); + } + + protected function createReportRow($row) + { + if ($row->sla === null) { + return null; + } + + return (new ReportRow()) + ->setDimensions([$row->display_name]) + ->setValues([(float) $row->sla]); + } + + protected function fetchSla(Timerange $timerange, Rule $filter = null) + { + $sla = Host::on($this->getDb()) + ->columns([ + 'display_name', + 'sla' => new Expression(sprintf( + "get_sla_ok_percent(%s, NULL, '%s', '%s')", + 'host.id', + $timerange->getStart()->format('Uv'), + $timerange->getEnd()->format('Uv') + )) + ]); + + $this->applyRestrictions($sla); + + if ($filter !== null) { + $sla->filter($filter); + } + + return $sla; + } +} diff --git a/library/Icingadb/ProvidedHook/Reporting/ServiceSlaReport.php b/library/Icingadb/ProvidedHook/Reporting/ServiceSlaReport.php new file mode 100644 index 0000000..46a0684 --- /dev/null +++ b/library/Icingadb/ProvidedHook/Reporting/ServiceSlaReport.php @@ -0,0 +1,72 @@ +<?php + +/* Icinga DB Web | (c) 2022 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\ProvidedHook\Reporting; + +use Icinga\Application\Icinga; +use Icinga\Module\Icingadb\Model\Service; +use Icinga\Module\Reporting\ReportData; +use Icinga\Module\Reporting\ReportRow; +use Icinga\Module\Reporting\Timerange; +use ipl\Sql\Expression; +use ipl\Stdlib\Filter\Rule; + +use function ipl\I18n\t; + +class ServiceSlaReport extends SlaReport +{ + public function getName() + { + $name = t('Service SLA'); + if (Icinga::app()->getModuleManager()->hasEnabled('idoreports')) { + $name .= ' (Icinga DB)'; + } + + return $name; + } + + protected function createReportData() + { + return (new ReportData()) + ->setDimensions([t('Hostname'), t('Service Name')]) + ->setValues([t('SLA in %')]); + } + + protected function createReportRow($row) + { + if ($row->sla === null) { + return null; + } + + return (new ReportRow()) + ->setDimensions([$row->host->display_name, $row->display_name]) + ->setValues([(float) $row->sla]); + } + + protected function fetchSla(Timerange $timerange, Rule $filter = null) + { + $sla = Service::on($this->getDb()) + ->columns([ + 'host.display_name', + 'display_name', + 'sla' => new Expression(sprintf( + "get_sla_ok_percent(%s, %s, '%s', '%s')", + 'service.host_id', + 'service.id', + $timerange->getStart()->format('Uv'), + $timerange->getEnd()->format('Uv') + )) + ]); + + $sla->resetOrderBy()->orderBy('host.display_name')->orderBy('display_name'); + + $this->applyRestrictions($sla); + + if ($filter !== null) { + $sla->filter($filter); + } + + return $sla; + } +} diff --git a/library/Icingadb/ProvidedHook/Reporting/SlaReport.php b/library/Icingadb/ProvidedHook/Reporting/SlaReport.php new file mode 100644 index 0000000..8dcc64e --- /dev/null +++ b/library/Icingadb/ProvidedHook/Reporting/SlaReport.php @@ -0,0 +1,297 @@ +<?php + +/* Icinga DB Web | (c) 2022 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\ProvidedHook\Reporting; + +use DateInterval; +use DatePeriod; +use Icinga\Module\Icingadb\Common\Auth; +use Icinga\Module\Icingadb\Common\Database; +use Icinga\Module\Reporting\Hook\ReportHook; +use Icinga\Module\Reporting\ReportData; +use Icinga\Module\Reporting\ReportRow; +use Icinga\Module\Reporting\Timerange; +use ipl\Html\Form; +use ipl\Html\Html; +use ipl\Stdlib\Filter\Rule; +use ipl\Web\Filter\QueryString; +use ipl\Web\Widget\EmptyState; + +use function ipl\I18n\t; + +/** + * Base class for host and service SLA reports + */ +abstract class SlaReport extends ReportHook +{ + use Auth; + use Database; + + /** @var float If an SLA value is lower than the threshold, it is considered not ok */ + const DEFAULT_THRESHOLD = 99.5; + + /** @var int The amount of decimal places for the report result */ + const DEFAULT_REPORT_PRECISION = 2; + + /** + * Create and return a {@link ReportData} container + * + * @return ReportData Container initialized with the expected dimensions and value labels for the specific report + */ + abstract protected function createReportData(); + + /** + * Create and return a {@link ReportRow} + * + * @param mixed $row Data for the row + * + * @return ReportRow|null Row with the dimensions and values for the specific report set according to the data + * expected in {@link createRepportData()} or null for no data + */ + abstract protected function createReportRow($row); + + /** + * Fetch SLA according to specified time range and filter + * + * @param Timerange $timerange + * @param Rule|null $filter + * + * @return iterable + */ + abstract protected function fetchSla(Timerange $timerange, Rule $filter = null); + + protected function fetchReportData(Timerange $timerange, array $config = null) + { + $rd = $this->createReportData(); + $rows = []; + + $filter = trim((string) $config['filter']) ?: '*'; + $filter = $filter !== '*' ? QueryString::parse($filter) : null; + + $interval = null; + $boundary = null; + $format = null; + if (isset($config['breakdown']) && $config['breakdown'] !== 'none') { + switch ($config['breakdown']) { + case 'hour': + $interval = new DateInterval('PT1H'); + $format = 'H:i:s'; + $boundary = '+1 hour'; + + break; + case 'day': + $interval = new DateInterval('P1D'); + $format = 'Y-m-d'; + $boundary = 'tomorrow midnight'; + + 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; + } + + $dimensions = $rd->getDimensions(); + $dimensions[] = ucfirst($config['breakdown']); + $rd->setDimensions($dimensions); + + foreach ($this->yieldTimerange($timerange, $interval, $boundary) as list($start, $end)) { + foreach ($this->fetchSla(new Timerange($start, $end), $filter) as $row) { + $row = $this->createReportRow($row); + + if ($row === null) { + continue; + } + + $dimensions = $row->getDimensions(); + $dimensions[] = $start->format($format); + $row->setDimensions($dimensions); + + $rows[] = $row; + } + } + } else { + foreach ($this->fetchSla($timerange, $filter) as $row) { + $rows[] = $this->createReportRow($row); + } + } + + $rd->setRows($rows); + + return $rd; + } + + /** + * Yield start and end times that recur at the specified interval over the given time range + * + * @param Timerange $timerange + * @param DateInterval $interval + * @param string|null $boundary English text datetime description for calculating bounds to get + * calendar days, weeks or months instead of relative times according to interval + * + * @return \Generator + */ + protected function yieldTimerange(Timerange $timerange, DateInterval $interval, $boundary = null) + { + $start = clone $timerange->getStart(); + $end = clone $timerange->getEnd(); + $oneSecond = new DateInterval('PT1S'); + + if ($boundary !== null) { + $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; + } + + yield [$start, $end]; + } + + public function initConfigForm(Form $form) + { + $form->addElement('text', 'filter', [ + 'label' => t('Filter') + ]); + + $form->addElement('select', 'breakdown', [ + 'label' => t('Breakdown'), + 'options' => [ + 'none' => t('None', 'SLA Report Breakdown'), + 'hour' => t('Hour'), + 'day' => t('Day'), + 'week' => t('Week'), + 'month' => t('Month') + ] + ]); + + $form->addElement('number', 'threshold', [ + 'label' => t('Threshold'), + 'placeholder' => static::DEFAULT_THRESHOLD, + 'step' => '0.01', + 'min' => '1', + 'max' => '100' + ]); + + $form->addElement('number', 'sla_precision', [ + 'label' => t('Amount Decimal Places'), + 'placeholder' => static::DEFAULT_REPORT_PRECISION, + 'min' => '1', + 'max' => '12' + ]); + + $form->addElement('checkbox', 'export_total', [ + 'label' => t('Export Total Averages'), + 'description' => t('Export total averages to CSV and JSON'), + // Instead of y/n, 0/1 can be implicitly cast to bool which is done where the config is actually used. + 'checkedValue' => '1', + 'uncheckedValue' => '0' + ]); + } + + public function getData(Timerange $timerange, array $config = null) + { + return $this->fetchReportData($timerange, $config); + } + + public function getHtml(Timerange $timerange, array $config = null) + { + $data = $this->getData($timerange, $config); + + if (! count($data)) { + return new EmptyState(t('No data found.')); + } + + $threshold = isset($config['threshold']) ? (float) $config['threshold'] : static::DEFAULT_THRESHOLD; + + $tableHeaderCells = []; + + foreach ($data->getDimensions() as $dimension) { + $tableHeaderCells[] = Html::tag('th', null, $dimension); + } + + foreach ($data->getValues() as $value) { + $tableHeaderCells[] = Html::tag('th', null, $value); + } + + $tableRows = []; + $precision = $config['sla_precision'] ?? static::DEFAULT_REPORT_PRECISION; + + 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, $precision)); + + $tableRows[] = Html::tag('tr', null, $cells); + } + + // We only have one average + $average = $data->getAverages()[0]; + + if ($average < $threshold) { + $slaClass = 'nok'; + } else { + $slaClass = 'ok'; + } + + $total = $this instanceof HostSlaReport + ? sprintf(t('Total (%d Hosts)'), $data->count()) + : sprintf(t('Total (%d Services)'), $data->count()); + + $tableRows[] = Html::tag('tr', null, [ + Html::tag('td', ['colspan' => count($data->getDimensions())], $total), + Html::tag('td', ['class' => "sla-column $slaClass"], round($average, $precision)) + ]); + + $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; + } +} diff --git a/library/Icingadb/ProvidedHook/Reporting/TotalHostSlaReport.php b/library/Icingadb/ProvidedHook/Reporting/TotalHostSlaReport.php new file mode 100644 index 0000000..b09ffb7 --- /dev/null +++ b/library/Icingadb/ProvidedHook/Reporting/TotalHostSlaReport.php @@ -0,0 +1,19 @@ +<?php + +/* Icinga DB Web | (c) 2023 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\ProvidedHook\Reporting; + +use Icinga\Module\Icingadb\Hook\Common\TotalSlaReportUtils; + +use function ipl\I18n\t; + +class TotalHostSlaReport extends HostSlaReport +{ + use TotalSlaReportUtils; + + public function getName() + { + return t('Total Host SLA'); + } +} diff --git a/library/Icingadb/ProvidedHook/Reporting/TotalServiceSlaReport.php b/library/Icingadb/ProvidedHook/Reporting/TotalServiceSlaReport.php new file mode 100644 index 0000000..e5ebf57 --- /dev/null +++ b/library/Icingadb/ProvidedHook/Reporting/TotalServiceSlaReport.php @@ -0,0 +1,19 @@ +<?php + +/* Icinga DB Web | (c) 2023 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\ProvidedHook\Reporting; + +use Icinga\Module\Icingadb\Hook\Common\TotalSlaReportUtils; + +use function ipl\I18n\t; + +class TotalServiceSlaReport extends ServiceSlaReport +{ + use TotalSlaReportUtils; + + public function getName() + { + return t('Total Service SLA'); + } +} |