1 import time
2 from tlib.asset.wpt_batch_lib import wpt_batch_lib
3 import xml.etree.ElementTree as elementTree
4
5
6 SUMMARY = 'summary'
7 RUNS = 'runs'
8 HEADERS_DATA = 'headers'
9 PAGE_DATA = 'pageData'
10 REQUESTS_DATA = 'requestsData'
11 UTILIZATION_DATA = 'Utilization'
12 PAGE_SPEED_DATA = 'pageSpeedData'
13 FIRST_VIEW = 'firstView'
14 REPEAT_VIEW = 'repeatView'
15 AVERAGE = 'average'
16 STD_DEV = 'standardDeviation'
17 MEDIAN = 'median'
18 TTFB = 'TTFB'
19 REQUESTS = 'requests'
20 REQUESTS_DOC = 'requestsDoc'
21 LOAD_TIME = 'loadTime'
22 FULLY_LOADED = 'fullyLoaded'
23
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
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
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
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
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
81 completed_test_ids = []
82 for test_id, test_status in id_status_dict.iteritems():
83
84
85
86
87
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
102 test_results = wpt_batch_lib.GetXMLResult(completed_test_ids, server_url=self._wpt_server)
103
104
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
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
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
138 self._wpt_params = dict(test_params)
139
140 return result
141
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
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
168 result[SUMMARY] = root.find("./data/summary").text
169
170
171 runs = int(root.find("./data/runs").text)
172 result[RUNS] = runs
173
174
175
176 for view in [FIRST_VIEW, REPEAT_VIEW]:
177
178 for metric in [AVERAGE, STD_DEV, MEDIAN]:
179 view_node = root.find("./data/%(metric)s/%(view)s" % {'metric': metric, 'view': view})
180
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
198 if view in result:
199
200 run_nodes = root.findall("./data/run")
201 for i in range(0, runs):
202
203 result[view].setdefault(RUNS, {}).setdefault(i, {})
204
205 data_node = run_nodes[i].find("./%(view)s" % {'view': view})
206
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