PageRenderTime 2ms CodeModel.GetById 49ms app.highlight 8ms RepoModel.GetById 0ms app.codeStats 0ms

/beacon/pagespeed/index.php

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