Chromecast Android buildfix: rework CommandLine initialization logic.
[chromium-blink-merge.git] / net / tools / gdig / gdig.cc
blob179ab78829475eb6166733f1a7d366e138820276
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include <stdio.h>
6 #include <string>
8 #include "base/at_exit.h"
9 #include "base/bind.h"
10 #include "base/cancelable_callback.h"
11 #include "base/command_line.h"
12 #include "base/files/file_util.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/time.h"
21 #include "net/base/address_list.h"
22 #include "net/base/ip_endpoint.h"
23 #include "net/base/net_errors.h"
24 #include "net/base/net_log.h"
25 #include "net/base/net_util.h"
26 #include "net/dns/dns_client.h"
27 #include "net/dns/dns_config_service.h"
28 #include "net/dns/dns_protocol.h"
29 #include "net/dns/host_cache.h"
30 #include "net/dns/host_resolver_impl.h"
31 #include "net/tools/gdig/file_net_log.h"
33 #if defined(OS_MACOSX)
34 #include "base/mac/scoped_nsautorelease_pool.h"
35 #endif
37 namespace net {
39 namespace {
41 bool StringToIPEndPoint(const std::string& ip_address_and_port,
42 IPEndPoint* ip_end_point) {
43 DCHECK(ip_end_point);
45 std::string ip;
46 int port;
47 if (!ParseHostAndPort(ip_address_and_port, &ip, &port))
48 return false;
49 if (port == -1)
50 port = dns_protocol::kDefaultPort;
52 net::IPAddressNumber ip_number;
53 if (!net::ParseIPLiteralToNumber(ip, &ip_number))
54 return false;
56 *ip_end_point = net::IPEndPoint(ip_number, port);
57 return true;
60 // Convert DnsConfig to human readable text omitting the hosts member.
61 std::string DnsConfigToString(const DnsConfig& dns_config) {
62 std::string output;
63 output.append("search ");
64 for (size_t i = 0; i < dns_config.search.size(); ++i) {
65 output.append(dns_config.search[i] + " ");
67 output.append("\n");
69 for (size_t i = 0; i < dns_config.nameservers.size(); ++i) {
70 output.append("nameserver ");
71 output.append(dns_config.nameservers[i].ToString()).append("\n");
74 base::StringAppendF(&output, "options ndots:%d\n", dns_config.ndots);
75 base::StringAppendF(&output, "options timeout:%d\n",
76 static_cast<int>(dns_config.timeout.InMilliseconds()));
77 base::StringAppendF(&output, "options attempts:%d\n", dns_config.attempts);
78 if (dns_config.rotate)
79 output.append("options rotate\n");
80 if (dns_config.edns0)
81 output.append("options edns0\n");
82 return output;
85 // Convert DnsConfig hosts member to a human readable text.
86 std::string DnsHostsToString(const DnsHosts& dns_hosts) {
87 std::string output;
88 for (DnsHosts::const_iterator i = dns_hosts.begin();
89 i != dns_hosts.end();
90 ++i) {
91 const DnsHostsKey& key = i->first;
92 std::string host_name = key.first;
93 output.append(IPEndPoint(i->second, -1).ToStringWithoutPort());
94 output.append(" ").append(host_name).append("\n");
96 return output;
99 struct ReplayLogEntry {
100 base::TimeDelta start_time;
101 std::string domain_name;
104 typedef std::vector<ReplayLogEntry> ReplayLog;
106 // Loads and parses a replay log file and fills |replay_log| with a structured
107 // representation. Returns whether the operation was successful. If not, the
108 // contents of |replay_log| are undefined.
110 // The replay log is a text file where each line contains
112 // timestamp_in_milliseconds domain_name
114 // The timestamp_in_milliseconds needs to be an integral delta from start of
115 // resolution and is in milliseconds. domain_name is the name to be resolved.
117 // The file should be sorted by timestamp in ascending time.
118 bool LoadReplayLog(const base::FilePath& file_path, ReplayLog* replay_log) {
119 std::string original_replay_log_contents;
120 if (!base::ReadFileToString(file_path, &original_replay_log_contents)) {
121 fprintf(stderr, "Unable to open replay file %s\n",
122 file_path.MaybeAsASCII().c_str());
123 return false;
126 // Strip out \r characters for Windows files. This isn't as efficient as a
127 // smarter line splitter, but this particular use does not need to target
128 // efficiency.
129 std::string replay_log_contents;
130 base::RemoveChars(original_replay_log_contents, "\r", &replay_log_contents);
132 std::vector<std::string> lines;
133 base::SplitString(replay_log_contents, '\n', &lines);
134 base::TimeDelta previous_delta;
135 bool bad_parse = false;
136 for (unsigned i = 0; i < lines.size(); ++i) {
137 if (lines[i].empty())
138 continue;
139 std::vector<std::string> time_and_name;
140 base::SplitString(lines[i], ' ', &time_and_name);
141 if (time_and_name.size() != 2) {
142 fprintf(
143 stderr,
144 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
145 file_path.MaybeAsASCII().c_str(),
146 i + 1);
147 bad_parse = true;
148 continue;
151 int64 delta_in_milliseconds;
152 if (!base::StringToInt64(time_and_name[0], &delta_in_milliseconds)) {
153 fprintf(
154 stderr,
155 "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
156 file_path.MaybeAsASCII().c_str(),
157 i + 1);
158 bad_parse = true;
159 continue;
162 base::TimeDelta delta =
163 base::TimeDelta::FromMilliseconds(delta_in_milliseconds);
164 if (delta < previous_delta) {
165 fprintf(
166 stderr,
167 "[%s %u] replay log should be sorted by time\n",
168 file_path.MaybeAsASCII().c_str(),
169 i + 1);
170 bad_parse = true;
171 continue;
174 previous_delta = delta;
175 ReplayLogEntry entry;
176 entry.start_time = delta;
177 entry.domain_name = time_and_name[1];
178 replay_log->push_back(entry);
180 return !bad_parse;
183 class GDig {
184 public:
185 GDig();
186 ~GDig();
188 enum Result {
189 RESULT_NO_RESOLVE = -3,
190 RESULT_NO_CONFIG = -2,
191 RESULT_WRONG_USAGE = -1,
192 RESULT_OK = 0,
193 RESULT_PENDING = 1,
196 Result Main(int argc, const char* argv[]);
198 private:
199 bool ParseCommandLine(int argc, const char* argv[]);
201 void Start();
202 void Finish(Result);
204 void OnDnsConfig(const DnsConfig& dns_config_const);
205 void OnResolveComplete(unsigned index, AddressList* address_list,
206 base::TimeDelta time_since_start, int val);
207 void OnTimeout();
208 void ReplayNextEntry();
210 base::TimeDelta config_timeout_;
211 bool print_config_;
212 bool print_hosts_;
213 net::IPEndPoint nameserver_;
214 base::TimeDelta timeout_;
215 int parallellism_;
216 ReplayLog replay_log_;
217 unsigned replay_log_index_;
218 base::Time start_time_;
219 int active_resolves_;
220 Result result_;
222 base::CancelableClosure timeout_closure_;
223 scoped_ptr<DnsConfigService> dns_config_service_;
224 scoped_ptr<FileNetLogObserver> log_observer_;
225 scoped_ptr<NetLog> log_;
226 scoped_ptr<HostResolver> resolver_;
228 #if defined(OS_MACOSX)
229 // Without this there will be a mem leak on osx.
230 base::mac::ScopedNSAutoreleasePool scoped_pool_;
231 #endif
233 // Need AtExitManager to support AsWeakPtr (in NetLog).
234 base::AtExitManager exit_manager_;
237 GDig::GDig()
238 : config_timeout_(base::TimeDelta::FromSeconds(5)),
239 print_config_(false),
240 print_hosts_(false),
241 parallellism_(6),
242 replay_log_index_(0u),
243 active_resolves_(0) {
246 GDig::~GDig() {
247 if (log_)
248 log_->RemoveThreadSafeObserver(log_observer_.get());
251 GDig::Result GDig::Main(int argc, const char* argv[]) {
252 if (!ParseCommandLine(argc, argv)) {
253 fprintf(stderr,
254 "usage: %s [--net_log[=<basic|no_bytes|all>]]"
255 " [--print_config] [--print_hosts]"
256 " [--nameserver=<ip_address[:port]>]"
257 " [--timeout=<milliseconds>]"
258 " [--config_timeout=<seconds>]"
259 " [--j=<parallel resolves>]"
260 " [--replay_file=<path>]"
261 " [domain_name]\n",
262 argv[0]);
263 return RESULT_WRONG_USAGE;
266 base::MessageLoopForIO loop;
268 result_ = RESULT_PENDING;
269 Start();
270 if (result_ == RESULT_PENDING)
271 base::MessageLoop::current()->Run();
273 // Destroy it while MessageLoopForIO is alive.
274 dns_config_service_.reset();
275 return result_;
278 bool GDig::ParseCommandLine(int argc, const char* argv[]) {
279 base::CommandLine::Init(argc, argv);
280 const base::CommandLine& parsed_command_line =
281 *base::CommandLine::ForCurrentProcess();
283 if (parsed_command_line.HasSwitch("config_timeout")) {
284 int timeout_seconds = 0;
285 bool parsed = base::StringToInt(
286 parsed_command_line.GetSwitchValueASCII("config_timeout"),
287 &timeout_seconds);
288 if (parsed && timeout_seconds > 0) {
289 config_timeout_ = base::TimeDelta::FromSeconds(timeout_seconds);
290 } else {
291 fprintf(stderr, "Invalid config_timeout parameter\n");
292 return false;
296 if (parsed_command_line.HasSwitch("net_log")) {
297 std::string log_param = parsed_command_line.GetSwitchValueASCII("net_log");
298 NetLog::LogLevel level = NetLog::LOG_ALL_BUT_BYTES;
300 if (log_param.length() > 0) {
301 std::map<std::string, NetLog::LogLevel> log_levels;
302 log_levels["all"] = NetLog::LOG_ALL;
303 log_levels["no_bytes"] = NetLog::LOG_ALL_BUT_BYTES;
305 if (log_levels.find(log_param) != log_levels.end()) {
306 level = log_levels[log_param];
307 } else {
308 fprintf(stderr, "Invalid net_log parameter\n");
309 return false;
312 log_.reset(new NetLog);
313 log_observer_.reset(new FileNetLogObserver(stderr));
314 log_->AddThreadSafeObserver(log_observer_.get(), level);
317 print_config_ = parsed_command_line.HasSwitch("print_config");
318 print_hosts_ = parsed_command_line.HasSwitch("print_hosts");
320 if (parsed_command_line.HasSwitch("nameserver")) {
321 std::string nameserver =
322 parsed_command_line.GetSwitchValueASCII("nameserver");
323 if (!StringToIPEndPoint(nameserver, &nameserver_)) {
324 fprintf(stderr,
325 "Cannot parse the namerserver string into an IPEndPoint\n");
326 return false;
330 if (parsed_command_line.HasSwitch("timeout")) {
331 int timeout_millis = 0;
332 bool parsed = base::StringToInt(
333 parsed_command_line.GetSwitchValueASCII("timeout"),
334 &timeout_millis);
335 if (parsed && timeout_millis > 0) {
336 timeout_ = base::TimeDelta::FromMilliseconds(timeout_millis);
337 } else {
338 fprintf(stderr, "Invalid timeout parameter\n");
339 return false;
343 if (parsed_command_line.HasSwitch("replay_file")) {
344 base::FilePath replay_path =
345 parsed_command_line.GetSwitchValuePath("replay_file");
346 if (!LoadReplayLog(replay_path, &replay_log_))
347 return false;
350 if (parsed_command_line.HasSwitch("j")) {
351 int parallellism = 0;
352 bool parsed = base::StringToInt(
353 parsed_command_line.GetSwitchValueASCII("j"),
354 &parallellism);
355 if (parsed && parallellism > 0) {
356 parallellism_ = parallellism;
357 } else {
358 fprintf(stderr, "Invalid parallellism parameter\n");
362 if (parsed_command_line.GetArgs().size() == 1) {
363 ReplayLogEntry entry;
364 entry.start_time = base::TimeDelta();
365 #if defined(OS_WIN)
366 entry.domain_name = base::UTF16ToASCII(parsed_command_line.GetArgs()[0]);
367 #else
368 entry.domain_name = parsed_command_line.GetArgs()[0];
369 #endif
370 replay_log_.push_back(entry);
371 } else if (parsed_command_line.GetArgs().size() != 0) {
372 return false;
374 return print_config_ || print_hosts_ || !replay_log_.empty();
377 void GDig::Start() {
378 if (nameserver_.address().size() > 0) {
379 DnsConfig dns_config;
380 dns_config.attempts = 1;
381 dns_config.nameservers.push_back(nameserver_);
382 OnDnsConfig(dns_config);
383 } else {
384 dns_config_service_ = DnsConfigService::CreateSystemService();
385 dns_config_service_->ReadConfig(base::Bind(&GDig::OnDnsConfig,
386 base::Unretained(this)));
387 timeout_closure_.Reset(base::Bind(&GDig::OnTimeout,
388 base::Unretained(this)));
389 base::MessageLoop::current()->PostDelayedTask(
390 FROM_HERE, timeout_closure_.callback(), config_timeout_);
394 void GDig::Finish(Result result) {
395 DCHECK_NE(RESULT_PENDING, result);
396 result_ = result;
397 if (base::MessageLoop::current())
398 base::MessageLoop::current()->Quit();
401 void GDig::OnDnsConfig(const DnsConfig& dns_config_const) {
402 timeout_closure_.Cancel();
403 DCHECK(dns_config_const.IsValid());
404 DnsConfig dns_config = dns_config_const;
406 if (timeout_.InMilliseconds() > 0)
407 dns_config.timeout = timeout_;
408 if (print_config_) {
409 printf("# Dns Configuration\n"
410 "%s", DnsConfigToString(dns_config).c_str());
412 if (print_hosts_) {
413 printf("# Host Database\n"
414 "%s", DnsHostsToString(dns_config.hosts).c_str());
417 if (replay_log_.empty()) {
418 Finish(RESULT_OK);
419 return;
422 scoped_ptr<DnsClient> dns_client(DnsClient::CreateClient(NULL));
423 dns_client->SetConfig(dns_config);
424 HostResolver::Options options;
425 options.max_concurrent_resolves = parallellism_;
426 options.max_retry_attempts = 1u;
427 scoped_ptr<HostResolverImpl> resolver(
428 new HostResolverImpl(options, log_.get()));
429 resolver->SetDnsClient(dns_client.Pass());
430 resolver_ = resolver.Pass();
432 start_time_ = base::Time::Now();
434 ReplayNextEntry();
437 void GDig::ReplayNextEntry() {
438 DCHECK_LT(replay_log_index_, replay_log_.size());
440 base::TimeDelta time_since_start = base::Time::Now() - start_time_;
441 while (replay_log_index_ < replay_log_.size()) {
442 const ReplayLogEntry& entry = replay_log_[replay_log_index_];
443 if (time_since_start < entry.start_time) {
444 // Delay call to next time and return.
445 base::MessageLoop::current()->PostDelayedTask(
446 FROM_HERE,
447 base::Bind(&GDig::ReplayNextEntry, base::Unretained(this)),
448 entry.start_time - time_since_start);
449 return;
452 HostResolver::RequestInfo info(HostPortPair(entry.domain_name.c_str(), 80));
453 AddressList* addrlist = new AddressList();
454 unsigned current_index = replay_log_index_;
455 CompletionCallback callback = base::Bind(&GDig::OnResolveComplete,
456 base::Unretained(this),
457 current_index,
458 base::Owned(addrlist),
459 time_since_start);
460 ++active_resolves_;
461 ++replay_log_index_;
462 int ret = resolver_->Resolve(
463 info,
464 DEFAULT_PRIORITY,
465 addrlist,
466 callback,
467 NULL,
468 BoundNetLog::Make(log_.get(), net::NetLog::SOURCE_NONE));
469 if (ret != ERR_IO_PENDING)
470 callback.Run(ret);
474 void GDig::OnResolveComplete(unsigned entry_index,
475 AddressList* address_list,
476 base::TimeDelta resolve_start_time,
477 int val) {
478 DCHECK_GT(active_resolves_, 0);
479 DCHECK(address_list);
480 DCHECK_LT(entry_index, replay_log_.size());
481 --active_resolves_;
482 base::TimeDelta resolve_end_time = base::Time::Now() - start_time_;
483 base::TimeDelta resolve_time = resolve_end_time - resolve_start_time;
484 printf("%u %d %d %s %d ",
485 entry_index,
486 static_cast<int>(resolve_end_time.InMilliseconds()),
487 static_cast<int>(resolve_time.InMilliseconds()),
488 replay_log_[entry_index].domain_name.c_str(), val);
489 if (val != OK) {
490 std::string error_string = ErrorToString(val);
491 printf("%s", error_string.c_str());
492 } else {
493 for (size_t i = 0; i < address_list->size(); ++i) {
494 if (i != 0)
495 printf(" ");
496 printf("%s", (*address_list)[i].ToStringWithoutPort().c_str());
499 printf("\n");
500 if (active_resolves_ == 0 && replay_log_index_ >= replay_log_.size())
501 Finish(RESULT_OK);
504 void GDig::OnTimeout() {
505 fprintf(stderr, "Timed out waiting to load the dns config\n");
506 Finish(RESULT_NO_CONFIG);
509 } // empty namespace
511 } // namespace net
513 int main(int argc, const char* argv[]) {
514 net::GDig dig;
515 return dig.Main(argc, argv);