Skip to content

Commit

Permalink
SLA: Use sla models for generating host/service reports
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabteab committed Feb 9, 2023
1 parent 490ca7b commit 72acb56
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 67 deletions.
26 changes: 0 additions & 26 deletions library/Icingadb/ProvidedHook/Reporting/HostSlaReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@
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;

Expand Down Expand Up @@ -43,26 +39,4 @@ protected function createReportRow($row)
->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;
}
}
33 changes: 2 additions & 31 deletions library/Icingadb/ProvidedHook/Reporting/ServiceSlaReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Icinga\Application\Icinga;
use Icinga\Module\Icingadb\Model\Service;
use Icinga\Module\Icingadb\Model\ServiceSlaHistory;
use Icinga\Module\Reporting\ReportData;
use Icinga\Module\Reporting\ReportRow;
use Icinga\Module\Reporting\Timerange;
Expand Down Expand Up @@ -35,38 +36,8 @@ protected function createReportData()

protected function createReportRow($row)
{
if ($row->sla === null) {
return null;
}

return (new ReportRow())
->setDimensions([$row->host->display_name, $row->display_name])
->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;
}
}
243 changes: 233 additions & 10 deletions library/Icingadb/ProvidedHook/Reporting/SlaReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,27 @@

use DateInterval;
use DatePeriod;
use DateTime;
use Icinga\Module\Icingadb\Common\Auth;
use Icinga\Module\Icingadb\Common\Database;
use Icinga\Module\Icingadb\Model\HostSlaHistory;
use Icinga\Module\Icingadb\Model\HostState;
use Icinga\Module\Icingadb\Model\ServiceSlaHistory;
use Icinga\Module\Icingadb\Model\ServiceState;
use Icinga\Module\Icingadb\Model\SlaHistoryState;
use Icinga\Module\Icingadb\Widget\EmptyState;
use Icinga\Module\Reporting\Hook\ReportHook;
use Icinga\Module\Reporting\ReportData;
use Icinga\Module\Reporting\ReportRow;
use Icinga\Module\Reporting\SlaTimeline;
use Icinga\Module\Reporting\Timerange;
use ipl\Html\Form;
use ipl\Html\Html;
use ipl\Orm\Query;
use ipl\Sql\Expression;
use ipl\Stdlib\Filter;
use ipl\Stdlib\Filter\Rule;
use ipl\Stdlib\Str;
use ipl\Web\Filter\QueryString;

use function ipl\I18n\t;
Expand Down Expand Up @@ -59,7 +70,78 @@ abstract protected function createReportRow($row);
*
* @return iterable
*/
abstract protected function fetchSla(Timerange $timerange, Rule $filter = null);
protected function fetchSla(Timerange $timerange, Rule $filter = null): Query
{
$start = $timerange->getStart();
$end = $timerange->getEnd();
$isHostQuery = $this instanceof HostSlaReport;
if ($isHostQuery) {
$query = HostSlaHistory::on($this->getDb());
} else {
$query = ServiceSlaHistory::on($this->getDb());
}

$index = 0;
foreach ($query->getUnions() as $union) {
$filterAll = Filter::all();
if ($index >= 2) {
if ($index < 3) {
if ($isHostQuery) {
$filterAll->add(Filter::unlike('sla_history_state.service_id', '*'));
}

$filterAll
->add(Filter::greaterThan('sla_history_state.event_time', $start))
->add(Filter::lessThan('sla_history_state.event_time', $end));
} else {
$union->columns(
array_merge($union->getColumns(), [
'event_time' => new Expression($end->format('Uv'))
])
);
}
} else {
if ($isHostQuery) {
$filterAll->add(Filter::unlike('sla_history_downtime.service_id', '*'));
}

$filterAll
->add(Filter::lessThan('sla_history_downtime.downtime_start', $end))
->add(Filter::greaterThanOrEqual('sla_history_downtime.downtime_end', $start));

if ($index === 1) {
$filterAll->add(Filter::lessThan('sla_history_downtime.downtime_end', $end));
} else {
$union->columns(
array_merge(
$union->getColumns(),
[
'event_time' => new Expression(
sprintf(
'GREATEST(%s_sla_history_downtime.downtime_start, %s)',
$isHostQuery ? 'host' : 'service',
$start->format('Uv')
)
)
]
)
);
}
}

++$index;

$union->filter($filterAll);

if ($filter !== null) {
$union->filter($filter);
}
}

$this->applyRestrictions($query);

return $query;
}

protected function fetchReportData(Timerange $timerange, array $config = null)
{
Expand Down Expand Up @@ -95,24 +177,93 @@ protected function fetchReportData(Timerange $timerange, array $config = null)
$dimensions[] = ucfirst($config['breakdown']);
$rd->setDimensions($dimensions);

$isHostQuery = $this instanceof HostSlaReport;
foreach ($this->yieldTimerange($timerange, $interval, $boundary) as list($start, $end)) {
$reports = [];
foreach ($this->fetchSla(new Timerange($start, $end), $filter) as $row) {
$row = $this->createReportRow($row);

if ($row === null) {
$key = $isHostQuery ? $row->display_name : $row->host_display_name . '!' . $row->display_name;
if (
$row->event_type === 'end'
&& (
! isset($reports[$key])
&& ! $rd->hasTimeline($key)
)
) {
// No data available
continue;
}

$dimensions = $row->getDimensions();
if (isset($reports[$key])) {
$timeline = $reports[$key];
} else {
$timeline = new SlaTimeline($start, $end);
$serviceId = null;
if (! $this instanceof HostSlaReport) {
$serviceId = $row->service_id;
}

$timeline->setInitialHardState(
$this->fetchInitialHardState($start, $row->host_id, $serviceId)
);
}

$timeline
->addEvent($row->event_type)
->addTime((int) $row->event_time)
->addState($row->hard_state)
->addPreviousState($row->previous_hard_state);

$reports[$key] = $timeline;
}

foreach ($reports as $name => $timeline) {
$rd->addTimeline($name, $timeline);
$row = (object) [];
$row->sla = $timeline->getResult();
$row->display_name = $name;

if (strpos($name, '!') !== false) {
list($host, $service) = Str::trimSplit($name, '!');
$row->display_name = $service;
$row->host_display_name = $host;
}

$report = $this->createReportRow($row);
$dimensions = $report->getDimensions();
$dimensions[] = $start->format($format);
$row->setDimensions($dimensions);
$report->setDimensions($dimensions);

$rows[] = $row;
$rows[] = $report;
}
}
} else {
foreach ($this->fetchSla($timerange, $filter) as $row) {
$rows[] = $this->createReportRow($row);
if ($rd->hasTimeline($row->display_name)) {
$timeline = $rd->getTimeline($row->display_name)[0];
} else {
$timeline = new SlaTimeline($timerange->getStart(), $timerange->getEnd());
$serviceId = null;
if (! $this instanceof HostSlaReport) {
$serviceId = $row->service_id;
}

$timeline->setInitialHardState(
$this->fetchInitialHardState($timerange->getStart(), $row->host_id, $serviceId)
);
}

$timeline
->addEvent($row->event_type)
->addTime((int) $row->event_time)
->addState($row->hard_state)
->addPreviousState($row->previous_hard_state);

$rd->setTimeline($row->display_name, $timeline);

if ($row->event_type === 'end') {
$row->sla = $timeline->getResult();
$rows[] = $this->createReportRow($row);
}
}
}

Expand Down Expand Up @@ -149,7 +300,7 @@ protected function yieldTimerange(Timerange $timerange, DateInterval $interval,
$period = new DatePeriod($start, $interval, $end, DatePeriod::EXCLUDE_START_DATE);

foreach ($period as $date) {
/** @var \DateTime $date */
/** @var DateTime $date */
yield [$start, (clone $date)->sub($oneSecond)];

$start = $date;
Expand Down Expand Up @@ -240,7 +391,11 @@ public function getHtml(Timerange $timerange, array $config = null)
}

// We only have one average
$average = $data->getAverages()[0];
if (strpos($this->getName(), 'Icinga DB') !== false) {
$average = $data->getIcingaDBAvg();
} else {
$average = $data->getAverages()[0];
}

if ($average < $threshold) {
$slaClass = 'nok';
Expand Down Expand Up @@ -274,6 +429,74 @@ public function getHtml(Timerange $timerange, array $config = null)
]
);

// echo '<pre>' . nl2br($data->getTimelineString()) . '</pre>';
return $table;
}

/**
* Get the initial hard state of the given host/service object
*
* @param DateTime $start The start time of the generated sla
* @param string $hostId Host binary/hex id to fetch the initial hard state for
*
* @return int
*/
protected function fetchInitialHardState(DateTime $start, string $hostId, string $serviceId = null): int
{
// Use OK/UP as initial hard state, when neither of the following queries could determine a correct state
$initialHardState = 0;
$start = $start->format('U');

$serviceFilter = $serviceId === null
? Filter::unlike('service_id', '*')
: Filter::equal('service_id', $serviceId);
// Use the latest event at or before the beginning of the SLA interval as the initial state.
$hardState = SlaHistoryState::on($this->getDb())
->columns(['hard_state'])
->filter(
Filter::all(
Filter::equal('host_id', $hostId),
$serviceFilter,
Filter::lessThanOrEqual('event_time', $start)
)
)
->resetOrderBy()
->orderBy('event_time', 'DESC')
->limit(1);

$hardState = $hardState->first();

// If this doesn't exit, use the previous state from the first event after the beginning of the SLA interval.
if (! $hardState) {
$hardState = SlaHistoryState::on($this->getDb())
->columns(['hard_state' => 'previous_hard_state'])
->filter(
Filter::all(
Filter::equal('host_id', $hostId),
$serviceFilter,
Filter::greaterThan('event_time', $start)
)
);

$hardState = $hardState->first();

// If this also doesn't exist, use the current host/service state.
if (! $hardState) {
if ($serviceId !== null) {
$hardState = ServiceState::on($this->getDb())
->filter(Filter::equal('service_id', $serviceId));
} else {
$hardState = HostState::on($this->getDb());
}

$hardState
->columns(['hard_state'])
->filter(Filter::equal('host_id', $hostId));

$hardState = $hardState->first();
}
}

return $hardState === null ? $initialHardState : (int) $hardState->hard_state;
}
}

0 comments on commit 72acb56

Please sign in to comment.