PageRenderTime 30ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/dist/webpagetest/www/work/getwork.php

http://webpagetest.googlecode.com/
PHP | 503 lines | 386 code | 56 blank | 61 comment | 105 complexity | 1446edd7a387a1810db1cf653760623b MD5 | raw file
Possible License(s): AGPL-1.0, Apache-2.0, GPL-3.0, LGPL-3.0, MIT, BSD-3-Clause, ISC, LGPL-2.1
  1. <?php
  2. if(extension_loaded('newrelic')) {
  3. newrelic_add_custom_tracer('GetUpdate');
  4. newrelic_add_custom_tracer('GetVideoJob');
  5. newrelic_add_custom_tracer('GetJobFile');
  6. }
  7. chdir('..');
  8. $debug = false;
  9. include 'common.inc';
  10. set_time_limit(600);
  11. $is_json = isset($_GET['f']) && $_GET['f'] == 'json';
  12. $location = $_GET['location'];
  13. $key = @$_GET['key'];
  14. $recover = @$_GET['recover'];
  15. $pc = @$_GET['pc'];
  16. $ec2 = @$_GET['ec2'];
  17. $tester = null;
  18. if (@strlen($ec2)) {
  19. $tester = $ec2;
  20. } elseif (@strlen($pc)) {
  21. $tester = $pc . '-' . trim($_SERVER['REMOTE_ADDR']);
  22. } else {
  23. $tester = trim($_SERVER['REMOTE_ADDR']);
  24. }
  25. $supports_sharding = false;
  26. if (array_key_exists('shards', $_REQUEST) && $_REQUEST['shards'])
  27. $supports_sharding = true;
  28. logMsg("getwork.php location:$location tester:$tester ex2:$ec2 recover:$recover");
  29. $is_done = false;
  30. if (!array_key_exists('freedisk', $_GET) || (float)$_GET['freedisk'] > 0.1) {
  31. // See if there is an update.
  32. if (!$is_done && $_GET['ver']) {
  33. $is_done = GetUpdate();
  34. }
  35. // see if there is a video job
  36. if (!$is_done && @$_GET['video']) {
  37. $is_done = GetVideoJob();
  38. }
  39. if (!$is_done) {
  40. $is_done = GetJob();
  41. }
  42. }
  43. // kick off any cron work we need to do asynchronously
  44. CheckCron();
  45. // Send back a blank result if we didn't have anything.
  46. if (!$is_done) {
  47. header('Content-type: text/plain');
  48. header("Cache-Control: no-cache, must-revalidate");
  49. header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
  50. }
  51. /**
  52. * Get an actual task to complete
  53. *
  54. */
  55. function GetJob() {
  56. $is_done = false;
  57. global $location;
  58. global $key;
  59. global $pc;
  60. global $ec2;
  61. global $tester;
  62. global $recover;
  63. global $is_json;
  64. // load all of the locations
  65. $locations = parse_ini_file('./settings/locations.ini', true);
  66. BuildLocations($locations);
  67. $workDir = $locations[$location]['localDir'];
  68. $locKey = @$locations[$location]['key'];
  69. if (strlen($workDir) && (!strlen($locKey) || !strcmp($key, $locKey))) {
  70. // see if the tester is marked as being offline
  71. $offline = false;
  72. if( strlen($ec2) && strlen($locations[$location]['ec2']) && is_file('./ec2/ec2.inc.php') )
  73. {
  74. logMsg("Checking $ec2 to see if it is offline");
  75. require_once('./ec2/ec2.inc.php');
  76. if( !EC2_CheckInstance($location, $locations[$location]['ec2'], $ec2) )
  77. {
  78. logMsg("$ec2 is offline");
  79. $offline = true;
  80. }
  81. }
  82. if( $lock = LockLocation($location) )
  83. {
  84. // make sure the work directory actually exists
  85. if( !is_dir($workDir) )
  86. mkdir($workDir, 0777, true);
  87. $now = time();
  88. $testers = GetTesters($location);
  89. // make sure the tester isn't marked as offline (usually when shutting down EC2 instances)
  90. if(!@$testers[$tester]['offline']) {
  91. $fileName = GetJobFile($workDir, $priority);
  92. if( isset($fileName) && strlen($fileName) )
  93. {
  94. $is_done = true;
  95. $delete = true;
  96. if ($is_json)
  97. header ("Content-type: application/json");
  98. else
  99. header('Content-type: text/plain');
  100. header("Cache-Control: no-cache, must-revalidate");
  101. header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
  102. // send the test info to the test agent
  103. $testInfo = file_get_contents("$workDir/$fileName");
  104. // extract the test ID from the job file
  105. if( preg_match('/Test ID=([^\r\n]+)\r/i', $testInfo, $matches) )
  106. $testId = trim($matches[1]);
  107. if( isset($testId) ) {
  108. // figure out the path to the results
  109. $testPath = './' . GetTestPath($testId);
  110. // flag the test with the start time
  111. $ini = file_get_contents("$testPath/testinfo.ini");
  112. if (stripos($ini, 'startTime=') === false) {
  113. $time = time();
  114. $start = "[test]\r\nstartTime=" . gmdate("m/d/y G:i:s", $time);
  115. $out = str_replace('[test]', $start, $ini);
  116. file_put_contents("$testPath/testinfo.ini", $out);
  117. }
  118. if( gz_is_file("$testPath/testinfo.json") ) {
  119. $testInfoJson = json_decode(gz_file_get_contents("$testPath/testinfo.json"), true);
  120. if (!array_key_exists('tester', $testInfoJson) || !strlen($testInfoJson['tester']))
  121. $testInfoJson['tester'] = $tester;
  122. if (!array_key_exists('started', $testInfoJson) || !strlen($testInfoJson['started']))
  123. $testInfoJson['started'] = $time;
  124. $testInfoJson['id'] = $testId;
  125. ProcessTestShard($testInfoJson, $testInfo, $delete);
  126. gz_file_put_contents("$testPath/testinfo.json", json_encode($testInfoJson));
  127. }
  128. }
  129. if ($delete) {
  130. unlink("$workDir/$fileName");
  131. } else {
  132. AddJobFileHead($workDir, $fileName, $priority, true);
  133. }
  134. if ($is_json) {
  135. $testJson = array();
  136. $script = '';
  137. $isScript = false;
  138. $lines = explode("\r\n", $testInfo);
  139. foreach($lines as $line) {
  140. if( strlen(trim($line)) ) {
  141. if( $isScript ) {
  142. if( strlen($script) )
  143. $script .= "\r\n";
  144. $script .= $line;
  145. } elseif( !strcasecmp($line, '[Script]') )
  146. $isScript = true;
  147. else {
  148. $pos = strpos($line, '=');
  149. if( $pos > -1 ) {
  150. $key = trim(substr($line, 0, $pos));
  151. $value = trim(substr($line, $pos + 1));
  152. if( strlen($key) && strlen($value) ) {
  153. if( is_numeric($value) )
  154. $testJson[$key] = (int)$value;
  155. else
  156. $testJson[$key] = $value;
  157. }
  158. }
  159. }
  160. }
  161. }
  162. if( strlen($script) )
  163. $testJson['script'] = $script;
  164. echo json_encode($testJson);
  165. }
  166. else
  167. echo $testInfo;
  168. $ok = true;
  169. }
  170. // zero out the tracked page loads in case some got lost
  171. if( !$is_done ) {
  172. $tests = json_decode(file_get_contents("./tmp/$location.tests"), true);
  173. if( $tests ) {
  174. $tests['tests'] = 0;
  175. file_put_contents("./tmp/$location.tests", json_encode($tests));
  176. }
  177. }
  178. }
  179. UnlockLocation($lock);
  180. // keep track of the last time this location reported in
  181. $testerInfo = array();
  182. $testerInfo['ip'] = $_SERVER['REMOTE_ADDR'];
  183. $testerInfo['pc'] = $pc;
  184. $testerInfo['ec2'] = $ec2;
  185. $testerInfo['ver'] = $_GET['ver'];
  186. $testerInfo['freedisk'] = @$_GET['freedisk'];
  187. $testerInfo['ie'] = @$_GET['ie'];
  188. $testerInfo['video'] = @$_GET['video'];
  189. $testerInfo['test'] = '';
  190. if (isset($testId)) {
  191. $testerInfo['test'] = $testId;
  192. }
  193. UpdateTester($location, $tester, $testerInfo);
  194. }
  195. }
  196. return $is_done;
  197. }
  198. /**
  199. * Get the next job from the work queue
  200. *
  201. * @param mixed $workDir
  202. */
  203. function GetNextJobFile($workDir)
  204. {
  205. $fileName = null;
  206. // get a list of all of the files in the directory and store them indexed by filetime
  207. $files = array();
  208. $f = scandir($workDir);
  209. foreach( $f as $file )
  210. {
  211. $fileTime = filemtime("$workDir/$file");
  212. if( $fileTime && !isset($files[$fileTime]) )
  213. $files[$fileTime] = $file;
  214. else
  215. $files[] = $file;
  216. }
  217. // sort it by time
  218. ksort($files);
  219. // loop through all of the possible extension types in priority order
  220. $priority = array( "url", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9" );
  221. foreach( $priority as $ext )
  222. {
  223. foreach( $files as $file )
  224. {
  225. if(is_file("$workDir/$file"))
  226. {
  227. $parts = pathinfo($file);
  228. if( !strcasecmp( $parts['extension'], $ext) )
  229. {
  230. $fileName = "$workDir/$file";
  231. break 2;
  232. }
  233. }
  234. }
  235. }
  236. return $fileName;
  237. }
  238. /**
  239. * See if there is a video rendering job that needs to be done
  240. *
  241. */
  242. function GetVideoJob()
  243. {
  244. global $debug;
  245. global $location;
  246. global $tester;
  247. $ret = false;
  248. $videoDir = './work/video';
  249. if( is_dir($videoDir) )
  250. {
  251. // lock the directory
  252. $lockFile = fopen( './tmp/video.lock', 'w', false);
  253. if( $lockFile )
  254. {
  255. if( flock($lockFile, LOCK_EX) )
  256. {
  257. // look for the first zip file
  258. $dir = opendir($videoDir);
  259. if( $dir )
  260. {
  261. $testFile = null;
  262. while(!$testFile && $file = readdir($dir))
  263. {
  264. $path = $videoDir . "/$file";
  265. if( is_file($path) && stripos($file, '.zip') )
  266. $testFile = $path;
  267. }
  268. if( $testFile )
  269. {
  270. header('Content-Type: application/zip');
  271. header("Cache-Control: no-cache, must-revalidate");
  272. header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
  273. logMsg("Video job $testFile sent to $tester from $location");
  274. readfile_chunked($testFile);
  275. $ret = true;
  276. // delete the test file
  277. unlink($testFile);
  278. }
  279. closedir($dir);
  280. }
  281. }
  282. fclose($lockFile);
  283. }
  284. }
  285. return $ret;
  286. }
  287. /**
  288. * See if there is a software update
  289. *
  290. */
  291. function GetUpdate()
  292. {
  293. global $location;
  294. $ret = false;
  295. // see if the client sent a version number
  296. if( $_GET['ver'] )
  297. {
  298. $fileBase = '';
  299. if( isset($_GET['software']) && strlen($_GET['software']) )
  300. $fileBase = trim($_GET['software']);
  301. $updateDir = './work/update';
  302. if( is_dir("$updateDir/$location") )
  303. $updateDir = "$updateDir/$location";
  304. // see if we have any software updates
  305. if( is_file("$updateDir/{$fileBase}update.ini") && is_file("$updateDir/{$fileBase}update.zip") )
  306. {
  307. $update = parse_ini_file("$updateDir/{$fileBase}update.ini");
  308. if( $update['ver'] && (int)$update['ver'] != (int)$_GET['ver'] )
  309. {
  310. header('Content-Type: application/zip');
  311. header("Cache-Control: no-cache, must-revalidate");
  312. header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
  313. readfile_chunked("$updateDir/{$fileBase}update.zip");
  314. $ret = true;
  315. }
  316. }
  317. }
  318. return $ret;
  319. }
  320. /**
  321. * Send a quick http request locally if we need to process cron events (to each of the cron entry points)
  322. *
  323. * This only runs events on 15-minute intervals and tries to keep it close to the clock increments (00, 15, 30, 45)
  324. *
  325. */
  326. function CheckCron() {
  327. // open and lock the cron job file - abandon quickly if we can't get a lock
  328. $should_run = false;
  329. $cron_lock = fopen('./tmp/wpt_cron.lock', 'w+');
  330. if ($cron_lock !== false) {
  331. if (flock($cron_lock, LOCK_EX | LOCK_NB)) {
  332. $last_run = 0;
  333. if (is_file('./tmp/wpt_cron.dat'))
  334. $last_run = file_get_contents('./tmp/wpt_cron.dat');
  335. $now = time();
  336. $elapsed = $now - $last_run;
  337. if (!$last_run || $elapsed > 120) {
  338. if ($elapsed > 1200) {
  339. // if it has been over 20 minutes, run regardless of the wall-clock time
  340. $should_run = true;
  341. } else {
  342. $minute = gmdate('i', $now) % 5;
  343. if ($minute < 2)
  344. $should_run = true;
  345. }
  346. }
  347. if ($should_run) {
  348. file_put_contents('./tmp/wpt_cron.dat', $now);
  349. }
  350. }
  351. fclose($cron_lock);
  352. }
  353. // send the crone requests
  354. if ($should_run) {
  355. if (is_file('./settings/benchmarks/benchmarks.txt') &&
  356. is_file('./benchmarks/cron.php')) {
  357. SendCronRequest('/benchmarks/cron.php');
  358. }
  359. }
  360. }
  361. /**
  362. * Send a request with a really short timeout to fire an async cron event
  363. *
  364. * @param mixed $relative_url
  365. */
  366. function SendCronRequest($relative_url) {
  367. $url = "http://{$_SERVER['HTTP_HOST']}$relative_url";
  368. $c = curl_init();
  369. curl_setopt($c, CURLOPT_URL, $url);
  370. curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
  371. curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
  372. curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 1);
  373. curl_setopt($c, CURLOPT_TIMEOUT, 1);
  374. curl_exec($c);
  375. curl_close($c);
  376. }
  377. /**
  378. * Process a sharded test
  379. *
  380. * @param mixed $testInfo
  381. */
  382. function ProcessTestShard(&$testInfo, &$test, &$delete) {
  383. global $supports_sharding;
  384. global $tester;
  385. if (isset($testInfo) && array_key_exists('shard_test', $testInfo) && $testInfo['shard_test']) {
  386. if ($supports_sharding) {
  387. if( $testLock = fopen( "$testPath/test.lock", 'w', false) )
  388. flock($testLock, LOCK_EX);
  389. $done = true;
  390. $assigned_run = 0;
  391. if (!array_key_exists('test_runs', $testInfo)) {
  392. $testInfo['test_runs'] = array();
  393. for ($run = 1; $run <= $testInfo['runs']; $run++) {
  394. $testInfo['test_runs'][$run] = array();
  395. }
  396. }
  397. // find a run to assign to a tester
  398. for ($run = 1; $run <= $testInfo['runs']; $run++) {
  399. if (!array_key_exists('tester', $testInfo['test_runs'][$run])) {
  400. $testInfo['test_runs'][$run]['tester'] = $tester;
  401. $testInfo['test_runs'][$run]['started'] = time();
  402. $testInfo['test_runs'][$run]['done'] = false;
  403. $assigned_run = $run;
  404. break;
  405. }
  406. }
  407. // go through again and see if all tests have been assigned
  408. for ($run = 1; $run <= $testInfo['runs']; $run++) {
  409. if (!array_key_exists('tester', $testInfo['test_runs'][$run])) {
  410. $done = false;
  411. break;
  412. }
  413. }
  414. if ($assigned_run) {
  415. $append = "run=$assigned_run\r\n";
  416. // Figure out if this test needs to be discarded
  417. $index = $assigned_run;
  418. if (array_key_exists('discard', $testInfo)) {
  419. if ($index <= $testInfo['discard']) {
  420. $append .= "discardTest=1\r\n";
  421. $index = 1;
  422. $done = true;
  423. $testInfo['test_runs'][$assigned_run]['discarded'] = true;
  424. } else {
  425. $index -= $testInfo['discard'];
  426. }
  427. }
  428. $append .= "index=$index\r\n";
  429. $insert = strpos($test, "\nurl");
  430. if ($insert !== false) {
  431. $test = substr($test, 0, $insert + 1) .
  432. $append .
  433. substr($test, $insert + 1);
  434. } else {
  435. $test = "run=$assigned_run\r\n" + $test;
  436. }
  437. }
  438. if (!$done)
  439. $delete = false;
  440. if ($testLock)
  441. fclose($testLock);
  442. } else {
  443. $testInfo['shard_test'] = 0;
  444. }
  445. }
  446. }
  447. ?>