Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
process.php
1<?
10
11use Bitrix\Main;
12
13abstract class Process
14{
15 const JUST_SHOW_STAGES = false;
16 const MIN_TIME_LIMIT = 5;
17 const DEBUG_MODE = false;
18 const DEBUG_FOLDER = '%BX_ROOT%/tmp/';
19 const DEBUG_FILE = '%SESSION_KEY%_process.txt';
20 const LOCK_FILE = '%SESSION_KEY%_lock';
21
22 const CALLBACK_TYPE_MANUAL = 'manual';
23 const CALLBACK_TYPE_QUOTA = 'quota';
24
25 protected $stages = array();
26 protected $stagesByCode = array();
27 protected $stage = 0;
28 protected $step = 0;
29 protected $data = array();
30 protected $time = 0;
31 protected $timeLimit = 20; // in seconds
32 protected $sessionKey = 'long_process';
33 protected $useLock = false;
34
35 protected $options = array();
36
37 public function __construct($options = array())
38 {
39 if (isset($options['INITIAL_TIME']))
40 $this->time = (int)$options['INITIAL_TIME'];
41 else
42 $this->time = time();
43
44 $this->useLock = (bool)($options['USE_LOCK'] ?? null);
45 $this->options = $options;
46
47 $this->restore();
48
49 if (isset($options['STEP']) && $options['STEP'] == 0)
50 {
51 $this->reset();
52 }
53
54 $this->logMessage('#############################', false);
55 $this->logMessage('HIT STARTED '.$this->getTimeStampString(), false);
56
57 $timeLimit = (int)($options['TIME_LIMIT'] ?? 0);
58 if ($timeLimit > 0)
59 {
61 }
62
63 $this->saveStartTime();
64 $this->saveMemoryPeak();
65 }
66
67 public function addStage($params)
68 {
69 if (empty($params['CODE']) || empty($params['CALLBACK']))
70 {
71 throw new Main\SystemException('Not enought params to add stage');
72 }
73
74 $ss = (int)($params['STEP_SIZE'] ?? 0);
75 $order = count($this->stages);
76 $type = (string)($params['TYPE'] ?? '');
77 if ($type === '')
78 {
79 $type = static::CALLBACK_TYPE_MANUAL;
80 }
81
82 $beforeCallback = (string)($params['ON_BEFORE_CALLBACK'] ?? '');
83 if ($beforeCallback === '')
84 {
85 $beforeCallback = false;
86 }
87 $afterCallback = (string)($params['ON_AFTER_CALLBACK'] ?? '');
88 if ($afterCallback === '')
89 {
90 $afterCallback = false;
91 }
92
93 $this->stages[] = [
94 'STEP_SIZE' => $ss ?: 1,
95 'PERCENT' => (int)($params['PERCENT'] ?? 0),
96 'CODE' => $params['CODE'],
97 'ORDER' => $order,
98 'TYPE' => $type,
99 'CALLBACK' => $params['CALLBACK'],
100 'SUBPERCENT_CALLBACK' => $params['SUBPERCENT_CALLBACK'] ?? null,
101 'ON_BEFORE_CALLBACK' => $beforeCallback,
102 'ON_AFTER_CALLBACK' => $afterCallback,
103 ];
104 $this->stagesByCode[$params['CODE']] =& $this->stages[count($this->stages) - 1];
105 }
106
107 public function restore()
108 {
109 if(!isset($_SESSION[$this->sessionKey]['STAGE']))
110 $_SESSION[$this->sessionKey]['STAGE'] = 0;
111
112 if(!isset($_SESSION[$this->sessionKey]['STEP']))
113 $_SESSION[$this->sessionKey]['STEP'] = 0;
114
115 if(!isset($_SESSION[$this->sessionKey]['DATA']))
116 $_SESSION[$this->sessionKey]['DATA'] = array();
117
118 $this->stage =& $_SESSION[$this->sessionKey]['STAGE'];
119 $this->step =& $_SESSION[$this->sessionKey]['STEP'];
120 $this->data =& $_SESSION[$this->sessionKey]['DATA'];
121 }
122
123 // reset current condition
124 public function reset()
125 {
126 $this->stage = 0;
127 $this->step = 0;
128 $this->data = array();
129
130 $this->clearLogFile();
131
132 $this->saveStartTime();
133 $this->saveMemoryPeak();
134 }
135
136 public function performStage()
137 {
138 return $this->performIteration();
139 }
140
141 public function performIteration()
142 {
143 if($this->stage == 0 && $this->step == 0)
144 {
145 $this->lockProcess();
146
147 if(static::DEBUG_MODE)
148 {
149 $logDir = $this->getLogFileDir();
150 if(!file_exists($logDir))
151 mkdir($logDir, 755, true);
152
153 $this->logMessage('PROCESS STARTED, STAGE '.$this->stages[0]['CODE']);
154 }
155 }
156
158
159 if(!isset($this->stages[$this->stage]))
160 throw new Main\SystemException('No more stages to perform');
161
162 if(self::JUST_SHOW_STAGES)
163 $this->nextStage();
164 else
165 {
167
168 if($this->stages[$stage]['ON_BEFORE_CALLBACK'] != false)
169 call_user_func(array($this, $this->stages[$stage]['ON_BEFORE_CALLBACK']));
170
171 if($this->stages[$this->stage]['TYPE'] == static::CALLBACK_TYPE_MANUAL)
172 call_user_func(array($this, $this->stages[$this->stage]['CALLBACK']));
173 elseif($this->stages[$this->stage]['TYPE'] == static::CALLBACK_TYPE_QUOTA)
174 {
175 while($this->checkQuota())
176 {
177 $result = call_user_func(array($this, $this->stages[$this->stage]['CALLBACK']));
178 $this->nextStep();
179
180 if($result)
181 break;
182 }
183
184 if($result)
185 $this->nextStage();
186 }
187
188 if($this->stages[$stage]['ON_AFTER_CALLBACK'] != false)
189 call_user_func(array($this, $this->stages[$stage]['ON_AFTER_CALLBACK']));
190 }
191
193 $percent = $this->getPercent();
194
195 $this->saveMemoryPeak();
196
197 $this->logMessage('HIT ENDED '.$this->getTimeStampString(), false);
198
199 if($percent == 100)
200 $this->unLockProcess();
201
202 return $percent;
203 }
204
208
209 public function setStepSize($code, $stepSize)
210 {
211 if(!isset($this->stagesByCode[$code]))
212 throw new Main\SystemException('Unknown stage code passed');
213
214 if(($stepSize = intval($stepSize)) <= 0)
215 throw new Main\SystemException('Bad step size passed');
216
217 $this->stagesByCode[$code]['STEP_SIZE'] = $stepSize;
218 }
219
220 // move to next stage
221 public function nextStage()
222 {
223 $this->stage++;
224 $this->step = 0;
225
226 if ($this->stage < count($this->stages))
227 {
228 $stageCode = $this->stages[$this->stage]['CODE'] ?? 'UNDEFINED for '.$this->stage;
229 }
230 else
231 {
232 $stageCode = 'FINAL';
233 }
234
235 $this->logMessage(
236 '### NEXT STAGE >>> ' . $stageCode
237 . ' in ' . $this->getElapsedTimeString()
238 . ', mem peak = ' . $this->getMemoryPeakString() . ' mb'
239 );
240 }
241
242 // move to next step
243 public function nextStep()
244 {
245 $this->step++;
246 }
247
248 public function isStage($code)
249 {
250 return $this->stages[$this->stage]['CODE'] == $code;
251 }
252
253 protected function stageCompare($code, $way)
254 {
255 $currIndex = $this->stages[$this->stage]['ORDER'];
256 $stageIndex = $this->stagesByCode[$code]['ORDER'];
257
258 if($currIndex == $stageIndex) return true;
259
260 if($way) // gt
261 return $currIndex > $stageIndex;
262 else // lt
263 return $currIndex < $stageIndex;
264 }
265
266 // $this->stage <= $code
267 public function stageLT($code)
268 {
269 return $this->stageCompare($code, false);
270 }
271
272 // $code <= $this->stage
273 public function stageGT($code)
274 {
275 return $this->stageCompare($code, true);
276 }
277
278 public function setStage($stage)
279 {
280 foreach($this->stages as $sId => $info)
281 {
282 if($info['CODE'] == $stage)
283 {
284 $this->stage = $sId;
285 $this->step = 0;
286 break;
287 }
288 }
289 }
290
291 public function onBeforePerformIteration()
292 {
293 }
294
295 public function onAfterPerformIteration()
296 {
297 }
298
299 public function getStageCode()
300 {
301 return $this->stages[$this->stage]['CODE'] ?? '';
302 }
303 public function getCurrStageIndex()
304 {
305 return $this->stage;
306 }
307
308 public function getStep()
309 {
310 return $this->step;
311 }
312
313 public function getStage($code)
314 {
315 return $this->stagesByCode[$code];
316 }
317
318 public function getCurrStageStepSize()
319 {
320 return $this->stages[$this->stage]['STEP_SIZE'];
321 }
322
326
327 public function getStagePercent($sNum = false)
328 {
329 if ($sNum === false)
330 {
331 $stage = $this->stages[$this->stage]['PERCENT'] ?? 0;
332 }
333 else
334 {
335 if (is_numeric($sNum))
336 {
337 $stage = $this->stages[$sNum]['PERCENT'] ?? 0;
338 }
339 else
340 {
341 $stage = $this->stagesByCode[$sNum]['PERCENT'] ?? 0;
342 }
343 }
344
345 return $stage ?: 0;
346 }
347
348 public function getPercentBetween($codeFrom, $codeTo)
349 {
350 return $this->getStagePercent($codeTo) - $this->getStagePercent($codeFrom);
351 }
352
353 public function getPercentFromToCurrent($codeFrom){
354 return $this->getStagePercent($this->stage - 1) - $this->getStagePercent($codeFrom);
355 }
356
357 public function getCurrentPercentRange()
358 {
359 return $this->getStagePercent($this->stage) - $this->getStagePercent($this->stage - 1);
360 }
361
362 public function getPercent()
363 {
364 $percent = $this->stage > 0 ? $this->stages[$this->stage - 1]['PERCENT'] : 0;
365
366 $addit = 0;
367 $cb = (string)($this->stages[$this->stage]['SUBPERCENT_CALLBACK'] ?? '');
368 if ($cb !== '' && method_exists($this, $cb))
369 {
370 $addit = $this->$cb();
371 }
372
373 return $percent + $addit;
374 }
375
376 public function calcSubPercent($range)
377 {
378 if(!$range) return 0;
379
380 return round(($this->step / $range)*($this->getStagePercent($this->stage) - $this->getStagePercent($this->stage - 1)));
381 }
382
383 public function getSubPercentByTotalAndDone($total, $done = 0)
384 {
385 if(!$done || !$total)
386 return 0;
387
388 $pRange = $this->getCurrentPercentRange();
389 $part = round($pRange * ($done / $total));
390
391 return $part >= $pRange ? $pRange : $part;
392 }
393
397
398 public function checkQuota()
399 {
400 return (time() - $this->time) < $this->timeLimit;
401 }
402
403 public function setTimeLimit($timeLimit)
404 {
405 $timeLimit = (int)$timeLimit;
406 if ($timeLimit > 0)
407 {
408 $this->timeLimit = max($timeLimit, static::MIN_TIME_LIMIT);
409 }
410 }
411
412 public function getMemoryPeak()
413 {
414 return $this->data['memory_peak'];
415 }
416
417 protected function saveStartTime()
418 {
419 if (!isset($this->data['process_time']))
420 {
421 $this->data['process_time'] = time();
422 }
423 }
424
425 protected function saveMemoryPeak()
426 {
427 $mp = memory_get_peak_usage(false);
428
429 if (!isset($this->data['memory_peak']))
430 {
431 $this->data['memory_peak'] = $mp;
432 }
433 else
434 {
435 if ($this->data['memory_peak'] < $mp)
436 {
437 $this->data['memory_peak'] = $mp;
438 }
439 }
440 }
441
445
446 public function clearLogFile()
447 {
448 $logDir = $this->getLogFileDir();
449
450 if(!Main\IO\Directory::isDirectoryExists($logDir))
451 Main\IO\Directory::createDirectory($logDir);
452
453 $logFile = $this->getLogFilePath();
454
455 Main\IO\File::putFileContents($logFile, '');
456 }
457
458 public function getLogFileDir()
459 {
460 return $_SERVER['DOCUMENT_ROOT'].'/'.str_replace('%BX_ROOT%', BX_ROOT, self::DEBUG_FOLDER);
461 }
462
463 public function getLogFilePath()
464 {
465 return $this->getLogFileDir().str_replace('%SESSION_KEY%', $this->sessionKey, self::DEBUG_FILE);
466 }
467
468 public function logMessage($message = '', $addTimeStamp = true)
469 {
470 if(!static::DEBUG_MODE || !mb_strlen($message))
471 return;
472
473 file_put_contents(
474 $this->getLogFilePath(),
475 ($addTimeStamp ? $this->getTimeStampString().' ' : '').$message.PHP_EOL,
476 FILE_APPEND
477 );
478 }
479
480 public function logMemoryUsage()
481 {
482 $this->logMessage('MEMORY USAGE: '.(memory_get_usage(false) / (1024 * 1024)).' MB', false);
483 }
484
485 public function logFinalResult()
486 {
487 $this->logMessage('ALL DONE!');
488
489 $this->logMessage('TOTAL PROCESS TIME: '.$this->getElapsedTimeString(), false);
490 $this->logMessage('MEMORY PEAK (mb): '.$this->getMemoryPeakString(), false);
491 }
492
496
497 public function getLockFilePath()
498 {
499 return $this->getLogFileDir().str_replace('%SESSION_KEY%', $this->sessionKey, self::LOCK_FILE);
500 }
501
502 public function lockProcess()
503 {
504 if(!$this->useLock)
505 return;
506
507 file_put_contents($this->getLockFilePath(), '1');
508 }
509
510 public function unLockProcess()
511 {
512 if(!$this->useLock)
513 return;
514
515 $file = $this->getLockFilePath();
516 if(file_exists($file))
517 unlink($file);
518 }
519
520 public function checkProcessLocked()
521 {
522 return $this->useLock && file_exists($this->getLockFilePath());
523 }
524
528
529 protected function getHitTime()
530 {
531 return time() - $this->time;
532 }
533
534 protected function getProcessTime()
535 {
536 return time() - $this->data['process_time'];
537 }
538
539 protected function getProcessTimeString()
540 {
541 return $this->getTimeString($this->getProcessTime());
542 }
543
544 protected function getHitTimeString()
545 {
546 return $this->getTimeString($this->getHitTime());
547 }
548
549 protected function getElapsedTimeString()
550 {
551 return $this->getProcessTimeString();
552 }
553
554 protected function getTimeString($time = 0)
555 {
556 $h = floor($time / 3600);
557 $m = floor(($time - $h * 3600) / 60);
558 $s = $time - $h * 3600 - $m * 60;
559
560 if(mb_strlen($m) == 1)
561 $m = '0'.$m;
562
563 if(mb_strlen($s) == 1)
564 $s = '0'.$s;
565
566 return $h.':'.$m.':'.$s;
567 }
568
569 protected function getTimeStampString()
570 {
571 return '['.date('H:i:s').']';
572 }
573
574 protected function getMemoryPeakString()
575 {
576 return $this->getMemoryPeak() / 1048576;
577 }
578
582
583 public function getData()
584 {
585 return $this->data;
586 }
587
588 // special case for array
589 protected function getBlock($from)
590 {
592
593 for($i = 0; $i < $step; $i++)
594 next($from);
595
596 $block = array();
597 $hadSmth = false;
598
599 for($i = $step; $i <= $step + $this->getCurrStageStepSize(); $i++)
600 {
601 $code = key($from);
602
603 if(!isset($code))
604 {
605 break;
606 }
607
608 $elem = current($from);
609 next($from);
610
611 $hadSmth = true;
612 $block[$code] = $elem;
613
614 $this->nextStep();
615 }
616
617 if(!$hadSmth)
618 {
619 $this->nextStage();
620 return false;
621 }
622
623 return $block;
624 }
625}
static isDirectoryExists($path)
getStagePercent($sNum=false)
Percentage.
Definition process.php:327
getSubPercentByTotalAndDone($total, $done=0)
Definition process.php:383
setStepSize($code, $stepSize)
Staging.
Definition process.php:209
__construct($options=array())
Definition process.php:37
logMessage($message='', $addTimeStamp=true)
Definition process.php:468
getHitTime()
Diagnostics tools.
Definition process.php:529
getPercentBetween($codeFrom, $codeTo)
Definition process.php:348