| // 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 |