Merge "Make it possible to sort on simple custom columns"
[ninja.git] / modules / reports / models / status_reports.php
blobd82ec14b18ad7a1f4ca0efc6127e02c4a48625cb
1 <?php
2 /**
3 * Reports model
4 * Responsible for fetching data for avail and SLA reports. This class
5 * must be instantiated to work properly.
7 * ## State interaction in subreports
8 * Given two objects, assuming only two states per object type, would interact
9 * such that the non-OK state overrules the OK state completely as such:
10 * host
11 * UP | DOWN
12 * | scheduled | unscheduled | scheduled | unscheduled
13 * ------------++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 * scheduled + sched up | unsched up | sched down | unsched down
15 * UP ------------+------------+-------------+------------+-------------
16 * unscheduled+ unsched up | unsched up | sched down | unsched down
17 * host ----------------+------------+-------------+------------+-------------
18 * scheduled + sched down | sched down | sched down | unsched down
19 * DOWN------------+------------+-------------+------------+-------------
20 * unscheduled+unsched down| unsched down|unsched down| unsched down
22 * When two sub-objects have different non-OK states, the outcome depends on
23 * whether scheduleddowntimeasuptime is used or not. If the option is used,
24 * then the service with the worst non-scheduled state is used. If the option
25 * is not used, the worst state is used, prioritizing any non-scheduled state.
27 * This applies to non-"cluster mode" reports. If you're in cluster mode, this
28 * applies backwards exactly.
30 class Status_Reports_Model extends Reports_Model
32 protected $st_is_service = false; /**< Whether the objects in this report are services */
33 protected $st_source = false; /**< Array of (non-group) objects that are part of this report */
34 protected $calculator = false; /**< The top-level calculator that represents this report */
36 /**
37 * Constructor
38 * @param $options An instance of Report_options
39 * @param $db_table Database name
41 public function __construct(Report_options $options, $db_table='report_data')
43 $this->db_table = $db_table;
44 parent::__construct($options);
47 /**
48 * Get log details for host/service
50 * @return PDO result object on success. FALSE on error.
52 public function uptime_query()
54 $event_type = Reports_Model::HOSTCHECK;
55 if ($this->st_is_service) {
56 $event_type = Reports_Model::SERVICECHECK;
59 # this query works out automatically, as we definitely don't
60 # want to get all state entries for a hosts services when
61 # we're only asking for uptime of the host
62 $sql = "SELECT host_name, service_description, " .
63 "state,timestamp AS the_time, hard, event_type";
64 # output is a TEXT field, so it needs an extra disk
65 # lookup to fetch and we don't always need it
66 if ($this->options['include_trends'])
67 $sql .= ", output";
69 $sql .= " FROM ".$this->db_table." ";
71 $time_first = 'timestamp >='.$this->options['start_time'];
72 $time_last = 'timestamp <='.$this->options['end_time'];
73 $process = false;
74 $purehost = false;
75 $objsel = false;
76 $downtime = 'event_type=' . Reports_Model::DOWNTIME_START . ' OR event_type=' . Reports_Model::DOWNTIME_STOP;
77 $softorhardcheck = 'event_type=' . $event_type;
79 if (!$this->options['assumestatesduringnotrunning'])
80 $process = 'event_type < 200';
82 if (!$this->options['includesoftstates']) {
83 $softorhardcheck .= ' AND hard=1';
86 if ($this->st_is_service) {
87 $hostname = array();
88 $servicename = array();
89 foreach ($this->st_source as $hst_srv) {
90 $ary = explode(';', $hst_srv, 2);
91 $hostname[] = $this->db->escape($ary[0]);
92 $servicename[] = $this->db->escape($ary[1]);
94 $purehost = "host_name IN (".join(", ", $hostname) . ") AND (service_description = '' OR service_description IS NULL)";
96 if (count($hostname) == 1) {
97 $hostname = array_pop($hostname);
98 $objsel = "host_name = $hostname AND service_description IN (".join(", ", $servicename) . ")";
99 } else {
100 foreach ($hostname as $i => $host) {
101 $svc = $servicename[$i];
102 $objsel[] = "host_name = $host AND service_description = $svc";
104 $objsel = '('.implode(') OR (', $objsel).')';
107 $sql_where = sql::combine('and',
108 $time_first,
109 $time_last,
110 sql::combine('or',
111 $process,
112 sql::combine('or',
113 sql::combine('and',
114 $purehost,
115 $downtime),
116 sql::combine('and',
117 $objsel,
118 sql::combine('or',
119 $downtime,
120 $softorhardcheck)))));
121 } else {
122 $objsel = "host_name IN ('" . join("', '", $this->st_source) . "') AND (service_description = '' OR service_description IS NULL)";
124 $sql_where = sql::combine('and',
125 $time_first,
126 $time_last,
127 sql::combine('or',
128 $process,
129 sql::combine('and',
130 $objsel,
131 sql::combine('or',
132 $downtime,
133 $softorhardcheck))));
136 $sql .= 'WHERE ' .$sql_where . ' ORDER BY timestamp';
138 return $this->db->query($sql)->result(false);
142 * Calculate uptime between two timestamps for host/service
143 * @return array or false on error
146 public function get_uptime()
148 if (!$this->options['host_name'] && !$this->options['hostgroup'] && !$this->options['service_description'] && !$this->options['servicegroup']) {
149 return false;
152 $is_running = !$this->get_last_shutdown();
154 switch ($this->options['report_type']) {
155 case 'services':
156 case 'servicegroups':
157 $this->st_is_service = true;
158 break;
160 $objects = $this->options->get_report_members();
161 $this->st_source = $objects;
163 $calculator_type = false;
164 switch ((int)$this->options['sla_mode']) {
165 case 0:
166 $calculator_type = 'WorstStateCalculator';
167 break;
168 case 1:
169 $calculator_type = 'AverageStateCalculator';
170 break;
171 case 2:
172 $calculator_type = 'BestStateCalculator';
173 break;
174 default:
175 die("Don't know how to do anything with this\n");
176 break;
179 $this->calculator = new $calculator_type($this->options, $this->timeperiod);
180 $optclass = get_class($this->options);
182 $subs = array();
184 $initial_states = $this->get_initial_states($this->st_is_service ? 'service' : 'host', $objects);
185 $downtimes = $this->get_initial_dt_depths($this->st_is_service ? 'service' : 'host', $objects);
186 foreach ($objects as $object) {
187 $opts = new $optclass($this->options);
188 $opts[$this->st_is_service ? 'service_description' : 'host_name'] = array($object);
189 $sub = new SingleStateCalculator($opts, $this->timeperiod);
190 if (isset( $initial_states[$object]))
191 $initial_state = $initial_states[$object];
192 else
193 $initial_state = Reports_Model::STATE_PENDING;
195 if (isset( $downtimes[$object]))
196 $initial_depth = $downtimes[$object];
197 else
198 $initial_depth = 0;
200 if (!$initial_depth && $this->st_is_service) { /* Is host scheduled? */
201 $srv = explode(';', $object);
202 if (isset($downtimes[$srv[0].';']) && $downtimes[$srv[0].';'])
203 $initial_depth = 1;
205 $sub->initialize($initial_state, $initial_depth, $is_running);
206 $subs[$object] = $sub;
209 switch ($this->options['report_type']) {
210 case 'servicegroups':
211 case 'hostgroups':
212 $groups = $this->options[$this->options->get_value('report_type')];
213 $all_subs = $subs;
214 $subs = array();
215 foreach ($groups as $group) {
216 $opts = new $optclass($this->options);
217 $opts[$this->options->get_value('report_type')] = array($group);
218 $members = $opts->get_report_members();
219 $these_subs = array();
220 foreach ($members as $member)
221 $these_subs[$member] = $all_subs[$member];
222 $this_sub = new $calculator_type($opts, $this->timeperiod);
223 $this_sub->set_sub_reports($these_subs);
224 $this_sub->initialize(Reports_Model::STATE_PENDING, Reports_Model::STATE_PENDING, $is_running);
225 $subs[$group] = $this_sub;
227 break;
228 case 'hosts':
229 case 'services':
230 $this_sub = new $calculator_type($this->options, $this->timeperiod);
231 $this_sub->set_sub_reports($subs);
232 $this_sub->initialize(Reports_Model::STATE_PENDING, Reports_Model::STATE_PENDING, $is_running);
233 $subs = array($this_sub);
234 break;
237 $this->calculator->set_sub_reports($subs);
238 $this->calculator->initialize(Reports_Model::STATE_PENDING, Reports_Model::STATE_PENDING, $is_running);
240 $this->st_parse_all_rows();
241 $this->calculator->finalize();
242 return $this->calculator->get_data();
246 * Get latest (useful) process shutdown event
248 * @return Timestamp when of last shutdown event prior to $start_time
250 public function get_last_shutdown()
252 # If we're assuming states during program downtime,
253 # we don't really need to know when the last shutdown
254 # took place, as the initial state will be used regardless
255 # of whether or not Monitor was up and running.
256 if ($this->options['assumestatesduringnotrunning']) {
257 return 0;
260 $query = "SELECT timestamp, event_type FROM ".
261 $this->db_table.
262 " WHERE timestamp <".$this->options['start_time'].
263 " ORDER BY timestamp DESC LIMIT 1";
264 $dbr = $this->db->query($query)->result(false);
266 if (!$dbr || !($row = $dbr->current()))
267 return false;
269 $event_type = $row['event_type'];
270 if ($event_type==Reports_Model::PROCESS_SHUTDOWN || $event_type==Reports_Model::PROCESS_RESTART)
271 $last_shutdown = $row['timestamp'];
272 else
273 $last_shutdown = 0;
275 return $last_shutdown;
280 * Runs the main query and loops through the results one by one
282 private function st_parse_all_rows()
284 $dbr = $this->uptime_query();
285 foreach ($dbr as $row) {
286 $this->calculator->add_event($row);
291 * Fetch information about SCHEDULED_DOWNTIME status for multiple objects
293 * @return array of Depth of initial downtime.
295 protected function get_initial_dt_depths( $type = 'host', $names = array() )
297 $objectmatches = array();
298 if( $type == 'service' ) {
299 foreach( $names as $name ) {
300 list( $host, $srv ) = explode( ';', $name, 2 );
301 $objectmatches[] = '(host_name = '
302 . $this->db->escape($host)
303 . ' AND (service_description = "" OR service_description IS NULL'
304 . ' OR service_description = '
305 . $this->db->escape($srv)
306 . '))';
308 } else {
309 foreach( $names as $name ) {
310 $objectmatches[] = '(host_name = '
311 . $this->db->escape($name)
312 . ' AND (service_description = "" OR service_description IS NULL))';
316 $sql = "SELECT DISTINCT lsc.host_name as host_name, lsc.service_description as service_description, rd.event_type as event_type FROM (";
317 $sql .= "SELECT host_name, service_description, max( timestamp ) as timestamp FROM ".$this->db_table;
318 $sql .= " WHERE (".implode(' OR ',$objectmatches).")";
319 $sql .= " AND (event_type = ".Reports_Model::DOWNTIME_START." OR event_type = ".Reports_Model::DOWNTIME_STOP.")";
320 $sql .= " AND timestamp < ".$this->options['start_time'];
321 $sql .= " GROUP BY host_name,service_description";
322 $sql .= ") AS lsc";
323 $sql .= " LEFT JOIN ".$this->db_table." AS rd";
324 $sql .= " ON lsc.host_name = rd.host_name";
325 $sql .= " AND lsc.service_description = rd.service_description";
326 $sql .= " AND lsc.timestamp = rd.timestamp";
327 $sql .= " AND (event_type = ".Reports_Model::DOWNTIME_START." OR event_type = ".Reports_Model::DOWNTIME_STOP.")";
329 $dbr = $this->db->query($sql)->result(false);
331 $downtimes = array();
332 foreach( $dbr as $staterow ) {
333 $in_downtime = (int)($staterow['event_type'] == Reports_Model::DOWNTIME_START);
334 if ( $type == 'service' ) {
335 $downtimes[ $staterow['host_name'] . ';' . $staterow['service_description'] ] = $in_downtime;
336 } else {
337 $downtimes[ $staterow['host_name'] ] = $in_downtime;
341 return $downtimes;
345 * Get inital states of a set of objects
347 * @return array of initial states
349 protected function get_initial_states( $type = 'host', $names = array() )
351 $objectmatches = array();
352 if( $type == 'service' ) {
353 foreach( $names as $name ) {
354 list( $host, $srv ) = explode( ';', $name, 2 );
355 $objectmatches[] = '(host_name = '
356 . $this->db->escape($host)
357 . ' AND service_description = '
358 . $this->db->escape($srv)
359 . ')';
361 } else {
362 foreach( $names as $name ) {
363 $objectmatches[] = '(host_name = '
364 . $this->db->escape($name)
365 . ' AND (service_description = "" OR service_description IS NULL))';
369 $sql = "SELECT DISTINCT lsc.host_name as host_name, lsc.service_description as service_description, rd.state as state FROM (";
370 $sql .= "SELECT host_name, service_description, max( timestamp ) as timestamp FROM ".$this->db_table;
371 $sql .= " WHERE (".implode(' OR ',$objectmatches).")";
372 if ( $type == 'service' ) {
373 $sql .= " AND event_type = ".Reports_Model::SERVICECHECK;
374 } else {
375 $sql .= " AND event_type = ".Reports_Model::HOSTCHECK;
377 if (!$this->options['includesoftstates'])
378 $sql .= " AND hard = 1";
379 $sql .= " AND timestamp < ".$this->options['start_time'];
380 $sql .= " GROUP BY host_name,service_description";
381 $sql .= ") AS lsc";
382 $sql .= " LEFT JOIN ".$this->db_table." AS rd";
383 $sql .= " ON lsc.host_name = rd.host_name";
384 $sql .= " AND lsc.service_description = rd.service_description";
385 $sql .= " AND lsc.timestamp = rd.timestamp";
386 if ( $type == 'service' ) {
387 $sql .= " AND event_type = ".Reports_Model::SERVICECHECK;
388 } else {
389 $sql .= " AND event_type = ".Reports_Model::HOSTCHECK;
392 $dbr = $this->db->query($sql)->result(false);
394 $states = array();
395 if ( $type == 'service' ) {
396 foreach( $dbr as $staterow ) {
397 $states[ $staterow['host_name'] . ';' . $staterow['service_description'] ] = $staterow['state'];
399 } else {
400 foreach( $dbr as $staterow ) {
401 $states[ $staterow['host_name'] ] = $staterow['state'];
405 return $states;