Browse Source

Optimized roughly the load of user management data, and added a mean for quick profiling (to enable, add the setting log_duration)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@711 a333f486-631f-4898-b8df-5754b55c2be0
romainq 14 years ago
parent
commit
c973dfd743

+ 61 - 39
addons/userrights/userrightsprofile.class.inc.php

@@ -412,7 +412,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
 
 	public function Init()
 	{
-		MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'LoadCache'));
+		MetaModel::RegisterPlugin('userrights', 'ACbyProfile');
 	}
 
 
@@ -422,8 +422,12 @@ class UserRightsProfile extends UserRightsAddOnAPI
 	protected $m_aUserProfiles; // userid,profileid -> object
 	protected $m_aUserOrgs; // userid,orgid -> object
 
-	protected $m_aClassActionGrants; // profile, class, action -> permission
-	protected $m_aClassStimulusGrants; // profile, class, stimulus -> permission
+	// Those arrays could be completed on demand (inheriting parent permissions)
+	protected $m_aClassActionGrants = null; // profile, class, action -> actiongrantid (or false if NO, or null/missing if undefined)
+	protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission
+
+	// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
+	protected $m_aObjectActionGrants = array();
 
 	public function ResetCache()
 	{
@@ -434,9 +438,30 @@ class UserRightsProfile extends UserRightsAddOnAPI
 
 		$this->m_aAdmins = null;
 
-		// Loaded on demand
-		$this->m_aClassActionGrants = array();
-		$this->m_aClassStimulusGrants = array();
+		// Loaded on demand (time consuming as compared to the others)
+		$this->m_aClassActionGrants = null;
+		$this->m_aClassStimulusGrants = null;
+		
+		$this->m_aObjectActionGrants = array();
+	}
+
+	// Separate load: this cache is much more time consuming while loading
+	// Thus it is loaded iif required
+	// Could be improved by specifying the profile id
+	public function LoadActionGrantCache()
+	{
+		if (!is_null($this->m_aClassActionGrants)) return;
+
+		$oDuration = new Duration();
+
+		$oFilter = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant AS p WHERE p.permission = 'yes'");
+		$aGrants = $oFilter->ToDataArray();
+		foreach($aGrants as $aGrant)
+		{
+			$this->m_aClassActionGrants[$aGrant['profileid']][$aGrant['class']][strtolower($aGrant['action'])] = $aGrant['id'];
+		}
+
+		$oDuration->Scratch('Load of action grants');
 	}
 
 	public function LoadCache()
@@ -444,6 +469,8 @@ class UserRightsProfile extends UserRightsAddOnAPI
 		if (!is_null($this->m_aProfiles)) return;
 		// Could be loaded in a shared memory (?)
 
+		$oDuration = new Duration();
+
 		$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
 		$this->m_aProfiles = array(); 
 		while ($oProfile = $oProfileSet->Fetch())
@@ -469,11 +496,24 @@ class UserRightsProfile extends UserRightsAddOnAPI
 		{
 			$this->m_aUserOrgs[$oUserOrg->Get('userid')][$oUserOrg->Get('allowed_org_id')] = $oUserOrg;
 		}
+
+		$this->m_aClassStimulusGrants = array();
+		$oStimGrantSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant"));
+		$this->m_aStimGrants = array();
+		while ($oStimGrant = $oStimGrantSet->Fetch())
+		{
+			$this->m_aClassStimulusGrants[$oStimGrant->Get('profileid')][$oStimGrant->Get('class')][$oStimGrant->Get('stimulus')] = $oStimGrant;
+		}
+
+		$oDuration->Scratch('Load of user management cache (excepted Action Grants)');
+
 /*
 		echo "<pre>\n";
 		print_r($this->m_aProfiles);
 		print_r($this->m_aUserProfiles);
 		print_r($this->m_aUserOrgs);
+		print_r($this->m_aClassActionGrants);
+		print_r($this->m_aClassStimulusGrants);
 		echo "</pre>\n";
 exit;
 */
@@ -544,36 +584,29 @@ exit;
 	// This verb has been made public to allow the development of an accurate feedback for the current configuration
 	public function GetProfileActionGrant($iProfile, $sClass, $sAction)
 	{
-		$this->LoadCache();
+		$this->LoadActionGrantCache();
 
+		// Note: action is forced lowercase to be more flexible (historical bug)
+		$sAction = strtolower($sAction);
 		if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction]))
 		{
 			return $this->m_aClassActionGrants[$iProfile][$sClass][$sAction];
 		}
 
-		// Get the permission for this profile/class/action
-		$oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile AND permission = 'yes'");
-		$oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfile));
-		if ($oSet->Count() >= 1)
+		// Recursively look for the grant record in the class hierarchy
+		$sParentClass = MetaModel::GetParentPersistentClass($sClass);
+		if (empty($sParentClass))
 		{
-			$oGrantRecord = $oSet->Fetch();
+			$iGrant = null;
 		}
 		else
 		{
-			$sParentClass = MetaModel::GetParentPersistentClass($sClass);
-			if (empty($sParentClass))
-			{
-				$oGrantRecord = null;
-			}
-			else
-			{
-				// Recursively look for the grant record in the class hierarchy
-				$oGrantRecord = $this->GetProfileActionGrant($iProfile, $sParentClass, $sAction);
-			}
+			// Recursively look for the grant record in the class hierarchy
+			$iGrant = $this->GetProfileActionGrant($iProfile, $sParentClass, $sAction);
 		}
 
-		$this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $oGrantRecord;
-		return $oGrantRecord;
+		$this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $iGrant;
+		return $iGrant;
 	}
 
 	protected function GetUserActionGrant($oUser, $sClass, $iActionCode)
@@ -594,8 +627,8 @@ exit;
 		{
 			foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile)
 			{
-				$oGrantRecord = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
-				if (is_null($oGrantRecord))
+				$iGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
+				if (is_null($iGrant) || !$iGrant)
 				{
 					continue; // loop to the next profile
 				}
@@ -606,7 +639,7 @@ exit;
 					// update the list of attributes with those allowed for this profile
 					//
 					$oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid");
-					$oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey()));
+					$oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $iGrant));
 					$aProfileAttributes = $oSet->GetColumnAsArray('attcode', false);
 					if (count($aProfileAttributes) == 0)
 					{
@@ -735,21 +768,10 @@ exit;
 		{
 			return $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode];
 		}
-
-		// Get the permission for this profile/class/stimulus
-		$oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile AND permission = 'yes'");
-		$oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile));
-		if ($oSet->Count() >= 1)
-		{
-			$oGrantRecord = $oSet->Fetch();
-		}
 		else
 		{
-			$oGrantRecord = null;
+			return null;
 		}
-
-		$this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode] = $oGrantRecord;
-		return $oGrantRecord;
 	}
 
 	public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null)

+ 1 - 0
core/cmdbobject.class.inc.php

@@ -35,6 +35,7 @@ require_once('coreexception.class.inc.php');
 
 require_once('config.class.inc.php');
 require_once('log.class.inc.php');
+require_once('duration.class.inc.php');
 
 require_once('dict.class.inc.php');
 

+ 19 - 13
core/cmdbsource.class.inc.php

@@ -265,19 +265,7 @@ class CMDBSource
 			throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
 		}
 
-		$aNames = array();
-		for ($i = 0; $i < mysql_num_fields($result) ; $i++)
-		{
-		   $meta = mysql_fetch_field($result, $i);
-		   if (!$meta)
-		   {
-				throw new MySQLException('mysql_fetch_field: No information available', array('query'=>$sSql, 'i'=>$i));
-		   }
-		   else
-		   {
-		   		$aNames[] = $meta->name;
-		   }
-		}
+		$aNames = self::GetColumns($result);
 
 		$aData[] = $aNames;
 		while ($aRow = mysql_fetch_array($result, MYSQL_ASSOC))
@@ -310,6 +298,24 @@ class CMDBSource
 		return mysql_fetch_array($result, MYSQL_ASSOC);
 	}
 
+	public static function GetColumns($result)
+	{
+		$aNames = array();
+		for ($i = 0; $i < mysql_num_fields($result) ; $i++)
+		{
+		   $meta = mysql_fetch_field($result, $i);
+		   if (!$meta)
+		   {
+				throw new MySQLException('mysql_fetch_field: No information available', array('query'=>$sSql, 'i'=>$i));
+		   }
+		   else
+		   {
+	   		$aNames[] = $meta->name;
+		   }
+		}
+		return $aNames;
+	}
+
 	public static function Seek($result, $iRow)
 	{
 		return mysql_data_seek($result, $iRow);

+ 9 - 0
core/config.class.inc.php

@@ -37,6 +37,7 @@ define ('DEFAULT_LOG_GLOBAL', true);
 define ('DEFAULT_LOG_NOTIFICATION', true);
 define ('DEFAULT_LOG_ISSUE', true);
 define ('DEFAULT_LOG_WEB_SERVICE', true);
+define ('DEFAULT_LOG_DURATION', false);
 
 define ('DEFAULT_MIN_DISPLAY_LIMIT', 10);
 define ('DEFAULT_MAX_DISPLAY_LIMIT', 15);
@@ -79,6 +80,7 @@ class Config
 	protected $m_bLogNotification;
 	protected $m_bLogIssue;
 	protected $m_bLogWebService;
+	protected $m_bLogDuration; // private setting
 
 	/**
 	 * @var integer Number of elements to be displayed when there are more than m_iMaxDisplayLimit elements
@@ -176,6 +178,7 @@ class Config
 		$this->m_bLogNotification = DEFAULT_LOG_NOTIFICATION;
 		$this->m_bLogIssue = DEFAULT_LOG_ISSUE;
 		$this->m_bLogWebService = DEFAULT_LOG_WEB_SERVICE;
+		$this->m_bLogDuration = DEFAULT_LOG_DURATION;
 		$this->m_iMinDisplayLimit = DEFAULT_MIN_DISPLAY_LIMIT;
 		$this->m_iMaxDisplayLimit = DEFAULT_MAX_DISPLAY_LIMIT;
 		$this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL;
@@ -275,6 +278,7 @@ class Config
 		$this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool) trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION;
 		$this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool) trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE;
 		$this->m_bLogWebService = isset($MySettings['log_web_service']) ? (bool) trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE;
+		$this->m_bLogDuration = isset($MySettings['log_duration']) ? (bool) trim($MySettings['log_duration']) : DEFAULT_LOG_DURATION;
 		$this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT;
 		$this->m_iMaxDisplayLimit = isset($MySettings['max_display_limit']) ? trim($MySettings['max_display_limit']) : DEFAULT_MAX_DISPLAY_LIMIT;
 		$this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL;
@@ -405,6 +409,11 @@ class Config
 		return $this->m_bLogWebService;
 	}
 
+	public function GetLogDuration()
+	{
+		return $this->m_bLogDuration;
+	}
+
 	public function GetMinDisplayLimit()
 	{
 		return $this->m_iMinDisplayLimit;

+ 42 - 0
core/dbobjectsearch.class.php

@@ -558,6 +558,48 @@ class DBObjectSearch
 		return $retValue;
 	}
 
+	// Alternative to object mapping: the data are transfered directly into an array
+	// This is 10 times faster than creating a set of objects, and makes sense when optimization is required
+	public function ToDataArray($aColumns = array(), $aOrderBy = array(), $aArgs = array())
+	{
+		$sSQL = MetaModel::MakeSelectQuery($this, $aOrderBy, $aArgs);
+		$resQuery = CMDBSource::Query($sSQL);
+		if (!$resQuery) return;
+
+		if (count($aColumns) == 0)
+		{
+			$aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass()));
+			// Add the standard id (as first column)
+			array_unshift($aColumns, 'id');
+		}
+
+		$aQueryCols = CMDBSource::GetColumns($resQuery);
+
+		$sClassAlias = $this->GetClassAlias();
+		$aColMap = array();
+		foreach ($aColumns as $sAttCode)
+		{
+			$sColName = $sClassAlias.$sAttCode;
+			if (in_array($sColName, $aQueryCols))
+			{
+				$aColMap[$sAttCode] = $sColName;
+			}
+		}
+
+		$aRes = array();
+		while ($aRow = CMDBSource::FetchArray($resQuery))
+		{
+			$aMappedRow = array();
+			foreach ($aColMap as $sAttCode => $sColName)
+			{
+				$aMappedRow[$sAttCode] = $aRow[$sColName];
+			}
+			$aRes[] = $aMappedRow;
+		}
+		CMDBSource::FreeResult($resQuery);
+		return $aRes;
+	}
+
 	public function ToOQL(&$aParams = null)
 	{
 		// Currently unused, but could be useful later

+ 82 - 0
core/duration.class.inc.php

@@ -0,0 +1,82 @@
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+/**
+ * Mesures operations duration
+ *
+ * @author      Erwan Taloc <erwan.taloc@combodo.com>
+ * @author      Romain Quetiez <romain.quetiez@combodo.com>
+ * @author      Denis Flaven <denis.flaven@combodo.com>
+ * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
+ */
+
+class Duration
+{
+	static $m_bEnabled = false;
+
+	static public function Enable()
+	{
+		self::$m_bEnabled = true;
+	}
+
+	protected $m_fStarted = null;
+
+	public function __construct()
+	{
+		if (!self::$m_bEnabled) return;
+
+		$this->m_fStarted = MyHelpers::getmicrotime();
+	}
+
+	// Get the duration since startup, and reset the counter for the next measure
+	//
+	public function Scratch($sMeasure)
+	{
+		if (!self::$m_bEnabled) return;
+
+		$fStopped = MyHelpers::getmicrotime();
+		$fDuration = $fStopped - $this->m_fStarted;
+		$this->Report($sMeasure.': '.round($fDuration, 3));
+
+		$this->m_fStarted = MyHelpers::getmicrotime();
+	}
+
+	protected function Report($sText)
+	{
+		echo "DURATION... $sText<br/>\n";
+	}
+}
+
+// Prototype, to be finalized later
+// Reports the function duration
+// One single thing to do: construct it
+class FunctionDuration
+{
+	protected $m_sFunction = null;
+
+	public function __construct()
+	{
+		$this->m_sFunction = 'my_function_name_in_call_stack';
+		$this->m_fStarted = MyHelpers::getmicrotime();
+	}
+
+	public function __destruct()
+	{
+		$this->Scratch('Exiting ');
+	}
+}
+
+?>

+ 9 - 0
core/metamodel.class.php

@@ -3169,6 +3169,11 @@ abstract class MetaModel
 			self::$m_bLogWebService = false;
 		}
 
+		if (self::$m_oConfig->GetLogDuration())
+		{
+			Duration::Enable();
+		}
+
 		// Note: load the dictionary as soon as possible, because it might be
 		//       needed when some error occur
 		foreach (self::$m_oConfig->GetDictionaries() as $sModule => $sToInclude)
@@ -3201,10 +3206,14 @@ abstract class MetaModel
 		$sSource = self::$m_oConfig->GetDBName();
 		$sTablePrefix = self::$m_oConfig->GetDBSubname();
 
+		$oDuration = new Duration();
+
 		// The include have been included, let's browse the existing classes and
 		// develop some data based on the proposed model
 		self::InitClasses($sTablePrefix);
 
+		$oDuration->Scratch('Initialization of Data model structures');
+
 		self::$m_sDBName = $sSource;
 		self::$m_sTablePrefix = $sTablePrefix;
 

+ 1 - 0
core/test.class.inc.php

@@ -35,6 +35,7 @@ require_once('cmdbsource.class.inc.php');
 require_once('sqlquery.class.inc.php');
 
 require_once('log.class.inc.php');
+require_once('duration.class.inc.php');
 
 require_once('dbobject.class.php');
 require_once('dbobjectsearch.class.php');

+ 11 - 0
core/userrights.class.inc.php

@@ -131,6 +131,8 @@ abstract class User extends cmdbAbstractObject
 			return;
 		}
 
+		$oDuration = new Duration();
+
 		$aDisplayData = array();
 		foreach (MetaModel::GetClasses($sClassCategory) as $sClass)
 		{
@@ -165,6 +167,8 @@ abstract class User extends cmdbAbstractObject
 			);
 		}
 
+		$oDuration->Scratch('Computation of user rights');
+	
 		$aDisplayConfig = array();
 		$aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+'));
 		$aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+'));
@@ -596,6 +600,13 @@ class UserRights
 		return self::$m_aAdmins[$iUser];
 	}
 
+	/**
+	 * Reset cached data
+	 * @param Bool Reset admin cache as well
+	 * @return void
+	 */
+	// Reset cached data
+	//
 	public static function FlushPrivileges($bResetAdminCache = false)
 	{
 		if ($bResetAdminCache)

+ 2 - 0
pages/UI.php

@@ -455,6 +455,7 @@ try
 	require_once('../application/itopwebpage.class.inc.php');
 	require_once('../application/wizardhelper.class.inc.php');
 
+	$oDuration = new Duration();
 	require_once('../application/startup.inc.php');
 	$oAppContext = new ApplicationContext();
 	$currentOrganization = utils::ReadParam('org_id', '');
@@ -1389,6 +1390,7 @@ EOF
 			$oP->set_title($oMenuNode->GetLabel());
 		}
 	}
+	$oDuration->Scratch('Total page execution time');
 	////MetaModel::ShowQueryTrace();
 	$oP->output();
 }

+ 1 - 0
setup/ajax.dataloader.php

@@ -89,6 +89,7 @@ ob_start('FatalErrorCatcher'); // Start capturing the output, and pass it throug
 
 require_once('../core/config.class.inc.php');
 require_once('../core/log.class.inc.php');
+require_once('../core/duration.class.inc.php');
 require_once('../core/cmdbsource.class.inc.php');
 require_once('./xmldataloader.class.inc.php');
 

+ 2 - 0
setup/index.php

@@ -26,6 +26,7 @@
 require_once('../application/utils.inc.php');
 require_once('../core/config.class.inc.php');
 require_once('../core/log.class.inc.php');
+require_once('../core/duration.class.inc.php');
 require_once('../core/cmdbsource.class.inc.php');
 require_once('./setuppage.class.inc.php');
 
@@ -375,6 +376,7 @@ function CheckServerConnection(SetupWebPage $oP, $sDBServer, $sDBUser, $sDBPwd)
 function InitDataModel(SetupWebPage $oP, $sConfigFileName, $bModelOnly = true)
 {
 	require_once('../core/log.class.inc.php');
+	require_once('../core/duration.class.inc.php');
 	require_once('../core/coreexception.class.inc.php');
 	require_once('../core/dict.class.inc.php');
 	require_once('../core/attributedef.class.inc.php');

+ 1 - 0
setup/xmldataloader.class.inc.php

@@ -88,6 +88,7 @@ class XMLDataLoader
 	protected function InitDataModel($sConfigFileName)
 	{
 		require_once('../core/log.class.inc.php');
+		require_once('../core/duration.class.inc.php');
 		require_once('../core/coreexception.class.inc.php');
 		require_once('../core/dict.class.inc.php');
 		require_once('../core/attributedef.class.inc.php');