taskman_atf/task.inc.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();
}
}