| // 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_ = 6; |
| // 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 = 1300; |
| |
| // 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"}; |
| |
| // 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; |
| |
| // 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), |
| current_test_index_(0), |
| 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, |
| const net::CompletionCallback& finished_callback) { |
| DCHECK(host_resolver); |
| histogram_port_ = histogram_port; |
| has_proxy_server_ = has_proxy_server; |
| probe_packet_bytes_ = probe_bytes; |
| 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) { |
| write_buffer_ = NULL; |
| SendHelloRequest(); |
| } else { |
| SendProbeRequest(); |
| } |
| } |
| |
| void NetworkStats::ResetData() { |
| 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_; |
| 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; |
| } |
| 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_packet_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; |
| |
| 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)); |
| return rv; |
| } |
| |
| 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. |
| 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 |
| // records the SendToLastRecvDelay. |
| if (test_type == NAT_BIND_TEST) { |
| return received_packets >= maximum_NAT_packets_; |
| } |
| |
| 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) { |
| // 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()) |
| 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) |
| return bytes_written; |
| UpdateSendBuffer(bytes_written); |
| return net::OK; |
| } |
| |
| void NetworkStats::OnWriteComplete(int result) { |
| DCHECK(socket_.get()); |
| DCHECK_NE(net::ERR_IO_PENDING, result); |
| if (result < 0) { |
| TestPhaseComplete(WRITE_FAILED, result); |
| return; |
| } |
| UpdateSendBuffer(result); |
| } |
| |
| void NetworkStats::UpdateSendBuffer(int bytes_sent) { |
| write_buffer_->DidConsume(bytes_sent); |
| DCHECK_EQ(write_buffer_->BytesRemaining(), 0); |
| 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(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 (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 && |
| test_sequence_[current_test_index_ - 1] == NAT_BIND_TEST) { |
| // We record the NATTestReceivedHistograms after the succeeding |
| // TokenRequest. |
| RecordNATTestReceivedHistograms(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; |
| default: |
| DVLOG(1) << "Unexpected test type " << test_type |
| << " in RecordHistograms."; |
| } |
| } |
| |
| void NetworkStats::RecordInterArrivalHistograms(TestType test_type) { |
| std::string histogram_name = |
| base::StringPrintf("NetConnectivity4.%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) { |
| const char* test_name = kTestName[test_type]; |
| std::string histogram_prefix = base::StringPrintf( |
| "NetConnectivity4.%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("NetConnectivity4.%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("NetConnectivity4.%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::RecordPacketLossSeriesHistograms(TestType test_type) { |
| const char* test_name = kTestName[test_type]; |
| // Build "NetConnectivity4.<TestName>.First6.SeriesRecv.<port>.<probe_size>" |
| // histogram name. Total 3(tests) x 12 histograms. |
| std::string series_acked_histogram_name = |
| base::StringPrintf("NetConnectivity4.%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_LT(index, packet_rtt_.size()); |
| |
| if (!packets_received_mask_.test(index)) |
| return; // Probe packet never received. |
| |
| std::string rtt_histogram_name = base::StringPrintf( |
| "NetConnectivity4.%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) { |
| 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( |
| "NetConnectivity4.%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)); |
| |
| // 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; |
| |
| 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; |
| } |
| |
| // 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); |
| |
| // |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], |
| net::CompletionCallback()); |
| } |
| |
| } // namespace chrome_browser_net |