--SELECT is(idoreports_get_sla_ok_percent(4,'2020-03-01 12:00', '2020-03-01 16:00')::float , 50.0::float,'Host 4 was considered down for 2 hours in a 4 hours time range starting with UP'); --SELECT is(idoreports_get_sla_ok_percent(5,'2020-04-01 12:00', '2020-04-01 16:00')::float , 50.0::float,'Host 5 was considered down for 2 hours in a 4 hours time range starting with DOWN'); \set id 5 \set start '2020-04-01 12:00' \set end '2020-04-01 16:00' \set sla_id null --'2019-02-19 00:00:00','2019-02-20 10:00:00' --12347 WITH crit AS ( SELECT CASE objecttype_id WHEN 1 THEN 0 WHEN 2 THEN 1 END AS value FROM icinga_objects WHERE object_id = :id ), before AS ( -- low border, last event before the range we are looking for: SELECT down, state_time_ AS state_time,state FROM ( (SELECT 1 AS prio ,state > crit.value AS down ,GREATEST(state_time,:'start') AS state_time_ ,state FROM icinga_statehistory,crit WHERE object_id = :id AND state_time < :'start' AND state_type = 1 ORDER BY state_time DESC LIMIT 1) UNION ALL (SELECT 2 AS prio ,state > crit.value AS down ,GREATEST(state_time,:'start') AS state_time_ ,state FROM icinga_statehistory,crit WHERE object_id = :id AND state_time < :'start' ORDER BY state_time DESC LIMIT 1) ) ranked ORDER BY prio LIMIT 1 ) SELECT * FROM before; ,all_hard_events AS ( -- the actual range we're looking for: SELECT state > crit.value AS down ,state_time ,state FROM icinga_statehistory,crit WHERE object_id = :id AND state_time >= :'start' AND state_time <= :'end' AND state_type = 1 ), after AS ( -- the "younger" of the current host/service state and the first recorded event (SELECT state > crit_value AS down ,LEAST(state_time,:'end') AS state_time ,state FROM ( (SELECT state_time ,state ,crit.value crit_value FROM icinga_statehistory,crit WHERE object_id = :id AND state_time > :'end' AND state_type = 1 ORDER BY state_time ASC LIMIT 1) UNION ALL SELECT status_update_time ,current_state ,crit.value crit_value FROM icinga_hoststatus,crit WHERE host_object_id = :id AND state_type = 1 UNION ALL SELECT status_update_time ,current_state ,crit.value crit_value FROM icinga_servicestatus,crit WHERE service_object_id = :id AND state_type = 1 ) AS after_searched_period ORDER BY state_time ASC LIMIT 1) ) , allevents AS ( TABLE before UNION ALL TABLE all_hard_events UNION ALL TABLE after ) --SELECT * FROM allevents; , downtimes AS ( SELECT tsrange( --GREATEST(actual_start_time, :'start') --, LEAST(actual_end_time, :'end') actual_start_time , actual_end_time ) AS downtime FROM icinga_downtimehistory WHERE object_id = :id -- AND actual_start_time <= :'end' -- AND COALESCE(actual_end_time,:'start') >= :'start' UNION ALL SELECT tsrange( --GREATEST(start_time, :'start') --, LEAST(end_time, :'end') start_time , end_time ) AS downtime FROM icinga_outofsla_periods WHERE timeperiod_object_id = :sla_id ) --SELECT * FROM allevents; , enriched AS ( SELECT down ,tsrange(state_time, COALESCE(lead(state_time) OVER w, :'end'),'(]') AS timeframe --,lead(state_time) OVER w - state_time AS dauer FROM ( SELECT state > crit.value AS down , lead(state,1,state) OVER w > crit.value AS next_down , lag(state,1,state) OVER w > crit.value AS prev_down , state_time , state FROM allevents,crit WINDOW w AS (ORDER BY state_time) ) alle --WHERE down != next_down OR down != prev_down WINDOW w AS (ORDER BY state_time) ) , relevant AS ( SELECT down ,timeframe * tsrange(:'start',:'end','(]') AS timeframe FROM enriched WHERE timeframe && tsrange(:'start',:'end','(]') ) SELECT * FROM relevant; , relevant_down AS ( SELECT timeframe ,down ,timeframe * downtime AS covered ,COALESCE( timeframe - downtime ,timeframe ) AS not_covered FROM relevant LEFT JOIN downtimes ON timeframe && downtime WHERE down ) -- SELECT * FROM relevant_down; , effective_downtimes AS ( SELECT not_covered , upper(not_covered) - lower(not_covered) AS dauer FROM relevant_down ) --SELECT * FROM effective_downtimes; , final_result AS ( SELECT sum(dauer) AS total_downtime , timestamp :'end' - timestamp :'start' AS considered , COALESCE(extract ('epoch' from sum(dauer)),0) AS down_secs , extract ('epoch' from timestamp :'end' - timestamp :'start' ) AS considered_secs FROM effective_downtimes ) --SELECT * FROM final_result; SELECT :'start' AS starttime, :'end' AS endtime,* , 100.0 - down_secs / considered_secs * 100.0 AS availability FROM final_result ;