普通文本  |  959行  |  34.86 KB

// Copyright 2012 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.

#include "chrome/browser/net/network_stats.h"

#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chrome/common/chrome_version_info.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/net_errors.h"
#include "net/base/network_change_notifier.h"
#include "net/base/test_completion_callback.h"
#include "net/dns/single_request_host_resolver.h"
#include "net/proxy/proxy_service.h"
#include "net/socket/client_socket_factory.h"
#include "net/udp/datagram_client_socket.h"
#include "url/gurl.h"

using content::BrowserThread;

namespace chrome_browser_net {

// static
uint32 NetworkStats::maximum_tests_ = 8;
// static
uint32 NetworkStats::maximum_sequential_packets_ = 21;
// static
uint32 NetworkStats::maximum_NAT_packets_ = 2;
// static
uint32 NetworkStats::maximum_NAT_idle_seconds_ = 300;
// static
bool NetworkStats::start_test_after_connect_ = true;

// Specify the possible choices of probe packet sizes.
const uint32 kProbePacketBytes[] = {100, 500, 1200};
const uint32 kPacketSizeChoices = arraysize(kProbePacketBytes);

// List of ports used for probing test.
const uint16 kPorts[] = {443, 80};

// Number of first few packets that are recorded in a packet-correlation
// histogram, which shows exactly what sequence of packets were received.
// We use this to deduce specific packet loss correlation.
const uint32 kCorrelatedLossPacketCount = 6;

// This specifies the maximum message (payload) size of one packet.
const uint32 kMaxMessageSize = 1600;

// This specifies the maximum udp receiver buffer size.
const uint32 kMaxUdpReceiveBufferSize = 63000;

// This specifies the maximum udp receiver buffer size.
const uint32 kMaxUdpSendBufferSize = 4096;

// This should match TestType except for the last one.
const char* kTestName[] = {"TokenRequest", "StartPacket", "NonPacedPacket",
                           "PacedPacket", "NATBind", "PacketSizeTest"};

// Perform Pacing/Non-pacing test only if at least 2 packets are received
// in the StartPacketTest.
const uint32 kMinimumReceivedPacketsForPacingTest = 2;
// Perform NAT binding test only if at least 10 packets are received.
const uint32 kMinimumReceivedPacketsForNATTest = 10;

// Maximum inter-packet pacing interval in microseconds.
const uint32 kMaximumPacingMicros = 1000000;
// Timeout value for getting the token.
const uint32 kGetTokenTimeoutSeconds = 10;
// Timeout value for StartPacket and NonPacedPacket if the client does not get
// reply. For PacedPacket test, the timeout value is this number plus the total
// pacing interval.
const uint32 kReadDataTimeoutSeconds = 30;
// This is the timeout for NAT without Idle periods.
// For NAT test with idle periods, the timeout is the Idle period + this value.
const uint32 kReadNATTimeoutSeconds = 10;
// This is the timeout for PACKET_SIZE_TEST.
const uint32 kReadPacketSizeTimeoutSeconds = 10;
// This is the maxmium number of packets we would send for PACKET_SIZE_TEST.
uint32 kMaximumPacketSizeTestPackets = 1;

// These helper functions are similar to UMA_HISTOGRAM_XXX except that they do
// not create a static histogram_pointer.
void DynamicHistogramEnumeration(const std::string& name,
                                 uint32 sample,
                                 uint32 boundary_value) {
  base::HistogramBase* histogram_pointer = base::LinearHistogram::FactoryGet(
      name,
      1,
      boundary_value,
      boundary_value + 1,
      base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram_pointer->Add(sample);
}

void DynamicHistogramTimes(const std::string& name,
                           const base::TimeDelta& sample) {
  base::HistogramBase* histogram_pointer = base::Histogram::FactoryTimeGet(
      name,
      base::TimeDelta::FromMilliseconds(1),
      base::TimeDelta::FromSeconds(30),
      50,
      base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram_pointer->AddTime(sample);
}

void DynamicHistogramCounts(const std::string& name,
                            uint32 sample,
                            uint32 min,
                            uint32 max,
                            uint32 bucket_count) {
  base::HistogramBase* histogram_pointer = base::Histogram::FactoryGet(
      name, min, max, bucket_count,
      base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram_pointer->Add(sample);
}

NetworkStats::NetworkStats(net::ClientSocketFactory* socket_factory)
    : socket_factory_(socket_factory),
      histogram_port_(0),
      has_proxy_server_(false),
      probe_packet_bytes_(0),
      bytes_for_packet_size_test_(0),
      current_test_index_(0),
      read_state_(READ_STATE_IDLE),
      write_state_(WRITE_STATE_IDLE),
      weak_factory_(this) {
  ResetData();
}

NetworkStats::~NetworkStats() {}

bool NetworkStats::Start(net::HostResolver* host_resolver,
                         const net::HostPortPair& server_host_port_pair,
                         uint16 histogram_port,
                         bool has_proxy_server,
                         uint32 probe_bytes,
                         uint32 bytes_for_packet_size_test,
                         const net::CompletionCallback& finished_callback) {
  DCHECK(host_resolver);
  histogram_port_ = histogram_port;
  has_proxy_server_ = has_proxy_server;
  probe_packet_bytes_ = probe_bytes;
  bytes_for_packet_size_test_ = bytes_for_packet_size_test;
  finished_callback_ = finished_callback;
  test_sequence_.clear();
  test_sequence_.push_back(TOKEN_REQUEST);

  ResetData();

  scoped_ptr<net::SingleRequestHostResolver> resolver(
      new net::SingleRequestHostResolver(host_resolver));
  net::HostResolver::RequestInfo request(server_host_port_pair);
  int rv =
      resolver->Resolve(request,
                        net::DEFAULT_PRIORITY,
                        &addresses_,
                        base::Bind(base::IgnoreResult(&NetworkStats::DoConnect),
                                   base::Unretained(this)),
                        net::BoundNetLog());
  if (rv == net::ERR_IO_PENDING) {
    resolver_.swap(resolver);
    return true;
  }
  return DoConnect(rv);
}

void NetworkStats::StartOneTest() {
  if (test_sequence_[current_test_index_] == TOKEN_REQUEST) {
    DCHECK_EQ(WRITE_STATE_IDLE, write_state_);
    write_buffer_ = NULL;
    SendHelloRequest();
  } else {
    SendProbeRequest();
  }
}

void NetworkStats::ResetData() {
  DCHECK_EQ(WRITE_STATE_IDLE, write_state_);
  write_buffer_ = NULL;
  packets_received_mask_.reset();
  first_arrival_time_ = base::TimeTicks();
  last_arrival_time_ = base::TimeTicks();

  packet_rtt_.clear();
  packet_rtt_.resize(maximum_sequential_packets_);
  probe_request_time_ = base::TimeTicks();
  // Note: inter_arrival_time_ should not be reset here because it is used in
  // subsequent tests.
}

bool NetworkStats::DoConnect(int result) {
  if (result != net::OK) {
    TestPhaseComplete(RESOLVE_FAILED, result);
    return false;
  }

  scoped_ptr<net::DatagramClientSocket> udp_socket =
      socket_factory_->CreateDatagramClientSocket(
          net::DatagramSocket::DEFAULT_BIND,
          net::RandIntCallback(),
          NULL,
          net::NetLog::Source());
  DCHECK(udp_socket);
  DCHECK(!socket_);
  socket_ = udp_socket.Pass();

  const net::IPEndPoint& endpoint = addresses_.front();
  int rv = socket_->Connect(endpoint);
  if (rv < 0) {
    TestPhaseComplete(CONNECT_FAILED, rv);
    return false;
  }

  socket_->SetSendBufferSize(kMaxUdpSendBufferSize);
  socket_->SetReceiveBufferSize(kMaxUdpReceiveBufferSize);
  return ConnectComplete(rv);
}

bool NetworkStats::ConnectComplete(int result) {
  if (result < 0) {
    TestPhaseComplete(CONNECT_FAILED, result);
    return false;
  }

  if (start_test_after_connect_) {
    // Reads data for all HelloReply and all subsequent probe tests.
    if (ReadData() != net::ERR_IO_PENDING) {
      TestPhaseComplete(READ_FAILED, result);
      return false;
    }
    SendHelloRequest();
  } else {
    // For unittesting. Only run the callback, do not destroy it.
    if (!finished_callback_.is_null())
      finished_callback_.Run(result);
  }
  return true;
}

void NetworkStats::SendHelloRequest() {
  StartReadDataTimer(kGetTokenTimeoutSeconds, current_test_index_);
  ProbePacket probe_packet;
  probe_message_.SetPacketHeader(ProbePacket_Type_HELLO_REQUEST, &probe_packet);
  probe_packet.set_group_id(current_test_index_);
  std::string output = probe_message_.MakeEncodedPacket(probe_packet);

  int result = SendData(output);
  if (result < 0 && result != net::ERR_IO_PENDING)
    TestPhaseComplete(WRITE_FAILED, result);
}

void NetworkStats::SendProbeRequest() {
  ResetData();
  // Use default timeout except for the NAT bind test.
  uint32 timeout_seconds = kReadDataTimeoutSeconds;
  uint32 number_packets = maximum_sequential_packets_;
  uint32 probe_bytes = probe_packet_bytes_;
  pacing_interval_ = base::TimeDelta();
  switch (test_sequence_[current_test_index_]) {
    case START_PACKET_TEST:
    case NON_PACED_PACKET_TEST:
      break;
    case PACED_PACKET_TEST: {
      pacing_interval_ =
          std::min(inter_arrival_time_,
                   base::TimeDelta::FromMicroseconds(kMaximumPacingMicros));
      timeout_seconds += pacing_interval_.InMicroseconds() *
                         (maximum_sequential_packets_ - 1) / 1000000;
      break;
    }
    case NAT_BIND_TEST: {
      // Make sure no integer overflow.
      DCHECK_LE(maximum_NAT_idle_seconds_, 4000U);
      int nat_test_idle_seconds = base::RandInt(1, maximum_NAT_idle_seconds_);
      pacing_interval_ = base::TimeDelta::FromSeconds(nat_test_idle_seconds);
      timeout_seconds = nat_test_idle_seconds + kReadNATTimeoutSeconds;
      number_packets = maximum_NAT_packets_;
      break;
    }
    case PACKET_SIZE_TEST: {
      number_packets = kMaximumPacketSizeTestPackets;
      probe_bytes = bytes_for_packet_size_test_;
      timeout_seconds = kReadPacketSizeTimeoutSeconds;
      break;
    }
    default:
      NOTREACHED();
      return;
  }
  DVLOG(1) << "NetworkStat: Probe pacing " << pacing_interval_.InMicroseconds()
           << " microseconds. Time out " << timeout_seconds << " seconds";
  ProbePacket probe_packet;
  probe_message_.GenerateProbeRequest(token_,
                                      current_test_index_,
                                      probe_bytes,
                                      pacing_interval_.InMicroseconds(),
                                      number_packets,
                                      &probe_packet);
  std::string output = probe_message_.MakeEncodedPacket(probe_packet);

  StartReadDataTimer(timeout_seconds, current_test_index_);
  probe_request_time_ = base::TimeTicks::Now();
  int result = SendData(output);
  if (result < 0 && result != net::ERR_IO_PENDING)
    TestPhaseComplete(WRITE_FAILED, result);
}

int NetworkStats::ReadData() {
  if (!socket_.get())
    return 0;

  if (read_state_ == READ_STATE_READ_PENDING)
    return net::ERR_IO_PENDING;

  int rv = 0;
  do {
    DCHECK(!read_buffer_.get());
    read_buffer_ = new net::IOBuffer(kMaxMessageSize);

    rv = socket_->Read(
        read_buffer_.get(),
        kMaxMessageSize,
        base::Bind(&NetworkStats::OnReadComplete, weak_factory_.GetWeakPtr()));
  } while (rv > 0 && !ReadComplete(rv));
  if (rv == net::ERR_IO_PENDING)
    read_state_ = READ_STATE_READ_PENDING;
  return rv;
}

void NetworkStats::OnReadComplete(int result) {
  DCHECK_NE(net::ERR_IO_PENDING, result);
  DCHECK_EQ(READ_STATE_READ_PENDING, read_state_);

  read_state_ = READ_STATE_IDLE;
  if (!ReadComplete(result)) {
    // Called ReadData() via PostDelayedTask() to avoid recursion. Added a delay
    // of 1ms so that the time-out will fire before we have time to really hog
    // the CPU too extensively (waiting for the time-out) in case of an infinite
    // loop.
    base::MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        base::Bind(base::IgnoreResult(&NetworkStats::ReadData),
                   weak_factory_.GetWeakPtr()),
        base::TimeDelta::FromMilliseconds(1));
  }
}

bool NetworkStats::ReadComplete(int result) {
  DCHECK(socket_.get());
  DCHECK_NE(net::ERR_IO_PENDING, result);
  if (result < 0) {
    // Something is wrong, finish the test.
    read_buffer_ = NULL;
    TestPhaseComplete(READ_FAILED, result);
    return true;
  }

  std::string encoded_message(read_buffer_->data(),
                              read_buffer_->data() + result);
  read_buffer_ = NULL;
  ProbePacket probe_packet;
  if (!probe_message_.ParseInput(encoded_message, &probe_packet))
    return false;
  // Discard if the packet is for a different test.
  if (probe_packet.group_id() != current_test_index_)
    return false;

  // Whether all packets in the current test have been received.
  bool current_test_complete = false;
  switch (probe_packet.header().type()) {
    case ProbePacket_Type_HELLO_REPLY:
      token_ = probe_packet.token();
      if (current_test_index_ == 0)
        test_sequence_.push_back(START_PACKET_TEST);
      current_test_complete = true;
      break;
    case ProbePacket_Type_PROBE_REPLY:
      current_test_complete = UpdateReception(probe_packet);
      break;
    default:
      DVLOG(1) << "Received unexpected packet type: "
               << probe_packet.header().type();
  }

  if (!current_test_complete) {
    // All packets have not been received for the current test.
    return false;
  }
  // All packets are received for the current test.
  // Read completes if all tests are done.
  bool all_tests_done = current_test_index_ >= maximum_tests_ ||
      current_test_index_ + 1 >= test_sequence_.size();
  TestPhaseComplete(SUCCESS, net::OK);
  return all_tests_done;
}

bool NetworkStats::UpdateReception(const ProbePacket& probe_packet) {
  uint32 packet_index = probe_packet.packet_index();
  if (packet_index >= packet_rtt_.size())
    return false;
  packets_received_mask_.set(packet_index);
  TestType test_type = test_sequence_[current_test_index_];
  uint32 received_packets = packets_received_mask_.count();

  // Now() has resolution ~1-15ms. HighResNow() has high resolution but it
  // is warned not to use it unless necessary.
  base::TimeTicks current_time = base::TimeTicks::Now();
  last_arrival_time_ = current_time;
  if (first_arrival_time_.is_null())
    first_arrival_time_ = current_time;

  // Need to do this after updating the last_arrival_time_ since NAT_BIND_TEST
  // and PACKET_SIZE_TEST record the SendToLastRecvDelay.
  if (test_type == NAT_BIND_TEST) {
    return received_packets >= maximum_NAT_packets_;
  }
  if (test_type == PACKET_SIZE_TEST) {
    return received_packets >= kMaximumPacketSizeTestPackets;
  }

  base::TimeDelta rtt =
      current_time - probe_request_time_ -
      base::TimeDelta::FromMicroseconds(std::max(
          static_cast<int64>(0), probe_packet.server_processing_micros()));
  base::TimeDelta min_rtt = base::TimeDelta::FromMicroseconds(1L);
  packet_rtt_[packet_index] = (rtt >= min_rtt) ? rtt : min_rtt;

  if (received_packets < maximum_sequential_packets_)
    return false;
  // All packets in the current test are received.
  inter_arrival_time_ = (last_arrival_time_ - first_arrival_time_) /
      std::max(1U, (received_packets - 1));
  if (test_type == START_PACKET_TEST) {
    test_sequence_.push_back(PACKET_SIZE_TEST);
    test_sequence_.push_back(TOKEN_REQUEST);
    // No need to add TOKEN_REQUEST here when all packets are received.
    test_sequence_.push_back(base::RandInt(0, 1) ? PACED_PACKET_TEST
                                                 : NON_PACED_PACKET_TEST);
    test_sequence_.push_back(TOKEN_REQUEST);
    test_sequence_.push_back(NAT_BIND_TEST);
    test_sequence_.push_back(TOKEN_REQUEST);
  }
  return true;
}

int NetworkStats::SendData(const std::string& output) {
  if (write_buffer_.get() || !socket_.get() ||
      write_state_ == WRITE_STATE_WRITE_PENDING) {
    return net::ERR_UNEXPECTED;
  }
  scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer(output));
  write_buffer_ = new net::DrainableIOBuffer(buffer.get(), buffer->size());

  int bytes_written = socket_->Write(
      write_buffer_.get(),
      write_buffer_->BytesRemaining(),
      base::Bind(&NetworkStats::OnWriteComplete, weak_factory_.GetWeakPtr()));
  if (bytes_written < 0) {
    if (bytes_written == net::ERR_IO_PENDING)
      write_state_ = WRITE_STATE_WRITE_PENDING;
    return bytes_written;
  }
  UpdateSendBuffer(bytes_written);
  return net::OK;
}

void NetworkStats::OnWriteComplete(int result) {
  DCHECK_NE(net::ERR_IO_PENDING, result);
  DCHECK_EQ(WRITE_STATE_WRITE_PENDING, write_state_);
  write_state_ = WRITE_STATE_IDLE;
  if (result < 0 || !socket_.get() || write_buffer_ == NULL) {
    TestPhaseComplete(WRITE_FAILED, result);
    return;
  }
  UpdateSendBuffer(result);
}

void NetworkStats::UpdateSendBuffer(int bytes_sent) {
  write_buffer_->DidConsume(bytes_sent);
  DCHECK_EQ(write_buffer_->BytesRemaining(), 0);
  DCHECK_EQ(WRITE_STATE_IDLE, write_state_);
  write_buffer_ = NULL;
}

void NetworkStats::StartReadDataTimer(uint32 seconds, uint32 test_index) {
  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&NetworkStats::OnReadDataTimeout,
                 weak_factory_.GetWeakPtr(),
                 test_index),
      base::TimeDelta::FromSeconds(seconds));
}

void NetworkStats::OnReadDataTimeout(uint32 test_index) {
  // If the current_test_index_ has changed since we set the timeout,
  // the current test has been completed, so do nothing.
  if (test_index != current_test_index_)
    return;
  // If test_type is TOKEN_REQUEST, it will do nothing but call
  // TestPhaseComplete().
  TestType test_type = test_sequence_[current_test_index_];

  uint32 received_packets = packets_received_mask_.count();
  if (received_packets >= 2) {
    inter_arrival_time_ =
        (last_arrival_time_ - first_arrival_time_) / (received_packets - 1);
  }
  // Add other tests if this is START_PACKET_TEST.
  if (test_type == START_PACKET_TEST) {
    if (received_packets >= kMinimumReceivedPacketsForPacingTest) {
      test_sequence_.push_back(TOKEN_REQUEST);
      test_sequence_.push_back(PACKET_SIZE_TEST);
      test_sequence_.push_back(TOKEN_REQUEST);
      test_sequence_.push_back(base::RandInt(0, 1) ? PACED_PACKET_TEST
                                                   : NON_PACED_PACKET_TEST);
    }
    if (received_packets >= kMinimumReceivedPacketsForNATTest) {
      test_sequence_.push_back(TOKEN_REQUEST);
      test_sequence_.push_back(NAT_BIND_TEST);
      test_sequence_.push_back(TOKEN_REQUEST);
    }
  }
  TestPhaseComplete(READ_TIMED_OUT, net::ERR_FAILED);
}

void NetworkStats::TestPhaseComplete(Status status, int result) {
  // If there is no valid token, do nothing and delete self.
  // This includes all connection error, name resolve error, etc.
  if (write_state_ == WRITE_STATE_WRITE_PENDING) {
    UMA_HISTOGRAM_BOOLEAN("NetConnectivity5.TestFailed.WritePending", true);
  } else if (token_.timestamp_micros() != 0 &&
             (status == SUCCESS || status == READ_TIMED_OUT)) {
    TestType current_test = test_sequence_[current_test_index_];
    DCHECK_LT(current_test, TEST_TYPE_MAX);
    if (current_test != TOKEN_REQUEST) {
      RecordHistograms(current_test);
    } else if (current_test_index_ > 0) {
      if (test_sequence_[current_test_index_ - 1] == NAT_BIND_TEST) {
        // We record the NATTestReceivedHistograms after the succeeding
        // TokenRequest.
        RecordNATTestReceivedHistograms(status);
      } else if (test_sequence_[current_test_index_ - 1] == PACKET_SIZE_TEST) {
        // We record the PacketSizeTestReceivedHistograms after the succeeding
        // TokenRequest.
        RecordPacketSizeTestReceivedHistograms(status);
      }
    }

    // Move to the next test.
    current_test = GetNextTest();
    if (current_test_index_ <= maximum_tests_ && current_test < TEST_TYPE_MAX) {
      DVLOG(1) << "NetworkStat: Start Probe test: " << current_test;
      base::MessageLoop::current()->PostTask(
          FROM_HERE,
          base::Bind(&NetworkStats::StartOneTest, weak_factory_.GetWeakPtr()));
      return;
    }
  }

  // All tests are done.
  DoFinishCallback(result);

  // Close the socket so that there are no more IO operations.
  if (socket_.get())
    socket_->Close();

  DVLOG(1) << "NetworkStat: schedule delete self at test index "
           << current_test_index_;
  delete this;
}

NetworkStats::TestType NetworkStats::GetNextTest() {
  ++current_test_index_;
  if (current_test_index_ >= test_sequence_.size())
    return TEST_TYPE_MAX;
  return test_sequence_[current_test_index_];
}

void NetworkStats::DoFinishCallback(int result) {
  if (!finished_callback_.is_null()) {
    net::CompletionCallback callback = finished_callback_;
    finished_callback_.Reset();
    callback.Run(result);
  }
}

void NetworkStats::RecordHistograms(TestType test_type) {
  switch (test_type) {
    case START_PACKET_TEST:
    case NON_PACED_PACKET_TEST:
    case PACED_PACKET_TEST: {
      RecordInterArrivalHistograms(test_type);
      RecordPacketLossSeriesHistograms(test_type);
      RecordPacketsReceivedHistograms(test_type);
      // Only record RTT for these packet indices.
      uint32 rtt_indices[] = {0, 1, 2, 9, 19};
      for (uint32 i = 0; i < arraysize(rtt_indices); ++i) {
        if (rtt_indices[i] < packet_rtt_.size())
          RecordRTTHistograms(test_type, rtt_indices[i]);
      }
      RecordSendToLastRecvDelayHistograms(test_type);
      return;
    }
    case NAT_BIND_TEST:
      RecordSendToLastRecvDelayHistograms(test_type);
      return;
    case PACKET_SIZE_TEST:
      // No need to record RTT for PacketSizeTest.
      return;
    default:
      DVLOG(1) << "Unexpected test type " << test_type
               << " in RecordHistograms.";
  }
}

void NetworkStats::RecordInterArrivalHistograms(TestType test_type) {
  DCHECK_NE(test_type, PACKET_SIZE_TEST);
  std::string histogram_name =
      base::StringPrintf("NetConnectivity5.%s.Sent%d.PacketDelay.%d.%dB",
                         kTestName[test_type],
                         maximum_sequential_packets_,
                         histogram_port_,
                         probe_packet_bytes_);
  // Record the time normalized to 20 packet inter-arrivals.
  DynamicHistogramTimes(histogram_name, inter_arrival_time_ * 20);
}

void NetworkStats::RecordPacketsReceivedHistograms(TestType test_type) {
  DCHECK_NE(test_type, PACKET_SIZE_TEST);
  const char* test_name = kTestName[test_type];
  std::string histogram_prefix = base::StringPrintf(
      "NetConnectivity5.%s.Sent%d.", test_name, maximum_sequential_packets_);
  std::string histogram_suffix =
      base::StringPrintf(".%d.%dB", histogram_port_, probe_packet_bytes_);
  std::string name = histogram_prefix + "GotAPacket" + histogram_suffix;
  base::HistogramBase* histogram_pointer = base::BooleanHistogram::FactoryGet(
      name, base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram_pointer->Add(packets_received_mask_.any());

  DynamicHistogramEnumeration(
      histogram_prefix + "PacketsRecv" + histogram_suffix,
      packets_received_mask_.count(),
      maximum_sequential_packets_ + 1);

  if (!packets_received_mask_.any())
    return;

  base::HistogramBase* received_nth_packet_histogram =
      base::Histogram::FactoryGet(
          histogram_prefix + "RecvNthPacket" + histogram_suffix,
          1,
          maximum_sequential_packets_ + 1,
          maximum_sequential_packets_ + 2,
          base::HistogramBase::kUmaTargetedHistogramFlag);

  int count = 0;
  for (size_t j = 0; j < maximum_sequential_packets_; ++j) {
    int packet_number = j + 1;
    if (packets_received_mask_.test(j)) {
      received_nth_packet_histogram->Add(packet_number);
      ++count;
    }
    std::string histogram_name =
        base::StringPrintf("%sNumRecvFromFirst%02dPackets%s",
                           histogram_prefix.c_str(),
                           packet_number,
                           histogram_suffix.c_str());
    DynamicHistogramEnumeration(histogram_name, count, packet_number + 1);
  }
}

void NetworkStats::RecordNATTestReceivedHistograms(Status status) {
  const char* test_name = kTestName[NAT_BIND_TEST];
  bool test_result = status == SUCCESS;
  std::string middle_name = test_result ? "Connectivity.Success"
                                        : "Connectivity.Failure";
  // Record whether the HelloRequest got reply successfully.
  std::string histogram_name =
      base::StringPrintf("NetConnectivity5.%s.Sent%d.%s.%d.%dB",
                         test_name,
                         maximum_NAT_packets_,
                         middle_name.c_str(),
                         histogram_port_,
                         probe_packet_bytes_);
  uint32 bucket_count = std::min(maximum_NAT_idle_seconds_ + 2, 50U);
  DynamicHistogramCounts(histogram_name,
                         pacing_interval_.InSeconds(),
                         1,
                         maximum_NAT_idle_seconds_ + 1,
                         bucket_count);

  // Record the NAT bind result only if the HelloRequest successfully got the
  // token and the first NAT test packet is received.
  if (!test_result || !packets_received_mask_.test(0))
    return;

  middle_name = packets_received_mask_.test(1) ? "Bind.Success"
                                               : "Bind.Failure";
  histogram_name = base::StringPrintf("NetConnectivity5.%s.Sent%d.%s.%d.%dB",
                                      test_name,
                                      maximum_NAT_packets_,
                                      middle_name.c_str(),
                                      histogram_port_,
                                      probe_packet_bytes_);
  DynamicHistogramCounts(histogram_name,
                         pacing_interval_.InSeconds(),
                         1,
                         maximum_NAT_idle_seconds_ + 1,
                         bucket_count);
}

void NetworkStats::RecordPacketSizeTestReceivedHistograms(Status status) {
  const char* test_name = kTestName[PACKET_SIZE_TEST];
  bool test_result = (status == SUCCESS && packets_received_mask_.test(0));
  std::string middle_name = test_result ? "Connectivity.Success"
                                        : "Connectivity.Failure";
  // Record whether the HelloRequest got reply successfully.
  std::string histogram_name =
      base::StringPrintf("NetConnectivity5.%s.%s.%d",
                         test_name,
                         middle_name.c_str(),
                         histogram_port_);
  base::HistogramBase* histogram_pointer = base::LinearHistogram::FactoryGet(
      histogram_name, kProbePacketBytes[kPacketSizeChoices - 1],
      ProbeMessage::kMaxProbePacketBytes, 60,
      base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram_pointer->Add(bytes_for_packet_size_test_);
}

void NetworkStats::RecordPacketLossSeriesHistograms(TestType test_type) {
  DCHECK_NE(test_type, PACKET_SIZE_TEST);
  const char* test_name = kTestName[test_type];
  // Build "NetConnectivity5.<TestName>.First6.SeriesRecv.<port>.<probe_size>"
  // histogram name. Total 3(tests) x 12 histograms.
  std::string series_acked_histogram_name =
      base::StringPrintf("NetConnectivity5.%s.First6.SeriesRecv.%d.%dB",
                         test_name,
                         histogram_port_,
                         probe_packet_bytes_);
  uint32 histogram_boundary = 1 << kCorrelatedLossPacketCount;
  uint32 correlated_packet_mask =
      (histogram_boundary - 1) & packets_received_mask_.to_ulong();
  DynamicHistogramEnumeration(
      series_acked_histogram_name, correlated_packet_mask, histogram_boundary);

  // If we are running without a proxy, we'll generate an extra histogram with
  // the ".NoProxy" suffix.
  if (!has_proxy_server_) {
    series_acked_histogram_name.append(".NoProxy");
    DynamicHistogramEnumeration(series_acked_histogram_name,
                                correlated_packet_mask,
                                histogram_boundary);
  }
}

void NetworkStats::RecordRTTHistograms(TestType test_type, uint32 index) {
  DCHECK_NE(test_type, PACKET_SIZE_TEST);
  DCHECK_LT(index, packet_rtt_.size());

  if (!packets_received_mask_.test(index))
    return;  // Probe packet never received.

  std::string rtt_histogram_name = base::StringPrintf(
      "NetConnectivity5.%s.Sent%d.Success.RTT.Packet%02d.%d.%dB",
      kTestName[test_type],
      maximum_sequential_packets_,
      index + 1,
      histogram_port_,
      probe_packet_bytes_);
  DynamicHistogramTimes(rtt_histogram_name, packet_rtt_[index]);
}

void NetworkStats::RecordSendToLastRecvDelayHistograms(TestType test_type) {
  DCHECK_NE(test_type, PACKET_SIZE_TEST);
  if (packets_received_mask_.count() < 2)
    return;  // Too few packets are received.
  uint32 packets_sent = test_type == NAT_BIND_TEST
      ? maximum_NAT_packets_ : maximum_sequential_packets_;
  std::string histogram_name = base::StringPrintf(
      "NetConnectivity5.%s.Sent%d.SendToLastRecvDelay.%d.%dB",
      kTestName[test_type],
      packets_sent,
      histogram_port_,
      probe_packet_bytes_);
  base::TimeDelta send_to_last_recv_time =
      std::max(last_arrival_time_ - probe_request_time_ -
                   pacing_interval_ * (packets_sent - 1),
               base::TimeDelta::FromMilliseconds(0));
  DynamicHistogramTimes(histogram_name, send_to_last_recv_time);
}

// ProxyDetector methods and members.
ProxyDetector::ProxyDetector(net::ProxyService* proxy_service,
                             const net::HostPortPair& server_address,
                             OnResolvedCallback callback)
    : proxy_service_(proxy_service),
      server_address_(server_address),
      callback_(callback),
      has_pending_proxy_resolution_(false) {}

ProxyDetector::~ProxyDetector() {
  CHECK(!has_pending_proxy_resolution_);
}

void ProxyDetector::StartResolveProxy() {
  std::string url =
      base::StringPrintf("https://%s", server_address_.ToString().c_str());
  GURL gurl(url);

  has_pending_proxy_resolution_ = true;
  DCHECK(proxy_service_);
  int rv = proxy_service_->ResolveProxy(
      gurl,
      &proxy_info_,
      base::Bind(&ProxyDetector::OnResolveProxyComplete,
                 base::Unretained(this)),
      NULL,
      net::BoundNetLog());
  if (rv != net::ERR_IO_PENDING)
    OnResolveProxyComplete(rv);
}

void ProxyDetector::OnResolveProxyComplete(int result) {
  has_pending_proxy_resolution_ = false;
  bool has_proxy_server =
      (result == net::OK && proxy_info_.proxy_server().is_valid() &&
       !proxy_info_.proxy_server().is_direct());

  OnResolvedCallback callback = callback_;
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE, base::Bind(callback, has_proxy_server));

  // TODO(rtenneti): Will we leak if ProxyResolve is cancelled (or proxy
  // resolution never completes).
  delete this;
}

void CollectNetworkStats(const std::string& network_stats_server,
                         IOThread* io_thread) {
  if (network_stats_server.empty())
    return;

  // If we are not on IO Thread, then post a task to call CollectNetworkStats on
  // IO Thread.
  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    BrowserThread::PostTask(
        BrowserThread::IO,
        FROM_HERE,
        base::Bind(&CollectNetworkStats, network_stats_server, io_thread));
    return;
  }

  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  if (net::NetworkChangeNotifier::IsOffline()) {
    return;
  }

  CR_DEFINE_STATIC_LOCAL(scoped_refptr<base::FieldTrial>, trial, ());
  static bool collect_stats = false;

  if (!trial.get()) {
    // Set up a field trial to collect network stats for UDP.
    const base::FieldTrial::Probability kDivisor = 1000;

    // Enable the connectivity testing for 0.5% of the users in stable channel.
    base::FieldTrial::Probability probability_per_group = kDivisor / 200;

    chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
    if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
      // Enable the connectivity testing for 50% of the users in canary channel.
      probability_per_group = kDivisor / 2;
    } else if (channel == chrome::VersionInfo::CHANNEL_DEV) {
      // Enable the connectivity testing for 10% of the users in dev channel.
      probability_per_group = kDivisor / 10;
    } else if (channel == chrome::VersionInfo::CHANNEL_BETA) {
      // Enable the connectivity testing for 1% of the users in beta channel.
      probability_per_group = kDivisor / 100;
    }

    // TODO(rtenneti): Enable the experiment after fixing
    // issue http://crbug.com/273917.
    probability_per_group = 0;

    // After July 31, 2014 builds, it will always be in default group
    // (disable_network_stats).
    trial = base::FieldTrialList::FactoryGetFieldTrial(
        "NetworkConnectivity", kDivisor, "disable_network_stats",
        2014, 7, 31, base::FieldTrial::SESSION_RANDOMIZED, NULL);

    // Add option to collect_stats for NetworkConnectivity.
    int collect_stats_group =
        trial->AppendGroup("collect_stats", probability_per_group);
    if (trial->group() == collect_stats_group)
      collect_stats = true;
  }

  if (!collect_stats)
    return;

  // Run test kMaxNumberOfTests times.
  const size_t kMaxNumberOfTests = INT_MAX;
  static size_t number_of_tests_done = 0;
  if (number_of_tests_done > kMaxNumberOfTests)
    return;
  ++number_of_tests_done;

  net::HostResolver* host_resolver = io_thread->globals()->host_resolver.get();
  DCHECK(host_resolver);

  uint32 port_index = base::RandInt(0, arraysize(kPorts) - 1);
  uint16 histogram_port = kPorts[port_index];
  net::HostPortPair server_address(network_stats_server, histogram_port);

  net::ProxyService* proxy_service =
      io_thread->globals()->system_proxy_service.get();
  DCHECK(proxy_service);

  ProxyDetector::OnResolvedCallback callback = base::Bind(
      &StartNetworkStatsTest, host_resolver, server_address, histogram_port);

  ProxyDetector* proxy_client =
      new ProxyDetector(proxy_service, server_address, callback);
  proxy_client->StartResolveProxy();
}

void StartNetworkStatsTest(net::HostResolver* host_resolver,
                           const net::HostPortPair& server_address,
                           uint16 histogram_port,
                           bool has_proxy_server) {
  int probe_choice = base::RandInt(0, kPacketSizeChoices - 1);

  DCHECK_LE(ProbeMessage::kMaxProbePacketBytes, kMaxMessageSize);
  // Pick a packet size between 1200 and kMaxProbePacketBytes bytes.
  uint32 bytes_for_packet_size_test =
      base::RandInt(kProbePacketBytes[kPacketSizeChoices - 1],
                    ProbeMessage::kMaxProbePacketBytes);

  // |udp_stats_client| is owned and deleted in the class NetworkStats.
  NetworkStats* udp_stats_client =
      new NetworkStats(net::ClientSocketFactory::GetDefaultFactory());
  udp_stats_client->Start(host_resolver,
                          server_address,
                          histogram_port,
                          has_proxy_server,
                          kProbePacketBytes[probe_choice],
                          bytes_for_packet_size_test,
                          net::CompletionCallback());
}

}  // namespace chrome_browser_net