#!/usr/bin/env python # Copyright 2013 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import csv import datetime import json import os import shlex import subprocess import sys from optparse import OptionParser """Start a client to fetch web pages either using wget or using quic_client. If --use_wget is set, it uses wget. Usage: This invocation run_client.py --quic_binary_dir=../../../../out/Debug \ --address=127.0.0.1 --port=5000 --infile=test_urls.json \ --delay_file=delay.csv --packets_file=packets.csv fetches pages listed in test_urls.json from a quic server running at 127.0.0.1 on port 5000 using quic binary ../../../../out/Debug/quic_client and stores the delay in delay.csv and the max received packet number (for QUIC) in packets.csv. If --use_wget is present, it will fetch the URLs using wget and ignores the flags --address, --port, --quic_binary_dir, etc. """ def Timestamp(datetm=None): """Get the timestamp in microseconds. Args: datetm: the date and time to be converted to timestamp. If not set, use the current UTC time. Returns: The timestamp in microseconds. """ datetm = datetm or datetime.datetime.utcnow() diff = datetm - datetime.datetime.utcfromtimestamp(0) timestamp = (diff.days * 86400 + diff.seconds) * 1000000 + diff.microseconds return timestamp class PageloadExperiment: def __init__(self, use_wget, quic_binary_dir, quic_server_address, quic_server_port): """Initialize PageloadExperiment. Args: use_wget: Whether to use wget. quic_binary_dir: Directory for quic_binary. quic_server_address: IP address of quic server. quic_server_port: Port of the quic server. """ self.use_wget = use_wget self.quic_binary_dir = quic_binary_dir self.quic_server_address = quic_server_address self.quic_server_port = quic_server_port if not use_wget and not os.path.isfile(quic_binary_dir + '/quic_client'): raise IOError('There is no quic_client in the given dir: %s.' % quic_binary_dir) @classmethod def ReadPages(cls, json_file): """Return the list of URLs from the json_file. One entry of the list may contain a html link and multiple resources. """ page_list = [] with open(json_file) as f: data = json.load(f) for page in data['pages']: url = page['url'] if 'resources' in page: resources = page['resources'] else: resources = None if not resources: page_list.append([url]) else: urls = [url] # For url http://x.com/z/y.html, url_dir is http://x.com/z url_dir = url.rsplit('/', 1)[0] for resource in resources: urls.append(url_dir + '/' + resource) page_list.append(urls) return page_list def DownloadOnePage(self, urls): """Download a page emulated by a list of urls. Args: urls: list of URLs to fetch. Returns: A tuple (page download time, max packet number). """ if self.use_wget: cmd = 'wget -O -' else: cmd = '%s/quic_client --port=%s --address=%s' % ( self.quic_binary_dir, self.quic_server_port, self.quic_server_address) cmd_in_list = shlex.split(cmd) cmd_in_list.extend(urls) start_time = Timestamp() ps_proc = subprocess.Popen(cmd_in_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _std_out, std_err = ps_proc.communicate() end_time = Timestamp() delta_time = end_time - start_time max_packets = 0 if not self.use_wget: for line in std_err.splitlines(): if line.find('Client: Got packet') >= 0: elems = line.split() packet_num = int(elems[4]) max_packets = max(max_packets, packet_num) return delta_time, max_packets def RunExperiment(self, infile, delay_file, packets_file=None, num_it=1): """Run the pageload experiment. Args: infile: Input json file describing the page list. delay_file: Output file storing delay in csv format. packets_file: Output file storing max packet number in csv format. num_it: Number of iterations to run in this experiment. """ page_list = self.ReadPages(infile) header = [urls[0].rsplit('/', 1)[1] for urls in page_list] header0 = 'wget' if self.use_wget else 'quic' header = [header0] + header plt_list = [] packets_list = [] for i in range(num_it): plt_one_row = [str(i)] packets_one_row = [str(i)] for urls in page_list: time_micros, num_packets = self.DownloadOnePage(urls) time_secs = time_micros / 1000000.0 plt_one_row.append('%6.3f' % time_secs) packets_one_row.append('%5d' % num_packets) plt_list.append(plt_one_row) packets_list.append(packets_one_row) with open(delay_file, 'w') as f: csv_writer = csv.writer(f, delimiter=',') csv_writer.writerow(header) for one_row in plt_list: csv_writer.writerow(one_row) if packets_file: with open(packets_file, 'w') as f: csv_writer = csv.writer(f, delimiter=',') csv_writer.writerow(header) for one_row in packets_list: csv_writer.writerow(one_row) def main(): parser = OptionParser() parser.add_option('--use_wget', dest='use_wget', action='store_true', default=False) # Note that only debug version generates the log containing packets # information. parser.add_option('--quic_binary_dir', dest='quic_binary_dir', default='../../../../out/Debug') # For whatever server address you specify, you need to run the # quic_server on that machine and populate it with the cache containing # the URLs requested in the --infile. parser.add_option('--address', dest='quic_server_address', default='127.0.0.1') parser.add_option('--port', dest='quic_server_port', default='5002') parser.add_option('--delay_file', dest='delay_file', default='delay.csv') parser.add_option('--packets_file', dest='packets_file', default='packets.csv') parser.add_option('--infile', dest='infile', default='test_urls.json') (options, _) = parser.parse_args() exp = PageloadExperiment(options.use_wget, options.quic_binary_dir, options.quic_server_address, options.quic_server_port) exp.RunExperiment(options.infile, options.delay_file, options.packets_file) if __name__ == '__main__': sys.exit(main())