page_timing_metrics_sender.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. // Copyright 2015 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/page_load_metrics/renderer/page_timing_metrics_sender.h"
  5. #include <utility>
  6. #include "base/bind.h"
  7. #include "base/callback.h"
  8. #include "base/containers/contains.h"
  9. #include "base/feature_list.h"
  10. #include "base/time/time.h"
  11. #include "base/timer/timer.h"
  12. #include "components/page_load_metrics/common/page_load_metrics.mojom.h"
  13. #include "components/page_load_metrics/common/page_load_metrics_util.h"
  14. #include "components/page_load_metrics/renderer/page_timing_sender.h"
  15. #include "services/network/public/cpp/url_loader_completion_status.h"
  16. #include "services/network/public/mojom/url_response_head.mojom.h"
  17. #include "third_party/blink/public/common/features.h"
  18. #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
  19. #include "third_party/blink/public/mojom/use_counter/use_counter_feature.mojom-shared.h"
  20. #include "ui/gfx/geometry/rect.h"
  21. namespace page_load_metrics {
  22. namespace {
  23. const int kInitialTimerDelayMillis = 50;
  24. const int64_t kInputDelayAdjustmentMillis = int64_t(50);
  25. mojom::UserInteractionType UserInteractionTypeForMojom(
  26. blink::UserInteractionType interaction_type) {
  27. switch (interaction_type) {
  28. case blink::UserInteractionType::kKeyboard:
  29. return mojom::UserInteractionType::kKeyboard;
  30. case blink::UserInteractionType::kTapOrClick:
  31. return mojom::UserInteractionType::kTapOrClick;
  32. case blink::UserInteractionType::kDrag:
  33. return mojom::UserInteractionType::kDrag;
  34. }
  35. // mojom::UserInteractionType should have the same interaction types as
  36. // blink::UserInteractionType does.
  37. NOTREACHED();
  38. return mojom::UserInteractionType::kMinValue;
  39. }
  40. } // namespace
  41. PageTimingMetricsSender::PageTimingMetricsSender(
  42. std::unique_ptr<PageTimingSender> sender,
  43. std::unique_ptr<base::OneShotTimer> timer,
  44. mojom::PageLoadTimingPtr initial_timing,
  45. const PageTimingMetadataRecorder::MonotonicTiming& initial_monotonic_timing,
  46. std::unique_ptr<PageResourceDataUse> initial_request)
  47. : sender_(std::move(sender)),
  48. timer_(std::move(timer)),
  49. last_timing_(std::move(initial_timing)),
  50. last_cpu_timing_(mojom::CpuTiming::New()),
  51. input_timing_delta_(mojom::InputTiming::New()),
  52. metadata_(mojom::FrameMetadata::New()),
  53. buffer_timer_delay_ms_(GetBufferTimerDelayMillis(TimerType::kRenderer)),
  54. metadata_recorder_(initial_monotonic_timing) {
  55. InitiateUserInteractionTiming();
  56. const auto resource_id = initial_request->resource_id();
  57. page_resource_data_use_.emplace(
  58. std::piecewise_construct, std::forward_as_tuple(resource_id),
  59. std::forward_as_tuple(std::move(initial_request)));
  60. if (!IsEmpty(*last_timing_)) {
  61. EnsureSendTimer();
  62. }
  63. }
  64. PageTimingMetricsSender::~PageTimingMetricsSender() {
  65. // Make sure we don't have any unsent data. If this assertion fails, then it
  66. // means metrics are somehow coming in between MetricsRenderFrameObserver's
  67. // ReadyToCommitNavigation and DidCommitProvisionalLoad.
  68. DCHECK(!timer_->IsRunning());
  69. }
  70. void PageTimingMetricsSender::DidObserveLoadingBehavior(
  71. blink::LoadingBehaviorFlag behavior) {
  72. if (behavior & metadata_->behavior_flags) {
  73. return;
  74. }
  75. metadata_->behavior_flags |= behavior;
  76. EnsureSendTimer();
  77. }
  78. void PageTimingMetricsSender::DidObserveNewFeatureUsage(
  79. const blink::UseCounterFeature& feature) {
  80. if (feature_tracker_.TestAndSet(feature))
  81. return;
  82. new_features_.push_back(feature);
  83. EnsureSendTimer();
  84. }
  85. void PageTimingMetricsSender::DidObserveSoftNavigation(uint32_t count) {
  86. DCHECK(count > soft_navigation_count_);
  87. soft_navigation_count_ = count;
  88. EnsureSendTimer();
  89. }
  90. void PageTimingMetricsSender::DidObserveLayoutShift(
  91. double score,
  92. bool after_input_or_scroll) {
  93. DCHECK(score > 0);
  94. render_data_.layout_shift_delta += score;
  95. render_data_.new_layout_shifts.push_back(
  96. mojom::LayoutShift::New(base::TimeTicks::Now(), score));
  97. if (!after_input_or_scroll)
  98. render_data_.layout_shift_delta_before_input_or_scroll += score;
  99. EnsureSendTimer();
  100. }
  101. void PageTimingMetricsSender::DidObserveLayoutNg(uint32_t all_block_count,
  102. uint32_t ng_block_count,
  103. uint32_t all_call_count,
  104. uint32_t ng_call_count) {
  105. render_data_.all_layout_block_count_delta += all_block_count;
  106. render_data_.ng_layout_block_count_delta += ng_block_count;
  107. render_data_.all_layout_call_count_delta += all_call_count;
  108. render_data_.ng_layout_call_count_delta += ng_call_count;
  109. EnsureSendTimer();
  110. }
  111. void PageTimingMetricsSender::DidObserveMobileFriendlinessChanged(
  112. const blink::MobileFriendliness& mf) {
  113. mobile_friendliness_ = mf;
  114. EnsureSendTimer();
  115. }
  116. void PageTimingMetricsSender::DidStartResponse(
  117. const url::SchemeHostPort& final_response_url,
  118. int resource_id,
  119. const network::mojom::URLResponseHead& response_head,
  120. network::mojom::RequestDestination request_destination) {
  121. DCHECK(!base::Contains(page_resource_data_use_, resource_id));
  122. auto resource_it = page_resource_data_use_.emplace(
  123. std::piecewise_construct, std::forward_as_tuple(resource_id),
  124. std::forward_as_tuple(std::make_unique<PageResourceDataUse>()));
  125. resource_it.first->second->DidStartResponse(
  126. final_response_url, resource_id, response_head, request_destination);
  127. }
  128. void PageTimingMetricsSender::DidReceiveTransferSizeUpdate(
  129. int resource_id,
  130. int received_data_length) {
  131. // Transfer size updates are called in a throttled manner.
  132. auto resource_it = page_resource_data_use_.find(resource_id);
  133. // It is possible that resources are not in the map, if response headers were
  134. // not received or for failed/cancelled resources.
  135. if (resource_it == page_resource_data_use_.end()) {
  136. return;
  137. }
  138. resource_it->second->DidReceiveTransferSizeUpdate(received_data_length);
  139. modified_resources_.insert(resource_it->second.get());
  140. EnsureSendTimer();
  141. }
  142. void PageTimingMetricsSender::DidCompleteResponse(
  143. int resource_id,
  144. const network::URLLoaderCompletionStatus& status) {
  145. auto resource_it = page_resource_data_use_.find(resource_id);
  146. // It is possible that resources are not in the map, if response headers were
  147. // not received or for failed/cancelled resources. For data reduction proxy
  148. // purposes treat these as having no savings.
  149. if (resource_it == page_resource_data_use_.end()) {
  150. auto new_resource_it = page_resource_data_use_.emplace(
  151. std::piecewise_construct, std::forward_as_tuple(resource_id),
  152. std::forward_as_tuple(std::make_unique<PageResourceDataUse>()));
  153. resource_it = new_resource_it.first;
  154. }
  155. resource_it->second->DidCompleteResponse(status);
  156. EnsureSendTimer();
  157. modified_resources_.insert(resource_it->second.get());
  158. }
  159. void PageTimingMetricsSender::DidCancelResponse(int resource_id) {
  160. auto resource_it = page_resource_data_use_.find(resource_id);
  161. if (resource_it == page_resource_data_use_.end()) {
  162. return;
  163. }
  164. resource_it->second->DidCancelResponse();
  165. }
  166. void PageTimingMetricsSender::DidLoadResourceFromMemoryCache(
  167. const GURL& response_url,
  168. int request_id,
  169. int64_t encoded_body_length,
  170. const std::string& mime_type) {
  171. // In general, we should not observe the same resource being loaded twice in
  172. // the frame. This is possible due to an existing workaround in
  173. // ResourceFetcher::EmulateLoadStartedForInspector(). In this case, ignore
  174. // multiple resources being loaded in the document, as memory cache resources
  175. // are only reported once per context by design in all other cases.
  176. if (base::Contains(page_resource_data_use_, request_id))
  177. return;
  178. auto resource_it = page_resource_data_use_.emplace(
  179. std::piecewise_construct, std::forward_as_tuple(request_id),
  180. std::forward_as_tuple(std::make_unique<PageResourceDataUse>()));
  181. resource_it.first->second->DidLoadFromMemoryCache(
  182. response_url, request_id, encoded_body_length, mime_type);
  183. modified_resources_.insert(resource_it.first->second.get());
  184. }
  185. void PageTimingMetricsSender::OnMainFrameIntersectionChanged(
  186. const gfx::Rect& main_frame_intersection_rect) {
  187. metadata_->main_frame_intersection_rect = main_frame_intersection_rect;
  188. EnsureSendTimer();
  189. }
  190. void PageTimingMetricsSender::OnMainFrameViewportRectangleChanged(
  191. const gfx::Rect& main_frame_viewport_rect) {
  192. metadata_->main_frame_viewport_rect = main_frame_viewport_rect;
  193. EnsureSendTimer();
  194. }
  195. void PageTimingMetricsSender::UpdateResourceMetadata(
  196. int resource_id,
  197. bool reported_as_ad_resource,
  198. bool is_main_frame_resource,
  199. bool completed_before_fcp) {
  200. auto it = page_resource_data_use_.find(resource_id);
  201. if (it == page_resource_data_use_.end())
  202. return;
  203. // This can get called multiple times for a resource, and this flag will only
  204. // be true once.
  205. if (reported_as_ad_resource)
  206. it->second->SetReportedAsAdResource(reported_as_ad_resource);
  207. // This can get called multiple times for a resource, and this flag will only
  208. // be true once.
  209. if (completed_before_fcp)
  210. it->second->SetCompletedBeforeFCP(completed_before_fcp);
  211. it->second->SetIsMainFrameResource(is_main_frame_resource);
  212. }
  213. void PageTimingMetricsSender::SetUpSmoothnessReporting(
  214. base::ReadOnlySharedMemoryRegion shared_memory) {
  215. sender_->SetUpSmoothnessReporting(std::move(shared_memory));
  216. }
  217. void PageTimingMetricsSender::Update(
  218. mojom::PageLoadTimingPtr timing,
  219. const PageTimingMetadataRecorder::MonotonicTiming& monotonic_timing) {
  220. if (last_timing_->Equals(*timing)) {
  221. return;
  222. }
  223. // We want to make sure that each PageTimingMetricsSender is associated
  224. // with a distinct page navigation. Because we reset the object on commit,
  225. // we can trash last_timing_ on a provisional load before SendNow() fires.
  226. if (!last_timing_->navigation_start.is_null() &&
  227. last_timing_->navigation_start != timing->navigation_start) {
  228. return;
  229. }
  230. // We want to force sending the metrics quickly when FCP is reached.
  231. bool send_urgently =
  232. (!last_timing_->paint_timing ||
  233. !last_timing_->paint_timing->first_contentful_paint.has_value()) &&
  234. timing->paint_timing &&
  235. timing->paint_timing->first_contentful_paint.has_value();
  236. last_timing_ = std::move(timing);
  237. metadata_recorder_.UpdateMetadata(monotonic_timing);
  238. EnsureSendTimer(send_urgently);
  239. }
  240. void PageTimingMetricsSender::SendLatest() {
  241. if (!timer_->IsRunning())
  242. return;
  243. timer_->Stop();
  244. SendNow();
  245. }
  246. void PageTimingMetricsSender::UpdateCpuTiming(base::TimeDelta task_time) {
  247. last_cpu_timing_->task_time += task_time;
  248. EnsureSendTimer();
  249. }
  250. void PageTimingMetricsSender::EnsureSendTimer(bool urgent) {
  251. if (urgent)
  252. timer_->Stop();
  253. else if (timer_->IsRunning())
  254. return;
  255. int delay_ms;
  256. if (urgent) {
  257. // Send as soon as possible, but not synchronously, so that all pending
  258. // presentation callbacks for the current frame can run first.
  259. delay_ms = 0;
  260. } else if (have_sent_ipc_) {
  261. // This is the typical case.
  262. delay_ms = buffer_timer_delay_ms_;
  263. } else {
  264. // Send the first IPC eagerly to make sure the receiving side knows we're
  265. // sending metrics as soon as possible.
  266. delay_ms = kInitialTimerDelayMillis;
  267. }
  268. timer_->Start(FROM_HERE, base::Milliseconds(delay_ms),
  269. base::BindOnce(&PageTimingMetricsSender::SendNow,
  270. base::Unretained(this)));
  271. }
  272. void PageTimingMetricsSender::SendNow() {
  273. have_sent_ipc_ = true;
  274. std::vector<mojom::ResourceDataUpdatePtr> resources;
  275. for (auto* resource : modified_resources_) {
  276. resources.push_back(resource->GetResourceDataUpdate());
  277. if (resource->IsFinishedLoading()) {
  278. page_resource_data_use_.erase(resource->resource_id());
  279. }
  280. }
  281. sender_->SendTiming(last_timing_, metadata_, std::move(new_features_),
  282. std::move(resources), render_data_, last_cpu_timing_,
  283. std::move(input_timing_delta_), mobile_friendliness_,
  284. soft_navigation_count_);
  285. input_timing_delta_ = mojom::InputTiming::New();
  286. mobile_friendliness_ = absl::nullopt;
  287. InitiateUserInteractionTiming();
  288. new_features_.clear();
  289. metadata_->main_frame_intersection_rect.reset();
  290. metadata_->main_frame_viewport_rect.reset();
  291. last_cpu_timing_->task_time = base::TimeDelta();
  292. modified_resources_.clear();
  293. render_data_.new_layout_shifts.clear();
  294. render_data_.layout_shift_delta = 0;
  295. render_data_.layout_shift_delta_before_input_or_scroll = 0;
  296. render_data_.all_layout_block_count_delta = 0;
  297. render_data_.ng_layout_block_count_delta = 0;
  298. render_data_.all_layout_call_count_delta = 0;
  299. render_data_.ng_layout_call_count_delta = 0;
  300. // As PageTimingMetricsSender is owned by MetricsRenderFrameObserver, which is
  301. // instantiated for each frame, there's no need to make soft_navigation_count_
  302. // zero here, as its value only increments through the lifetime of the frame.
  303. }
  304. void PageTimingMetricsSender::DidObserveInputDelay(
  305. base::TimeDelta input_delay) {
  306. input_timing_delta_->num_input_events++;
  307. input_timing_delta_->total_input_delay += input_delay;
  308. input_timing_delta_->total_adjusted_input_delay +=
  309. base::Milliseconds(std::max(int64_t(0), input_delay.InMilliseconds() -
  310. kInputDelayAdjustmentMillis));
  311. EnsureSendTimer();
  312. }
  313. void PageTimingMetricsSender::InitiateUserInteractionTiming() {
  314. input_timing_delta_->max_event_durations =
  315. mojom::UserInteractionLatencies::NewUserInteractionLatencies({});
  316. }
  317. void PageTimingMetricsSender::DidObserveUserInteraction(
  318. base::TimeDelta max_event_duration,
  319. blink::UserInteractionType interaction_type) {
  320. input_timing_delta_->num_interactions++;
  321. input_timing_delta_->max_event_durations->get_user_interaction_latencies()
  322. .emplace_back(mojom::UserInteractionLatency::New(
  323. max_event_duration, UserInteractionTypeForMojom(interaction_type)));
  324. EnsureSendTimer();
  325. }
  326. } // namespace page_load_metrics