1 #!/usr/bin/env python 2 3 # Copyright 2013 The Chromium Authors. All rights reserved. 4 # Use of this source code is governed by a BSD-style license that can be 5 # found in the LICENSE file. 6 7 import csv 8 import datetime 9 import json 10 import os 11 import shlex 12 import subprocess 13 import sys 14 from optparse import OptionParser 15 16 """Start a client to fetch web pages either using wget or using quic_client. 17 If --use_wget is set, it uses wget. 18 Usage: This invocation 19 run_client.py --quic_binary_dir=../../../../out/Debug \ 20 --address=127.0.0.1 --port=5000 --infile=test_urls.json \ 21 --delay_file=delay.csv --packets_file=packets.csv 22 fetches pages listed in test_urls.json from a quic server running at 23 127.0.0.1 on port 5000 using quic binary ../../../../out/Debug/quic_client 24 and stores the delay in delay.csv and the max received packet number (for 25 QUIC) in packets.csv. 26 If --use_wget is present, it will fetch the URLs using wget and ignores 27 the flags --address, --port, --quic_binary_dir, etc. 28 """ 29 30 def Timestamp(datetm=None): 31 """Get the timestamp in microseconds. 32 Args: 33 datetm: the date and time to be converted to timestamp. 34 If not set, use the current UTC time. 35 Returns: 36 The timestamp in microseconds. 37 """ 38 datetm = datetm or datetime.datetime.utcnow() 39 diff = datetm - datetime.datetime.utcfromtimestamp(0) 40 timestamp = (diff.days * 86400 + diff.seconds) * 1000000 + diff.microseconds 41 return timestamp 42 43 class PageloadExperiment: 44 def __init__(self, use_wget, quic_binary_dir, quic_server_address, 45 quic_server_port): 46 """Initialize PageloadExperiment. 47 48 Args: 49 use_wget: Whether to use wget. 50 quic_binary_dir: Directory for quic_binary. 51 quic_server_address: IP address of quic server. 52 quic_server_port: Port of the quic server. 53 """ 54 self.use_wget = use_wget 55 self.quic_binary_dir = quic_binary_dir 56 self.quic_server_address = quic_server_address 57 self.quic_server_port = quic_server_port 58 if not use_wget and not os.path.isfile(quic_binary_dir + '/quic_client'): 59 raise IOError('There is no quic_client in the given dir: %s.' 60 % quic_binary_dir) 61 62 @classmethod 63 def ReadPages(cls, json_file): 64 """Return the list of URLs from the json_file. 65 66 One entry of the list may contain a html link and multiple resources. 67 """ 68 page_list = [] 69 with open(json_file) as f: 70 data = json.load(f) 71 for page in data['pages']: 72 url = page['url'] 73 if 'resources' in page: 74 resources = page['resources'] 75 else: 76 resources = None 77 if not resources: 78 page_list.append([url]) 79 else: 80 urls = [url] 81 # For url http://x.com/z/y.html, url_dir is http://x.com/z 82 url_dir = url.rsplit('/', 1)[0] 83 for resource in resources: 84 urls.append(url_dir + '/' + resource) 85 page_list.append(urls) 86 return page_list 87 88 def DownloadOnePage(self, urls): 89 """Download a page emulated by a list of urls. 90 91 Args: 92 urls: list of URLs to fetch. 93 Returns: 94 A tuple (page download time, max packet number). 95 """ 96 if self.use_wget: 97 cmd = 'wget -O -' 98 else: 99 cmd = '%s/quic_client --port=%s --address=%s' % ( 100 self.quic_binary_dir, self.quic_server_port, self.quic_server_address) 101 cmd_in_list = shlex.split(cmd) 102 cmd_in_list.extend(urls) 103 start_time = Timestamp() 104 ps_proc = subprocess.Popen(cmd_in_list, 105 stdout=subprocess.PIPE, 106 stderr=subprocess.PIPE) 107 _std_out, std_err = ps_proc.communicate() 108 end_time = Timestamp() 109 delta_time = end_time - start_time 110 max_packets = 0 111 if not self.use_wget: 112 for line in std_err.splitlines(): 113 if line.find('Client: Got packet') >= 0: 114 elems = line.split() 115 packet_num = int(elems[4]) 116 max_packets = max(max_packets, packet_num) 117 return delta_time, max_packets 118 119 def RunExperiment(self, infile, delay_file, packets_file=None, num_it=1): 120 """Run the pageload experiment. 121 122 Args: 123 infile: Input json file describing the page list. 124 delay_file: Output file storing delay in csv format. 125 packets_file: Output file storing max packet number in csv format. 126 num_it: Number of iterations to run in this experiment. 127 """ 128 page_list = self.ReadPages(infile) 129 header = [urls[0].rsplit('/', 1)[1] for urls in page_list] 130 header0 = 'wget' if self.use_wget else 'quic' 131 header = [header0] + header 132 133 plt_list = [] 134 packets_list = [] 135 for i in range(num_it): 136 plt_one_row = [str(i)] 137 packets_one_row = [str(i)] 138 for urls in page_list: 139 time_micros, num_packets = self.DownloadOnePage(urls) 140 time_secs = time_micros / 1000000.0 141 plt_one_row.append('%6.3f' % time_secs) 142 packets_one_row.append('%5d' % num_packets) 143 plt_list.append(plt_one_row) 144 packets_list.append(packets_one_row) 145 146 with open(delay_file, 'w') as f: 147 csv_writer = csv.writer(f, delimiter=',') 148 csv_writer.writerow(header) 149 for one_row in plt_list: 150 csv_writer.writerow(one_row) 151 if packets_file: 152 with open(packets_file, 'w') as f: 153 csv_writer = csv.writer(f, delimiter=',') 154 csv_writer.writerow(header) 155 for one_row in packets_list: 156 csv_writer.writerow(one_row) 157 158 159 def main(): 160 parser = OptionParser() 161 parser.add_option('--use_wget', dest='use_wget', action='store_true', 162 default=False) 163 # Note that only debug version generates the log containing packets 164 # information. 165 parser.add_option('--quic_binary_dir', dest='quic_binary_dir', 166 default='../../../../out/Debug') 167 # For whatever server address you specify, you need to run the 168 # quic_server on that machine and populate it with the cache containing 169 # the URLs requested in the --infile. 170 parser.add_option('--address', dest='quic_server_address', 171 default='127.0.0.1') 172 parser.add_option('--port', dest='quic_server_port', 173 default='5002') 174 parser.add_option('--delay_file', dest='delay_file', default='delay.csv') 175 parser.add_option('--packets_file', dest='packets_file', 176 default='packets.csv') 177 parser.add_option('--infile', dest='infile', default='test_urls.json') 178 (options, _) = parser.parse_args() 179 180 exp = PageloadExperiment(options.use_wget, options.quic_binary_dir, 181 options.quic_server_address, 182 options.quic_server_port) 183 exp.RunExperiment(options.infile, options.delay_file, options.packets_file) 184 185 if __name__ == '__main__': 186 sys.exit(main()) 187