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

SVN:trunk[711]
This commit is contained in:
Romain Quetiez
2010-08-29 19:49:17 +00:00
parent e6bdaf87e7
commit 75bbad57fa
13 changed files with 241 additions and 52 deletions

View File

@@ -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)

View File

@@ -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');

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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 ');
}
}
?>

View File

@@ -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;

View File

@@ -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');

View File

@@ -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)

View File

@@ -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();
}

View File

@@ -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');

View File

@@ -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');

View File

@@ -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');