diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php
index 833ecb6e0..e50bd73aa 100644
--- a/application/applicationextension.inc.php
+++ b/application/applicationextension.inc.php
@@ -1857,4 +1857,28 @@ class RestUtils
interface iModuleExtension
{
public function __construct();
+}
+
+/**
+ * KPI logging extensibility point
+ *
+ * KPI Logger extension
+ */
+interface iKPILoggerExtension
+{
+ /**
+ * Init the statistics collected
+ *
+ * @return void
+ */
+ public function InitStats();
+
+ /**
+ * Add a new KPI to the stats
+ *
+ * @param \Combodo\iTop\Core\Kpi\KpiLogData $oKPILogData
+ *
+ * @return mixed
+ */
+ public function LogOperation($oKPILogData);
}
\ No newline at end of file
diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index 9f4188c18..f4844313e 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -4003,7 +4003,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
- $oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
+ $oKPI = new ExecutionKPI();
+ $oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
+ $oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
return $res;
@@ -4020,13 +4022,16 @@ EOF
protected function DBCloneTracked_Internal($newKey = null)
{
- $oNewObj = parent::DBCloneTracked_Internal($newKey);
+ /** @var cmdbAbstractObject $oNewObj */
+ $oNewObj = MetaModel::GetObject(get_class($this), parent::DBCloneTracked_Internal($newKey));
// Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
+ $oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
+ $oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
return $oNewObj;
@@ -4054,7 +4059,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
+ $oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
+ $oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
}
}
catch (Exception $e)
@@ -4100,7 +4107,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
+ $oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
+ $oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
}
return parent::DBDeleteTracked_Internal($oDeletionPlan);
@@ -4118,7 +4127,10 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
- if ($oExtensionInstance->OnIsModified($this))
+ $oKPI = new ExecutionKPI();
+ $bIsModified = $oExtensionInstance->OnIsModified($this);
+ $oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
+ if ($bIsModified)
{
return true;
}
@@ -4162,7 +4174,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
+ $oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
+ $oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
{
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
@@ -4210,7 +4224,9 @@ EOF
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
+ $oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
+ $oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
if (is_array($aNewIssues) && count($aNewIssues) > 0)
{
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
@@ -4722,7 +4738,7 @@ EOF
$bResult = (count($aErrors) == 0);
if ($bResult)
{
- list($bResult, $aErrors) = $oObj->CheckToWrite();
+ [$bResult, $aErrors] = $oObj->CheckToWrite();
}
if ($bPreview)
{
diff --git a/application/utils.inc.php b/application/utils.inc.php
index 4f12fec5f..2746aff09 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
*/
+use Combodo\iTop\Service\Module\ModuleService;
use ScssPhp\ScssPhp\Compiler;
@@ -1946,24 +1947,7 @@ class utils
*/
public static function GetCurrentModuleName($iCallDepth = 0)
{
- $sCurrentModuleName = '';
- $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- $sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
-
- foreach(GetModulesInfo() as $sModuleName => $aInfo)
- {
- if ($aInfo['root_dir'] !== '')
- {
- $sRootDir = realpath(APPROOT.$aInfo['root_dir']);
-
- if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
- {
- $sCurrentModuleName = $sModuleName;
- break;
- }
- }
- }
- return $sCurrentModuleName;
+ return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth = 0);
}
/**
@@ -1979,24 +1963,7 @@ class utils
*/
public static function GetCurrentModuleDir($iCallDepth)
{
- $sCurrentModuleDir = '';
- $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- $sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
-
- foreach(GetModulesInfo() as $sModuleName => $aInfo)
- {
- if ($aInfo['root_dir'] !== '')
- {
- $sRootDir = realpath(APPROOT.$aInfo['root_dir']);
-
- if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
- {
- $sCurrentModuleDir = basename($sRootDir);
- break;
- }
- }
- }
- return $sCurrentModuleDir;
+ return ModuleService::GetInstance()->GetCurrentModuleDir($iCallDepth);
}
/**
@@ -2011,12 +1978,7 @@ class utils
*/
public static function GetCurrentModuleUrl()
{
- $sDir = static::GetCurrentModuleDir(1);
- if ( $sDir !== '')
- {
- return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
- }
- return '';
+ return ModuleService::GetInstance()->GetCurrentModuleUrl();
}
/**
@@ -2026,8 +1988,7 @@ class utils
*/
public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
{
- $sModuleName = static::GetCurrentModuleName(1);
- return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
+ return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
}
/**
@@ -2036,12 +1997,7 @@ class utils
*/
public static function GetCompiledModuleVersion($sModuleName)
{
- $aModulesInfo = GetModulesInfo();
- if (array_key_exists($sModuleName, $aModulesInfo))
- {
- return $aModulesInfo[$sModuleName]['version'];
- }
- return null;
+ return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
}
/**
@@ -2505,4 +2461,5 @@ class utils
return (substr(PHP_OS,0,3) === 'WIN');
}
+
}
diff --git a/bootstrap.inc.php b/bootstrap.inc.php
index abbc1a877..4c97a9811 100644
--- a/bootstrap.inc.php
+++ b/bootstrap.inc.php
@@ -22,14 +22,8 @@ define('ITOP_DEFAULT_ENV', 'production');
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
-if (function_exists('microtime'))
-{
- $fItopStarted = microtime(true);
-}
-else
-{
- $fItopStarted = 1000 * time();
-}
+$fItopStarted = microtime(true);
+$iItopInitialMemory = memory_get_usage(true);
if (! isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false)
{
diff --git a/composer.json b/composer.json
index afa4b10b3..e236ecfea 100644
--- a/composer.json
+++ b/composer.json
@@ -62,6 +62,7 @@
"sources/application",
"sources/Composer",
"sources/Controller",
+ "sources/Service",
"sources/Core"
],
"exclude-from-classmap": [
diff --git a/core/MyHelpers.class.inc.php b/core/MyHelpers.class.inc.php
index 9d78d657c..31e5a253a 100644
--- a/core/MyHelpers.class.inc.php
+++ b/core/MyHelpers.class.inc.php
@@ -67,8 +67,7 @@ class MyHelpers
// format sss.mmmuuupppnnn
public static function getmicrotime()
{
- list($usec, $sec) = explode(" ",microtime());
- return ((float)$usec + (float)$sec);
+ return microtime(true);
}
/*
@@ -420,6 +419,7 @@ class MyHelpers
//}
return $sOutput;
}
+
}
/**
@@ -524,5 +524,3 @@ class Str
return (strtolower($sString) == $sString);
}
}
-
-?>
diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php
index 190babbdc..6c0839cc7 100644
--- a/core/cmdbsource.class.inc.php
+++ b/core/cmdbsource.class.inc.php
@@ -731,8 +731,9 @@ class CMDBSource
{
self::LogDeadLock($e);
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
- }
- $oKPI->ComputeStats('Query exec (mySQL)', $sSql);
+ } finally {
+ $oKPI->ComputeStats('Query exec (mySQL)', $sSql);
+ }
if ($oResult === false)
{
$aContext = array('query' => $sSql);
diff --git a/core/config.class.inc.php b/core/config.class.inc.php
index c0178cc7e..3d23ed24b 100644
--- a/core/config.class.inc.php
+++ b/core/config.class.inc.php
@@ -963,6 +963,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
+ 'log_kpi_report_to_extensions_only' => array(
+ 'type' => 'bool',
+ 'description' => 'Report only the KPI logging extensions',
+ 'default' => false,
+ 'value' => '',
+ 'source_of_value' => '',
+ 'show_in_conf_sample' => false,
+ ),
'max_linkset_output' => array(
'type' => 'integer',
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 2baf3c3f7..3d87d3abf 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -2225,7 +2225,7 @@ abstract class DBObject implements iDisplay
$oKPI = new ExecutionKPI();
$this->DoCheckToWrite();
- $oKPI->ComputeStats('CheckToWrite', get_class($this));
+ $oKPI->ComputeStatsForExtension($this, 'DoCheckToWrite');
if (count($this->m_aCheckIssues) == 0)
{
$this->m_bCheckStatus = true;
@@ -2693,8 +2693,12 @@ abstract class DBObject implements iDisplay
$sRootClass = MetaModel::GetRootClass($sClass);
// Ensure the update of the values (we are accessing the data directly)
+ $oKPI = new ExecutionKPI();
$this->DoComputeValues();
+ $oKPI->ComputeStatsForExtension($this, 'DoComputeValues');
+ $oKPI = new ExecutionKPI();
$this->OnInsert();
+ $oKPI->ComputeStatsForExtension($this, 'OnInsert');
if ($this->m_iKey < 0)
{
@@ -2712,7 +2716,7 @@ abstract class DBObject implements iDisplay
}
// Ultimate check - ensure DB integrity
- list($bRes, $aIssues) = $this->CheckToWrite();
+ [$bRes, $aIssues] = $this->CheckToWrite();
if (!$bRes)
{
throw new CoreCannotSaveObjectException(array('issues' => $aIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
@@ -2818,7 +2822,9 @@ abstract class DBObject implements iDisplay
$this->m_aOrigValues[$sAttCode] = $value;
}
+ $oKPI = new ExecutionKPI();
$this->AfterInsert();
+ $oKPI->ComputeStatsForExtension($this, 'AfterInsert');
// Activate any existing trigger
$sClass = get_class($this);
@@ -3094,8 +3100,11 @@ abstract class DBObject implements iDisplay
try
{
+ $oKPI = new ExecutionKPI();
$this->DoComputeValues();
- // Stop watches
+ $oKPI->ComputeStatsForExtension($this, 'DoComputeValues');
+
+ // Stop watches
$sState = $this->GetState();
if ($sState != '')
{
@@ -3114,7 +3123,9 @@ abstract class DBObject implements iDisplay
}
}
}
- $this->OnUpdate();
+ $oKPI = new ExecutionKPI();
+ $this->OnUpdate();
+ $oKPI->ComputeStatsForExtension($this, 'OnUpdate');
$aChanges = $this->ListChanges();
if (count($aChanges) == 0)
@@ -3126,7 +3137,7 @@ abstract class DBObject implements iDisplay
}
// Ultimate check - ensure DB integrity
- list($bRes, $aIssues) = $this->CheckToWrite();
+ [$bRes, $aIssues] = $this->CheckToWrite();
if (!$bRes)
{
throw new CoreCannotSaveObjectException(array(
@@ -3326,7 +3337,9 @@ abstract class DBObject implements iDisplay
try
{
- $this->AfterUpdate();
+ $oKPI = new ExecutionKPI();
+ $this->AfterUpdate();
+ $oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
// Reload to get the external attributes
if ($bHasANewExternalKeyValue)
diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php
index 076dbb954..d90e652a4 100644
--- a/core/dbobjectset.class.php
+++ b/core/dbobjectset.class.php
@@ -763,7 +763,10 @@ class DBObjectSet implements iDBObjectSetIterator
try
{
+ $oKPI = new ExecutionKPI();
$this->m_oSQLResult = CMDBSource::Query($sSQL);
+ $sOQL = $this->GetPseudoOQL($this->m_oFilter, $this->GetRealSortOrder(), $this->m_iLimitCount, $this->m_iLimitStart, false);
+ $oKPI->ComputeStats('OQL Query Exec', $sOQL);
} catch (MySQLException $e)
{
// 1116 = ER_TOO_MANY_TABLES
@@ -843,8 +846,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
+ $oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
$resQuery = CMDBSource::Query($sSQL);
+ $sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), 0, 0, true);
+ $oKPI->ComputeStats('OQL Query Exec', $sOQL);
if (!$resQuery) return 0;
$aRow = CMDBSource::FetchArray($resQuery);
@@ -855,6 +861,42 @@ class DBObjectSet implements iDBObjectSetIterator
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
}
+ /**
+ * @param \DBSearch $oFilter
+ * @param array $aOrder
+ * @param int $iLimitCount
+ * @param int $iLimitStart
+ * @param bool $bCount
+ *
+ * @return string
+ */
+ private function GetPseudoOQL($oFilter, $aOrder, $iLimitCount, $iLimitStart, $bCount)
+ {
+ $sOQL = '';
+ if ($bCount) {
+ $sOQL .= 'COUNT ';
+ }
+ $sOQL .= $oFilter->ToOQL();
+
+ if ($iLimitCount > 0) {
+ $sOQL .= ' LIMIT ';
+ if ($iLimitStart > 0) {
+ $sOQL .= "$iLimitStart, ";
+ }
+ $sOQL .= "$iLimitCount";
+ }
+
+ if (count($aOrder) > 0) {
+ $sOQL .= ' ORDER BY ';
+ $aOrderBy = [];
+ foreach ($aOrder as $sAttCode => $bAsc) {
+ $aOrderBy[] = $sAttCode.' '.($bAsc ? 'ASC' : 'DESC');
+ }
+ $sOQL .= implode(', ', $aOrderBy);
+ }
+ return $sOQL;
+ }
+
/**
* Check if the count exceeds a given limit
*
@@ -871,8 +913,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
+ $oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL);
+ $sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
+ $oKPI->ComputeStats('OQL Query Exec', $sOQL);
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
@@ -883,7 +928,7 @@ class DBObjectSet implements iDBObjectSetIterator
{
$iCount = 0;
}
- }
+ }
else
{
$iCount = $this->m_iNumTotalDBRows;
@@ -908,8 +953,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
+ $oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL);
+ $sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
+ $oKPI->ComputeStats('OQL Query Exec', $sOQL);
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
@@ -920,7 +968,7 @@ class DBObjectSet implements iDBObjectSetIterator
{
$iCount = 0;
}
- }
+ }
else
{
$iCount = $this->m_iNumTotalDBRows;
diff --git a/core/kpi.class.inc.php b/core/kpi.class.inc.php
index a5514e23b..57f167d87 100644
--- a/core/kpi.class.inc.php
+++ b/core/kpi.class.inc.php
@@ -15,6 +15,8 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see
+use Combodo\iTop\Core\Kpi\KpiLogData;
+use Combodo\iTop\Service\Module\ModuleService;
/**
@@ -30,6 +32,8 @@ class ExecutionKPI
static protected $m_bEnabled_Memory = false;
static protected $m_bBlameCaller = false;
static protected $m_sAllowedUser = '*';
+ static protected $m_bReportExtensionsOnly = false;
+ static protected $m_fSlowQueries = 0;
static protected $m_aStats = array(); // Recurrent operations
static protected $m_aExecData = array(); // One shot operations
@@ -77,14 +81,39 @@ class ExecutionKPI
return false;
}
+ static public function SetReportExtensionsOnly($bReportExtensionsOnly)
+ {
+ self::$m_bReportExtensionsOnly = $bReportExtensionsOnly;
+ }
+
+ static public function SetSlowQueries($fSlowQueries)
+ {
+ self::$m_fSlowQueries = $fSlowQueries;
+ }
+
static public function GetDescription()
{
$aFeatures = array();
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
- $sFeatures = implode(', ', $aFeatures);
+ $sFeatures = 'Measures: '.implode(', ', $aFeatures);
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
- return "KPI logging is active for $sFor. Measures: $sFeatures";
+ $sSlowQueries = '';
+ if (self::$m_fSlowQueries > 0) {
+ $sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
+ }
+
+ $aExtensions = [];
+ /** @var \iKPILoggerExtension $oExtensionInstance */
+ foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
+ $aExtensions[] = ModuleService::GetInstance()->GetModuleNameFromObject($oExtensionInstance);
+ }
+ $sExtensions = '';
+ if (count($aExtensions) > 0) {
+ $sExtensions = '. KPI Extensions: ['.implode(', ', $aExtensions).']';
+ }
+
+ return "KPI logging is active for $sFor. $sFeatures$sSlowQueries$sExtensions";
}
static public function ReportStats()
@@ -92,7 +121,28 @@ class ExecutionKPI
if (!self::IsEnabled()) return;
global $fItopStarted;
+ global $iItopInitialMemory;
$sExecId = microtime(); // id to differentiate the hrefs!
+ $sRequest = $_SERVER['REQUEST_URI'].' ('.$_SERVER['REQUEST_METHOD'].')';
+ if (isset($_POST['operation'])) {
+ $sRequest .= ' operation: '.$_POST['operation'];
+ }
+
+ $fStop = MyHelpers::getmicrotime();
+ if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
+ // Invoke extensions to log the KPI operation
+ /** @var \iKPILoggerExtension $oExtensionInstance */
+ $iCurrentMemory = self::memory_get_usage();
+ $iPeakMemory = self::memory_get_peak_usage();
+ foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
+ $oKPILogData = new KpiLogData(KpiLogData::TYPE_REQUEST, 'Page', $sRequest, $fItopStarted, $fStop, '', $iItopInitialMemory, $iCurrentMemory, $iPeakMemory);
+ $oExtensionInstance->LogOperation($oKPILogData);
+ }
+ }
+
+ if (self::$m_bReportExtensionsOnly) {
+ return;
+ }
$aBeginTimes = array();
foreach (self::$m_aExecData as $aOpStats)
@@ -105,7 +155,7 @@ class ExecutionKPI
self::Report("
");
self::Report("");
- self::Report("
KPIs - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")
");
+ self::Report("
KPIs - $sRequest
");
self::Report("
".date('Y-m-d H:i:s', $fItopStarted)."
");
self::Report("
log_kpi_user_id: ".UserRights::GetUserId()."
");
self::Report("
");
@@ -200,8 +250,6 @@ class ExecutionKPI
self::Report("
Next page stats
");
- $fSlowQueries = MetaModel::GetConfig()->Get('log_kpi_slow_queries');
-
// Report operation details
foreach (self::$m_aStats as $sOperation => $aOpStats)
{
@@ -245,7 +293,7 @@ class ExecutionKPI
$sTotalInter = round($fTotalInter, 3);
$sMinInter = round($fMinInter, 3);
$sMaxInter = round($fMaxInter, 3);
- if (($fTotalInter >= $fSlowQueries))
+ if (($fTotalInter >= self::$m_fSlowQueries))
{
if ($bDisplayHeader)
{
@@ -271,11 +319,19 @@ class ExecutionKPI
self::Report('
');
}
+ public static function InitStats()
+ {
+ // Invoke extensions to initialize the KPI statistics
+ /** @var \iKPILoggerExtension $oExtensionInstance */
+ foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
+ $oExtensionInstance->InitStats();
+ }
+ }
public function __construct()
{
$this->ResetCounters();
- }
+ }
// Get the duration since startup, and reset the counter for the next measure
//
@@ -285,6 +341,8 @@ class ExecutionKPI
$aNewEntry = null;
+ $fStarted = $this->m_fStarted;
+ $fStopped = $this->m_fStarted;
if (self::$m_bEnabled_Duration)
{
$fStopped = MyHelpers::getmicrotime();
@@ -297,6 +355,9 @@ class ExecutionKPI
$this->m_fStarted = $fStopped;
}
+ $iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
+ $iCurrentMemory = 0;
+ $iPeakMemory = 0;
if (self::$m_bEnabled_Memory)
{
$iCurrentMemory = self::memory_get_usage();
@@ -306,41 +367,95 @@ class ExecutionKPI
}
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
$aNewEntry['mem_end'] = $iCurrentMemory;
- if (function_exists('memory_get_peak_usage'))
- {
- $aNewEntry['mem_peak'] = memory_get_peak_usage();
- }
+ $iPeakMemory = self::memory_get_peak_usage();
+ $aNewEntry['mem_peak'] = $iPeakMemory;
// Reset for the next operation (if the object is recycled)
$this->m_iInitialMemory = $iCurrentMemory;
}
- if (!is_null($aNewEntry))
+ if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory) {
+ // Invoke extensions to log the KPI operation
+ /** @var \iKPILoggerExtension $oExtensionInstance */
+ foreach(MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance)
+ {
+ $sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
+ $oKPILogData = new KpiLogData(
+ KpiLogData::TYPE_REPORT,
+ 'Step',
+ $sOperationDesc,
+ $fStarted,
+ $fStopped,
+ $sExtension,
+ $iInitialMemory,
+ $iCurrentMemory,
+ $iPeakMemory);
+ $oExtensionInstance->LogOperation($oKPILogData);
+ }
+ }
+
+ if (!is_null($aNewEntry) && !self::$m_bReportExtensionsOnly)
{
self::$m_aExecData[] = $aNewEntry;
}
$this->ResetCounters();
}
+ public function ComputeStatsForExtension($object, $sMethod)
+ {
+ $sSignature = ModuleService::GetInstance()->GetModuleMethodSignature($object, $sMethod);
+ if (utils::StartsWith($sSignature, '[')) {
+ $this->ComputeStats('Extension', $sSignature);
+ }
+ }
+
public function ComputeStats($sOperation, $sArguments)
{
if (self::$m_bEnabled_Duration)
{
$fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted;
- if (self::$m_bBlameCaller)
- {
- self::$m_aStats[$sOperation][$sArguments][] = array(
- 'time' => $fDuration,
- 'callers' => MyHelpers::get_callstack(1),
- );
- }
- else
- {
- self::$m_aStats[$sOperation][$sArguments][] = array(
- 'time' => $fDuration
- );
- }
- }
+ $aCallstack = [];
+ if (!self::$m_bReportExtensionsOnly) {
+ if (self::$m_bBlameCaller) {
+ $aCallstack = MyHelpers::get_callstack(1);
+ self::$m_aStats[$sOperation][$sArguments][] = [
+ 'time' => $fDuration,
+ 'callers' => $aCallstack,
+ ];
+ } else {
+ self::$m_aStats[$sOperation][$sArguments][] = [
+ 'time' => $fDuration
+ ];
+ }
+ }
+
+ $iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
+ $iCurrentMemory = 0;
+ $iPeakMemory = 0;
+ if (self::$m_bEnabled_Memory)
+ {
+ $iCurrentMemory = self::memory_get_usage();
+ $iPeakMemory = self::memory_get_peak_usage();
+ }
+
+ // Invoke extensions to log the KPI operation
+ /** @var \iKPILoggerExtension $oExtensionInstance */
+ foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
+ $sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
+ $oKPILogData = new KpiLogData(
+ KpiLogData::TYPE_STATS,
+ $sOperation,
+ $sArguments,
+ $this->m_fStarted,
+ $fStopped,
+ $sExtension,
+ $iInitialMemory,
+ $iCurrentMemory,
+ $iPeakMemory,
+ $aCallstack);
+ $oExtensionInstance->LogOperation($oKPILogData);
+ }
+ }
}
protected function ResetCounters()
@@ -370,35 +485,7 @@ class ExecutionKPI
static protected function memory_get_usage()
{
- if (function_exists('memory_get_usage'))
- {
- return memory_get_usage(true);
- }
-
- // Copied from the PHP manual
- //
- //If its Windows
- //Tested on Win XP Pro SP2. Should work on Win 2003 Server too
- //Doesn't work for 2000
- //If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
- if (substr(PHP_OS,0,3) == 'WIN')
- {
- $output = array();
- exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
-
- return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
- }
- else
- {
- //We now assume the OS is UNIX
- //Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
- //This should work on most UNIX systems
- $pid = getmypid();
- exec("ps -eo%mem,rss,pid | grep $pid", $output);
- $output = explode(" ", $output[0]);
- //rss is given in 1024 byte units
- return $output[1] * 1024;
- }
+ return memory_get_usage(true);
}
static public function memory_get_peak_usage($bRealUsage = false)
diff --git a/core/metamodel.class.php b/core/metamodel.class.php
index d523fcc87..9af5005e8 100644
--- a/core/metamodel.class.php
+++ b/core/metamodel.class.php
@@ -2778,7 +2778,7 @@ abstract class MetaModel
// Build the list of available extensions
//
- $aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginUIExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider', 'iModuleExtension');
+ $aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginUIExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider', 'iModuleExtension', 'iKPILoggerExtension');
foreach($aInterfaces as $sInterface)
{
self::$m_aExtensionClasses[$sInterface] = array();
@@ -6348,7 +6348,9 @@ abstract class MetaModel
ExecutionKPI::EnableDuration(self::$m_oConfig->Get('log_kpi_duration'));
ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory'));
- ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
+ ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
+ ExecutionKPI::SetReportExtensionsOnly(self::$m_oConfig->Get('log_kpi_report_to_extensions_only'));
+ ExecutionKPI::SetSlowQueries(self::$m_oConfig->Get('log_kpi_slow_queries'));
self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write');
self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys');
@@ -6485,6 +6487,7 @@ abstract class MetaModel
CMDBSource::InitFromConfig(self::$m_oConfig);
// Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone);
+ ExecutionKPI::InitStats();
}
/**
diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php
index 6783882ca..99a389f2f 100644
--- a/core/trigger.class.inc.php
+++ b/core/trigger.class.inc.php
@@ -126,7 +126,9 @@ abstract class Trigger extends cmdbAbstractObject
$oAction = MetaModel::GetObject('Action', $iActionId);
if ($oAction->IsActive())
{
+ $oKPI = new ExecutionKPI();
$oAction->DoExecute($this, $aContextArgs);
+ $oKPI->ComputeStatsForExtension($oAction, 'DoExecute');
}
}
}
diff --git a/lib/autoload.php b/lib/autoload.php
index 64168f99a..79c1600b5 100644
--- a/lib/autoload.php
+++ b/lib/autoload.php
@@ -2,11 +2,6 @@
// autoload.php @generated by Composer
-if (PHP_VERSION_ID < 50600) {
- echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
- exit(1);
-}
-
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b::getLoader();
diff --git a/lib/composer/ClassLoader.php b/lib/composer/ClassLoader.php
index afef3fa2a..0cd6055d1 100644
--- a/lib/composer/ClassLoader.php
+++ b/lib/composer/ClassLoader.php
@@ -149,7 +149,7 @@ class ClassLoader
/**
* @return string[] Array of classname => path
- * @psalm-return array
+ * @psalm-var array
*/
public function getClassMap()
{
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index adac47083..f080a002f 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -2,7 +2,7 @@
// autoload_classmap.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
@@ -158,8 +158,10 @@ return array(
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php',
'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php',
'Combodo\\iTop\\Core\\Email\\iEMail' => $baseDir . '/sources/Core/Email/iEMail.php',
+ 'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => $baseDir . '/sources/Core/Kpi/KpiLogData.php',
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php',
+ 'Combodo\\iTop\\Service\\Module\\ModuleService' => $baseDir . '/sources/Service/Module/ModuleService.php',
'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Config' => $baseDir . '/core/config.class.inc.php',
@@ -2712,6 +2714,7 @@ return array(
'iDBObjectSetIterator' => $baseDir . '/core/dbobjectiterator.php',
'iDBObjectURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',
'iDisplay' => $baseDir . '/core/dbobject.class.php',
+ 'iKPILoggerExtension' => $baseDir . '/application/applicationextension.inc.php',
'iLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
'iLoginExtension' => $baseDir . '/application/applicationextension.inc.php',
'iLoginFSMExtension' => $baseDir . '/application/applicationextension.inc.php',
diff --git a/lib/composer/autoload_files.php b/lib/composer/autoload_files.php
index ae02e5199..7be757bea 100644
--- a/lib/composer/autoload_files.php
+++ b/lib/composer/autoload_files.php
@@ -2,25 +2,25 @@
// autoload_files.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
'023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php',
- '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+ '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php',
+ '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
+ 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php',
'7e9bd612cc444b3eed788ebbe46263a0' => $vendorDir . '/laminas/laminas-zendframework-bridge/src/autoload.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
- 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
- '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php',
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
- '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',
);
diff --git a/lib/composer/autoload_namespaces.php b/lib/composer/autoload_namespaces.php
index e6117c750..d12922d08 100644
--- a/lib/composer/autoload_namespaces.php
+++ b/lib/composer/autoload_namespaces.php
@@ -2,7 +2,7 @@
// autoload_namespaces.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php
index 651c9f0c1..ca8b4b9f6 100644
--- a/lib/composer/autoload_psr4.php
+++ b/lib/composer/autoload_psr4.php
@@ -2,7 +2,7 @@
// autoload_psr4.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
diff --git a/lib/composer/autoload_real.php b/lib/composer/autoload_real.php
index 752e35fbd..661cd2543 100644
--- a/lib/composer/autoload_real.php
+++ b/lib/composer/autoload_real.php
@@ -25,20 +25,33 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader'), true, true);
- self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader'));
$includePaths = require __DIR__ . '/include_paths.php';
$includePaths[] = get_include_path();
set_include_path(implode(PATH_SEPARATOR, $includePaths));
- require __DIR__ . '/autoload_static.php';
- call_user_func(\Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::getInitializer($loader));
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::getInitializer($loader));
+ } else {
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
$loader->setClassMapAuthoritative(true);
$loader->register(true);
- $includeFiles = \Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$files;
+ if ($useStaticLoader) {
+ $includeFiles = Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$files;
+ } else {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ }
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file);
}
@@ -47,16 +60,11 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b
}
}
-/**
- * @param string $fileIdentifier
- * @param string $file
- * @return void
- */
function composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
- $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
-
require $file;
+
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index de1646c31..4a74a9cc4 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -8,21 +8,21 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
{
public static $files = array (
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
'023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php',
- '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+ '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php',
+ '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
+ 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php',
'7e9bd612cc444b3eed788ebbe46263a0' => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src/autoload.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
- 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php',
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
- '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php',
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php',
- '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php',
);
@@ -526,8 +526,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php',
'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php',
'Combodo\\iTop\\Core\\Email\\iEMail' => __DIR__ . '/../..' . '/sources/Core/Email/iEMail.php',
+ 'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => __DIR__ . '/../..' . '/sources/Core/Kpi/KpiLogData.php',
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
+ 'Combodo\\iTop\\Service\\Module\\ModuleService' => __DIR__ . '/../..' . '/sources/Service/Module/ModuleService.php',
'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
@@ -3080,6 +3082,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'iDBObjectSetIterator' => __DIR__ . '/../..' . '/core/dbobjectiterator.php',
'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php',
+ 'iKPILoggerExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iLoginFSMExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
diff --git a/lib/composer/include_paths.php b/lib/composer/include_paths.php
index af33c1491..d4fb96718 100644
--- a/lib/composer/include_paths.php
+++ b/lib/composer/include_paths.php
@@ -2,7 +2,7 @@
// include_paths.php @generated by Composer
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
diff --git a/log/.htaccess b/log/.htaccess
deleted file mode 100644
index 782472c78..000000000
--- a/log/.htaccess
+++ /dev/null
@@ -1,13 +0,0 @@
-# Apache 2.4
-
-Require all denied
-
-
-# Apache 2.2
-
-deny from all
-Satisfy All
-
-
-# Apache 2.2 and 2.4
-IndexIgnore *
diff --git a/log/index.php b/log/index.php
deleted file mode 100644
index 112807643..000000000
--- a/log/index.php
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/sources/Core/Kpi/KpiLogData.php b/sources/Core/Kpi/KpiLogData.php
new file mode 100644
index 000000000..de39ab388
--- /dev/null
+++ b/sources/Core/Kpi/KpiLogData.php
@@ -0,0 +1,125 @@
+sType = $sType;
+ $this->sOperation = $sOperation;
+ $this->sArguments = @iconv(mb_detect_encoding($sArguments, mb_detect_order(), true), 'UTF-8', $sArguments);
+ $this->fStartTime = $fStartTime;
+ $this->fStopTime = $fStopTime;
+ $this->sExtension = $sExtension;
+ $this->iInitialMemory = $iInitialMemory;
+ $this->iCurrentMemory = $iCurrentMemory;
+ $this->iPeakMemory = $iPeakMemory;
+ $this->aData = $aData;
+ }
+
+ /**
+ * Return the CSV Header
+ *
+ * @return string
+ */
+ public static function GetCSVHeader()
+ {
+ return "Type,Operation,Arguments,StartTime,StopTime,Duration,Extension,InitialMemory,CurrentMemory,PeakMemory";
+ }
+
+ /**
+ * Return the CSV line for the values
+ * @return string
+ */
+ public function GetCSV()
+ {
+ $fDuration = sprintf('%01.4f', $this->fStopTime - $this->fStartTime);
+ $sType = $this->RemoveQuotes($this->sType);
+ $sOperation = $this->RemoveQuotes($this->sOperation);
+ $sArguments = $this->RemoveQuotes($this->sArguments);
+ $sExtension = $this->RemoveQuotes($this->sExtension);
+ return "\"$sType\",\"$sOperation\",\"$sArguments\",$this->fStartTime,$this->fStopTime,$fDuration,\"$sExtension\",$this->iInitialMemory,$this->iCurrentMemory,$this->iPeakMemory";
+ }
+
+ private function RemoveQuotes(string $sEntry): string
+ {
+ return str_replace('"', "'", $sEntry);
+ }
+
+ /**
+ * @param \Combodo\iTop\Core\Kpi\KpiLogData $oOther
+ *
+ * @return float
+ */
+ public function Compare(KpiLogData $oOther): float
+ {
+ if ($oOther->fStartTime > $this->fStartTime) {
+ return -1;
+ }
+ return 1;
+ }
+
+ public function Contains(KpiLogData $oOther): bool
+ {
+ if ($oOther->fStartTime < $this->fStartTime) {
+ return false;
+ }
+
+ if ($oOther->fStartTime > $this->fStopTime) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function __toString()
+ {
+ return "$this->sType:$this->sOperation:$this->sArguments";
+ }
+
+ public function GetUUID(): string
+ {
+ return sha1($this->__toString());
+ }
+}
\ No newline at end of file
diff --git a/sources/Service/Module/ModuleService.php b/sources/Service/Module/ModuleService.php
new file mode 100644
index 000000000..5370f7bbe
--- /dev/null
+++ b/sources/Service/Module/ModuleService.php
@@ -0,0 +1,214 @@
+getDeclaringClass();
+ $sExtension = $this->GetModuleNameFromObject($oReflectionClass->getName());
+ if (strlen($sExtension) !== 0) {
+ $sSignature .= '['.$sExtension.'] ';
+ }
+ $sSignature .= $oReflectionClass->getShortName().'::'.$sMethod.'()';
+
+ return $sSignature;
+ }
+
+ /**
+ * Get the module name from an object or class
+ *
+ * @param object|string $object
+ *
+ * @return string
+ * @throws \ReflectionException
+ */
+ public function GetModuleNameFromObject($object): string
+ {
+ $oReflectionClass = new ReflectionClass($object);
+ $sPath = str_replace('\\', '/', $oReflectionClass->getFileName());
+ $sPattern = str_replace('\\', '/', '@'.APPROOT.'env-'.utils::GetCurrentEnvironment()).'/(?.+)/@U';
+ if (preg_match($sPattern, $sPath, $aMatches) !== false) {
+ if (isset($aMatches['ext'])) {
+ return $aMatches['ext'];
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
+ *
+ * @param int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
+ *
+ * @return string the relative (to MODULESROOT) path of the root directory of the module containing the file where the call to
+ * this function is made
+ * or an empty string if no such module is found (or not called within a module file)
+ *
+ * @uses \debug_backtrace()
+ */
+ public function GetCurrentModuleDir(int $iCallDepth): string
+ {
+ $sCurrentModuleDir = '';
+ $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ $sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
+
+ foreach(GetModulesInfo() as $sModuleName => $aInfo)
+ {
+ if ($aInfo['root_dir'] !== '')
+ {
+ $sRootDir = realpath(APPROOT.$aInfo['root_dir']);
+
+ if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
+ {
+ $sCurrentModuleDir = basename($sRootDir);
+ break;
+ }
+ }
+ }
+ return $sCurrentModuleDir;
+ }
+
+ /**
+ * **Warning** : as this method uses {@see GetCurrentModuleDir} it produces hazardous results.
+ * You should better uses directly {@see GetAbsoluteUrlModulesRoot} and add the module dir name yourself ! See N°4573
+ *
+ * @return string the base URL for all files in the current module from which this method is called
+ * or an empty string if no such module is found (or not called within a module file)
+ * @throws \Exception
+ *
+ * @uses GetCurrentModuleDir
+ */
+ public function GetCurrentModuleUrl(): string
+ {
+ $sDir = $this->GetCurrentModuleDir(1);
+ if ( $sDir !== '')
+ {
+ return utils::GetAbsoluteUrlModulesRoot().'/'.$sDir;
+ }
+ return '';
+ }
+
+ /**
+ * @param string $sProperty The name of the property to retrieve
+ * @param mixed $defaultValue
+ *
+ * @return mixed the value of a given setting for the current module
+ */
+ public function GetCurrentModuleSetting(string $sProperty, $defaultValue = null)
+ {
+ $sModuleName = $this->GetCurrentModuleName(1);
+ return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultValue);
+ }
+
+ /**
+ * @param string $sModuleName
+ *
+ * @return string|NULL compiled version of a given module, as it was seen by the compiler
+ */
+ public function GetCompiledModuleVersion(string $sModuleName): ?string
+ {
+ $aModulesInfo = GetModulesInfo();
+ if (array_key_exists($sModuleName, $aModulesInfo))
+ {
+ return $aModulesInfo[$sModuleName]['version'];
+ }
+ return null;
+ }
+
+ /**
+ * Returns the name of the module containing the file where the call to this function is made
+ * or an empty string if no such module is found (or not called within a module file)
+ *
+ * @param int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
+ *
+ * @return string
+ */
+ public function GetCurrentModuleName(int $iCallDepth = 0): string
+ {
+ $sCurrentModuleName = '';
+ $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ $sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
+
+ return $this->GetModuleNameFromPath($sCallerFile);
+ }
+
+ private function GetModuleNameFromPath($sPath)
+ {
+ foreach (GetModulesInfo() as $sModuleName => $aInfo) {
+ if ($aInfo['root_dir'] !== '') {
+ $sRootDir = realpath(APPROOT.$aInfo['root_dir']);
+ if (substr($sPath, 0, strlen($sRootDir)) === $sRootDir) {
+
+ return $sModuleName;
+ }
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Get the extension code from the call stack.
+ * Scan the call stack until a module is found.
+ *
+ * @param int $iLevelsToIgnore
+ *
+ * @return string module name
+ */
+ public function GetModuleNameFromCallStack(int $iLevelsToIgnore = 0): string
+ {
+ $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ $aCallStack = array_slice($aCallStack, $iLevelsToIgnore);
+
+ foreach ($aCallStack as $aCallInfo) {
+ $sFile = realpath(empty($aCallInfo['file']) ? '' : $aCallInfo['file']);
+
+ $sModuleName = $this->GetModuleNameFromPath($sFile);
+ if (strlen($sModuleName) > 0) {
+ return $sModuleName;
+ }
+ }
+
+ return '';
+ }
+
+}
\ No newline at end of file