diff --git a/core/backgroundprocess.inc.php b/core/backgroundprocess.inc.php
index e19e63e79..ea5a2e4f3 100644
--- a/core/backgroundprocess.inc.php
+++ b/core/backgroundprocess.inc.php
@@ -18,7 +18,7 @@
/**
- * Class BackgroundProcess
+ * interface iBackgroundProcess
* Any extension that must be called regularly to be executed in the background
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
diff --git a/core/backgroundtask.class.inc.php b/core/backgroundtask.class.inc.php
new file mode 100644
index 000000000..bb1300816
--- /dev/null
+++ b/core/backgroundtask.class.inc.php
@@ -0,0 +1,76 @@
+
+
+
+/**
+ * Class BackgroundTask
+ * A class to record information about the execution of background processes
+ *
+ * @copyright Copyright (C) 2010-2012 Combodo SARL
+ * @license http://opensource.org/licenses/AGPL-3.0
+ */
+class BackgroundTask extends DBObject
+{
+ public static function Init()
+ {
+ $aParams = array
+ (
+ "category" => "core/cmdb",
+ "key_type" => "autoincrement",
+ "name_attcode" => "class_name",
+ "state_attcode" => "",
+ "reconc_keys" => array(),
+ "db_table" => "priv_backgroundtask",
+ "db_key_field" => "id",
+ "db_finalclass_field" => "",
+ "display_template" => "",
+ );
+ MetaModel::Init_Params($aParams);
+
+ MetaModel::Init_AddAttribute(new AttributeString("class_name", array("allowed_values"=>null, "sql"=>"class_name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDateTime("first_run_date", array("allowed_values"=>null, "sql"=>"first_run_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDateTime("latest_run_date", array("allowed_values"=>null, "sql"=>"latest_run_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDateTime("next_run_date", array("allowed_values"=>null, "sql"=>"next_run_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+ MetaModel::Init_AddAttribute(new AttributeInteger("total_exec_count", array("allowed_values"=>null, "sql"=>"total_exec_count", "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDecimal("latest_run_duration", array("allowed_values"=>null, "sql"=>"latest_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDecimal("min_run_duration", array("allowed_values"=>null, "sql"=>"min_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDecimal("max_run_duration", array("allowed_values"=>null, "sql"=>"max_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDecimal("average_run_duration", array("allowed_values"=>null, "sql"=>"average_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
+
+ MetaModel::Init_AddAttribute(new AttributeBoolean("running", array("allowed_values"=>null, "sql"=>"running", "default_value"=>false, "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('active,paused'), "sql"=>"status", "default_value"=>'active', "is_null_allowed"=>false, "depends_on"=>array())));
+ }
+
+ public function ComputeDurations($fLatestDuration)
+ {
+ $iTotalRun = $this->Get('total_exec_count');
+ $fAverageDuration = ($this->Get('average_run_duration') * $iTotalRun + $fLatestDuration) / (1+$iTotalRun);
+ $this->Set('average_run_duration', sprintf('%.3f',$fAverageDuration));
+ $this->Set('total_exec_count', 1+$iTotalRun);
+ if ($fLatestDuration < $this->Get('min_run_duration'))
+ {
+ $this->Set('min_run_duration', sprintf('%.3f',$fLatestDuration));
+ }
+ if ($fLatestDuration > $this->Get('max_run_duration'))
+ {
+ $this->Set('max_run_duration', sprintf('%.3f',$fLatestDuration));
+ }
+ $this->Set('latest_run_duration', sprintf('%.3f',$fLatestDuration));
+ }
+}
\ No newline at end of file
diff --git a/core/config.class.inc.php b/core/config.class.inc.php
index 64ead9973..8ba2b804a 100644
--- a/core/config.class.inc.php
+++ b/core/config.class.inc.php
@@ -747,6 +747,7 @@ class Config
'core/action.class.inc.php',
'core/trigger.class.inc.php',
'synchro/synchrodatasource.class.inc.php',
+ 'core/backgroundtask.class.inc.php',
);
$this->m_aDataModels = array();
$this->m_aWebServiceCategories = array(
diff --git a/core/ormstopwatch.class.inc.php b/core/ormstopwatch.class.inc.php
index 5370898f3..edbe1f5d5 100644
--- a/core/ormstopwatch.class.inc.php
+++ b/core/ormstopwatch.class.inc.php
@@ -389,6 +389,7 @@ class CheckStopWatchThresholds implements iBackgroundProcess
{
foreach (MetaModel::GetClasses() as $sClass)
{
+ $aList = array();
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeStopWatch)
@@ -400,7 +401,6 @@ class CheckStopWatchThresholds implements iBackgroundProcess
$sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < NOW()";
//echo $sExpression."
\n";
$oFilter = DBObjectSearch::FromOQL($sExpression);
- $aList = array();
$oSet = new DBObjectSet($oFilter);
while ((time() < $iTimeLimit) && ($oObj = $oSet->Fetch()))
{
diff --git a/webservices/cron.php b/webservices/cron.php
index 21f05f307..26d52c94e 100644
--- a/webservices/cron.php
+++ b/webservices/cron.php
@@ -1,5 +1,5 @@
p("USAGE:\n");
- $oP->p("php -q cron.php --auth_user= --auth_pwd= [--param_file=] [--verbose=1]\n");
+ $oP->p("php cron.php --auth_user= --auth_pwd= [--param_file=] [--verbose=1] [--status_only=1]\n");
}
else
{
- $oP->p("Optional parameters: verbose, param_file\n");
+ $oP->p("Optional parameters: verbose, param_file, status_only\n");
}
$oP->output();
exit -2;
}
+function RunTask($oBackgroundProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
+{
+ $oNow = new DateTime();
+ $fStart = microtime(true);
+ $sMessage = $oBackgroundProcess->Process($iTimeLimit);
+ $fDuration = microtime(true) - $fStart;
+ $oTask->ComputeDurations($fDuration);
+ $oTask->Set('latest_run_date', $oNow->format('Y-m-d H:i:s'));
+ $oPlannedStart = new DateTime($oTask->Get('latest_run_date'));
+ // Let's assume that the task was started exactly when planned so that the schedule does no shift each time
+ // this allows to schedule a task everyday "around" 11:30PM for example
+ $oPlannedStart->modify('+'.$oBackgroundProcess->GetPeriodicity().' seconds');
+ $oEnd = new DateTime();
+ if ($oPlannedStart->format('U') < $oEnd->format('U'))
+ {
+ // Huh, next planned start is already in the past, shift it of the periodicity !
+ $oPlannedStart = $oEnd->modify('+'.$oBackgroundProcess->GetPeriodicity().' seconds');
+ }
+ $oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s'));
+ $oTask->DBUpdate();
+
+ return $sMessage;
+}
// Known limitation - the background process periodicity is NOT taken into account
function CronExec($oP, $aBackgroundProcesses, $bVerbose)
@@ -76,18 +99,72 @@ function CronExec($oP, $aBackgroundProcesses, $bVerbose)
$iCronSleep = MetaModel::GetConfig()->Get('cron_sleep');
+ $oSearch = new DBObjectSearch('BackgroundTask');
while (time() < $iTimeLimit)
{
+ $oTasks = new DBObjectSet($oSearch);
+ $aTasks = array();
+ while($oTask = $oTasks->Fetch())
+ {
+ $aTasks[$oTask->Get('class_name')] = $oTask;
+ }
foreach ($aBackgroundProcesses as $oBackgroundProcess)
{
- if ($bVerbose)
+ $sTaskClass = get_class($oBackgroundProcess);
+ $oNow = new DateTime();
+ if (!array_key_exists($sTaskClass, $aTasks))
{
- $oP->p("Processing asynchronous task: ".get_class($oBackgroundProcess));
+ // New entry, let's create a new BackgroundTask record and run the task immediately
+ $oTask = new BackgroundTask();
+ $oTask->Set('class_name', get_class($oBackgroundProcess));
+ $oTask->Set('first_run_date', $oNow->format('Y-m-d H:i:s'));
+ $oTask->Set('total_exec_count', 0);
+ $oTask->Set('min_run_duration', 99999.999);
+ $oTask->Set('max_run_duration', 0);
+ $oTask->Set('average_run_duration', 0);
+ $oTask->Set('next_run_date', $oNow->format('Y-m-d H:i:s')); // in case of crash...
+ $oTask->DBInsert();
+ if ($bVerbose)
+ {
+ $oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=40s", ' '.$sTaskClass.' (first run) '));
+ }
+ $sMessage = RunTask($oBackgroundProcess, $oTask, $oNow, $iTimeLimit);
+ if ($bVerbose)
+ {
+ if(!empty($sMessage))
+ {
+ $oP->p("$sTaskClass: $sMessage");
+ }
+ $oEnd = new DateTime();
+ $oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=40s", ' '.$sTaskClass.' '));
+ }
}
- $sMessage = $oBackgroundProcess->Process($iTimeLimit);
- if ($bVerbose && !empty($sMessage))
+ else if( ($aTasks[$sTaskClass]->Get('status') == 'active') && ($aTasks[$sTaskClass]->Get('next_run_date') <= $oNow->format('Y-m-d H:i:s')))
{
- $oP->p("Returned: $sMessage");
+ $oTask = $aTasks[$sTaskClass];
+ // Run the task and record its next run time
+ if ($bVerbose)
+ {
+ $oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=40s", ' '.$sTaskClass.' '));
+ }
+ $sMessage = RunTask($oBackgroundProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
+ if ($bVerbose)
+ {
+ if(!empty($sMessage))
+ {
+ $oP->p("$sTaskClass: $sMessage");
+ }
+ $oEnd = new DateTime();
+ $oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=40s", ' '.$sTaskClass.' '));
+ }
+ }
+ else
+ {
+ // will run later
+ if (($aTasks[$sTaskClass]->Get('status') == 'active') && $bVerbose)
+ {
+ $oP->p("Skipping asynchronous task: $sTaskClass until ".$aTasks[$sTaskClass]->Get('next_run_date'));
+ }
}
}
if ($bVerbose)
@@ -102,6 +179,25 @@ function CronExec($oP, $aBackgroundProcesses, $bVerbose)
}
}
+function DisplayStatus($oP)
+{
+ $oSearch = new DBObjectSearch('BackgroundTask');
+ $oTasks = new DBObjectSet($oSearch);
+ $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+');
+ $oP->p('| Task Class | Status | Last Run | Next Run | Nb Run | Avg. Dur. |');
+ $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+');
+ while($oTask = $oTasks->Fetch())
+ {
+ $sTaskName = $oTask->Get('class_name');
+ $sStatus = $oTask->Get('status');
+ $sLastRunDate = $oTask->Get('latest_run_date');
+ $sNextRunDate = $oTask->Get('next_run_date');
+ $iNbRun = (int)$oTask->Get('total_exec_count');
+ $sAverageRunTime = $oTask->Get('average_run_duration');
+ $oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$s | %4$s | %5$6d | %6$7s s |', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate, $iNbRun, $sAverageRunTime));
+ }
+ $oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+');
+}
////////////////////////////////////////////////////////////////////////////////
//
// Main
@@ -189,10 +285,16 @@ if ($bVerbose)
$sDisplayProcesses = implode(', ', $aDisplayProcesses);
$oP->p("Background processes: ".$sDisplayProcesses);
}
+if (utils::ReadParam('status_only', false, true /* Allow CLI */))
+{
+ // Display status and exit
+ DisplayStatus($oP);
+ exit(0);
+}
$sLockName = 'itop.cron.php';
-$oP->p("Starting: ".time());
+$oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')');
$res = CMDBSource::QueryToScalar("SELECT GET_LOCK('$sLockName', 1)");// timeout = 1 second (see also IS_FREE_LOCK)
if (is_null($res))
{
@@ -209,7 +311,8 @@ elseif ($res === '1')
catch(Exception $e)
{
// TODO - Log ?
- $oP->p("ERROR:".$e->GetMessage());
+ $oP->p("ERROR:".$e->getMessage());
+ $oP->p($e->getTraceAsString());
}
$res = CMDBSource::QueryToScalar("SELECT RELEASE_LOCK('$sLockName')");
}
@@ -219,7 +322,7 @@ else
// Exit silently
$oP->p("Already running...");
}
-$oP->p("Exiting: ".time());
+$oP->p("Exiting: ".time().' ('.date('Y-m-d H:i:s').')');
$oP->Output();
?>