Bladeren bron

Enhancement: take into account the "periodicity" of the background processes.
Bug fix: fixed a warning in CheckStopWatchThresholds

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2609 a333f486-631f-4898-b8df-5754b55c2be0

dflaven 12 jaren geleden
bovenliggende
commit
9faf2e7cc9
5 gewijzigde bestanden met toevoegingen van 193 en 13 verwijderingen
  1. 1 1
      core/backgroundprocess.inc.php
  2. 76 0
      core/backgroundtask.class.inc.php
  3. 1 0
      core/config.class.inc.php
  4. 1 1
      core/ormstopwatch.class.inc.php
  5. 114 11
      webservices/cron.php

+ 1 - 1
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

+ 76 - 0
core/backgroundtask.class.inc.php

@@ -0,0 +1,76 @@
+<?php
+// Copyright (C) 2013 Combodo SARL
+//
+//   This file is part of iTop.
+//
+//   iTop is free software; you can redistribute it and/or modify	
+//   it under the terms of the GNU Affero General Public License as published by
+//   the Free Software Foundation, either version 3 of the License, or
+//   (at your option) any later version.
+//
+//   iTop is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU Affero General Public License for more details.
+//
+//   You should have received a copy of the GNU Affero General Public License
+//   along with iTop. If not, see <http://www.gnu.org/licenses/>
+
+
+/**
+ * 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));
+	}
+}

+ 1 - 0
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(

+ 1 - 1
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."<br/>\n";
 						$oFilter = DBObjectSearch::FromOQL($sExpression);
-						$aList = array();
 						$oSet = new DBObjectSet($oFilter);
 						while ((time() < $iTimeLimit) && ($oObj = $oSet->Fetch()))
 						{

+ 114 - 11
webservices/cron.php

@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010-2012 Combodo SARL
+// Copyright (C) 2010-2013 Combodo SARL
 //
 //   This file is part of iTop.
 //
@@ -51,16 +51,39 @@ function UsageAndExit($oP)
 	if ($bModeCLI)
 	{
 		$oP->p("USAGE:\n");
-		$oP->p("php -q cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<file>] [--verbose=1]\n");		
+		$oP->p("php cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<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();
 ?>