Package tlib :: Package base :: Module WptHelper
[hide private]
[frames] | no frames]

Source Code for Module tlib.base.WptHelper

  1  import time 
  2  from tlib.asset.wpt_batch_lib import wpt_batch_lib 
  3  import xml.etree.ElementTree as elementTree 
  4   
  5  #Constants used for creating the results object 
  6  SUMMARY = 'summary'                 #:Link to the summary report 
  7  RUNS = 'runs'                       #:How many test runs were executed 
  8  HEADERS_DATA = 'headers'            #:Link to raw headers data 
  9  PAGE_DATA = 'pageData'              #:Link to raw page data 
 10  REQUESTS_DATA = 'requestsData'      #:Link to raw requests data 
 11  UTILIZATION_DATA = 'Utilization'    #:Link to raw utilization data 
 12  PAGE_SPEED_DATA = 'pageSpeedData'   #:Link to raw page speed data 
 13  FIRST_VIEW = 'firstView'            #:Results for first view (with empty cache) 
 14  REPEAT_VIEW = 'repeatView'          #:Results for repeat view (with cache populated) 
 15  AVERAGE = 'average'                 #:Response time average 
 16  STD_DEV = 'standardDeviation'       #:Response time standard deviation 
 17  MEDIAN = 'median'                   #:Response time median 
 18  TTFB = 'TTFB'                       #:Time to first byte 
 19  REQUESTS = 'requests'               #:Total number of requests 
 20  REQUESTS_DOC = 'requestsDoc'        #:Number of requests before document is complete 
 21  LOAD_TIME = 'loadTime'              #:Page load time until document is complete 
 22  FULLY_LOADED = 'fullyLoaded'        #Page load time until there is no traffic for 2 seconds 
 23   
24 -class WptHelper(object):
25 """ 26 Helper class to send jobs to WebPagetest and get job results 27 """ 28 _wpt_server = None 29 _wpt_params = {} 30 _logger = None 31
32 - def __init__(self, logger, wpt_server, wpt_params):
33 """ 34 Constructor for class 35 36 @param logger: instance of a logging object configured in testing project 37 @type logger: logging.Logger 38 @param wpt_server: URL of the WebPagetest server used for testing, e.g. http://172.27.108.148:8080 39 @type wpt_server: str 40 @param wpt_params: Dictionarry with test settings. See https://sites.google.com/a/webpagetest.org/docs/advanced-features/webpagetest-restful-apis for more details 41 @type wpt_params: dict 42 """ 43 logger.debug("Initializing WptOrchestrator") 44 logger.debug("WebPagetest server: %s" % wpt_server) 45 logger.debug("WebPagetest test parameters: %s" % wpt_params) 46 47 self._logger = logger 48 self._wpt_server = wpt_server 49 self._wpt_params = wpt_params
50
51 - def test_urls_and_wait(self, requested_urls):
52 """ 53 Submits a test request for a list of URLs and waits until results are returned 54 55 @param requested_urls: List of URLs to test 56 @type requested_urls: list 57 @return: Results as generated by WebPagetest 58 For each item in the response, URL is the key and a dom object is the value 59 @rtype: dict 60 """ 61 self._logger.debug("Requested URLs\n%s" % requested_urls) 62 63 #Submit test request 64 id_url_dict = wpt_batch_lib.SubmitBatch(requested_urls, self._wpt_params, self._wpt_server) 65 self._logger.debug("SubmitBatch returned\n%s" % id_url_dict) 66 67 #Check all URLs were submitted to test server 68 submitted_urls = set(id_url_dict.values()) 69 self._logger.debug("Submitted URLs\n%(submitted_urls)s" % {"submitted_urls": submitted_urls}) 70 for url in requested_urls: 71 if url not in submitted_urls: 72 self._logger.error('URL submission failed: %(url)s' % {"url": url}) 73 74 #Wait until all tests are completed 75 pending_test_ids = id_url_dict.keys() 76 results = [] 77 while pending_test_ids: 78 id_status_dict = wpt_batch_lib.CheckBatchStatus(pending_test_ids, server_url=self._wpt_server) 79 80 #Get list of completed tests 81 completed_test_ids = [] 82 for test_id, test_status in id_status_dict.iteritems(): 83 # We could get 4 different status codes with different meanings as 84 # as follows: 85 # 1XX: Test in progress 86 # 200: Test complete 87 # 4XX: Test request not found 88 if int(test_status) >= 400: 89 self._logger.error('Tests failed with status %(test_id)s: %(test_status)s' % {'test_id':test_id,'test_status':test_status}) 90 pending_test_ids.remove(test_id) 91 continue 92 93 if int(test_status) == 200: 94 self._logger.debug("Test %(test_id)s completed, removing from list of pending tests" % {'test_id': test_id}) 95 pending_test_ids.remove(test_id) 96 completed_test_ids.append(test_id) 97 98 else: 99 self._logger.debug("Test %(test_id)s has not completed, status=%(test_status)s" % {'test_id':test_id,'test_status':test_status}) 100 101 #Get results for completed tests 102 test_results = wpt_batch_lib.GetXMLResult(completed_test_ids, server_url=self._wpt_server) 103 104 #Check we got one result for each of the completed tests 105 result_test_ids = set(test_results.keys()) 106 for test_id in completed_test_ids: 107 if test_id not in result_test_ids: 108 self._logger.error('The XML failed to retrieve: %(test_id)s' % {'test_id': test_id}) 109 110 for test_id, dom in test_results.iteritems(): 111 self._logger.debug("Test id %(test_id)s result:\n%(xml)s" % {'test_id': test_id, 'xml': dom.toxml()}) 112 test_result = self._get_test_results(dom.toxml()) 113 results.append(test_result) 114 115 if pending_test_ids: 116 time.sleep(5) 117 118 return results
119
120 - def test_script_and_wait(self, script):
121 """ 122 Submits a test request with a script and waits until results are returned 123 124 @param script: script 125 @type script: str 126 @return: Results as generated by WebPagetest 127 For each item in the response, URL is the key and a dom object is the value 128 @rtype: dict 129 """ 130 self._logger.debug("Script to execute\n%(script)s" % {"script": script}) 131 #Create a new instance of _wpt_params to not change the original parameters 132 test_params = dict(self._wpt_params) 133 try: 134 self._wpt_params['script'] = script 135 result = self.test_urls_and_wait([""]) 136 finally: 137 #Restore WPT params 138 self._wpt_params = dict(test_params) 139 140 return result
141
142 - def test_url_and_wait(self, url):
143 """ 144 Submits a test request for a URL and waits until results are returned 145 146 @param url: URLs to test 147 @type url: str 148 @return: Results as generated by WebPagetest 149 For each item in the response, URL is the key and a dom object is the value 150 @rtype: dict 151 """ 152 self._logger.debug("URL to test\n%(url)s" % {"url": url}) 153 return self.test_urls_and_wait([url])
154
155 - def _get_test_results(self, xml_text):
156 """ 157 Extracts global test metrics from DOM object returned by WPT and returns them as a dictionary 158 159 @param xml_text: XML returned by WPT server 160 @type xml_text: str 161 @return: Returns a dictionary with the metrics extracted from XML 162 @rtype: dict 163 """ 164 root = elementTree.fromstring(xml_text) 165 166 result = dict() 167 #Get data node 168 result[SUMMARY] = root.find("./data/summary").text 169 170 #Number of test runs 171 runs = int(root.find("./data/runs").text) 172 result[RUNS] = runs 173 174 #Get fist view and last view aggregated metrics 175 #It's possible that RepeatView doesn't exist depending on how test was ran 176 for view in [FIRST_VIEW, REPEAT_VIEW]: 177 #Get aggegated metrics 178 for metric in [AVERAGE, STD_DEV, MEDIAN]: 179 view_node = root.find("./data/%(metric)s/%(view)s" % {'metric': metric, 'view': view}) 180 #Check there is an element "view", otherwise skip 181 if view_node is not None: 182 result.setdefault(view, {}).setdefault(LOAD_TIME, {}) 183 result[view][LOAD_TIME][metric] = int(view_node.find("./%(LOAD_TIME)s" % {'LOAD_TIME': LOAD_TIME}).text) 184 185 result.setdefault(view, {}).setdefault(TTFB, {}) 186 result[view][TTFB][metric] = int(view_node.find("./%(TTFB)s" % {'TTFB': TTFB}).text) 187 188 result.setdefault(view, {}).setdefault(REQUESTS, {}) 189 result[view][REQUESTS][metric] = int(view_node.find("./%(REQUESTS)s" % {'REQUESTS': REQUESTS}).text) 190 191 result.setdefault(view, {}).setdefault(REQUESTS_DOC, {}) 192 result[view][REQUESTS_DOC][metric] = int(view_node.find("./%(REQUESTS_DOC)s" % {'REQUESTS_DOC': REQUESTS_DOC}).text) 193 194 result.setdefault(view, {}).setdefault(FULLY_LOADED, {}) 195 result[view][FULLY_LOADED][metric] = int(view_node.find("./%(FULLY_LOADED)s" % {'FULLY_LOADED': FULLY_LOADED}).text) 196 197 #If element "view" doesn't exist, skip getting test runs for that "view" 198 if view in result: 199 #Get results for each run 200 run_nodes = root.findall("./data/run") 201 for i in range(0, runs): 202 #Create missing keys or there will be an exception when assigning values 203 result[view].setdefault(RUNS, {}).setdefault(i, {}) 204 205 data_node = run_nodes[i].find("./%(view)s" % {'view': view}) 206 #If there is no 'run'.'view' element, it means that test run failed for that view 207 if data_node is not None: 208 result[view][RUNS][i][HEADERS_DATA] = data_node.find('./rawData/headers').text 209 result[view][RUNS][i][PAGE_DATA] = data_node.find('./rawData/pageData').text 210 result[view][RUNS][i][REQUESTS_DATA] = data_node.find('./rawData/requestsData').text 211 result[view][RUNS][i][UTILIZATION_DATA] = data_node.find('./rawData/utilization').text 212 result[view][RUNS][i][PAGE_SPEED_DATA] = data_node.find('./rawData/PageSpeedData').text 213 else: 214 result[view][RUNS][i][HEADERS_DATA] = '' 215 result[view][RUNS][i][PAGE_DATA] = '' 216 result[view][RUNS][i][REQUESTS_DATA] = '' 217 result[view][RUNS][i][UTILIZATION_DATA] = '' 218 result[view][RUNS][i][PAGE_SPEED_DATA] = '' 219 220 self._logger.debug('Result:\n%(result)s' % {'result': result}) 221 return result
222