source: trunk/Modules/Measure/Measure.php@ 69

Last change on this file since 69 was 69, checked in by chronos, 9 years ago
  • Modified: Use object oriented approach for page drawing using Application class.
  • Added: SQL updated will be automatic using UpdateTrace.php file.
  • Added: Use generic setup page at URL /setup for SQL structure update.
  • Modified: Update Common package to newer version.
File size: 18.9 KB
Line 
1<?php
2
3include_once(dirname(__FILE__).'/Page.php');
4
5class ModuleMeasure extends AppModule
6{
7 function __construct($System)
8 {
9 parent::__construct($System);
10 $this->Name = 'Measure';
11 $this->Version = '1.0';
12 $this->Creator = 'Chronos';
13 $this->License = 'GNU/GPL';
14 $this->Description = 'Measurement processing';
15 $this->Dependencies = array();
16 }
17
18 function DoStart()
19 {
20 $this->System->RegisterPage('', 'PageMain');
21 }
22}
23
24include_once('Point.php');
25
26class Measure
27{
28 var $Data;
29 var $ValueTypes;
30 var $LevelReducing;
31 var $Database;
32 var $MaxLevel;
33 var $ReferenceTime;
34 var $Differential;
35 var $DivisionCount;
36 var $PeriodTolerance;
37
38 function __construct(Database $Database)
39 {
40 $this->Id = 0;
41 $this->ValueTypes = array('min', 'avg', 'max');
42 $this->Database = &$Database;
43 $this->LevelReducing = 5;
44 $this->MaxLevel = 4;
45 $this->ReferenceTime = 0;
46 $this->Differential = 0;
47 $this->DivisionCount = 500;
48 $this->PeriodTolerance = 0.25; // 25%
49 }
50
51 function TimeSegment($Level)
52 {
53 return(pow($this->LevelReducing, $Level) * 60);
54 }
55
56 function StatTableName($Level)
57 {
58 if($Level == 0) return('data');
59 else return('data_cache');
60 }
61
62 function AlignTime($Time, $TimeSegment)
63 {
64 return(round(($Time - $this->ReferenceTime) / $TimeSegment) * $TimeSegment +
65 $this->ReferenceTime);
66 }
67
68 function Load($Id)
69 {
70 $Result = $this->Database->select('Measure', '*', '`Id`='.$Id);
71 if($Result->num_rows > 0)
72 {
73 $this->Data = $Result->fetch_array();
74 if($this->Data['Continuity'] == 0) $this->Data['ContinuityEnabled'] = 0; // non continuous
75 else $this->Data['ContinuityEnabled'] = 2; // continuous graph
76 } else die('Měřená veličina nenalezena');
77 }
78
79 function AddValue($Value)
80 {
81 $Time = time();
82
83 $Result = $this->Database->select($this->Data['DataTable'], '*', '(`Measure`='.$this->Data['Id'].') AND '.
84 '(`level`=0) ORDER BY `time` DESC LIMIT 2');
85 if($Result->num_rows == 0) {
86 // No measure value found. Simply insert new first value.
87 $this->Database->insert($this->Data['DataTable'],
88 array('min' => $Value, 'avg' => $Value, 'max' => $Value, 'level' => 0,
89 'measure' => $this->Data['Id'], 'time' => TimeToMysqlDateTime($Time),
90 'continuity' => 0));
91 } else if($Result->num_rows == 1) {
92 // One value exists. Add second value.
93 $this->Database->insert($this->Data['DataTable'],
94 array('min' => $Value, 'avg' => $Value, 'max' => $Value, 'level' => 0,
95 'measure' => $this->Data['Id'], 'time' => TimeToMysqlDateTime($Time),
96 'continuity' => 1));
97 } else {
98 // Two values already exist in measure table
99 $LastValue = $Result->fetch_array();
100 $NextToLastValue = $Result->fetch_array();
101 if(($Time - MysqlDateTimeToTime($LastValue['time'])) < (1 - $this->PeriodTolerance) * $this->Data['Period'])
102 {
103 // New value added too quickly. Need to wait for next measure period.
104 }
105 else
106 {
107 // We are near defined period and can add new value.
108 if(($Time - MysqlDateTimeToTime($LastValue['time'])) < (1 + $this->PeriodTolerance) * $this->Data['Period']) {
109 // New value added near defined measure period inside tolerance. Keep continuity.
110 $Continuity = 1;
111 } else {
112 // New value added too late after defined measure period. Stop
113 // continuity and let gap to be added.
114 $Continuity = 0;
115 }
116 if(($LastValue['avg'] == $NextToLastValue['avg']) and ($LastValue['avg'] == $Value) and
117 ($LastValue['continuity'] == 1) and ($Continuity == 1))
118 {
119 // New value is same as last value and continuity mode is enabled and
120 // continuity flag is present. Just shift forward time for last value.
121 $this->Database->update($this->Data['DataTable'], '(`time`="'.$LastValue['time'].'") AND '.
122 '(`level`=0) AND (`measure`='.$this->Data['Id'].')', array('time' => TimeToMysqlDateTime($Time)));
123 } else
124 {
125 // Last value is different or not with continuity flag. Need to add new value.
126 $this->Database->insert($this->Data['DataTable'], array('min' => $Value,
127 'avg' => $Value, 'max' => $Value, 'level' => 0, 'measure' => $this->Data['Id'], 'time' => TimeToMysqlDateTime($Time),
128 'continuity' => $Continuity));
129 }
130 }
131 }
132
133 // Update higher levels
134 for($Level = 1; $Level <= $this->MaxLevel; $Level++)
135 {
136 $TimeSegment = $this->TimeSegment($Level);
137 $EndTime = $this->AlignTime($Time, $TimeSegment);
138 //if($EndTime < $Time) $EndTime = $EndTime + $TimeSegment;
139 $StartTime = $EndTime - $TimeSegment;
140
141 // Load values in time range
142 $Values = array();
143 $Result = $this->Database->select($this->Data['DataTable'], '*', '(`time` > "'.
144 TimeToMysqlDateTime($StartTime).'") AND '.
145 '(`time` < "'.TimeToMysqlDateTime($EndTime).'") AND '.
146 '(`measure`='.$this->Data['Id'].') AND (`level`='.($Level - 1).') ORDER BY `time`');
147 while($Row = $Result->fetch_array())
148 {
149 $Row['time'] = MysqlDateTimeToTime($Row['time']);
150 $Values[] = $Row;
151 }
152 //array_pop($Values);
153
154 // Load subsidary values
155 $Values = array_merge(
156 $this->LoadLeftSideValue($Level - 1, $StartTime),
157 $Values,
158 $this->LoadRightSideValue($Level - 1, $EndTime)
159 );
160
161 $Point = $this->ComputeOneValue($StartTime, $EndTime, $Values, $Level);
162
163 $this->Database->delete($this->Data['DataTable'], '(`time` > "'.TimeToMysqlDateTime($StartTime).'") AND
164 (`time` < "'.TimeToMysqlDateTime($EndTime).'") AND (`measure`='.$this->Data['Id'].') '.
165 'AND (`level`='.$Level.')');
166 $Continuity = $Values[1]['continuity'];
167 $this->Database->insert($this->Data['DataTable'], array('level' => $Level,
168 'measure' => $this->Data['Id'], 'min' => $Point['min'],
169 'avg' => $Point['avg'], 'max' => $Point['max'], 'continuity' => $Continuity,
170 'time' => TimeToMysqlDateTime($StartTime + ($EndTime - $StartTime) / 2)));
171 }
172 }
173
174 /* Compute one value for upper time level from multiple values */
175 function ComputeOneValue($LeftTime, $RightTime, $Values, $Level)
176 {
177 $NewValue = array('min' => +1000000000000000000, 'avg' => 0, 'max' => -1000000000000000000);
178
179 // Trim outside parts
180 foreach($this->ValueTypes as $ValueType)
181 {
182 $Values[0][$ValueType] = Interpolation(
183 NewPoint($Values[0]['time'], $Values[0][$ValueType]),
184 NewPoint($Values[1]['time'], $Values[1][$ValueType]), $LeftTime);
185 }
186 $Values[0]['time'] = $LeftTime;
187 foreach($this->ValueTypes as $ValueType)
188 {
189 $Values[count($Values) - 1][$ValueType] = Interpolation(
190 NewPoint($Values[count($Values) - 2]['time'], $Values[count($Values) - 2][$ValueType]),
191 NewPoint($Values[count($Values) - 1]['time'], $Values[count($Values) - 1][$ValueType]),
192 $RightTime);
193 }
194 $Values[count($Values) - 1]['time'] = $RightTime;
195
196 // Perform computation
197 foreach($this->ValueTypes as $ValueType)
198 {
199 // Compute new value
200 for($I = 0; $I < (count($Values) - 1); $I++)
201 {
202 if($ValueType == 'avg')
203 {
204 if($Values[$I + 1]['continuity'] == $this->Data['ContinuityEnabled']);
205 else if($this->Differential == 0)
206 {
207 $NewValue[$ValueType] = $NewValue[$ValueType] + ($Values[$I + 1]['time'] - $Values[$I]['time']) *
208 (($Values[$I + 1][$ValueType] - $Values[$I][$ValueType]) / 2 + $Values[$I][$ValueType]);
209 } else {
210 $NewValue[$ValueType] = $NewValue[$ValueType] + ($Values[$I + 1]['time'] - $Values[$I]['time']) *
211 (($Values[$I + 1][$ValueType] - $Values[$I][$ValueType]) / 2);
212 }
213 }
214 else if($ValueType == 'max')
215 {
216 if($Values[$I + 1]['continuity'] == $this->Data['ContinuityEnabled'])
217 {
218 if(0 > $NewValue[$ValueType]) $NewValue[$ValueType] = 0;
219 }
220 else
221 {
222 if($this->Differential == 0)
223 {
224 if($Values[$I + 1][$ValueType] > $NewValue[$ValueType]) $NewValue[$ValueType] = $Values[$I + 1][$ValueType];
225 } else {
226 $Difference = $Values[$I + 1][$ValueType] - $Values[$I][$ValueType];
227 if($Difference > $NewValue[$ValueType]) $NewValue[$ValueType] = $Difference;
228 }
229 }
230 }
231 else if($ValueType == 'min')
232 {
233 if($Values[$I + 1]['continuity'] == $this->Data['ContinuityEnabled'])
234 {
235 if(0 < $NewValue[$ValueType]) $NewValue[$ValueType] = 0;
236 } else {
237 if($this->Differential == 0)
238 {
239 if($Values[$I + 1][$ValueType] < $NewValue[$ValueType]) $NewValue[$ValueType] = $Values[$I + 1][$ValueType];
240 } else {
241 $Difference = $Values[$I + 1][$ValueType] - $Values[$I][$ValueType];
242 if($Difference < $NewValue[$ValueType]) $NewValue[$ValueType] = $Difference;
243 }
244 }
245 }
246 }
247 $NewValue[$ValueType] = $NewValue[$ValueType];
248 }
249 //if(($RightTime - $LeftTime) > 0)
250 if($this->Data['Cumulative'] == 0)
251 {
252 $NewValue['avg'] = $NewValue['avg'] / ($RightTime - $LeftTime);
253 }
254 return($NewValue);
255 }
256
257 function GetTimeRange($Level)
258 {
259 // Get first and last time
260 $Result = $this->Database->select($this->Data['DataTable'], '*', '(`measure`='.$this->Data['Id'].') AND '.
261 '(`level`='.$Level.') ORDER BY `time` LIMIT 1');
262 if($Result->num_rows > 0)
263 {
264 $Row = $Result->fetch_array();
265 $AbsoluteLeftTime = MysqlDateTimeToTime($Row['time']);
266 } else $AbsoluteLeftTime = 0;
267
268 $Result = $this->Database->select($this->Data['DataTable'], '*', '(`measure`='.$this->Data['Id'].') AND '.
269 '(`level`='.$Level.') ORDER BY `time` DESC LIMIT 1');
270 if($Result->num_rows > 0)
271 {
272 $Row = $Result->fetch_array();
273 $AbsoluteRightTime = MysqlDateTimeToTime($Row['time']);
274 } else $AbsoluteRightTime = 0;
275
276 return(array('left' => $AbsoluteLeftTime, 'right' => $AbsoluteRightTime));
277 }
278
279 // Load one nearest value newer then given time
280 function LoadRightSideValue($Level, $Time)
281 {
282 $Result = array();
283 $DbResult = $this->Database->select($this->Data['DataTable'], '*',
284 '(`time` > "'.TimeToMysqlDateTime($Time).'") AND (`measure`='.
285 $this->Data['Id'].') AND (`level`='.$Level.') ORDER BY `time` ASC LIMIT 1');
286 if($DbResult->num_rows > 0)
287 {
288 // Found one value, simply return it
289 $Row = $DbResult->fetch_array();
290 $Row['time'] = MysqlDateTimeToTime($Row['time']);
291 return(array($Row));
292 } else
293 {
294 // Not found any value. Calculate it
295 //$Time = $Values[count($Values) - 1]['time'] + 60;
296 //array_push($Values, array('time' => $Time, 'min' => 0, 'avg' => 0, 'max' => 0, 'continuity' => 0));
297 $Result[] = array('time' => ($Time + $this->TimeSegment($Level)), 'min' => 0,
298 'avg' => 0, 'max' => 0, 'continuity' => 0);
299 $DbResult = $this->Database->select($this->Data['DataTable'], '*',
300 '(`time` < "'.TimeToMysqlDateTime($Time).'") AND (`measure`='.$this->Data['Id'].') '.
301 'AND (`level`='.$Level.') ORDER BY `time` DESC LIMIT 1');
302 if($DbResult->num_rows > 0)
303 {
304 $Row = $DbResult->fetch_array();
305 array_unshift($Result, array('time' => (MysqlDateTimeToTime($Row['time']) + 10),
306 'min' => 0, 'avg' => 0, 'max' => 0, 'continuity' => 0));
307 }
308 return($Result);
309 }
310 }
311
312 // Load one nearest value older then given time
313 function LoadLeftSideValue($Level, $Time)
314 {
315 $Result = array();
316 $DbResult = $this->Database->select($this->Data['DataTable'], '*',
317 '(`time` < "'.TimeToMysqlDateTime($Time).'") AND (`measure`='.$this->Data['Id'].') '.
318 'AND (`level`='.$Level.') ORDER BY `time` DESC LIMIT 1');
319 if($DbResult->num_rows > 0)
320 {
321 // Found one value, simply return it
322 $Row = $DbResult->fetch_array();
323 $Row['time'] = MysqlDateTimeToTime($Row['time']);
324 return(array($Row));
325 } else {
326 // Not found any value. Calculate it
327 //$Time = $Values[0]['time'] - 60;
328 //array_unshift($Values, array('time' => $Time, 'min' => 0, 'avg' => 0, 'max' => 0, 'continuity' => 0));
329 $Result[] = array('time' => ($Time - $this->TimeSegment($Level)), 'min' => 0,
330 'avg' => 0, 'max' => 0, 'continuity' => 0);
331
332 $DbResult = $this->Database->select($this->Data['DataTable'], '*',
333 '(`time` > "'.TimeToMysqlDateTime($Time).'") AND (`measure`='.$this->Data['Id'].') '.
334 'AND (`level`='.$Level.') ORDER BY `time` ASC LIMIT 1');
335 if($DbResult->num_rows > 0)
336 {
337 $Row = $DbResult->fetch_array();
338 array_push($Result, array('time' => (MysqlDateTimeToTime($Row['time']) - 10),
339 'min' => 0, 'avg' => 0, 'max' => 0, 'continuity' => 0));
340 }
341 return($Result);
342 }
343 }
344
345 function GetValues($TimeFrom, $TimeTo, $Level)
346 {
347 //$AbsoluteTime = GetTimeRange($this->DataId);
348
349// if(($TimeFrom > $AbsoluteLeftTime) and ($TimeStart < $AbsoluteRightTime) and
350// ($TimeTo > $AbsoluteLeftTime) and ($TimeTo < $AbsoluteRightTime))
351// {
352
353 // Load values in time range
354 $Result = $this->Database->select($this->Data['DataTable'], '`time`, `min`, '.
355 '`avg`, `max`, `continuity`',
356 '(`time` > "'.TimeToMysqlDateTime($TimeFrom).'") AND '.
357 '(`time` < "'.TimeToMysqlDateTime($TimeTo).'") AND '.
358 '(`measure`='.$this->Data['Id'].') AND (`level`='.$Level.') ORDER BY `time`');
359 $Values = array();
360 while($Row = $Result->fetch_array())
361 {
362 $Values[] = array('time' => MysqlDateTimeToTime($Row['time']),
363 'min' => $Row['min'], 'avg' => $Row['avg'], 'max' => $Row['max'],
364 'continuity' => $Row['continuity']);
365 }
366 // array_pop($Values);
367
368 $Points = array();
369 if(count($Values) > 0)
370 {
371 $Values = array_merge(
372 $this->LoadLeftSideValue($Level, $TimeFrom),
373 $Values,
374 $this->LoadRightSideValue($Level, $TimeTo));
375
376 $StartIndex = 0;
377 $Points = array();
378 for($I = 0; $I < $this->DivisionCount; $I++)
379 {
380 $TimeStart = $TimeFrom + (($TimeTo - $TimeFrom) / $this->DivisionCount) * $I;
381 $TimeEnd = $TimeFrom + (($TimeTo - $TimeFrom) / $this->DivisionCount) * ($I + 1);
382
383 $EndIndex = $StartIndex;
384 while($Values[$EndIndex]['time'] < $TimeEnd) $EndIndex = $EndIndex + 1;
385 $SubValues = array_slice($Values, $StartIndex, $EndIndex - $StartIndex + 1);
386 $Points[] = $this->ComputeOneValue($TimeStart, $TimeEnd, $SubValues, $Level);
387 $StartIndex = $EndIndex - 1;
388 }
389 } else $Points[] = array('min' => 0, 'avg' => 0, 'max' => 0);
390 return($Points);
391 }
392
393 function RebuildMeasureCache()
394 {
395 echo('Velicina '.$this->Data['Name']."<br>\n");
396 if($this->Data['Continuity'] == 0) $this->Data['ContinuityEnabled'] = 0; // non continuous
397 else $this->Data['ContinuityEnabled'] = 2; // continuous graph
398
399 // Clear previous items
400 $DbResult = $this->Database->select($this->Data['DataTable'], 'COUNT(*)', '(`level`>0) AND (`measure`='.$this->Data['Id'].')');
401 $Row = $DbResult->fetch_array();
402 echo("Mazu starou cache (".$Row[0]." polozek)...");
403 $this->Database->delete($this->Data['DataTable'], '(`level`>0) AND (`measure`='.$this->Data['Id'].')');
404 echo("<br>\n");
405
406 for($Level = 1; $Level <= $this->MaxLevel; $Level++)
407 {
408 echo('Uroven '.$Level."<br>\n");
409 $TimeRange = $this->GetTimeRange($Level - 1);
410 $TimeSegment = $this->TimeSegment($Level);
411 $StartTime = $this->AlignTime($TimeRange['left'], $TimeSegment) - $TimeSegment;
412 $EndTime = $this->AlignTime($TimeRange['right'], $TimeSegment);
413 $BurstCount = 500;
414 echo('For 0 to '.round(($EndTime - $StartTime) / $TimeSegment / $BurstCount)."<br>\n");
415 for($I = 0; $I <= round(($EndTime - $StartTime) / $TimeSegment / $BurstCount); $I++)
416 {
417 echo($I.' ');
418 $StartTime2 = $StartTime + $I * $BurstCount * $TimeSegment;
419 $EndTime2 = $StartTime + ($I + 1) * $BurstCount * $TimeSegment;
420 $Values = array();
421 $DbResult = $this->Database->select($this->Data['DataTable'], '*', '(`time` > "'.TimeToMysqlDateTime($StartTime2).'") AND (`time` < "'.
422 TimeToMysqlDateTime($EndTime2).'") AND (`measure`='.$this->Data['Id'].') AND (`level`='.($Level - 1).') ORDER BY `time`');
423 while($Row = $DbResult->fetch_array())
424 {
425 $Row['time'] = MysqlDateTimeToTime($Row['time']);
426 $Values[] = $Row;
427 }
428
429 if(count($Values) > 0)
430 {
431 $Values = array_merge(
432 $this->LoadLeftSideValue($Level - 1, $StartTime2),
433 $Values,
434 $this->LoadRightSideValue($Level - 1, $EndTime2));
435
436 $StartIndex = 0;
437 for($B = 0; $B < $BurstCount; $B++)
438 {
439 echo('.');
440 $StartTime3 = $StartTime2 + (($EndTime2 - $StartTime2) / $BurstCount) * $B;
441 $EndTime3 = $StartTime2 + (($EndTime2 - $StartTime2) / $BurstCount) * ($B + 1);
442
443 $EndIndex = $StartIndex;
444 while($Values[$EndIndex]['time'] < $EndTime3) $EndIndex = $EndIndex + 1;
445 $SubValues = array_slice($Values, $StartIndex, $EndIndex - $StartIndex + 1);
446 if(count($SubValues) > 2)
447 {
448 $Point = $this->ComputeOneValue($StartTime3, $EndTime3, $SubValues, $Level);
449 $Continuity = $SubValues[1]['continuity'];
450 $this->Database->insert($this->Data['DataTable'], array('level' => $Level, 'measure' => $this->Data['Id'],
451 'min' => $Point['min'], 'avg' => $Point['avg'], 'max' => $Point['max'],
452 'continuity' => $Continuity, 'time' => TimeToMysqlDateTime($StartTime3 + ($EndTime3 - $StartTime3) / 2)));
453 }
454 $StartIndex = $EndIndex - 1;
455 }
456 }
457 // Load values in time range
458 //array_pop($NextValues);
459 }
460 echo("Uroven dokoncena<br>\n");
461 $DbResult = $this->Database->select($this->Data['DataTable'], 'COUNT(*)',
462 '(`level`='.$Level.') AND (`measure`='.$this->Data['Id'].')');
463 $Row = $DbResult->fetch_array();
464 echo("Vlozeno ".$Row[0]." polozek.<br>\n");
465 }
466 }
467
468 function RebuildAllMeasuresCache()
469 {
470 $Result = $this->Database->select('measure', 'Id');
471 while($Row = $Result->fetch_array())
472 {
473 $Measure = new Measure();
474 $Measure->Load($Row['Id']);
475 $Measure->RebuildMeasureCache();
476 echo('Velicina id '.$Row['Id'].' dokoncena<br/>');
477 }
478 }
479
480 function InitMeasureDataTable()
481 {
482 $this->Database->query('CREATE TABLE `data_'.$this->Data['Name'].'` (
483 `Time` TIMESTAMP NOT NULL ,
484 `Avg` '.$this->Data['DataType'].' NOT NULL ,
485 `Continuity` BOOL NOT NULL
486 ) ENGINE = MYISAM ;');
487
488 $this->Database->query('CREATE TABLE `data_'.$this->Data['Name'].'_cache` (
489`Time` TIMESTAMP NOT NULL ,
490`Level` TINYINT NOT NULL ,
491`Min` '.$this->Data['DataType'].' NOT NULL ,
492`Avg` '.$this->Data['DataType'].' NOT NULL ,
493`Max` '.$this->Data['DataType'].' NOT NULL ,
494`Continuity` BOOL NOT NULL
495) ENGINE = MYISAM ;');
496 }
497}
Note: See TracBrowser for help on using the repository browser.