AsyncJobs.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <?php
  2. // /opt/local/pl.procesy5/p5build_SE/temp/WPS_Functions/default_db/CRM_PROCES_tree/relations-22
  3. // /opt/local/pl.procesy5/ -> /Library/New_Server/opt/local/pl.procesy5
  4. // /opt/local/pl.procesy5/async_jobs - TODO: APP_PATH_ASYNC_JOB from .htaccess or config
  5. // Config example:
  6. // cat SE/config/.cnf-biuro.biall-net.pl.ini.php:
  7. // APP_PATH_ASYNC_JOB="/opt/local/pl.procesy5/async_jobs"
  8. // register new job:
  9. // - $TODAY = date("Y-m-d");
  10. // - $JOB_ID = generateJobID if not already started?
  11. // - make folder APP_PATH_ASYNC_JOB / $TODAY / job-{$JOB_ID}
  12. // - make base files:
  13. // - log files: `out.log`, `error.log`
  14. // - `output`: if process creates file. @param --out_file=out
  15. // - `output_type`: output mime type or namespace
  16. // - `progress`: if process implement progress. @param --progress_file=progress
  17. Lib::loadClass('Request');
  18. Lib::loadClass('Core_AsyncJobsFiles');
  19. Lib::loadClass('Core_AsyncJobsDB');
  20. Lib::loadClass('Core_AsyncJobsServer'); // pm2
  21. /**
  22. * @usage: Core_AsyncJobs::startAsyncJob($idJob)
  23. * -> Core_AsyncJobsServer::startAsyncJob(execPath, name, [ 'out.log', 'error.log', 'cwd', 'args' ])
  24. */
  25. class Core_AsyncJobs {
  26. static function registerNewAntTask($props) {
  27. $namespace = V::get('namespace', '', $props);
  28. $primaryKey = V::get('primaryKey', '', $props);
  29. $ant_path = V::get('ant_path', '', $props);
  30. $ant_template = V::get('ant_template', '', $props);
  31. if (empty($namespace)) throw new Exception("Missing namespace");
  32. if (empty($primaryKey)) throw new Exception("Missing primaryKey");
  33. if (empty($ant_path)) throw new Exception("Missing ant_path");
  34. if (empty($ant_template)) throw new Exception("Missing ant_template");
  35. // https://biuro.biall-net.pl/dev-pl/se-master/index.php?_route=UrlAction_Ant
  36. // & _task=ant
  37. // & path=default_db.in7_dziennik_koresp/test-async
  38. // & template=test-loop
  39. // & typeName=default_db:IN7_DZIENNIK_KORESP
  40. // & primaryKey=66263
  41. // & primaryKeyField=ID
  42. Lib::loadClass('ACL');
  43. $acl = ACL::getAclByNamespace($namespace);
  44. $typeName = Api_WfsNs::typeName($namespace);
  45. $pkField = $acl->getPrimaryKeyField();
  46. // DB::getPDO()->tryHandleException
  47. $idJob = V::tryHandleException($handler = [ 'Core_AsyncJobs', 'isInstalled' ], $callback = [ 'Core_AsyncJobsDB', 'insert' ], $args = [
  48. [
  49. 'JOB_NAME' => "Ant|{$namespace}|{$ant_path}|{$ant_template}",
  50. 'LOCK_VALUE' => $primaryKey,
  51. 'USER' => User::getLogin(),
  52. ],
  53. ]);
  54. {
  55. $path = $ant_path; // default_db.in7_dziennik_koresp/test-async
  56. $template = $ant_template; // test-loop
  57. $webRootUrl = Request::getPathUri() . "schema/ant-url_action/{$path}"; // TODO: security - only for test
  58. $outputFunctionUrl = Router::getRoute('UrlAction_Ant')->getLink('output') . "&path={$path}&file=";
  59. $antFunctionUrl = Router::getRoute('UrlAction_Ant')->getLink('ant') . "&path={$path}&file={$file}&template={$template}&typeName={$typeName}&primaryKey={$primaryKey}&primaryKeyField={$pkField}"; // &confirmAntfile={$confirmAntfile}&confirmAntfileTarget={$confirmAntfileTarget} sdo confirmacji potrzebne - wzglednie przetwarzac z tresci output URL_TASK - ale potrzebne parametry analogiczne aby chodzily
  60. // $testUrl = Request::getPathUri() . "wfs-data.php/default_db/?SERVICE=WFS&VERSION=1.0.0&TYPENAME={$typeName}&SRSNAME=EPSG:3003&featureID={$objectName}.{$primaryKey}";// &REQUEST=GetFeature
  61. $uuid = date("Y-m-d-H_i_s") . substr(md5(time()), 0, 6); // TODO: uniq id for every request
  62. Lib::loadClass('Crypt');
  63. $passwordBase64Basic = base64_encode(User::getLogin() . ":" . Crypt::decrypt($_SESSION['ADM_PASS_HASH']));
  64. // /* TODO: ??? */ if( strlen($confirmAntfile) > 0 ) $cmd .= " -DconfirmAntfile={$confirmAntfile}";
  65. // /* TODO: ??? */ if( strlen($confirmAntfileTarget) > 0 ) $cmd .= " -DconfirmAntfileTarget={$confirmAntfileTarget}";
  66. $php_session_id = session_id();
  67. }
  68. Core_AsyncJobsFiles::createNewJobFiles($idJob, $jobProps = [
  69. 'p5_ant_url_action_base_path' => APP_PATH_ROOT . "/schema/ant-url_action/{$path}",
  70. 'webRootUrl' => $webRootUrl,
  71. 'outputFunctionUrl' => $outputFunctionUrl,
  72. 'antFunctionUrl' => $antFunctionUrl,
  73. 'uuid' => $uuid,
  74. 'passwordBase64Basic' => $passwordBase64Basic,
  75. 'php_session_id' => $passwordBase64Basic,
  76. 'xpath' => $pkField,
  77. 'xpath_value' => $primaryKey,
  78. 'template' => $template,
  79. 'api_url' => Request::getPathUri()."wfs-data.php", //potrzebuje ten parametr do dzialania w AMS itp
  80. 'typeName' => $typeName,
  81. ]);
  82. $jobStartScript = "";
  83. {
  84. $propFilePath = Core_AsyncJobsFiles::propertyFile($idJob, $today = date("Y-m-d"));
  85. $cmd = "cd " . APP_PATH_SCHEMA . "/ant-url_action/{$ant_path}";
  86. $cmd .= " && ";
  87. $cmd .= implode(" ", [
  88. 'ant',
  89. '-S',
  90. '-propertyfile', $propFilePath,
  91. ($ant_template) ? $ant_template : '',
  92. '2>&1',
  93. ]);
  94. $pathCmd = [];
  95. $pathCmd[] = '/opt/local/bin/ant';
  96. $pathCmd[] = '/bin';
  97. $pathCmd[] = '/usr/bin';
  98. $pathCmd[] = '/usr/local/bin';
  99. $pathCmd[] = '/opt/local/bin';
  100. $pathCmd[] = '/sbin';
  101. $pathCmd[] = '/usr/sbin';
  102. $pathCmd[] = '/opt/local/sbin/skrypty';
  103. $pathCmd[] = '/opt/local/var/macports/software';
  104. $pathCmd[] = '/Applications/Server.app/Contents/ServerRoot/usr/bin';
  105. $pathCmd[] = '/Applications/Server.app/Contents/ServerRoot/usr/sbin';
  106. $jobStartScript = implode("\n", [
  107. 'export PATH=' . implode(':', $pathCmd),
  108. 'JAVA_HOME="/usr/bin/java"',
  109. 'MAVEN_OPTS="-Xms256m -Xmx512m"',
  110. $cmd,
  111. "",
  112. ]);
  113. }
  114. Core_AsyncJobsFiles::createTaskStartScript($idJob, $jobDate = date("Y-m-d"), $jobStartScript);
  115. return $idJob;
  116. }
  117. static function startAsyncJob($idJob, $item = []) {
  118. if (empty($item)) $item = Core_AsyncJobsDB::fetch($idJob);
  119. DBG::nicePrint($item, '$item');
  120. // Core_AsyncJobsFiles::basePath($idJob, $jobDate = $item['DATE']);
  121. // Core_AsyncJobsFiles::propertyFile($idJob, $jobDate = $item['DATE']);
  122. // Core_AsyncJobsFiles::startScript($idJob, $jobDate = $item['DATE']);
  123. // Core_AsyncJobsFiles::outLog($idJob, $jobDate = $item['DATE']);
  124. // Core_AsyncJobsFiles::errorLog($idJob, $jobDate = $item['DATE']);
  125. $jobDate = $item['DATE'];
  126. $execPath = Core_AsyncJobsFiles::startScript($idJob, $jobDate);
  127. Core_AsyncJobsDB::updateStatus($idJob, $status = 'NORMAL', $item);
  128. // Core_AsyncJobsServer::startAsyncJob(execPath, name, [ 'out.log', 'error.log', 'cwd', 'args' ])
  129. $out = [];
  130. $ret = Core_AsyncJobsServer::startAsyncJob(
  131. $execPath,
  132. $name = implode("|", [ $item['JOB_NAME'], $item['LOCK_VALUE'] ]),
  133. [
  134. 'out.log' => Core_AsyncJobsFiles::outLog($idJob, $jobDate),
  135. 'error.log' => Core_AsyncJobsFiles::errorLog($idJob, $jobDate),
  136. 'cwd' => dirname(Core_AsyncJobsFiles::basePath($idJob, $jobDate)),
  137. ],
  138. $out
  139. );
  140. if (0 === $ret) {
  141. throw new AlertInfoException("Uruchomiono zadanie. " . implode(" ", $out));
  142. } else {
  143. throw new Exception("Nie uruchomiono zadania. " . implode(" ", $out));
  144. }
  145. }
  146. static function getSimpleList() {
  147. $fullList = self::getFullList();
  148. return array_map(function ($jobInfo) {
  149. return [
  150. 'name' => $jobInfo['name'],
  151. 'pid' => $jobInfo['pid'],
  152. 'pm_id' => $jobInfo['pm_id'],
  153. 'pm2_env.status' => $jobInfo['pm2_env']['status'],
  154. 'monit.memory' => $jobInfo['monit']['memory'],
  155. 'monit.cpu' => $jobInfo['monit']['cpu'],
  156. // "pm_out_log_path": "/Library/WebServer/.pm2/logs/test-job-1-out.log",
  157. // "pm_err_log_path": "/Library/WebServer/.pm2/logs/test-job-1-error.log",
  158. // "pm_pid_path": "/Library/WebServer/.pm2/pids/test-job-0.pid",
  159. 'pm2_env.pm_out_log_path' => $jobInfo['pm2_env']['pm_out_log_path'],
  160. 'pm2_env.pm_err_log_path' => $jobInfo['pm2_env']['pm_err_log_path'],
  161. 'pm2_env.pm_pid_path' => $jobInfo['pm2_env']['pm_pid_path'],
  162. ];
  163. }, $fullList);
  164. }
  165. static function getFullList() {
  166. $jobListJson = V::shell_exec("pm2 jlist 2>&1");
  167. if (empty($jobListJson)) throw new Exception("Reading async job list failed");
  168. $parsedJobList = @json_decode($jobListJson, $assoc = true);
  169. if (null == $parsedJobList && 0 !== json_last_error()) throw new Exception("Parsing async job list failed: " . json_last_error());
  170. return $parsedJobList;
  171. }
  172. static function getJobLogs($jobNameOrID, $lastLines = 10) {
  173. if (empty($jobNameOrID) && $jobNameOrID !== 0) throw new Exception("Missing job name or id in getJobLogs");
  174. $lastLines = ((int)$lastLines > 0) ? (int)$lastLines : 10;
  175. // $cmd = "pm2 logs {$jobNameOrID} --lines {$lastLines} --nostream | tail -n {$lastLines}";
  176. $cmd = "pm2 logs {$jobNameOrID} --lines {$lastLines} --nostream";
  177. return V::shell_exec($cmd);
  178. // [TAILING] Tailing last 3 lines for [0] process (change the value with --lines option)
  179. // /Library/WebServer/.pm2/logs/test-job-1-out.log last 3 lines:
  180. // /Library/WebServer/
  181. }
  182. static function stopJob($jobNameOrID) {
  183. if (empty($jobNameOrID) && $jobNameOrID !== 0) throw new Exception("Missing job name or id in stopJob");
  184. $cmd = "pm2 stop {$jobNameOrID}";
  185. return V::shell_exec($cmd);
  186. }
  187. static function startJob($jobNameOrID) {
  188. if (empty($jobNameOrID) && $jobNameOrID !== 0) throw new Exception("Missing job name or id in startJob");
  189. $cmd = "pm2 start {$jobNameOrID}";
  190. return V::shell_exec($cmd);
  191. }
  192. static function deleteStopped() {
  193. // TODO: script to remove stopped in loop with delay
  194. // pm2 start app.js --restart-delay=3000
  195. // $ pm2 list | grep '^│' | awk -F'│' '{ gsub(/ /, "", $2); gsub(/ /, "", $10); if ("stopped" == $10) { print $2" # STOPPED" } else { print $10 } }' | grep '# STOPPED' | xargs -n1 pm2 delete
  196. $testCmd = implode(" | ", [
  197. "pm2 list",
  198. "grep '^│'",
  199. "awk -F'│' '{ gsub(/ /, \"\", \$2); gsub(/ /, \"\", \$10); if (\"stopped\" == \$10) { print \$2\" # STOPPED\" } else { print \$10 } }'",
  200. "grep '# STOPPED'",
  201. "awk '{print \$1}'",
  202. ]);
  203. V::exec($testCmd . " 2>&1", $out, $ret);
  204. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  205. $cmd = implode(" | ", [
  206. "pm2 list",
  207. "grep '^│'",
  208. "awk -F'│' '{ gsub(/ /, \"\", \$2); gsub(/ /, \"\", \$10); if (\"stopped\" == \$10) { print \$2\" # STOPPED\" } else { print \$10 } }'",
  209. "grep '# STOPPED'",
  210. "awk '{print \$1}'",
  211. "xargs -n1 pm2 delete",
  212. ]);
  213. }
  214. static function isInstalled() {
  215. if (!Core_AsyncJobsFiles::isInstalled()) throw new Exception("AsyncJobs files not installed");
  216. if (!Core_AsyncJobsServer::isInstalled()) throw new Exception("AsyncJobs server not installed");
  217. if (!Core_AsyncJobsDB::checkAsyncJobDatabase()) throw new Exception("AsyncJobs database schema not installed");
  218. return true;
  219. }
  220. // pm2 logs -h
  221. //
  222. // Usage: logs [options] [id|name]
  223. // stream logs file. Default stream all logs
  224. // Options:
  225. // --json json log output
  226. // --format formated log output
  227. // --raw raw output
  228. // --err only shows error output
  229. // --out only shows standard output
  230. // --lines <n> output the last N lines, instead of the last 15 by default
  231. // --timestamp [format] add timestamps (default format YYYY-MM-DD-HH:mm:ss)
  232. // --nostream print logs without lauching the log stream
  233. // --highlight [value] highlights the given value
  234. // -h, --help output usage information
  235. // [0] => Array:
  236. // [pid] => 71728
  237. // [name] => test-job-1
  238. // [pm2_env] => Array:
  239. // [exit_code] => 0
  240. // [versioning] =>
  241. // [version] => N/A
  242. // [unstable_restarts] => 0
  243. // [restart_time] => 53
  244. // [pm_id] => 0
  245. // [created_at] => 1581938950672
  246. // [axm_dynamic] => Array:
  247. // [axm_options] => Array:
  248. // [axm_monitor] => Array:
  249. // [axm_actions] => Array:
  250. // [pm_uptime] => 1581939524854
  251. // [status] => online
  252. // [unique_id] => 80fefcff-8605-44cc-9829-bbcba4de4e7c
  253. // [PM2_HOME] => /Library/WebServer/.pm2
  254. // [PATH] => /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/local/bin:/opt/local/lib/mysql55/bin:/Applications/Server.app/Contents/ServerRoot/usr/bin:/Applications/Server.app/Contents/ServerRoot/usr/sbin:/Users/pl/programy/bin
  255. // [PWD] => /Users/plabudda/rsync.se.master/SE
  256. // [XPC_FLAGS] => 0x80
  257. // [XPC_SERVICE_NAME] => 0
  258. // [HOME] => /Library/WebServer
  259. // [SHLVL] => 1
  260. // [SERVER_INSTALL_PATH_PREFIX] => /Applications/Server.app/Contents/ServerRoot
  261. // [XPC_SERVICES_UNAVAILABLE] => 1
  262. // [MODULE_INSTALL_PATH_PREFIX] =>
  263. // [_] => /usr/local/bin/pm2
  264. // [__CF_USER_TEXT_ENCODING] => 0x46:0:0
  265. // [PM2_USAGE] => CLI
  266. // [NODE_APP_INSTANCE] => 0
  267. // [vizion_running] =>
  268. // [km_link] =>
  269. // [pm_pid_path] => /Library/WebServer/.pm2/pids/test-job-0.pid
  270. // [pm_err_log_path] => /Library/WebServer/.pm2/logs/test-job-1-error.log
  271. // [pm_out_log_path] => /Library/WebServer/.pm2/logs/test-job-1-out.log
  272. // [instances] => 1
  273. // [exec_mode] => fork_mode
  274. // [exec_interpreter] => php
  275. // [pm_cwd] => /Users/plabudda/rsync.se.master/SE
  276. // [pm_exec_path] => /Users/plabudda/rsync.se.master/sbin/test-sleep-loop.php
  277. // [node_args] => Array:
  278. // [name] => test-job-1
  279. // [namespace] => default
  280. // [env] => Array:
  281. // [unique_id] => 80fefcff-8605-44cc-9829-bbcba4de4e7c
  282. // [test-job-1] => Array:
  283. // [PM2_HOME] => /Library/WebServer/.pm2
  284. // [PATH] => /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/local/bin:/opt/local/lib/mysql55/bin:/Applications/Server.app/Contents/ServerRoot/usr/bin:/Applications/Server.app/Contents/ServerRoot/usr/sbin:/Users/pl/programy/bin
  285. // [PWD] => /Users/plabudda/rsync.se.master/SE
  286. // [XPC_FLAGS] => 0x80
  287. // [XPC_SERVICE_NAME] => 0
  288. // [HOME] => /Library/WebServer
  289. // [SHLVL] => 1
  290. // [SERVER_INSTALL_PATH_PREFIX] => /Applications/Server.app/Contents/ServerRoot
  291. // [XPC_SERVICES_UNAVAILABLE] => 1
  292. // [MODULE_INSTALL_PATH_PREFIX] =>
  293. // [_] => /usr/local/bin/pm2
  294. // [__CF_USER_TEXT_ENCODING] => 0x46:0:0
  295. // [PM2_USAGE] => CLI
  296. // [merge_logs] => 1
  297. // [vizion] => 1
  298. // [autorestart] => 1
  299. // [watch] =>
  300. // [instance_var] => NODE_APP_INSTANCE
  301. // [pmx] => 1
  302. // [automation] => 1
  303. // [treekill] => 1
  304. // [username] => _www
  305. // [windowsHide] => 1
  306. // [kill_retry_time] => 100
  307. // [pm_id] => 0
  308. // [monit] => Array:
  309. // [memory] => 15720448
  310. // [cpu] => 0
  311. }