blob: eab5b91c4a54bfd081aa7f0c0452fe5e5457c294 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/loader/keep_alive_request_tracker.h"
#include <stdint.h>
#include <string>
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "components/page_load_metrics/browser/features.h"
#include "components/page_load_metrics/browser/page_load_metrics_util.h"
#include "net/http/http_response_headers.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/attribution.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace {
// The maximum number of redirects to be recorded without bucketing.
// This is unlikely to reach as the maximum is 20 per spec
// https://fetch.spec.whatwg.org/#http-redirect-fetch
constexpr size_t kMaxNonBucketedNumRedirects = 20;
ChromeKeepAliveRequestTracker::RequestType ComputeRequestType(
const network::ResourceRequest& request) {
switch (request.attribution_reporting_eligibility) {
case network::mojom::AttributionReportingEligibility::kUnset:
case network::mojom::AttributionReportingEligibility::kEmpty:
break;
case network::mojom::AttributionReportingEligibility::kEventSource:
case network::mojom::AttributionReportingEligibility::kNavigationSource:
case network::mojom::AttributionReportingEligibility::kTrigger:
case network::mojom::AttributionReportingEligibility::kEventSourceOrTrigger:
return ChromeKeepAliveRequestTracker::RequestType::kAttribution;
}
if (request.is_fetch_later_api) {
return ChromeKeepAliveRequestTracker::RequestType::kFetchLater;
}
return ChromeKeepAliveRequestTracker::RequestType::kFetch;
}
} // namespace
// static
std::unique_ptr<ChromeKeepAliveRequestTracker>
ChromeKeepAliveRequestTracker::MaybeCreateKeepAliveRequestTracker(
const network::ResourceRequest& request,
std::optional<ukm::SourceId> ukm_source_id,
IsContextDetachedCallback is_context_detached_callback) {
if (!request.keepalive || !request.keepalive_token.has_value()) {
return nullptr;
}
if (!ukm_source_id.has_value()) {
return nullptr;
}
if (!base::FeatureList::IsEnabled(
page_load_metrics::features::kBeaconLeakageLogging)) {
return nullptr;
}
auto category_id = page_load_metrics::GetCategoryIdFromUrl(request.url);
if (!category_id.has_value()) {
return nullptr;
}
ChromeKeepAliveRequestTracker::RequestType request_type =
ComputeRequestType(request);
return base::WrapUnique(new ChromeKeepAliveRequestTracker(
request_type, *category_id, *ukm_source_id,
std::move(is_context_detached_callback), *request.keepalive_token));
}
ChromeKeepAliveRequestTracker::ChromeKeepAliveRequestTracker(
RequestType request_type,
uint32_t request_category,
ukm::SourceId ukm_source_id,
IsContextDetachedCallback is_context_detached_callback,
base::UnguessableToken keepalive_token)
: KeepAliveRequestTracker(request_type),
created_time_(base::TimeTicks::Now()),
ukm_builder_(ukm_source_id),
is_context_detached_callback_(std::move(is_context_detached_callback)) {
ukm_builder_.SetId_Low(keepalive_token.GetLowForSerialization());
ukm_builder_.SetId_High(keepalive_token.GetHighForSerialization());
ukm_builder_.SetRequestType(static_cast<int64_t>(request_type));
ukm_builder_.SetCategory(request_category);
}
ChromeKeepAliveRequestTracker::~ChromeKeepAliveRequestTracker() {
// TODO(crbug.com/382527001): Logging in dtor might not work on Android.
LogUkmEvent();
}
void ChromeKeepAliveRequestTracker::AddStageMetrics(const RequestStage& stage) {
base::TimeDelta relative_to_created_time =
base::TimeTicks::Now() - created_time_;
switch (stage.type) {
case RequestStageType::kLoaderCreated:
// kLoaderCreated is the initial stage set in ctor.
NOTREACHED();
case RequestStageType::kRequestStarted:
ukm_builder_.SetTimeDelta_RequestStarted(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kFirstRedirectReceived:
IncreaseNumRedirects();
ukm_builder_.SetTimeDelta_FirstRedirectReceived(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kSecondRedirectReceived:
IncreaseNumRedirects();
ukm_builder_.SetTimeDelta_SecondRedirectReceived(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kThirdOrLaterRedirectReceived:
IncreaseNumRedirects();
ukm_builder_.SetTimeDelta_ThirdOrLaterRedirectReceived(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kResponseReceived:
ukm_builder_.SetTimeDelta_ResponseReceived(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kRequestFailed:
ukm_builder_.SetTimeDelta_RequestFailed(
relative_to_created_time.InMilliseconds());
ukm_builder_.SetCompletionStatus_ErrorCode(stage.status->error_code);
ukm_builder_.SetCompletionStatus_ExtendedErrorCode(
stage.status->extended_error_code);
break;
case RequestStageType::kLoaderDisconnectedFromRenderer:
ukm_builder_.SetTimeDelta_LoaderDisconnectedFromRenderer(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kRequestCancelledByRenderer:
ukm_builder_.SetTimeDelta_RequestCancelledByRenderer(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kRequestCancelledAfterTimeLimit:
ukm_builder_.SetTimeDelta_RequestCancelledAfterTimeLimit(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kBrowserShutdown:
ukm_builder_.SetTimeDelta_BrowserShutdown(
relative_to_created_time.InMilliseconds());
break;
case RequestStageType::kLoaderCompleted:
ukm_builder_.SetTimeDelta_LoaderCompleted(
relative_to_created_time.InMilliseconds());
ukm_builder_.SetCompletionStatus_ErrorCode(stage.status->error_code);
ukm_builder_.SetCompletionStatus_ExtendedErrorCode(
stage.status->extended_error_code);
break;
}
}
void ChromeKeepAliveRequestTracker::LogUkmEvent() {
ukm_builder_.SetEndStage(static_cast<int64_t>(GetCurrentStage().type));
if (auto previous_stage = GetPreviousStage(); previous_stage.has_value()) {
ukm_builder_.SetPreviousStage(static_cast<int64_t>(previous_stage->type));
}
uint32_t num_redirects = GetNumRedirects();
ukm_builder_.SetNumRedirects(
num_redirects <= kMaxNonBucketedNumRedirects
? num_redirects
: ukm::GetExponentialBucketMinForCounts1000(num_redirects));
ukm_builder_.SetIsContextDetached(is_context_detached_callback_.Run());
ukm_builder_.SetTimeDelta_EventLogged(
(base::TimeTicks::Now() - created_time_).InMilliseconds());
auto* ukm_recorder = ukm::UkmRecorder::Get();
ukm_builder_.Record(ukm_recorder->Get());
}