diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 2b41ece36..75e64031b 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/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 "
\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 "
\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) diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 50ba69a6b..ecb58ce24 100644 --- a/core/cmdbobject.class.inc.php +++ b/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'); diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 499c50117..e42d66b8d 100644 --- a/core/cmdbsource.class.inc.php +++ b/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); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 1afedc700..f11c35fa0 100644 --- a/core/config.class.inc.php +++ b/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; diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 47253927b..e1cb27358 100644 --- a/core/dbobjectsearch.class.php +++ b/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 diff --git a/core/duration.class.inc.php b/core/duration.class.inc.php new file mode 100644 index 000000000..c02745526 --- /dev/null +++ b/core/duration.class.inc.php @@ -0,0 +1,82 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @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
\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 '); + } +} + +?> diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 4c242009d..d50970728 100644 --- a/core/metamodel.class.php +++ b/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; diff --git a/core/test.class.inc.php b/core/test.class.inc.php index 30949936c..015eb7877 100644 --- a/core/test.class.inc.php +++ b/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'); diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 6502085a1..9521a2418 100644 --- a/core/userrights.class.inc.php +++ b/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) diff --git a/pages/UI.php b/pages/UI.php index 116223594..c2570fb5e 100644 --- a/pages/UI.php +++ b/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(); } diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 4a8667b76..c1db84a5d 100644 --- a/setup/ajax.dataloader.php +++ b/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'); diff --git a/setup/index.php b/setup/index.php index 7eb710b53..309ce4144 100644 --- a/setup/index.php +++ b/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'); diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index 8e416a8f9..f874d9e5f 100644 --- a/setup/xmldataloader.class.inc.php +++ b/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');