taskman_atf/atf.inc.php

537 lines
15 KiB
PHP

<?php
namespace ATF;
use Exception;
use Amp;
use taskman;
function host_exec_async(string $atf_host, string $cmd, $opts = 0, $timeout = 10) : Amp\Promise
{
return Amp\call(function() use($atf_host, $cmd, $opts, $timeout) {
$timeout_cmd = "timeout -k 5s $timeout $cmd";
$res = yield taskman\deploy_ssh_exec_async($atf_host, $timeout_cmd, $opts);
list($status, $lines) = current($res);
//in case of timeout we set status to false explicitely
if($status === 124)
$status = false;
return array($status, $lines);
});
}
function host_exec(string $atf_host, string $cmd, $opts = 0, $timeout = 10)
{
$timeout_cmd = "timeout -k 5s $timeout $cmd";
$res = taskman\deploy_ssh_exec($atf_host, $timeout_cmd, $opts);
list($status, $lines) = current($res);
//in case of timeout we set status to false explicitely
if($status === 124)
$status = false;
return array($status, $lines);
}
function host_get_file(string $atf_host, string $file_name)
{
$file_data = current(taskman\deploy_get_file($atf_host, $file_name));
return $file_data;
}
function host_put_file(string $atf_host, string $local_path, string $remote_path)
{
taskman\deploy_rsync($atf_host, $local_path, $remote_path);
}
function host_put_file_async(string $atf_host, string $local_path, string $remote_path)
{
return taskman\deploy_rsync_async($atf_host, $local_path, $remote_path);
}
function spread(array $items, $num)
{
$k = sizeof($items) / $num;
$res = array();
for($i=0;$i<$num;++$i)
{
$tmp = array_slice($items, (int)floor($k*$i), (int)ceil($k));
$res[$i] = $tmp;
}
return $res;
}
function slice(array $items, $max)
{
$sliced = array();
$offset = 0;
while(true)
{
$tmp = array_slice($items, $offset, $max);
if(!$tmp)
break;
$sliced[] = $tmp;
$offset += $max;
}
return $sliced;
}
function _trim($txt, $max_len)
{
return strlen($txt) > $max_len ? substr($txt, 0, $max_len) . "..." : $txt;
}
function _trim_start($txt, $max_len)
{
return strlen($txt) > $max_len ? "..." . substr($txt, strlen($txt) - $max_len) : $txt;
}
function _trim_lines($txt, $max_lines)
{
$lines = explode("\n", $txt, $max_lines+1);
$res = '';
for($i=0;$i<sizeof($lines) && $i<$max_lines;++$i)
$res .= $lines[$i] . "\n";
return $res;
}
function _try_lz4_replay(string $txt)
{
if(!$txt)
return $txt;
//let's check if it's already archieved
if(strlen($txt) > 2 && $txt[1] === ':')
return $txt;
if(!function_exists('lz4_compress'))
return $txt;
return "4:" . base64_encode(lz4_compress(base64_decode($txt)));
}
function log($msg)
{
echo date("Y-m-d H:i:s") . " " . $msg . "\n";
}
function err($msg)
{
taskman\stderr(date("Y-m-d H:i:s") . " " . $msg . "\n");
}
function _retry($max_tries, $func)
{
for($i=0;$i<$max_tries;++$i)
{
try
{
$func();
return;
}
catch(Exception $e)
{
if(($i+1) == $max_tries)
throw $e;
}
sleep(1);
}
}
function adb_reboot(string $atf_host)
{
host_exec($atf_host, "killall adb", DEPLOY_OPT_ERR_OK);
_retry(3, function() use($atf_host) {
host_exec($atf_host, "%{adb}% kill-server");
});
host_exec($atf_host, "%{adb}% start-server");
}
function reboot_devices(string $atf_host, array $devices, int $wait_after = 40)
{
log("Rebooting devices...");
//let's reboot devices
$ces = array();
foreach($devices as $device)
{
$ces[] = Amp\call(function() use($atf_host, $device, $wait_after) {
yield host_exec_async($atf_host, "%{adb}% -s $device reboot", DEPLOY_OPT_ERR_OK);
yield Amp\delay($wait_after*1000);
});
}
Amp\Promise\wait(Amp\Promise\all($ces));
}
function get_ext_status_async(string $atf_host, string $device, int $timeout = 20) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device, $timeout) {
$file_data = yield device_pull_file_async($atf_host, $device, "atf_status.js", $timeout, /*throw on timeout*/true);
if($file_data === null)
return null;
$file_data = preg_replace('/[[:cntrl:]]/', '', $file_data);
return json_decode($file_data, true);
});
}
function start_ext_cmd_on_device_async(string $atf_host, string $device, Cmd $cmd, array $cmd_args) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device, $cmd, $cmd_args) {
yield device_del_file_async($atf_host, $device, 'atf_status.js', DEPLOY_OPT_ERR_OK);
yield host_exec_async($atf_host, "%{adb}% -s $device shell am force-stop %{package_id}%", DEPLOY_OPT_ERR_OK, 30);
yield update_ext_cmd_async($atf_host, $device, $cmd, $cmd_args);
yield host_exec_async($atf_host, "%{adb}% -s $device shell am start -n %{package_id}%/%{activity}%", 0, 30);
});
}
function get_devices(string $atf_host, bool $extended = false)
{
$devices = array();
list($_, $lines) = host_exec($atf_host, "%{adb}% devices -l", DEPLOY_OPT_SILENT);
foreach($lines as $line)
{
if(preg_match('~^(\S+)\s+device(.*)~', $line, $m))
{
if($extended)
{
$devices[$m[1]] = array();
$items = explode(" ", trim($m[2]));
foreach($items as $item)
{
list($k, $v) = explode(":", $item);
$devices[$m[1]][$k] = $v;
}
}
else
$devices[] = $m[1];
}
}
return $devices;
}
function update_ext_cmd_async(string $atf_host, string $device, Cmd $cmd, array $args)
{
$ext_cmd = array(
"device_id" => $device,
"module" => $cmd->module,
"func" => $cmd->func,
"args" => $args,
);
return device_put_file_async($atf_host, $device, json_encode($ext_cmd), 'atf_cmd.js');
}
function package_dir(string $atf_host)
{
return "/storage/emulated/0/Android/data/".taskman\deploy_get($atf_host, 'package_id')."/files/";
}
function device_put_file_async(string $atf_host, string $device, string $data_or_file, string $device_path, bool $is_file = false, $timeout = 30) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device, $data_or_file, $device_path, $is_file, $timeout) {
if($device_path[0] !== "/")
$device_path = package_dir($atf_host) . $device_path;
$tmp_remote_file = '%{dir}%'.uniqid(basename($device_path)."_");
if(!$is_file)
{
$local_path = tempnam("/tmp", "atf_");
taskman\ensure_write($local_path, $data_or_file);
}
else
{
$local_path = $data_or_file;
if(!is_file($local_path))
throw new Exception("No such local file: $local_path");
}
try
{
//1. let's copy local file to the ATF host as temp one
yield host_put_file_async($atf_host, $local_path, $tmp_remote_file);
}
finally
{
if(!$is_file)
taskman\ensure_rm($local_path);
}
//2. let's push the temp file to the device
$device_dir = dirname($device_path);
yield host_exec_async($atf_host, "%{adb}% -s $device shell mkdir -p $device_dir");
yield host_exec_async($atf_host, "%{adb}% -s $device push $tmp_remote_file $device_path", 0, $timeout);
yield host_exec_async($atf_host, "%{adb}% -s $device shell chmod -R 777 $device_dir", DEPLOY_OPT_ERR_OK);
yield host_exec_async($atf_host, "rm -rf $tmp_remote_file");
});
}
function device_del_file(string $atf_host, string $device, string $device_path, $opts = 0)
{
if($device_path[0] !== "/")
$device_path = package_dir($atf_host) . $device_path;
host_exec($atf_host, "%{adb}% -s $device shell rm -rf $device_path", $opts);
}
function device_del_file_async(string $atf_host, string $device, string $device_path, $opts = 0)
{
if($device_path[0] !== "/")
$device_path = package_dir($atf_host) . $device_path;
return host_exec_async($atf_host, "%{adb}% -s $device shell rm -rf $device_path", $opts);
}
function device_blink($atf_host, string $device, int $times = 5)
{
//disable auto brightness
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness_mode 0");
for($i=0;$i<$times;++$i)
{
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness 255");
sleep(1);
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness 5");
}
//enable auto brightness
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness 100");
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness_mode 1");
}
function device_put_dir_async(string $atf_host, string $device, string $folder_path, string $device_parent_path, int $timeout = 60) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device, $folder_path, $device_parent_path, $timeout) {
if($device_parent_path[0] !== "/")
$device_parent_path = package_dir($atf_host) . $device_parent_path;
$remote_path = '/tmp/' . basename($folder_path);
yield taskman\deploy_rsync_async($atf_host, "$folder_path/", "$remote_path/", '--delete');
yield host_exec_async($atf_host, "%{adb}% -s $device push --sync $remote_path $device_parent_path", 0, $timeout);
});
}
function device_pull_file_async(string $atf_host, string $device, string $device_path, int $timeout = 10, bool $throw_error_on_timeout = false) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device, $device_path, $throw_error_on_timeout, $timeout) {
if($device_path[0] !== "/")
$device_path = package_dir($atf_host) . $device_path;
$tmp_remote_file = '%{dir}%/'.uniqid(basename($device_path)."_");
list($status, $_) = yield host_exec_async($atf_host, "%{adb}% -s $device pull $device_path $tmp_remote_file", DEPLOY_OPT_ERR_OK, $timeout);
if($status !== 0)
{
if($status === false && $throw_error_on_timeout)
throw new Exception("Could not pull from device '$device' file '$device_path' due to timeout");
return null;
}
$file_data = host_get_file($atf_host, $tmp_remote_file);
yield host_exec_async($atf_host, "rm -rf $tmp_remote_file");
return $file_data;
});
}
function get_last_replay_async(string $atf_host, string $device, string $device_replay_path) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device, $device_replay_path) {
$file = package_dir($atf_host) . $device_replay_path;
list($status, $contents) = yield host_exec_async($atf_host, "%{adb}% -s $device shell cat $file", DEPLOY_OPT_ERR_OK);
return array($status, implode("\n", $contents));
});
}
function _multi_curl_async($ch, $get_content) : Amp\Promise
{
return Amp\call(function() use($ch, $get_content) {
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch);
do {
$status = curl_multi_exec($mh, $active);
yield Amp\delay(1);
} while ($active && $status == CURLM_OK);
$result = null;
if($get_content)
$result = curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
curl_multi_close($mh);
return $result;
});
}
function device_screen(string $atf_host, string $device)
{
$screen_file_name = '%{dir}%/'.uniqid("screen_").'.png';
try
{
host_exec($atf_host, "%{adb}% -s $device exec-out screencap -p > $screen_file_name");
}
catch(Exception $e)
{
return false;
}
$data = host_get_file($atf_host, $screen_file_name);
host_exec($atf_host, "rm -rf $screen_file_name");
return $data;
}
function del_external_files_async(string $atf_host, string $device, array $external_files) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device, $external_files) {
foreach($external_files as $real_path => $remote_path)
yield device_del_file_async($atf_host, $device, $remote_path);
});
}
function put_external_files_async(string $atf_host, string $device, array $external_files) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device, $external_files) {
foreach($external_files as $real_path => $remote_path)
yield device_put_file_async($atf_host, $device, $real_path, $remote_path, true);
});
}
function get_locat_errors_since(string $atf_host, string $device, int $since_stamp)
{
$time_spec = gmdate('m-d h:m:s.0', $since_stamp);
list($_, $lines) = host_exec($atf_host, "%{adb}% -s $device logcat -s -v UTC -d -t '$time_spec' AndroidRuntime:E Unity:E", DEPLOY_OPT_ERR_OK);
_filter_error_logs($lines);
return implode("\n", $lines);
}
function get_logcat_errors(string $atf_host, string $device, int $limit = 0)
{
list($_, $lines) = host_exec($atf_host, "%{adb}% -s $device logcat -s -d ".($limit > 0 ? "-t $limit" : "")." AndroidRuntime:E Unity:E", DEPLOY_OPT_ERR_OK);
_filter_error_logs($lines);
return implode("\n", $lines);
}
function get_locat_unity_since(string $atf_host, string $device, int $since_stamp)
{
$time_spec = gmdate('m-d h:m:s.0', $since_stamp);
list($_, $lines) = host_exec($atf_host, "%{adb}% -s $device logcat -s -v UTC -d -t '$time_spec' Unity:'*'", DEPLOY_OPT_ERR_OK);
return implode("\n", $lines);
}
function get_logcat_unity(string $atf_host, string $device, int $limit = 0)
{
list($_, $lines) = host_exec($atf_host, "%{adb}% -s $device logcat -s -d Unity:'*'", DEPLOY_OPT_ERR_OK);
if($limit > 0)
array_splice($lines, 0, sizeof($lines) - $limit);
return implode("\n", $lines);
}
function _filter_error_logs(array &$lines)
{
$exception_lines = 0;
$filtered = array();
for($i=0;$i<sizeof($lines);++$i)
{
$line = $lines[$i];
if(strpos($line, 'E AndroidRuntime') !== false)
$filtered[] = $line;
else if(strpos($line, 'E Unity') !== false)
{
if(strpos($line, '[EXCEPTION]') !== false)
$exception_lines = 15;
if(--$exception_lines > 0)
$filtered[] = $line;
}
}
$lines = $filtered;
}
function device_pss_async(string $atf_host, string $device) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device) {
$res = yield device_mem_async($atf_host, $device);
return $res['total'];
});
}
function device_mem_async(string $atf_host, string $device) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device) {
list($code, $lines) = yield host_exec_async($atf_host, "%{adb}% -s $device shell dumpsys meminfo -s %{package_id}%", DEPLOY_OPT_ERR_OK, 20);
$res = array(
'total' => 0,
'native' => 0,
'java' => 0,
'system' => 0,
'graphics' => 0,
);
if($code !== 0)
return $res;
foreach($lines as $idx => $line)
{
$items = preg_split('/\s+/', trim($line));
if($items && $items[0] === "TOTAL:")
$res['total'] = intval($items[1]);
else if($items && $items[0] === "TOTAL" && $items[1] === "PSS:")
$res['total'] = intval($items[2]);
else if($items && $items[0] === "Native" && $items[1] === "Heap:")
$res['native'] = intval($items[2]);
else if($items && $items[0] === "Java" && $items[1] === "Heap:")
$res['java'] = intval($items[2]);
else if($items && $items[0] === "System:")
$res['system'] = intval($items[1]);
else if($items && $items[0] === "Graphics:")
$res['graphics'] = intval($items[1]);
}
return $res;
});
}
function device_temperature_async(string $atf_host, string $device) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device) {
//NOTE: on current devices 16 thermal zone is responsible for GPU temperature probing
list($code, $lines) = yield host_exec_async($atf_host, "%{adb}% -s $device shell cat /sys/class/thermal/thermal_zone16/temp", DEPLOY_OPT_ERR_OK, 1);
if($code !== 0)
return 0;
foreach($lines as $idx => $line)
{
$line = trim($line);
if(!$line)
continue;
return intval($line);
}
return 0;
});
}