Bug 1808323 - Add telemetry for the page load time and the time to first paint for sites that supports Early Hints response, r=manuel,necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D165966
This commit is contained in:
Kershaw Chang 2023-05-02 08:08:44 +00:00
parent e936789b18
commit 8f95e3fca6
23 changed files with 132 additions and 23 deletions

View file

@ -2108,6 +2108,7 @@ void Document::AccumulatePageLoadTelemetry(
nsAutoCString dnsKey("Native");
nsAutoCString http3Key;
nsAutoCString http3WithPriorityKey;
nsAutoCString earlyHintKey;
nsCOMPtr<nsIHttpChannelInternal> httpChannel =
do_QueryInterface(GetChannel());
if (httpChannel) {
@ -2147,6 +2148,16 @@ void Document::AccumulatePageLoadTelemetry(
aEventTelemetryDataOut.httpVer = mozilla::Some(major);
}
uint32_t earlyHintType = 0;
Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
if (earlyHintType & LinkStyle::ePRECONNECT) {
earlyHintKey.Append("preconnect_"_ns);
}
if (earlyHintType & LinkStyle::ePRELOAD) {
earlyHintKey.Append("preload_"_ns);
earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
}
}
TimeStamp asyncOpen;
@ -2174,6 +2185,12 @@ void Document::AccumulatePageLoadTelemetry(
navigationStart, firstContentfulComposite);
}
if (!earlyHintKey.IsEmpty()) {
Telemetry::AccumulateTimeDelta(
Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey,
navigationStart, firstContentfulComposite);
}
Telemetry::AccumulateTimeDelta(
Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
firstContentfulComposite);
@ -2212,6 +2229,12 @@ void Document::AccumulatePageLoadTelemetry(
loadEventStart);
}
if (!earlyHintKey.IsEmpty()) {
Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS,
earlyHintKey, navigationStart,
loadEventStart);
}
Telemetry::AccumulateTimeDelta(
Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
loadEventStart);

View file

@ -519,7 +519,7 @@ nsresult FetchDriver::HttpFetch(
RefPtr<PreloaderBase> fetchPreload = FindPreload(uri);
if (fetchPreload) {
fetchPreload->RemoveSelf(mDocument);
fetchPreload->NotifyUsage(PreloaderBase::LoadBackground::Keep);
fetchPreload->NotifyUsage(mDocument, PreloaderBase::LoadBackground::Keep);
rv = fetchPreload->AsyncConsume(this);
if (NS_SUCCEEDED(rv)) {

View file

@ -3609,6 +3609,7 @@ mozilla::ipc::IPCResult ContentChild::RecvCrossProcessRedirect(
if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) {
httpChannel->SetEarlyHints(std::move(aArgs.earlyHints()));
httpChannel->SetEarlyHintLinkType(aArgs.earlyHintLinkType());
}
// This is used to report any errors back to the parent by calling

View file

@ -1370,7 +1370,7 @@ ScriptLoadRequest* ScriptLoader::LookupPreloadRequest(
// This makes sure the pending preload (if exists) for this resource is
// properly marked as used and thus not notified in the console as unused.
request->GetScriptLoadContext()->NotifyUsage();
request->GetScriptLoadContext()->NotifyUsage(mDocument);
// A used preload must no longer be found in the Document's hash table. Any
// <link preload> tag after the <script> tag will start a new request, that
// can be satisfied from a different cache, but not from the preload cache.

View file

@ -2756,7 +2756,7 @@ already_AddRefed<PreloaderBase> XMLHttpRequestMainThread::FindPreload() {
}
preload->RemoveSelf(doc);
preload->NotifyUsage(PreloaderBase::LoadBackground::Keep);
preload->NotifyUsage(doc, PreloaderBase::LoadBackground::Keep);
return preload.forget();
}

View file

@ -2418,7 +2418,7 @@ nsresult imgLoader::LoadImage(
// preload image interacts with list of available images, see
// https://github.com/whatwg/html/issues/4474.
proxy->RemoveSelf(aLoadingDocument);
proxy->NotifyUsage();
proxy->NotifyUsage(aLoadingDocument);
imgRequest* request = proxy->GetOwner();
nsresult rv =

View file

@ -1712,7 +1712,7 @@ void Loader::MaybeNotifyPreloadUsed(SheetLoadData& aData) {
return;
}
preload->NotifyUsage();
preload->NotifyUsage(mDocument);
}
Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(

View file

@ -261,6 +261,7 @@ IPCResult DocumentChannelChild::RecvRedirectToRealChannel(
if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) {
httpChannel->SetEarlyHints(std::move(aArgs.earlyHints()));
httpChannel->SetEarlyHintLinkType(aArgs.earlyHintLinkType());
}
// This is used to report any errors back to the parent by calling

View file

@ -97,7 +97,8 @@ bool DocumentChannelParent::Init(dom::CanonicalBrowsingContext* aContext,
auto promise = self->RedirectToRealChannel(
std::move(aResolveValue.mStreamFilterEndpoints),
aResolveValue.mRedirectFlags, aResolveValue.mLoadFlags,
std::move(aResolveValue.mEarlyHints));
std::move(aResolveValue.mEarlyHints),
aResolveValue.mEarlyHintLinkType);
// We chain the promise the DLL is waiting on to the one returned by
// RedirectToRealChannel. As soon as the promise returned is resolved
// or rejected, so will the DLL's promise.
@ -137,7 +138,7 @@ DocumentChannelParent::RedirectToRealChannel(
nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
aStreamFilterEndpoints,
uint32_t aRedirectFlags, uint32_t aLoadFlags,
nsTArray<EarlyHintConnectArgs>&& aEarlyHints) {
nsTArray<EarlyHintConnectArgs>&& aEarlyHints, uint32_t aEarlyHintLinkType) {
if (!CanSend()) {
return PDocumentChannelParent::RedirectToRealChannelPromise::
CreateAndReject(ResponseRejectReason::ChannelClosed, __func__);
@ -145,8 +146,8 @@ DocumentChannelParent::RedirectToRealChannel(
RedirectToRealChannelArgs args;
mDocumentLoadListener->SerializeRedirectData(
args, false, aRedirectFlags, aLoadFlags,
static_cast<ContentParent*>(Manager()->Manager()),
std::move(aEarlyHints));
static_cast<ContentParent*>(Manager()->Manager()), std::move(aEarlyHints),
aEarlyHintLinkType);
return SendRedirectToRealChannel(args, std::move(aStreamFilterEndpoints));
}

View file

@ -55,7 +55,8 @@ class DocumentChannelParent final
nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
aStreamFilterEndpoints,
uint32_t aRedirectFlags, uint32_t aLoadFlags,
nsTArray<EarlyHintConnectArgs>&& aEarlyHints);
nsTArray<EarlyHintConnectArgs>&& aEarlyHints,
uint32_t aEarlyHintLinkType);
virtual ~DocumentChannelParent();

View file

@ -1457,10 +1457,12 @@ bool DocumentLoadListener::ResumeSuspendedChannel(
void DocumentLoadListener::SerializeRedirectData(
RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
uint32_t aRedirectFlags, uint32_t aLoadFlags, ContentParent* aParent,
nsTArray<EarlyHintConnectArgs>&& aEarlyHints) const {
nsTArray<EarlyHintConnectArgs>&& aEarlyHints,
uint32_t aEarlyHintLinkType) const {
aArgs.uri() = GetChannelCreationURI();
aArgs.loadIdentifier() = mLoadIdentifier;
aArgs.earlyHints() = std::move(aEarlyHints);
aArgs.earlyHintLinkType() = aEarlyHintLinkType;
// I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that
// clears the principal to inherit, which fails tests (probably because this
@ -2096,7 +2098,8 @@ DocumentLoadListener::RedirectToRealChannel(
RedirectToRealChannelArgs args;
SerializeRedirectData(args, /* aIsCrossProcess */ true, aRedirectFlags,
aLoadFlags, cp, std::move(ehArgs));
aLoadFlags, cp, std::move(ehArgs),
mEarlyHintsService.LinkType());
if (mTiming) {
mTiming->Anonymize(args.uri());
args.timing() = std::move(mTiming);
@ -2142,10 +2145,11 @@ DocumentLoadListener::RedirectToRealChannel(
nsTArray<EarlyHintConnectArgs> ehArgs;
mEarlyHintsService.RegisterLinksAndGetConnectArgs(ehArgs);
mOpenPromise->Resolve(OpenPromiseSucceededType(
{std::move(aStreamFilterEndpoints), aRedirectFlags,
aLoadFlags, std::move(ehArgs), promise}),
__func__);
mOpenPromise->Resolve(
OpenPromiseSucceededType({std::move(aStreamFilterEndpoints),
aRedirectFlags, aLoadFlags, std::move(ehArgs),
mEarlyHintsService.LinkType(), promise}),
__func__);
// There is no way we could come back here if the promise had been resolved
// previously. But for clarity and to avoid all doubt, we set this boolean to

View file

@ -111,6 +111,7 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
uint32_t mRedirectFlags;
uint32_t mLoadFlags;
nsTArray<EarlyHintConnectArgs> mEarlyHints;
uint32_t mEarlyHintLinkType;
RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>
mPromise;
};
@ -279,10 +280,11 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
// Serializes all data needed to setup the new replacement channel
// in the content process into the RedirectToRealChannelArgs struct.
void SerializeRedirectData(
RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
uint32_t aRedirectFlags, uint32_t aLoadFlags, dom::ContentParent* aParent,
nsTArray<EarlyHintConnectArgs>&& aEarlyHints) const;
void SerializeRedirectData(RedirectToRealChannelArgs& aArgs,
bool aIsCrossProcess, uint32_t aRedirectFlags,
uint32_t aLoadFlags, dom::ContentParent* aParent,
nsTArray<EarlyHintConnectArgs>&& aEarlyHints,
uint32_t aEarlyHintLinkType) const;
uint64_t GetLoadIdentifier() const { return mLoadIdentifier; }
uint32_t GetLoadType() const { return mLoadStateLoadType; }

View file

@ -493,6 +493,7 @@ struct RedirectToRealChannelArgs {
uint64_t loadIdentifier;
nsCString? originalUriString;
EarlyHintConnectArgs[] earlyHints;
uint32_t earlyHintLinkType;
};
struct TimingStructArgs {

View file

@ -8,6 +8,7 @@
#include "EarlyHintsService.h"
#include "EarlyHintPreconnect.h"
#include "EarlyHintPreloader.h"
#include "mozilla/dom/LinkStyle.h"
#include "mozilla/PreloadHashKey.h"
#include "mozilla/Telemetry.h"
#include "mozilla/glean/GleanMetrics.h"
@ -88,8 +89,10 @@ void EarlyHintsService::EarlyHint(const nsACString& aLinkHeader,
for (auto& linkHeader : linkHeaders) {
CollectLinkTypeTelemetry(linkHeader.mRel);
if (linkHeader.mRel.LowerCaseEqualsLiteral("preconnect")) {
mLinkType |= dom::LinkStyle::ePRECONNECT;
EarlyHintPreconnect::MaybePreconnect(linkHeader, aBaseURI, principal);
} else if (linkHeader.mRel.LowerCaseEqualsLiteral("preload")) {
mLinkType |= dom::LinkStyle::ePRELOAD;
EarlyHintPreloader::MaybeCreateAndInsertPreload(
mOngoingEarlyHints, linkHeader, aBaseURI, principal,
cookieJarSettings, aReferrerPolicy, aCSPHeader);

View file

@ -35,6 +35,8 @@ class EarlyHintsService {
void RegisterLinksAndGetConnectArgs(
nsTArray<EarlyHintConnectArgs>& aOutLinks);
uint32_t LinkType() const { return mLinkType; }
private:
void CollectTelemetry(Maybe<uint32_t> aResponseStatus);
void CollectLinkTypeTelemetry(const nsAString& aRel);
@ -43,6 +45,7 @@ class EarlyHintsService {
uint32_t mEarlyHintsCount{0};
RefPtr<OngoingEarlyHints> mOngoingEarlyHints;
uint32_t mLinkType = 0;
};
} // namespace mozilla::net

View file

@ -6129,5 +6129,18 @@ void HttpBaseChannel::LogORBError(const nsAString& aReason) {
nsContentUtils::eNECKO_PROPERTIES,
"ResourceBlockedORB", params);
}
NS_IMETHODIMP HttpBaseChannel::SetEarlyHintLinkType(
uint32_t aEarlyHintLinkType) {
mEarlyHintLinkType = aEarlyHintLinkType;
return NS_OK;
}
NS_IMETHODIMP HttpBaseChannel::GetEarlyHintLinkType(
uint32_t* aEarlyHintLinkType) {
*aEarlyHintLinkType = mEarlyHintLinkType;
return NS_OK;
}
} // namespace net
} // namespace mozilla

View file

@ -352,6 +352,9 @@ class HttpBaseChannel : public nsHashPropertyBag,
NS_IMETHOD SetEarlyHintPreloaderId(uint64_t aEarlyHintPreloaderId) override;
NS_IMETHOD GetEarlyHintPreloaderId(uint64_t* aEarlyHintPreloaderId) override;
NS_IMETHOD SetEarlyHintLinkType(uint32_t aEarlyHintLinkType) override;
NS_IMETHOD GetEarlyHintLinkType(uint32_t* aEarlyHintLinkType) override;
NS_IMETHOD SetClassicScriptHintCharset(
const nsAString& aClassicScriptHintCharset) override;
NS_IMETHOD GetClassicScriptHintCharset(
@ -822,6 +825,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
// EarlyHintRegistrar id to connect back to the preload. Set on preload
// channels started from the above list
uint64_t mEarlyHintPreloaderId = 0;
uint32_t mEarlyHintLinkType = 0;
nsString mClassicScriptHintCharset;
nsString mDocumentCharacterSet;

View file

@ -506,4 +506,10 @@ interface nsIHttpChannelInternal : nsISupports
*/
[must_use] void setWebTransportSessionEventListener(
in WebTransportSessionEventListener aListener);
/**
* This attribute indicates the type of Link header in the received
* 103 response.
*/
[must_use] attribute unsigned long earlyHintLinkType;
};

View file

@ -17311,6 +17311,32 @@
"kind": "enumerated",
"description": "The usage of a early hint preload request"
},
"EH_PERF_PAGE_LOAD_TIME_MS": {
"record_in_processes": ["content"],
"products": ["firefox"],
"alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"],
"expires_in_version": "never",
"releaseChannelCollection": "opt-out",
"kind": "exponential",
"high": 50000,
"n_buckets": 100,
"bug_numbers": [1808323],
"keyed": true,
"description": "Time in milliseconds from navigationStart to loadEventStart for the foreground http or https root content document. This is collected only on page load where the main document uses or suppports Early Hints preload."
},
"EH_PERF_FIRST_CONTENTFUL_PAINT_MS": {
"record_in_processes": ["content"],
"products": ["firefox"],
"alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"],
"expires_in_version": "never",
"releaseChannelCollection": "opt-out",
"kind": "exponential",
"high": 50000,
"n_buckets": 100,
"bug_numbers": [1808323],
"keyed": true,
"description": "The time between navigationStart and the first contentful paint of a foreground http or https root content document, in milliseconds. The contentful paint timestamp is taken during display list building and does not include rasterization or compositing of that paint. This is collected only on page load where the main document uses or supports Early Hints preload."
},
"FX_PICTURE_IN_PICTURE_WINDOW_OPEN_DURATION": {
"record_in_processes": ["main"],
"products": ["firefox"],

View file

@ -188,7 +188,12 @@ PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce(
PreloadFetch(uri, aCORS, aReferrerPolicy, aEarlyHintPreloaderId);
}
return {LookupPreload(preloadKey), false};
RefPtr<PreloaderBase> preload = LookupPreload(preloadKey);
if (preload && aEarlyHintPreloaderId) {
preload->SetForEarlyHints();
}
return {preload, false};
}
void PreloadService::PreloadScript(nsIURI* aURI, const nsAString& aType,

View file

@ -98,6 +98,9 @@ class PreloadService {
static void NotifyNodeEvent(nsINode* aNode, bool aSuccess);
void SetEarlyHintUsed() { mEarlyHintUsed = true; }
bool GetEarlyHintUsed() const { return mEarlyHintUsed; }
private:
dom::ReferrerPolicy PreloadReferrerPolicy(const nsAString& aReferrerPolicy);
nsIURI* BaseURIForPreload();
@ -123,6 +126,8 @@ class PreloadService {
// Set by `nsHtml5TreeOpExecutor::SetSpeculationBase`.
nsCOMPtr<nsIURI> mSpeculationBaseURI;
bool mEarlyHintUsed = false;
};
} // namespace mozilla

View file

@ -157,7 +157,8 @@ void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey, nsIChannel* aChannel,
mChannel->SetNotificationCallbacks(sink);
}
void PreloaderBase::NotifyUsage(LoadBackground aLoadBackground) {
void PreloaderBase::NotifyUsage(dom::Document* aDocument,
LoadBackground aLoadBackground) {
if (!mIsUsed && mChannel && aLoadBackground == LoadBackground::Drop) {
nsLoadFlags loadFlags;
mChannel->GetLoadFlags(&loadFlags);
@ -186,6 +187,9 @@ void PreloaderBase::NotifyUsage(LoadBackground aLoadBackground) {
mIsUsed = true;
ReportUsageTelemetry();
CancelUsageTimer();
if (mIsEarlyHintsPreload) {
aDocument->Preloads().SetEarlyHintUsed();
}
}
void PreloaderBase::RemoveSelf(dom::Document* aDocument) {

View file

@ -72,7 +72,8 @@ class PreloaderBase : public SupportsWeakPtr, public nsISupports {
// progress, will not be removed the LOAD_BACKGROUND flag, for instance XHR is
// the user here.
enum class LoadBackground { Keep, Drop };
void NotifyUsage(LoadBackground aLoadBackground = LoadBackground::Drop);
void NotifyUsage(dom::Document* aDocument,
LoadBackground aLoadBackground = LoadBackground::Drop);
// Whether this preloader has been used for a regular/actual load or not.
bool IsUsed() const { return mIsUsed; }
@ -122,6 +123,8 @@ class PreloaderBase : public SupportsWeakPtr, public nsISupports {
const nsTArray<RedirectRecord>& Redirects() { return mRedirectRecords; }
void SetForEarlyHints() { mIsEarlyHintsPreload = true; }
protected:
virtual ~PreloaderBase();
@ -183,6 +186,9 @@ class PreloaderBase : public SupportsWeakPtr, public nsISupports {
// True after we have reported the usage telemetry. Prevent duplicates.
bool mUsageTelementryReported = false;
// True when this is used to Early Hints preload.
bool mIsEarlyHintsPreload = false;
// Emplaced when the data delivery has finished, in NotifyStop, holds the
// result of the load.
Maybe<nsresult> mOnStopStatus;