fetch_client.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott// Use of this source code is governed by a BSD-style license that can be 3c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott// found in the LICENSE file. 4c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 5c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "build/build_config.h" 6c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 7c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "base/at_exit.h" 8c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "base/command_line.h" 9c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "base/message_loop.h" 10731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick#include "base/metrics/stats_counters.h" 11c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "base/singleton.h" 123345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick#include "base/string_number_conversions.h" 13c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "base/string_util.h" 14c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/base/completion_callback.h" 15c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/base/host_resolver.h" 16c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/base/io_buffer.h" 17c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/base/net_errors.h" 18c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/base/ssl_config_service.h" 19c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "net/http/http_auth_handler_factory.h" 20c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/http/http_cache.h" 21c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/http/http_network_layer.h" 22c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/http/http_request_info.h" 23c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/http/http_transaction.h" 24c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/proxy/proxy_service.h" 25c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott#include "net/socket/client_socket_factory.h" 26c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 27c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scottvoid usage(const char* program_name) { 28c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("usage: %s --url=<url> [--n=<clients>] [--stats] [--use_cache]\n", 29c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott program_name); 30c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott exit(1); 31c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott} 32c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 33c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott// Test Driver 34c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scottclass Driver { 35c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott public: 36c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott Driver() 37c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott : clients_(0) {} 38c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 39c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott void ClientStarted() { clients_++; } 40c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott void ClientStopped() { 41c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (!--clients_) { 42c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott MessageLoop::current()->Quit(); 43c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 44c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 45c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 46c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott private: 47c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int clients_; 48c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott}; 49c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 50c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott// A network client 51c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scottclass Client { 52c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott public: 53c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott Client(net::HttpTransactionFactory* factory, const std::string& url) : 54c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott url_(url), 55c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott buffer_(new net::IOBuffer(kBufferSize)), 56c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott ALLOW_THIS_IN_INITIALIZER_LIST( 57c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott connect_callback_(this, &Client::OnConnectComplete)), 58c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott ALLOW_THIS_IN_INITIALIZER_LIST( 59c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott read_callback_(this, &Client::OnReadComplete)) { 60c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int rv = factory->CreateTransaction(&transaction_); 61c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott DCHECK_EQ(net::OK, rv); 62c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott buffer_->AddRef(); 63c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott driver_->ClientStarted(); 64c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott request_info_.url = url_; 65c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott request_info_.method = "GET"; 66c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int state = transaction_->Start( 67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch &request_info_, &connect_callback_, net::BoundNetLog()); 68c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott DCHECK(state == net::ERR_IO_PENDING); 69c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott }; 70c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 71c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott private: 72c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott void OnConnectComplete(int result) { 73c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott // Do work here. 74c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int state = transaction_->Read(buffer_.get(), kBufferSize, &read_callback_); 75c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (state == net::ERR_IO_PENDING) 76c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott return; // IO has started. 77c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (state < 0) 78c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott return; // ERROR! 79c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott OnReadComplete(state); 80c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 81c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 82c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott void OnReadComplete(int result) { 83c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (result == 0) { 84c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott OnRequestComplete(result); 85c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott return; 86c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 87c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 88c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott // Deal with received data here. 89731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick static base::StatsCounter bytes_read("FetchClient.bytes_read"); 90c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott bytes_read.Add(result); 91c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 92c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott // Issue a read for more data. 93c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int state = transaction_->Read(buffer_.get(), kBufferSize, &read_callback_); 94c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (state == net::ERR_IO_PENDING) 95c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott return; // IO has started. 96c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (state < 0) 97c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott return; // ERROR! 98c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott OnReadComplete(state); 99c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 100c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 101c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott void OnRequestComplete(int result) { 102731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick static base::StatsCounter requests("FetchClient.requests"); 103c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott requests.Increment(); 104c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott driver_->ClientStopped(); 105c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("."); 106c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 107c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 108c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott static const int kBufferSize = (16 * 1024); 109c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott GURL url_; 110c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott net::HttpRequestInfo request_info_; 111c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott scoped_ptr<net::HttpTransaction> transaction_; 112c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott scoped_refptr<net::IOBuffer> buffer_; 113c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott net::CompletionCallbackImpl<Client> connect_callback_; 114c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott net::CompletionCallbackImpl<Client> read_callback_; 115c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott Singleton<Driver> driver_; 116c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott}; 117c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 118c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scottint main(int argc, char**argv) { 119c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott base::AtExitManager exit; 120731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick base::StatsTable table("fetchclient", 50, 1000); 121c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott table.set_current(&table); 122c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 123c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott CommandLine::Init(argc, argv); 124c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 125c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott std::string url = parsed_command_line.GetSwitchValueASCII("url"); 126c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (!url.length()) 127c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott usage(argv[0]); 128c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int client_limit = 1; 1293345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick if (parsed_command_line.HasSwitch("n")) { 1303345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick base::StringToInt(parsed_command_line.GetSwitchValueASCII("n"), 1313345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick &client_limit); 1323345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick } 133c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott bool use_cache = parsed_command_line.HasSwitch("use-cache"); 134c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 135c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott // Do work here. 136c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott MessageLoop loop(MessageLoop::TYPE_IO); 137c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 138731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick scoped_ptr<net::HostResolver> host_resolver( 1393345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism, 1403345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick NULL)); 141c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 142c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott scoped_refptr<net::ProxyService> proxy_service( 1433345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick net::ProxyService::CreateDirect()); 144c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott scoped_refptr<net::SSLConfigService> ssl_config_service( 145c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott net::SSLConfigService::CreateSystemSSLConfigService()); 146c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott net::HttpTransactionFactory* factory = NULL; 147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch scoped_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory( 148731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick net::HttpAuthHandlerFactory::CreateDefault(host_resolver.get())); 149c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (use_cache) { 150731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick factory = new net::HttpCache(host_resolver.get(), NULL, proxy_service, 151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ssl_config_service, http_auth_handler_factory.get(), NULL, NULL, 152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch net::HttpCache::DefaultBackend::InMemory(0)); 153c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } else { 154c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott factory = new net::HttpNetworkLayer( 155731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick net::ClientSocketFactory::GetDefaultFactory(), 156731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick host_resolver.get(), 157731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick NULL /* dnsrr_resolver */, 158731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick NULL /* ssl_host_info_factory */, 159731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick proxy_service, 160731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick ssl_config_service, 161731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick http_auth_handler_factory.get(), 162731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick NULL, 163731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick NULL); 164c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 165c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 166c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott { 167731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick base::StatsCounterTimer driver_time("FetchClient.total_time"); 168731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick base::StatsScope<base::StatsCounterTimer> scope(driver_time); 169c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 170c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott Client** clients = new Client*[client_limit]; 171c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott for (int i = 0; i < client_limit; i++) 172c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott clients[i] = new Client(factory, url); 173c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 174c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott MessageLoop::current()->Run(); 175c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 176c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 177c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott // Print Statistics here. 178c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int num_clients = table.GetCounterValue("c:FetchClient.requests"); 179c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int test_time = table.GetCounterValue("t:FetchClient.total_time"); 180c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int bytes_read = table.GetCounterValue("c:FetchClient.bytes_read"); 181c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 182c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("\n"); 183c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("Clients : %d\n", num_clients); 184c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("Time : %dms\n", test_time); 185c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("Bytes Read : %d\n", bytes_read); 186c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (test_time > 0) { 187c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott const char *units = "bps"; 188c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott double bps = static_cast<float>(bytes_read * 8) / 189c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott (static_cast<float>(test_time) / 1000.0); 190c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 191c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (bps > (1024*1024)) { 192c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott bps /= (1024*1024); 193c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott units = "Mbps"; 194c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } else if (bps > 1024) { 195c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott bps /= 1024; 196c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott units = "Kbps"; 197c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 198c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("Bandwidth : %.2f%s\n", bps, units); 199c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 200c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott 201c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (parsed_command_line.HasSwitch("stats")) { 202c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott // Dump the stats table. 203c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("<stats>\n"); 204c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int counter_max = table.GetMaxCounters(); 205c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott for (int index=0; index < counter_max; index++) { 206c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott std::string name(table.GetRowName(index)); 207c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott if (name.length() > 0) { 208c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott int value = table.GetRowValue(index); 209c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("%s:\t%d\n", name.c_str(), value); 210c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 211c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 212c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott printf("</stats>\n"); 213c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott } 214c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott return 0; 215c7f5f8508d98d5952d42ed7648c2a8f30a4da156Patrick Scott} 216