blob: fbaeae308b1da01dbbdfd7914648a8a273d49018 [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 "chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.h"
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "chrome/browser/loader/chrome_navigation_data.h"
#include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
#include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
#include "chrome/common/page_load_metrics/page_load_timing.h"
#include "chrome/test/base/testing_browser_process.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.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"
namespace data_reduction_proxy {
namespace {
const char kDefaultTestUrl[] = "http://google.com";
data_reduction_proxy::DataReductionProxyData* DataForNavigationHandle(
content::WebContents* web_contents,
content::NavigationHandle* navigation_handle) {
ChromeNavigationData* chrome_navigation_data = new ChromeNavigationData();
content::WebContentsTester::For(web_contents)
->SetNavigationData(navigation_handle,
base::WrapUnique(chrome_navigation_data));
data_reduction_proxy::DataReductionProxyData* data =
new data_reduction_proxy::DataReductionProxyData();
chrome_navigation_data->SetDataReductionProxyData(base::WrapUnique(data));
return data;
}
// Pingback client responsible for recording the timing information it receives
// from a SendPingback call.
class TestPingbackClient
: public data_reduction_proxy::DataReductionProxyPingbackClient {
public:
TestPingbackClient()
: data_reduction_proxy::DataReductionProxyPingbackClient(nullptr),
send_pingback_called_(false) {}
~TestPingbackClient() override {}
void SendPingback(
const data_reduction_proxy::DataReductionProxyData& data,
const data_reduction_proxy::DataReductionProxyPageLoadTiming& timing)
override {
timing_.reset(
new data_reduction_proxy::DataReductionProxyPageLoadTiming(timing));
send_pingback_called_ = true;
data_ = data.DeepCopy();
}
data_reduction_proxy::DataReductionProxyPageLoadTiming* timing() const {
return timing_.get();
}
const data_reduction_proxy::DataReductionProxyData& data() const {
return *data_;
}
bool send_pingback_called() const { return send_pingback_called_; }
void Reset() {
send_pingback_called_ = false;
timing_.reset();
}
private:
std::unique_ptr<data_reduction_proxy::DataReductionProxyPageLoadTiming>
timing_;
std::unique_ptr<data_reduction_proxy::DataReductionProxyData> data_;
bool send_pingback_called_;
DISALLOW_COPY_AND_ASSIGN(TestPingbackClient);
};
} // namespace
// DataReductionProxyMetricsObserver responsible for modifying data about the
// navigation in OnCommit. It is also responsible for using a passed in
// DataReductionProxyPingbackClient instead of the default.
class TestDataReductionProxyMetricsObserver
: public DataReductionProxyMetricsObserver {
public:
TestDataReductionProxyMetricsObserver(content::WebContents* web_contents,
TestPingbackClient* pingback_client,
bool data_reduction_proxy_used,
bool lofi_used)
: web_contents_(web_contents),
pingback_client_(pingback_client),
data_reduction_proxy_used_(data_reduction_proxy_used),
lofi_used_(lofi_used) {}
~TestDataReductionProxyMetricsObserver() override {}
// page_load_metrics::PageLoadMetricsObserver implementation:
ObservePolicy OnCommit(
content::NavigationHandle* navigation_handle) override {
DataReductionProxyData* data =
DataForNavigationHandle(web_contents_, navigation_handle);
data->set_used_data_reduction_proxy(data_reduction_proxy_used_);
data->set_lofi_requested(lofi_used_);
return DataReductionProxyMetricsObserver::OnCommit(navigation_handle);
}
DataReductionProxyPingbackClient* GetPingbackClient() const override {
return pingback_client_;
}
private:
content::WebContents* web_contents_;
TestPingbackClient* pingback_client_;
bool data_reduction_proxy_used_;
bool lofi_used_;
DISALLOW_COPY_AND_ASSIGN(TestDataReductionProxyMetricsObserver);
};
class DataReductionProxyMetricsObserverTest
: public page_load_metrics::PageLoadMetricsObserverTestHarness {
public:
DataReductionProxyMetricsObserverTest()
: pingback_client_(new TestPingbackClient()),
data_reduction_proxy_used_(false),
is_using_lofi_(false) {}
void ResetTest() {
// Reset to the default testing state. Does not reset histogram state.
timing_.navigation_start = base::Time::FromDoubleT(1);
timing_.response_start = base::TimeDelta::FromSeconds(2);
timing_.parse_timing.parse_start = base::TimeDelta::FromSeconds(3);
timing_.paint_timing.first_contentful_paint =
base::TimeDelta::FromSeconds(4);
timing_.paint_timing.first_paint = base::TimeDelta::FromSeconds(4);
timing_.paint_timing.first_meaningful_paint =
base::TimeDelta::FromSeconds(8);
timing_.paint_timing.first_image_paint = base::TimeDelta::FromSeconds(5);
timing_.paint_timing.first_text_paint = base::TimeDelta::FromSeconds(6);
timing_.document_timing.load_event_start = base::TimeDelta::FromSeconds(7);
timing_.parse_timing.parse_stop = base::TimeDelta::FromSeconds(4);
timing_.parse_timing.parse_blocked_on_script_load_duration =
base::TimeDelta::FromSeconds(1);
PopulateRequiredTimingFields(&timing_);
}
void RunTest(bool data_reduction_proxy_used, bool is_using_lofi) {
data_reduction_proxy_used_ = data_reduction_proxy_used;
is_using_lofi_ = is_using_lofi;
NavigateAndCommit(GURL(kDefaultTestUrl));
SimulateTimingUpdate(timing_);
pingback_client_->Reset();
}
void RunTestAndNavigateToUntrackedUrl(bool data_reduction_proxy_used,
bool is_using_lofi) {
RunTest(data_reduction_proxy_used, is_using_lofi);
NavigateToUntrackedUrl();
}
// Verify that, if expected and actual are set, their values are equal.
// Otherwise, verify that both are unset.
void ExpectEqualOrUnset(const base::Optional<base::TimeDelta>& expected,
const base::Optional<base::TimeDelta>& actual) {
if (expected && actual) {
EXPECT_EQ(expected.value(), actual.value());
} else {
EXPECT_TRUE(!expected);
EXPECT_TRUE(!actual);
}
}
void ValidateTimes() {
EXPECT_TRUE(pingback_client_->send_pingback_called());
EXPECT_EQ(timing_.navigation_start,
pingback_client_->timing()->navigation_start);
ExpectEqualOrUnset(timing_.paint_timing.first_contentful_paint,
pingback_client_->timing()->first_contentful_paint);
ExpectEqualOrUnset(
timing_.paint_timing.first_meaningful_paint,
pingback_client_->timing()->experimental_first_meaningful_paint);
ExpectEqualOrUnset(timing_.response_start,
pingback_client_->timing()->response_start);
ExpectEqualOrUnset(timing_.document_timing.load_event_start,
pingback_client_->timing()->load_event_start);
ExpectEqualOrUnset(timing_.paint_timing.first_image_paint,
pingback_client_->timing()->first_image_paint);
}
void ValidateLoFiInPingback(bool lofi_expected) {
EXPECT_TRUE(pingback_client_->send_pingback_called());
EXPECT_EQ(lofi_expected, pingback_client_->data().lofi_received());
}
void ValidateHistograms() {
ValidateHistogramsForSuffix(
internal::kHistogramDOMContentLoadedEventFiredSuffix,
timing_.document_timing.dom_content_loaded_event_start);
ValidateHistogramsForSuffix(internal::kHistogramFirstLayoutSuffix,
timing_.document_timing.first_layout);
ValidateHistogramsForSuffix(internal::kHistogramLoadEventFiredSuffix,
timing_.document_timing.load_event_start);
ValidateHistogramsForSuffix(internal::kHistogramFirstContentfulPaintSuffix,
timing_.paint_timing.first_contentful_paint);
ValidateHistogramsForSuffix(internal::kHistogramFirstMeaningfulPaintSuffix,
timing_.paint_timing.first_meaningful_paint);
ValidateHistogramsForSuffix(internal::kHistogramFirstImagePaintSuffix,
timing_.paint_timing.first_image_paint);
ValidateHistogramsForSuffix(internal::kHistogramFirstPaintSuffix,
timing_.paint_timing.first_paint);
ValidateHistogramsForSuffix(internal::kHistogramFirstTextPaintSuffix,
timing_.paint_timing.first_text_paint);
ValidateHistogramsForSuffix(internal::kHistogramParseStartSuffix,
timing_.parse_timing.parse_start);
ValidateHistogramsForSuffix(
internal::kHistogramParseBlockedOnScriptLoadSuffix,
timing_.parse_timing.parse_blocked_on_script_load_duration);
ValidateHistogramsForSuffix(internal::kHistogramParseDurationSuffix,
timing_.parse_timing.parse_stop.value() -
timing_.parse_timing.parse_start.value());
}
void ValidateHistogramsForSuffix(
const std::string& histogram_suffix,
const base::Optional<base::TimeDelta>& event) {
histogram_tester().ExpectTotalCount(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(histogram_suffix),
data_reduction_proxy_used_ ? 1 : 0);
histogram_tester().ExpectTotalCount(
std::string(internal::kHistogramDataReductionProxyLoFiOnPrefix)
.append(histogram_suffix),
is_using_lofi_ ? 1 : 0);
if (!data_reduction_proxy_used_)
return;
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(histogram_suffix),
static_cast<base::HistogramBase::Sample>(
event.value().InMilliseconds()),
1);
if (!is_using_lofi_)
return;
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyLoFiOnPrefix)
.append(histogram_suffix),
event.value().InMilliseconds(), is_using_lofi_ ? 1 : 0);
}
void ValidateDataHistograms(int network_resources,
int drp_resources,
int64_t network_bytes,
int64_t drp_bytes,
int64_t ocl_bytes) {
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kResourcesPercentProxied),
100 * drp_resources / network_resources, 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kBytesPercentProxied),
static_cast<int>(100 * drp_bytes / network_bytes), 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kNetworkResources),
network_resources, 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kResourcesProxied),
drp_resources, 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kResourcesNotProxied),
network_resources - drp_resources, 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kNetworkBytes),
static_cast<int>(network_bytes / 1024), 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kBytesProxied),
static_cast<int>(drp_bytes / 1024), 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kBytesNotProxied),
static_cast<int>((network_bytes - drp_bytes) / 1024), 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kBytesOriginal),
static_cast<int>(ocl_bytes / 1024), 1);
if (ocl_bytes < network_bytes) {
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kBytesInflationPercent),
static_cast<int>(100 * network_bytes / ocl_bytes - 100), 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kBytesInflation),
static_cast<int>((network_bytes - ocl_bytes) / 1024), 1);
} else {
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kBytesCompressionRatio),
static_cast<int>(100 * network_bytes / ocl_bytes), 1);
histogram_tester().ExpectUniqueSample(
std::string(internal::kHistogramDataReductionProxyPrefix)
.append(internal::kBytesSavings),
static_cast<int>((ocl_bytes - network_bytes) / 1024), 1);
}
}
protected:
void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
tracker->AddObserver(
base::MakeUnique<TestDataReductionProxyMetricsObserver>(
web_contents(), pingback_client_.get(), data_reduction_proxy_used_,
is_using_lofi_));
}
std::unique_ptr<TestPingbackClient> pingback_client_;
page_load_metrics::PageLoadTiming timing_;
private:
bool data_reduction_proxy_used_;
bool is_using_lofi_;
DISALLOW_COPY_AND_ASSIGN(DataReductionProxyMetricsObserverTest);
};
TEST_F(DataReductionProxyMetricsObserverTest, DataReductionProxyOff) {
ResetTest();
// Verify that when the data reduction proxy was not used, no UMA is reported.
RunTest(false, false);
ValidateHistograms();
}
TEST_F(DataReductionProxyMetricsObserverTest, DataReductionProxyOn) {
ResetTest();
// Verify that when the data reduction proxy was used, but lofi was not used,
// the correpsonding UMA is reported.
RunTest(true, false);
ValidateHistograms();
}
TEST_F(DataReductionProxyMetricsObserverTest, LofiEnabled) {
ResetTest();
// Verify that when the data reduction proxy was used and lofi was used, both
// histograms are reported.
RunTest(true, true);
ValidateHistograms();
}
TEST_F(DataReductionProxyMetricsObserverTest, OnCompletePingback) {
ResetTest();
// Verify that when data reduction proxy was used the correct timing
// information is sent to SendPingback.
RunTestAndNavigateToUntrackedUrl(true, false);
ValidateTimes();
ResetTest();
// Verify that when data reduction proxy was used but first image paint is
// unset, the correct timing information is sent to SendPingback.
timing_.paint_timing.first_image_paint = base::nullopt;
RunTestAndNavigateToUntrackedUrl(true, false);
ValidateTimes();
ResetTest();
// Verify that when data reduction proxy was used but first contentful paint
// is unset, SendPingback is not called.
timing_.paint_timing.first_contentful_paint = base::nullopt;
RunTestAndNavigateToUntrackedUrl(true, false);
ValidateTimes();
ResetTest();
// Verify that when data reduction proxy was used but first meaningful paint
// is unset, SendPingback is not called.
timing_.paint_timing.first_meaningful_paint = base::nullopt;
RunTestAndNavigateToUntrackedUrl(true, false);
ValidateTimes();
ResetTest();
// Verify that when data reduction proxy was used but load event start is
// unset, SendPingback is not called.
timing_.document_timing.load_event_start = base::nullopt;
RunTestAndNavigateToUntrackedUrl(true, false);
ValidateTimes();
ValidateLoFiInPingback(false);
ResetTest();
std::unique_ptr<DataReductionProxyData> data =
base::MakeUnique<DataReductionProxyData>();
data->set_used_data_reduction_proxy(true);
data->set_lofi_received(true);
// Verify LoFi is tracked when a LoFi response is received.
page_load_metrics::ExtraRequestInfo resource = {
true /*was_cached*/, 1024 * 40 /* raw_body_bytes */,
0 /* original_network_content_length */, std::move(data)};
RunTest(true, false);
SimulateLoadedResource(resource);
NavigateToUntrackedUrl();
ValidateTimes();
ValidateLoFiInPingback(true);
ResetTest();
// Verify that when data reduction proxy was not used, SendPingback is not
// called.
RunTestAndNavigateToUntrackedUrl(false, false);
EXPECT_FALSE(pingback_client_->send_pingback_called());
ResetTest();
// Verify that when the holdback experiment is enabled, no pingback is sent.
base::FieldTrialList field_trial_list(nullptr);
ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
"DataCompressionProxyHoldback", "Enabled"));
RunTestAndNavigateToUntrackedUrl(true, false);
EXPECT_FALSE(pingback_client_->send_pingback_called());
}
TEST_F(DataReductionProxyMetricsObserverTest, ByteInformationCompression) {
ResetTest();
RunTest(true, false);
std::unique_ptr<DataReductionProxyData> data =
base::MakeUnique<DataReductionProxyData>();
data->set_used_data_reduction_proxy(true);
// Prepare 4 resources of varying size and configurations.
page_load_metrics::ExtraRequestInfo resources[] = {
// Cached request.
{true /*was_cached*/, 1024 * 40 /* raw_body_bytes */,
0 /* original_network_content_length */,
nullptr /* data_reduction_proxy_data */},
// Uncached non-proxied request.
{false /*was_cached*/, 1024 * 40 /* raw_body_bytes */,
1024 * 40 /* original_network_content_length */,
nullptr /* data_reduction_proxy_data */},
// Uncached proxied request with .1 compression ratio.
{false /*was_cached*/, 1024 * 40 /* raw_body_bytes */,
1024 * 40 * 10 /* original_network_content_length */, data->DeepCopy()},
// Uncached proxied request with .5 compression ratio.
{false /*was_cached*/, 1024 * 40 /* raw_body_bytes */,
1024 * 40 * 5 /* original_network_content_length */, std::move(data)},
};
int network_resources = 0;
int drp_resources = 0;
int64_t network_bytes = 0;
int64_t drp_bytes = 0;
int64_t ocl_bytes = 0;
for (const auto& request : resources) {
SimulateLoadedResource(request);
if (!request.was_cached) {
network_bytes += request.raw_body_bytes;
ocl_bytes += request.original_network_content_length;
++network_resources;
}
if (request.data_reduction_proxy_data &&
request.data_reduction_proxy_data->used_data_reduction_proxy()) {
drp_bytes += request.raw_body_bytes;
++drp_resources;
}
}
NavigateToUntrackedUrl();
ValidateDataHistograms(network_resources, drp_resources, network_bytes,
drp_bytes, ocl_bytes);
}
TEST_F(DataReductionProxyMetricsObserverTest, ByteInformationInflation) {
ResetTest();
RunTest(true, false);
std::unique_ptr<DataReductionProxyData> data =
base::MakeUnique<DataReductionProxyData>();
data->set_used_data_reduction_proxy(true);
// Prepare 4 resources of varying size and configurations.
page_load_metrics::ExtraRequestInfo resources[] = {
// Cached request.
{true /*was_cached*/, 1024 * 40 /* raw_body_bytes */,
0 /* original_network_content_length */,
nullptr /* data_reduction_proxy_data */},
// Uncached non-proxied request.
{false /*was_cached*/, 1024 * 40 /* raw_body_bytes */,
1024 * 40 /* original_network_content_length */,
nullptr /* data_reduction_proxy_data */},
// Uncached proxied request with .1 compression ratio.
{false /*was_cached*/, 1024 * 40 * 10 /* raw_body_bytes */,
1024 * 40 /* original_network_content_length */, data->DeepCopy()},
// Uncached proxied request with .5 compression ratio.
{false /*was_cached*/, 1024 * 40 * 5 /* raw_body_bytes */,
1024 * 40 /* original_network_content_length */, std::move(data)},
};
int network_resources = 0;
int drp_resources = 0;
int64_t network_bytes = 0;
int64_t drp_bytes = 0;
int64_t ocl_bytes = 0;
for (const auto& request : resources) {
SimulateLoadedResource(request);
if (!request.was_cached) {
network_bytes += request.raw_body_bytes;
ocl_bytes += request.original_network_content_length;
++network_resources;
}
if (request.data_reduction_proxy_data &&
request.data_reduction_proxy_data->used_data_reduction_proxy()) {
drp_bytes += request.raw_body_bytes;
++drp_resources;
}
}
NavigateToUntrackedUrl();
ValidateDataHistograms(network_resources, drp_resources, network_bytes,
drp_bytes, ocl_bytes);
}
} // namespace data_reduction_proxy