| // Copyright 2016 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 "components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.h" |
| |
| #include <stdint.h> |
| |
| #include "base/bind_helpers.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/optional.h" |
| #include "base/rand_util.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_util.h" |
| #include "components/data_reduction_proxy/proto/client_config.pb.h" |
| #include "components/data_use_measurement/core/data_use_user_data.h" |
| #include "net/base/load_flags.h" |
| #include "net/nqe/effective_connection_type.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "net/url_request/url_request_status.h" |
| #include "url/gurl.h" |
| |
| namespace data_reduction_proxy { |
| |
| namespace { |
| |
| static const char kHistogramSucceeded[] = |
| "DataReductionProxy.Pingback.Succeeded"; |
| static const char kHistogramAttempted[] = |
| "DataReductionProxy.Pingback.Attempted"; |
| |
| // Adds the relevant information to |request| for this page load based on page |
| // timing and data reduction proxy state. |
| void AddDataToPageloadMetrics(const DataReductionProxyData& request_data, |
| const DataReductionProxyPageLoadTiming& timing, |
| PageloadMetrics* request, |
| bool opted_out) { |
| request->set_session_key(request_data.session_key()); |
| // For the timing events, any of them could be zero. Fill the message as a |
| // best effort. |
| request->set_allocated_first_request_time( |
| protobuf_parser::CreateTimestampFromTime(timing.navigation_start) |
| .release()); |
| if (request_data.request_url().is_valid()) |
| request->set_first_request_url(request_data.request_url().spec()); |
| if (timing.first_contentful_paint) { |
| request->set_allocated_time_to_first_contentful_paint( |
| protobuf_parser::CreateDurationFromTimeDelta( |
| timing.first_contentful_paint.value()) |
| .release()); |
| } |
| if (timing.experimental_first_meaningful_paint) { |
| request->set_allocated_experimental_time_to_first_meaningful_paint( |
| protobuf_parser::CreateDurationFromTimeDelta( |
| timing.experimental_first_meaningful_paint.value()) |
| .release()); |
| } |
| if (timing.first_image_paint) { |
| request->set_allocated_time_to_first_image_paint( |
| protobuf_parser::CreateDurationFromTimeDelta( |
| timing.first_image_paint.value()) |
| .release()); |
| } |
| if (timing.response_start) { |
| request->set_allocated_time_to_first_byte( |
| protobuf_parser::CreateDurationFromTimeDelta( |
| timing.response_start.value()) |
| .release()); |
| } |
| if (timing.load_event_start) { |
| request->set_allocated_page_load_time( |
| protobuf_parser::CreateDurationFromTimeDelta( |
| timing.load_event_start.value()) |
| .release()); |
| } |
| if (timing.parse_blocked_on_script_load_duration) { |
| request->set_allocated_parse_blocked_on_script_load_duration( |
| protobuf_parser::CreateDurationFromTimeDelta( |
| timing.parse_blocked_on_script_load_duration.value()) |
| .release()); |
| } |
| if (timing.parse_stop) { |
| request->set_allocated_parse_stop( |
| protobuf_parser::CreateDurationFromTimeDelta(timing.parse_stop.value()) |
| .release()); |
| } |
| |
| request->set_effective_connection_type( |
| protobuf_parser::ProtoEffectiveConnectionTypeFromEffectiveConnectionType( |
| request_data.effective_connection_type())); |
| request->set_compressed_page_size_bytes(timing.network_bytes); |
| request->set_original_page_size_bytes(timing.original_network_bytes); |
| |
| if (request_data.page_id()) { |
| request->set_page_id(request_data.page_id().value()); |
| } |
| |
| bool was_preview_shown = false; |
| if (request_data.lofi_received()) { |
| request->set_previews_type(PageloadMetrics_PreviewsType_LOFI); |
| was_preview_shown = true; |
| } else if (request_data.lite_page_received()) { |
| request->set_previews_type(PageloadMetrics_PreviewsType_LITE_PAGE); |
| was_preview_shown = true; |
| } else { |
| request->set_previews_type(PageloadMetrics_PreviewsType_NONE); |
| } |
| |
| if (!was_preview_shown || timing.app_background_occurred) { |
| request->set_previews_opt_out(PageloadMetrics_PreviewsOptOut_UNKNOWN); |
| return; |
| } |
| |
| if (opted_out) { |
| request->set_previews_opt_out(PageloadMetrics_PreviewsOptOut_OPT_OUT); |
| return; |
| } |
| request->set_previews_opt_out(PageloadMetrics_PreviewsOptOut_NON_OPT_OUT); |
| } |
| |
| // Adds |current_time| as the metrics sent time to |request_data|, and returns |
| // the serialized request. |
| std::string AddTimeAndSerializeRequest( |
| RecordPageloadMetricsRequest* request_data, |
| base::Time current_time) { |
| request_data->set_allocated_metrics_sent_time( |
| protobuf_parser::CreateTimestampFromTime(current_time).release()); |
| std::string serialized_request; |
| request_data->SerializeToString(&serialized_request); |
| return serialized_request; |
| } |
| |
| } // namespace |
| |
| DataReductionProxyPingbackClient::DataReductionProxyPingbackClient( |
| net::URLRequestContextGetter* url_request_context) |
| : url_request_context_(url_request_context), |
| pingback_url_(util::AddApiKeyToUrl(params::GetPingbackURL())), |
| pingback_reporting_fraction_(0.0) {} |
| |
| DataReductionProxyPingbackClient::~DataReductionProxyPingbackClient() { |
| DCHECK(opt_outs_.empty()); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| } |
| |
| void DataReductionProxyPingbackClient::OnURLFetchComplete( |
| const net::URLFetcher* source) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(source == current_fetcher_.get()); |
| UMA_HISTOGRAM_BOOLEAN(kHistogramSucceeded, source->GetStatus().is_success()); |
| current_fetcher_.reset(); |
| if (metrics_request_.pageloads_size() > 0) { |
| CreateFetcherForDataAndStart(); |
| } |
| } |
| |
| void DataReductionProxyPingbackClient::SendPingback( |
| const DataReductionProxyData& request_data, |
| const DataReductionProxyPageLoadTiming& timing) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| bool send_pingback = ShouldSendPingback(); |
| UMA_HISTOGRAM_BOOLEAN(kHistogramAttempted, send_pingback); |
| if (!send_pingback) |
| return; |
| |
| bool opted_out = false; |
| if (request_data.page_id()) { |
| auto opt_out = opt_outs_.find(NavigationID(request_data.page_id().value(), |
| request_data.session_key())); |
| opted_out = opt_out != opt_outs_.end(); |
| if (opted_out) |
| opt_outs_.erase(opt_out); |
| } |
| |
| PageloadMetrics* pageload_metrics = metrics_request_.add_pageloads(); |
| AddDataToPageloadMetrics(request_data, timing, pageload_metrics, opted_out); |
| if (current_fetcher_.get()) |
| return; |
| DCHECK_EQ(1, metrics_request_.pageloads_size()); |
| CreateFetcherForDataAndStart(); |
| } |
| |
| void DataReductionProxyPingbackClient::CreateFetcherForDataAndStart() { |
| DCHECK(!current_fetcher_); |
| DCHECK_GE(metrics_request_.pageloads_size(), 1); |
| std::string serialized_request = |
| AddTimeAndSerializeRequest(&metrics_request_, CurrentTime()); |
| metrics_request_.Clear(); |
| current_fetcher_ = |
| net::URLFetcher::Create(pingback_url_, net::URLFetcher::POST, this); |
| data_use_measurement::DataUseUserData::AttachToFetcher( |
| current_fetcher_.get(), |
| data_use_measurement::DataUseUserData::DATA_REDUCTION_PROXY); |
| current_fetcher_->SetLoadFlags(net::LOAD_BYPASS_PROXY); |
| current_fetcher_->SetUploadData("application/x-protobuf", serialized_request); |
| current_fetcher_->SetRequestContext(url_request_context_); |
| // |current_fetcher_| should not retry on 5xx errors since the server may |
| // already be overloaded. |
| static const int kMaxRetries = 5; |
| current_fetcher_->SetAutomaticallyRetryOnNetworkChanges(kMaxRetries); |
| current_fetcher_->Start(); |
| } |
| |
| bool DataReductionProxyPingbackClient::ShouldSendPingback() const { |
| return params::IsForcePingbackEnabledViaFlags() || |
| GenerateRandomFloat() < pingback_reporting_fraction_; |
| } |
| |
| base::Time DataReductionProxyPingbackClient::CurrentTime() const { |
| return base::Time::Now(); |
| } |
| |
| float DataReductionProxyPingbackClient::GenerateRandomFloat() const { |
| return static_cast<float>(base::RandDouble()); |
| } |
| |
| void DataReductionProxyPingbackClient::SetPingbackReportingFraction( |
| float pingback_reporting_fraction) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_LE(0.0f, pingback_reporting_fraction); |
| DCHECK_GE(1.0f, pingback_reporting_fraction); |
| pingback_reporting_fraction_ = pingback_reporting_fraction; |
| } |
| |
| void DataReductionProxyPingbackClient::AddOptOut( |
| const NavigationID& navigation_id) { |
| opt_outs_.emplace(navigation_id); |
| } |
| |
| void DataReductionProxyPingbackClient::ClearNavigationKeySync( |
| const NavigationID& navigation_id) { |
| opt_outs_.erase(navigation_id); |
| } |
| |
| void DataReductionProxyPingbackClient::ClearNavigationKeyAsync( |
| const NavigationID& navigation_id) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&DataReductionProxyPingbackClient::ClearNavigationKeySync, |
| base::Unretained(this), navigation_id)); |
| } |
| |
| size_t DataReductionProxyPingbackClient::OptOutsSizeForTesting() const { |
| return opt_outs_.size(); |
| } |
| |
| } // namespace data_reduction_proxy |