/** * For each IP/Installer pair, keep track of the last 4 checks and if they * were within the last hour fail the request. * * @param mixed $installer */ function CheckIp($installer) { $ok = true; $ip = $_SERVER["REMOTE_ADDR"]; if (isset($ip) && strlen($ip)) { $lock = Lock("Installers", true, 5); if ($lock) { $now = time(); $file = "./tmp/installers.dat"; if (gz_is_file($file)) { $history = json_decode(gz_file_get_contents($file), true); } if (!isset($history) || !is_array($history)) { $history = array(); } if (isset($history[$ip])) { if (isset($history[$ip][$installer])) { $history[$ip][$installer][] = $now; if (count($history[$ip][$installer]) > 10) { array_shift($history[$ip][$installer]); } if (isset($history[$ip]["last-{$installer}"]) && $now - $history[$ip]["last-{$installer}"] < 3600) { $count = 0; foreach ($history[$ip][$installer] as $time) { if ($now - $time < 3600) { $count++; } } if ($count > 4) { $ok = false; } } } else { $history[$ip][$installer] = array($now); } } else { $history[$ip] = array($installer => array($now)); } $history[$ip]['last'] = $now; if ($ok) { $history[$ip]["last-{$installer}"] = $now; } // prune any agents that haven't connected in 7 days foreach ($history as $agent => $info) { if ($now - $info['last'] > 604800) { unset($history[$agent]); } } gz_file_put_contents($file, json_encode($history)); Unlock($lock); } } return $ok; }
function GetLastFrameHistogram($test, $run) { $histogram = null; $testPath = GetTestPath($test); $videoPath = "./{$testPath}/video_{$run}"; $files = glob("{$videoPath}/*.jpg"); if ($files) { rsort($files); $lastFrame = $files[0]; if (gz_is_file("{$testPath}/{$run}.0.histograms.json")) { $histograms = json_decode(gz_file_get_contents("{$testPath}/{$run}.0.histograms.json"), true); } $histogram = GetImageHistogram($lastFrame, null, $histograms); } return $histogram; }
/** * Recursively compress the text files in the given directory and all directories under it * * @param mixed $dir */ function CheckDir($dir, $startWith = '') { $started = false; if (!strlen($startWith)) { $started = true; } // compress the text data files $f = scandir($dir); foreach ($f as $file) { if (!$started && $file == $startWith) { $started = true; } if ($started) { if (gz_is_file("{$dir}/{$file}/testinfo.json")) { CheckTest("{$dir}/{$file}"); } elseif (is_dir("{$dir}/{$file}") && $file != '.' && $file != '..') { CheckDir("{$dir}/{$file}"); } } } }
function ResubmitTest($id) { echo "{$id} - Resubmitting..."; $testPath = './' . GetTestPath($id); if (gz_is_file("{$testPath}/testinfo.json")) { $test = json_decode(gz_file_get_contents("{$testPath}/testinfo.json"), true); if (array_key_exists('job_file', $test) && array_key_exists('location', $test) && is_file("{$testPath}/test.job")) { if ($lock = LockLocation($test['location'])) { if (copy("{$testPath}/test.job", $test['job_file'])) { $files = scandir($testPath); foreach ($files as $file) { if ($file != '.' && $file != '..' && strncasecmp($file, 'test', 4)) { if (is_file("{$testPath}/{$file}")) { unlink("{$testPath}/{$file}"); } elseif (is_dir("{$testPath}/{$file}")) { delTree("{$testPath}/{$file}"); } } } AddJobFile($test['workdir'], $test['job'], $test['priority'], false); $test['started'] = time(); unset($test['completeTime']); gz_file_put_contents("{$testPath}/testinfo.json", json_encode($test)); echo "OK"; } else { echo "Failed to copy job file"; } UnlockLocation($lock); } else { echo "Failed to lock location"; } } else { echo "Invalid test"; } } else { echo "Test not found"; } echo "\n"; }
function GetDevToolsCPUTime($testPath, $run, $cached, $endTime = 0) { $times = null; $ver = 1; $ver = 2; $cacheFile = "{$testPath}/{$run}.{$cached}.devToolsCPUTime.{$ver}"; if (gz_is_file($cacheFile)) { $cache = json_decode(gz_file_get_contents($cacheFile), true); } // If an end time wasn't specified, figure out what the fully loaded time is if (!$endTime) { if (GetDevToolsRequests($testPath, $run, $cached, $requests, $pageData) && isset($pageData) && is_array($pageData) && isset($pageData['fullyLoaded'])) { $endTime = $pageData['fullyLoaded']; } } if (isset($cache[$endTime])) { $times = $cache[$endTime]; } else { $slices = DevToolsGetCPUSlices($testPath, $run, $cached); if (isset($slices) && is_array($slices) && isset($slices[0]) && is_array($slices[0]) && count($slices[0])) { $times = array('Idle' => 0.0); foreach ($slices[0] as $ms => $breakdown) { if (!$endTime || $ms < $endTime) { $idle = 1.0; if (isset($breakdown) && is_array($breakdown) && count($breakdown)) { foreach ($breakdown as $event => $ms_time) { if (!isset($times[$event])) { $times[$event] = 0; } $times[$event] += $ms_time; $idle -= $ms_time; } } $times['Idle'] += $idle; } } // round the times to the nearest millisecond $total = 0; foreach ($times as $event => &$val) { $val = round($val); if ($event !== 'Idle') { $total += $val; } } if ($endTime && $endTime > $total) { $times['Idle'] = $endTime - $total; } } $cache[$endTime] = $times; gz_file_put_contents($cacheFile, json_encode($cache)); } return $times; }
/** * Calculate the progress for all of the images in a given directory */ function GetVisualProgress($testPath, $run, $cached, $options = null, $end = null, $startOffset = null) { $frames = null; if (substr($testPath, 0, 1) !== '.') { $testPath = './' . $testPath; } $testInfo = GetTestInfo($testPath); $completed = IsTestRunComplete($run, $testInfo); $video_directory = "{$testPath}/video_{$run}"; if ($cached) { $video_directory .= '_cached'; } $cache_file = "{$testPath}/{$run}.{$cached}.visual.dat"; if (!isset($startOffset)) { $startOffset = 0; } $visual_data_file = "{$testPath}/llab_{$run}.{$cached}.visual.dat"; if (gz_is_file($visual_data_file)) { $visual_data = json_decode(gz_file_get_contents($visual_data_file), true); // see if we are processing an externally-uploaded visual data file if (isset($visual_data['timespans']['page_load']['startOffset'])) { $startOffset += $visual_data['timespans']['page_load']['startOffset']; } } $dirty = false; $current_version = VIDEO_CODE_VERSION; if (isset($end)) { if (is_numeric($end)) { $end = (int) ($end * 1000); } else { unset($end); } } if (!isset($end) && !isset($options) && gz_is_file($cache_file)) { $frames = json_decode(gz_file_get_contents($cache_file), true); if (!array_key_exists('frames', $frames) || !array_key_exists('version', $frames)) { unset($frames); } elseif (array_key_exists('version', $frames) && $frames['version'] !== $current_version) { unset($frames); } } $base_path = substr($video_directory, 1); if ((!isset($frames) || !count($frames)) && (is_dir($video_directory) || gz_is_file("{$testPath}/{$run}.{$cached}.histograms.json"))) { $frames = array('version' => $current_version); $frames['frames'] = array(); $dirty = true; if (is_dir($video_directory)) { $files = scandir($video_directory); $last_file = null; $first_file = null; $previous_file = null; foreach ($files as $file) { if (strpos($file, 'frame_') !== false && strpos($file, '.hist') === false) { $parts = explode('_', $file); if (count($parts) >= 2) { $time = (int) $parts[1] * 100 - $startOffset; if ($time >= 0 && (!isset($end) || $time <= $end)) { if (isset($previous_file) && !array_key_exists(0, $frames['frames']) && $time > 0) { $frames['frames'][0] = array('path' => "{$base_path}/{$previous_file}", 'file' => $previous_file); $first_file = $previous_file; } elseif (!isset($first_file)) { $first_file = $file; } $last_file = $file; $frames['frames'][$time] = array('path' => "{$base_path}/{$file}", 'file' => $file); } $previous_file = $file; } } elseif (strpos($file, 'ms_') !== false && strpos($file, '.hist') === false) { $parts = explode('_', $file); if (count($parts) >= 2) { $time = intval($parts[1]) - $startOffset; if ($time >= 0 && (!isset($end) || $time <= $end)) { if (isset($previous_file) && !array_key_exists(0, $frames['frames']) && $time > 0) { $frames['frames'][0] = array('path' => "{$base_path}/{$previous_file}", 'file' => $previous_file); $first_file = $previous_file; } elseif (!isset($first_file)) { $first_file = $file; } $last_file = $file; $frames['frames'][$time] = array('path' => "{$base_path}/{$file}", 'file' => $file); } $previous_file = $file; } } } if (count($frames['frames']) == 1) { foreach ($frames['frames'] as $time => &$frame) { $frame['progress'] = 100; $frames['complete'] = $time; } } elseif (isset($first_file) && strlen($first_file) && isset($last_file) && strlen($last_file) && count($frames['frames'])) { $histograms = null; if (gz_is_file("{$testPath}/{$run}.{$cached}.histograms.json")) { $histograms = json_decode(gz_file_get_contents("{$testPath}/{$run}.{$cached}.histograms.json"), true); } $start_histogram = GetImageHistogram("{$video_directory}/{$first_file}", $options, $histograms); $final_histogram = GetImageHistogram("{$video_directory}/{$last_file}", $options, $histograms); foreach ($frames['frames'] as $time => &$frame) { $histogram = GetImageHistogram("{$video_directory}/{$frame['file']}", $options, $histograms); $frame['progress'] = CalculateFrameProgress($histogram, $start_histogram, $final_histogram, 5); if ($frame['progress'] == 100 && !array_key_exists('complete', $frames)) { $frames['complete'] = $time; } } } } elseif (gz_is_file("{$testPath}/{$run}.{$cached}.histograms.json")) { $raw = json_decode(gz_file_get_contents("{$testPath}/{$run}.{$cached}.histograms.json"), true); $histograms = array(); foreach ($raw as $h) { if (isset($h['time']) && isset($h['histogram'])) { $histograms[$h['time']] = $h['histogram']; } } ksort($histograms, SORT_NUMERIC); $final_histogram = end($histograms); $start_histogram = reset($histograms); foreach ($histograms as $time => $histogram) { $frames['frames'][$time] = array(); $progress = CalculateFrameProgress($histogram, $start_histogram, $final_histogram, 5); $frames['frames'][$time]['progress'] = $progress; if ($progress == 100 && !isset($frames['complete'])) { $frames['complete'] = $time; } } } } if (isset($frames) && !array_key_exists('SpeedIndex', $frames)) { $dirty = true; $frames['SpeedIndex'] = CalculateSpeedIndex($frames); } if (isset($frames)) { $frames['visualComplete'] = 0; foreach ($frames['frames'] as $time => &$frame) { if ($frame['progress'] > 0 && !array_key_exists('startRender', $frames)) { $frames['startRender'] = $time; } if (!$frames['visualComplete'] && $frame['progress'] == 100) { $frames['visualComplete'] = $time; } // fix up the frame paths in case we have a cached version referencing a relay path if (isset($frame['path'])) { $frame['path'] = $base_path . '/' . basename($frame['path']); } } } if ($completed && !isset($end) && !isset($options) && $dirty && isset($frames) && count($frames)) { gz_file_put_contents($cache_file, json_encode($frames)); } return $frames; }
<?php include 'common.inc'; $file = "{$testPath}/{$_GET['file']}"; if (isset($_GET['file']) && strlen($_GET['file']) && strpos($_GET['file'], '/') === false && strpos($_GET['file'], '\\') === false && strpos($_GET['file'], '..') === false && strpos($_GET['file'], 'testinfo') === false && gz_is_file($file)) { header("Content-disposition: attachment; filename={$_GET['file']}"); if (strpos($_GET['file'], 'pagespeed') !== false || strpos($_GET['file'], '.json') !== false) { header("Content-type: application/json"); } else { header("Content-type: application/octet-stream"); } gz_readfile_chunked($file); } else { header("HTTP/1.0 404 Not Found"); }
$labelHeight = 0; } $start = microtime(true); // if FreeType isn't supported we can't draw text $gdinfo = gd_info(); if (!isset($gdinfo['FreeType Support']) || !$gdinfo['FreeType Support']) { $labelHeight = 0; $timeHeight = 0; } // Load the information about the video that needs rendering if (isset($_REQUEST['id'])) { $videoId = trim($_REQUEST['id']); $videoPath = './' . GetVideoPath($_REQUEST['id']); if (!is_file("{$videoPath}/video.ini")) { $optionsFile = "{$videoPath}/testinfo.json"; if (gz_is_file($optionsFile)) { $tests = json_decode(gz_file_get_contents($optionsFile), true); if (isset($tests) && !is_array($tests)) { unset($tests); } } } } // Render the video if (isset($tests) && count($tests)) { $lock = Lock("video-{$videoId}", false, 600); if ($lock) { RenderVideo($tests); if (is_file("{$videoPath}/render.mp4")) { rename("{$videoPath}/render.mp4", "{$videoPath}/video.mp4"); }
/** * @param TestPaths $localPaths Paths for this run/step to get the CPU time for * @param int $endTime End time to consider (optional, will be retrieved from requests otherwise) * @return array */ function GetDevToolsCPUTimeForStep($localPaths, $endTime = 0) { if (!$endTime) { require_once __DIR__ . '/page_data.inc'; $runCompleted = IsTestRunComplete($localPaths->getRunNumber(), $testInfo); $pageData = loadPageStepData($localPaths, $runCompleted); if (isset($pageData) && is_array($pageData) && isset($pageData['fullyLoaded'])) { $endTime = $pageData['fullyLoaded']; } } $times = null; $ver = 3; $cacheFile = $localPaths->devtoolsCPUTimeCacheFile($ver); if (gz_is_file($cacheFile)) { $cache = json_decode(gz_file_get_contents($cacheFile), true); } if (isset($cache) && is_array($cache) && isset($cache[$endTime])) { $times = $cache[$endTime]; } else { $cpu = DevToolsGetCPUSlicesForStep($localPaths); if (isset($cpu) && is_array($cpu) && isset($cpu['main_thread']) && isset($cpu['slices'][$cpu['main_thread']]) && isset($cpu['slice_usecs'])) { $busy = 0; $times = array(); if (!$endTime && isset($cpu['total_usecs'])) { $endTime = $cpu['total_usecs'] / 1000; } foreach ($cpu['slices'][$cpu['main_thread']] as $name => $slices) { $last_slice = min(intval(ceil($endTime * 1000 / $cpu['slice_usecs'])), count($slices)); $times[$name] = 0; for ($i = 0; $i < $last_slice; $i++) { $times[$name] += $slices[$i] / 1000.0; } $busy += $times[$name]; $times[$name] = intval(round($times[$name])); } $times['Idle'] = max($endTime - intval(round($busy)), 0); } // Cache the result if (!isset($cache) || !is_array($cache)) { $cache = array(); } $cache[$endTime] = $times; gz_file_put_contents($cacheFile, json_encode($cache)); } return $times; }
<?php /* To update the timeline, clone the inspector from a chromium build: out\Release\resources\inspector */ chdir('..'); include 'common.inc'; $newTimeline = gz_is_file("{$testPath}/{$run}{$cachedText}_trace.json"); $timelineUrlParam = "/getTimeline.php?timeline=t:{$id},r:{$run},c:{$cached}"; ?> <!DOCTYPE html> <html> <head> <style type="text/css"> html, body{min-height: 100% !important; height: 100%;} body {margin:0px;padding:0px;overflow:hidden;} #devtools {overflow:hidden;height:100%;width:100%} </style> </head> <body> <script> function DevToolsLoaded() { var devTools = document.getElementById("devtools").contentWindow; <?php if (!$newTimeline) { echo "devTools.InspectorFrontendAPI._runOnceLoaded(function(){(devTools.WebInspector.inspectorView.showPanel(\"timeline\")).loadFromURL(\"{$timelineUrlParam}\");});\n"; } ?> } </script> <?php
/** * @param TestStepResult $stepResult * @return string Markup with links */ private function _getNetlogLinks($stepResult) { $localPaths = $stepResult->createTestPaths(); if (!gz_is_file($localPaths->netlogFile())) { return ""; } $urlGenerator = $stepResult->createUrlGenerator("", FRIENDLY_URLS); $zipUrl = $urlGenerator->getGZip($stepResult->createTestPaths("")->netlogFile()); return "<br><br><a href=\"{$zipUrl}\" title=\"Download Network Log\">Net Log</a>"; }
/** * Build the data set * * @param mixed $pageData */ function BuildHAR(&$pageData, $id, $testPath, $options) { $result = array(); $entries = array(); $result['log'] = array(); $result['log']['version'] = '1.1'; $result['log']['creator'] = array('name' => 'WebPagetest', 'version' => VER_WEBPAGETEST); $result['log']['pages'] = array(); foreach ($pageData as $run => $pageRun) { foreach ($pageRun as $cached => $data) { $cached_text = ''; if ($cached) { $cached_text = '_Cached'; } if (!array_key_exists('browser', $result['log'])) { $result['log']['browser'] = array('name' => $data['browser_name'], 'version' => $data['browser_version']); } $pd = array(); $pd['startedDateTime'] = msdate($data['date']); $pd['title'] = "Run {$run}, "; if ($cached) { $pd['title'] .= "Repeat View"; } else { $pd['title'] .= "First View"; } $pd['title'] .= " for " . $data['URL']; $pd['id'] = "page_{$run}_{$cached}"; $pd['pageTimings'] = array('onLoad' => $data['docTime'], 'onContentLoad' => -1, '_startRender' => $data['render']); // add the pagespeed score if (gz_is_file("{$testPath}/{$run}{$cached_text}_pagespeed.txt")) { $pagespeed_data = LoadPageSpeedData("{$testPath}/{$run}{$cached_text}_pagespeed.txt"); if ($pagespeed_data) { $score = GetPageSpeedScore(null, $pagespeed_data); if (strlen($score)) { $pd['_pageSpeed'] = array('score' => $score, 'result' => $pagespeed_data); } } } // dump all of our metrics into the har data as custom fields foreach ($data as $name => $value) { if (!is_array($value)) { $pd["_{$name}"] = $value; } } // add the page-level ldata to the result $result['log']['pages'][] = $pd; // now add the object-level data to the result $secure = false; $haveLocations = false; $requests = getRequests($id, $testPath, $run, $cached, $secure, $haveLocations, false, true); foreach ($requests as &$r) { $entry = array(); $entry['pageref'] = $pd['id']; $entry['startedDateTime'] = msdate((double) $data['date'] + $r['load_start'] / 1000.0); $entry['time'] = $r['all_ms']; $request = array(); $request['method'] = $r['method']; $protocol = $r['is_secure'] ? 'https://' : 'http://'; $request['url'] = $protocol . $r['host'] . $r['url']; $request['headersSize'] = -1; $request['bodySize'] = -1; $request['cookies'] = array(); $request['headers'] = array(); $ver = ''; $headersSize = 0; if (isset($r['headers']) && isset($r['headers']['request'])) { foreach ($r['headers']['request'] as &$header) { $headersSize += strlen($header) + 2; // add 2 for the \r\n that is on the raw headers $pos = strpos($header, ':'); if ($pos > 0) { $name = trim(substr($header, 0, $pos)); $val = trim(substr($header, $pos + 1)); if (strlen($name)) { $request['headers'][] = array('name' => $name, 'value' => $val); } // parse out any cookies if (!strcasecmp($name, 'cookie')) { $cookies = explode(';', $val); foreach ($cookies as &$cookie) { $pos = strpos($cookie, '='); if ($pos > 0) { $name = (string) trim(substr($cookie, 0, $pos)); $val = (string) trim(substr($cookie, $pos + 1)); if (strlen($name)) { $request['cookies'][] = array('name' => $name, 'value' => $val); } } } } } else { $pos = strpos($header, 'HTTP/'); if ($pos >= 0) { $ver = (string) trim(substr($header, $pos + 5, 3)); } } } } if ($headersSize) { $request['headersSize'] = $headersSize; } $request['httpVersion'] = $ver; $request['queryString'] = array(); $parts = parse_url($request['url']); if (isset($parts['query'])) { $qs = array(); parse_str($parts['query'], $qs); foreach ($qs as $name => $val) { $request['queryString'][] = array('name' => (string) $name, 'value' => (string) $val); } } if (!strcasecmp(trim($request['method']), 'post')) { $request['postData'] = array(); $request['postData']['mimeType'] = ''; $request['postData']['text'] = ''; } $entry['request'] = $request; $response = array(); $response['status'] = (int) $r['responseCode']; $response['statusText'] = ''; $response['headersSize'] = -1; $response['bodySize'] = (int) $r['objectSize']; $response['headers'] = array(); $ver = ''; $loc = ''; $headersSize = 0; if (isset($r['headers']) && isset($r['headers']['response'])) { foreach ($r['headers']['response'] as &$header) { $headersSize += strlen($header) + 2; // add 2 for the \r\n that is on the raw headers $pos = strpos($header, ':'); if ($pos > 0) { $name = (string) trim(substr($header, 0, $pos)); $val = (string) trim(substr($header, $pos + 1)); if (strlen($name)) { $response['headers'][] = array('name' => $name, 'value' => $val); } if (!strcasecmp($name, 'location')) { $loc = (string) $val; } } else { $pos = strpos($header, 'HTTP/'); if ($pos >= 0) { $ver = (string) trim(substr($header, $pos + 5, 3)); } } } } if ($headersSize) { $response['headersSize'] = $headersSize; } $response['httpVersion'] = $ver; $response['redirectURL'] = $loc; $response['content'] = array(); $response['content']['size'] = (int) $r['objectSize']; if (isset($r['contentType']) && strlen($r['contentType'])) { $response['content']['mimeType'] = (string) $r['contentType']; } else { $response['content']['mimeType'] = ''; } // unsupported fields that are required $response['cookies'] = array(); $entry['response'] = $response; $entry['cache'] = (object) array(); $timings = array(); $timings['blocked'] = -1; $timings['dns'] = (int) $r['dns_ms']; if (!$timings['dns']) { $timings['dns'] = -1; } // HAR did not have an ssl time until version 1.2 . For // backward compatibility, "connect" includes "ssl" time. // WepbageTest's internal representation does not assume any // overlap, so we must add our connect and ssl time to get the // connect time expected by HAR. $timings['connect'] = durationOfInterval($r['connect_ms']) + durationOfInterval($r['ssl_ms']); if (!$timings['connect']) { $timings['connect'] = -1; } $timings['ssl'] = (int) $r['ssl_ms']; if (!$timings['ssl']) { $timings['ssl'] = -1; } // TODO(skerner): WebpageTest's data model has no way to // represent the difference between the states HAR calls // send (time required to send HTTP request to the server) // and wait (time spent waiting for a response from the server). // We lump both into "wait". Issue 24* tracks this work. When // it is resolved, read the real values for send and wait // instead of using the request's TTFB. // *: http://code.google.com/p/webpagetest/issues/detail?id=24 $timings['send'] = 0; $timings['wait'] = (int) $r['ttfb_ms']; $timings['receive'] = (int) $r['download_ms']; $entry['timings'] = $timings; // The HAR spec defines time as the sum of the times in the // timings object, excluding any unknown (-1) values and ssl // time (which is included in "connect", for backward // compatibility with tools written before "ssl" was defined // in HAR version 1.2). $entry['time'] = 0; foreach ($timings as $timingKey => $duration) { if ($timingKey != 'ssl' && $duration != UNKNOWN_TIME) { $entry['time'] += $duration; } } if (array_key_exists('custom_rules', $r)) { $entry['_custom_rules'] = $r['custom_rules']; } // dump all of our metrics into the har data as custom fields foreach ($r as $name => $value) { if (!is_array($value)) { $entry["_{$name}"] = $value; } } // add it to the list of entries $entries[] = $entry; } // add the bodies to the requests if (isset($options['bodies']) && $options['bodies']) { $bodies_file = $testPath . '/' . $run . $cached_text . '_bodies.zip'; if (is_file($bodies_file)) { $zip = new ZipArchive(); if ($zip->open($bodies_file) === TRUE) { for ($i = 0; $i < $zip->numFiles; $i++) { $index = intval($zip->getNameIndex($i), 10) - 1; if (array_key_exists($index, $entries)) { $entries[$index]['response']['content']['text'] = utf8_encode($zip->getFromIndex($i)); } } } } } } } $result['log']['entries'] = $entries; return $result; }
/** * Calculate the progress for all of the images in a given directory */ function GetVisualProgress($testPath, $run, $cached, $options = null, $end = null, $startOffset = null) { $frames = null; if (substr($testPath, 0, 1) !== '.') { $testPath = './' . $testPath; } $testInfo = GetTestInfo($testPath); $completed = IsTestRunComplete($run, $testInfo); $video_directory = "{$testPath}/video_{$run}"; if ($cached) { $video_directory .= '_cached'; } $cache_file = "{$testPath}/{$run}.{$cached}.visual.dat"; if (!isset($startOffset)) { $startOffset = 0; } $dirty = false; $current_version = VIDEO_CODE_VERSION; if (isset($end)) { if (is_numeric($end)) { $end = (int) ($end * 1000); } else { unset($end); } } if (!isset($end) && !isset($options) && gz_is_file($cache_file)) { $frames = json_decode(gz_file_get_contents($cache_file), true); if (!array_key_exists('frames', $frames) || !array_key_exists('version', $frames)) { unset($frames); } elseif (array_key_exists('version', $frames) && $frames['version'] !== $current_version) { unset($frames); } } if ((!isset($frames) || !count($frames)) && is_dir($video_directory)) { $frames = array('version' => $current_version); $frames['frames'] = array(); $dirty = true; $base_path = substr($video_directory, 1); $files = scandir($video_directory); $last_file = null; $first_file = null; $previous_file = null; foreach ($files as $file) { if (strpos($file, 'frame_') !== false && strpos($file, '.hist') === false) { $parts = explode('_', $file); if (count($parts) >= 2) { $time = (int) $parts[1] * 100 - $startOffset; if ($time >= 0 && (!isset($end) || $time <= $end)) { if (isset($previous_file) && !array_key_exists(0, $frames['frames']) && $time > 0) { $frames['frames'][0] = array('path' => "{$base_path}/{$previous_file}", 'file' => $previous_file); $first_file = $previous_file; } elseif (!isset($first_file)) { $first_file = $file; } $last_file = $file; $frames['frames'][$time] = array('path' => "{$base_path}/{$file}", 'file' => $file); } $previous_file = $file; } } elseif (strpos($file, 'ms_') !== false && strpos($file, '.hist') === false) { $parts = explode('_', $file); if (count($parts) >= 2) { $time = intval($parts[1]) - $startOffset; if ($time >= 0 && (!isset($end) || $time <= $end)) { if (isset($previous_file) && !array_key_exists(0, $frames['frames']) && $time > 0) { $frames['frames'][0] = array('path' => "{$base_path}/{$previous_file}", 'file' => $previous_file); $first_file = $previous_file; } elseif (!isset($first_file)) { $first_file = $file; } $last_file = $file; $frames['frames'][$time] = array('path' => "{$base_path}/{$file}", 'file' => $file); } $previous_file = $file; } } } if (count($frames['frames']) == 1) { foreach ($frames['frames'] as $time => &$frame) { $frame['progress'] = 100; $frames['complete'] = $time; } } elseif (isset($first_file) && strlen($first_file) && isset($last_file) && strlen($last_file) && count($frames['frames'])) { $start_histogram = GetImageHistogram("{$video_directory}/{$first_file}", $options); $final_histogram = GetImageHistogram("{$video_directory}/{$last_file}", $options); foreach ($frames['frames'] as $time => &$frame) { $histogram = GetImageHistogram("{$video_directory}/{$frame['file']}", $options); $frame['progress'] = CalculateFrameProgress($histogram, $start_histogram, $final_histogram, 5); if ($frame['progress'] == 100 && !array_key_exists('complete', $frames)) { $frames['complete'] = $time; } } } } if (isset($frames) && !array_key_exists('SpeedIndex', $frames)) { $dirty = true; $frames['SpeedIndex'] = CalculateSpeedIndex($frames); } if (isset($frames)) { $frames['visualComplete'] = 0; foreach ($frames['frames'] as $time => &$frame) { if ($frame['progress'] > 0 && !array_key_exists('startRender', $frames)) { $frames['startRender'] = $time; } if ($frame['progress'] == 100) { $frames['visualComplete'] = $time; break; } } } $devTools = GetDevToolsProgress($testPath, $run, $cached); if (isset($devTools)) { if (!isset($frames)) { $frames = array(); } $frames['DevTools'] = $devTools; } if ($completed && !isset($end) && !isset($options) && $dirty && isset($frames) && count($frames)) { gz_file_put_contents($cache_file, json_encode($frames)); } return $frames; }
/** * Load the test data and keep just the median run for each config */ function LoadMedianData($benchmark, $test_time) { global $median_data; global $raw_data; if (!isset($median_data)) { // see if we have a custom metric to use to calculate the median for the given benchmark $info = GetBenchmarkInfo($benchmark); $median_metric = 'docTime'; if (isset($info) && is_array($info) && array_key_exists('options', $info) && array_key_exists('median_run', $info['options'])) { $median_metric = $info['options']['median_run']; } $date = gmdate('Ymd_Hi', $test_time); $data_file = "./results/benchmarks/{$benchmark}/data/{$date}.json"; $key = "{$benchmark}-{$date}"; if (gz_is_file($data_file)) { if (!array_key_exists($key, $raw_data)) { $raw_data[$key] = json_decode(gz_file_get_contents($data_file), true); usort($raw_data[$key], 'RawDataCompare'); } if (count($raw_data[$key])) { $tests = array(); // group the results by test ID foreach ($raw_data[$key] as &$row) { if (array_key_exists($median_metric, $row) && ($row['result'] == 0 || $row['result'] == 99999)) { $id = $row['id']; $cached = $row['cached']; $key = "{$id}-{$cached}"; if (!array_key_exists($key, $tests)) { $tests[$key] = array(); } $tests[$key][] = $row; } } // extract just the median runs $median_data = array(); foreach ($tests as &$test) { $times = array(); foreach ($test as $row) { $times[] = $row[$median_metric]; } $median_run_index = 0; $count = count($times); if ($count > 1) { asort($times); $medianIndex = (int) floor(((double) $count + 1.0) / 2.0); $current = 0; foreach ($times as $index => $time) { $current++; if ($current == $medianIndex) { $median_run_index = $index; break; } } } $row = $test[$median_run_index]; if (array_key_exists('cached', $row) && array_key_exists('url', $row) && array_key_exists('config', $row) && array_key_exists('location', $row)) { $url = $row['url']; if (array_key_exists('label', $row) && strlen($row['label'])) { $url = $row['label']; } $config = $row['config']; $location = $row['location']; $cached = $row['cached']; if (isset($loc_aliases) && count($loc_aliases)) { foreach ($loc_aliases as $loc_name => &$aliases) { foreach ($aliases as $alias) { if ($location == $alias) { $location = $loc_name; break 2; } } } } if (!array_key_exists($config, $median_data)) { $median_data[$config] = array(); } if (!array_key_exists($location, $median_data[$config])) { $median_data[$config][$location] = array(); } if (!array_key_exists($cached, $median_data[$config][$location])) { $median_data[$config][$location][$cached] = array(); } $median_data[$config][$location][$cached][$url] = $row; } } } } } }
/** * If we have a timeline, figure out what each thread was doing at each point in time. * Basically CPU utilization from the timeline. * * returns an array of threads with each thread being an array of slices (one for * each time period). Each slice is an array of events and the fraction of that * slice that they consumed (with a total maximum of 1 for any slice). */ function DevToolsGetCPUSlices($testPath, $run, $cached) { $count = 0; $slices = null; $devTools = array(); $startOffset = null; $ver = 1; $cacheFile = "{$testPath}/{$run}.{$cached}.devToolsCPUSlices.{$ver}"; if (gz_is_file($cacheFile)) { $slices = json_decode(gz_file_get_contents($cacheFile), true); } if (!isset($slices)) { GetTimeline($testPath, $run, $cached, $devTools, $startOffset); if (isset($devTools) && is_array($devTools) && count($devTools)) { // Do a first pass to get the start and end times as well as the number of threads $threads = array(0 => true); $startTime = 0; $endTime = 0; foreach ($devTools as &$entry) { if (isset($entry['method']) && $entry['method'] == 'Timeline.eventRecorded' && isset($entry['params']['record'])) { $start = DevToolsEventTime($entry); if ($start && (!$startTime || $start < $startTime)) { $startTime = $start; } $end = DevToolsEventEndTime($entry); if ($end && (!$endTime || $end > $endTime)) { $endTime = $end; } $thread = isset($entry['params']['record']['thread']) ? $entry['params']['record']['thread'] : 0; $threads[$thread] = true; } } // create time slice arrays for each thread $slices = array(); foreach ($threads as $id => $bogus) { $slices[$id] = array(); } // create 1ms time slices for the full time if ($endTime > $startTime) { $startTime = floor($startTime); $endTime = ceil($endTime); for ($i = $startTime; $i <= $endTime; $i++) { $ms = intval($i - $startTime); foreach ($threads as $id => $bogus) { $slices[$id][$ms] = array(); } } // Go through each element and account for the time foreach ($devTools as &$entry) { if (isset($entry['method']) && $entry['method'] == 'Timeline.eventRecorded' && isset($entry['params']['record'])) { $count += DevToolsGetEventTimes($entry['params']['record'], $startTime, $slices); } } } } if ($count) { // remove any threads that didn't have actual slices populated $emptyThreads = array(); foreach ($slices as $thread => &$records) { $is_empty = true; foreach ($records as $ms => &$values) { if (count($values)) { $is_empty = false; break; } } if ($is_empty) { $emptyThreads[] = $thread; } } if (count($emptyThreads)) { foreach ($emptyThreads as $thread) { unset($slices[$thread]); } } gz_file_put_contents($cacheFile, json_encode($slices)); } else { $slices = null; } } return $slices; }
/** * Send back the data for a batch test (just the list of test ID's) * * @param mixed $id * @param mixed $testPath */ function BatchResult($id, $testPath) { header('Content-type: text/xml'); echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; echo "<response>"; if (strlen($_REQUEST['r'])) { echo "<requestId>{$_REQUEST['r']}</requestId>"; } $tests = null; if (gz_is_file("{$testPath}/bulk.json")) { $tests = json_decode(gz_file_get_contents("{$testPath}/bulk.json"), true); } elseif (gz_is_file("{$testPath}/tests.json")) { $legacyData = json_decode(gz_file_get_contents("{$testPath}/tests.json"), true); $tests = array(); $tests['variations'] = array(); $tests['urls'] = array(); foreach ($legacyData as &$legacyTest) { $tests['urls'][] = array('u' => $legacyTest['url'], 'id' => $legacyTest['id']); } } if (count($tests['urls'])) { echo "<statusCode>200</statusCode>"; echo "<statusText>Ok</statusText>"; if (strlen($_REQUEST['r'])) { echo "<requestId>{$_REQUEST['r']}</requestId>"; } $host = $_SERVER['HTTP_HOST']; $uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\'); echo "<data>"; foreach ($tests['urls'] as &$test) { echo "<test>"; echo "<testId>{$test['id']}</testId>"; echo "<testUrl>" . xml_entities($test['u']) . "</testUrl>"; echo "<xmlUrl>http://{$host}{$uri}/xmlResult/{$test['id']}/</xmlUrl>"; echo "<userUrl>http://{$host}{$uri}/result/{$test['id']}/</userUrl>"; echo "<summaryCSV>http://{$host}{$uri}/result/{$test['id']}/page_data.csv</summaryCSV>"; echo "<detailCSV>http://{$host}{$uri}/result/{$test['id']}/requests.csv</detailCSV>"; echo "</test>"; // go through all of the variations as well foreach ($test['v'] as $variationIndex => $variationId) { echo "<test>"; echo "<testId>{$variationId}</testId>"; echo "<testUrl>" . xml_entities(CreateUrlVariation($test['u'], $tests['variations'][$variationIndex]['q'])) . "</testUrl>"; echo "<xmlUrl>http://{$host}{$uri}/xmlResult/{$variationId}/</xmlUrl>"; echo "<userUrl>http://{$host}{$uri}/result/{$variationId}/</userUrl>"; echo "<summaryCSV>http://{$host}{$uri}/result/{$variationId}/page_data.csv</summaryCSV>"; echo "<detailCSV>http://{$host}{$uri}/result/{$variationId}/requests.csv</detailCSV>"; echo "</test>"; } } echo "</data>"; } else { echo "<statusCode>404</statusCode>"; echo "<statusText>Invalid Test ID: {$id}</statusText>"; echo "<data>"; echo "</data>"; } echo "</response>"; }
} header("Content-disposition: attachment; filename={$filename}"); header("Content-type: text/csv"); header("Cache-Control: no-cache, must-revalidate"); header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); if ($test['test']['batch']) { $tests = null; if (gz_is_file("{$testPath}/tests.json")) { $legacyData = json_decode(gz_file_get_contents("{$testPath}/tests.json"), true); $tests = array(); $tests['variations'] = array(); $tests['urls'] = array(); foreach ($legacyData as &$legacyTest) { $tests['urls'][] = array('u' => $legacyTest['url'], 'id' => $legacyTest['id']); } } elseif (gz_is_file("{$testPath}/bulk.json")) { $tests = json_decode(gz_file_get_contents("{$testPath}/bulk.json"), true); } if (isset($tests)) { foreach ($tests['urls'] as &$testData) { RestoreTest($testData['id']); $path = './' . GetTestPath($testData['id']); if (!isset($hasCSV)) { $files = glob("{$path}/*{$fileType}"); if ($files && is_array($files) && count($files)) { $hasCSV = true; } else { $hasCSV = false; } } $label = $testData['l'];
if (is_file("./{$dir}/video.mp4")) { $date = gmdate("M j, Y", filemtime("./{$dir}/video.mp4")); } $title .= " - {$date}"; $labels = json_decode(file_get_contents("./{$dir}/labels.txt"), true); if (count($labels)) { $title .= ' : '; foreach ($labels as $index => $label) { if ($index > 0) { $title .= ", "; } $title .= $label; } } $location = null; if (gz_is_file("./{$dir}/testinfo.json")) { $tests = json_decode(gz_file_get_contents("./{$dir}/testinfo.json"), true); if (is_array($tests) && count($tests)) { foreach ($tests as &$test) { if (array_key_exists('location', $test)) { if (!isset($location)) { $location = $test['location']; } elseif ($location != $test['location']) { $location = ''; } } else { $location = ''; } } } }
/** * Create the various aggregations for the given data chunk * * @param mixed $info * @param mixed $data * @param mixed $benchmark */ function CreateAggregates(&$info, &$data, $benchmark, $run_time, $options) { foreach ($info['metrics'] as $metric) { $metric_file = "./results/benchmarks/{$benchmark}/aggregate/{$metric}.json"; if (gz_is_file($metric_file)) { $agg_data = json_decode(gz_file_get_contents($metric_file), true); } else { $agg_data = array(); } AggregateMetric($metric, $info, $data, $run_time, $agg_data, $options); gz_file_put_contents($metric_file, @json_encode($agg_data)); unset($agg_data); if (array_key_exists('labels', $info) && count($info['labels']) <= 20) { $metric_file = "./results/benchmarks/{$benchmark}/aggregate/{$metric}.labels.json"; if (gz_is_file($metric_file)) { $agg_data = json_decode(gz_file_get_contents($metric_file), true); } else { $agg_data = array(); } AggregateMetricByLabel($metric, $info, $data, $run_time, $agg_data, $options); gz_file_put_contents($metric_file, json_encode($agg_data)); unset($agg_data); } } }
} elseif (substr($buffer, 0, 5) == "[{}\n,") { $buffer = '[' . substr($buffer, 5); } } ob_flush(); flush(); } echo rtrim($buffer, "\r\n}"); gzclose($handle); } } elseif (gz_is_file("{$testPath}/{$run}{$cachedText}_timeline.json")) { $ok = true; header("Content-disposition: attachment; filename=timeline.json"); header("Content-type: application/json"); gz_readfile_chunked("{$testPath}/{$run}{$cachedText}_timeline.json"); } elseif (gz_is_file("{$testPath}/{$run}{$cachedText}_devtools.json")) { require_once 'devtools.inc.php'; $devTools = array(); $startOffset = null; GetTimeline($testPath, $run, $cached, $devTools, $startOffset); if (isset($devTools) && is_array($devTools) && count($devTools)) { foreach ($devTools as $entry) { $events = GetEvents($entry); if (count($events)) { if (!$ok) { $ok = true; header("Content-disposition: attachment; filename=timeline.json"); header("Content-type: application/json"); echo "[\"WebPagetest\""; } foreach ($events as $event) {
/** * Gather all of the data that we collect for a single run * * @param mixed $id * @param mixed $testPath * @param mixed $run * @param mixed $cached */ function GetSingleRunData($id, $testPath, $run, $cached, &$pageData, $testInfo) { $ret = null; if (array_key_exists($run, $pageData) && is_array($pageData[$run]) && array_key_exists($cached, $pageData[$run]) && is_array($pageData[$run][$cached])) { $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' || isset($_SERVER['HTTP_SSL']) && $_SERVER['HTTP_SSL'] == 'On' ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST']; $uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\'); $path = substr($testPath, 1); $ret = $pageData[$run][$cached]; $ret['run'] = $run; $cachedText = ''; if ($cached) { $cachedText = '_Cached'; } if (isset($testInfo)) { if (array_key_exists('tester', $testInfo)) { $ret['tester'] = $testInfo['tester']; } if (array_key_exists('test_runs', $testInfo) && array_key_exists($run, $testInfo['test_runs']) && array_key_exists('tester', $testInfo['test_runs'][$run])) { $ret['tester'] = $testInfo['test_runs'][$run]['tester']; } } $basic_results = false; if (array_key_exists('basic', $_REQUEST) && $_REQUEST['basic']) { $basic_results = true; } if (!$basic_results && gz_is_file("{$testPath}/{$run}{$cachedText}_pagespeed.txt")) { $ret['PageSpeedScore'] = GetPageSpeedScore("{$testPath}/{$run}{$cachedText}_pagespeed.txt"); $ret['PageSpeedData'] = "{$protocol}://{$host}{$uri}//getgzip.php?test={$id}&file={$run}{$cachedText}_pagespeed.txt"; } $ret['pages'] = array(); $ret['pages']['details'] = "{$protocol}://{$host}{$uri}/details.php?test={$id}&run={$run}&cached={$cached}"; $ret['pages']['checklist'] = "{$protocol}://{$host}{$uri}/performance_optimization.php?test={$id}&run={$run}&cached={$cached}"; $ret['pages']['breakdown'] = "{$protocol}://{$host}{$uri}/breakdown.php?test={$id}&run={$run}&cached={$cached}"; $ret['pages']['domains'] = "{$protocol}://{$host}{$uri}/domains.php?test={$id}&run={$run}&cached={$cached}"; $ret['pages']['screenShot'] = "{$protocol}://{$host}{$uri}/screen_shot.php?test={$id}&run={$run}&cached={$cached}"; $ret['thumbnails'] = array(); $ret['thumbnails']['waterfall'] = "{$protocol}://{$host}{$uri}/result/{$id}/{$run}{$cachedText}_waterfall_thumb.png"; $ret['thumbnails']['checklist'] = "{$protocol}://{$host}{$uri}/result/{$id}/{$run}{$cachedText}_optimization_thumb.png"; $ret['thumbnails']['screenShot'] = "{$protocol}://{$host}{$uri}/result/{$id}/{$run}{$cachedText}_screen_thumb.png"; $ret['images'] = array(); $ret['images']['waterfall'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_waterfall.png"; $ret['images']['connectionView'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_connection.png"; $ret['images']['checklist'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_optimization.png"; $ret['images']['screenShot'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_screen.jpg"; if (is_file("{$testPath}/{$run}{$cachedText}_screen.png")) { $ret['images']['screenShotPng'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_screen.png"; } $ret['rawData'] = array(); $ret['rawData']['headers'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_report.txt"; $ret['rawData']['pageData'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_IEWPG.txt"; $ret['rawData']['requestsData'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_IEWTR.txt"; $ret['rawData']['utilization'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_progress.csv"; if (is_file("{$testPath}/{$run}{$cachedText}_bodies.zip")) { $ret['rawData']['bodies'] = "{$protocol}://{$host}{$uri}{$path}/{$run}{$cachedText}_bodies.zip"; } if (!$basic_results) { $startOffset = array_key_exists('testStartOffset', $ret) ? intval(round($ret['testStartOffset'])) : 0; $progress = GetVisualProgress($testPath, $run, $cached, null, null, $startOffset); if (array_key_exists('frames', $progress) && is_array($progress['frames']) && count($progress['frames'])) { $cachedTextLower = strtolower($cachedText); $ret['videoFrames'] = array(); foreach ($progress['frames'] as $ms => $frame) { $videoFrame = array('time' => $ms); $videoFrame['image'] = "http://{$host}{$uri}{$path}/video_{$run}{$cachedTextLower}/{$frame['file']}"; $videoFrame['VisuallyComplete'] = $frame['progress']; $ret['videoFrames'][] = $videoFrame; } } $requests = getRequests($id, $testPath, $run, $cached, $secure, $haveLocations, false, true); $ret['domains'] = getDomainBreakdown($id, $testPath, $run, $cached, $requests); $ret['breakdown'] = getBreakdown($id, $testPath, $run, $cached, $requests); // check if removing requests $addRequests = 1; if (isset($_GET['requests'])) { if ($_GET['requests'] == 0) { $addRequests = 0; } } // add requests if ($addRequests == 1) { $ret['requests'] = $requests; } $console_log = DevToolsGetConsoleLog($testPath, $run, $cached); if (isset($console_log)) { $ret['consoleLog'] = $console_log; } if (gz_is_file("{$testPath}/{$run}{$cachedText}_status.txt")) { $ret['status'] = array(); $lines = gz_file("{$testPath}/{$run}{$cachedText}_status.txt"); foreach ($lines as $line) { $line = trim($line); if (strlen($line)) { list($time, $message) = explode("\t", $line); if (strlen($time) && strlen($message)) { $ret['status'][] = array('time' => $time, 'message' => $message); } } } } } } return $ret; }
/** * Get the relative offset for the video capture (in milliseconds). * This is the time between the first non-timeline event and the * last paint or rasterize event prior to it. * * @param mixed $testPath * @param mixed $run * @param mixed $cached */ function DevToolsGetVideoOffset($testPath, $run, $cached, &$endTime) { $offset = 0; $endTime = 0; $lastEvent = 0; $cachedText = ''; if ($cached) { $cachedText = '_Cached'; } $devToolsFile = "{$testPath}/{$run}{$cachedText}_devtools.json"; if (gz_is_file($devToolsFile)) { $events = json_decode(gz_file_get_contents($devToolsFile), true); if (is_array($events)) { $lastPaint = 0; $startTime = 0; foreach ($events as &$event) { if (is_array($event) && array_key_exists('method', $event)) { $method_class = substr($event['method'], 0, strpos($event['method'], '.')); // calculate the start time stuff if ($method_class === 'Timeline') { $encoded = json_encode($event); $eventTime = DevToolsEventEndTime($event); if ($eventTime && (!$startTime || $eventTime <= $startTime) && (!$lastPaint || $eventTime > $lastPaint)) { if (strpos($encoded, '"type":"ResourceSendRequest"') !== false) { $startTime = DevToolsEventTime($event); } if (strpos($encoded, '"type":"Rasterize"') !== false || strpos($encoded, '"type":"CompositeLayers"') !== false || strpos($encoded, '"type":"Paint"') !== false) { $lastPaint = $eventTime; } } if ($eventTime > $lastEvent && strpos($encoded, '"type":"Resource') !== false) { $lastEvent = $eventTime; } } } } } } if ($startTime && $lastPaint && $lastPaint < $startTime) { $offset = round($startTime - $lastPaint); } if ($startTime && $lastEvent && $lastEvent > $startTime) { $endTime = ceil($lastEvent - $startTime); } return $offset; }