AsyncJobs.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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. 'webRootUrl' => $webRootUrl,
  70. 'outputFunctionUrl' => $outputFunctionUrl,
  71. 'antFunctionUrl' => $antFunctionUrl,
  72. 'uuid' => $uuid,
  73. 'passwordBase64Basic' => $passwordBase64Basic,
  74. 'php_session_id' => $passwordBase64Basic,
  75. 'xpath' => $pkField,
  76. 'xpath_value' => $primaryKey,
  77. 'template' => $template,
  78. 'api_url' => Request::getPathUri()."wfs-data.php", //potrzebuje ten parametr do dzialania w AMS itp
  79. 'typeName' => $typeName,
  80. ]);
  81. $jobStartScript = "";
  82. {
  83. $propFilePath = Core_AsyncJobsFiles::propertyFile($idJob, $today = date("Y-m-d"));
  84. $cmd = "cd " . APP_PATH_SCHEMA . "/ant-url_action/{$ant_path}";
  85. $cmd .= " && ";
  86. $cmd .= implode(" ", [
  87. 'ant',
  88. '-S',
  89. '-propertyfile', $propFilePath,
  90. ($ant_template) ? $ant_template : '',
  91. '2>&1',
  92. ]);
  93. $pathCmd = [];
  94. $pathCmd[] = '/opt/local/bin/ant';
  95. $pathCmd[] = '/bin';
  96. $pathCmd[] = '/usr/bin';
  97. $pathCmd[] = '/usr/local/bin';
  98. $pathCmd[] = '/opt/local/bin';
  99. $pathCmd[] = '/sbin';
  100. $pathCmd[] = '/usr/sbin';
  101. $pathCmd[] = '/opt/local/sbin/skrypty';
  102. $pathCmd[] = '/opt/local/var/macports/software';
  103. $pathCmd[] = '/Applications/Server.app/Contents/ServerRoot/usr/bin';
  104. $pathCmd[] = '/Applications/Server.app/Contents/ServerRoot/usr/sbin';
  105. $jobStartScript = implode("\n", [
  106. 'export PATH=' . implode(':', $pathCmd),
  107. 'JAVA_HOME="/usr/bin/java"',
  108. 'MAVEN_OPTS="-Xms256m -Xmx512m"',
  109. $cmd,
  110. "",
  111. ]);
  112. }
  113. Core_AsyncJobsFiles::createTaskStartScript($idJob, $jobDate = date("Y-m-d"), $jobStartScript);
  114. return $idJob;
  115. }
  116. static function startAsyncJob($idJob, $item = []) {
  117. if (empty($item)) $item = Core_AsyncJobsDB::fetch($idJob);
  118. DBG::nicePrint($item, '$item');
  119. // Core_AsyncJobsFiles::basePath($idJob, $jobDate = $item['DATE']);
  120. // Core_AsyncJobsFiles::propertyFile($idJob, $jobDate = $item['DATE']);
  121. // Core_AsyncJobsFiles::startScript($idJob, $jobDate = $item['DATE']);
  122. // Core_AsyncJobsFiles::outLog($idJob, $jobDate = $item['DATE']);
  123. // Core_AsyncJobsFiles::errorLog($idJob, $jobDate = $item['DATE']);
  124. $jobDate = $item['DATE'];
  125. $execPath = Core_AsyncJobsFiles::startScript($idJob, $jobDate);
  126. // Core_AsyncJobsServer::startAsyncJob(execPath, name, [ 'out.log', 'error.log', 'cwd', 'args' ])
  127. $out = [];
  128. $ret = Core_AsyncJobsServer::startAsyncJob(
  129. $execPath,
  130. $name = implode("|", [ $item['JOB_NAME'], $item['LOCK_VALUE'] ]),
  131. [
  132. 'out.log' => Core_AsyncJobsFiles::outLog($idJob, $jobDate),
  133. 'error.log' => Core_AsyncJobsFiles::errorLog($idJob, $jobDate),
  134. 'cwd' => dirname(Core_AsyncJobsFiles::basePath($idJob, $jobDate)),
  135. ],
  136. $out
  137. );
  138. if (0 === $ret) {
  139. throw new AlertInfoException("Uruchomiono zadanie. " . implode(" ", $out));
  140. } else {
  141. throw new Exception("Nie uruchomiono zadania. " . implode(" ", $out));
  142. }
  143. }
  144. static function getSimpleList() {
  145. $fullList = self::getFullList();
  146. return array_map(function ($jobInfo) {
  147. return [
  148. 'name' => $jobInfo['name'],
  149. 'pid' => $jobInfo['pid'],
  150. 'pm_id' => $jobInfo['pm_id'],
  151. 'pm2_env.status' => $jobInfo['pm2_env']['status'],
  152. 'monit.memory' => $jobInfo['monit']['memory'],
  153. 'monit.cpu' => $jobInfo['monit']['cpu'],
  154. // "pm_out_log_path": "/Library/WebServer/.pm2/logs/test-job-1-out.log",
  155. // "pm_err_log_path": "/Library/WebServer/.pm2/logs/test-job-1-error.log",
  156. // "pm_pid_path": "/Library/WebServer/.pm2/pids/test-job-0.pid",
  157. 'pm2_env.pm_out_log_path' => $jobInfo['pm2_env']['pm_out_log_path'],
  158. 'pm2_env.pm_err_log_path' => $jobInfo['pm2_env']['pm_err_log_path'],
  159. 'pm2_env.pm_pid_path' => $jobInfo['pm2_env']['pm_pid_path'],
  160. ];
  161. }, $fullList);
  162. }
  163. static function getFullList() {
  164. $jobListJson = V::shell_exec("pm2 jlist 2>&1");
  165. if (empty($jobListJson)) throw new Exception("Reading async job list failed");
  166. $parsedJobList = @json_decode($jobListJson, $assoc = true);
  167. if (null == $parsedJobList && 0 !== json_last_error()) throw new Exception("Parsing async job list failed: " . json_last_error());
  168. return $parsedJobList;
  169. }
  170. static function getJobLogs($jobNameOrID, $lastLines = 10) {
  171. if (empty($jobNameOrID) && $jobNameOrID !== 0) throw new Exception("Missing job name or id in getJobLogs");
  172. $lastLines = ((int)$lastLines > 0) ? (int)$lastLines : 10;
  173. // $cmd = "pm2 logs {$jobNameOrID} --lines {$lastLines} --nostream | tail -n {$lastLines}";
  174. $cmd = "pm2 logs {$jobNameOrID} --lines {$lastLines} --nostream";
  175. return V::shell_exec($cmd);
  176. // [TAILING] Tailing last 3 lines for [0] process (change the value with --lines option)
  177. // /Library/WebServer/.pm2/logs/test-job-1-out.log last 3 lines:
  178. // /Library/WebServer/
  179. }
  180. static function stopJob($jobNameOrID) {
  181. if (empty($jobNameOrID) && $jobNameOrID !== 0) throw new Exception("Missing job name or id in stopJob");
  182. $cmd = "pm2 stop {$jobNameOrID}";
  183. return V::shell_exec($cmd);
  184. }
  185. static function startJob($jobNameOrID) {
  186. if (empty($jobNameOrID) && $jobNameOrID !== 0) throw new Exception("Missing job name or id in startJob");
  187. $cmd = "pm2 start {$jobNameOrID}";
  188. return V::shell_exec($cmd);
  189. }
  190. static function deleteStopped() {
  191. // TODO: script to remove stopped in loop with delay
  192. // pm2 start app.js --restart-delay=3000
  193. // $ pm2 list | grep '^│' | awk -F'│' '{ gsub(/ /, "", $2); gsub(/ /, "", $10); if ("stopped" == $10) { print $2" # STOPPED" } else { print $10 } }' | grep '# STOPPED' | xargs -n1 pm2 delete
  194. $testCmd = implode(" | ", [
  195. "pm2 list",
  196. "grep '^│'",
  197. "awk -F'│' '{ gsub(/ /, \"\", \$2); gsub(/ /, \"\", \$10); if (\"stopped\" == \$10) { print \$2\" # STOPPED\" } else { print \$10 } }'",
  198. "grep '# STOPPED'",
  199. "awk '{print \$1}'",
  200. ]);
  201. V::exec($testCmd . " 2>&1", $out, $ret);
  202. echo "cmd: <code>{$cmd}</code><br>RETURN CODE: '{$ret}'<br><pre>OUTPUT:\n" . implode("\n", $out) . "</pre>";
  203. $cmd = implode(" | ", [
  204. "pm2 list",
  205. "grep '^│'",
  206. "awk -F'│' '{ gsub(/ /, \"\", \$2); gsub(/ /, \"\", \$10); if (\"stopped\" == \$10) { print \$2\" # STOPPED\" } else { print \$10 } }'",
  207. "grep '# STOPPED'",
  208. "awk '{print \$1}'",
  209. "xargs -n1 pm2 delete",
  210. ]);
  211. }
  212. static function isInstalled() {
  213. if (!Core_AsyncJobsFiles::isInstalled()) throw new Exception("AsyncJobs files not installed");
  214. if (!Core_AsyncJobsServer::isInstalled()) throw new Exception("AsyncJobs server not installed");
  215. if (!Core_AsyncJobsDB::checkAsyncJobDatabase()) throw new Exception("AsyncJobs database schema not installed");
  216. return true;
  217. }
  218. // pm2 logs -h
  219. //
  220. // Usage: logs [options] [id|name]
  221. // stream logs file. Default stream all logs
  222. // Options:
  223. // --json json log output
  224. // --format formated log output
  225. // --raw raw output
  226. // --err only shows error output
  227. // --out only shows standard output
  228. // --lines <n> output the last N lines, instead of the last 15 by default
  229. // --timestamp [format] add timestamps (default format YYYY-MM-DD-HH:mm:ss)
  230. // --nostream print logs without lauching the log stream
  231. // --highlight [value] highlights the given value
  232. // -h, --help output usage information
  233. // [0] => Array:
  234. // [pid] => 71728
  235. // [name] => test-job-1
  236. // [pm2_env] => Array:
  237. // [exit_code] => 0
  238. // [versioning] =>
  239. // [version] => N/A
  240. // [unstable_restarts] => 0
  241. // [restart_time] => 53
  242. // [pm_id] => 0
  243. // [created_at] => 1581938950672
  244. // [axm_dynamic] => Array:
  245. // [axm_options] => Array:
  246. // [axm_monitor] => Array:
  247. // [axm_actions] => Array:
  248. // [pm_uptime] => 1581939524854
  249. // [status] => online
  250. // [unique_id] => 80fefcff-8605-44cc-9829-bbcba4de4e7c
  251. // [PM2_HOME] => /Library/WebServer/.pm2
  252. // [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
  253. // [PWD] => /Users/plabudda/rsync.se.master/SE
  254. // [XPC_FLAGS] => 0x80
  255. // [XPC_SERVICE_NAME] => 0
  256. // [HOME] => /Library/WebServer
  257. // [SHLVL] => 1
  258. // [SERVER_INSTALL_PATH_PREFIX] => /Applications/Server.app/Contents/ServerRoot
  259. // [XPC_SERVICES_UNAVAILABLE] => 1
  260. // [MODULE_INSTALL_PATH_PREFIX] =>
  261. // [_] => /usr/local/bin/pm2
  262. // [__CF_USER_TEXT_ENCODING] => 0x46:0:0
  263. // [PM2_USAGE] => CLI
  264. // [NODE_APP_INSTANCE] => 0
  265. // [vizion_running] =>
  266. // [km_link] =>
  267. // [pm_pid_path] => /Library/WebServer/.pm2/pids/test-job-0.pid
  268. // [pm_err_log_path] => /Library/WebServer/.pm2/logs/test-job-1-error.log
  269. // [pm_out_log_path] => /Library/WebServer/.pm2/logs/test-job-1-out.log
  270. // [instances] => 1
  271. // [exec_mode] => fork_mode
  272. // [exec_interpreter] => php
  273. // [pm_cwd] => /Users/plabudda/rsync.se.master/SE
  274. // [pm_exec_path] => /Users/plabudda/rsync.se.master/sbin/test-sleep-loop.php
  275. // [node_args] => Array:
  276. // [name] => test-job-1
  277. // [namespace] => default
  278. // [env] => Array:
  279. // [unique_id] => 80fefcff-8605-44cc-9829-bbcba4de4e7c
  280. // [test-job-1] => Array:
  281. // [PM2_HOME] => /Library/WebServer/.pm2
  282. // [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
  283. // [PWD] => /Users/plabudda/rsync.se.master/SE
  284. // [XPC_FLAGS] => 0x80
  285. // [XPC_SERVICE_NAME] => 0
  286. // [HOME] => /Library/WebServer
  287. // [SHLVL] => 1
  288. // [SERVER_INSTALL_PATH_PREFIX] => /Applications/Server.app/Contents/ServerRoot
  289. // [XPC_SERVICES_UNAVAILABLE] => 1
  290. // [MODULE_INSTALL_PATH_PREFIX] =>
  291. // [_] => /usr/local/bin/pm2
  292. // [__CF_USER_TEXT_ENCODING] => 0x46:0:0
  293. // [PM2_USAGE] => CLI
  294. // [merge_logs] => 1
  295. // [vizion] => 1
  296. // [autorestart] => 1
  297. // [watch] =>
  298. // [instance_var] => NODE_APP_INSTANCE
  299. // [pmx] => 1
  300. // [automation] => 1
  301. // [treekill] => 1
  302. // [username] => _www
  303. // [windowsHide] => 1
  304. // [kill_retry_time] => 100
  305. // [pm_id] => 0
  306. // [monit] => Array:
  307. // [memory] => 15720448
  308. // [cpu] => 0
  309. }