Name = 'Measure'; $this->Version = '1.0'; $this->Creator = 'Chronos'; $this->License = 'GNU/GPL'; $this->Description = 'Measurement processing'; $this->Dependencies = array(); } function DoStart(): void { $this->System->RegisterPage(array(''), 'PageMain'); } function DoInstall(): void { $this->Database->query('CREATE TABLE IF NOT EXISTS `Measure` ( `Id` int(11) NOT NULL auto_increment, `Name` varchar(255) collate utf8_general_ci NOT NULL, `Description` varchar(255) collate utf8_general_ci NOT NULL, `Divider` int(11) NOT NULL default 1, `Unit` varchar(16) collate utf8_general_ci NOT NULL, `Continuity` tinyint(1) NOT NULL default 0, `Period` int(11) NOT NULL default 60, `PermissionView` varchar(255) collate utf8_general_ci NOT NULL default "all", `PermissionAdd` varchar(255) collate utf8_general_ci NOT NULL default "localhost.localdomain", `Info` varchar(255) collate utf8_general_ci NOT NULL, `Enabled` int(11) NOT NULL default 1, `Cumulative` int(11) NOT NULL default 0, `DataTable` varchar(32) collate utf8_general_ci NOT NULL default "data", `DataType` varchar(32) collate utf8_general_ci NOT NULL, PRIMARY KEY (`Id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;'); } function DoUninstall(): void { $this->Database->query('DROP TABLE IF EXISTS `Measure`'); } } include_once('Point.php'); class Measure { var $Data; var $ValueTypes; var $LevelReducing; var $Database; var $MaxLevel; var $ReferenceTime; var $Differential; var $DivisionCount; var $PeriodTolerance; function __construct(Database $Database) { $this->Id = 0; $this->ValueTypes = array('Min', 'Avg', 'Max'); $this->Database = &$Database; $this->LevelReducing = 5; $this->MaxLevel = 4; $this->ReferenceTime = 0; $this->Differential = 0; $this->DivisionCount = 500; $this->PeriodTolerance = 0.25; // 25% $this->Data = array('Name' => '', 'Description' => '', 'Unit' => '', 'Info' => ''); } function TimeSegment(int $Level): int { return pow($this->LevelReducing, $Level) * 60; } function GetDataTable(): string { return 'data_'.$this->Data['Name']; } function AlignTime(int $Time, int $TimeSegment): int { return round(($Time - $this->ReferenceTime) / $TimeSegment) * $TimeSegment + $this->ReferenceTime; } function Load(int $Id): void { $Result = $this->Database->select('Measure', '*', '`Id`='.$Id); if ($Result->num_rows > 0) { $this->Data = $Result->fetch_array(); if ($this->Data['Continuity'] == 0) $this->Data['ContinuityEnabled'] = 0; // non continuous else $this->Data['ContinuityEnabled'] = 2; // continuous graph } else die('Měřená veličina '.$Id.' nenalezena.'); } function AddValue(float $Value, int $Time): void { $Result = $this->Database->select($this->Data['DataTable'], '*', '(`Measure`='.$this->Data['Id'].') AND '. '(`Level`=0) ORDER BY `Time` DESC LIMIT 2'); if ($Result->num_rows == 0) { // No measure value found. Simply insert new first value. $this->Database->insert($this->Data['DataTable'], array('Min' => $Value, 'Avg' => $Value, 'Max' => $Value, 'Level' => 0, 'Measure' => $this->Data['Id'], 'Time' => TimeToMysqlDateTime($Time), 'Continuity' => 0)); } else if ($Result->num_rows == 1) { // One value exists. Add second value. $this->Database->insert($this->Data['DataTable'], array('Min' => $Value, 'Avg' => $Value, 'Max' => $Value, 'Level' => 0, 'Measure' => $this->Data['Id'], 'Time' => TimeToMysqlDateTime($Time), 'Continuity' => 1)); } else { // Two values already exist in measure table $LastValue = $Result->fetch_array(); $NextToLastValue = $Result->fetch_array(); if (($Time - MysqlDateTimeToTime($LastValue['Time'])) < (1 - $this->PeriodTolerance) * $this->Data['Period']) { // New value added too quickly. Need to wait for next measure period. } else { // We are near defined period and can add new value. if (($Time - MysqlDateTimeToTime($LastValue['Time'])) < (1 + $this->PeriodTolerance) * $this->Data['Period']) { // New value added near defined measure period inside tolerance. Keep continuity. $Continuity = 1; } else { // New value added too late after defined measure period. Stop // continuity and let gap to be added. $Continuity = 0; } if (($LastValue['Avg'] == $NextToLastValue['Avg']) and ($LastValue['Avg'] == $Value) and ($LastValue['Continuity'] == 1) and ($Continuity == 1)) { // New value is same as last value and continuity mode is enabled and // continuity flag is present. Just shift forward time for last value. $this->Database->update($this->Data['DataTable'], '(`Time`="'.$LastValue['Time'].'") AND '. '(`Level`=0) AND (`Measure`='.$this->Data['Id'].')', array('Time' => TimeToMysqlDateTime($Time))); } else { // Last value is different or not with continuity flag. Need to add new value. $this->Database->insert($this->Data['DataTable'], array('Min' => $Value, 'Avg' => $Value, 'max' => $Value, 'Level' => 0, 'Measure' => $this->Data['Id'], 'Time' => TimeToMysqlDateTime($Time), 'Continuity' => $Continuity)); } } } $this->UpdateHigherLevels($Time); } function UpdateHigherLevels(int $Time): void { for ($Level = 1; $Level <= $this->MaxLevel; $Level++) { $TimeSegment = $this->TimeSegment($Level); $EndTime = $this->AlignTime($Time, $TimeSegment); //if ($EndTime < $Time) $EndTime = $EndTime + $TimeSegment; $StartTime = $EndTime - $TimeSegment; // Load values in time range $Values = array(); $Result = $this->Database->select($this->Data['DataTable'], '*', '(`Time` > "'. TimeToMysqlDateTime($StartTime).'") AND '. '(`Time` < "'.TimeToMysqlDateTime($EndTime).'") AND '. '(`Measure`='.$this->Data['Id'].') AND (`Level`='.($Level - 1).') ORDER BY `Time`'); while ($Row = $Result->fetch_array()) { $Row['Time'] = MysqlDateTimeToTime($Row['Time']); $Values[] = $Row; } //array_pop($Values); // Load subsidary values $Values = array_merge( $this->LoadLeftSideValue($Level - 1, $StartTime), $Values, $this->LoadRightSideValue($Level - 1, $EndTime) ); $Point = $this->ComputeOneValue($StartTime, $EndTime, $Values, $Level); $this->Database->delete($this->Data['DataTable'], '(`Time` > "'.TimeToMysqlDateTime($StartTime).'") AND (`Time` < "'.TimeToMysqlDateTime($EndTime).'") AND (`Measure`='.$this->Data['Id'].') '. 'AND (`Level`='.$Level.')'); $Continuity = $Values[1]['Continuity']; $this->Database->insert($this->Data['DataTable'], array('Level' => $Level, 'Measure' => $this->Data['Id'], 'Min' => $Point['Min'], 'Avg' => $Point['Avg'], 'Max' => $Point['Max'], 'Continuity' => $Continuity, 'Time' => TimeToMysqlDateTime($StartTime + ($EndTime - $StartTime) / 2))); } } /* Compute one value for upper time level from multiple values */ function ComputeOneValue(int $LeftTime, int $RightTime, array $Values, int $Level): array { $NewValue = array('Min' => +1000000000000000000, 'Avg' => 0, 'Max' => -1000000000000000000); // Trim outside parts foreach ($this->ValueTypes as $ValueType) { $Values[0][$ValueType] = Interpolation( NewPoint($Values[0]['Time'], $Values[0][$ValueType]), NewPoint($Values[1]['Time'], $Values[1][$ValueType]), $LeftTime); } $Values[0]['Time'] = $LeftTime; foreach ($this->ValueTypes as $ValueType) { $Values[count($Values) - 1][$ValueType] = Interpolation( NewPoint($Values[count($Values) - 2]['Time'], $Values[count($Values) - 2][$ValueType]), NewPoint($Values[count($Values) - 1]['Time'], $Values[count($Values) - 1][$ValueType]), $RightTime); } $Values[count($Values) - 1]['Time'] = $RightTime; // Perform computation foreach ($this->ValueTypes as $ValueType) { // Compute new value for ($I = 0; $I < (count($Values) - 1); $I++) { if ($ValueType == 'Avg') { if ($this->Data['Cumulative']) { $NewValue[$ValueType] = $NewValue[$ValueType] + $Values[$I][$ValueType]; } else { if ($Values[$I + 1]['Continuity'] == $this->Data['ContinuityEnabled']); else if ($this->Differential == 0) { $NewValue[$ValueType] = $NewValue[$ValueType] + ($Values[$I + 1]['Time'] - $Values[$I]['Time']) * (($Values[$I + 1][$ValueType] - $Values[$I][$ValueType]) / 2 + $Values[$I][$ValueType]); } else { $NewValue[$ValueType] = $NewValue[$ValueType] + ($Values[$I + 1]['Time'] - $Values[$I]['Time']) * (($Values[$I + 1][$ValueType] - $Values[$I][$ValueType]) / 2); } } } else if ($ValueType == 'Max') { if ($Values[$I + 1]['Continuity'] == $this->Data['ContinuityEnabled']) { if (0 > $NewValue[$ValueType]) $NewValue[$ValueType] = 0; } else { if ($this->Differential == 0) { if ($Values[$I + 1][$ValueType] > $NewValue[$ValueType]) $NewValue[$ValueType] = $Values[$I + 1][$ValueType]; } else { $Difference = $Values[$I + 1][$ValueType] - $Values[$I][$ValueType]; if ($Difference > $NewValue[$ValueType]) $NewValue[$ValueType] = $Difference; } } } else if ($ValueType == 'Min') { if ($Values[$I + 1]['Continuity'] == $this->Data['ContinuityEnabled']) { if (0 < $NewValue[$ValueType]) $NewValue[$ValueType] = 0; } else { if ($this->Differential == 0) { if ($Values[$I + 1][$ValueType] < $NewValue[$ValueType]) $NewValue[$ValueType] = $Values[$I + 1][$ValueType]; } else { $Difference = $Values[$I + 1][$ValueType] - $Values[$I][$ValueType]; if ($Difference < $NewValue[$ValueType]) $NewValue[$ValueType] = $Difference; } } } } $NewValue[$ValueType] = $NewValue[$ValueType]; } //if (($RightTime - $LeftTime) > 0) if ($this->Data['Cumulative'] == 0) { $NewValue['Avg'] = $NewValue['Avg'] / ($RightTime - $LeftTime); } return $NewValue; } function GetTimeRange(int $Level): array { // Get first and last time $Result = $this->Database->select($this->Data['DataTable'], '*', '(`Measure`='.$this->Data['Id'].') AND '. '(`Level`='.$Level.') ORDER BY `Time` LIMIT 1'); if ($Result->num_rows > 0) { $Row = $Result->fetch_array(); $AbsoluteLeftTime = MysqlDateTimeToTime($Row['Time']); } else $AbsoluteLeftTime = 0; $Result = $this->Database->select($this->Data['DataTable'], '*', '(`Measure`='.$this->Data['Id'].') AND '. '(`Level`='.$Level.') ORDER BY `Time` DESC LIMIT 1'); if ($Result->num_rows > 0) { $Row = $Result->fetch_array(); $AbsoluteRightTime = MysqlDateTimeToTime($Row['Time']); } else $AbsoluteRightTime = 0; return array('Left' => $AbsoluteLeftTime, 'Right' => $AbsoluteRightTime); } // Load one nearest value newer then given time function LoadRightSideValue(int $Level, int $Time): array { $Result = array(); $DbResult = $this->Database->select($this->Data['DataTable'], '*', '(`Time` > "'.TimeToMysqlDateTime($Time).'") AND (`Measure`='. $this->Data['Id'].') AND (`Level`='.$Level.') ORDER BY `Time` ASC LIMIT 1'); if ($DbResult->num_rows > 0) { // Found one value, simply return it $Row = $DbResult->fetch_array(); $Row['Time'] = MysqlDateTimeToTime($Row['Time']); return array($Row); } else { // Not found any value. Calculate it //$Time = $Values[count($Values) - 1]['time'] + 60; //array_push($Values, array('time' => $Time, 'min' => 0, 'avg' => 0, 'max' => 0, 'continuity' => 0)); $Result[] = array('Time' => ($Time + $this->TimeSegment($Level)), 'Min' => 0, 'Avg' => 0, 'Max' => 0, 'Continuity' => 0); $DbResult = $this->Database->select($this->Data['DataTable'], '*', '(`Time` < "'.TimeToMysqlDateTime($Time).'") AND (`Measure`='.$this->Data['Id'].') '. 'AND (`Level`='.$Level.') ORDER BY `Time` DESC LIMIT 1'); if ($DbResult->num_rows > 0) { $Row = $DbResult->fetch_array(); array_unshift($Result, array('Time' => (MysqlDateTimeToTime($Row['Time']) + 10), 'Min' => 0, 'Avg' => 0, 'Max' => 0, 'Continuity' => 0)); } return $Result; } } // Load one nearest value older then given time function LoadLeftSideValue(int $Level, int $Time): array { $Result = array(); $DbResult = $this->Database->select($this->Data['DataTable'], '*', '(`Time` < "'.TimeToMysqlDateTime($Time).'") AND (`Measure`='.$this->Data['Id'].') '. 'AND (`Level`='.$Level.') ORDER BY `Time` DESC LIMIT 1'); if ($DbResult->num_rows > 0) { // Found one value, simply return it $Row = $DbResult->fetch_array(); $Row['Time'] = MysqlDateTimeToTime($Row['Time']); return array($Row); } else { // Not found any value. Calculate it //$Time = $Values[0]['time'] - 60; //array_unshift($Values, array('time' => $Time, 'min' => 0, 'avg' => 0, 'max' => 0, 'continuity' => 0)); $Result[] = array('Time' => ($Time - $this->TimeSegment($Level)), 'Min' => 0, 'Avg' => 0, 'Max' => 0, 'Continuity' => 0); $DbResult = $this->Database->select($this->Data['DataTable'], '*', '(`Time` > "'.TimeToMysqlDateTime($Time).'") AND (`Measure`='.$this->Data['Id'].') '. 'AND (`Level`='.$Level.') ORDER BY `Time` ASC LIMIT 1'); if ($DbResult->num_rows > 0) { $Row = $DbResult->fetch_array(); array_push($Result, array('Time' => (MysqlDateTimeToTime($Row['Time']) - 10), 'Min' => 0, 'Avg' => 0, 'Max' => 0, 'Continuity' => 0)); } return $Result; } } function GetValues(int $TimeFrom, int $TimeTo, int $Level): array { //$AbsoluteTime = GetTimeRange($this->DataId); // if (($TimeFrom > $AbsoluteLeftTime) and ($TimeStart < $AbsoluteRightTime) and // ($TimeTo > $AbsoluteLeftTime) and ($TimeTo < $AbsoluteRightTime)) // { // Load values in time range $Result = $this->Database->select($this->Data['DataTable'], '`Time`, `Min`, '. '`Avg`, `Max`, `Continuity`', '(`Time` > "'.TimeToMysqlDateTime($TimeFrom).'") AND '. '(`Time` < "'.TimeToMysqlDateTime($TimeTo).'") AND '. '(`Measure`='.$this->Data['Id'].') AND (`Level`='.$Level.') ORDER BY `Time`'); $Values = array(); while ($Row = $Result->fetch_array()) { $Values[] = array('Time' => MysqlDateTimeToTime($Row['Time']), 'Min' => $Row['Min'], 'Avg' => $Row['Avg'], 'Max' => $Row['Max'], 'Continuity' => $Row['Continuity']); } // array_pop($Values); $Points = array(); if (count($Values) > 0) { $Values = array_merge( $this->LoadLeftSideValue($Level, $TimeFrom), $Values, $this->LoadRightSideValue($Level, $TimeTo)); $StartIndex = 0; $Points = array(); for ($I = 0; $I < $this->DivisionCount; $I++) { $TimeStart = $TimeFrom + (($TimeTo - $TimeFrom) / $this->DivisionCount) * $I; $TimeEnd = $TimeFrom + (($TimeTo - $TimeFrom) / $this->DivisionCount) * ($I + 1); $EndIndex = $StartIndex; while ($Values[$EndIndex]['Time'] < $TimeEnd) $EndIndex = $EndIndex + 1; $SubValues = array_slice($Values, $StartIndex, $EndIndex - $StartIndex + 1); $Points[] = $this->ComputeOneValue($TimeStart, $TimeEnd, $SubValues, $Level); $StartIndex = $EndIndex - 1; } } else $Points[] = array('Min' => 0, 'Avg' => 0, 'Max' => 0); return $Points; } function RebuildMeasureCache(): void { echo('Velicina '.$this->Data['Name']."
\n"); if ($this->Data['Continuity'] == 0) $this->Data['ContinuityEnabled'] = 0; // non continuous else $this->Data['ContinuityEnabled'] = 2; // continuous graph // Clear previous items $DbResult = $this->Database->select($this->Data['DataTable'], 'COUNT(*)', '(`Level`>0) AND (`Measure`='.$this->Data['Id'].')'); $Row = $DbResult->fetch_array(); echo("Mazu starou cache (".$Row[0]." polozek)..."); $this->Database->delete($this->Data['DataTable'], '(`Level`>0) AND (`Measure`='.$this->Data['Id'].')'); echo("
\n"); for ($Level = 1; $Level <= $this->MaxLevel; $Level++) { echo('Uroven '.$Level."
\n"); $TimeRange = $this->GetTimeRange($Level - 1); $TimeSegment = $this->TimeSegment($Level); $StartTime = $this->AlignTime($TimeRange['Left'], $TimeSegment) - $TimeSegment; $EndTime = $this->AlignTime($TimeRange['Right'], $TimeSegment); $BurstCount = 500; $Count = round(($EndTime - $StartTime) / $TimeSegment / $BurstCount); echo('For 0 to '.$Count."
\n"); for ($I = 0; $I <= $Count; $I++) { echo($I.' '); $StartTime2 = $StartTime + $I * $BurstCount * $TimeSegment; $EndTime2 = $StartTime + ($I + 1) * $BurstCount * $TimeSegment; $Values = array(); $DbResult = $this->Database->select($this->Data['DataTable'], '*', '(`Time` > "'. TimeToMysqlDateTime($StartTime2).'") AND (`Time` < "'. TimeToMysqlDateTime($EndTime2).'") AND (`Measure`='.$this->Data['Id']. ') AND (`Level`='.($Level - 1).') ORDER BY `Time`'); while ($Row = $DbResult->fetch_array()) { $Row['Time'] = MysqlDateTimeToTime($Row['Time']); $Values[] = $Row; } if (count($Values) > 0) { $Values = array_merge( $this->LoadLeftSideValue($Level - 1, $StartTime2), $Values, $this->LoadRightSideValue($Level - 1, $EndTime2)); $StartIndex = 0; for ($B = 0; $B < $BurstCount; $B++) { echo('.'); $StartTime3 = $StartTime2 + (($EndTime2 - $StartTime2) / $BurstCount) * $B; $EndTime3 = $StartTime2 + (($EndTime2 - $StartTime2) / $BurstCount) * ($B + 1); $EndIndex = $StartIndex; while ($Values[$EndIndex]['Time'] < $EndTime3) $EndIndex = $EndIndex + 1; $SubValues = array_slice($Values, $StartIndex, $EndIndex - $StartIndex + 1); if (count($SubValues) > 2) { $Point = $this->ComputeOneValue($StartTime3, $EndTime3, $SubValues, $Level); $Continuity = $SubValues[1]['Continuity']; $this->Database->insert($this->Data['DataTable'], array('Level' => $Level, 'Measure' => $this->Data['Id'], 'Min' => $Point['Min'], 'Avg' => $Point['Avg'], 'Max' => $Point['Max'], 'Continuity' => $Continuity, 'Time' => TimeToMysqlDateTime($StartTime3 + ($EndTime3 - $StartTime3) / 2))); } $StartIndex = $EndIndex - 1; } } // Load values in time range //array_pop($NextValues); } echo("Uroven dokoncena
\n"); $DbResult = $this->Database->select($this->Data['DataTable'], 'COUNT(*)', '(`level`='.$Level.') AND (`measure`='.$this->Data['Id'].')'); $Row = $DbResult->fetch_array(); echo("Vlozeno ".$Row[0]." polozek.
\n"); } } function RebuildAllMeasuresCache(): void { $Result = $this->Database->select('Measure', 'Id'); while ($Row = $Result->fetch_array()) { $Measure = new Measure($this->Database); $Measure->Load($Row['Id']); $Measure->RebuildMeasureCache(); echo('Velicina id '.$Row['Id'].' dokoncena
'); } } function ClearData(): void { $this->Database->delete($this->Data['DataTable'], '1'); } } class MeasureDataType { const Int8 = 0; const Int16 = 1; const Int32 = 2; const Int64 = 3; const Float = 4; const Double = 5; } class MeasureList extends Model { function AddItem(Measure $Measure) { $this->Database->insert('Measure', array( 'Name' => $Measure->Data['Name'], 'Description' => $Measure->Data['Description'], 'Unit' => $Measure->Data['Unit'], 'Info' => $Measure->Data['Info'], 'DataType' => $Measure->Data['DataType'], 'DataTable' => $Measure->GetDataTable(), )); $Measure->Data['Id'] = $this->Database->insert_id; $this->Database->query('CREATE TABLE `'.$Measure->GetDataTable().'` ( `Time` TIMESTAMP NOT NULL , `Measure` INT NOT NULL , `Level` TINYINT NOT NULL , `Min` '.$Measure->Data['DataType'].' NOT NULL , `Avg` '.$Measure->Data['DataType'].' NOT NULL , `Max` '.$Measure->Data['DataType'].' NOT NULL , `Continuity` BOOL NOT NULL ) ENGINE = InnoDb ;'); } function RemoveItem(Measure $Measure) { $this->Database->delete('Measure', '`Id`='.$Measure->Data['Id']); $this->Database->query('DROP TABLE `'.$Measure->GetDataTable().'`'); } }