From b4df81e75a26c00f6b6830b15815eb20f6fd817a Mon Sep 17 00:00:00 2001
From: Eric Lippmann <eric.lippmann@icinga.com>
Date: Thu, 27 Jul 2017 15:03:12 +0200
Subject: [PATCH] Optimize queries used for fetching the service group
 summaries

---
 .../Backend/Ido/Query/ServicegroupQuery.php   | 109 ++++++-------
 .../Ido/Query/ServicegroupsummaryQuery.php    | 152 +++++-------------
 2 files changed, 90 insertions(+), 171 deletions(-)

diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php
index c0c49ca9a..ad23bc3ca 100644
--- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php
@@ -5,64 +5,44 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
 
 class ServicegroupQuery extends IdoQuery
 {
-    /**
-     * {@inheritdoc}
-     */
+    protected $groupBase = array(
+        'servicegroups' => array('sgo.object_id, sg.servicegroup_id'),
+        'servicestatus' => array('ss.servicestatus_id', 'hs.hoststatus_id')
+    );
+
+    protected $groupOrigin = array('members');
+
     protected $allowCustomVars = true;
 
-    /**
-     * {@inheritdoc}
-     */
-    protected $groupBase = array('servicegroups' => array('sg.servicegroup_id', 'sgo.object_id'));
-
-    /**
-     * {@inheritdoc}
-     */
-    protected $groupOrigin = array('serviceobjects');
-
-    /**
-     * {@inheritdoc}
-     */
     protected $columnMap = array(
         'hostgroups' => array(
-            'hostgroup'             => 'hgo.name1 COLLATE latin1_general_ci',
-            'hostgroup_alias'       => 'hg.alias COLLATE latin1_general_ci',
-            'hostgroup_name'        => 'hgo.name1'
-        ),
-        'hosts' => array(
-            'host_alias'            => 'h.alias',
-            'host_display_name'     => 'h.display_name COLLATE latin1_general_ci',
+            'hostgroup_name' => 'hgo.name1'
         ),
         'instances' => array(
             'instance_name' => 'i.instance_name'
         ),
+        'members' => array(
+            'host_name'             => 'so.name1',
+            'service_description'   => 'so.name2'
+        ),
         'servicegroups' => array(
-            'servicegroup'          => 'sgo.name1 COLLATE latin1_general_ci',
             'servicegroup_alias'    => 'sg.alias COLLATE latin1_general_ci',
             'servicegroup_name'     => 'sgo.name1'
         ),
-        'serviceobjects' => array(
-            'host'                  => 'so.name1 COLLATE latin1_general_ci',
-            'host_name'             => 'so.name1',
-            'service'               => 'so.name2 COLLATE latin1_general_ci',
-            'service_description'   => 'so.name2'
-        ),
-        'services' => array(
-            'service_display_name'  => 's.display_name COLLATE latin1_general_ci',
+        'servicestatus' => array(
+            'service_handled'   => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END',
+            'service_state'     => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END'
         )
     );
 
-    /**
-     * {@inheritdoc}
-     */
     protected function joinBaseTables()
     {
         $this->select->from(
-            array('sg' => $this->prefix . 'servicegroups'),
+            array('sgo' => $this->prefix . 'objects'),
             array()
         )->join(
-            array('sgo' => $this->prefix . 'objects'),
-            'sg.servicegroup_object_id = sgo.object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+            array('sg' => $this->prefix . 'servicegroups'),
+            'sg.servicegroup_object_id = sgo.object_id AND sgo.objecttype_id = 4 AND sgo.is_active = 1',
             array()
         );
         $this->joinedVirtualTables = array('servicegroups' => true);
@@ -74,30 +54,17 @@ class ServicegroupQuery extends IdoQuery
     protected function joinHostgroups()
     {
         $this->requireVirtualTable('services');
-        $this->select->joinLeft(
+        $this->select->join(
             array('hgm' => $this->prefix . 'hostgroup_members'),
             'hgm.host_object_id = s.host_object_id',
             array()
-        )->joinLeft(
+        )->join(
             array('hg' => $this->prefix . 'hostgroups'),
             'hg.hostgroup_id = hgm.hostgroup_id',
             array()
-        )->joinLeft(
+        )->join(
             array('hgo' => $this->prefix . 'objects'),
-            'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
-            array()
-        );
-    }
-
-    /**
-     * Join hosts
-     */
-    protected function joinHosts()
-    {
-        $this->requireVirtualTable('services');
-        $this->select->joinLeft(
-            array('h' => $this->prefix . 'hosts'),
-            'h.host_object_id = s.host_object_id',
+            'hgo.object_id = hg.hostgroup_object_id AND hgo.objecttype_id = 3 AND hgo.is_active = 1',
             array()
         );
     }
@@ -117,15 +84,15 @@ class ServicegroupQuery extends IdoQuery
     /**
      * Join service objects
      */
-    protected function joinServiceobjects()
+    protected function joinMembers()
     {
-        $this->select->joinLeft(
+        $this->select->join(
             array('sgm' => $this->prefix . 'servicegroup_members'),
-            'sgm.' . $this->servicegroup_id . ' = sg.' . $this->servicegroup_id,
+            'sgm.servicegroup_id = sg.servicegroup_id',
             array()
-        )->joinLeft(
+        )->join(
             array('so' => $this->prefix . 'objects'),
-            'sgm.service_object_id = so.object_id',
+            'so.object_id = sgm.service_object_id AND so.objecttype_id = 2 AND so.is_active = 1',
             array()
         );
     }
@@ -135,11 +102,29 @@ class ServicegroupQuery extends IdoQuery
      */
     protected function joinServices()
     {
-        $this->requireVirtualTable('serviceobjects');
-        $this->select->joinLeft(
+        $this->requireVirtualTable('members');
+        $this->select->join(
             array('s' => $this->prefix . 'services'),
             's.service_object_id = so.object_id',
             array()
         );
     }
+
+    /**
+     * Join service status
+     */
+    protected function joinServicestatus()
+    {
+        $this->requireVirtualTable('services');
+        $this->select->join(
+            array('hs' => $this->prefix . 'hoststatus'),
+            'hs.host_object_id = s.host_object_id',
+            array()
+        );
+        $this->select->join(
+            array('ss' => $this->prefix . 'servicestatus'),
+            'ss.service_object_id = so.object_id',
+            array()
+        );
+    }
 }
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php
index a99728b81..e34f8e38e 100644
--- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php
@@ -4,149 +4,83 @@
 namespace Icinga\Module\Monitoring\Backend\Ido\Query;
 
 use Icinga\Data\Filter\Filter;
-use Zend_Db_Expr;
-use Zend_Db_Select;
 
 /**
  * Query for service group summary
  */
 class ServicegroupsummaryQuery extends IdoQuery
 {
-    /**
-     * {@inheritdoc}
-     */
+
+    protected $allowCustomVars = true;
+
     protected $columnMap = array(
-        'servicestatussummary' => array(
-            'servicegroup'                                  => 'servicegroup COLLATE latin1_general_ci',
-            'servicegroup_alias'                            => 'servicegroup_alias COLLATE latin1_general_ci',
+        'servicegroupsummary' => array(
+            'servicegroup_alias'                            => 'servicegroup_alias',
             'servicegroup_name'                             => 'servicegroup_name',
-            'services_critical'                             => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)',
-            'services_critical_handled'                     => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state > 0 THEN 1 ELSE 0 END)',
-            'services_critical_handled_last_state_change'   => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state > 0 THEN state_change ELSE 0 END)',
-            'services_critical_unhandled'                   => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state = 0 THEN 1 ELSE 0 END)',
-            'services_critical_unhandled_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state = 0 THEN state_change ELSE 0 END)',
-            'services_ok'                                   => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)',
-            'services_ok_last_state_change'                 => 'MAX(CASE WHEN object_type = \'service\' AND state = 0 THEN state_change ELSE 0 END)',
-            'services_pending'                              => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)',
-            'services_pending_last_state_change'            => 'MAX(CASE WHEN object_type = \'service\' AND state = 99 THEN state_change ELSE 0 END)',
-            'services_severity'                             => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)',
-            'services_total'                                => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)',
-            'services_unknown'                              => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)',
-            'services_unknown_handled'                      => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state > 0 THEN 1 ELSE 0 END)',
-            'services_unknown_handled_last_state_change'    => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state > 0 THEN state_change ELSE 0 END)',
-            'services_unknown_unhandled'                    => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state = 0 THEN 1 ELSE 0 END)',
-            'services_unknown_unhandled_last_state_change'  => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state = 0 THEN state_change ELSE 0 END)',
-            'services_warning'                              => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)',
-            'services_warning_handled'                      => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state > 0 THEN 1 ELSE 0 END)',
-            'services_warning_handled_last_state_change'    => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state > 0 THEN state_change ELSE 0 END)',
-            'services_warning_unhandled'                    => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state = 0 THEN 1 ELSE 0 END)',
-            'services_warning_unhandled_last_state_change'  => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state = 0 THEN state_change ELSE 0 END)'
+            'services_critical'                             => 'SUM(CASE WHEN service_state = 2 THEN 1 ELSE 0 END)',
+            'services_critical_handled'                     => 'SUM(CASE WHEN service_state = 2 AND service_handled = 1 THEN 1 ELSE 0 END)',
+            'services_critical_unhandled'                   => 'SUM(CASE WHEN service_state = 2 AND service_handled = 0 THEN 1 ELSE 0 END)',
+            'services_ok'                                   => 'SUM(CASE WHEN service_state = 0 THEN 1 ELSE 0 END)',
+            'services_pending'                              => 'SUM(CASE WHEN service_state = 99 THEN 1 ELSE 0 END)',
+            'services_total'                                => 'SUM(1)',
+            'services_unknown'                              => 'SUM(CASE WHEN service_state = 3 THEN 1 ELSE 0 END)',
+            'services_unknown_handled'                      => 'SUM(CASE WHEN service_state = 3 AND service_handled = 1 THEN 1 ELSE 0 END)',
+            'services_unknown_unhandled'                    => 'SUM(CASE WHEN service_state = 3 AND service_handled = 0 THEN 1 ELSE 0 END)',
+            'services_warning'                              => 'SUM(CASE WHEN service_state = 1 THEN 1 ELSE 0 END)',
+            'services_warning_handled'                      => 'SUM(CASE WHEN service_state = 1 AND service_handled = 1 THEN 1 ELSE 0 END)',
+            'services_warning_unhandled'                    => 'SUM(CASE WHEN service_state = 1 AND service_handled = 0 THEN 1 ELSE 0 END)',
         )
     );
 
     /**
-     * The union
+     * Subquery used for the summary query
      *
-     * @var Zend_Db_Select
+     * @var IdoQuery
      */
-    protected $summaryQuery;
+    protected $subQuery;
 
     /**
-     * Subqueries used for the summary query
+     * Count query
      *
-     * @var IdoQuery[]
+     * @var IdoQuery
      */
-    protected $subQueries = array();
+    protected $countQuery;
 
-    /**
-     * {@inheritdoc}
-     */
-    public function allowsCustomVars()
-    {
-        foreach ($this->subQueries as $query) {
-            if (! $query->allowsCustomVars()) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
     public function addFilter(Filter $filter)
     {
-        foreach ($this->subQueries as $sub) {
-            $sub->applyFilter(clone $filter);
-        }
+        $this->subQuery->applyFilter(clone $filter);
+        $this->countQuery->applyFilter(clone $filter);
         return $this;
     }
 
-    /**
-     * {@inheritdoc}
-     */
     protected function joinBaseTables()
     {
-        // TODO(el): Allow to switch between hard and soft states
-        $hosts = $this->createSubQuery(
-            'Hoststatus',
+        $this->countQuery = $this->createSubQuery(
+            'Servicegroup',
+            array()
+        );
+        $subQuery = $this->createSubQuery(
+            'Servicegroup',
             array(
-                'handled'       => 'host_handled',
-                'host_state'    => new Zend_Db_Expr('NULL'),
                 'servicegroup_alias',
                 'servicegroup_name',
-                'object_type',
-                'severity'      => new Zend_Db_Expr('NULL'),
-                'state'         => 'host_state',
-                'state_change'  => 'host_last_state_change'
+                'service_handled',
+                'service_state'
             )
         );
-        $hosts->select()->where('sgo.name1 IS NOT NULL'); // TODO(9458): Should be possible using our filters!
-        $this->subQueries[] = $hosts;
-        $services = $this->createSubQuery(
-            'Servicestatus',
-            array(
-                'handled'       => 'service_handled',
-                'host_state'    => 'host_state',
-                'servicegroup_alias',
-                'servicegroup_name',
-                'object_type',
-                'severity'      => 'service_severity',
-                'state'         => 'service_state',
-                'state_change'  => 'service_last_state_change'
-            )
-        );
-        $services->select()->where('sgo.name1 IS NOT NULL'); // TODO(9458): Should be possible using our filters!
-        $this->subQueries[] = $services;
-        $this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL);
-        $this->select->from(array('statussummary' => $this->summaryQuery), array());
+        $subQuery->setIsSubQuery();
+        $this->subQuery = $subQuery;
+        $this->select->from(array('servicesgroupsummary' => $this->subQuery), array());
         $this->group(array('servicegroup_name', 'servicegroup_alias'));
-        $this->joinedVirtualTables['servicestatussummary'] = true;
+        $this->joinedVirtualTables['servicegroupsummary'] = true;
     }
 
-    /**
-     * {@inheritdoc}
-     */
-    public function order($columnOrAlias, $dir = null)
+    public function getCountQuery()
     {
-        if (! $this->hasAliasName($columnOrAlias)) {
-            foreach ($this->subQueries as $sub) {
-                $sub->requireColumn($columnOrAlias);
-            }
-        }
-        return parent::order($columnOrAlias, $dir);
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function where($condition, $value = null)
-    {
-        $this->requireColumn($condition);
-        foreach ($this->subQueries as $sub) {
-            $sub->where($condition, $value);
-        }
-        return $this;
+        $count = $this->countQuery->select();
+        $this->countQuery->applyFilterSql($count);
+        $count->columns(array('sgo.object_id'));
+        $count->group(array('sgo.object_id'));
+        return $this->db->select()->from($count, array('cnt' => 'COUNT(*)'));
     }
 }