#!/usr/bin/env php rsc_fpops_est = 3600e9; // estimate: 1 GFLOPS-hour $job_params->rsc_fpops_bound = 86400e9; // limit: 1 GFLOPS-day $job_params->rsc_memory_bound = 5e8; // mem bound: 500MB $job_params->rsc_disk_bound = 1e9; // disk bound: 1GB $job_params->delay_bound = 7*86400; // delay bound: 1 week // global vars // $project_dir = null; $job_dir = getcwd(); $platform = `arch|tr -d "\n"`.'-pc-linux-gnu'; $infiles = array(); $infiles_phys = array(); // input filename with _WUID appended $outfiles = array(); $stdin_file = null; $stdout_file = null; $program = null; $program_phys = null; // the physical name of the program $cmdline_args = null; $app_name = null; $wu_template_filename = null; // relative to project dir $result_template_filename = null; // relative to project dir $wrapper_job_filename = null; $verbose = false; $wuid = null; function get_project_dir() { global $project_dir; $project_dir = getenv('BOINC_PROJECT_DIR'); if (!$project_dir) { echo "You must set the environment variable BOINC_PROJECT_DIR to the path of a BOINC project, e.g.: > export BOINC_PROJECT_DIR=~/projects/my_project or in csh: > setenv BOINC_PROJECT_DIR ~/projects/my_project "; exit(1); } } function usage() { echo "Usage: boinc_job [boinc-options] program [program-options] boinc-options: --platform p Run the program on platform p --infile f The program will use f as an input file --outfile f The program will use f as an output file --stdin f Direct f to the program's stdin --stdout f Direct the program's stdout to f --wait jobID Wait for the completion of an existing job --abort jobID Abort an existing job --jobs Show pending jobs --help Print this "; exit(1); } function error($msg) { echo "$msg\n"; exit(1); } function download_path($filename) { global $project_dir; return dir_hier_path($filename, "$project_dir/download", 1024); } function upload_path($filename) { global $project_dir; return dir_hier_path($filename, "$project_dir/upload", 1024); } function do_includes() { global $project_dir; chdir("$project_dir/html/ops"); require_once("../inc/boinc_db.inc"); require_once("../inc/dir_hier.inc"); BoincDb::get(); } function check_infiles() { global $infiles, $stdin_file, $job_dir; chdir($job_dir); foreach ($infiles as $i) { if (!file_exists($i)) { error("Missing input file $i\n"); } } if ($stdin_file) { if (!file_exists($stdin_file)) { error("Missing input file $stdin_file\n"); } } } function check_app_version() { global $platform, $app_name; $app_name = "single_job_$platform"; $app = BoincApp::lookup("name='$app_name'"); if (!$app) { error("This project isn't configured to run single jobs."); } } // make the job.xml file used by the wrapper // function make_wrapper_job_file() { global $program_phys, $stdin_file, $stdout_file, $cmdline_args, $wuid; global $project_dir, $wrapper_job_filename; chdir($project_dir); $wrapper_job_filename = "sj_$wuid.xml"; $path = download_path($wrapper_job_filename); $f = fopen($path, "w"); if (!$f) { error("Can't open $path"); } fwrite($f, " $program_phys "); if ($stdin_file) { fwrite($f, " $stdin_file\n"); } if ($stdout_file) { fwrite($f, " $stdout_file\n"); } if ($cmdline_args) { fwrite($f, " $cmdline_args\n"); } fwrite($f, " \n\n"); fclose($f); } function make_wu_template() { global $wuid, $infiles, $stdin_file, $program_phys, $wu_template_filename; global $project_dir, $job_params; chdir($project_dir); $wu_template_filename = "templates/sj_wu_template_$wuid"; $f = fopen($wu_template_filename, "w"); if (!$f) { error("Can't open $wu_template_filename"); } $n = count($infiles); $n++; // for job file if ($stdin_file) { $n++; } for ($i=0; $i<$n; $i++) { fwrite($f, " $i "); } // The program file needs to be executable. // Make it sticky too. // fwrite($f, " $i "); fwrite($f, "\n"); $i = 0; foreach($infiles as $infile) { fwrite($f, " $i $infile "); $i++; } if ($stdin_file) { fwrite($f, " $i $stdin_file "); $i++; } fwrite($f, " $i job.xml "); $i++; fwrite($f, " $i $program_phys "); fwrite($f, " $job_params->rsc_fpops_est $job_params->rsc_fpops_bound $job_params->rsc_memory_bound $job_params->rsc_disk_bound $job_params->delay_bound "); fclose($f); } function make_result_template() { global $wuid, $outfiles, $stdout_file, $project_dir; global $result_template_filename; chdir($project_dir); $result_template_filename = "templates/sj_result_template_$wuid"; $f = fopen($result_template_filename, "w"); if (!$f) { error("Can't open $result_template_filename"); } $i = 0; foreach($outfiles as $outfile) { fwrite($f, " 1e12 "); $i++; } if ($stdout_file) { fwrite($f, " 1e12 "); } fwrite($f, "\n"); $i = 0; foreach($outfiles as $outfile) { fwrite($f, " $outfile "); $i++; } if ($stdout_file) { fwrite($f, " $stdout_file "); } fwrite($f, "\n"); fclose($f); } // make the sj_WUID file // function make_job_file() { global $wuid, $job_dir, $project_dir; chdir($project_dir); $filename = "sj_$wuid"; $path = upload_path($filename); $f = fopen($path, "w"); if (!$f) { error("Can't open $path"); } fwrite($f, "$job_dir "); fclose($f); } function create_wu() { global $wuid; $name = md5(uniqid(rand(), true)); $wuid = BoincWorkunit::insert("(name, transition_time) values ('$name', ".PHP_INT_MAX.")"); } function create_job() { global $wuid, $app_name, $infiles_phys, $program_phys, $project_dir; global $result_template_filename, $wu_template_filename; global $wrapper_job_filename, $verbose; chdir($project_dir); $cmd = "bin/create_work --min_quorum 1 --target_nresults 1 --appname $app_name --wu_name sj_$wuid --wu_id $wuid --wu_template $wu_template_filename --result_template $result_template_filename"; foreach ($infiles_phys as $infile) { $cmd .= " $infile"; } $cmd .= " $wrapper_job_filename"; $cmd .= " $program_phys"; if ($verbose) { echo "Executing command: $cmd\n"; } system($cmd, $retval); if ($retval) { echo "create_work in $project_dir failed: $retval\n"; exit(1); } } // copy input files and program file to the download hierarchy // function copy_files() { global $infiles, $infiles_phys, $wuid, $job_dir, $program, $program_phys; global $verbose; chdir($job_dir); foreach ($infiles as $infile) { $filename = $infile.'_'.$wuid; $infiles_phys[] = $filename; $path = download_path($filename); if ($verbose) { echo "copying $infile to $path\n"; } copy($infile, $path); } $path = download_path($program_phys); if ($verbose) { echo "copying $program to $path\n"; } copy($program, $path); } // make sure the program is there, MD5 it, and get physical name // function check_program() { global $program, $job_dir, $program_phys, $platform; chdir($job_dir); if (!is_file($program)) { error("Program file $program not found"); } $m = md5_file($program); $m = substr($m, 0, 8); $program_phys = $program.'_'.$platform.'_'.$m; } function parse_args($argc, $argv) { global $platform, $infiles, $outfiles, $stdin_file, $stdout_file; global $program, $cmdline_args, $wuid, $verbose; for ($i=1; $i<$argc; $i++) { switch ($argv[$i]) { case '--help': usage(); case '--platform': $platform = $argv[++$i]; break; case '--infile': $infiles[] = $argv[++$i]; break; case '--outfile': $outfiles[] = $argv[++$i]; break; case '--stdin': $stdin_file = $argv[++$i]; break; case '--stdout': $stdout_file = $argv[++$i]; break; case '--verbose': $verbose = true; break; case '--wait': $wuid = $argv[++$i]; wait(); case '--abort': $wuid = $argv[++$i]; abort_job($wuid); case '--jobs': show_jobs(); default: if ($program) { $cmdline_args .= ''.$argv[$i]; } else { $program = $argv[$i]; } break; } } if (!$program) usage(); } function abort_job($wuid) { $wu = BoincWorkunit::lookup_id($wuid); if (!$wu) error("No such job"); $app = BoincApp::lookup_id($wu->appid); if (!strstr($app->name, "single_job")) { error("Not a boinc_submit job"); } if ($wu->error_mask) { echo "Job $wuid has already been aborted.\n"; exit; } $x = $wu->error_mask | 16; $now = time(); BoincResult::update_aux("server_state=5, outcome=5 where server_state=2 and workunitid=$wuid"); $wu->update("error_mask=$x, transition_time=$now"); echo "Job $wuid has been aborted.\n"; exit; } function show_jobs() { $apps = BoincApp::enum(""); foreach($apps as $app) { if (!strstr($app->name, "single_job")) continue; $avs = BoincAppVersion::enum("appid=$app->id"); $av = $avs[0]; $platform = BoincPlatform::lookup_id($av->platformid); echo "Jobs for $platform->user_friendly_name:\n"; $wus = BoincWorkunit::enum("appid=$app->id"); foreach ($wus as $wu) { show_job($wu); } } exit; } function show_result($result, $i) { switch ($result->server_state) { case 2: echo " Instance $i: unsent\n"; break; case 4: echo " Instance $i: in progress on host $result->hostid\n"; break; case 5: echo " Instance $i: completed on host $result->hostid\n"; break; } } function show_job($wu) { echo "Job $wu->id: "; switch ($wu->assimilate_state) { case 0: echo "in progress\n"; break; case 1: echo "being assimilated\n"; break; case 2: echo "completed\n"; break; } } function show_wu_status($wu) { $now = date("F j, Y, g:i A"); switch ($wu->assimilate_state) { case 0: echo "$now: job $wu->id is in progress\n"; $results = BoincResult::enum("workunitid=$wu->id"); $n = count($results); if ($n) { $i = 0; foreach ($results as $result) { show_result($result, $i); $i++; } } else { echo " (no instances yet)\n"; } break; case 1: echo "$now: job $wu->id is being assimilated\n"; break; case 2: echo "$now: job $wu->id completed\n"; exit; } } function wait() { global $wuid; while (1) { $wu = BoincWorkunit::lookup_id($wuid); if (!$wu) { echo "Job $wuid is not in the database\n"; exit; } show_wu_status($wu); sleep(10); } } get_project_dir(); do_includes(); parse_args($argc, $argv); check_infiles(); check_app_version(); check_program(); create_wu(); make_wrapper_job_file(); make_job_file(); make_wu_template(); make_result_template(); // from this point on, stdin file is like other input files if ($stdin_file) { $infiles[] = $stdin_file; } copy_files(); create_job(); wait(); ?>