309 lines
6.6 KiB
PHP
309 lines
6.6 KiB
PHP
|
<?php
|
||
|
namespace ATF;
|
||
|
|
||
|
use Exception;
|
||
|
use Amp;
|
||
|
|
||
|
class Cmd
|
||
|
{
|
||
|
public string $module;
|
||
|
public string $func;
|
||
|
|
||
|
function __construct(string $module, string $func)
|
||
|
{
|
||
|
$this->module = $module;
|
||
|
$this->func = $func;
|
||
|
}
|
||
|
|
||
|
function __toString()
|
||
|
{
|
||
|
return $this->module.':'.$this->func;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class Task
|
||
|
{
|
||
|
const CODE_MESSAGE = 0;
|
||
|
const CODE_EXCEPTION = 1;
|
||
|
const CODE_WARN = 2;
|
||
|
const CODE_NSTART = 124;
|
||
|
const CODE_STUCK = 125;
|
||
|
const CODE_GONE = 126;
|
||
|
const CODE_HUNG = 127;
|
||
|
const CODE_DONE = 128;
|
||
|
|
||
|
static function code2string(int $code) : string
|
||
|
{
|
||
|
switch($code)
|
||
|
{
|
||
|
case self::CODE_MESSAGE:
|
||
|
return "message";
|
||
|
case self::CODE_EXCEPTION:
|
||
|
return "exception";
|
||
|
case self::CODE_WARN:
|
||
|
return "warning";
|
||
|
case self::CODE_STUCK:
|
||
|
return "stuck";
|
||
|
case self::CODE_NSTART:
|
||
|
return "nstart";
|
||
|
case self::CODE_GONE:
|
||
|
return "gone";
|
||
|
case self::CODE_HUNG:
|
||
|
return "hung";
|
||
|
case self::CODE_DONE:
|
||
|
return "done";
|
||
|
}
|
||
|
return "???";
|
||
|
}
|
||
|
|
||
|
public ?Plan $plan = null;
|
||
|
|
||
|
//async execution id
|
||
|
public int $run_id = 0;
|
||
|
//'true' if async coroutine is resolved
|
||
|
public bool $run_status = false;
|
||
|
//async execution exception if any
|
||
|
public ?object $run_error = null;
|
||
|
|
||
|
//attached device
|
||
|
public ?string $device = null;
|
||
|
|
||
|
public int $attempts = 1;
|
||
|
//signal to dispatcher to re-start execution of the task
|
||
|
public bool $need_reschedule = false;
|
||
|
|
||
|
public Cmd $cmd;
|
||
|
public array $args = array();
|
||
|
|
||
|
public string $title = '';
|
||
|
|
||
|
public float $start_time = 0;
|
||
|
public float $reset_time = 0;
|
||
|
|
||
|
public float $last_progress = 0;
|
||
|
public int $last_done_arg_idx = -1;
|
||
|
public float $last_alive_check_time = 0;
|
||
|
public float $last_stuck_check_time = 1;
|
||
|
public float $last_ext_status_item_time = 0;
|
||
|
//array of tuples [code, msg]
|
||
|
public array $status_codes = array();
|
||
|
|
||
|
function __construct(Cmd $cmd, array $args, $title = '')
|
||
|
{
|
||
|
$this->cmd = $cmd;
|
||
|
$this->args = $args;
|
||
|
$this->title = $title;
|
||
|
}
|
||
|
|
||
|
function getCmd() : Cmd
|
||
|
{
|
||
|
return $this->cmd;
|
||
|
}
|
||
|
|
||
|
function getCmdArgs() : array
|
||
|
{
|
||
|
return $this->args;
|
||
|
}
|
||
|
|
||
|
function getProgress() : float
|
||
|
{
|
||
|
if($this->last_progress == 0 && $this->isDone())
|
||
|
return 1;
|
||
|
return $this->last_progress;
|
||
|
}
|
||
|
|
||
|
static function isProblemCode($code) : bool
|
||
|
{
|
||
|
return $code > self::CODE_MESSAGE && $code < self::CODE_DONE;
|
||
|
}
|
||
|
|
||
|
static function isFatalCode($code) : bool
|
||
|
{
|
||
|
return
|
||
|
$code == Task::CODE_STUCK ||
|
||
|
$code == Task::CODE_NSTART ||
|
||
|
$code == Task::CODE_GONE ||
|
||
|
$code == Task::CODE_HUNG ||
|
||
|
$code == Task::CODE_EXCEPTION;
|
||
|
}
|
||
|
|
||
|
function hasFatalProblem() : bool
|
||
|
{
|
||
|
return $this->getLastFatalProblem() != 0;
|
||
|
}
|
||
|
|
||
|
function getLastFatalProblem() : int
|
||
|
{
|
||
|
for($i=sizeof($this->status_codes);$i-- > 0;)
|
||
|
{
|
||
|
$item = $this->status_codes[$i];
|
||
|
if(self::isFatalCode($item[0]))
|
||
|
return $item[0];
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function hasStatusCode($code)
|
||
|
{
|
||
|
foreach($this->status_codes as $item)
|
||
|
{
|
||
|
if($item[0] === $code)
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function isDone()
|
||
|
{
|
||
|
return $this->hasStatusCode(Task::CODE_DONE);
|
||
|
}
|
||
|
|
||
|
function addStatusCode($code, $msg = '')
|
||
|
{
|
||
|
//plan stores history of all status codes
|
||
|
$this->plan->addStatusCode($code, $msg);
|
||
|
|
||
|
$this->status_codes[] = array($code, $msg);
|
||
|
}
|
||
|
|
||
|
function onFatalProblem()
|
||
|
{
|
||
|
if($this->attempts < 3)
|
||
|
$this->reschedule();
|
||
|
}
|
||
|
|
||
|
function onProgress($jzon)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
$data = jzon_parse(trim(str_replace('\"', '"', $jzon)));
|
||
|
if(isset($data['p']))
|
||
|
$this->last_progress = floatval(str_replace(',', '.', $data['p']));
|
||
|
if(isset($data['arg_idx']))
|
||
|
$this->last_done_arg_idx = (int)$data['arg_idx'];
|
||
|
}
|
||
|
catch(Exception $e)
|
||
|
{
|
||
|
echo $e->getMessage() . "\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function _resetCheckTimes()
|
||
|
{
|
||
|
$this->reset_time = microtime(true);
|
||
|
$this->last_alive_check_time = $this->reset_time;
|
||
|
$this->last_stuck_check_time = $this->reset_time;
|
||
|
$this->last_ext_status_item_time = 0;
|
||
|
}
|
||
|
|
||
|
function reschedule()
|
||
|
{
|
||
|
++$this->attempts;
|
||
|
$this->device = null;
|
||
|
$this->need_reschedule = true;
|
||
|
}
|
||
|
|
||
|
function start()
|
||
|
{
|
||
|
$this->_resetCheckTimes();
|
||
|
$this->status_codes = array();
|
||
|
$this->start_time = time();
|
||
|
}
|
||
|
|
||
|
function getDuration()
|
||
|
{
|
||
|
return time() - $this->start_time;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class TestLevelsTask extends Task
|
||
|
{
|
||
|
public $arg_offset = 0;
|
||
|
|
||
|
private $rseeds = array();
|
||
|
private $seed = null;
|
||
|
private $timescale = null;
|
||
|
|
||
|
function __construct(Cmd $cmd, array $args, $with_rseeds = false, $seed = null, $timescale = null, $title = '')
|
||
|
{
|
||
|
$level_ids = array();
|
||
|
//TODO: should not really be here
|
||
|
if($with_rseeds)
|
||
|
{
|
||
|
foreach($args as $item)
|
||
|
{
|
||
|
$level_ids[] = $item[0];
|
||
|
$this->rseeds[] = $item[1];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
$level_ids = $args;
|
||
|
|
||
|
$this->seed = $seed;
|
||
|
$this->timescale = $timescale;
|
||
|
|
||
|
parent::__construct($cmd, $level_ids, $title);
|
||
|
}
|
||
|
|
||
|
function getCmdArgs() : array
|
||
|
{
|
||
|
$args = array();
|
||
|
|
||
|
if($this->rseeds)
|
||
|
{
|
||
|
foreach($this->args as $idx => $arg)
|
||
|
{
|
||
|
$args[] = $arg;
|
||
|
$args[] = $this->rseeds[$idx];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
$args = $this->args;
|
||
|
|
||
|
//appending arg. offset as a first argument
|
||
|
array_unshift($args, ''.$this->arg_offset);
|
||
|
array_unshift($args, '--offset');
|
||
|
|
||
|
if($this->rseeds)
|
||
|
{
|
||
|
array_unshift($args, 'true');
|
||
|
array_unshift($args, '--levels-with-rseed');
|
||
|
}
|
||
|
|
||
|
if($this->seed !== null)
|
||
|
{
|
||
|
array_unshift($args, ''.$this->seed);
|
||
|
array_unshift($args, '--seed');
|
||
|
}
|
||
|
|
||
|
if($this->timescale !== null)
|
||
|
{
|
||
|
array_unshift($args, ''.$this->timescale);
|
||
|
array_unshift($args, '--timescale');
|
||
|
}
|
||
|
|
||
|
return $args;
|
||
|
}
|
||
|
|
||
|
function onFatalProblem()
|
||
|
{
|
||
|
$start_arg = isset($this->args[$this->arg_offset]) ? $this->args[$this->arg_offset] : '?';
|
||
|
$done_arg = isset($this->args[$this->last_done_arg_idx]) ? $this->args[$this->last_done_arg_idx] : '?';
|
||
|
|
||
|
$this->plan->post("Handling error, progress:".round($this->getProgress(),2).", last done arg:{$done_arg}, start arg:{$start_arg}, arg offset:{$this->arg_offset}, args:".sizeof($this->args)." *$this->device*");
|
||
|
|
||
|
//we're allowed to skip only in case of exceptions
|
||
|
if($this->getLastFatalProblem() == Task::CODE_EXCEPTION)
|
||
|
{
|
||
|
if($this->last_done_arg_idx > $this->arg_offset)
|
||
|
$this->arg_offset = $this->last_done_arg_idx;
|
||
|
++$this->arg_offset;
|
||
|
}
|
||
|
|
||
|
//let's reschedule the task if it still makes sense
|
||
|
if($this->arg_offset < sizeof($this->args))
|
||
|
$this->reschedule();
|
||
|
}
|
||
|
}
|
||
|
|