Avail feature updated
[ninja.git] / modules / reports / models / summary_reports.php
blobd36be9454c7fb0f90e63f1431c612469b10073c1
1 <?php
2 /**
3 * Big, fat TODO: Almost every method asks the DB for all data and returns it.
4 * Instead, users should subscribe what they're interested in, and be fed that
5 * data once the query runs, once.
6 */
7 class Summary_Reports_Model extends Reports_Model
9 # alert summary options
10 private $summary_result = array();
12 /**
13 * Used from the HTTP API
15 * @param $auth Op5Auth
16 * @return array
18 function get_events($auth)
20 $querym = new Report_query_builder_Model($this->db_table, $this->options);
21 $query = $querym->build_alert_summary_query
22 ('timestamp, event_type, host_name, service_description, ' .
23 'state, hard, retry, downtime_depth, output');
25 // investigate if there are more rows available for this query,
26 // with another set of pagination parameters
27 $limit = $this->options['limit'] + 1;
28 $offset = $this->options['offset'];
30 if($this->options['include_comments']) {
31 $query = "
32 SELECT
33 data.timestamp,
34 data.event_type,
35 data.host_name,
36 data.service_description,
37 data.state,
38 data.hard,
39 data.retry,
40 data.downtime_depth,
41 data.output,
42 comments.username,
43 comments.user_comment,
44 comments.comment_timestamp
45 FROM ($query) data
46 LEFT JOIN
47 ninja_report_comments comments
48 ON data.timestamp = comments.timestamp
49 AND data.host_name = comments.host_name
50 AND data.service_description = comments.service_description
51 AND data.event_type = comments.event_type";
53 $query .= " LIMIT ".$limit." OFFSET ". $offset;
55 $events = $this->db->query($query)->result(false);
56 $can_paginate = false;
57 if(count($events) > $this->options['limit']) {
58 $can_paginate = true;
61 return array(
62 'can_paginate' => $can_paginate,
63 'events' => $events, // note that this is the size you asked for, plus one
64 'limit' => (int) $this->options['limit'],
65 'offset' => (int) $this->options['offset']
69 private function comparable_state($row)
71 return $row['state'] << 1 | $row['hard'];
74 /**
75 * Get alert summary for "top (hard) alert producers"
77 * @return Array in the form { rank => array() }
79 public function top_alert_producers()
81 $start = microtime(true);
82 $host_states = $this->options['host_states'];
83 $service_states = $this->options['service_states'];
84 $this->options['host_states'] = self::HOST_ALL;
85 $this->options['service_states'] = self::SERVICE_ALL;
86 $querym = new Report_query_builder_Model($this->db_table, $this->options);
87 $query = $querym->build_alert_summary_query();
88 $this->options['host_states'] = $host_states;
89 $this->options['service_states'] = $service_states;
91 $dbr = $this->db->query($query);
92 if (!is_object($dbr)) {
93 return false;
95 $dbr = $dbr->result(false);
96 $result = array();
97 $pstate = array();
98 foreach ($dbr as $row) {
99 if (empty($row['service_description'])) {
100 $name = $row['host_name'];
101 $interesting_states = $host_states;
102 } else {
103 $name = $row['host_name'] . ';' . $row['service_description'];
104 $interesting_states = $service_states;
107 # only count true state-changes
108 $state = $this->comparable_state($row);
109 if (isset($pstate[$name]) && $pstate[$name] === $state) {
110 continue;
112 $pstate[$name] = $state;
114 # if we're not interested in this state, just move along
115 if (!(1 << $row['state'] & $interesting_states)) {
116 continue;
119 if (empty($result[$name])) {
120 $result[$name] = 1;
121 } else {
122 $result[$name]++;
126 # sort the result and return only the necessary items
127 arsort($result);
128 if ($this->options['summary_items'] > 0) {
129 $result = array_slice($result, 0, $this->options['summary_items'], true);
132 $i = 1;
133 $this->summary_result = array();
134 foreach ($result as $obj => $alerts) {
135 $ary = array();
136 if (strstr($obj, ';')) {
137 $obj_ary = explode(';', $obj);
138 $ary['host_name'] = $obj_ary[0];
139 $ary['service_description'] = $obj_ary[1];
140 $ary['event_type'] = self::SERVICECHECK;
141 } else {
142 $ary['host_name'] = $obj;
143 $ary['event_type'] = self::HOSTCHECK;
145 $ary['total_alerts'] = $alerts;
146 $this->summary_result[$i++] = $ary;
148 return $this->summary_result;
151 private function set_alert_total_totals(&$result)
153 foreach ($result as $name => $ary) {
154 $ary['total'] = 0;
155 foreach ($ary as $type => $state_ary) {
156 if ($type === 'total')
157 continue;
158 $ary[$type . '_totals'] = array('soft' => 0, 'hard' => 0);
159 $ary[$type . '_total'] = 0;
160 foreach ($state_ary as $sh) {
161 $ary[$type . '_totals']['soft'] += $sh[0];
162 $ary[$type . '_totals']['hard'] += $sh[1];
163 $ary[$type . '_total'] += $sh[0] + $sh[1];
164 $ary['total'] += $sh[0] + $sh[1];
167 $result[$name] = $ary;
171 private function alert_totals_by_host($dbr)
173 $template = $this->summary_result;
174 $result = array();
175 foreach ($this->options['objects'] as $hn) {
176 $result[$hn] = $template;
178 $pstate = array();
179 foreach ($dbr as $row) {
180 if (empty($row['service_description'])) {
181 $type = 'host';
182 $sname = $row['host_name'];
183 } else {
184 $type = 'service';
185 $sname = $row['host_name'] . ';' . $row['service_description'];
188 # only count real state-changes
189 $state = $this->comparable_state($row);
190 if (isset($pstate[$sname]) && $pstate[$sname] === $state) {
191 continue;
193 $pstate[$sname] = $state;
195 $name = $row['host_name'];
196 $result[$name][$type][$row['state']][$row['hard']]++;
199 return $result;
202 private function alert_totals_by_service($dbr)
204 $template = $this->summary_result;
205 $result = array();
206 foreach ($this->options['objects'] as $name) {
207 list($host, $svc) = explode(';', $name);
208 # Assign host first, so it's position in the array is before services
209 $result[$host] = $template;
210 $result[$name] = $template;
212 $pstate = array();
213 foreach ($dbr as $row) {
214 if (!$row['service_description']) {
215 $name = $row['host_name'];
216 $type = 'host';
218 else {
219 $name = $row['host_name'] . ';' . $row['service_description'];
220 $type = 'service';
222 $state = $this->comparable_state($row);
223 if (isset($pstate[$name]) && $pstate[$name] === $state) {
224 continue;
226 $pstate[$name] = $state;
227 $result[$name][$type][$row['state']][$row['hard']]++;
230 return $result;
234 private function alert_totals_by_hostgroup($dbr, $host_hostgroup)
236 # pre-load the result set to keep conditionals away
237 # from the inner loop
238 $template = $this->summary_result;
239 $result = array();
240 foreach ($this->options['objects'] as $hostgroup) {
241 $result[$hostgroup] = $template;
244 $pstate = array();
245 foreach ($dbr as $row) {
246 if (empty($row['service_description'])) {
247 $type = 'host';
248 $name = $row['host_name'];
249 } else {
250 $type = 'service';
251 $name = $row['host_name'] . ';' . $row['service_description'];
253 $state = $this->comparable_state($row);
254 if (isset($pstate[$name]) && $pstate[$name] === $state) {
255 continue;
257 $pstate[$name] = $state;
258 $hostgroups = $host_hostgroup[$row['host_name']];
259 foreach ($hostgroups as $hostgroup) {
260 $result[$hostgroup][$type][$row['state']][$row['hard']]++;
263 return $result;
267 private function alert_totals_by_servicegroup($dbr, $service_servicegroup)
269 # pre-load the result set to keep conditionals away
270 # from the inner loop
271 $template = $this->summary_result;
272 $result = array();
273 foreach ($this->options['objects'] as $servicegroup) {
274 $result[$servicegroup] = $template;
277 $pstate = array();
278 foreach ($dbr as $row) {
279 if (empty($row['service_description'])) {
280 $type = 'host';
281 $name = $row['host_name'];
282 } else {
283 $type = 'service';
284 $name = $row['host_name'] . ';' . $row['service_description'];
286 $state = $this->comparable_state($row);
287 if (isset($pstate[$name]) && $pstate[$name] === $state) {
288 continue;
290 $pstate[$name] = $state;
292 $servicegroups = $service_servicegroup[$type][$name];
293 foreach ($servicegroups as $sg) {
294 $result[$sg][$type][$row['state']][$row['hard']]++;
297 return $result;
301 * Get alert totals. This is identical to the toplist in
302 * many respects, but the result array is different.
304 * @return Array of counts divided by object types and states
306 public function alert_totals()
308 $querym = new Report_query_builder_Model($this->db_table, $this->options);
309 $query = $querym->build_alert_summary_query();
311 $dbr = $this->db->query($query)->result(false);
312 if (!is_object($dbr)) {
313 echo Kohana::debug($this->db->errorinfo(), explode("\n", $query));
316 # preparing the result array in advance speeds up the
317 # parsing somewhat. Completing it either way makes it
318 # easier to write templates for it as well.
319 # We stash it in $this->summary_result so all functions
320 # can take advantage of it
321 for ($state = 0; $state < 4; $state++) {
322 $this->summary_result['host'][$state] = array(0, 0);
323 $this->summary_result['service'][$state] = array(0, 0);
325 unset($this->summary_result['host'][3]);
327 $result = false;
328 # groups must be first here, since the other variables
329 # are expanded in the build_alert_summary_query() method
330 switch ($this->options['report_type']) {
331 case 'servicegroups':
332 $result = $this->alert_totals_by_servicegroup($dbr, $querym->service_servicegroup);
333 break;
334 case 'hostgroups':
335 $result = $this->alert_totals_by_hostgroup($dbr, $querym->host_hostgroup);
336 break;
337 case 'services':
338 $result = $this->alert_totals_by_service($dbr);
339 break;
340 case 'hosts':
341 $result = $this->alert_totals_by_host($dbr);
342 break;
345 $this->set_alert_total_totals($result);
346 $this->summary_result = $result;
347 return $this->summary_result;
351 * Find and return the latest $this->options['summary_items'] alert
352 * producers according to the search criteria.
354 public function recent_alerts()
356 $querym = new Report_query_builder_Model($this->db_table, $this->options);
357 $query = $querym->build_alert_summary_query('*');
359 $query .= ' ORDER BY timestamp '.(isset($this->options['oldest_first']) && $this->options['oldest_first']?'ASC':'DESC');
360 if ($this->options['summary_items'] > 0) {
361 $query .= " LIMIT " . $this->options['summary_items'];
362 if (isset($this->options['page']) && $this->options['page'])
363 $query .= ' OFFSET ' . ($this->options['summary_items'] * ($this->options['page'] - 1));
366 $query = '
367 SELECT
368 data.*,
369 comments.username,
370 comments.user_comment
371 FROM ('.$query.') data
372 LEFT JOIN
373 ninja_report_comments comments
374 ON data.timestamp = comments.timestamp
375 AND data.host_name = comments.host_name
376 AND data.service_description = comments.service_description
377 AND data.event_type = comments.event_type';
379 $dbr = $this->db->query($query)->result(false);
380 if (!is_object($dbr)) {
381 echo Kohana::debug($this->db->errorinfo(), explode("\n", $query));
384 $this->summary_result = array();
385 foreach ($dbr as $row) {
386 if ($this->timeperiod->inside($row['timestamp']))
387 $this->summary_result[] = $row;
390 return $this->summary_result;
394 * Add a new comment to the event pointed to by the timestamp/event_type/host_name/service
396 public static function add_event_comment($timestamp, $event_type, $host_name, $service, $comment, $username) {
397 $db = Database::instance();
398 $db->query('DELETE FROM ninja_report_comments WHERE timestamp='.$db->escape($timestamp).' AND event_type = '.$db->escape($event_type).' AND host_name = '.$db->escape($host_name).' AND service_description = '.$db->escape($service));
399 $db->query('INSERT INTO ninja_report_comments(timestamp, event_type, host_name, service_description, comment_timestamp, username, user_comment) VALUES ('.$db->escape($timestamp).', '.$db->escape($event_type).', '.$db->escape($host_name).', '.$db->escape($service).', UNIX_TIMESTAMP(), '.$db->escape($username).', '.$db->escape($comment).')');
400 return true;
403 * Fetch alert history for histogram report
404 * @param $slots array with slots to fill with data
405 * @return array with keys: min, max, avg, data
407 public function histogram($slots=false)
409 if (empty($slots) || !is_array($slots))
410 return array();
412 $breakdown = $this->options['breakdown'];
413 $report_type = $this->options['report_type'];
414 $newstatesonly = $this->options['newstatesonly'];
416 # compute what event counters we need depending on report type
417 $events = false;
418 switch ($report_type) {
419 case 'hosts': case 'hostgroups':
420 if (!$this->options['host_states'] || $this->options['host_states'] == self::HOST_ALL) {
421 $events = array(0 => 0, 1 => 0, 2 => 0);
422 } else {
423 $events = array();
424 for ($i = 0; $i <= 2; $i++) {
425 if (1 << $i & $this->options['host_states']) {
426 $events[$i] = 0;
430 $this->options['alert_types'] = 1;
431 break;
432 case 'services': case 'servicegroups':
433 if (!$this->options['service_states'] || $this->options['service_states'] == self::SERVICE_ALL) {
434 $events = array(0 => 0, 1 => 0, 2 => 0, 3 => 0);
435 } else {
436 $events = array();
437 for ($i = 0; $i <= 3; $i++) {
438 if (1 << $i & $this->options['service_states']) {
439 $events[$i] = 0;
443 $this->options['alert_types'] = 2;
444 break;
447 # add event (state) counters to slots
448 $data = false;
449 foreach ($slots as $s => $l) {
450 $data[$l] = $events;
453 # fields to fetch from db
454 $fields = 'timestamp, event_type, host_name, service_description, state, hard, retry';
455 $querym = new Report_query_builder_Model($this->db_table, $this->options);
456 $query = $querym->build_alert_summary_query($fields);
458 # tell histogram_data() how to treat timestamp
459 $date_str = false;
460 switch ($breakdown) {
461 case 'monthly':
462 $date_str = 'n';
463 break;
464 case 'dayofmonth':
465 $date_str = 'j';
466 break;
467 case 'dayofweek':
468 $date_str = 'N';
469 break;
470 case 'hourly':
471 $date_str = 'H';
472 break;
475 $res = $this->db->query($query)->result(false);
476 if (!$res) {
477 return array();
479 $last_state = null;
480 foreach ($res as $row) {
481 if ($newstatesonly) {
482 if ($row['state'] != $last_state) {
483 # only count this state if it differs from the last
484 $data[date($date_str, $row['timestamp'])][$row['state']]++;
486 } else {
487 $data[date($date_str, $row['timestamp'])][$row['state']]++;
489 $last_state = $row['state'];
492 $min = $events;
493 $max = $events;
494 $avg = $events;
495 $sum = $events;
496 if (empty($data))
497 return array();
499 foreach ($data as $slot => $slotstates) {
500 foreach ($slotstates as $id => $val) {
501 if ($val > $max[$id]) $max[$id] = $val;
502 if ($val < $min[$id]) $min[$id] = $val;
503 $sum[$id] += $val;
506 foreach ($max as $v => $k) {
507 if ($k != 0) {
508 $avg[$v] = number_format(($k/count($data)), 2);
511 return array('min' => $min, 'max' => $max, 'avg' => $avg, 'sum' => $sum, 'data' => $data);