blob: 4d21e6666ed003690fe490ec8143dff739d2b086 [file] [log] [blame]
// 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