From b6c3b3c7abb05c8df215eb0b287c8cbc13a78be8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 12 Jul 2012 13:55:26 +0000 Subject: [PATCH] Stop watches - alpha (GUI is missing) SVN:trunk[2122] --- application/cmdbabstract.class.inc.php | 4 + core/attributedef.class.inc.php | 240 +++++++++++++- core/cmdbobject.class.inc.php | 23 ++ core/computing.inc.php | 121 +++++++ core/dbobject.class.php | 86 ++++- core/filterdef.class.inc.php | 5 +- core/metamodel.class.php | 3 +- core/ormstopwatch.class.inc.php | 440 +++++++++++++++++++++++++ core/trigger.class.inc.php | 31 ++ setup/compiler.class.inc.php | 45 +++ 10 files changed, 993 insertions(+), 5 deletions(-) create mode 100644 core/computing.inc.php create mode 100644 core/ormstopwatch.class.inc.php diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 115aa6d50..66f1e85c2 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1875,6 +1875,10 @@ EOF $sHTMLValue .= " {$sValidationField}\n"; break; + case 'StopWatch': + $sHTMLValue = "The edition of a stopwatch is not allowed!!!"; + break; + case 'List': // Not editable for now... $sHTMLValue = ''; diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index b71e7cbda..50c7f9860 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -26,6 +26,7 @@ require_once('MyHelpers.class.inc.php'); require_once('ormdocument.class.inc.php'); +require_once('ormstopwatch.class.inc.php'); require_once('ormpassword.class.inc.php'); require_once('ormcaselog.class.inc.php'); @@ -95,7 +96,7 @@ abstract class AttributeDefinition protected $m_sCode; private $m_aParams = array(); protected $m_sHostClass = '!undefined!'; - protected function Get($sParamName) {return $this->m_aParams[$sParamName];} + public function Get($sParamName) {return $this->m_aParams[$sParamName];} protected function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));} protected function GetOptional($sParamName, $default) @@ -3199,6 +3200,243 @@ class AttributeBlob extends AttributeDefinition return ''; // Not exportable in XML, or as CDATA + some subtags ?? } } + +/** + * A stop watch is an ormStopWatch object, it is stored as several columns in the database + * + * @package iTopORM + */ +class AttributeStopWatch extends AttributeDefinition +{ + static public function ListExpectedParams() + { + // The list of thresholds must be an array of iPercent => array of 'option' => value + return array_merge(parent::ListExpectedParams(), array("states", "goal_computing", "working_time_computing", "thresholds")); + } + + public function GetEditClass() {return "StopWatch";} + + public function IsDirectField() {return true;} + public function IsScalar() {return true;} + public function IsWritable() {return false;} + public function GetDefaultValue() {return $this->NewStopWatch();} + //public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);} + + public function GetStates() + { + return $this->Get('states'); + } + + /** + * Construct a brand new (but configured) stop watch + */ + public function NewStopWatch() + { + $oSW = new ormStopWatch(); + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $oSW->DefineThreshold($iThreshold); + } + return $oSW; + } + + // Facilitate things: allow the user to Set the value from a string + public function MakeRealValue($proposedValue, $oHostObj) + { + if (!$proposedValue instanceof ormStopWatch) + { + return $this->NewStopWatch(); + } + return $proposedValue; + } + + public function GetSQLExpressions($sPrefix = '') + { + if ($sPrefix == '') + { + $sPrefix = $this->GetCode(); + } + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $sPrefix.'_timespent'; + $aColumns['_started'] = $sPrefix.'_started'; + $aColumns['_laststart'] = $sPrefix.'_laststart'; + $aColumns['_stopped'] = $sPrefix.'_stopped'; + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = '_'.$iThreshold; + $aColumns[$sThPrefix.'_deadline'] = $sPrefix.$sThPrefix.'_deadline'; + $aColumns[$sThPrefix.'_passed'] = $sPrefix.$sThPrefix.'_passed'; + $aColumns[$sThPrefix.'_overrun'] = $sPrefix.$sThPrefix.'_overrun'; + } + return $aColumns; + } + + public static function DateToSeconds($sDate) + { + if (is_null($sDate)) + { + return null; + } + $oDateTime = new DateTime($sDate); + $iSeconds = $oDateTime->format('U'); + return $iSeconds; + } + + public static function SecondsToDate($iSeconds) + { + if (is_null($iSeconds)) + { + return null; + } + return date("Y-m-d H:i:s", $iSeconds); + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + $aExpectedCols = array($sPrefix, $sPrefix.'_started', $sPrefix.'_laststart', $sPrefix.'_stopped'); + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = '_'.$iThreshold; + $aExpectedCols[] = $sPrefix.$sThPrefix.'_deadline'; + $aExpectedCols[] = $sPrefix.$sThPrefix.'_passed'; + $aExpectedCols[] = $sPrefix.$sThPrefix.'_overrun'; + } + foreach ($aExpectedCols as $sExpectedCol) + { + if (!array_key_exists($sExpectedCol, $aCols)) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sExpectedCol' from {$sAvailable}"); + } + } + + $value = new ormStopWatch( + $aCols[$sPrefix], + self::DateToSeconds($aCols[$sPrefix.'_started']), + self::DateToSeconds($aCols[$sPrefix.'_laststart']), + self::DateToSeconds($aCols[$sPrefix.'_stopped']) + ); + + $aThresholds = array(); + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = '_'.$iThreshold; + $value->DefineThreshold( + $iThreshold, + self::DateToSeconds($aCols[$sPrefix.$sThPrefix.'_deadline']), + (bool)($aCols[$sPrefix.$sThPrefix.'_passed'] == 1), + $aCols[$sPrefix.$sThPrefix.'_overrun'] + ); + } + + return $value; + } + + public function GetSQLValues($value) + { + if ($value instanceOf ormStopWatch) + { + $aValues = array(); + $aValues[$this->GetCode().'_timespent'] = $value->GetTimeSpent(); + $aValues[$this->GetCode().'_started'] = self::SecondsToDate($value->GetStartDate()); + $aValues[$this->GetCode().'_laststart'] = self::SecondsToDate($value->GetLastStartDate()); + $aValues[$this->GetCode().'_stopped'] = self::SecondsToDate($value->GetStopDate()); + + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sPrefix = $this->GetCode().'_'.$iThreshold; + $aValues[$sPrefix.'_deadline'] = self::SecondsToDate($value->GetThresholdDate($iThreshold)); + $aValues[$sPrefix.'_passed'] = $value->IsThresholdPassed($iThreshold) ? '1' : '0'; + $aValues[$sPrefix.'_overrun'] = $value->GetOverrun($iThreshold); + } + } + else + { + $aValues = array(); + $aValues[$this->GetCode().'_timespent'] = ''; + $aValues[$this->GetCode().'_started'] = ''; + $aValues[$this->GetCode().'_laststart'] = ''; + $aValues[$this->GetCode().'_stopped'] = ''; + } + return $aValues; + } + + public function GetSQLColumns() + { + $aColumns = array(); + $aColumns[$this->GetCode().'_timespent'] = 'INT(11) UNSIGNED DEFAULT 0'; + $aColumns[$this->GetCode().'_started'] = 'DATETIME NULL'; + $aColumns[$this->GetCode().'_laststart'] = 'DATETIME NULL'; + $aColumns[$this->GetCode().'_stopped'] = 'DATETIME NULL'; + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sPrefix = $this->GetCode().'_'.$iThreshold; + $aColumns[$sPrefix.'_deadline'] = 'DATETIME NULL'; + $aColumns[$sPrefix.'_passed'] = 'TINYINT(1) NULL'; + $aColumns[$sPrefix.'_overrun'] = 'INT(11) UNSIGNED NULL'; + } + return $aColumns; + } + + public function GetFilterDefinitions() + { + //return array(); + // still not working... see later... + $aRes = array( + $this->GetCode() => new FilterFromAttribute($this), + $this->GetCode().'_started' => new FilterFromAttribute($this, '_started'), + $this->GetCode().'_laststart' => new FilterFromAttribute($this, '_laststart'), + $this->GetCode().'_stopped' => new FilterFromAttribute($this, '_stopped') + ); + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sPrefix = $this->GetCode().'_'.$iThreshold; + $aRes[$sPrefix.'_deadline'] = new FilterFromAttribute($this, '_deadline'); + $aRes[$sPrefix.'_passed'] = new FilterFromAttribute($this, '_passed'); + $aRes[$sPrefix.'_overrun'] = new FilterFromAttribute($this, '_overrun'); + } + return $aRes; + } + + public function GetBasicFilterOperators() + { + return array(); + } + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return 'true'; + } + + public function GetAsHTML($value, $oHostObject = null) + { + if (is_object($value)) + { + return $value->GetAsHTML($this, $oHostObject); + } + } + + public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null) + { + return $value->GetTimeSpent(); + } + + public function GetAsXML($value, $oHostObject = null) + { + return $value->GetTimeSpent(); + } + + public function ListThresholds() + { + return $this->Get('thresholds'); + } +} + /** * One way encrypted (hashed) password */ diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 5c0aa9051..9a36938d1 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -195,6 +195,29 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("prevdata", $original); $iId = $oMyChangeOp->DBInsertNoReload(); } + elseif ($oAttDef instanceOf AttributeStopWatch) + { + // Stop watches + // TEMPORARY IMPLEMENTATION + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + + // Temporary - working thanks to ormStopWatch::__toString() + if (array_key_exists($sAttCode, $aOrigValues)) + { + $sOriginalValue = $aOrigValues[$sAttCode]; + } + else + { + $sOriginalValue = 'undefined'; + } + $oMyChangeOp->Set("oldvalue", $sOriginalValue); + $oMyChangeOp->Set("newvalue", $value); + $iId = $oMyChangeOp->DBInsertNoReload(); + } elseif ($oAttDef instanceOf AttributeCaseLog) { $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog"); diff --git a/core/computing.inc.php b/core/computing.inc.php new file mode 100644 index 000000000..fc04f1758 --- /dev/null +++ b/core/computing.inc.php @@ -0,0 +1,121 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +/** + * Metric computing for stop watches + */ +interface iMetricComputer +{ + public static function GetDescription(); + public function ComputeMetric($oObject); +} + +/** + * Working time computing for stop watches + */ +interface iWorkingTimeComputer +{ + public static function GetDescription(); + + /** + * Get the date/time corresponding to a given delay in the future from the present + * considering only the valid (open) hours for a specified object + * @param $oObject DBObject The object for which to compute the deadline + * @param $iDuration integer The duration (in seconds) in the future + * @param $oStartDate DateTime The starting point for the computation + * @return DateTime The date/time for the deadline + */ + public function GetDeadline($oObject, $iDuration, DateTime $oStartDate); + + /** + * Get duration (considering only open hours) elapsed bewteen two given DateTimes + * @param $oObject DBObject The object for which to compute the duration + * @param $oStartDate DateTime The starting point for the computation (default = now) + * @param $oEndDate DateTime The ending point for the computation (default = now) + * @return integer The duration (number of seconds) of open hours elapsed between the two dates + */ + public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate); +} + +/** + * Default implementation oof deadline computing: NO deadline + */ +class DefaultMetricComputer implements iMetricComputer +{ + public static function GetDescription() + { + return "Null"; + } + + public function ComputeMetric($oObject) + { + return null; + } +} + +/** + * Default implementation of working time computing + */ +class DefaultWorkingTimeComputer implements iWorkingTimeComputer +{ + public static function GetDescription() + { + return "24x7, no holidays"; + } + + /** + * Get the date/time corresponding to a given delay in the future from the present + * considering only the valid (open) hours for a specified object + * @param $oObject DBObject The object for which to compute the deadline + * @param $iDuration integer The duration (in seconds) in the future + * @param $oStartDate DateTime The starting point for the computation + * @return DateTime The date/time for the deadline + */ + public function GetDeadline($oObject, $iDuration, DateTime $oStartDate) + { + //echo "GetDeadline - default: ".$oStartDate->format('Y-m-d H:i:s')." + $iDuration
\n"; + // Default implementation: 24x7, no holidays: to compute the deadline, just add + // the specified duration to the given date/time + $oResult = clone $oStartDate; + $oResult->modify('+'.$iDuration.' seconds'); + return $oResult; + } + + /** + * Get duration (considering only open hours) elapsed bewteen two given DateTimes + * @param $oObject DBObject The object for which to compute the duration + * @param $oStartDate DateTime The starting point for the computation (default = now) + * @param $oEndDate DateTime The ending point for the computation (default = now) + * @return integer The duration (number of seconds) of open hours elapsed between the two dates + */ + public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate) + { + //echo "GetOpenDuration - default: ".$oStartDate->format('Y-m-d H:i:s')." to ".$oEndDate->format('Y-m-d H:i:s')."
\n"; + return abs($oEndDate->format('U') - $oStartDate->format('U')); + } +} + + +?> diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 6f404009a..787b3eccf 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -259,7 +259,14 @@ abstract class DBObject $value = $oAttDef->FromSQLToValue($aRow, $sAttRef); $this->m_aCurrValues[$sAttCode] = $value; - $this->m_aOrigValues[$sAttCode] = $value; + if (is_object($value)) + { + $this->m_aOrigValues[$sAttCode] = clone $value; + } + else + { + $this->m_aOrigValues[$sAttCode] = $value; + } $this->m_aLoadedAtt[$sAttCode] = true; } else @@ -357,6 +364,7 @@ abstract class DBObject } $realvalue = $oAttDef->MakeRealValue($value, $this); + $this->m_aCurrValues[$sAttCode] = $realvalue; // The object has changed, reset caches @@ -1313,6 +1321,26 @@ abstract class DBObject throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey())); } + // Stop watches + $sState = $this->GetState(); + if ($sState != '') + { + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + if ($oAttDef instanceof AttributeStopWatch) + { + if (in_array($sState, $oAttDef->GetStates())) + { + // Start the stop watch and compute the deadlines + $oSW = $this->Get($sAttCode); + $oSW->Start($this, $oAttDef); + $oSW->ComputeDeadlines($this, $oAttDef); + $this->Set($sAttCode, $oSW); + } + } + } + } + // First query built upon on the root class, because the ID must be created first $this->m_iKey = $this->DBInsertSingleTable($sRootClass); @@ -1396,6 +1424,25 @@ abstract class DBObject throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead"); } + // Stop watches + $sState = $this->GetState(); + if ($sState != '') + { + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) + { + if ($oAttDef instanceof AttributeStopWatch) + { + if (in_array($sState, $oAttDef->GetStates())) + { + // Compute or recompute the deadlines + $oSW = $this->Get($sAttCode); + $oSW->ComputeDeadlines($this, $oAttDef); + $this->Set($sAttCode, $oSW); + } + } + } + } + $this->DoComputeValues(); $this->OnUpdate(); @@ -1637,6 +1684,10 @@ abstract class DBObject return MetaModel::EnumTransitions(get_class($this), $sState); } + /** + * Designed as an action to be called when a stop watch threshold times out + * or from within the framework + */ public function ApplyStimulus($sStimulusCode) { $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); @@ -1687,11 +1738,44 @@ abstract class DBObject { $oTrigger->DoActivate($this->ToArgs('this')); } + + // Stop watches + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + if ($oAttDef instanceof AttributeStopWatch) + { + $oSW = $this->Get($sAttCode); + if (in_array($sNewState, $oAttDef->GetStates())) + { + $oSW->Start($this, $oAttDef); + } + else + { + $oSW->Stop($this, $oAttDef); + } + $this->Set($sAttCode, $oSW); + } + } } return $bSuccess; } + /** + * Designed as an action to be called when a stop watch threshold times out + */ + public function ResetStopWatch($sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); + if (!$oAttDef instanceof AttributeStopWatch) + { + throw new CoreException("Invalid stop watch id: '$sAttCode'"); + } + $oSW = $this->Get($sAttCode); + $oSW->Reset($this, $oAttDef); + $this->Set($sAttCode, $oSW); + } + // Make standard context arguments // Note: Needs to be reviewed because it is currently called once per attribute when an object is written (CheckToWrite / CheckValue) // Several options here: diff --git a/core/filterdef.class.inc.php b/core/filterdef.class.inc.php index 09c0c488a..06dfe64cf 100644 --- a/core/filterdef.class.inc.php +++ b/core/filterdef.class.inc.php @@ -155,12 +155,13 @@ class FilterFromAttribute extends FilterDefinition return array_merge(parent::ListExpectedParams(), array("refattribute")); } - public function __construct($oRefAttribute, $aParam = array()) + public function __construct($oRefAttribute, $sSuffix = '') { // In this very specific case, the code is the one of the attribute // (this to get a very very simple syntax upon declaration) + $aParam = array(); $aParam["refattribute"] = $oRefAttribute; - parent::__construct($oRefAttribute->GetCode(), $aParam); + parent::__construct($oRefAttribute->GetCode().$sSuffix, $aParam); } public function GetType() {return "Basic";} diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 79456ca0b..d2bb2c214 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -18,6 +18,7 @@ require_once(APPROOT.'core/modulehandler.class.inc.php'); require_once(APPROOT.'core/querybuildercontext.class.inc.php'); require_once(APPROOT.'core/querymodifier.class.inc.php'); require_once(APPROOT.'core/metamodelmodifier.inc.php'); +require_once(APPROOT.'core/computing.inc.php'); /** * Metamodel @@ -2591,7 +2592,7 @@ abstract class MetaModel // add it to the output foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) { - if (array_key_exists($sAttCode, $aExpectedAtts)) + if (array_key_exists($sAttCode.$sColId, $aExpectedAtts)) { $oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sTableAlias); foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier) diff --git a/core/ormstopwatch.class.inc.php b/core/ormstopwatch.class.inc.php new file mode 100644 index 000000000..f39c45c7f --- /dev/null +++ b/core/ormstopwatch.class.inc.php @@ -0,0 +1,440 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +/** + * ormStopWatch + * encapsulate the behavior of a stop watch that will be stored as an attribute of class AttributeStopWatch + * + * @package itopORM + */ +class ormStopWatch +{ + protected $iTimeSpent; // seconds + protected $iStarted; // unix time (seconds) + protected $iLastStart; // unix time (seconds) + protected $iStopped; // unix time (seconds) + protected $aThresholds; + + /** + * Constructor + */ + public function __construct($iTimeSpent = 0, $iStarted = null, $iLastStart = null, $iStopped = null) + { + $this->iTimeSpent = (int) $iTimeSpent; + $this->iStarted = $iStarted; + $this->iLastStart = $iLastStart; + $this->iStopped = $iStopped; + + $this->aThresholds = array(); + } + + public function DefineThreshold($iPercent, $tDeadline = null, $bPassed = false, $iOverrun = 0) + { + $this->aThresholds[$iPercent] = array( + 'deadline' => $tDeadline, // unix time (seconds) + 'passed' => $bPassed, + 'overrun' => $iOverrun + ); + } + + public function MarkThresholdAsPassed($iPercent) + { + $this->aThresholds[$iPercent]['passed'] = true; + } + + public function __toString() + { + return (string)$this->iTimeSpent; + } + + public function GetTimeSpent() + { + return $this->iTimeSpent; + } + + public function GetStartDate() + { + return $this->iStarted; + } + + public function GetLastStartDate() + { + return $this->iLastStart; + } + + public function GetStopDate() + { + return $this->iStopped; + } + + public function GetThresholdDate($iPercent) + { + if (array_key_exists($iPercent, $this->aThresholds)) + { + return $this->aThresholds[$iPercent]['deadline']; + } + else + { + return null; + } + } + + public function GetOverrun($iPercent) + { + if (array_key_exists($iPercent, $this->aThresholds)) + { + return $this->aThresholds[$iPercent]['overrun']; + } + else + { + return null; + } + } + public function IsThresholdPassed($iPercent) + { + if (array_key_exists($iPercent, $this->aThresholds)) + { + return $this->aThresholds[$iPercent]['passed']; + } + else + { + return false; + } + } + + public function GetAsHTML($oAttDef, $oHostObject = null) + { + $aProperties = array(); + + $aProperties['States'] = implode(', ', $oAttDef->GetStates()); + + if (is_null($this->iLastStart)) + { + if (is_null($this->iStarted)) + { + $aProperties['Elapsed'] = 'never started'; + } + else + { + $aProperties['Elapsed'] = $this->iTimeSpent.' s'; + } + } + else + { + $iElapsedTemp = $this->ComputeDuration($oHostObject, $oAttDef, $this->iLastStart, time()); + $aProperties['Elapsed'] = $this->iTimeSpent.' + '.$iElapsedTemp.' s + '; + } + + $aProperties['Started'] = $oAttDef->SecondsToDate($this->iStarted); + $aProperties['LastStart'] = $oAttDef->SecondsToDate($this->iLastStart); + $aProperties['Stopped'] = $oAttDef->SecondsToDate($this->iStopped); + + foreach ($this->aThresholds as $iPercent => $aThresholdData) + { + if ($aThresholdData['passed']) + { + if ($aThresholdData['overrun']) + { + $aProperties[$iPercent.'%'] = $oAttDef->SecondsToDate($aThresholdData['deadline'])." PASSED by ".$aThresholdData['overrun']." seconds"; + } + else + { + $aProperties[$iPercent.'%'] = $oAttDef->SecondsToDate($aThresholdData['deadline'])." PASSED"; + } + } + else + { + $aProperties[$iPercent.'%'] = $oAttDef->SecondsToDate($aThresholdData['deadline']); + } + } + $sRes = ""; + $sRes .= ""; + foreach ($aProperties as $sProperty => $sValue) + { + $sRes .= ""; + $sCell = str_replace("\n", "
\n", $sValue); + $sRes .= ""; + $sRes .= ""; + } + $sRes .= ""; + $sRes .= "
$sProperty$sCell
"; + return $sRes; + } + + protected function ComputeGoal($oObject, $oAttDef) + { + $sMetricComputer = $oAttDef->Get('goal_computing'); + $oComputer = new $sMetricComputer(); + $aCallSpec = array($oComputer, 'ComputeMetric'); + if (!is_callable($aCallSpec)) + { + throw new CoreException("Unknown class/verb '$sMetricComputer/ComputeMetric'"); + } + $iRet = call_user_func($aCallSpec, $oObject); + return $iRet; + } + + protected function ComputeDeadline($oObject, $oAttDef, $iStartTime, $iDurationSec) + { + $sWorkingTimeComputer = $oAttDef->Get('working_time_computing'); + $aCallSpec = array($sWorkingTimeComputer, '__construct'); + if (!is_callable($aCallSpec)) + { + //throw new CoreException("Pas de constructeur pour $sWorkingTimeComputer!"); + } + $oComputer = new $sWorkingTimeComputer(); + $aCallSpec = array($oComputer, 'GetDeadline'); + if (!is_callable($aCallSpec)) + { + throw new CoreException("Unknown class/verb '$sWorkingTimeComputer/GetDeadline'"); + } + // GetDeadline($oObject, $iDuration, DateTime $oStartDate) + $oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2 + $oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate); + $iRet = $oDeadline->format('U'); + return $iRet; + } + + protected function ComputeDuration($oObject, $oAttDef, $iStartTime, $iEndTime) + { + $sWorkingTimeComputer = $oAttDef->Get('working_time_computing'); + $oComputer = new $sWorkingTimeComputer(); + $aCallSpec = array($oComputer, 'GetOpenDuration'); + if (!is_callable($aCallSpec)) + { + throw new CoreException("Unknown class/verb '$sWorkingTimeComputer/GetOpenDuration'"); + } + // GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate) + $oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2 + $oEndDate = new DateTime('@'.$iEndTime); + $iRet = call_user_func($aCallSpec, $oObject, $oStartDate, $oEndDate); + return $iRet; + } + + public function Reset($oObject, $oAttDef) + { + $this->iTimeSpent = 0; + $this->iStarted = null; + $this->iLastStart = null; + $this->iStopped = null; + + foreach ($this->aThresholds as $iPercent => &$aThresholdData) + { + $aThresholdData['passed'] = false; + $aThresholdData['deadline'] = null; + $aThresholdData['overrun'] = null; + } + } + + /** + * Start or continue + * It is the responsibility of the caller to compute the deadlines + * (to avoid computing twice for the same result) + */ + public function Start($oObject, $oAttDef) + { + if (!is_null($this->iLastStart)) + { + // Already started + return false; + } + + if (is_null($this->iStarted)) + { + $this->iStarted = time(); + } + $this->iLastStart = time(); + $this->iStopped = null; + + return true; + } + + /** + * Compute or recompute the goal and threshold deadlines + */ + public function ComputeDeadlines($oObject, $oAttDef) + { + if (is_null($this->iLastStart)) + { + // Currently stopped - do nothing + return false; + } + + $iDurationGoal = $this->ComputeGoal($oObject, $oAttDef); + foreach ($this->aThresholds as $iPercent => &$aThresholdData) + { + if (!$aThresholdData['passed']) + { + if (is_null($iDurationGoal)) + { + // No limit: leave null thresholds + $aThresholdData['deadline'] = null; + } + else + { + $iThresholdDuration = round($iPercent * $iDurationGoal / 100); + $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent); + // OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iStarted, $iThresholdDuration); + } + } + } + return true; + } + + /** + * Stop counting if not already done + */ + public function Stop($oObject, $oAttDef) + { + if (is_null($this->iLastStart)) + { + // Already stopped + return false; + } + + $iElapsed = $this->ComputeDuration($oObject, $oAttDef, $this->iLastStart, time()); + $this->iTimeSpent = $this->iTimeSpent + $iElapsed; + + foreach ($this->aThresholds as $iPercent => &$aThresholdData) + { + if ($aThresholdData['passed']) + { + if (is_null($aThresholdData['overrun'])) + { + // First stop after the deadline is passed + $iOverrun = $this->ComputeDuration($oObject, $oAttDef, $aThresholdData['deadline'], time()); + $aThresholdData['overrun'] = $iOverrun; + } + else + { + // Accumulate from last start + $aThresholdData['overrun'] += $iElapsed; + } + } + $aThresholdData['deadline'] = null; + } + + $this->iLastStart = null; + $this->iStopped = time(); + + return true; + } +} + +/** + * CheckStopWatchThresholds + * Implements the automatic actions + * + * @package itopORM + */ +class CheckStopWatchThresholds implements iBackgroundProcess +{ + public function GetPeriodicity() + { + return 10; // seconds + } + + public function Process($iTimeLimit) + { + foreach (MetaModel::GetClasses() as $sClass) + { + foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + if ($oAttDef instanceof AttributeStopWatch) + { + foreach ($oAttDef->ListThresholds() as $iThreshold => $aThresholdData) + { + $iPercent = $aThresholdData['percent']; // could be different than the index ! + + $sExpression = "SELECT $sClass WHERE {$sAttCode}_{$iThreshold}_passed = 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())) + { + $sClass = get_class($oObj); + + $aList[] = $sClass.'::'.$oObj->GetKey().' '.$sAttCode.' '.$iThreshold; + echo $sClass.'::'.$oObj->GetKey().' '.$sAttCode.' '.$iThreshold."
\n"; + + // Execute planned actions + // + foreach ($aThresholdData['actions'] as $aActionData) + { + $sVerb = $aActionData['verb']; + $aParams = $aActionData['params']; + $sParams = implode(', ', $aParams); + //echo "Calling: $sVerb($sParams)
\n"; + $aCallSpec = array($oObj, $sVerb); + call_user_func_array($aCallSpec, $aParams); + } + + // Mark the threshold as "passed" + // + $oSW = $oObj->Get($sAttCode); + $oSW->MarkThresholdAsPassed($iThreshold); + $oObj->Set($sAttCode, $oSW); + + if($oObj->IsModified()) + { + // Todo - factorize so that only one single change will be instantiated + $oMyChange = new CMDBChange(); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Automatic - threshold passed"); + $iChangeId = $oMyChange->DBInsertNoReload(); + + $oObj->DBUpdateTracked($oMyChange, true /*skip security*/); + } + + // Activate any existing trigger + // + $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); + $oSet = new DBObjectSet( + DBObjectSearch::FromOQL("SELECT TriggerOnThresholdReached AS t WHERE t.target_class IN ('$sClassList') AND stop_watch_code=:stop_watch_code AND threshold_index = :threshold_index"), + array(), // order by + array('stop_watch_code' => $sAttCode, 'threshold_index' => $iThreshold) + ); + while ($oTrigger = $oSet->Fetch()) + { + $oTrigger->DoActivate($oObj->ToArgs('this')); + } + } + } + } + } + } + + $iProcessed = count($aList); + return "Encountered $iProcessed passed threshold(s)"; + } +} + + +?> diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 2dddb3f64..bbbc759a8 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -278,4 +278,35 @@ class lnkTriggerAction extends cmdbAbstractObject MetaModel::Init_SetZListItems('advanced_search', array('action_id', 'trigger_id', 'order')); // Criteria of the advanced search form } } + +class TriggerOnThresholdReached extends TriggerOnObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb,bizmodel", + "key_type" => "autoincrement", + "name_attcode" => "description", + "state_attcode" => "", + "reconc_keys" => array('description'), + "db_table" => "priv_trigger_threshold", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeString("stop_watch_code", array("allowed_values"=>null, "sql"=>"stop_watch_code", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("threshold_index", array("allowed_values"=>null, "sql"=>"threshold_index", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'stop_watch_code', 'threshold_index', 'action_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('target_class', 'threshold_index', 'threshold_index')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } +} ?> diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index a4ff43a8b..798ee059a 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -654,6 +654,51 @@ EOF; { $aParameters['depends_on'] = $sDependencies; } + elseif ($sAttType == 'AttributeStopWatch') + { + $oStates = $oField->GetUniqueElement('states'); + $oStateNodes = $oStates->getElementsByTagName('state'); + $aStates = array(); + foreach($oStateNodes as $oState) + { + $aStates[] = '"'.$oState->GetAttribute('id').'"'; + } + $aParameters['states'] = 'array('.implode(', ', $aStates).')'; + + $aParameters['goal_computing'] = $this->GetPropString($oField, 'goal', 'DefaultMetricComputer'); // Optional, no deadline by default + $aParameters['working_time_computing'] = $this->GetPropString($oField, 'working_time', 'DefaultWorkingTimeComputer'); // Optional, defaults to 24x7 + + $oThresholds = $oField->GetUniqueElement('thresholds'); + $oThresholdNodes = $oThresholds->getElementsByTagName('threshold'); + $aThresholds = array(); + foreach($oThresholdNodes as $oThreshold) + { + $iPercent = $this->GetPropNumber($oThreshold, 'percent'); + + $oActions = $oThreshold->GetUniqueElement('actions'); + $oActionNodes = $oActions->getElementsByTagName('action'); + $aActions = array(); + foreach($oActionNodes as $oAction) + { + $oParams = $oAction->GetOptionalElement('params'); + $aActionParams = array(); + if ($oParams) + { + $oParamNodes = $oParams->getElementsByTagName('param'); + foreach($oParamNodes as $oParam) + { + $aActionParams[] = self::QuoteForPHP($oParam->textContent); + } + } + $sActionParams = 'array('.implode(', ', $aActionParams).')'; + $sVerb = $this->GetPropString($oAction, 'verb'); + $aActions[] = "array('verb' => $sVerb, 'params' => $sActionParams)"; + } + $sActions = 'array('.implode(', ', $aActions).')'; + $aThresholds[] = $iPercent." => array('percent' => $iPercent, 'actions' => $sActions)"; + } + $aParameters['thresholds'] = 'array('.implode(', ', $aThresholds).')'; + } else { $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')"