123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- // Copyright 2015 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/page_load_metrics/renderer/page_timing_metrics_sender.h"
- #include <utility>
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/containers/contains.h"
- #include "base/feature_list.h"
- #include "base/time/time.h"
- #include "base/timer/timer.h"
- #include "components/page_load_metrics/common/page_load_metrics.mojom.h"
- #include "components/page_load_metrics/common/page_load_metrics_util.h"
- #include "components/page_load_metrics/renderer/page_timing_sender.h"
- #include "services/network/public/cpp/url_loader_completion_status.h"
- #include "services/network/public/mojom/url_response_head.mojom.h"
- #include "third_party/blink/public/common/features.h"
- #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
- #include "third_party/blink/public/mojom/use_counter/use_counter_feature.mojom-shared.h"
- #include "ui/gfx/geometry/rect.h"
- namespace page_load_metrics {
- namespace {
- const int kInitialTimerDelayMillis = 50;
- const int64_t kInputDelayAdjustmentMillis = int64_t(50);
- mojom::UserInteractionType UserInteractionTypeForMojom(
- blink::UserInteractionType interaction_type) {
- switch (interaction_type) {
- case blink::UserInteractionType::kKeyboard:
- return mojom::UserInteractionType::kKeyboard;
- case blink::UserInteractionType::kTapOrClick:
- return mojom::UserInteractionType::kTapOrClick;
- case blink::UserInteractionType::kDrag:
- return mojom::UserInteractionType::kDrag;
- }
- // mojom::UserInteractionType should have the same interaction types as
- // blink::UserInteractionType does.
- NOTREACHED();
- return mojom::UserInteractionType::kMinValue;
- }
- } // namespace
- PageTimingMetricsSender::PageTimingMetricsSender(
- std::unique_ptr<PageTimingSender> sender,
- std::unique_ptr<base::OneShotTimer> timer,
- mojom::PageLoadTimingPtr initial_timing,
- const PageTimingMetadataRecorder::MonotonicTiming& initial_monotonic_timing,
- std::unique_ptr<PageResourceDataUse> initial_request)
- : sender_(std::move(sender)),
- timer_(std::move(timer)),
- last_timing_(std::move(initial_timing)),
- last_cpu_timing_(mojom::CpuTiming::New()),
- input_timing_delta_(mojom::InputTiming::New()),
- metadata_(mojom::FrameMetadata::New()),
- buffer_timer_delay_ms_(GetBufferTimerDelayMillis(TimerType::kRenderer)),
- metadata_recorder_(initial_monotonic_timing) {
- InitiateUserInteractionTiming();
- const auto resource_id = initial_request->resource_id();
- page_resource_data_use_.emplace(
- std::piecewise_construct, std::forward_as_tuple(resource_id),
- std::forward_as_tuple(std::move(initial_request)));
- if (!IsEmpty(*last_timing_)) {
- EnsureSendTimer();
- }
- }
- PageTimingMetricsSender::~PageTimingMetricsSender() {
- // Make sure we don't have any unsent data. If this assertion fails, then it
- // means metrics are somehow coming in between MetricsRenderFrameObserver's
- // ReadyToCommitNavigation and DidCommitProvisionalLoad.
- DCHECK(!timer_->IsRunning());
- }
- void PageTimingMetricsSender::DidObserveLoadingBehavior(
- blink::LoadingBehaviorFlag behavior) {
- if (behavior & metadata_->behavior_flags) {
- return;
- }
- metadata_->behavior_flags |= behavior;
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::DidObserveNewFeatureUsage(
- const blink::UseCounterFeature& feature) {
- if (feature_tracker_.TestAndSet(feature))
- return;
- new_features_.push_back(feature);
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::DidObserveSoftNavigation(uint32_t count) {
- DCHECK(count > soft_navigation_count_);
- soft_navigation_count_ = count;
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::DidObserveLayoutShift(
- double score,
- bool after_input_or_scroll) {
- DCHECK(score > 0);
- render_data_.layout_shift_delta += score;
- render_data_.new_layout_shifts.push_back(
- mojom::LayoutShift::New(base::TimeTicks::Now(), score));
- if (!after_input_or_scroll)
- render_data_.layout_shift_delta_before_input_or_scroll += score;
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::DidObserveLayoutNg(uint32_t all_block_count,
- uint32_t ng_block_count,
- uint32_t all_call_count,
- uint32_t ng_call_count) {
- render_data_.all_layout_block_count_delta += all_block_count;
- render_data_.ng_layout_block_count_delta += ng_block_count;
- render_data_.all_layout_call_count_delta += all_call_count;
- render_data_.ng_layout_call_count_delta += ng_call_count;
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::DidObserveMobileFriendlinessChanged(
- const blink::MobileFriendliness& mf) {
- mobile_friendliness_ = mf;
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::DidStartResponse(
- const url::SchemeHostPort& final_response_url,
- int resource_id,
- const network::mojom::URLResponseHead& response_head,
- network::mojom::RequestDestination request_destination) {
- DCHECK(!base::Contains(page_resource_data_use_, resource_id));
- auto resource_it = page_resource_data_use_.emplace(
- std::piecewise_construct, std::forward_as_tuple(resource_id),
- std::forward_as_tuple(std::make_unique<PageResourceDataUse>()));
- resource_it.first->second->DidStartResponse(
- final_response_url, resource_id, response_head, request_destination);
- }
- void PageTimingMetricsSender::DidReceiveTransferSizeUpdate(
- int resource_id,
- int received_data_length) {
- // Transfer size updates are called in a throttled manner.
- auto resource_it = page_resource_data_use_.find(resource_id);
- // It is possible that resources are not in the map, if response headers were
- // not received or for failed/cancelled resources.
- if (resource_it == page_resource_data_use_.end()) {
- return;
- }
- resource_it->second->DidReceiveTransferSizeUpdate(received_data_length);
- modified_resources_.insert(resource_it->second.get());
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::DidCompleteResponse(
- int resource_id,
- const network::URLLoaderCompletionStatus& status) {
- auto resource_it = page_resource_data_use_.find(resource_id);
- // It is possible that resources are not in the map, if response headers were
- // not received or for failed/cancelled resources. For data reduction proxy
- // purposes treat these as having no savings.
- if (resource_it == page_resource_data_use_.end()) {
- auto new_resource_it = page_resource_data_use_.emplace(
- std::piecewise_construct, std::forward_as_tuple(resource_id),
- std::forward_as_tuple(std::make_unique<PageResourceDataUse>()));
- resource_it = new_resource_it.first;
- }
- resource_it->second->DidCompleteResponse(status);
- EnsureSendTimer();
- modified_resources_.insert(resource_it->second.get());
- }
- void PageTimingMetricsSender::DidCancelResponse(int resource_id) {
- auto resource_it = page_resource_data_use_.find(resource_id);
- if (resource_it == page_resource_data_use_.end()) {
- return;
- }
- resource_it->second->DidCancelResponse();
- }
- void PageTimingMetricsSender::DidLoadResourceFromMemoryCache(
- const GURL& response_url,
- int request_id,
- int64_t encoded_body_length,
- const std::string& mime_type) {
- // In general, we should not observe the same resource being loaded twice in
- // the frame. This is possible due to an existing workaround in
- // ResourceFetcher::EmulateLoadStartedForInspector(). In this case, ignore
- // multiple resources being loaded in the document, as memory cache resources
- // are only reported once per context by design in all other cases.
- if (base::Contains(page_resource_data_use_, request_id))
- return;
- auto resource_it = page_resource_data_use_.emplace(
- std::piecewise_construct, std::forward_as_tuple(request_id),
- std::forward_as_tuple(std::make_unique<PageResourceDataUse>()));
- resource_it.first->second->DidLoadFromMemoryCache(
- response_url, request_id, encoded_body_length, mime_type);
- modified_resources_.insert(resource_it.first->second.get());
- }
- void PageTimingMetricsSender::OnMainFrameIntersectionChanged(
- const gfx::Rect& main_frame_intersection_rect) {
- metadata_->main_frame_intersection_rect = main_frame_intersection_rect;
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::OnMainFrameViewportRectangleChanged(
- const gfx::Rect& main_frame_viewport_rect) {
- metadata_->main_frame_viewport_rect = main_frame_viewport_rect;
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::UpdateResourceMetadata(
- int resource_id,
- bool reported_as_ad_resource,
- bool is_main_frame_resource,
- bool completed_before_fcp) {
- auto it = page_resource_data_use_.find(resource_id);
- if (it == page_resource_data_use_.end())
- return;
- // This can get called multiple times for a resource, and this flag will only
- // be true once.
- if (reported_as_ad_resource)
- it->second->SetReportedAsAdResource(reported_as_ad_resource);
- // This can get called multiple times for a resource, and this flag will only
- // be true once.
- if (completed_before_fcp)
- it->second->SetCompletedBeforeFCP(completed_before_fcp);
- it->second->SetIsMainFrameResource(is_main_frame_resource);
- }
- void PageTimingMetricsSender::SetUpSmoothnessReporting(
- base::ReadOnlySharedMemoryRegion shared_memory) {
- sender_->SetUpSmoothnessReporting(std::move(shared_memory));
- }
- void PageTimingMetricsSender::Update(
- mojom::PageLoadTimingPtr timing,
- const PageTimingMetadataRecorder::MonotonicTiming& monotonic_timing) {
- if (last_timing_->Equals(*timing)) {
- return;
- }
- // We want to make sure that each PageTimingMetricsSender is associated
- // with a distinct page navigation. Because we reset the object on commit,
- // we can trash last_timing_ on a provisional load before SendNow() fires.
- if (!last_timing_->navigation_start.is_null() &&
- last_timing_->navigation_start != timing->navigation_start) {
- return;
- }
- // We want to force sending the metrics quickly when FCP is reached.
- bool send_urgently =
- (!last_timing_->paint_timing ||
- !last_timing_->paint_timing->first_contentful_paint.has_value()) &&
- timing->paint_timing &&
- timing->paint_timing->first_contentful_paint.has_value();
- last_timing_ = std::move(timing);
- metadata_recorder_.UpdateMetadata(monotonic_timing);
- EnsureSendTimer(send_urgently);
- }
- void PageTimingMetricsSender::SendLatest() {
- if (!timer_->IsRunning())
- return;
- timer_->Stop();
- SendNow();
- }
- void PageTimingMetricsSender::UpdateCpuTiming(base::TimeDelta task_time) {
- last_cpu_timing_->task_time += task_time;
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::EnsureSendTimer(bool urgent) {
- if (urgent)
- timer_->Stop();
- else if (timer_->IsRunning())
- return;
- int delay_ms;
- if (urgent) {
- // Send as soon as possible, but not synchronously, so that all pending
- // presentation callbacks for the current frame can run first.
- delay_ms = 0;
- } else if (have_sent_ipc_) {
- // This is the typical case.
- delay_ms = buffer_timer_delay_ms_;
- } else {
- // Send the first IPC eagerly to make sure the receiving side knows we're
- // sending metrics as soon as possible.
- delay_ms = kInitialTimerDelayMillis;
- }
- timer_->Start(FROM_HERE, base::Milliseconds(delay_ms),
- base::BindOnce(&PageTimingMetricsSender::SendNow,
- base::Unretained(this)));
- }
- void PageTimingMetricsSender::SendNow() {
- have_sent_ipc_ = true;
- std::vector<mojom::ResourceDataUpdatePtr> resources;
- for (auto* resource : modified_resources_) {
- resources.push_back(resource->GetResourceDataUpdate());
- if (resource->IsFinishedLoading()) {
- page_resource_data_use_.erase(resource->resource_id());
- }
- }
- sender_->SendTiming(last_timing_, metadata_, std::move(new_features_),
- std::move(resources), render_data_, last_cpu_timing_,
- std::move(input_timing_delta_), mobile_friendliness_,
- soft_navigation_count_);
- input_timing_delta_ = mojom::InputTiming::New();
- mobile_friendliness_ = absl::nullopt;
- InitiateUserInteractionTiming();
- new_features_.clear();
- metadata_->main_frame_intersection_rect.reset();
- metadata_->main_frame_viewport_rect.reset();
- last_cpu_timing_->task_time = base::TimeDelta();
- modified_resources_.clear();
- render_data_.new_layout_shifts.clear();
- render_data_.layout_shift_delta = 0;
- render_data_.layout_shift_delta_before_input_or_scroll = 0;
- render_data_.all_layout_block_count_delta = 0;
- render_data_.ng_layout_block_count_delta = 0;
- render_data_.all_layout_call_count_delta = 0;
- render_data_.ng_layout_call_count_delta = 0;
- // As PageTimingMetricsSender is owned by MetricsRenderFrameObserver, which is
- // instantiated for each frame, there's no need to make soft_navigation_count_
- // zero here, as its value only increments through the lifetime of the frame.
- }
- void PageTimingMetricsSender::DidObserveInputDelay(
- base::TimeDelta input_delay) {
- input_timing_delta_->num_input_events++;
- input_timing_delta_->total_input_delay += input_delay;
- input_timing_delta_->total_adjusted_input_delay +=
- base::Milliseconds(std::max(int64_t(0), input_delay.InMilliseconds() -
- kInputDelayAdjustmentMillis));
- EnsureSendTimer();
- }
- void PageTimingMetricsSender::InitiateUserInteractionTiming() {
- input_timing_delta_->max_event_durations =
- mojom::UserInteractionLatencies::NewUserInteractionLatencies({});
- }
- void PageTimingMetricsSender::DidObserveUserInteraction(
- base::TimeDelta max_event_duration,
- blink::UserInteractionType interaction_type) {
- input_timing_delta_->num_interactions++;
- input_timing_delta_->max_event_durations->get_user_interaction_latencies()
- .emplace_back(mojom::UserInteractionLatency::New(
- max_event_duration, UserInteractionTypeForMojom(interaction_type)));
- EnsureSendTimer();
- }
- } // namespace page_load_metrics
|