PageRenderTime 56ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/beacon/pagespeed/index.php

https://github.com/russelldavis/showslow
PHP | 382 lines | 304 code | 52 blank | 26 comment | 66 complexity | 11a177561bc00e9d3807d7a4a7a55e1f MD5 | raw file
  1. <?php
  2. require_once(dirname(dirname(dirname(__FILE__))).'/global.php');
  3. function updateUrlAggregates($url_id, $measurement_id)
  4. {
  5. # updating latest values for the URL
  6. $query = sprintf("UPDATE urls set pagespeed_last_id = %d, last_update = now(), p_refresh_request = 0 WHERE id = %d",
  7. mysql_real_escape_string($measurement_id),
  8. mysql_real_escape_string($url_id)
  9. );
  10. $result = mysql_query($query);
  11. if (!$result) {
  12. beaconError(mysql_error());
  13. }
  14. }
  15. if (array_key_exists('u', $_GET)) {
  16. $url_id = getUrlId($_GET['u']);
  17. $metrics = array(
  18. 'pBadReqs',
  19. 'pBrowserCache',
  20. 'pCacheValid',
  21. 'pCharsetEarly',
  22. 'pCombineCSS',
  23. 'pCombineJS',
  24. 'pCssImport',
  25. 'pCssInHead',
  26. 'pCssJsOrder',
  27. 'pCssSelect',
  28. 'pDeferJS',
  29. 'pDocWrite',
  30. 'pDupeRsrc',
  31. 'pGzip',
  32. 'pImgDims',
  33. 'pMinDns',
  34. 'pMinifyCSS',
  35. 'pMinifyHTML',
  36. 'pMinifyJS',
  37. 'pMinRedirect',
  38. 'pMinReqSize',
  39. 'pNoCookie',
  40. 'pOptImgs',
  41. 'pParallelDl',
  42. 'pPreferAsync',
  43. 'pRemoveQuery',
  44. 'pScaleImgs',
  45. 'pSprite',
  46. 'pUnusedCSS',
  47. 'pVaryAE'
  48. );
  49. // array to store core metrics:
  50. // w total size of all resources loaded by the page
  51. // htmlResponseBytes
  52. // textResponseBytes
  53. // cssResponseBytes
  54. // imageResponseBytes
  55. // javascriptResponseBytes
  56. // flashResponseBytes
  57. // otherResponseBytes
  58. // o score
  59. // l -
  60. // r numberResources
  61. // t -
  62. $core_metrics = array();
  63. // processed data will be stored in this array
  64. $rules = array();
  65. // indicates if data was successfully gathered and we can store it
  66. $got_data = false;
  67. $sdk_version = null;
  68. if (!is_null($pageSpeedOnlineAPIKey) && array_key_exists('api', $_GET) ) {
  69. // map of rule => metric relationships
  70. $rule_metric_map = array(
  71. 'AvoidBadRequests' => 'pBadReqs',
  72. 'LeverageBrowserCaching' => 'pBrowserCache',
  73. 'SpecifyACacheValidator' => 'pCacheValid',
  74. 'SpecifyCharsetEarly' => 'pCharsetEarly',
  75. 'CombineExternalCSS' => 'pCombineCSS',
  76. 'CombineExternalJavaScript' => 'pCombineJS',
  77. 'AvoidCssImport' => 'pCssImport',
  78. 'PutCssInTheDocumentHead' => 'pCssInHead',
  79. 'OptimizeTheOrderOfStylesAndScripts' => 'pCssJsOrder',
  80. 'AvoidDocumentWrite' => 'pDocWrite',
  81. 'ServeResourcesFromAConsistentUrl' => 'pDupeRsrc',
  82. 'EnableGzipCompression' => 'pGzip',
  83. 'SpecifyImageDimensions' => 'pImgDims',
  84. 'MinimizeDnsLookups' => 'pMinDns',
  85. 'MinifyCss' => 'pMinifyCSS',
  86. 'MinifyHTML' => 'pMinifyHTML',
  87. 'MinifyJavaScript' => 'pMinifyJS',
  88. 'MinimizeRedirects' => 'pMinRedirect',
  89. 'MinimizeRequestSize' => 'pMinReqSize',
  90. 'ServeStaticContentFromACookielessDomain' => 'pNoCookie',
  91. 'OptimizeImages' => 'pOptImgs',
  92. 'ParallelizeDownloadsAcrossHostnames' => 'pParallelDl',
  93. 'PreferAsyncResources' => 'pPreferAsync',
  94. 'RemoveQueryStringsFromStaticResources' => 'pRemoveQuery',
  95. 'ServeScaledImages' => 'pScaleImgs',
  96. 'SpriteImages' => 'pSprite',
  97. 'SpecifyAVaryAcceptEncodingHeader' => 'pVaryAE'
  98. );
  99. // making an API call
  100. $apicall = 'https://www.googleapis.com/pagespeedonline/v1/runPagespeed?url='.urlencode(validateURL($_GET['u'])).'&key='.$pageSpeedOnlineAPIKey;
  101. $ch = curl_init();
  102. curl_setopt($ch, CURLOPT_URL, $apicall);
  103. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  104. $output = curl_exec($ch);
  105. if (empty($output)) {
  106. $err = curl_error($ch);
  107. curl_close($ch);
  108. failWithMessage("API call ($apicall) failed: ".$err);
  109. }
  110. $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  111. if ($code != 200) {
  112. curl_close($ch);
  113. failWithMessage("API returns error code other then 200: $code");
  114. }
  115. curl_close($ch);
  116. $response = json_decode($output, true);
  117. if (!array_key_exists('responseCode', $response)
  118. || !array_key_exists('kind', $response)
  119. || $response['kind'] != 'pagespeedonline#result'
  120. ) {
  121. failWithMessage("API returns data in the wrong format");
  122. }
  123. if ($response['responseCode'] != 200) {
  124. failWithMessage("URL tested returns bad response code: ".$response['responseCode']);
  125. }
  126. // core metrics
  127. if (!array_key_exists('score', $response)
  128. || ($core_metrics['o'] = filter_var($response['score'], FILTER_VALIDATE_INT)) === false
  129. ) {
  130. failWithMessage("No score returned");
  131. }
  132. if (!array_key_exists('pageStats', $response) || !is_array($response['pageStats'])) {
  133. failWithMessage("No page statistics returned");
  134. }
  135. $stats = $response['pageStats'];
  136. #error_log(var_export($stats, true));
  137. $h = $t = $c = $i = $j = $f = $o = 0;
  138. if ((array_key_exists('htmlResponseBytes', $stats)
  139. && ($h = filter_var($stats['htmlResponseBytes'], FILTER_VALIDATE_INT)) === false)
  140. || (array_key_exists('textResponseBytes', $response['pageStats'])
  141. && ($t = filter_var($stats['textResponseBytes'], FILTER_VALIDATE_INT)) === false)
  142. || (array_key_exists('cssResponseBytes', $response['pageStats'])
  143. && ($c = filter_var($stats['cssResponseBytes'], FILTER_VALIDATE_INT)) === false)
  144. || (array_key_exists('imageResponseBytes', $response['pageStats'])
  145. && ($i = filter_var($stats['imageResponseBytes'], FILTER_VALIDATE_INT)) === false)
  146. || (array_key_exists('javascriptResponseBytes', $response['pageStats'])
  147. && ($j = filter_var($stats['javascriptResponseBytes'], FILTER_VALIDATE_INT)) === false)
  148. || (array_key_exists('flashResponseBytes', $response['pageStats'])
  149. && ($f = filter_var($stats['flashResponseBytes'], FILTER_VALIDATE_INT)) === false)
  150. || (array_key_exists('otherResponseBytes', $response['pageStats'])
  151. && ($o = filter_var($stats['otherResponseBytes'], FILTER_VALIDATE_INT)) === false)
  152. ) {
  153. failWithMessage("One of response size statistics is invalid");
  154. }
  155. $core_metrics['w'] = $h + $t + $c + $i + $j + $f + $o;
  156. if (!array_key_exists('numberResources', $stats)
  157. || ($core_metrics['r'] = filter_var($stats['numberResources'], FILTER_VALIDATE_INT)) === false
  158. ) {
  159. failWithMessage("Number of resources is not returned");
  160. }
  161. // TODO replace these with reasonable values or make them optional
  162. $core_metrics['t'] = 0;
  163. $core_metrics['l'] = 0;
  164. // rules
  165. if (!array_key_exists('formattedResults', $response) || !is_array($response['formattedResults'])
  166. || !array_key_exists('ruleResults', $response['formattedResults'])
  167. || !is_array($response['formattedResults']['ruleResults'])
  168. ) {
  169. failWithMessage("Data structure is not recognized");
  170. }
  171. foreach ($response['formattedResults']['ruleResults'] as $rule => $data) {
  172. if (!array_key_exists($rule, $rule_metric_map)) {
  173. error_log('Unrecognized rule: '.$rule.' (skipping)');
  174. continue;
  175. }
  176. $metric = $rule_metric_map[$rule];
  177. if (!array_key_exists('ruleScore', $data)) {
  178. error_log('Rule score is not specified: '.$rule.' (skipping)');
  179. continue;
  180. }
  181. $value = filter_var($data['ruleScore'], FILTER_VALIDATE_INT);
  182. if ($value === false) {
  183. error_log('Rule score is not an integer: '.$rule.' = '.$data['ruleScore'].' (skipping)');
  184. continue;
  185. }
  186. $rules[$metric] = $value;
  187. }
  188. if (!array_key_exists('version', $response) || !is_array($response['version'])
  189. || !array_key_exists('major', $response['version'])
  190. || $major = filter_var($response['version']['major'], FILTER_VALIDATE_INT) === false
  191. || !array_key_exists('minor', $response['version'])
  192. || $minor = filter_var($response['version']['minor'], FILTER_VALIDATE_INT) === false
  193. ) {
  194. failWithMessage("Number of resources is not returned");
  195. }
  196. $sdk_version = "$major.$minor";
  197. $got_data = true;
  198. } else if (array_key_exists('v', $_GET)
  199. && array_key_exists('w', $_GET)
  200. && ($core_metrics['w'] = filter_var($_GET['w'], FILTER_VALIDATE_INT)) !== false
  201. && array_key_exists('o', $_GET)
  202. && ($core_metrics['o'] = filter_var($_GET['o'], FILTER_VALIDATE_FLOAT)) !== false
  203. && array_key_exists('l', $_GET)
  204. && ($core_metrics['l'] = filter_var($_GET['l'], FILTER_VALIDATE_INT)) !== false
  205. && array_key_exists('r', $_GET)
  206. && ($core_metrics['r'] = filter_var($_GET['r'], FILTER_VALIDATE_INT)) !== false
  207. && array_key_exists('t', $_GET)
  208. && ($core_metrics['t'] = filter_var($_GET['t'], FILTER_VALIDATE_INT)) !== false
  209. )
  210. {
  211. $sdk_version = $_GET['v'];
  212. // list of old metric names that should still be suported
  213. $metric_renames = array(
  214. 'pSpecifyCharsetEarly' => 'pCharsetEarly',
  215. 'pProxyCache' => 'pCacheValid',
  216. 'pPutCssInTheDocumentHead' => 'pCssInHead',
  217. 'pOptimizeTheOrderOfStylesAndScripts' => 'pCssJsOrder',
  218. 'pMinimizeRequestSize' => 'pMinReqSize',
  219. 'pParallelizeDownloadsAcrossHostnames' => 'pParallelDl',
  220. 'pServeStaticContentFromACookielessDomain' => 'pNoCookie',
  221. 'pAvoidBadRequests' => 'pBadReqs',
  222. 'pLeverageBrowserCaching' => 'pBrowserCache',
  223. 'pRemoveQueryStringsFromStaticResources' => 'pRemoveQuery',
  224. 'pServeScaledImages' => 'pScaleImgs',
  225. 'pSpecifyACacheValidator' => 'pCacheValid',
  226. 'pSpecifyAVaryAcceptEncodingHeader' => 'pVaryAE',
  227. 'pSpecifyImageDimensions' => 'pImgDims'
  228. );
  229. foreach ($metrics as $metric) {
  230. $param = $metric;
  231. foreach (array_reverse($metric_renames) as $from => $to) {
  232. // if legacy parameter name is sent, use it to get the value
  233. if ($metric == $to
  234. && !array_key_exists($metric, $_GET)
  235. && array_key_exists($from, $_GET))
  236. {
  237. $param = $from;
  238. }
  239. }
  240. // if value is passed and it's a float number, store it
  241. if (array_key_exists($param, $_GET)) {
  242. $value = filter_var($_GET[$param], FILTER_VALIDATE_FLOAT);
  243. if ($value !== false) {
  244. $rules[$metric] = $value;
  245. }
  246. }
  247. }
  248. $got_data = true;
  249. }
  250. if ($got_data) {
  251. $values = array();
  252. foreach ($rules as $metric => $value) {
  253. $names[] = $metric;
  254. $values[] = "'".mysql_real_escape_string($value)."'";
  255. }
  256. # adding new entry
  257. $query = sprintf("INSERT INTO pagespeed (
  258. `ip` , `user_agent` , `url_id` , `v`,
  259. `w` , `o` , `l`, `r` , `t`,
  260. %s
  261. )
  262. VALUES (inet_aton('%s'), '%s', '%d', '%s',
  263. '%d', '%f', '%d', '%d', '%d',
  264. %s
  265. )",
  266. implode(', ', $names),
  267. mysql_real_escape_string($_SERVER['REMOTE_ADDR']),
  268. mysql_real_escape_string($_SERVER['HTTP_USER_AGENT']),
  269. mysql_real_escape_string($url_id),
  270. mysql_real_escape_string($sdk_version),
  271. mysql_real_escape_string($core_metrics['w']),
  272. mysql_real_escape_string($core_metrics['o']),
  273. mysql_real_escape_string($core_metrics['l']),
  274. mysql_real_escape_string($core_metrics['r']),
  275. mysql_real_escape_string($core_metrics['t']),
  276. implode(', ', $values)
  277. );
  278. if (!mysql_query($query))
  279. {
  280. beaconError(mysql_error());
  281. }
  282. updateUrlAggregates($url_id, mysql_insert_id());
  283. header('HTTP/1.0 204 Data accepted');
  284. exit;
  285. }
  286. }
  287. header('HTTP/1.0 400 Bad Request');
  288. ?><html>
  289. <head>
  290. <title>Bad Request: Page Speed beacon</title>
  291. </head>
  292. <body>
  293. <h1>Page Speed beacon</h1>
  294. <p>This is <a href="http://code.google.com/speed/page-speed/">Page Speed</a> beacon entry point.</p>
  295. <h1>Configure your Page Speed Extension</h1>
  296. <p><b style="color: red">WARNING! Only use this beacon If you're OK with all your Page Speed data to be recorded by this instance of ShowSlow and displayed at <a href="<?php echo $showslow_base?>"><?php echo $showslow_base?></a><br/>You can also <a href="http://www.showslow.org/Installation_and_configuration">install ShowSlow on your own server</a> to limit the risk.</b></p>
  297. Set these two Firefox parameters on <b>about:config</b> page:</p>
  298. <ul>
  299. <li>extensions.PageSpeed.beacon.minimal.url = <b style="color: blue"><?php echo $showslow_base?>beacon/pagespeed/</b></li>
  300. <li>extensions.PageSpeed.beacon.minimal.enabled = <b style="color: blue">true</b></li>
  301. </ul>
  302. <h1>Configuring Page Speed Online API</h1>
  303. Another alternative to using browser extension is to use <a href="https://code.google.com/apis/pagespeedonline/">Google Page Speed Online API</a>
  304. <h2>Getting the key</h2>
  305. Get your Google Web Services <a href="https://code.google.com/apis/console/b/0/#access">API key</a>
  306. Open your config.php file and set $pageSpeedOnlineAPIKey variable.
  307. <pre>
  308. $pageSpeedOnlineAPIKey = '<b style="color: blue">your-code-goes-here</b>';
  309. </pre>
  310. <?php if (!is_null($pageSpeedOnlineAPIKey)) { ?>
  311. <h2>Running the tests</h2>
  312. <p>To send API calls and import metrics into ShowSlow, just use your favorite tool to open beacon URL:</p>
  313. <p>
  314. <b><?php echo $showslow_base ?>/beacon/pagespeed/?api&u=<span style="color: red">url-you-are-testing</span></b>
  315. </p>
  316. <p>You can do that periodically using a cron-job, but keep in mind API call limits on the API.</p>
  317. <?php } ?>
  318. <hr/>
  319. <p><a href="../">&lt;&lt; back to the list of beacons</a></p>
  320. </body></html>