blob: b70771b8304b09cf72117489def3faba65a65831 [file] [log] [blame]
// Copyright (c) 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.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/rand_util.h"
#include "base/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time.h"
#include "base/tuple.h"
#include "chrome/common/chrome_version_info.h"
#include "content/public/browser/browser_thread.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/network_change_notifier.h"
#include "net/base/test_completion_callback.h"
#include "net/proxy/proxy_service.h"
#include "net/udp/udp_client_socket.h"
#include "net/udp/udp_server_socket.h"
using content::BrowserThread;
namespace chrome_browser_net {
// This specifies the number of bytes to be sent to the UDP echo servers as part
// of small packet size test.
static const uint32 kSmallTestBytesToSend = 100;
// This specifies the number of bytes to be sent to the UDP echo servers as part
// of medium packet size test.
static const uint32 kMediumTestBytesToSend = 500;
// This specifies the number of bytes to be sent to the UDP echo servers as part
// of large packet size test.
static const uint32 kLargeTestBytesToSend = 1200;
// This specifies starting position of the <version> and length of the
// <version> in "echo request" and "echo response".
static const uint32 kVersionNumber = 1;
static const uint32 kVersionStart = 0;
static const uint32 kVersionLength = 2;
static const uint32 kVersionEnd = kVersionStart + kVersionLength;
// This specifies the starting position of the <checksum> and length of the
// <checksum> in "echo request" and "echo response". Maximum value for the
// <checksum> is less than (2 ** 31 - 1).
static const uint32 kChecksumStart = kVersionEnd;
static const uint32 kChecksumLength = 10;
static const uint32 kChecksumEnd = kChecksumStart + kChecksumLength;
// This specifies the starting position of the <payload_size> and length of the
// <payload_size> in "echo request" and "echo response". Maximum number of bytes
// that can be sent in the <payload> is 9,999,999.
static const uint32 kPayloadSizeStart = kChecksumEnd;
static const uint32 kPayloadSizeLength = 7;
static const uint32 kPayloadSizeEnd = kPayloadSizeStart + kPayloadSizeLength;
// This specifies the starting position of the <key> and length of the <key> in
// "echo response".
static const uint32 kKeyStart = kPayloadSizeEnd;
static const uint32 kKeyLength = 6;
static const uint32 kKeyEnd = kKeyStart + kKeyLength;
static const int32 kKeyMinValue = 0;
static const int32 kKeyMaxValue = 999999;
// This specifies the starting position of the <payload> in "echo request".
static const uint32 kPayloadStart = kPayloadSizeEnd;
// This specifies the length of the packet_number in the payload.
static const uint32 kPacketNumberLength = 10;
// This specifies the starting position of the <encoded_payload> in
// "echo response".
static const uint32 kEncodedPayloadStart = kKeyEnd;
// HistogramPortSelector and kPorts should be kept in sync. Port 999999 is
// used by the unit tests.
static const int32 kPorts[] = {6121, 999999};
// Number of packets that are recorded in a packet-correlation histogram, which
// shows exactly what sequence of packets were responded to. We use this to
// deduce specific packet loss correlation.
static const uint32 kCorrelatedLossPacketCount = 6;
// Maximum number of packets that can be sent to the server.
static const uint32 kMaximumSequentialPackets = 21;
// This specifies the maximum message (payload) size.
static const uint32 kMaxMessage = kMaximumSequentialPackets * 2048;
// NetworkStats methods and members.
NetworkStats::NetworkStats()
: read_buffer_(NULL),
write_buffer_(NULL),
load_size_(0),
bytes_to_read_(0),
bytes_to_send_(0),
has_proxy_server_(false),
packets_to_send_(0),
packets_sent_(0),
packets_received_(0),
packets_received_mask_(0),
packet_number_(0),
base_packet_number_(0),
sending_complete_(false),
current_test_(START_PACKET_TEST),
next_test_(TEST_TYPE_MAX),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
}
NetworkStats::~NetworkStats() {
socket_.reset();
}
bool NetworkStats::Start(net::HostResolver* host_resolver,
const net::HostPortPair& server_host_port_pair,
HistogramPortSelector histogram_port,
bool has_proxy_server,
uint32 bytes_to_send,
uint32 packets_to_send,
const net::CompletionCallback& finished_callback) {
DCHECK(host_resolver);
DCHECK(bytes_to_send); // We should have data to send.
DCHECK_LE(packets_to_send, kMaximumSequentialPackets);
Initialize(bytes_to_send,
histogram_port,
has_proxy_server,
packets_to_send,
finished_callback);
net::HostResolver::RequestInfo request(server_host_port_pair);
int rv = host_resolver->Resolve(
request, &addresses_,
base::Bind(&NetworkStats::OnResolveComplete,
base::Unretained(this)),
NULL, net::BoundNetLog());
if (rv == net::ERR_IO_PENDING)
return true;
return DoConnect(rv);
}
void NetworkStats::RestartPacketTest() {
ResetData();
current_test_ = next_test_;
next_test_ = TEST_TYPE_MAX;
if (!bytes_to_read_) {
read_buffer_ = NULL;
ReadData();
}
SendPacket();
}
void NetworkStats::Initialize(
uint32 bytes_to_send,
HistogramPortSelector histogram_port,
bool has_proxy_server,
uint32 packets_to_send,
const net::CompletionCallback& finished_callback) {
DCHECK(bytes_to_send); // We should have data to send.
DCHECK(packets_to_send); // We should send at least 1 packet.
DCHECK_LE(bytes_to_send, kLargeTestBytesToSend);
DCHECK_LE(packets_to_send, 8 * sizeof(packets_received_mask_));
load_size_ = bytes_to_send;
packets_to_send_ = packets_to_send;
histogram_port_ = histogram_port;
has_proxy_server_ = has_proxy_server;
finished_callback_ = finished_callback;
ResetData();
packet_number_ = base::RandInt(1 << 28, INT_MAX);
}
void NetworkStats::ResetData() {
write_buffer_ = NULL;
bytes_to_send_ = 0;
packet_status_.clear();
packet_status_.resize(packets_to_send_);
packets_sent_ = 0;
packets_received_ = 0;
packets_received_mask_ = 0;
sending_complete_ = false;
}
void NetworkStats::OnResolveComplete(int result) {
DoConnect(result);
}
bool NetworkStats::DoConnect(int result) {
if (result != net::OK) {
Finish(RESOLVE_FAILED, result);
return false;
}
net::UDPClientSocket* udp_socket =
new net::UDPClientSocket(net::DatagramSocket::DEFAULT_BIND,
net::RandIntCallback(),
NULL,
net::NetLog::Source());
if (!udp_socket) {
Finish(SOCKET_CREATE_FAILED, net::ERR_INVALID_ARGUMENT);
return false;
}
set_socket(udp_socket);
if (addresses().empty()) {
Finish(RESOLVE_FAILED, net::ERR_INVALID_ARGUMENT);
return false;
}
const net::IPEndPoint& endpoint = addresses().front();
int rv = udp_socket->Connect(endpoint);
if (rv < 0) {
Finish(CONNECT_FAILED, rv);
return false;
}
const int kSocketBufferSize = 2 * packets_to_send_ * 2048;
udp_socket->SetSendBufferSize(kSocketBufferSize);
udp_socket->SetReceiveBufferSize(kSocketBufferSize);
return ConnectComplete(rv);
}
bool NetworkStats::ConnectComplete(int result) {
if (result < 0) {
Finish(CONNECT_FAILED, result);
return false;
}
ReadData();
SendPacket();
return true;
}
void NetworkStats::SendPacket() {
while (true) {
if (bytes_to_send_ == 0u) {
if (packets_sent_ >= packets_to_send_) {
// Timeout if we don't get response back from echo servers in 30 secs.
sending_complete_ = true;
const int kReadDataTimeoutMs = 30000;
StartReadDataTimer(kReadDataTimeoutMs);
break;
}
++packet_number_;
if (packets_sent_ == 0)
base_packet_number_ = packet_number_;
bytes_to_send_ = SendingPacketSize();
SendNextPacketAfterDelay();
break;
}
int rv = SendData();
if (rv < 0) {
if (rv != net::ERR_IO_PENDING)
Finish(WRITE_FAILED, rv);
break;
}
DCHECK_EQ(bytes_to_send_, 0u);
};
}
void NetworkStats::SendNextPacketAfterDelay() {
if (current_test_ == PACED_PACKET_TEST) {
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&NetworkStats::SendPacket, weak_factory_.GetWeakPtr()),
average_time_);
return;
}
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&NetworkStats::SendPacket, weak_factory_.GetWeakPtr()));
}
bool NetworkStats::ReadComplete(int result) {
DCHECK(socket_.get());
DCHECK_NE(net::ERR_IO_PENDING, result);
if (result < 0) {
Finish(READ_FAILED, result);
return true;
}
if (result > 0) {
std::string encoded_message;
encoded_message.append(read_buffer_->data(), result);
if (VerifyBytes(encoded_message) == SUCCESS) {
base::TimeTicks now = base::TimeTicks::Now();
if (packets_received_ == 0)
packet_1st_byte_read_time_ = now;
packet_last_byte_read_time_ = now;
DCHECK_GE(bytes_to_read_, static_cast<uint32>(result));
if (bytes_to_read_ >= static_cast<uint32>(result))
bytes_to_read_ -= result;
++packets_received_;
}
}
read_buffer_ = NULL;
// No more data to read.
if (!bytes_to_read_ || result == 0) {
if (!sending_complete_)
return false;
Status status = VerifyPackets();
if (status == SUCCESS)
Finish(status, net::OK);
else
Finish(status, net::ERR_INVALID_RESPONSE);
return true;
}
return false;
}
void NetworkStats::OnReadComplete(int result) {
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.
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&NetworkStats::ReadData, weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(1));
}
}
void NetworkStats::OnWriteComplete(int result) {
DCHECK(socket_.get());
DCHECK_NE(net::ERR_IO_PENDING, result);
if (result < 0) {
Finish(WRITE_FAILED, result);
return;
}
DidSendData(result);
if (bytes_to_send_) {
int rv = SendData();
if (rv < 0) {
if (rv != net::ERR_IO_PENDING)
Finish(WRITE_FAILED, rv);
return;
}
DCHECK_EQ(rv, net::OK);
DCHECK_EQ(bytes_to_send_, 0u);
}
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&NetworkStats::SendPacket, weak_factory_.GetWeakPtr()));
}
void NetworkStats::ReadData() {
int rv;
do {
if (!socket_.get())
break;
DCHECK(!read_buffer_.get());
// We release the read_buffer_ in the destructor if there is an error.
read_buffer_ = new net::IOBuffer(kMaxMessage);
rv = socket_->Read(read_buffer_, kMaxMessage,
base::Bind(&NetworkStats::OnReadComplete,
base::Unretained(this)));
if (rv == net::ERR_IO_PENDING)
break;
// If we have read all the data then return.
if (ReadComplete(rv))
break;
} while (rv > 0);
}
int NetworkStats::SendData() {
DCHECK(bytes_to_send_); // We should have data to send.
do {
if (!write_buffer_.get()) {
// Send a new packet.
scoped_refptr<net::IOBufferWithSize> buffer(
new net::IOBufferWithSize(bytes_to_send_));
GetEchoRequest(buffer);
write_buffer_ = new net::DrainableIOBuffer(buffer, bytes_to_send_);
// As soon as we write, a read could happen. Thus update all the book
// keeping data.
bytes_to_read_ += ReceivingPacketSize();
++packets_sent_;
if (packets_sent_ >= packets_to_send_)
sending_complete_ = true;
uint32 packet_index = packet_number_ - base_packet_number_;
DCHECK_GE(packet_index, 0u);
DCHECK_LT(packet_index, packet_status_.size());
packet_status_[packet_index].start_time_ = base::TimeTicks::Now();
}
if (!socket_.get())
return net::ERR_UNEXPECTED;
int rv = socket_->Write(write_buffer_,
write_buffer_->BytesRemaining(),
base::Bind(&NetworkStats::OnWriteComplete,
base::Unretained(this)));
if (rv < 0)
return rv;
DidSendData(rv);
} while (bytes_to_send_);
return net::OK;
}
uint32 NetworkStats::SendingPacketSize() const {
return kVersionLength + kChecksumLength + kPayloadSizeLength + load_size_;
}
uint32 NetworkStats::ReceivingPacketSize() const {
return kVersionLength + kChecksumLength + kPayloadSizeLength + kKeyLength +
load_size_;
}
void NetworkStats::DidSendData(int bytes_sent) {
write_buffer_->DidConsume(bytes_sent);
if (!write_buffer_->BytesRemaining())
write_buffer_ = NULL;
bytes_to_send_ -= bytes_sent;
}
void NetworkStats::StartReadDataTimer(int milliseconds) {
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&NetworkStats::OnReadDataTimeout,
weak_factory_.GetWeakPtr(),
base_packet_number_),
base::TimeDelta::FromMilliseconds(milliseconds));
}
void NetworkStats::OnReadDataTimeout(uint32 test_base_packet_number) {
if (test_base_packet_number != base_packet_number_)
return;
Status status = VerifyPackets();
if (status == SUCCESS)
Finish(status, net::OK);
else
Finish(READ_TIMED_OUT, net::ERR_INVALID_ARGUMENT);
}
uint32 NetworkStats::GetChecksum(const char* message, uint32 message_length) {
// Calculate the <checksum> of the <message>.
uint32 sum = 0;
for (uint32 i = 0; i < message_length; ++i)
sum += message[i];
return sum;
}
void NetworkStats::Crypt(const char* key,
uint32 key_length,
const char* data,
uint32 data_length,
char* encoded_data) {
// Decrypt the data by looping through the |data| and XOR each byte with the
// |key| to get the decoded byte. Append the decoded byte to the
// |encoded_data|.
for (uint32 data_index = 0, key_index = 0;
data_index < data_length;
++data_index) {
char data_byte = data[data_index];
char key_byte = key[key_index];
char encoded_byte = data_byte ^ key_byte;
encoded_data[data_index] = encoded_byte;
key_index = (key_index + 1) % key_length;
}
}
void NetworkStats::GetEchoRequest(net::IOBufferWithSize* io_buffer) {
char* buffer = io_buffer->data();
uint32 buffer_size = static_cast<uint32>(io_buffer->size());
// Copy the <version> into the io_buffer starting from the kVersionStart
// position.
std::string version = base::StringPrintf("%02d", kVersionNumber);
DCHECK(kVersionLength == version.length());
DCHECK_GE(buffer_size, kVersionStart + kVersionLength);
memcpy(buffer + kVersionStart, version.c_str(), kVersionLength);
// Copy the packet_number into the payload.
std::string packet_number = base::StringPrintf("%010d", packet_number_);
DCHECK(kPacketNumberLength == packet_number.length());
DCHECK_GE(buffer_size, kPayloadStart + kPacketNumberLength);
memcpy(buffer + kPayloadStart, packet_number.c_str(), kPacketNumberLength);
// Get the <payload> from the |stream_| and copy it into io_buffer after
// packet_number.
stream_.Reset();
DCHECK_GE(buffer_size, kPayloadStart + load_size_);
stream_.GetBytes(buffer + kPayloadStart + kPacketNumberLength,
load_size_ - kPacketNumberLength);
// Calculate the <checksum> of the <payload>.
uint32 sum = GetChecksum(buffer + kPayloadStart, load_size_);
// Copy the <checksum> into the io_buffer starting from the kChecksumStart
// position.
std::string checksum = base::StringPrintf("%010d", sum);
DCHECK(kChecksumLength == checksum.length());
DCHECK_GE(buffer_size, kChecksumStart + kChecksumLength);
memcpy(buffer + kChecksumStart, checksum.c_str(), kChecksumLength);
// Copy the size of the <payload> into the io_buffer starting from the
// kPayloadSizeStart position.
std::string payload_size = base::StringPrintf("%07d", load_size_);
DCHECK(kPayloadSizeLength == payload_size.length());
DCHECK_GE(buffer_size, kPayloadSizeStart + kPayloadSizeLength);
memcpy(buffer + kPayloadSizeStart, payload_size.c_str(), kPayloadSizeLength);
}
NetworkStats::Status NetworkStats::VerifyPackets() {
Status status = SUCCESS;
uint32 successful_packets = 0;
for (uint32 i = 0; i < packet_status_.size(); i++) {
if (packets_received_mask_ & (1 << i))
++successful_packets;
}
if (packets_received_ > packets_to_send_)
status = TOO_MANY_PACKETS;
if (packets_to_send_ > successful_packets)
status = SOME_PACKETS_NOT_VERIFIED;
if (packets_to_send_ == kMaximumSequentialPackets &&
successful_packets > 1) {
base::TimeDelta total_time;
if (packet_last_byte_read_time_ > packet_1st_byte_read_time_) {
total_time =
packet_last_byte_read_time_ - packet_1st_byte_read_time_;
}
average_time_ = total_time / (successful_packets - 1);
std::string histogram_name = base::StringPrintf(
"NetConnectivity3.%s.Sent%02d.%d.%dB.PacketDelay",
TestName(),
kMaximumSequentialPackets,
kPorts[histogram_port_],
load_size_);
base::Histogram* histogram = base::Histogram::FactoryTimeGet(
histogram_name, base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromSeconds(30), 50,
base::Histogram::kUmaTargetedHistogramFlag);
histogram->AddTime(total_time);
if (current_test_ == START_PACKET_TEST) {
int experiment_to_run = base::RandInt(1, 2);
if (experiment_to_run == 1)
next_test_ = NON_PACED_PACKET_TEST;
else
next_test_ = PACED_PACKET_TEST;
}
}
return status;
}
NetworkStats::Status NetworkStats::VerifyBytes(const std::string& response) {
// If the "echo response" doesn't have enough bytes, then return false.
if (response.length() <= kVersionStart)
return ZERO_LENGTH_ERROR;
if (response.length() <= kChecksumStart)
return NO_CHECKSUM_ERROR;
if (response.length() <= kPayloadSizeStart)
return NO_PAYLOAD_SIZE_ERROR;
if (response.length() <= kKeyStart)
return NO_KEY_ERROR;
if (response.length() <= kEncodedPayloadStart)
return NO_PAYLOAD_ERROR;
// Extract the |key| from the "echo response".
std::string key_string = response.substr(kKeyStart, kKeyLength);
const char* key = key_string.c_str();
int key_value = atoi(key);
if (key_value < kKeyMinValue || key_value > kKeyMaxValue)
return INVALID_KEY_ERROR;
std::string encoded_payload = response.substr(kEncodedPayloadStart);
const char* encoded_data = encoded_payload.c_str();
uint32 message_length = encoded_payload.length();
message_length = std::min(message_length, kMaxMessage);
if (message_length < load_size_)
return TOO_SHORT_PAYLOAD;
if (message_length > load_size_)
return TOO_LONG_PAYLOAD;
// Decode/decrypt the |encoded_data| into |decoded_data|.
char decoded_data[kMaxMessage + 1];
DCHECK_LE(message_length, kMaxMessage);
memset(decoded_data, 0, kMaxMessage + 1);
Crypt(key, kKeyLength, encoded_data, message_length, decoded_data);
// Calculate the <checksum> of the <decoded_data>.
uint32 sum = GetChecksum(decoded_data, message_length);
// Extract the |checksum| from the "echo response".
std::string checksum_string =
response.substr(kChecksumStart, kChecksumLength);
const char* checksum = checksum_string.c_str();
uint32 checksum_value = atoi(checksum);
if (checksum_value != sum)
return INVALID_CHECKSUM;
// Verify the packet_number.
char packet_number_data[kPacketNumberLength + 1];
memset(packet_number_data, 0, kPacketNumberLength + 1);
memcpy(packet_number_data, decoded_data, kPacketNumberLength);
uint32 packet_number_received = atoi(packet_number_data);
if (packet_number_received < base_packet_number_)
return PREVIOUS_PACKET_NUMBER;
uint32 packet_index = packet_number_received - base_packet_number_;
if (packet_index >= packets_to_send_)
return INVALID_PACKET_NUMBER;
stream_.Reset();
if (!stream_.VerifyBytes(&decoded_data[kPacketNumberLength],
message_length - kPacketNumberLength)) {
return PATTERN_CHANGED;
}
if (packets_received_mask_ & (1 << packet_index))
return DUPLICATE_PACKET;
packets_received_mask_ |= 1 << packet_index;
DCHECK_GE(packet_index, 0u);
DCHECK_LT(packet_index, packet_status_.size());
packet_status_[packet_index].end_time_ = base::TimeTicks::Now();
return SUCCESS;
}
void NetworkStats::Finish(Status status, int result) {
// Set the base_packet_number_ for the start of next test. Changing the
// |base_packet_number_| indicates to OnReadDataTimeout that the Finish has
// already been called for the test and that it doesn't need to call Finish
// again.
base_packet_number_ = packet_number_ + 1;
RecordHistograms(PROTOCOL_UDP, status, result);
if (next_test() == NON_PACED_PACKET_TEST ||
next_test() == PACED_PACKET_TEST) {
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&NetworkStats::RestartPacketTest,
weak_factory_.GetWeakPtr()));
return;
}
DoFinishCallback(result);
// Close the socket so that there are no more IO operations.
net::UDPClientSocket* udp_socket =
static_cast<net::UDPClientSocket*>(socket());
if (udp_socket)
udp_socket->Close();
delete this;
}
void NetworkStats::DoFinishCallback(int result) {
if (!finished_callback_.is_null()) {
net::CompletionCallback callback = finished_callback_;
finished_callback_.Reset();
callback.Run(result);
}
}
void NetworkStats::RecordHistograms(const ProtocolValue& protocol,
const Status& status,
int result) {
if (packets_to_send_ != kMaximumSequentialPackets)
return;
std::string load_size_string = base::StringPrintf("%dB", load_size_);
RecordPacketLossSeriesHistograms(protocol, load_size_string, status, result);
for (uint32 i = 0; i < 3; i++)
RecordRTTHistograms(protocol, load_size_string, i);
RecordRTTHistograms(protocol, load_size_string, 9);
RecordRTTHistograms(protocol, load_size_string, 19);
RecordAcksReceivedHistograms(load_size_string);
}
void NetworkStats::RecordAcksReceivedHistograms(
const std::string& load_size_string) {
DCHECK_EQ(packets_to_send_, kMaximumSequentialPackets);
const char* test_name = TestName();
bool received_atleast_one_packet = packets_received_mask_ > 0;
std::string histogram_name = base::StringPrintf(
"NetConnectivity3.%s.Sent%02d.GotAnAck.%d.%s",
test_name,
kMaximumSequentialPackets,
kPorts[histogram_port_],
load_size_string.c_str());
base::Histogram* got_an_ack_histogram = base::BooleanHistogram::FactoryGet(
histogram_name, base::Histogram::kUmaTargetedHistogramFlag);
got_an_ack_histogram->AddBoolean(received_atleast_one_packet);
histogram_name = base::StringPrintf(
"NetConnectivity3.%s.Sent%02d.PacketsSent.%d.%s",
test_name,
kMaximumSequentialPackets,
kPorts[histogram_port_],
load_size_string.c_str());
base::Histogram* packets_sent_histogram =
base::Histogram::FactoryGet(
histogram_name,
1, kMaximumSequentialPackets, kMaximumSequentialPackets + 1,
base::Histogram::kUmaTargetedHistogramFlag);
packets_sent_histogram->Add(packets_sent_);
if (!received_atleast_one_packet || packets_sent_ != packets_to_send_)
return;
histogram_name = base::StringPrintf(
"NetConnectivity3.%s.Sent%02d.AckReceivedForNthPacket.%02d.%s",
test_name,
kMaximumSequentialPackets,
kPorts[histogram_port_],
load_size_string.c_str());
base::Histogram* ack_received_for_nth_packet_histogram =
base::Histogram::FactoryGet(
histogram_name,
1, kMaximumSequentialPackets + 1, kMaximumSequentialPackets + 2,
base::Histogram::kUmaTargetedHistogramFlag);
int count = 0;
for (size_t j = 0; j < packets_to_send_; j++) {
int packet_number = j + 1;
if (packets_received_mask_ & (1 << j)) {
ack_received_for_nth_packet_histogram->Add(packet_number);
count++;
}
if (packet_number < 2)
continue;
histogram_name = base::StringPrintf(
"NetConnectivity3.%s.Sent%02d.AcksReceivedFromFirst%02dPackets.%d.%s",
test_name,
kMaximumSequentialPackets,
packet_number,
kPorts[histogram_port_],
load_size_string.c_str());
base::Histogram* acks_received_count_histogram =
base::Histogram::FactoryGet(
histogram_name, 1, packet_number, packet_number + 1,
base::Histogram::kUmaTargetedHistogramFlag);
acks_received_count_histogram->Add(count);
}
}
void NetworkStats::RecordPacketLossSeriesHistograms(
const ProtocolValue& protocol,
const std::string& load_size_string,
const Status& status,
int result) {
DCHECK_GT(packets_to_send_, kCorrelatedLossPacketCount);
const char* test_name = TestName();
// Build "NetConnectivity3.Send6.SeriesAcked.<port>.<load_size>" histogram
// name. Total number of histograms are 5*2.
std::string series_acked_histogram_name = base::StringPrintf(
"NetConnectivity3.%s.Send6.SeriesAcked.%d.%s",
test_name,
kPorts[histogram_port_],
load_size_string.c_str());
uint32 correlated_packet_mask =
((1 << kCorrelatedLossPacketCount) - 1) & packets_received_mask_;
// If we are running without a proxy, we'll generate 2 distinct histograms in
// each case, one will have the ".NoProxy" suffix.
size_t histogram_count = has_proxy_server_ ? 1 : 2;
for (size_t i = 0; i < histogram_count; i++) {
// For packet loss test, just record packet loss data.
base::Histogram* series_acked_histogram = base::LinearHistogram::FactoryGet(
series_acked_histogram_name,
1,
1 << kCorrelatedLossPacketCount,
(1 << kCorrelatedLossPacketCount) + 1,
base::Histogram::kUmaTargetedHistogramFlag);
series_acked_histogram->Add(correlated_packet_mask);
series_acked_histogram_name.append(".NoProxy");
}
}
void NetworkStats::RecordRTTHistograms(const ProtocolValue& protocol,
const std::string& load_size_string,
uint32 index) {
DCHECK_GE(index, 0u);
DCHECK_LT(index, packet_status_.size());
const char* test_name = TestName();
std::string rtt_histogram_name = base::StringPrintf(
"NetConnectivity3.%s.Sent%02d.Success.RTT.Packet%02d.%d.%s",
test_name,
packets_to_send_,
index + 1,
kPorts[histogram_port_],
load_size_string.c_str());
base::Histogram* rtt_histogram = base::Histogram::FactoryTimeGet(
rtt_histogram_name,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromSeconds(30), 50,
base::Histogram::kUmaTargetedHistogramFlag);
base::TimeDelta duration =
packet_status_[index].end_time_ - packet_status_[index].start_time_;
rtt_histogram->AddTime(duration);
}
const char* NetworkStats::TestName() {
switch (current_test_) {
case START_PACKET_TEST:
return "StartPacket";
case NON_PACED_PACKET_TEST:
return "NonPacedPacket";
case PACED_PACKET_TEST:
return "PacedPacket";
default:
NOTREACHED();
return "None";
}
}
void NetworkStats::set_socket(net::Socket* socket) {
DCHECK(socket);
DCHECK(!socket_.get());
socket_.reset(socket);
}
// 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;
}
// static
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));
// Check that there is a network connection. We get called only if UMA upload
// to the server has succeeded.
DCHECK(!net::NetworkChangeNotifier::IsOffline());
CR_DEFINE_STATIC_LOCAL(scoped_refptr<base::FieldTrial>, trial, ());
static bool collect_stats = false;
static NetworkStats::HistogramPortSelector histogram_port =
NetworkStats::PORT_6121;
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 = 5;
chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
if (channel == chrome::VersionInfo::CHANNEL_CANARY)
probability_per_group = kDivisor;
else if (channel == chrome::VersionInfo::CHANNEL_DEV)
// Enable the connectivity testing for 10% of the users in dev channel.
probability_per_group = 500;
else if (channel == chrome::VersionInfo::CHANNEL_BETA)
// Enable the connectivity testing for 5% of the users in beta channel.
probability_per_group = 50;
// After October 30, 2012 builds, it will always be in default group
// (disable_network_stats).
trial = base::FieldTrialList::FactoryGetFieldTrial(
"NetworkConnectivity", kDivisor, "disable_network_stats",
2012, 10, 30, 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);
net::HostPortPair server_address(network_stats_server,
kPorts[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();
}
// static
void StartNetworkStatsTest(net::HostResolver* host_resolver,
const net::HostPortPair& server_address,
NetworkStats::HistogramPortSelector histogram_port,
bool has_proxy_server) {
int experiment_to_run = base::RandInt(1, 3);
switch (experiment_to_run) {
case 1:
{
NetworkStats* udp_stats_client = new NetworkStats();
udp_stats_client->Start(
host_resolver, server_address, histogram_port, has_proxy_server,
kSmallTestBytesToSend, kMaximumSequentialPackets,
net::CompletionCallback());
}
break;
case 2:
{
NetworkStats* udp_stats_client = new NetworkStats();
udp_stats_client->Start(
host_resolver, server_address, histogram_port, has_proxy_server,
kMediumTestBytesToSend, kMaximumSequentialPackets,
net::CompletionCallback());
}
break;
case 3:
{
NetworkStats* udp_stats_client = new NetworkStats();
udp_stats_client->Start(
host_resolver, server_address, histogram_port, has_proxy_server,
kLargeTestBytesToSend, kMaximumSequentialPackets,
net::CompletionCallback());
}
break;
}
}
} // namespace chrome_browser_net