diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php
index f877239ce..c9a1279aa 100644
--- a/application/applicationextension.inc.php
+++ b/application/applicationextension.inc.php
@@ -2185,4 +2185,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 891487c97..6ffb976ff 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -3063,7 +3063,7 @@ EOF
}
$sCancelButtonOnClickScript .= "$('#form_{$this->m_iFormId} button.cancel').on('click', fOnClick{$this->m_iFormId}CancelButton);";
$oPage->add_ready_script($sCancelButtonOnClickScript);
-
+
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
@@ -4493,7 +4493,9 @@ HTML;
/** @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;
@@ -4510,13 +4512,16 @@ HTML;
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;
@@ -4544,7 +4549,9 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
+ $oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
+ $oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
}
}
catch (Exception $e)
@@ -4590,7 +4597,9 @@ HTML;
/** @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);
@@ -4608,7 +4617,10 @@ HTML;
/** @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;
}
@@ -4662,7 +4674,9 @@ HTML;
/** @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);
@@ -4710,7 +4724,9 @@ HTML;
/** @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);
diff --git a/application/startup.inc.php b/application/startup.inc.php
index 95a3a59d2..601f2a21d 100644
--- a/application/startup.inc.php
+++ b/application/startup.inc.php
@@ -98,4 +98,10 @@ else
Session::Set('itop_env', ITOP_DEFAULT_ENV);
}
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
-MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
+try {
+ MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
+}
+catch (MySQLException $e) {
+ IssueLog::Debug($e->getMessage());
+ throw new MySQLException('Could not connect to the DB server', []);
+}
\ No newline at end of file
diff --git a/application/utils.inc.php b/application/utils.inc.php
index 39f87c6be..5672bda2c 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -20,6 +20,7 @@
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
+use Combodo\iTop\Service\Module\ModuleService;
use ScssPhp\ScssPhp\Compiler;
use ScssPhp\ScssPhp\OutputStyle;
use ScssPhp\ScssPhp\ValueConverter;
@@ -2131,24 +2132,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);
}
/**
@@ -2170,24 +2154,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);
}
/**
@@ -2202,12 +2169,7 @@ class utils
*/
public static function GetCurrentModuleUrl()
{
- $sDir = static::GetCurrentModuleDir(1);
- if ( $sDir !== '')
- {
- return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
- }
- return '';
+ return ModuleService::GetInstance()->GetCurrentModuleUrl();
}
/**
@@ -2217,8 +2179,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);
}
/**
@@ -2227,12 +2188,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);
}
/**
@@ -2756,7 +2712,7 @@ HTML;
foreach ($aClassMap as $sPHPClass => $sPHPFile) {
$bSkipped = false;
-
+
// Check if our class matches name filter, or is in an excluded path
if ($sClassNameFilter !== '' && strpos($sPHPClass, $sClassNameFilter) === false) {
$bSkipped = true;
@@ -2776,7 +2732,7 @@ HTML;
$bSkipped = true; // file not found
}
}
-
+
if(!$bSkipped){
try {
$oRefClass = new ReflectionClass($sPHPClass);
diff --git a/bootstrap.inc.php b/bootstrap.inc.php
index e9fb69663..bccbcefdc 100644
--- a/bootstrap.inc.php
+++ b/bootstrap.inc.php
@@ -45,6 +45,7 @@ define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
$fItopStarted = microtime(true);
+$iItopInitialMemory = memory_get_usage(true);
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
require_once APPROOT.'/lib/autoload.php';
diff --git a/core/MyHelpers.class.inc.php b/core/MyHelpers.class.inc.php
index f5260d987..8d08472ca 100644
--- a/core/MyHelpers.class.inc.php
+++ b/core/MyHelpers.class.inc.php
@@ -419,6 +419,7 @@ class MyHelpers
//}
return $sOutput;
}
+
}
/**
@@ -523,5 +524,3 @@ class Str
return (strtolower($sString) == $sString);
}
}
-
-?>
diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php
index f3946f3db..b70f17cfe 100644
--- a/core/cmdbsource.class.inc.php
+++ b/core/cmdbsource.class.inc.php
@@ -606,8 +606,9 @@ class CMDBSource
{
self::LogDeadLock($e, true);
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 6cd2e3ff3..d8db58669 100644
--- a/core/config.class.inc.php
+++ b/core/config.class.inc.php
@@ -584,22 +584,22 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
- 'email_transport_smtp.allow_self_signed' => array(
+ 'email_transport_smtp.allow_self_signed' => [
'type' => 'bool',
'description' => 'Allow self signed peer certificates',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
- ),
- 'email_transport_smtp.verify_peer' => array(
+ ],
+ 'email_transport_smtp.verify_peer' => [
'type' => 'bool',
'description' => 'Verify peer certificate',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
- ),
+ ],
'email_css' => [
'type' => 'string',
'description' => 'CSS that will override the standard stylesheet used for the notifications',
@@ -989,6 +989,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
+ 'log_kpi_report_to_extensions_only' => [
+ 'type' => 'bool',
+ 'description' => 'Report only the KPI logging extensions',
+ 'default' => false,
+ 'value' => '',
+ 'source_of_value' => '',
+ 'show_in_conf_sample' => false,
+ ],
'max_linkset_output' => [
'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/counter.class.inc.php b/core/counter.class.inc.php
index 86ecc8d80..c43a2fd13 100644
--- a/core/counter.class.inc.php
+++ b/core/counter.class.inc.php
@@ -188,8 +188,8 @@ final class ItopCounter
if (!$hDBLink)
{
- throw new Exception("Could not connect to the DB server (host=$sDBHost, user=$sDBUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
- }
+ throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
+ }
return $hDBLink;
}
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 93d3d019e..2f6694cb7 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -2321,7 +2321,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;
@@ -2788,8 +2788,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 not automatically computed, then check that the key is given by the caller
if (!MetaModel::IsAutoIncrementKey($sRootClass))
@@ -2801,7 +2805,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()));
@@ -2916,7 +2920,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);
@@ -3153,8 +3159,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 != '')
{
@@ -3173,7 +3182,9 @@ abstract class DBObject implements iDisplay
}
}
}
- $this->OnUpdate();
+ $oKPI = new ExecutionKPI();
+ $this->OnUpdate();
+ $oKPI->ComputeStatsForExtension($this, 'OnUpdate');
$aChanges = $this->ListChanges();
if (count($aChanges) == 0)
@@ -3185,7 +3196,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(
@@ -3369,7 +3380,9 @@ abstract class DBObject implements iDisplay
$this->m_aModifiedAtt = array();
try {
- $this->AfterUpdate();
+ $oKPI = new ExecutionKPI();
+ $this->AfterUpdate();
+ $oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
// Reload to get the external attributes
if ($bNeedReload) {
diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php
index 6e70cbc3b..59b369d6f 100644
--- a/core/dbobjectset.class.php
+++ b/core/dbobjectset.class.php
@@ -767,7 +767,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
@@ -847,8 +850,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);
@@ -859,6 +865,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
*
@@ -875,8 +917,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);
@@ -887,7 +932,7 @@ class DBObjectSet implements iDBObjectSetIterator
{
$iCount = 0;
}
- }
+ }
else
{
$iCount = $this->m_iNumTotalDBRows;
@@ -912,8 +957,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);
@@ -924,7 +972,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 446cc7aa6..a6efd775c 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 = []; // Recurrent operations
static protected $m_aExecData = []; // One shot operations
@@ -86,14 +90,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()
@@ -101,7 +130,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)
@@ -114,9 +164,9 @@ class ExecutionKPI
$sHtml = "
";
$sHtml .= "";
- $sHtml .= "
KPIs - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")
";
+ $sHtml .= "
KPIs - $sRequest
";
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
- $sHtml .= "
".$oStarted->format('Y-m-d H:i:s.u')."
";
+ $sHtml .= '
'.$oStarted->format('Y-m-d H:i:s.u').'
';
$sHtml .= "
log_kpi_user_id: ".UserRights::GetUserId()."
";
$sHtml .= "
";
$sHtml .= "
";
@@ -257,7 +307,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)
{
@@ -285,37 +335,19 @@ class ExecutionKPI
self::Report($sHtml);
}
+ 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();
- self::Push($this);
- }
-
- /**
- * Stack executions to remove children duration from stats
- *
- * @param \ExecutionKPI $oExecutionKPI
- */
- private static function Push(ExecutionKPI $oExecutionKPI)
- {
- array_push(self::$m_aExecutionStack, $oExecutionKPI);
- }
-
- /**
- * Pop current child and count its duration in its parent
- *
- * @param float|int $fChildDuration
- */
- private static function Pop(float $fChildDuration = 0)
- {
- array_pop(self::$m_aExecutionStack);
- // Update the parent's children duration
- $oPrevExecutionKPI = end(self::$m_aExecutionStack);
- if ($oPrevExecutionKPI) {
- $oPrevExecutionKPI->m_fChildrenDuration += $fChildDuration;
- }
- }
+ }
// Get the duration since startup, and reset the counter for the next measure
//
@@ -325,7 +357,9 @@ class ExecutionKPI
$aNewEntry = null;
- if (self::$m_bEnabled_Duration) {
+ $fStarted = $this->m_fStarted;
+ $fStopped = $this->m_fStarted;
+ if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$aNewEntry = array(
'op' => $sOperationDesc,
@@ -336,6 +370,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();
@@ -345,40 +382,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)
{
$fDuration = 0;
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted;
- $fSelfDuration = $fDuration - $this->m_fChildrenDuration;
- if (self::$m_bBlameCaller) {
- self::$m_aStats[$sOperation][$sArguments][] = array(
- 'time' => $fSelfDuration,
- 'callers' => MyHelpers::get_callstack(1),
- );
- } else {
- self::$m_aStats[$sOperation][$sArguments][] = array(
- 'time' => $fSelfDuration,
- );
- }
- }
- self::Pop($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()
@@ -408,35 +500,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 7acdabafc..a185a3510 100644
--- a/core/metamodel.class.php
+++ b/core/metamodel.class.php
@@ -3001,32 +3001,7 @@ abstract class MetaModel
// Build the list of available extensions
//
- $aInterfaces = [
- 'iApplicationUIExtension',
- 'iPreferencesExtension',
- 'iApplicationObjectExtension',
- 'iLoginFSMExtension',
- 'iLoginUIExtension',
- 'iLogoutExtension',
- 'iQueryModifier',
- 'iOnClassInitialization',
- 'iPopupMenuExtension',
- 'iPageUIExtension',
- 'iPageUIBlockExtension',
- 'iBackofficeLinkedScriptsExtension',
- 'iBackofficeEarlyScriptExtension',
- 'iBackofficeScriptExtension',
- 'iBackofficeInitScriptExtension',
- 'iBackofficeReadyScriptExtension',
- 'iBackofficeLinkedStylesheetsExtension',
- 'iBackofficeStyleExtension',
- 'iBackofficeDictEntriesExtension',
- 'iBackofficeDictEntriesPrefixesExtension',
- '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_aExtensionClassNames[$sInterface] = array();
@@ -6588,7 +6563,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');
@@ -6713,6 +6690,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/mutex.class.inc.php b/core/mutex.class.inc.php
index b94f31bde..db7a4f387 100644
--- a/core/mutex.class.inc.php
+++ b/core/mutex.class.inc.php
@@ -257,7 +257,7 @@ class iTopMutex
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
if (!$this->hDBLink) {
- throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
+ throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
}
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,
diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php
index 303aacf8d..ef2bb871f 100644
--- a/core/trigger.class.inc.php
+++ b/core/trigger.class.inc.php
@@ -117,7 +117,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/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index 0dc29f123..acd509563 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -310,6 +310,7 @@ return array(
'Combodo\\iTop\\Core\\DbConnectionWrapper' => $baseDir . '/core/DbConnectionWrapper.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\\Core\\MetaModel\\FriendlyNameType' => $baseDir . '/sources/Core/MetaModel/FriendlyNameType.php',
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => $baseDir . '/sources/Core/MetaModel/HierarchicalKey.php',
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
@@ -364,6 +365,7 @@ return array(
'Combodo\\iTop\\Renderer\\FieldRenderer' => $baseDir . '/sources/Renderer/FieldRenderer.php',
'Combodo\\iTop\\Renderer\\FormRenderer' => $baseDir . '/sources/Renderer/FormRenderer.php',
'Combodo\\iTop\\Renderer\\RenderingOutput' => $baseDir . '/sources/Renderer/RenderingOutput.php',
+ 'Combodo\\iTop\\Service\\Module\\ModuleService' => $baseDir . '/sources/Service/Module/ModuleService.php',
'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php',
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
@@ -2944,6 +2946,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',
'iKeyboardShortcut' => $baseDir . '/sources/application/UI/Hook/iKeyboardShortcut.php',
'iLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
'iLoginExtension' => $baseDir . '/application/applicationextension.inc.php',
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index 026b5cc7c..6fc0575bf 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -678,6 +678,7 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239
'Combodo\\iTop\\Core\\DbConnectionWrapper' => __DIR__ . '/../..' . '/core/DbConnectionWrapper.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\\Core\\MetaModel\\FriendlyNameType' => __DIR__ . '/../..' . '/sources/Core/MetaModel/FriendlyNameType.php',
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => __DIR__ . '/../..' . '/sources/Core/MetaModel/HierarchicalKey.php',
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
@@ -732,6 +733,7 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239
'Combodo\\iTop\\Renderer\\FieldRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FieldRenderer.php',
'Combodo\\iTop\\Renderer\\FormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FormRenderer.php',
'Combodo\\iTop\\Renderer\\RenderingOutput' => __DIR__ . '/../..' . '/sources/Renderer/RenderingOutput.php',
+ 'Combodo\\iTop\\Service\\Module\\ModuleService' => __DIR__ . '/../..' . '/sources/Service/Module/ModuleService.php',
'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php',
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
@@ -3312,6 +3314,7 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239
'iDBObjectSetIterator' => __DIR__ . '/../..' . '/core/dbobjectiterator.php',
'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php',
+ 'iKPILoggerExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iKeyboardShortcut' => __DIR__ . '/../..' . '/sources/application/UI/Hook/iKeyboardShortcut.php',
'iLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
diff --git a/log/web.config b/log/web.config
index 58c9c3ac3..599a5f260 100644
--- a/log/web.config
+++ b/log/web.config
@@ -1,13 +1,8 @@
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/setup/backup.class.inc.php b/setup/backup.class.inc.php
index da722b1ef..2fd094ed7 100644
--- a/setup/backup.class.inc.php
+++ b/setup/backup.class.inc.php
@@ -416,8 +416,8 @@ EOF;
if ($oMysqli->connect_errno)
{
$sHost = is_null($this->iDBPort) ? $this->sDBHost : $this->sDBHost.' on port '.$this->iDBPort;
- throw new BackupException("Cannot connect to the MySQL server '$sHost' (".$oMysqli->connect_errno.") ".$oMysqli->connect_error);
- }
+ throw new MySQLException('Could not connect to the DB server '.$oMysqli->connect_errno.' (mysql errno: '.$oMysqli->connect_error, array('host' => $sHost, 'user' => $sUser));
+ }
if (!$oMysqli->select_db($this->sDBName))
{
throw new BackupException("The database '$this->sDBName' does not seem to exist");
@@ -522,6 +522,8 @@ EOF;
}
/**
+ * Define if we should force a transport option
+ *
* @param string $sHost
*
* @return string .
@@ -532,6 +534,8 @@ EOF;
{
$sTransportOptions = '';
+ /** N°6123 As we're using a --port option, if we use localhost as host,
+ * MariaDB > 10.6 will implicitly change its protocol from socket to tcp and throw a warning **/
if($sHost === 'localhost'){
$sTransportOptions = '--protocol=tcp';
}
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
diff --git a/tests/php-unit-tests/unitary-tests/setup/DBBackupTest.php b/tests/php-unit-tests/unitary-tests/setup/DBBackupTest.php
index 9506b5a59..f0694967b 100644
--- a/tests/php-unit-tests/unitary-tests/setup/DBBackupTest.php
+++ b/tests/php-unit-tests/unitary-tests/setup/DBBackupTest.php
@@ -84,4 +84,31 @@ class DBBackupTest extends ItopTestCase
$this->assertStringStartsWith(' --ssl', $sCliArgsCapathCfg);
$this->assertStringEndsWith('--ssl-ca='.DBBackup::EscapeShellArg($sTestCa), $sCliArgsCapathCfg);
}
+
+ /**
+ * Host is localhost, we should be forced into tcp
+ *
+ * @return void
+ */
+ public function testGetMysqlCliTransportOptionWithLocalhost()
+ {
+ $sHost= 'localhost';
+ $sTransport = DBBackup::GetMysqlCliTransportOption($sHost);
+
+ $this->assertStringStartsWith('--protocol=tcp', $sTransport);
+ $this->assertStringEndsWith('--protocol=tcp', $sTransport);
+ }
+
+ /**
+ * Host is not localhost, we shouldn't be forced into tcp
+ *
+ * @return void
+ */
+ public function testGetMysqlCliTransportOptionWithoutLocalhost()
+ {
+ $sHost= '127.0.0.1';
+ $sTransport = DBBackup::GetMysqlCliTransportOption($sHost);
+
+ $this->assertEmpty($sTransport);
+ }
}