Skip to content

Commit 1b25462

Browse files
committed
Merge branch 'master' of github.com:WPO-Foundation/wptagent
2 parents e3c5f46 + 6605cc9 commit 1b25462

8 files changed

+157
-10
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ run the agent in a docker container.
7878
* none - Disable traffic-shaping (i.e. when root is not available).
7979
* netem,\<interface\> - Use NetEm for bridging rndis traffic (specify outbound interface). i.e. --shaper netem,eth0
8080
* remote,\<server\>,\<down pipe\>,\<up pipe\> - Connect to the remote server over ssh and use pre-configured dummynet pipes (ssh keys for root user should be pre-authorized).
81+
* chrome - Use Chrome's dev tools traffic-shaping. Only supports Chromium browsers and should be used as a last resort.
8182

8283
### Android testing options
8384
* **--android** : Run tests on an attached android device.

internal/android_browser.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,10 @@ def on_start_recording(self, task):
203203
if version is not None:
204204
task['page_data']['browserVersion'] = version
205205
task['page_data']['browser_version'] = version
206-
if not self.job['shaper'].configure(self.job, task):
207-
task['error'] = "Error configuring traffic-shaping"
208-
task['page_data']['result'] = 12999
206+
if not self.job['dtShaper']:
207+
if not self.job['shaper'].configure(self.job, task):
208+
task['error'] = "Error configuring traffic-shaping"
209+
task['page_data']['result'] = 12999
209210
if self.tcpdump_enabled:
210211
self.adb.start_tcpdump()
211212
if self.video_enabled and not self.job['disable_video']:

internal/desktop_browser.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,9 @@ def on_start_recording(self, task):
429429
return
430430
import psutil
431431
if task['log_data']:
432-
if not self.job['shaper'].configure(self.job, task):
433-
self.task['error'] = "Error configuring traffic-shaping"
432+
if not self.job['dtShaper']:
433+
if not self.job['shaper'].configure(self.job, task):
434+
self.task['error'] = "Error configuring traffic-shaping"
434435
self.cpu_start = psutil.cpu_times()
435436
self.recording = True
436437
ver = platform.uname()

internal/devtools.py

+30-2
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def connect(self, timeout):
220220
try:
221221
self.websocket = DevToolsClient(websocket_url)
222222
self.websocket.connect()
223+
self.job['shaper'].set_devtools(self)
223224
ret = True
224225
except Exception as err:
225226
logging.exception("Connect to dev tools websocket Error: %s", err.__str__())
@@ -246,11 +247,35 @@ def connect(self, timeout):
246247
self.profile_end('connect')
247248
return ret
248249

250+
def _to_int(self, s):
251+
return int(re.search(r'\d+', str(s)).group())
252+
253+
def enable_shaper(self, target_id=None):
254+
"""Enable the Chromium dev tools traffic shaping"""
255+
if self.job['dtShaper']:
256+
in_Bps = -1
257+
if 'bwIn' in self.job:
258+
in_Bps = (self._to_int(self.job['bwIn']) * 1000) / 8
259+
out_Bps = -1
260+
if 'bwOut' in self.job:
261+
out_Bps = (self._to_int(self.job['bwOut']) * 1000) / 8
262+
rtt = 0
263+
if 'latency' in self.job:
264+
rtt = self._to_int(self.job['latency'])
265+
self.send_command('Network.emulateNetworkConditions', {
266+
'offline': False,
267+
'latency': rtt,
268+
'downloadThroughput': in_Bps,
269+
'uploadThroughput': out_Bps
270+
}, wait=True, target_id=target_id)
271+
249272
def enable_webkit_events(self):
250273
if self.is_webkit:
251274
self.send_command('Inspector.enable', {})
252275
self.send_command('Network.enable', {})
253276
self.send_command('Runtime.enable', {})
277+
self.job['shaper'].apply()
278+
self.enable_shaper()
254279
if self.headers:
255280
self.send_command('Network.setExtraHTTPHeaders', {'headers': self.headers})
256281
if len(self.workers):
@@ -281,6 +306,7 @@ def prepare_browser(self):
281306

282307
def close(self, close_tab=True):
283308
"""Close the dev tools connection"""
309+
self.job['shaper'].set_devtools(None)
284310
if self.websocket:
285311
try:
286312
self.websocket.close()
@@ -1185,6 +1211,8 @@ def enable_target(self, target_id=None):
11851211
self.send_command('Log.enable', {}, target_id=target_id)
11861212
self.send_command('Log.startViolationsReport', {'config': [{'name': 'discouragedAPIUse', 'threshold': -1}]}, target_id=target_id)
11871213
self.send_command('Audits.enable', {}, target_id=target_id)
1214+
self.job['shaper'].apply(target_id=target_id)
1215+
self.enable_shaper(target_id=target_id)
11881216
if self.headers:
11891217
self.send_command('Network.setExtraHTTPHeaders', {'headers': self.headers}, target_id=target_id, wait=True)
11901218
if 'user_agent_string' in self.job:
@@ -1491,8 +1519,8 @@ def process_target_event(self, event, msg):
14911519
target = msg['params']['targetInfo']
14921520
if 'type' in target and target['type'] == 'service_worker':
14931521
self.workers.append(target)
1494-
if self.recording:
1495-
self.enable_target(target['targetId'])
1522+
if self.recording:
1523+
self.enable_target(target['targetId'])
14961524
self.send_command('Runtime.runIfWaitingForDebugger', {},
14971525
target_id=target['targetId'])
14981526
if event == 'receivedMessageFromTarget' or event == 'dispatchMessageFromTarget':

internal/devtools_browser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ def run_lighthouse_test(self, task):
750750
"""Run a lighthouse test against the current browser session"""
751751
task['lighthouse_log'] = ''
752752
if 'url' in self.job and self.job['url'] is not None and not self.is_webkit:
753-
if not self.job['lighthouse_config']:
753+
if not self.job['lighthouse_config'] and not self.job['dtShaper']:
754754
self.job['shaper'].configure(self.job, task)
755755
output_path = os.path.join(task['dir'], 'lighthouse.json')
756756
json_file = os.path.join(task['dir'], 'lighthouse.report.json')

internal/safari_ios.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -843,8 +843,9 @@ def on_start_recording(self, task):
843843
self.flush_messages()
844844
self.enable_safari_events()
845845
if self.task['log_data']:
846-
if not self.job['shaper'].configure(self.job, task):
847-
self.task['error'] = "Error configuring traffic-shaping"
846+
if not self.job['dtShaper']:
847+
if not self.job['shaper'].configure(self.job, task):
848+
self.task['error'] = "Error configuring traffic-shaping"
848849
if self.bodies_zip_file is not None:
849850
self.bodies_zip_file.close()
850851
self.bodies_zip_file = None

internal/traffic_shaping.py

+114
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def __init__(self, options, root_path):
2424
if shaper_name is not None:
2525
if shaper_name == 'none':
2626
self.shaper = NoShaper()
27+
elif shaper_name == 'chrome':
28+
self.shaper = ChromeShaper()
2729
elif shaper_name[:5] == 'netem':
2830
parts = shaper_name.split(',')
2931
if_out = parts[1].strip() if len(parts) > 1 else None
@@ -111,6 +113,20 @@ def configure(self, job, task):
111113
job['interface'] = self.shaper.interface
112114
return ret
113115

116+
def set_devtools(self, devtools):
117+
"""Configure the devtools interface for the shaper (Chrome-only)"""
118+
try:
119+
self.shaper.set_devtools(devtools)
120+
except Exception:
121+
logging.exception('Error setting shaper devtools interface')
122+
return
123+
124+
def apply(self, target_id=None):
125+
"""Apply the traffic-shaping for Chrome"""
126+
try:
127+
self.shaper.apply(target_id)
128+
except Exception:
129+
logging.exception('Error applying traffic shaping')
114130

115131
#
116132
# NoShaper
@@ -137,6 +153,64 @@ def configure(self, in_bps, out_bps, rtt, plr, shaperLimit):
137153
if in_bps > 0 or out_bps > 0 or rtt > 0 or plr > 0 or shaperLimit > 0:
138154
return False
139155
return True
156+
157+
def set_devtools(self, devtools):
158+
"""Stub for configuring the devtools interface"""
159+
return
160+
161+
def apply(self, target_id):
162+
"""Stub for applying Chrome traffic-shaping"""
163+
return
164+
#
165+
# ChromeShaper
166+
#
167+
class ChromeShaper(object):
168+
"""Allow resets but fail any explicit shaping"""
169+
def __init__(self):
170+
self.interface = None
171+
self.devtools = None
172+
self.rtt = 0
173+
self.in_Bps = -1
174+
self.out_Bps = -1
175+
176+
def install(self):
177+
"""Install and configure the traffic-shaper"""
178+
return True
179+
180+
def remove(self):
181+
"""Uninstall traffic-shaping"""
182+
return True
183+
184+
def reset(self):
185+
"""Disable traffic-shaping"""
186+
self.rtt = 0
187+
self.in_Bps = -1
188+
self.out_Bps = -1
189+
self.apply()
190+
return True
191+
192+
def configure(self, in_bps, out_bps, rtt, plr, shaperLimit):
193+
"""Enable traffic-shaping"""
194+
self.rtt = rtt
195+
self.in_Bps = in_bps / 8
196+
self.out_Bps = out_bps / 8
197+
self.apply()
198+
return True
199+
200+
def set_devtools(self, devtools):
201+
"""Stub for configuring the devtools interface"""
202+
self.devtools = devtools
203+
204+
def apply(self, target_id=None):
205+
"""Stub for applying Chrome traffic-shaping"""
206+
if self.devtools is not None:
207+
self.devtools.send_command('Network.emulateNetworkConditions', {
208+
'offline': False,
209+
'latency': self.rtt,
210+
'downloadThroughput': self.in_Bps,
211+
'uploadThroughput': self.out_Bps
212+
}, wait=True, target_id=target_id)
213+
return
140214

141215
#
142216
# winshaper
@@ -180,6 +254,14 @@ def configure(self, in_bps, out_bps, rtt, plr, shaperLimit):
180254
'inbuff={0:d}'.format(int(self.in_buff)),
181255
'outbuff={0:d}'.format(int(self.out_buff))])
182256

257+
def set_devtools(self, devtools):
258+
"""Stub for configuring the devtools interface"""
259+
return
260+
261+
def apply(self, target_id):
262+
"""Stub for applying Chrome traffic-shaping"""
263+
return
264+
183265
#
184266
# Dummynet
185267
#
@@ -273,6 +355,14 @@ def configure(self, in_bps, out_bps, rtt, plr, shaperLimit):
273355
self.ipfw(in_queue_command) and\
274356
self.ipfw(out_queue_command)
275357

358+
def set_devtools(self, devtools):
359+
"""Stub for configuring the devtools interface"""
360+
return
361+
362+
def apply(self, target_id):
363+
"""Stub for applying Chrome traffic-shaping"""
364+
return
365+
276366
#
277367
# MacDummynet - Dummynet through pfctl
278368
#
@@ -375,6 +465,14 @@ def configure(self, in_bps, out_bps, rtt, plr, shaperLimit):
375465
return self.dnctl(in_command) and\
376466
self.dnctl(out_command)
377467

468+
def set_devtools(self, devtools):
469+
"""Stub for configuring the devtools interface"""
470+
return
471+
472+
def apply(self, target_id):
473+
"""Stub for applying Chrome traffic-shaping"""
474+
return
475+
378476
#
379477
# RemoteDummynet - Remote PC running dummynet with pre-configured pipes
380478
#
@@ -411,6 +509,14 @@ def remove(self):
411509
"""Uninstall traffic-shaping"""
412510
return True
413511

512+
def set_devtools(self, devtools):
513+
"""Stub for configuring the devtools interface"""
514+
return
515+
516+
def apply(self, target_id):
517+
"""Stub for applying Chrome traffic-shaping"""
518+
return
519+
414520
#
415521
# netem
416522
#
@@ -515,3 +621,11 @@ def configure_interface(self, interface, bps, latency, plr, shaperLimit):
515621
args = self.build_command_args(interface, bps, latency, plr, shaperLimit)
516622
logging.debug(' '.join(args))
517623
return subprocess.call(args) == 0
624+
625+
def set_devtools(self, devtools):
626+
"""Stub for configuring the devtools interface"""
627+
return
628+
629+
def apply(self, target_id):
630+
"""Stub for applying Chrome traffic-shaping"""
631+
return

internal/webpagetest.py

+1
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ def get_test(self, browsers):
625625
if 'video' not in job:
626626
job['video'] = bool('Capture Video' in job and job['Capture Video'])
627627
job['keepvideo'] = bool('keepvideo' in job and job['keepvideo'])
628+
job['dtShaper'] = bool('dtShaper' in job and job['dtShaper'])
628629
job['disable_video'] = bool(not job['video'] and
629630
'disable_video' in job and
630631
job['disable_video'])

0 commit comments

Comments
 (0)