Skip to content

Commit 72acb56

Browse files
committed
SLA: Use sla models for generating host/service reports
1 parent 490ca7b commit 72acb56

File tree

3 files changed

+235
-67
lines changed

3 files changed

+235
-67
lines changed

library/Icingadb/ProvidedHook/Reporting/HostSlaReport.php

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,8 @@
55
namespace Icinga\Module\Icingadb\ProvidedHook\Reporting;
66

77
use Icinga\Application\Icinga;
8-
use Icinga\Module\Icingadb\Model\Host;
98
use Icinga\Module\Reporting\ReportData;
109
use Icinga\Module\Reporting\ReportRow;
11-
use Icinga\Module\Reporting\Timerange;
12-
use ipl\Sql\Expression;
13-
use ipl\Stdlib\Filter\Rule;
1410

1511
use function ipl\I18n\t;
1612

@@ -43,26 +39,4 @@ protected function createReportRow($row)
4339
->setDimensions([$row->display_name])
4440
->setValues([(float) $row->sla]);
4541
}
46-
47-
protected function fetchSla(Timerange $timerange, Rule $filter = null)
48-
{
49-
$sla = Host::on($this->getDb())
50-
->columns([
51-
'display_name',
52-
'sla' => new Expression(sprintf(
53-
"get_sla_ok_percent(%s, NULL, '%s', '%s')",
54-
'host.id',
55-
$timerange->getStart()->format('Uv'),
56-
$timerange->getEnd()->format('Uv')
57-
))
58-
]);
59-
60-
$this->applyRestrictions($sla);
61-
62-
if ($filter !== null) {
63-
$sla->filter($filter);
64-
}
65-
66-
return $sla;
67-
}
6842
}

library/Icingadb/ProvidedHook/Reporting/ServiceSlaReport.php

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Icinga\Application\Icinga;
88
use Icinga\Module\Icingadb\Model\Service;
9+
use Icinga\Module\Icingadb\Model\ServiceSlaHistory;
910
use Icinga\Module\Reporting\ReportData;
1011
use Icinga\Module\Reporting\ReportRow;
1112
use Icinga\Module\Reporting\Timerange;
@@ -35,38 +36,8 @@ protected function createReportData()
3536

3637
protected function createReportRow($row)
3738
{
38-
if ($row->sla === null) {
39-
return null;
40-
}
41-
4239
return (new ReportRow())
43-
->setDimensions([$row->host->display_name, $row->display_name])
40+
->setDimensions([$row->host_display_name, $row->display_name])
4441
->setValues([(float) $row->sla]);
4542
}
46-
47-
protected function fetchSla(Timerange $timerange, Rule $filter = null)
48-
{
49-
$sla = Service::on($this->getDb())
50-
->columns([
51-
'host.display_name',
52-
'display_name',
53-
'sla' => new Expression(sprintf(
54-
"get_sla_ok_percent(%s, %s, '%s', '%s')",
55-
'service.host_id',
56-
'service.id',
57-
$timerange->getStart()->format('Uv'),
58-
$timerange->getEnd()->format('Uv')
59-
))
60-
]);
61-
62-
$sla->resetOrderBy()->orderBy('host.display_name')->orderBy('display_name');
63-
64-
$this->applyRestrictions($sla);
65-
66-
if ($filter !== null) {
67-
$sla->filter($filter);
68-
}
69-
70-
return $sla;
71-
}
7243
}

library/Icingadb/ProvidedHook/Reporting/SlaReport.php

Lines changed: 233 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,27 @@
66

77
use DateInterval;
88
use DatePeriod;
9+
use DateTime;
910
use Icinga\Module\Icingadb\Common\Auth;
1011
use Icinga\Module\Icingadb\Common\Database;
12+
use Icinga\Module\Icingadb\Model\HostSlaHistory;
13+
use Icinga\Module\Icingadb\Model\HostState;
14+
use Icinga\Module\Icingadb\Model\ServiceSlaHistory;
15+
use Icinga\Module\Icingadb\Model\ServiceState;
16+
use Icinga\Module\Icingadb\Model\SlaHistoryState;
1117
use Icinga\Module\Icingadb\Widget\EmptyState;
1218
use Icinga\Module\Reporting\Hook\ReportHook;
1319
use Icinga\Module\Reporting\ReportData;
1420
use Icinga\Module\Reporting\ReportRow;
21+
use Icinga\Module\Reporting\SlaTimeline;
1522
use Icinga\Module\Reporting\Timerange;
1623
use ipl\Html\Form;
1724
use ipl\Html\Html;
25+
use ipl\Orm\Query;
26+
use ipl\Sql\Expression;
27+
use ipl\Stdlib\Filter;
1828
use ipl\Stdlib\Filter\Rule;
29+
use ipl\Stdlib\Str;
1930
use ipl\Web\Filter\QueryString;
2031

2132
use function ipl\I18n\t;
@@ -59,7 +70,78 @@ abstract protected function createReportRow($row);
5970
*
6071
* @return iterable
6172
*/
62-
abstract protected function fetchSla(Timerange $timerange, Rule $filter = null);
73+
protected function fetchSla(Timerange $timerange, Rule $filter = null): Query
74+
{
75+
$start = $timerange->getStart();
76+
$end = $timerange->getEnd();
77+
$isHostQuery = $this instanceof HostSlaReport;
78+
if ($isHostQuery) {
79+
$query = HostSlaHistory::on($this->getDb());
80+
} else {
81+
$query = ServiceSlaHistory::on($this->getDb());
82+
}
83+
84+
$index = 0;
85+
foreach ($query->getUnions() as $union) {
86+
$filterAll = Filter::all();
87+
if ($index >= 2) {
88+
if ($index < 3) {
89+
if ($isHostQuery) {
90+
$filterAll->add(Filter::unlike('sla_history_state.service_id', '*'));
91+
}
92+
93+
$filterAll
94+
->add(Filter::greaterThan('sla_history_state.event_time', $start))
95+
->add(Filter::lessThan('sla_history_state.event_time', $end));
96+
} else {
97+
$union->columns(
98+
array_merge($union->getColumns(), [
99+
'event_time' => new Expression($end->format('Uv'))
100+
])
101+
);
102+
}
103+
} else {
104+
if ($isHostQuery) {
105+
$filterAll->add(Filter::unlike('sla_history_downtime.service_id', '*'));
106+
}
107+
108+
$filterAll
109+
->add(Filter::lessThan('sla_history_downtime.downtime_start', $end))
110+
->add(Filter::greaterThanOrEqual('sla_history_downtime.downtime_end', $start));
111+
112+
if ($index === 1) {
113+
$filterAll->add(Filter::lessThan('sla_history_downtime.downtime_end', $end));
114+
} else {
115+
$union->columns(
116+
array_merge(
117+
$union->getColumns(),
118+
[
119+
'event_time' => new Expression(
120+
sprintf(
121+
'GREATEST(%s_sla_history_downtime.downtime_start, %s)',
122+
$isHostQuery ? 'host' : 'service',
123+
$start->format('Uv')
124+
)
125+
)
126+
]
127+
)
128+
);
129+
}
130+
}
131+
132+
++$index;
133+
134+
$union->filter($filterAll);
135+
136+
if ($filter !== null) {
137+
$union->filter($filter);
138+
}
139+
}
140+
141+
$this->applyRestrictions($query);
142+
143+
return $query;
144+
}
63145

64146
protected function fetchReportData(Timerange $timerange, array $config = null)
65147
{
@@ -95,24 +177,93 @@ protected function fetchReportData(Timerange $timerange, array $config = null)
95177
$dimensions[] = ucfirst($config['breakdown']);
96178
$rd->setDimensions($dimensions);
97179

180+
$isHostQuery = $this instanceof HostSlaReport;
98181
foreach ($this->yieldTimerange($timerange, $interval, $boundary) as list($start, $end)) {
182+
$reports = [];
99183
foreach ($this->fetchSla(new Timerange($start, $end), $filter) as $row) {
100-
$row = $this->createReportRow($row);
101-
102-
if ($row === null) {
184+
$key = $isHostQuery ? $row->display_name : $row->host_display_name . '!' . $row->display_name;
185+
if (
186+
$row->event_type === 'end'
187+
&& (
188+
! isset($reports[$key])
189+
&& ! $rd->hasTimeline($key)
190+
)
191+
) {
192+
// No data available
103193
continue;
104194
}
105195

106-
$dimensions = $row->getDimensions();
196+
if (isset($reports[$key])) {
197+
$timeline = $reports[$key];
198+
} else {
199+
$timeline = new SlaTimeline($start, $end);
200+
$serviceId = null;
201+
if (! $this instanceof HostSlaReport) {
202+
$serviceId = $row->service_id;
203+
}
204+
205+
$timeline->setInitialHardState(
206+
$this->fetchInitialHardState($start, $row->host_id, $serviceId)
207+
);
208+
}
209+
210+
$timeline
211+
->addEvent($row->event_type)
212+
->addTime((int) $row->event_time)
213+
->addState($row->hard_state)
214+
->addPreviousState($row->previous_hard_state);
215+
216+
$reports[$key] = $timeline;
217+
}
218+
219+
foreach ($reports as $name => $timeline) {
220+
$rd->addTimeline($name, $timeline);
221+
$row = (object) [];
222+
$row->sla = $timeline->getResult();
223+
$row->display_name = $name;
224+
225+
if (strpos($name, '!') !== false) {
226+
list($host, $service) = Str::trimSplit($name, '!');
227+
$row->display_name = $service;
228+
$row->host_display_name = $host;
229+
}
230+
231+
$report = $this->createReportRow($row);
232+
$dimensions = $report->getDimensions();
107233
$dimensions[] = $start->format($format);
108-
$row->setDimensions($dimensions);
234+
$report->setDimensions($dimensions);
109235

110-
$rows[] = $row;
236+
$rows[] = $report;
111237
}
112238
}
113239
} else {
114240
foreach ($this->fetchSla($timerange, $filter) as $row) {
115-
$rows[] = $this->createReportRow($row);
241+
if ($rd->hasTimeline($row->display_name)) {
242+
$timeline = $rd->getTimeline($row->display_name)[0];
243+
} else {
244+
$timeline = new SlaTimeline($timerange->getStart(), $timerange->getEnd());
245+
$serviceId = null;
246+
if (! $this instanceof HostSlaReport) {
247+
$serviceId = $row->service_id;
248+
}
249+
250+
$timeline->setInitialHardState(
251+
$this->fetchInitialHardState($timerange->getStart(), $row->host_id, $serviceId)
252+
);
253+
}
254+
255+
$timeline
256+
->addEvent($row->event_type)
257+
->addTime((int) $row->event_time)
258+
->addState($row->hard_state)
259+
->addPreviousState($row->previous_hard_state);
260+
261+
$rd->setTimeline($row->display_name, $timeline);
262+
263+
if ($row->event_type === 'end') {
264+
$row->sla = $timeline->getResult();
265+
$rows[] = $this->createReportRow($row);
266+
}
116267
}
117268
}
118269

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

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

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

242393
// We only have one average
243-
$average = $data->getAverages()[0];
394+
if (strpos($this->getName(), 'Icinga DB') !== false) {
395+
$average = $data->getIcingaDBAvg();
396+
} else {
397+
$average = $data->getAverages()[0];
398+
}
244399

245400
if ($average < $threshold) {
246401
$slaClass = 'nok';
@@ -274,6 +429,74 @@ public function getHtml(Timerange $timerange, array $config = null)
274429
]
275430
);
276431

432+
// echo '<pre>' . nl2br($data->getTimelineString()) . '</pre>';
277433
return $table;
278434
}
435+
436+
/**
437+
* Get the initial hard state of the given host/service object
438+
*
439+
* @param DateTime $start The start time of the generated sla
440+
* @param string $hostId Host binary/hex id to fetch the initial hard state for
441+
*
442+
* @return int
443+
*/
444+
protected function fetchInitialHardState(DateTime $start, string $hostId, string $serviceId = null): int
445+
{
446+
// Use OK/UP as initial hard state, when neither of the following queries could determine a correct state
447+
$initialHardState = 0;
448+
$start = $start->format('U');
449+
450+
$serviceFilter = $serviceId === null
451+
? Filter::unlike('service_id', '*')
452+
: Filter::equal('service_id', $serviceId);
453+
// Use the latest event at or before the beginning of the SLA interval as the initial state.
454+
$hardState = SlaHistoryState::on($this->getDb())
455+
->columns(['hard_state'])
456+
->filter(
457+
Filter::all(
458+
Filter::equal('host_id', $hostId),
459+
$serviceFilter,
460+
Filter::lessThanOrEqual('event_time', $start)
461+
)
462+
)
463+
->resetOrderBy()
464+
->orderBy('event_time', 'DESC')
465+
->limit(1);
466+
467+
$hardState = $hardState->first();
468+
469+
// If this doesn't exit, use the previous state from the first event after the beginning of the SLA interval.
470+
if (! $hardState) {
471+
$hardState = SlaHistoryState::on($this->getDb())
472+
->columns(['hard_state' => 'previous_hard_state'])
473+
->filter(
474+
Filter::all(
475+
Filter::equal('host_id', $hostId),
476+
$serviceFilter,
477+
Filter::greaterThan('event_time', $start)
478+
)
479+
);
480+
481+
$hardState = $hardState->first();
482+
483+
// If this also doesn't exist, use the current host/service state.
484+
if (! $hardState) {
485+
if ($serviceId !== null) {
486+
$hardState = ServiceState::on($this->getDb())
487+
->filter(Filter::equal('service_id', $serviceId));
488+
} else {
489+
$hardState = HostState::on($this->getDb());
490+
}
491+
492+
$hardState
493+
->columns(['hard_state'])
494+
->filter(Filter::equal('host_id', $hostId));
495+
496+
$hardState = $hardState->first();
497+
}
498+
}
499+
500+
return $hardState === null ? $initialHardState : (int) $hardState->hard_state;
501+
}
279502
}

0 commit comments

Comments
 (0)