Telemetry for stalled webpage loads
authorkrollin@apple.com <krollin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 15 Mar 2018 22:27:25 +0000 (22:27 +0000)
committerkrollin@apple.com <krollin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 15 Mar 2018 22:27:25 +0000 (22:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=183221
<rdar://problem/36549013>

Reviewed by Chris Dumez.

Add telemetry for page loads, tracking the pages that succeed, fail,
or are canceled. This information will be used to track the overall
health of our page loading as time goes on.

Source/WebCore:

No new tests -- no new/changed user-level functionality.

* page/DiagnosticLoggingKeys.cpp:
(WebCore::DiagnosticLoggingKeys::telemetryPageLoadKey):
(WebCore::DiagnosticLoggingKeys::timedOutKey):
(WebCore::DiagnosticLoggingKeys::canceledLessThan2SecondsKey):
(WebCore::DiagnosticLoggingKeys::canceledLessThan5SecondsKey):
(WebCore::DiagnosticLoggingKeys::canceledLessThan20SecondsKey):
(WebCore::DiagnosticLoggingKeys::canceledMoreThan20SecondsKey):
(WebCore::DiagnosticLoggingKeys::failedLessThan2SecondsKey):
(WebCore::DiagnosticLoggingKeys::failedLessThan5SecondsKey):
(WebCore::DiagnosticLoggingKeys::failedLessThan20SecondsKey):
(WebCore::DiagnosticLoggingKeys::failedMoreThan20SecondsKey):
(WebCore::DiagnosticLoggingKeys::occurredKey):
(WebCore::DiagnosticLoggingKeys::succeededLessThan2SecondsKey):
(WebCore::DiagnosticLoggingKeys::succeededLessThan5SecondsKey):
(WebCore::DiagnosticLoggingKeys::succeededLessThan20SecondsKey):
(WebCore::DiagnosticLoggingKeys::succeededMoreThan20SecondsKey):
* page/DiagnosticLoggingKeys.h:
* platform/network/cf/ResourceError.h:
(WebCore::ResourceError::ResourceError):
* platform/network/mac/ResourceErrorMac.mm:
(WebCore::ResourceError::ResourceError):
(WebCore::ResourceError::getNSURLErrorDomain const):
(WebCore::ResourceError::getCFErrorDomainCFNetwork const):
(WebCore::ResourceError::mapPlatformError):

Source/WebKit:

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::close):
(WebKit::WebPageProxy::didStartProvisionalLoadForFrame):
(WebKit::WebPageProxy::didFailProvisionalLoadForFrame):
(WebKit::WebPageProxy::didFinishLoadForFrame):
(WebKit::WebPageProxy::didFailLoadForFrame):
(WebKit::WebPageProxy::reportPageLoadResult):
* UIProcess/WebPageProxy.h:
(WebKit::WebPageProxy::reportPageLoadResult):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@229643 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/page/DiagnosticLoggingKeys.cpp
Source/WebCore/page/DiagnosticLoggingKeys.h
Source/WebCore/platform/network/cf/ResourceError.h
Source/WebCore/platform/network/mac/ResourceErrorMac.mm
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h

index f63d4ed..74f111b 100644 (file)
@@ -1,3 +1,42 @@
+2018-03-15  Keith Rollin  <krollin@apple.com>
+
+        Telemetry for stalled webpage loads
+        https://bugs.webkit.org/show_bug.cgi?id=183221
+        <rdar://problem/36549013>
+
+        Reviewed by Chris Dumez.
+
+        Add telemetry for page loads, tracking the pages that succeed, fail,
+        or are canceled. This information will be used to track the overall
+        health of our page loading as time goes on.
+
+        No new tests -- no new/changed user-level functionality.
+
+        * page/DiagnosticLoggingKeys.cpp:
+        (WebCore::DiagnosticLoggingKeys::telemetryPageLoadKey):
+        (WebCore::DiagnosticLoggingKeys::timedOutKey):
+        (WebCore::DiagnosticLoggingKeys::canceledLessThan2SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::canceledLessThan5SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::canceledLessThan20SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::canceledMoreThan20SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::failedLessThan2SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::failedLessThan5SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::failedLessThan20SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::failedMoreThan20SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::occurredKey):
+        (WebCore::DiagnosticLoggingKeys::succeededLessThan2SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::succeededLessThan5SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::succeededLessThan20SecondsKey):
+        (WebCore::DiagnosticLoggingKeys::succeededMoreThan20SecondsKey):
+        * page/DiagnosticLoggingKeys.h:
+        * platform/network/cf/ResourceError.h:
+        (WebCore::ResourceError::ResourceError):
+        * platform/network/mac/ResourceErrorMac.mm:
+        (WebCore::ResourceError::ResourceError):
+        (WebCore::ResourceError::getNSURLErrorDomain const):
+        (WebCore::ResourceError::getCFErrorDomainCFNetwork const):
+        (WebCore::ResourceError::mapPlatformError):
+
 2018-03-15  Youenn Fablet  <youenn@apple.com>
 
         ActiveDOMObject should assert that they are destroyed in the thread they are created
index 39a4846..0e793ff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2012-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -565,6 +565,81 @@ String DiagnosticLoggingKeys::synchronousMessageFailedKey()
     return ASCIILiteral("synchronousMessageFailed");
 }
 
+String DiagnosticLoggingKeys::telemetryPageLoadKey()
+{
+    return ASCIILiteral("telemetryPageLoad");
+}
+
+String DiagnosticLoggingKeys::timedOutKey()
+{
+    return ASCIILiteral("timedOut");
+}
+
+String DiagnosticLoggingKeys::canceledLessThan2SecondsKey()
+{
+    return ASCIILiteral("canceledLessThan2Seconds");
+}
+
+String DiagnosticLoggingKeys::canceledLessThan5SecondsKey()
+{
+    return ASCIILiteral("canceledLessThan5Seconds");
+}
+
+String DiagnosticLoggingKeys::canceledLessThan20SecondsKey()
+{
+    return ASCIILiteral("canceledLessThan20Seconds");
+}
+
+String DiagnosticLoggingKeys::canceledMoreThan20SecondsKey()
+{
+    return ASCIILiteral("canceledMoreThan20Seconds");
+}
+
+String DiagnosticLoggingKeys::failedLessThan2SecondsKey()
+{
+    return ASCIILiteral("failedLessThan2Seconds");
+}
+
+String DiagnosticLoggingKeys::failedLessThan5SecondsKey()
+{
+    return ASCIILiteral("failedLessThan5Seconds");
+}
+
+String DiagnosticLoggingKeys::failedLessThan20SecondsKey()
+{
+    return ASCIILiteral("failedLessThan20Seconds");
+}
+
+String DiagnosticLoggingKeys::failedMoreThan20SecondsKey()
+{
+    return ASCIILiteral("failedMoreThan20Seconds");
+}
+
+String DiagnosticLoggingKeys::occurredKey()
+{
+    return ASCIILiteral("occurred");
+}
+
+String DiagnosticLoggingKeys::succeededLessThan2SecondsKey()
+{
+    return ASCIILiteral("succeededLessThan2Seconds");
+}
+
+String DiagnosticLoggingKeys::succeededLessThan5SecondsKey()
+{
+    return ASCIILiteral("succeededLessThan5Seconds");
+}
+
+String DiagnosticLoggingKeys::succeededLessThan20SecondsKey()
+{
+    return ASCIILiteral("succeededLessThan20Seconds");
+}
+
+String DiagnosticLoggingKeys::succeededMoreThan20SecondsKey()
+{
+    return ASCIILiteral("succeededMoreThan20Seconds");
+}
+
 String DiagnosticLoggingKeys::uncacheableStatusCodeKey()
 {
     return ASCIILiteral("uncacheableStatusCode");
index 41dc61f..fd944f0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2012-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -43,6 +43,10 @@ public:
     static String cachedResourceRevalidationKey();
     static String cachedResourceRevalidationReasonKey();
     static String canCacheKey();
+    WEBCORE_EXPORT static String canceledLessThan2SecondsKey();
+    WEBCORE_EXPORT static String canceledLessThan5SecondsKey();
+    WEBCORE_EXPORT static String canceledLessThan20SecondsKey();
+    WEBCORE_EXPORT static String canceledMoreThan20SecondsKey();
     static String cannotSuspendActiveDOMObjectsKey();
     WEBCORE_EXPORT static String cpuUsageKey();
     WEBCORE_EXPORT static String createSharedBufferFailedKey();
@@ -64,6 +68,10 @@ public:
     WEBCORE_EXPORT static String entryRightlyNotWarmedUpKey();
     WEBCORE_EXPORT static String entryWronglyNotWarmedUpKey();
     static String expiredKey();
+    WEBCORE_EXPORT static String failedLessThan2SecondsKey();
+    WEBCORE_EXPORT static String failedLessThan5SecondsKey();
+    WEBCORE_EXPORT static String failedLessThan20SecondsKey();
+    WEBCORE_EXPORT static String failedMoreThan20SecondsKey();
     static String fontKey();
     static String hasPluginsKey();
     static String httpsNoStoreKey();
@@ -105,6 +113,7 @@ public:
     WEBCORE_EXPORT static String nonVisibleStateKey();
     WEBCORE_EXPORT static String notHTTPFamilyKey();
     static String notInMemoryCacheKey();
+    WEBCORE_EXPORT static String occurredKey();
     WEBCORE_EXPORT static String otherKey();
     static String pageCacheKey();
     static String pageCacheFailureKey();
@@ -142,10 +151,16 @@ public:
     static String serviceWorkerKey();
     WEBCORE_EXPORT static String streamingMedia();
     static String styleSheetKey();
+    WEBCORE_EXPORT static String succeededLessThan2SecondsKey();
+    WEBCORE_EXPORT static String succeededLessThan5SecondsKey();
+    WEBCORE_EXPORT static String succeededLessThan20SecondsKey();
+    WEBCORE_EXPORT static String succeededMoreThan20SecondsKey();
     WEBCORE_EXPORT static String successfulSpeculativeWarmupWithRevalidationKey();
     WEBCORE_EXPORT static String successfulSpeculativeWarmupWithoutRevalidationKey();
     static String svgDocumentKey();
     WEBCORE_EXPORT static String synchronousMessageFailedKey();
+    WEBCORE_EXPORT static String telemetryPageLoadKey();
+    WEBCORE_EXPORT static String timedOutKey();
     WEBCORE_EXPORT static String uncacheableStatusCodeKey();
     static String underMemoryPressureKey();
     WEBCORE_EXPORT static String unknownEntryRequestKey();
index b1b4846..40701ac 100644 (file)
@@ -54,6 +54,10 @@ public:
         : ResourceErrorBase(domain, errorCode, failingURL, localizedDescription, type)
         , m_dataIsUpToDate(true)
     {
+#if PLATFORM(COCOA)
+        ASSERT(domain != getNSURLErrorDomain());
+        ASSERT(domain != getCFErrorDomainCFNetwork());
+#endif
     }
 
     WEBCORE_EXPORT ResourceError(CFErrorRef error);
@@ -81,6 +85,11 @@ public:
 private:
     friend class ResourceErrorBase;
 
+#if PLATFORM(COCOA)
+    WEBCORE_EXPORT const String& getNSURLErrorDomain() const;
+    WEBCORE_EXPORT const String& getCFErrorDomainCFNetwork() const;
+    WEBCORE_EXPORT void mapPlatformError();
+#endif
     void platformLazyInit();
 
     void doPlatformIsolatedCopy(const ResourceError&);
index 709b699..a27a9b1 100644 (file)
@@ -30,6 +30,8 @@
 #import <CoreFoundation/CFError.h>
 #import <Foundation/Foundation.h>
 #import <wtf/BlockObjCExceptions.h>
+#import <wtf/NeverDestroyed.h>
+#import <wtf/text/WTFString.h>
 
 @interface NSError (WebExtras)
 - (NSString *)_web_localizedDescription;
@@ -112,8 +114,7 @@ ResourceError::ResourceError(NSError *nsError)
     , m_dataIsUpToDate(false)
     , m_platformError(nsError)
 {
-    if (nsError)
-        setType(([m_platformError.get() code] == NSURLErrorTimedOut) ? Type::Timeout : Type::General);
+    mapPlatformError();
 }
 
 ResourceError::ResourceError(CFErrorRef cfError)
@@ -121,6 +122,35 @@ ResourceError::ResourceError(CFErrorRef cfError)
 {
 }
 
+const String& ResourceError::getNSURLErrorDomain() const
+{
+    static const NeverDestroyed<String> errorDomain(NSURLErrorDomain);
+    return errorDomain.get();
+}
+
+const String& ResourceError::getCFErrorDomainCFNetwork() const
+{
+    static const NeverDestroyed<String> errorDomain(kCFErrorDomainCFNetwork);
+    return errorDomain.get();
+}
+
+void ResourceError::mapPlatformError()
+{
+    static_assert(NSURLErrorTimedOut == kCFURLErrorTimedOut, "NSURLErrorTimedOut needs to equal kCFURLErrorTimedOut");
+    static_assert(NSURLErrorCancelled == kCFURLErrorCancelled, "NSURLErrorCancelled needs to equal kCFURLErrorCancelled");
+
+    if (!m_platformError)
+        return;
+
+    auto domain = [m_platformError.get() domain];
+    auto errorCode = [m_platformError.get() code];
+
+    if ([domain isEqualToString:NSURLErrorDomain] || [domain isEqualToString:(__bridge NSString *)kCFErrorDomainCFNetwork])
+        setType((errorCode == NSURLErrorTimedOut) ? Type::Timeout : (errorCode == NSURLErrorCancelled) ? Type::Cancellation : Type::General);
+    else
+        setType(Type::General);
+}
+
 void ResourceError::platformLazyInit()
 {
     if (m_dataIsUpToDate)
index 42dd920..8bcc20f 100644 (file)
@@ -1,3 +1,25 @@
+2018-03-15  Keith Rollin  <krollin@apple.com>
+
+        Telemetry for stalled webpage loads
+        https://bugs.webkit.org/show_bug.cgi?id=183221
+        <rdar://problem/36549013>
+
+        Reviewed by Chris Dumez.
+
+        Add telemetry for page loads, tracking the pages that succeed, fail,
+        or are canceled. This information will be used to track the overall
+        health of our page loading as time goes on.
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::close):
+        (WebKit::WebPageProxy::didStartProvisionalLoadForFrame):
+        (WebKit::WebPageProxy::didFailProvisionalLoadForFrame):
+        (WebKit::WebPageProxy::didFinishLoadForFrame):
+        (WebKit::WebPageProxy::didFailLoadForFrame):
+        (WebKit::WebPageProxy::reportPageLoadResult):
+        * UIProcess/WebPageProxy.h:
+        (WebKit::WebPageProxy::reportPageLoadResult):
+
 2018-03-15  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [iOS WK2] Hit-testing fails when specifying a large top content inset
index 51dd3cb..197b466 100644 (file)
@@ -776,6 +776,9 @@ void WebPageProxy::close()
 
     m_isClosed = true;
 
+    if (m_pageLoadStart)
+        reportPageLoadResult(ResourceError { ResourceError::Type::Cancellation });
+
     if (m_activePopupMenu)
         m_activePopupMenu->cancelTracking();
 
@@ -3257,6 +3260,9 @@ void WebPageProxy::didStartProvisionalLoadForFrame(uint64_t frameID, uint64_t na
         navigation = &navigationState().navigation(navigationID);
 
     if (frame->isMainFrame()) {
+        if (m_pageLoadStart)
+            reportPageLoadResult(ResourceError { ResourceError::Type::Cancellation });
+        m_pageLoadStart = MonotonicTime::now();
         m_pageLoadState.didStartProvisionalLoad(transaction, url, unreachableURL);
         m_pageClient.didStartProvisionalLoadForMainFrame();
         hideValidationMessage();
@@ -3368,6 +3374,7 @@ void WebPageProxy::didFailProvisionalLoadForFrame(uint64_t frameID, const Securi
     auto transaction = m_pageLoadState.transaction();
 
     if (frame->isMainFrame()) {
+        reportPageLoadResult(error);
         m_pageLoadState.didFailProvisionalLoad(transaction);
         m_pageClient.didFailProvisionalLoadForMainFrame();
     }
@@ -3537,8 +3544,10 @@ void WebPageProxy::didFinishLoadForFrame(uint64_t frameID, uint64_t navigationID
     } else
         m_loaderClient->didFinishLoadForFrame(*this, *frame, navigation.get(), m_process->transformHandlesToObjects(userData.object()).get());
 
-    if (isMainFrame)
+    if (isMainFrame) {
+        reportPageLoadResult();
         m_pageClient.didFinishLoadForMainFrame();
+    }
 
     m_isLoadingAlternateHTMLStringForFailingProvisionalLoad = false;
 }
@@ -3578,8 +3587,10 @@ void WebPageProxy::didFailLoadForFrame(uint64_t frameID, uint64_t navigationID,
     } else
         m_loaderClient->didFailLoadWithErrorForFrame(*this, *frame, navigation.get(), error, m_process->transformHandlesToObjects(userData.object()).get());
 
-    if (isMainFrame)
+    if (isMainFrame) {
+        reportPageLoadResult(error);
         m_pageClient.didFailLoadForMainFrame();
+    }
 }
 
 void WebPageProxy::didSameDocumentNavigationForFrame(uint64_t frameID, uint64_t navigationID, uint32_t opaqueSameDocumentNavigationType, WebCore::URL&& url, const UserData& userData)
@@ -7373,4 +7384,61 @@ void WebPageProxy::getApplicationManifest(Function<void(const std::optional<WebC
 }
 #endif
 
+namespace {
+enum class CompletionCondition {
+    Cancellation,
+    Error,
+    Success,
+    Timeout,
+};
+struct MessageType {
+    CompletionCondition condition;
+    Seconds seconds;
+    String message;
+};
+}
+
+void WebPageProxy::reportPageLoadResult(const ResourceError& error)
+{
+    static const NeverDestroyed<Vector<MessageType>> messages(std::initializer_list<MessageType> {
+        { CompletionCondition::Cancellation, 2_s, DiagnosticLoggingKeys::canceledLessThan2SecondsKey() },
+        { CompletionCondition::Cancellation, 5_s, DiagnosticLoggingKeys::canceledLessThan5SecondsKey() },
+        { CompletionCondition::Cancellation, 20_s, DiagnosticLoggingKeys::canceledLessThan20SecondsKey() },
+        { CompletionCondition::Cancellation, Seconds::infinity(), DiagnosticLoggingKeys::canceledMoreThan20SecondsKey() },
+
+        { CompletionCondition::Error, 2_s, DiagnosticLoggingKeys::failedLessThan2SecondsKey() },
+        { CompletionCondition::Error, 5_s, DiagnosticLoggingKeys::failedLessThan5SecondsKey() },
+        { CompletionCondition::Error, 20_s, DiagnosticLoggingKeys::failedLessThan20SecondsKey() },
+        { CompletionCondition::Error, Seconds::infinity(), DiagnosticLoggingKeys::failedMoreThan20SecondsKey() },
+
+        { CompletionCondition::Success, 2_s, DiagnosticLoggingKeys::succeededLessThan2SecondsKey() },
+        { CompletionCondition::Success, 5_s, DiagnosticLoggingKeys::succeededLessThan5SecondsKey() },
+        { CompletionCondition::Success, 20_s, DiagnosticLoggingKeys::succeededLessThan20SecondsKey() },
+        { CompletionCondition::Success, Seconds::infinity(), DiagnosticLoggingKeys::succeededMoreThan20SecondsKey() },
+
+        { CompletionCondition::Timeout, Seconds::infinity(), DiagnosticLoggingKeys::timedOutKey() }
+        });
+
+    ASSERT(m_pageLoadStart);
+
+    auto pageLoadTime = MonotonicTime::now() - *m_pageLoadStart;
+    m_pageLoadStart = std::nullopt;
+
+    CompletionCondition condition { CompletionCondition::Success };
+    if (error.isCancellation())
+        condition = CompletionCondition::Cancellation;
+    else if (error.isTimeout())
+        condition = CompletionCondition::Timeout;
+    else if (!error.isNull() || error.errorCode())
+        condition = CompletionCondition::Error;
+
+    for (auto& messageItem : messages.get()) {
+        if (condition == messageItem.condition && pageLoadTime < messageItem.seconds) {
+            logDiagnosticMessage(DiagnosticLoggingKeys::telemetryPageLoadKey(), messageItem.message, ShouldSample::No);
+            logDiagnosticMessage(DiagnosticLoggingKeys::telemetryPageLoadKey(), DiagnosticLoggingKeys::occurredKey(), ShouldSample::No);
+            break;
+        }
+    }
+}
+
 } // namespace WebKit
index a4c6685..ef42a7a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010-2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2010-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -87,6 +87,7 @@
 #include <wtf/HashMap.h>
 #include <wtf/HashSet.h>
 #include <wtf/MonotonicTime.h>
+#include <wtf/Optional.h>
 #include <wtf/ProcessID.h>
 #include <wtf/Ref.h>
 #include <wtf/RefPtr.h>
@@ -1720,6 +1721,8 @@ private:
     void didRemoveAttachment(const String& identifier);
 #endif
 
+    void reportPageLoadResult(const WebCore::ResourceError& = { });
+
     PageClient& m_pageClient;
     Ref<API::PageConfiguration> m_configuration;
 
@@ -2118,6 +2121,8 @@ private:
 
     HashMap<String, Ref<WebURLSchemeHandler>> m_urlSchemeHandlersByScheme;
     HashMap<uint64_t, Ref<WebURLSchemeHandler>> m_urlSchemeHandlersByIdentifier;
+
+    std::optional<MonotonicTime> m_pageLoadStart;
 };
 
 } // namespace WebKit